Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-02-28 06:12:01 +00:00
parent 58c37fa91f
commit cddb9394be
26 changed files with 669 additions and 178 deletions

View File

@ -0,0 +1,138 @@
<!--The purpose of this issue template is to ensure that Product Designers have all the information they need BEFORE starting design (workflow::ready for design). Product Designers should collaborate with the team members requesting design work to ensure the request is fully thought through and that all necessary details to meet users' needs are included.
For example:
- Who's the user?
- What are they trying to accomplish?
- Why do they need this?, etc.-->
## Problem and scope
<!--To be filled out by designer in collaboration with requestor (if different from team counterparts), PM and Engineering counterparts-->
<details>
<summary>See details</summary>
### What is the problem to solve?
`{ Add a brief description about the problem to solve for }`
### Who is the design solution for?
`{ Add persona and/or job performer }`
### What is the [Job](https://handbook.gitlab.com/handbook/product/ux/jobs-to-be-done/#main-job-what-is-the-job-performer-trying-to-get-done) this user is trying to achieve?
- ...
### What [outcomes](https://handbook.gitlab.com/handbook/product/ux/jobs-to-be-done/outcome-driven-innovation-pilot/topics-and-definitions/#outcomes) is this design solution helping them achieve?
If you have measured Outcome data, put that in the table. If not, delete the table and add the Outcomes to be designed for in a bulleted list.
| Outcome Statement | Importance | Satisfaction | Overall Score |
|-------------------|------------|--------------|---------------|
| `{ e.g. Minimize the time it takes for users to navigate to the desired section of the application. }` | `5` | `1` | `9` |
### What are the requirements necessary to solve for this problem and Outcomes?
`{ Create a bulleted list of everything that will need to be addressed in the holistic Design Vision in order to fully solve for the problem and Outcomes. Work with your counterparts to define this list. Keep it solution agnostic and try to understand any technical constraints that each requirement may imply. Only remove a requirement due to a technical constraint if it's not technically feasible to build within a reasonable amount of time (1-3 milestones is reasonable, anything longer than that you'll have to decide as a team how important it is to keep it in scope or address it in parallel in later iterations). }`
* ...
* ...
* ...
### What supporting research or customer validation do we have?
- `{ Add links to any supporting research and related problem validation issues }`
### What is the timeline?
`{ Add milestone or link to planning issue that clarifies when the design must be ready for the Build phase by }`
### What are the technical constraints?
`{ `:warning:` This is to understand initial constraints in which the design solution needs to work within, NOT whether the solution can be implemented in a given milestone.`
``Once the Product Designer has come up with a holistic Design Vision, or an ideal state for solving the problem, they should collaborate with their team members and engineers to continue the technical feasibility discussion during ``~"workflow::planning breakdown"``. }``
- `{ e.g. All visualization must use [eCharts](https://echarts.apache.org/examples/en/index.html#chart-type-line) }`
- `{ e.g. Data prior to 2024-10-10 will not be available }`
- `{ e.g. Solution will only be visible to Maintainer roles }`
### In what parts of GitLab will this solution be available?
Plans:
* [ ] Free
* [ ] Premium
* [ ] Ultimate
Instances:
* [ ] Self-managed
* [ ] Dedicated
* [ ] GitLab.com
Levels:
* [ ] Instance
* [ ] Group
* [ ] Project
### How will we know if the solution is successful?
`{ If you don't have measured Outcome data to measure against, what other success metrics can we use? Otherwise you can reference the Outcomes table above (your goal is to beat the previous Outcome measurements Satisfaction numbers with this new design). }`
</details>
## Ready for design
<details>
<summary>See checklist</summary>
<!--*** Note for Product Designer:
- Do not begin designing until you are confident you have everything you need to begin designing.
- Do not default to designing a small MVC first. Instead, think about the ideal solution / holistic Design Vision first.-->
* [ ] The problem has been defined and is well understood
* [ ] Who the design solution is for has been defined
* [ ] User goals and outcomes have been defined
* [ ] Supporting research has been reviewed and linked
* [ ] The product requirements have been defined, and the scope has been agreed upon
* [ ] Success metrics have been defined and agreed upon
* [ ] I, as the Product Designer, believe I have all the information I need to begin creating a design solution
* [ ] Move this issue to \~"workflow::ready for design" or ~"workflow::design" :tada:
* [ ] (Optional) Help improve this issue template, [view feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/519682)
</details>
## Proposal
<details>
<summary>See checklist reminder</summary>
* [ ] Follow the [Product design process](https://handbook.gitlab.com/handbook/product/ux/product-designer/#ideate-and-iterate)
* [ ] [Start with a long-term vision](https://handbook.gitlab.com/handbook/values/#start-with-a-long-term-vision)
* [ ] Remember to link your video walkthrough, prototypes, Figma project
</details>
- :tv: Walkthrough
- :frame_photo: Design Solution Proposal
- ❖ Figma project →
## Design breakdown
Once the proposal is agreed upon, work with your team to break it down into buildable parts (MVC, Iteration 1, Iteration 2, etc... until fully built).
<details>
<summary>See checklist</summary>
* [ ] Design Vision broken down into MVC and follow-up iterations based on their ability to stand alone and provide value to the user
* [ ] Create MVC and other necessary Iteration 1, Iteration 2... issues and add them as Linked items to this issue
* [ ] Include all necessary requirements, and specs needed to create the designs for each broken down issue
</details>
/label ~"UX" ~"UX Template Applied" ~"workflow::start"

View File

@ -674,7 +674,6 @@ RSpec/FeatureCategory:
- 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_ci_builds_metric_spec.rb'
- 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_users_deployment_approvals_spec.rb'
- 'ee/spec/lib/gitlab/usage/metrics/instrumentations/epics_deepest_relationship_level_metric_spec.rb'
- 'ee/spec/lib/gitlab/usage/metrics/instrumentations/license_management_jobs_metric_spec.rb'
- 'ee/spec/lib/gitlab/usage/metrics/instrumentations/protected_environment_approval_rules_required_approvals_average_metric_spec.rb'
- 'ee/spec/lib/gitlab/usage/metrics/instrumentations/protected_environments_required_approvals_average_metric_spec.rb'
- 'ee/spec/lib/gitlab/usage/metrics/instrumentations/user_cap_setting_enabled_metric_spec.rb'

View File

@ -1 +1 @@
efced52dc4e4f9f202e32dc6239573d4aceb4d4e
e947561ab2240a27ca13c4339e2cdd70115fc3ff

View File

@ -0,0 +1,204 @@
<script>
import { GlButton, GlFormGroup, GlFormInput, GlAlert, GlSprintf, GlLink, GlIcon } from '@gitlab/ui';
import csrf from '~/lib/utils/csrf';
import SafeHtml from '~/vue_shared/directives/safe_html';
import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue';
export default {
components: {
GlButton,
GlFormGroup,
GlFormInput,
GlAlert,
GlSprintf,
GlLink,
GlIcon,
MultiStepFormTemplate,
},
directives: {
SafeHtml,
},
inject: {
backButtonPath: {
type: String,
required: true,
},
namespaceId: {
type: String,
required: false,
default: null,
},
messageAdmin: {
type: String,
required: false,
default: '',
},
isCiCdOnly: {
type: Boolean,
required: true,
},
isConfigured: {
type: Boolean,
required: true,
},
buttonAuthHref: {
type: String,
required: false,
default: '',
},
formPath: {
type: String,
required: true,
},
},
computed: {
showAuthButton() {
return !this.isCiCdOnly && this.isConfigured;
},
showAdminMessage() {
return !this.isCiCdOnly && !this.isConfigured;
},
},
csrf,
placeholders: {
token: '8d3f016698e...',
},
};
</script>
<template>
<form method="post" :action="formPath">
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
<input
id="namespace_id"
type="hidden"
name="namespace_id"
autocomplete="off"
:value="namespaceId"
/>
<multi-step-form-template
:title="s__('ProjectsNew|Authenticate with GitHub')"
:current-step="3"
:steps-total="4"
>
<template #form>
<template v-if="showAuthButton">
<gl-button
category="primary"
variant="confirm"
icon="github"
:href="buttonAuthHref"
data-testid="github-auth-button"
>
{{ s__('ProjectsNew|Authenticate through GitHub') }}
</gl-button>
<div class="gl-my-6 gl-flex gl-items-center gl-gap-4">
<div class="gl-border-t gl-w-full"></div>
<div class="gl-text-primary">{{ __('or') }}</div>
<div class="gl-border-t gl-w-full"></div>
</div>
</template>
<gl-alert v-if="showAdminMessage" class="gl-mb-5" :dismissible="false">
<span v-safe-html="messageAdmin"></span>
</gl-alert>
<gl-form-group :label="__('Use personal access token')" label-for="personal_access_token">
<gl-form-input
id="personal_access_token"
name="personal_access_token"
type="password"
required
:placeholder="$options.placeholders.token"
/>
<template #description>
<p class="gl-mb-3">
<gl-sprintf
:message="
s__(
'GithubImport|Create and provide your GitHub %{linkStart}personal access token%{linkEnd}.',
)
"
>
<template #link="{ content }">
<gl-link href="https://github.com/settings/tokens" target="_blank">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</p>
<p class="gl-mb-3">
{{
s__(
'GithubImport|Use a classic GitHub personal access token with the following scopes:',
)
}}
</p>
<ul>
<li v-if="isCiCdOnly" class="gl-mb-3">
<gl-sprintf
:message="
s__(
'GithubImporter|%{codeStart}repo%{codeEnd}: Used to display a list of your public and private repositories that are available to connect to.',
)
"
>
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</li>
<li v-if="!isCiCdOnly" class="gl-mb-3">
<gl-sprintf
:message="
s__(
'GithubImporter|%{codeStart}repo%{codeEnd}: Used to display a list of your public and private repositories that are available to import from.',
)
"
>
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</li>
<li v-if="!isCiCdOnly" class="gl-mb-3">
<gl-sprintf
:message="
s__(
'GithubImporter|%{codeStart}read:org%{codeEnd} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files.',
)
"
>
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</li>
</ul>
<gl-sprintf :message="s__('GithubImport|%{linkStart}Learn more%{linkEnd}.')">
<template #link="{ content }">
<gl-link
href="https://github.com/settings/tokens#use-a-github-personal-access-token"
target="_blank"
>
{{ content }}
<gl-icon name="external-link" :aria-label="__('(external link)')" />
</gl-link>
</template>
</gl-sprintf>
</template>
</gl-form-group>
</template>
<template #back>
<gl-button category="primary" variant="default" :href="backButtonPath">
{{ __('Go back') }}
</gl-button>
</template>
<template #next>
<gl-button type="submit" category="primary" variant="confirm">
{{ __('Next step') }}
</gl-button>
</template>
</multi-step-form-template>
</form>
</template>

View File

@ -0,0 +1,23 @@
import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import ImportFromGithubApp from './import_from_github_app.vue';
export function initGitHubImportProjectForm() {
const el = document.getElementById('js-vue-import-github-project-app');
if (!el) {
return null;
}
const { viewModel } = el.dataset;
const provide = JSON.parse(viewModel);
return new Vue({
el,
name: 'ImportFromGitHubRoot',
provide: convertObjectPropsToCamelCase(provide),
render(createElement) {
return createElement(ImportFromGithubApp);
},
});
}

View File

@ -1,3 +1,8 @@
import { initGitHubImportProjectForm } from '~/import/github';
import { initPersonalAccessTokenFormValidation } from './init_personal_access_token_form_validation';
initPersonalAccessTokenFormValidation();
if (gon.features.newProjectCreationForm) {
initGitHubImportProjectForm();
} else {
initPersonalAccessTokenFormValidation();
}

View File

@ -183,7 +183,6 @@ module Ci
scope :with_live_trace, -> { where_exists(Ci::BuildTraceChunk.scoped_build) }
scope :with_stale_live_trace, -> { with_live_trace.finished_before(12.hours.ago) }
scope :finished_before, ->(date) { finished.where('finished_at < ?', date) }
scope :license_management_jobs, -> { where(name: %i[license_management license_scanning]) } # handle license rename https://gitlab.com/gitlab-org/gitlab/issues/8911
# WARNING: This scope could lead to performance implications for large size of tables `ci_builds` and ci_runners`.
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123131
scope :with_runner_type, ->(runner_type) { joins(:runner).where(runner: { runner_type: runner_type }) }
@ -1343,7 +1342,14 @@ module Ci
end
def track_ci_build_created_event
Gitlab::InternalEvents.track_event('create_ci_build', project: project, user: user)
Gitlab::InternalEvents.track_event(
'create_ci_build',
project: project,
user: user,
additional_properties: {
property: name
}
)
end
def partition_id_prefix_in_16_bit_encode

View File

@ -21,8 +21,7 @@ module HasUserType
llm_bot: 14,
placeholder: 15,
duo_code_review_bot: 16,
import_user: 17,
ci_pipeline_bot: 18
import_user: 17
}.with_indifferent_access.freeze
BOT_USER_TYPES = %w[
@ -39,7 +38,6 @@ module HasUserType
service_account
llm_bot
duo_code_review_bot
ci_pipeline_bot
].freeze
# `service_account` allows instance/namespaces to configure a user for external integrations/automations

View File

@ -1260,7 +1260,7 @@ class Repository
def get_patch_id(old_revision, new_revision)
raw_repository.get_patch_id(old_revision, new_revision)
rescue Gitlab::Git::CommandError, Gitlab::Git::Repository::NoRepository => e
rescue Gitlab::Git::CommandError, Gitlab::Git::Repository::NoRepository, Gitlab::Git::CommandTimedOut => e
# This is expected when there are no differences between the old_revision and the new_revision.
# It's not ideal, but is simpler to handle this here than making breaking changes to gitaly.
return if e.message.match?(/no difference between old and new revision./)

View File

@ -3,50 +3,61 @@
- header_title _("New project"), new_project_path
- add_to_breadcrumbs s_('ProjectsNew|Import project'), new_project_path(anchor: 'import_project')
= render ::Layouts::PageHeadingComponent.new('') do |c|
- c.with_heading do
.gl-flex.gl-gap-3.gl-items-center
= sprite_icon('github', size: 32)
= title
- c.with_description do
= import_github_authorize_message
- if !has_ci_cd_only_params?
.gl-mt-5
- if github_import_configured?
= render Pajamas::ButtonComponent.new(variant: :confirm,
href: status_import_github_path(namespace_id: params[:namespace_id]),
icon: 'github') do
= title
- else
= render Pajamas::AlertComponent.new(variant: :info, dismissible: false) do |c|
- c.with_body do
= import_configure_github_admin_message
- if Feature.enabled?(:new_project_creation_form, @user)
#js-vue-import-github-project-app{ data: { view_model: Gitlab::Json.generate({
back_button_path: new_project_path(anchor: 'import_project'),
namespace_id: namespace_id_from(params),
message_admin: import_configure_github_admin_message,
is_ci_cd_only: has_ci_cd_only_params?,
is_configured: github_import_configured?,
button_auth_href: status_import_github_path(namespace_id: params[:namespace_id]),
form_path: personal_access_token_import_github_path
}) } }
- else
= render ::Layouts::PageHeadingComponent.new('') do |c|
- c.with_heading do
.gl-flex.gl-gap-3.gl-items-center
= sprite_icon('github', size: 32)
= title
- c.with_description do
= import_github_authorize_message
- if !has_ci_cd_only_params?
.gl-mt-5
- if github_import_configured?
= render Pajamas::ButtonComponent.new(variant: :confirm,
href: status_import_github_path(namespace_id: params[:namespace_id]),
icon: 'github') do
= title
- else
= render Pajamas::AlertComponent.new(variant: :info, dismissible: false) do |c|
- c.with_body do
= import_configure_github_admin_message
= form_tag personal_access_token_import_github_path, method: :post, class: 'gl-mt-3' do
.form-group.gl-form-group
%label.col-form-label{ for: 'personal_access_token' }= _('Personal access token')
= hidden_field_tag(:namespace_id, params[:namespace_id])
= password_field_tag :personal_access_token, '', class: 'form-control gl-form-input js-import-github-pat-field', placeholder: _('e.g. %{token}') % { token: '8d3f016698e...' }, data: { testid: 'personal-access-token-field' }
%p.invalid-feedback.js-import-github-pat-validation
= _('Personal access token is required.')
%span.form-text.gl-text-subtle
- code_pair = tag_pair(tag.code, :code_start, :code_end)
- github_link_tag_pair = tag_pair(link_to('', 'https://github.com/settings/tokens', target: '_blank', rel: 'noopener noreferrer'), :link_start, :link_end)
= safe_format(s_('GithubImport|Create and provide your GitHub %{link_start}personal access token%{link_end}.'), github_link_tag_pair)
%br
= safe_format(s_('GithubImport|Use a classic GitHub personal access token with the following scopes:'))
%ul
- if has_ci_cd_only_params?
%li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to connect to.'), code_pair)
- else
%li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to import from.'), code_pair)
%li= safe_format(s_('GithubImporter|%{code_start}read:org%{code_end} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files.'), code_pair)
- docs_link = link_to('', help_page_path('user/project/import/github.md', anchor: 'use-a-github-personal-access-token'), target: '_blank', rel: 'noopener noreferrer')
- docs_link_tag_pair = tag_pair(docs_link, :link_start, :link_end)
= safe_format(s_('GithubImport|%{link_start}Learn more%{link_end}.'), docs_link_tag_pair)
= form_tag personal_access_token_import_github_path, method: :post, class: 'gl-mt-3' do
.form-group.gl-form-group
%label.col-form-label{ for: 'personal_access_token' }= _('Personal access token')
= hidden_field_tag(:namespace_id, params[:namespace_id])
= password_field_tag :personal_access_token, '', class: 'form-control gl-form-input js-import-github-pat-field', placeholder: _('e.g. %{token}') % { token: '8d3f016698e...' }, data: { testid: 'personal-access-token-field' }
%p.invalid-feedback.js-import-github-pat-validation
= _('Personal access token is required.')
%span.form-text.gl-text-subtle
- code_pair = tag_pair(tag.code, :code_start, :code_end)
- github_link_tag_pair = tag_pair(link_to('', 'https://github.com/settings/tokens', target: '_blank', rel: 'noopener noreferrer'), :link_start, :link_end)
= safe_format(s_('GithubImport|Create and provide your GitHub %{link_start}personal access token%{link_end}.'), github_link_tag_pair)
%br
= safe_format(s_('GithubImport|Use a classic GitHub personal access token with the following scopes:'))
%ul
- if has_ci_cd_only_params?
%li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to connect to.'), code_pair)
- else
%li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to import from.'), code_pair)
%li= safe_format(s_('GithubImporter|%{code_start}read:org%{code_end} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files.'), code_pair)
- docs_link = link_to('', help_page_path('user/project/import/github.md', anchor: 'use-a-github-personal-access-token'), target: '_blank', rel: 'noopener noreferrer')
- docs_link_tag_pair = tag_pair(docs_link, :link_start, :link_end)
= safe_format(s_('GithubImport|%{link_start}Learn more%{link_end}.'), docs_link_tag_pair)
.gl-mt-5.gl-flex.gl-gap-3
= render Pajamas::ButtonComponent.new(variant: :confirm, type: :submit, button_options: { class: 'js-import-github-pat-authenticate', data: { testid: 'authenticate-button' } }) do
= _('Authenticate')
= render Pajamas::ButtonComponent.new(href: new_project_path) do
= _('Cancel')
.gl-mt-5.gl-flex.gl-gap-3
= render Pajamas::ButtonComponent.new(variant: :confirm, type: :submit, button_options: { class: 'js-import-github-pat-authenticate', data: { testid: 'authenticate-button' } }) do
= _('Authenticate')
= render Pajamas::ButtonComponent.new(href: new_project_path) do
= _('Cancel')

View File

@ -13,3 +13,6 @@ tiers:
- free
- premium
- ultimate
additional_properties:
property:
description: the name of the build

View File

@ -43626,7 +43626,6 @@ Possible types of user.
| <a id="usertypeadmin_bot"></a>`ADMIN_BOT` | Admin bot. |
| <a id="usertypealert_bot"></a>`ALERT_BOT` | Alert bot. |
| <a id="usertypeautomation_bot"></a>`AUTOMATION_BOT` | Automation bot. |
| <a id="usertypeci_pipeline_bot"></a>`CI_PIPELINE_BOT` | Ci pipeline bot. |
| <a id="usertypeduo_code_review_bot"></a>`DUO_CODE_REVIEW_BOT` | Duo code review bot. |
| <a id="usertypeghost"></a>`GHOST` | Ghost. |
| <a id="usertypehuman"></a>`HUMAN` | Human. |

View File

@ -70,11 +70,13 @@ to be available in the RubyGems index. We want to minimize external build
dependencies and build times. It's enforced by the RuboCop rule
[`Cop/GemFetcher`](https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/blob/master/lib/rubocop/cop/gem_fetcher.rb).
## No gems for importers or integrations
## No gems that make HTTP calls for importers or integrations
Do not add gems for use in importers or integrations. For more information, see:
Do not add gems that make HTTP calls in importers or integrations.
In general, other gems are also strongly discouraged from these domains.
For more information, see:
- [Integration developer guide](integrations/_index.md#do-not-add-ruby-gems)
- [Integration development guidelines](integrations/_index.md#no-ruby-gems-that-make-http-calls)
- [Principles of importer design](import/principles_of_importer_design.md#security)
## Review the new dependency for quality

View File

@ -10,9 +10,9 @@ title: Principles of Importer Design
- Uploaded files must be validated. Examples:
- [`BulkImports::FileDownloadService`](https://gitlab.com/gitlab-org/gitlab/-/blob/cd4a880cbb2bc56b3a55f14c1d8370f4385319db/app/services/bulk_imports/file_download_service.rb#L38-46)
- [`ImportExport::CommandLineUtil`](https://gitlab.com/gitlab-org/gitlab/blob/139690b3aeac69675119ce70f17f70bc1753de48/lib/gitlab/import_export/command_line_util.rb#L134)
- Importers must not add third-party Ruby gems to our `Gemfile`.
For more information, see the
[reasons](../integrations/_index.md#do-not-add-ruby-gems) given for integrations.
- Importers must not add third-party Ruby gems that make HTTP calls.
Importers use the same
[Ruby gem policy as for integrations](../integrations/_index.md#no-ruby-gems-that-make-http-calls), for more information about Ruby gem use for importers see that page.
- All HTTP calls must use `Gitlab::HTTP`.
`Gitlab::HTTP` ensures that [network settings](../../security/webhooks.md) of the instance
are enforced and has other [security hardening](../../security/webhooks.md#enforce-dns-rebinding-attack-protection) measures.

View File

@ -185,21 +185,6 @@ attribute :wiki_page_events, default: false
If an event attribute for an existing integration changes to `true`,
this requires a data migration to back-fill the attribute value for old records.
#### Do not add Ruby gems
GitLab integrations must not add Ruby gems to our `Gemfile`.
Instead, any required HTTP clients or small abstractions must be implemented in GitLab.
Gems that wrap interactions with third-party services may look convenient at first glance,
but they offer minimal benefit compared to the costs involved:
- They increase the potential surface area of security problems and the effort required to fix them.
- Often these gems make HTTP calls on your behalf. As integrations can make HTTP calls to remote
servers configured by users, it is critical that we
[fully control the network calls](#all-http-calls-must-use-gitlabhttp).
- There is a maintenance cost of managing gem upgrades.
- They can block us from using newer features.
### Security requirements
#### All HTTP calls must use `Gitlab::HTTP`
@ -225,6 +210,23 @@ def mask_configurable_channels?
end
```
## No Ruby gems that make HTTP calls
GitLab integrations must not add Ruby gems that make HTTP calls.
Other gems that add small abstractions should also not be added.
Certain utility-like gems from official sources, like `atlassian-jwt` gem can be used if required.
Gems that wrap interactions with third-party services may look convenient at first glance,
but they offer minimal benefit compared to the costs involved:
- They increase the potential surface area of security problems and the effort required to fix them.
- Often these gems make HTTP calls on your behalf. As integrations can make HTTP calls to remote
servers configured by users, it is critical that we
[fully control the network calls](#all-http-calls-must-use-gitlabhttp).
- There is a maintenance cost of managing gem upgrades.
- They can block us from using newer features.
## Define configuration test
Optionally, you can define a configuration test of an integration's settings. The test is executed from the integration form's **Test** button, and results are returned to the user.

View File

@ -85,14 +85,15 @@ module API
agent = ::Clusters::AgentsFinder.new(user_project, current_user).execute.find_by_id(params[:cluster_agent_id])
bad_request!("cluster agent doesn't exist or cannot be associated with this environment") unless agent
params[:cluster_agent] = agent
end
environment = user_project.environments.create(params)
response = ::Environments::CreateService.new(user_project, current_user, params).execute
if environment.persisted?
present environment, with: Entities::Environment, current_user: current_user
if response.success?
present response.payload[:environment], with: Entities::Environment, current_user: current_user
else
render_validation_error!(environment)
render_api_error!(response.message, 400)
end
end

View File

@ -283,7 +283,7 @@ module Gitlab
end
def bot_user_can_read_project?(user, project)
(user.project_bot? || user.ci_pipeline_bot? || user.service_account? || user.security_policy_bot?) && can_read_project?(user, project)
(user.project_bot? || user.service_account? || user.security_policy_bot?) && can_read_project?(user, project)
end
def valid_oauth_token?(token)

View File

@ -91,6 +91,7 @@ module Gitlab
push_frontend_feature_flag(:work_items_view_preference, current_user)
push_frontend_feature_flag(:search_button_top_right, current_user)
push_frontend_feature_flag(:merge_request_dashboard, current_user, type: :wip)
push_frontend_feature_flag(:new_project_creation_form, current_user, type: :wip)
end
# Exposes the state of a feature flag to the frontend code.

View File

@ -26668,6 +26668,15 @@ msgstr ""
msgid "Gitea import"
msgstr ""
msgid "GithubImporter|%{codeStart}read:org%{codeEnd} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files."
msgstr ""
msgid "GithubImporter|%{codeStart}repo%{codeEnd}: Used to display a list of your public and private repositories that are available to connect to."
msgstr ""
msgid "GithubImporter|%{codeStart}repo%{codeEnd}: Used to display a list of your public and private repositories that are available to import from."
msgstr ""
msgid "GithubImporter|%{code_start}read:org%{code_end} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files."
msgstr ""
@ -26755,9 +26764,15 @@ msgstr ""
msgid "GithubImport|\"%{repository_name}\" size (%{repository_size}) is larger than the limit of %{limit}."
msgstr ""
msgid "GithubImport|%{linkStart}Learn more%{linkEnd}."
msgstr ""
msgid "GithubImport|%{link_start}Learn more%{link_end}."
msgstr ""
msgid "GithubImport|Create and provide your GitHub %{linkStart}personal access token%{linkEnd}."
msgstr ""
msgid "GithubImport|Create and provide your GitHub %{link_start}personal access token%{link_end}."
msgstr ""
@ -46236,6 +46251,12 @@ msgstr ""
msgid "ProjectsNew|Analyze your source code for known security vulnerabilities."
msgstr ""
msgid "ProjectsNew|Authenticate through GitHub"
msgstr ""
msgid "ProjectsNew|Authenticate with GitHub"
msgstr ""
msgid "ProjectsNew|Available only for projects within groups"
msgstr ""
@ -52853,7 +52874,7 @@ msgstr ""
msgid "SecurityOrchestration|This %{namespaceType} does not contain any security policies."
msgstr ""
msgid "SecurityOrchestration|This %{namespaceType} is not linked to a security policy project"
msgid "SecurityOrchestration|This %{namespaceType} is not linked to a security policy project. Either link it to an existing project or create a new policy, which will create a new project that you can use as a security policy project. For help, see %{linkStart}policies%{linkEnd}."
msgstr ""
msgid "SecurityOrchestration|This applies to following compliance frameworks:"
@ -62650,6 +62671,9 @@ msgstr ""
msgid "Use one line per URI"
msgstr ""
msgid "Use personal access token"
msgstr ""
msgid "Use primary email (%{email})"
msgstr ""

View File

@ -136,10 +136,6 @@ FactoryBot.define do
user_type { :llm_bot }
end
trait :ci_pipeline_bot do
user_type { :ci_pipeline_bot }
end
trait :duo_code_review_bot do
user_type { :duo_code_review_bot }
end

View File

@ -0,0 +1,58 @@
import { GlAlert } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ImportFromGithubApp from '~/import/github/import_from_github_app.vue';
import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue';
describe('Import from GitHub app', () => {
let wrapper;
const createComponent = (provide = {}) => {
wrapper = shallowMountExtended(ImportFromGithubApp, {
provide: {
backButtonPath: 'https://gitlab.com',
namespaceId: '1',
messageAdmin: 'This is an admin alert.',
isCiCdOnly: false,
isConfigured: true,
buttonAuthHref: 'https://gitlab.com/submit',
formPath: 'https://gitlab.com/submit',
...provide,
},
});
};
const findMultiStepForm = () => wrapper.findComponent(MultiStepFormTemplate);
const findGithubAuthButton = () => wrapper.findByTestId('github-auth-button');
const findAlert = () => wrapper.findComponent(GlAlert);
it('renders a form', () => {
createComponent();
expect(findMultiStepForm().exists()).toBe(true);
});
describe('not a ci/cd project', () => {
it('renders a button if github is configured', () => {
createComponent();
expect(findGithubAuthButton().exists()).toBe(true);
expect(findAlert().exists()).toBe(false);
});
it('renders an alert if github is not configured', () => {
createComponent({ isConfigured: false });
expect(findGithubAuthButton().exists()).toBe(false);
expect(findAlert().exists()).toBe(true);
});
});
describe('is a ci/cd project', () => {
it('does not render a github auth button or alert', () => {
createComponent({ isCiCdOnly: true });
expect(findGithubAuthButton().exists()).toBe(false);
expect(findAlert().exists()).toBe(false);
});
});
});

View File

@ -158,14 +158,19 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
context 'when running after_commit callbacks' do
it 'tracks creation event' do
expect(Gitlab::InternalEvents).to receive(:track_event).with(
'create_ci_build',
project: project,
user: user
)
let(:name) { 'test123' }
create(:ci_build, user: user, project: project)
subject(:create_ci_build) { create(:ci_build, user: user, project: project, name: name) }
it 'tracks creation event' do
expect { create_ci_build }
.to trigger_internal_events('create_ci_build')
.with(
category: 'InternalEventTracking',
user: user,
project: project,
additional_properties: { property: name }
)
end
end
end
@ -360,26 +365,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
end
describe '.license_management_jobs' do
subject { described_class.license_management_jobs }
let!(:management_build) { create(:ci_build, :success, name: :license_management, pipeline: pipeline) }
let!(:scanning_build) { create(:ci_build, :success, name: :license_scanning, pipeline: pipeline) }
let!(:another_build) { create(:ci_build, :success, name: :another_type, pipeline: pipeline) }
it 'returns license_scanning jobs' do
is_expected.to include(scanning_build)
end
it 'returns license_management jobs' do
is_expected.to include(management_build)
end
it 'doesnt return filtered out jobs' do
is_expected.not_to include(another_build)
end
end
describe '.finished_before' do
subject { described_class.finished_before(date) }

View File

@ -14,7 +14,7 @@ RSpec.describe User, feature_category: :system_access do
expect(described_class::USER_TYPES.keys)
.to match_array(%w[human ghost alert_bot project_bot support_bot service_user security_bot
visual_review_bot migration_bot automation_bot security_policy_bot admin_bot suggested_reviewers_bot
service_account llm_bot placeholder duo_code_review_bot import_user ci_pipeline_bot])
service_account llm_bot placeholder duo_code_review_bot import_user])
expect(described_class::USER_TYPES).to include(*described_class::BOT_USER_TYPES)
expect(described_class::USER_TYPES).to include(*described_class::NON_INTERNAL_USER_TYPES)
expect(described_class::USER_TYPES).to include(*described_class::INTERNAL_USER_TYPES)

View File

@ -4075,6 +4075,30 @@ RSpec.describe Repository, feature_category: :source_code_management do
end
end
context 'when a Gitlab::Git::CommandTimedOut is raised' do
before do
expect(repository.raw_repository)
.to receive(:get_patch_id).and_raise(Gitlab::Git::CommandTimedOut)
end
it 'returns nil' do
expect(repository.get_patch_id('HEAD', 'f' * 40)).to be_nil
end
it 'reports the exception' do
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with(
instance_of(Gitlab::Git::CommandTimedOut),
project_id: repository.project.id,
old_revision: 'HEAD',
new_revision: 'HEAD'
)
repository.get_patch_id('HEAD', 'HEAD')
end
end
context 'when a Gitlab::Git::Repository::NoRepository is raised' do
before do
expect(repository.raw_repository)

View File

@ -158,73 +158,86 @@ RSpec.describe API::Environments, feature_category: :continuous_delivery do
let_it_be(:cluster_agent) { create(:cluster_agent, project: project) }
let_it_be(:foreign_cluster_agent) { create(:cluster_agent) }
it 'creates an environment with associated cluster agent' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id }
context 'when user access authorization exists' do
let_it_be(:user_access_auth) { create(:agent_user_access_project_authorization, project: project, agent: cluster_agent) }
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/environment')
expect(json_response['cluster_agent']).to be_present
it 'creates an environment with associated cluster agent' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id }
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/environment')
expect(json_response['cluster_agent']).to be_present
end
it 'creates an environment with associated kubernetes namespace' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id, kubernetes_namespace: 'flux-system' }
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/environment')
expect(json_response['cluster_agent']).to be_present
expect(json_response['kubernetes_namespace']).to eq('flux-system')
end
it 'creates an environment with associated flux resource path' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id, kubernetes_namespace: 'flux-system', flux_resource_path: 'HelmRelease/flux-system' }
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/environment')
expect(json_response['cluster_agent']).to be_present
expect(json_response['kubernetes_namespace']).to eq('flux-system')
expect(json_response['flux_resource_path']).to eq('HelmRelease/flux-system')
end
it 'fails to create environment with kubernetes namespace but no cluster agent' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", kubernetes_namespace: 'flux-system' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq(['Kubernetes namespace cannot be set without a cluster agent'])
end
it 'fails to create environment with flux resource path but no cluster agent and kubernetes namespace' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", flux_resource_path: 'HelmRelease/flux-system' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq(['Flux resource path cannot be set without a kubernetes namespace'])
end
it 'fails to create environment with flux resource path but no cluster agent' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", kubernetes_namespace: 'flux-system', flux_resource_path: 'HelmRelease/flux-system' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq(['Kubernetes namespace cannot be set without a cluster agent'])
end
it 'fails to create environment with cluster agent and flux resource path but no kubernetes namespace' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent: cluster_agent.id, flux_resource_path: 'HelmRelease/flux-system' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq(['Flux resource path cannot be set without a kubernetes namespace'])
end
it 'fails to create environment with a non existing cluster agent' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: non_existing_record_id }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq("400 Bad request - cluster agent doesn't exist or cannot be associated with this environment")
end
it 'fails to create environment with a foreign cluster agent' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: foreign_cluster_agent.id }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq("400 Bad request - cluster agent doesn't exist or cannot be associated with this environment")
end
end
it 'creates an environment with associated kubernetes namespace' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id, kubernetes_namespace: 'flux-system' }
context 'when user access authorization does not exist' do
it 'fails to create environment with associated cluster agent' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id }
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/environment')
expect(json_response['cluster_agent']).to be_present
expect(json_response['kubernetes_namespace']).to eq('flux-system')
end
it 'creates an environment with associated flux resource path' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: cluster_agent.id, kubernetes_namespace: 'flux-system', flux_resource_path: 'HelmRelease/flux-system' }
expect(response).to have_gitlab_http_status(:created)
expect(response).to match_response_schema('public_api/v4/environment')
expect(json_response['cluster_agent']).to be_present
expect(json_response['kubernetes_namespace']).to eq('flux-system')
expect(json_response['flux_resource_path']).to eq('HelmRelease/flux-system')
end
it 'fails to create environment with kubernetes namespace but no cluster agent' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", kubernetes_namespace: 'flux-system' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']['kubernetes_namespace']).to eq(['cannot be set without a cluster agent'])
end
it 'fails to create environment with flux resource path but no cluster agent and kubernetes namespace' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", flux_resource_path: 'HelmRelease/flux-system' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']['flux_resource_path']).to eq(['cannot be set without a kubernetes namespace'])
end
it 'fails to create environment with flux resource path but no cluster agent' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", kubernetes_namespace: 'flux-system', flux_resource_path: 'HelmRelease/flux-system' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']['kubernetes_namespace']).to eq(['cannot be set without a cluster agent'])
end
it 'fails to create environment with cluster agent and flux resource path but no kubernetes namespace' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent: cluster_agent.id, flux_resource_path: 'HelmRelease/flux-system' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']['flux_resource_path']).to eq(['cannot be set without a kubernetes namespace'])
end
it 'fails to create environment with a non existing cluster agent' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: non_existing_record_id }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq("400 Bad request - cluster agent doesn't exist or cannot be associated with this environment")
end
it 'fails to create environment with a foreign cluster agent' do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep", cluster_agent_id: foreign_cluster_agent.id }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq("400 Bad request - cluster agent doesn't exist or cannot be associated with this environment")
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq('Unauthorized to access the cluster agent in this project')
end
end
end

View File

@ -1097,7 +1097,6 @@
- './ee/spec/lib/gitlab/usage/metrics/instrumentations/count_users_deployment_approvals_spec.rb'
- './ee/spec/lib/gitlab/usage/metrics/instrumentations/historical_max_users_metrics_spec.rb'
- './ee/spec/lib/gitlab/usage/metrics/instrumentations/licensee_metrics_spec.rb'
- './ee/spec/lib/gitlab/usage/metrics/instrumentations/license_management_jobs_metric_spec.rb'
- './ee/spec/lib/gitlab/usage/metrics/instrumentations/license_metric_spec.rb'
- './ee/spec/lib/gitlab/usage/metrics/instrumentations/protected_environment_approval_rules_required_approvals_average_metric_spec.rb'
- './ee/spec/lib/gitlab/usage/metrics/instrumentations/protected_environments_required_approvals_average_metric_spec.rb'