Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d07e9f8c10
commit
3c3db9066b
|
|
@ -66,7 +66,7 @@ export default ({ editorAiActions = [] } = {}) => {
|
|||
reportAbusePath: notesDataset.reportAbusePath,
|
||||
newCommentTemplatePaths: JSON.parse(notesDataset.newCommentTemplatePaths),
|
||||
resourceGlobalId: convertToGraphQLId(noteableData.noteableType, noteableData.id),
|
||||
editorAiActions: editorAiActions.map((factory) => factory(noteableData)),
|
||||
legacyEditorAiActions: editorAiActions.map((factory) => factory(noteableData)),
|
||||
newCustomEmojiPath: notesDataset.newCustomEmojiPath,
|
||||
},
|
||||
data() {
|
||||
|
|
|
|||
|
|
@ -96,6 +96,11 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
defaultBranch: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -131,6 +136,13 @@ export default {
|
|||
selectedRefFallback;
|
||||
}
|
||||
|
||||
const defaultBranchData = branches.find((branch) => branch.name === this.defaultBranch);
|
||||
// since the API for getting list of branches is paginated, we might not have a
|
||||
// default branch available, so in that case we add it to the list
|
||||
if (this.defaultBranch && !defaultBranchData) {
|
||||
branches.push({ name: this.defaultBranch, value: this.defaultBranch, default: true });
|
||||
}
|
||||
|
||||
return formatListBoxItems({ branches, tags, commits, selectedRef });
|
||||
},
|
||||
branches() {
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@ export default {
|
|||
data-testid="ref-dropdown-container"
|
||||
:project-id="projectId"
|
||||
:value="refSelectorValue"
|
||||
:default-branch="rootRef"
|
||||
use-symbolic-ref-names
|
||||
:query-params="refSelectorQueryParams"
|
||||
@input="onInput"
|
||||
|
|
|
|||
|
|
@ -139,6 +139,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
editorAiActions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -362,6 +367,7 @@ export default {
|
|||
:data-uploads-path="uploadsPath"
|
||||
>
|
||||
<markdown-header
|
||||
:editor-ai-actions="editorAiActions"
|
||||
:preview-markdown="previewMarkdown"
|
||||
:line-content="lineContent"
|
||||
:can-suggest="canSuggest"
|
||||
|
|
|
|||
|
|
@ -55,12 +55,17 @@ export default {
|
|||
newCommentTemplatePaths: {
|
||||
default: () => [],
|
||||
},
|
||||
editorAiActions: { default: () => [] },
|
||||
mrGeneratedContent: { default: null },
|
||||
canSummarizeChanges: { default: false },
|
||||
canUseComposer: { default: false },
|
||||
legacyEditorAiActions: { default: () => [] },
|
||||
},
|
||||
props: {
|
||||
editorAiActions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
previewMarkdown: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
|
|
@ -123,6 +128,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
const modifierKey = getModifierKey();
|
||||
|
||||
return {
|
||||
tag: '> ',
|
||||
suggestPopoverVisible: false,
|
||||
|
|
@ -138,6 +144,12 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
aiActions() {
|
||||
if (this.editorAiActions.length > 0) {
|
||||
return this.editorAiActions;
|
||||
}
|
||||
return this.legacyEditorAiActions;
|
||||
},
|
||||
commentTemplatePaths() {
|
||||
return this.newCommentTemplatePaths.length > 0
|
||||
? this.newCommentTemplatePaths
|
||||
|
|
@ -181,6 +193,17 @@ export default {
|
|||
totalHighlights: this.findAndReplace.totalMatchCount,
|
||||
});
|
||||
},
|
||||
previewToggleTooltip() {
|
||||
return sprintf(
|
||||
this.previewMarkdown
|
||||
? s__('MarkdownEditor|Continue editing (%{shiftKey}%{modifierKey}P)')
|
||||
: s__('MarkdownEditor|Preview (%{shiftKey}%{modifierKey}P)'),
|
||||
{
|
||||
shiftKey: this.shiftKey,
|
||||
modifierKey: this.modifierKey,
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
showSuggestPopover() {
|
||||
|
|
@ -518,8 +541,10 @@ export default {
|
|||
>
|
||||
<gl-button
|
||||
v-if="enablePreview"
|
||||
v-gl-tooltip
|
||||
data-testid="preview-toggle"
|
||||
:value="previewMarkdown ? 'preview' : 'edit'"
|
||||
:title="previewToggleTooltip"
|
||||
:label="$options.i18n.previewTabTitle"
|
||||
class="js-md-preview-button gl-flex-row-reverse gl-items-center !gl-font-normal"
|
||||
size="small"
|
||||
|
|
@ -572,10 +597,10 @@ export default {
|
|||
</div>
|
||||
</template>
|
||||
<div class="gl-flex gl-gap-y-2">
|
||||
<div v-if="!previewMarkdown && editorAiActions.length" class="gl-flex gl-gap-y-2">
|
||||
<div v-if="!previewMarkdown && aiActions.length" class="gl-flex gl-gap-y-2">
|
||||
<header-divider v-if="!previewMarkdown" />
|
||||
<ai-actions-dropdown
|
||||
:actions="editorAiActions"
|
||||
:actions="aiActions"
|
||||
@input="insertAIAction"
|
||||
@replace="replaceTextarea"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -143,6 +143,11 @@ export default {
|
|||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
editorAiActions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
let editingMode;
|
||||
|
|
@ -399,6 +404,7 @@ export default {
|
|||
:new-comment-template-paths="newCommentTemplatePaths"
|
||||
:can-attach-file="!disableAttachments"
|
||||
:can-suggest="codeSuggestionsConfig.canSuggest"
|
||||
:editor-ai-actions="editorAiActions"
|
||||
:line="codeSuggestionsConfig.line"
|
||||
:lines="codeSuggestionsConfig.lines"
|
||||
:show-suggest-popover="codeSuggestionsConfig.showPopover"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
autocompleteDataSources,
|
||||
markdownPreviewPath,
|
||||
} from '~/work_items/utils';
|
||||
import projectPermissionsQuery from '../graphql/ai_permissions_for_project.query.graphql';
|
||||
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
|
||||
import workItemDescriptionTemplateQuery from '../graphql/work_item_description_template.query.graphql';
|
||||
import { i18n, NEW_WORK_ITEM_IID, TRACKING_CATEGORY_SHOW, ROUTES } from '../constants';
|
||||
|
|
@ -39,11 +40,6 @@ export default {
|
|||
WorkItemDescriptionTemplateListbox,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
provide: {
|
||||
editorAiActions: window.gon?.licensed_features?.generateDescription
|
||||
? [generateDescriptionAction()]
|
||||
: [],
|
||||
},
|
||||
inject: ['isGroup'],
|
||||
props: {
|
||||
description: {
|
||||
|
|
@ -137,12 +133,19 @@ export default {
|
|||
descriptionTemplate: null,
|
||||
appliedTemplate: '',
|
||||
showTemplateApplyWarning: false,
|
||||
workspacePermissions: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
createFlow() {
|
||||
return this.workItemId === newWorkItemId(this.newWorkItemType);
|
||||
},
|
||||
editorAiActions() {
|
||||
const { id, userPermissions } = this.workspacePermissions;
|
||||
return userPermissions?.generateDescription
|
||||
? [generateDescriptionAction({ resourceId: id })]
|
||||
: [];
|
||||
},
|
||||
workItemFullPath() {
|
||||
return this.createFlow
|
||||
? newWorkItemFullPath(this.fullPath, this.newWorkItemType)
|
||||
|
|
@ -343,6 +346,25 @@ export default {
|
|||
this.$emit('error', s__('WorkItem|Unable to find selected template.'));
|
||||
},
|
||||
},
|
||||
workspacePermissions: {
|
||||
query() {
|
||||
return projectPermissionsQuery;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.fullPath,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return data.workspace || {};
|
||||
},
|
||||
skip() {
|
||||
return this.isGroup;
|
||||
},
|
||||
error(error) {
|
||||
Sentry.captureException(error);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
checkForConflicts() {
|
||||
|
|
@ -545,6 +567,7 @@ export default {
|
|||
:autocomplete-data-sources="autocompleteDataSources"
|
||||
:restricted-tool-bar-items="restrictedToolBarItems"
|
||||
:uploads-path="uploadsPath"
|
||||
:editor-ai-actions="editorAiActions"
|
||||
enable-autocomplete
|
||||
supports-quick-actions
|
||||
:autofocus="autofocus"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
query projectGenerateDescriptionPermissions($fullPath: ID!) {
|
||||
workspace: project(fullPath: $fullPath) {
|
||||
id
|
||||
userPermissions {
|
||||
generateDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1097,7 +1097,7 @@ module Ci
|
|||
|
||||
def debug_mode?
|
||||
# perform the check on both sides in case the runner version is old
|
||||
metadata&.debug_trace_enabled? ||
|
||||
debug_trace_enabled? ||
|
||||
Gitlab::Utils.to_boolean(variables['CI_DEBUG_SERVICES']&.value, default: false) ||
|
||||
Gitlab::Utils.to_boolean(variables['CI_DEBUG_TRACE']&.value, default: false)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -30,14 +30,22 @@ module Ci
|
|||
@predicate_type = "https://slsa.dev/provenance/v1"
|
||||
end
|
||||
|
||||
def as_json(options = nil)
|
||||
json = super
|
||||
exceptions = ["_type"]
|
||||
json.deep_transform_keys do |k|
|
||||
next k if exceptions.include?(k)
|
||||
def deep_change_case(json)
|
||||
exceptions = %w[_type variables]
|
||||
|
||||
k.camelize(:lower)
|
||||
new_json = {}
|
||||
json.each do |key, value|
|
||||
key = key.camelize(:lower) if exceptions.exclude?(key)
|
||||
value = deep_change_case(value) if value.is_a?(Hash) && exceptions.exclude?(key)
|
||||
|
||||
new_json[key] = value
|
||||
end
|
||||
|
||||
new_json
|
||||
end
|
||||
|
||||
def as_json(options = nil)
|
||||
deep_change_case(super)
|
||||
end
|
||||
|
||||
def attributes
|
||||
|
|
@ -52,7 +60,7 @@ module Ci
|
|||
def self.from_build(build)
|
||||
# TODO: update buildType as part of https://gitlab.com/gitlab-org/gitlab/-/issues/426764
|
||||
build_type = "https://gitlab.com/gitlab-org/gitlab/-/issues/546150"
|
||||
external_parameters = { variables: build.variables.map(&:key) }
|
||||
external_parameters = ExternalParameters.from_build(build)
|
||||
internal_parameters = {
|
||||
architecture: build.runner_manager.architecture,
|
||||
executor: build.runner_manager.executor_type,
|
||||
|
|
@ -94,6 +102,28 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
class ExternalParameters
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_accessor :source, :entry_point, :variables
|
||||
|
||||
def self.from_build(build)
|
||||
source = Gitlab::Routing.url_helpers.project_url(build.project)
|
||||
entry_point = build.name
|
||||
|
||||
variables = {}
|
||||
build.variables.each do |variable|
|
||||
variables[variable.key] = if variable.masked?
|
||||
'[MASKED]'
|
||||
else
|
||||
variable.value
|
||||
end
|
||||
end
|
||||
|
||||
ExternalParameters.new(source: source, entry_point: entry_point, variables: variables)
|
||||
end
|
||||
end
|
||||
|
||||
class Builder
|
||||
include ActiveModel::Model
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,14 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: Update this logic when column `p_ci_builds.debug_trace_enabled` is added.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/194954#note_2574776849.
|
||||
def debug_trace_enabled?
|
||||
return true if degenerated?
|
||||
|
||||
metadata&.debug_trace_enabled?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def read_metadata_attribute(legacy_key, metadata_key, default_value = nil)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,11 @@ namespace :admin do
|
|||
|
||||
resource :system_info, controller: 'system_info', only: [:show]
|
||||
|
||||
resources :projects, only: [:index]
|
||||
resources :projects, only: [:index] do
|
||||
collection do
|
||||
get :active, :inactive, to: 'projects#index'
|
||||
end
|
||||
end
|
||||
|
||||
resources :usage_trends, only: :index
|
||||
resource :dev_ops_reports, controller: 'dev_ops_report', only: :show
|
||||
|
|
|
|||
|
|
@ -10,4 +10,5 @@ description: Routing table for CI runner managers
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/168131
|
||||
milestone: '17.6'
|
||||
gitlab_schema: gitlab_ci_cell_local
|
||||
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/460084
|
||||
table_size: small
|
||||
|
|
|
|||
|
|
@ -10,4 +10,5 @@ description: Routing table for CI runners
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/166308
|
||||
milestone: '17.6'
|
||||
gitlab_schema: gitlab_ci_cell_local
|
||||
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442395
|
||||
table_size: small
|
||||
|
|
|
|||
|
|
@ -50,6 +50,6 @@ Automatically sync Jira issues to GitLab to unlock VSA metrics tracking. Real-ti
|
|||
|
||||
[Agentic Workflow: Apply Coding Style Guide](duo_workflow/duo_workflow_codestyle.md)
|
||||
|
||||
## Compliance and Best Practice
|
||||
## Compliance and Best Practices
|
||||
|
||||
[Guide on Separation of Duties](guide_on_sod.md)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ The GitLab approach to implementing SoD through Role-Based Access Control (RBAC)
|
|||
|
||||
### Role-Based Access Control (RBAC)
|
||||
|
||||
TRBAC forms the framework for implementing and enforcing SoD. It governs permissions and responsibilities across the platform, ensuring compliance with the principles of least privilege. Through RBAC, organizations can:
|
||||
RBAC forms the framework for implementing and enforcing SoD. It governs permissions and responsibilities across the platform, ensuring compliance with the principles of least privilege. Through RBAC, organizations can:
|
||||
|
||||
- Implement holistic user management with granular role-based controls
|
||||
- Assign roles with the least privileged access principles
|
||||
|
|
|
|||
|
|
@ -20,20 +20,88 @@ title: Static reachability analysis
|
|||
|
||||
{{< /history >}}
|
||||
|
||||
Static reachability analysis (SRA) helps you prioritize remediation of vulnerabilities in dependencies.
|
||||
Static reachability analysis (SRA) helps you prioritize remediation of vulnerabilities in
|
||||
dependencies. SRA identifies which dependencies your application actually uses. While dependency
|
||||
scanning finds all vulnerable dependencies, SRA focuses on those that are reachable and pose higher
|
||||
security risks, helping you prioritize remediation based on actual threat exposure.
|
||||
|
||||
An application is generally deployed with many dependencies. Dependency scanning identifies which of
|
||||
those dependencies have vulnerabilities. However, not all dependencies are used by an application.
|
||||
Static reachability analysis identifies those dependencies that are used, in other words reachable,
|
||||
and so are a higher security risk than others. Use this information to help prioritize remediation
|
||||
of vulnerabilities according to risk.
|
||||
## Getting started
|
||||
|
||||
If you are new to static reachability analysis, the following steps show how to enable it for your
|
||||
project.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Only Python projects are supported.
|
||||
- [Dependency Scanning analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/dependency-scanning)
|
||||
version 0.23.0 and later.
|
||||
- Enable [Dependency Scanning by using SBOM](dependency_scanning_sbom/_index.md#configuration).
|
||||
[Gemnasium](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium) analyzers are not
|
||||
supported.
|
||||
|
||||
Follow the [pip](dependency_scanning_sbom/_index.md#pip) or
|
||||
[pipenv](dependency_scanning_sbom/_index.md#pipenv)
|
||||
related instructions for dependency scanning using SBOM. You can also use any other Python package
|
||||
manager that is
|
||||
[supported](https://gitlab.com/gitlab-org/security-products/analyzers/dependency-scanning#supported-files)
|
||||
by the DS analyzer.
|
||||
|
||||
Exclusions:
|
||||
|
||||
- SRA cannot be used together with either a scan execution policy or pipeline execution policy.
|
||||
|
||||
To enable SRA:
|
||||
|
||||
- On the left sidebar, select **Search or go to** and find your project.
|
||||
- Edit the `.gitlab-ci.yml` file, and add one of the following.
|
||||
|
||||
If you're using the CI/CD template, add the following (ensure there is only one `variables:`
|
||||
line):
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
DS_STATIC_REACHABILITY_ENABLED: true
|
||||
```
|
||||
|
||||
If you're using the [Dependency Scanning component](https://gitlab.com/components/dependency-scanning),
|
||||
add the following (ensuring there is only one `include:` line.):
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- component: ${CI_SERVER_FQDN}/components/dependency-scanning/main@0
|
||||
inputs:
|
||||
enable_static_reachability: true
|
||||
rules:
|
||||
- if: $CI_SERVER_HOST == "gitlab.com"
|
||||
```
|
||||
|
||||
At this point, SRA is enabled in your pipeline. When dependency scanning runs and outputs an SBOM,
|
||||
the results are supplemented by static reachability analysis.
|
||||
|
||||
## Understanding the results
|
||||
|
||||
To identify vulnerable dependencies that are reachable, either:
|
||||
|
||||
- Hover over the **Severity** value of a vulnerability in the vulnerability report.
|
||||
- Check the `Reachable` value in the vulnerability page.
|
||||
- In the vulnerability report, hover over the **Severity** value of a vulnerability.
|
||||
- In a vulnerability's details page, check the **Reachable** value.
|
||||
- Use a GraphQL query to list those vulnerabilities that are reachable.
|
||||
|
||||
A dependency can have one of the following reachability values:
|
||||
|
||||
Yes
|
||||
: The package linked to this vulnerability is confirmed reachable in code.
|
||||
|
||||
Not Found
|
||||
: SRA ran successfully but did not detect usage of the vulnerable package. If a vulnerable
|
||||
dependency's reachability value is shown as **Not Found** exercise caution rather than completely
|
||||
dismissing it, because the beta version of SRA may produce false negatives.
|
||||
|
||||
Not Available
|
||||
: SRA was not executed, so no reachability data exists.
|
||||
|
||||
When a direct dependency is marked as **in use**, all its transitive dependencies are also marked as
|
||||
**in use**.
|
||||
|
||||
## Supported languages and package managers
|
||||
|
||||
Static reachability analysis is available only for Python projects. SRA uses the new dependency
|
||||
|
|
@ -43,140 +111,20 @@ scanning analyzer to generate SBOMs and so supports the same package managers as
|
|||
|----------|----------------------------|
|
||||
| Python | `pip`, `pipenv`, `poetry`, `uv` |
|
||||
|
||||
## Enable static reachability analysis
|
||||
## Running SRA in an offline environment
|
||||
|
||||
Enable static reachability analysis to identify high-risk dependencies.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Enable [Dependency Scanning by using SBOM](dependency_scanning_sbom/_index.md#getting-started).
|
||||
|
||||
Make sure you follow the [pip](dependency_scanning_sbom/_index.md#pip) or [pipenv](dependency_scanning_sbom/_index.md#pipenv)
|
||||
related instructions for dependency scanning using SBOM. You can also use any other Python package manager that is [supported](https://gitlab.com/gitlab-org/security-products/analyzers/dependency-scanning#supported-files) by the DS analyzer.
|
||||
|
||||
To enable static reachability analysis from GitLab 18.0 and later:
|
||||
|
||||
- Set the CI/CD variable `DS_STATIC_REACHABILITY_ENABLED` to `true`
|
||||
|
||||
Static reachability is integrated into the `dependency-scanning` job of the latest Dependency-Scanning template.
|
||||
Alternatively you can enable Static Reachability by including the [Dependency Scanning component](https://gitlab.com/components/dependency-scanning) rather than using the standard Dependency-Scanning template.
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- component: ${CI_SERVER_FQDN}/components/dependency-scanning/main@0
|
||||
inputs:
|
||||
enable_static_reachability: true
|
||||
rules:
|
||||
- if: $CI_SERVER_HOST == "gitlab.com"
|
||||
```
|
||||
|
||||
Please notice that to use GitLab.com components on a GitLab Self-Managed instance, you [must mirror](../../../ci/components/_index.md#use-a-gitlabcom-component-on-gitlab-self-managed) the component project.
|
||||
|
||||
Static reachability analysis functionality is supported in [Dependency Scanning analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/dependency-scanning) version `0.23.0` and all subsequent versions.
|
||||
|
||||
<details><summary>If you are using GitLab 17.11 follow these instructions to enable Static Reachability Analysis</summary>
|
||||
|
||||
- Make sure you extend `dependency-scanning-with-reachability` needs section to depend on the build job that creates the artifact required by the DS analyzer.
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
|
||||
include:
|
||||
- template: Jobs/Dependency-Scanning.latest.gitlab-ci.yml
|
||||
|
||||
variables:
|
||||
DS_STATIC_REACHABILITY_ENABLED: true
|
||||
DS_ENFORCE_NEW_ANALYZER: true
|
||||
|
||||
# create job required by the DS analyzer to create pipdeptree.json
|
||||
# https://docs.gitlab.com/user/application_security/dependency_scanning/dependency_scanning_sbom/#pip
|
||||
create:
|
||||
stage: build
|
||||
image: "python:latest"
|
||||
script:
|
||||
- "pip install -r requirements.txt"
|
||||
- "pip install pipdeptree"
|
||||
- "pipdeptree --json > pipdeptree.json"
|
||||
artifacts:
|
||||
when: on_success
|
||||
access: developer
|
||||
paths: ["**/pipdeptree.json"]
|
||||
|
||||
dependency-scanning-with-reachability:
|
||||
needs:
|
||||
- job: gitlab-static-reachability
|
||||
optional: true
|
||||
artifacts: true
|
||||
- job: create
|
||||
optional: true
|
||||
artifacts: true
|
||||
```
|
||||
|
||||
Static reachability in 17.11 introduces two key jobs:
|
||||
|
||||
- `gitlab-static-reachability`: Performs Static Reachability Analysis (SRA) on your Python files.
|
||||
- `dependency-scanning-with-reachability`: Executes dependency scanning and generates an SBOM report enriched with reachability data. This job requires the artifact output from the `gitlab-static-reachability` job.
|
||||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
When you enable static reachability feature for non-Python projects, the
|
||||
`gitlab-static-reachability` job will fail but won't break your pipeline, because it's configured to
|
||||
allow failures. In such cases, the `dependency-scanning-with-reachability` job will perform standard
|
||||
dependency scanning without adding reachability data to the SBOM.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
</details>
|
||||
To use the dependency scanning component in an offline environment, you must first
|
||||
[mirror the component project](../../../ci/components/_index.md#use-a-gitlabcom-component-on-gitlab-self-managed).
|
||||
|
||||
## How static reachability analysis works
|
||||
|
||||
SRA (Static reachability analysis) identifies dependencies used in a project's code and marks them and their dependencies as reachable.
|
||||
Dependency scanning generates an SBOM report that identifies all components and their transitive
|
||||
dependencies. Static reachability analysis checks each dependency in the SBOM report and adds a
|
||||
reachability value to the SBOM report. The enriched SBOM is then ingested by the GitLab instance.
|
||||
|
||||
The following are marked as not found:
|
||||
|
||||
- Dependencies that are found in the project's lock files but are not imported in the code.
|
||||
- Tools that are included in the project's lock files for local usage but are not imported in the code.
|
||||
For example, tools such as coverage testing or linting packages are marked as not found even if used locally.
|
||||
|
||||
SRA requires two key components:
|
||||
|
||||
- Dependency scanning (DS): Generates an SBOM report that identifies all components and their transitive dependencies.
|
||||
- GitLab Advanced SAST (GLAS): Performs static reachability analysis to provide a report showing direct dependencies usage in the codebase.
|
||||
|
||||
SRA adds reachability data to the SBOM output by dependency scanning. The enriched SBOM is then ingested by the GitLab instance.
|
||||
|
||||
Reachability data in the UI can have one of the following values:
|
||||
|
||||
| Reachability values | Description |
|
||||
|---------------------|---------------------------------------------------------------------------|
|
||||
| Yes | The package linked to this vulnerability is confirmed reachable in code |
|
||||
| Not Found | SRA ran successfully but did not detect usage of the vulnerable package |
|
||||
| Not Available | SRA was not executed, therefore no reachability data exists |
|
||||
|
||||
## Where to find the reachability data
|
||||
|
||||
The reachability data is available in the vulnerability report
|
||||
|
||||

|
||||
|
||||
and the vulnerability page
|
||||
|
||||

|
||||
|
||||
Finally reachability data can be reached using GraphQL.
|
||||
|
||||
{{< alert type="warning" >}}
|
||||
|
||||
When a vulnerability reachability value shows as "Not Found," exercise caution rather than completely dismissing it, because the beta version of SRA may produce false negatives.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
## Restrictions
|
||||
|
||||
Static reachability analysis has the following limitations:
|
||||
|
||||
- When a direct dependency is marked as `in use`, all its transitive dependencies are also marked as `in use`.
|
||||
- Requires the new [dependency scanning analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/dependency-scanning). [Gemnasium](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium) analyzers are not supported.
|
||||
- SRA on beta is not supported in combination with Scan and Pipeline execution policies
|
||||
- Tools that are included in the project's lock files for local usage but are not imported in the
|
||||
code. For example, tools such as coverage testing or linting packages are marked as not found even
|
||||
if used locally.
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ To install the Helm chart for the GitLab workspaces proxy:
|
|||
|
||||
helm upgrade --install gitlab-workspaces-proxy \
|
||||
gitlab-workspaces-proxy/gitlab-workspaces-proxy \
|
||||
--version=0.1.19 \
|
||||
--version=0.1.20 \
|
||||
--namespace="gitlab-workspaces" \
|
||||
--set="ingress.enabled=true" \
|
||||
--set="ingress.hosts[0].host=${GITLAB_WORKSPACES_PROXY_DOMAIN}" \
|
||||
|
|
|
|||
|
|
@ -4471,6 +4471,9 @@ msgstr ""
|
|||
msgid "AdminSelfHostedModels|There was an error saving the self-hosted model. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminSelfHostedModels|This model cannot be applied to all %{mainFeature} sub-features"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminSelfHostedModels|This self-hosted model cannot be deleted"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -8187,6 +8190,9 @@ msgstr ""
|
|||
msgid "Apply this approval rule to all branches or a specific protected branch."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply to all button"
|
||||
msgstr ""
|
||||
|
||||
msgid "Applying"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -23358,6 +23364,9 @@ msgstr ""
|
|||
msgid "DuoAgenticChat|GitLab Duo Agentic Chat"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoAgentsPlatform|Agent Flow"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoAgentsPlatform|Agents"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -23397,9 +23406,6 @@ msgstr ""
|
|||
msgid "DuoAgentsPlatform|Prompt"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoAgentsPlatform|Prompt is unavailable"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoAgentsPlatform|Run an Agent Flow"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37381,6 +37387,9 @@ msgstr ""
|
|||
msgid "MarkdownEditor|Close find and replace bar"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownEditor|Continue editing (%{shiftKey}%{modifierKey}P)"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownEditor|Find and replace"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37405,6 +37414,9 @@ msgstr ""
|
|||
msgid "MarkdownEditor|Outdent line (%{modifier_key}[)"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownEditor|Preview (%{shiftKey}%{modifierKey}P)"
|
||||
msgstr ""
|
||||
|
||||
msgid "MarkdownEditor|header"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40011,6 +40023,9 @@ msgstr ""
|
|||
msgid "ModelSelection|Successfully updated %{mainFeature} / %{title}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ModelSelection|Successfully updated all %{mainFeature} features"
|
||||
msgstr ""
|
||||
|
||||
msgid "Modified"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ spec/frontend/sidebar/components/confidential/confidentiality_dropdown_spec.js
|
|||
spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js
|
||||
spec/frontend/sidebar/components/milestone/milestone_dropdown_spec.js
|
||||
spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js
|
||||
spec/frontend/snippets/components/snippet_description_edit_spec.js
|
||||
spec/frontend/super_sidebar/components/sidebar_portal_spec.js
|
||||
spec/frontend/super_sidebar/components/user_menu_spec.js
|
||||
spec/frontend/tooltips/index_spec.js
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ fi
|
|||
|
||||
if [ -z "${GITLAB_WORKSPACES_PROXY_HELM_CHART_VERSION}" ]; then
|
||||
echo "GITLAB_WORKSPACES_PROXY_HELM_CHART_VERSION is not explicitly set. Using default."
|
||||
GITLAB_WORKSPACES_PROXY_HELM_CHART_VERSION="0.1.19"
|
||||
GITLAB_WORKSPACES_PROXY_HELM_CHART_VERSION="0.1.20"
|
||||
fi
|
||||
|
||||
if [ -z "${GITLAB_WORKSPACES_PROXY_HELM_RELEASE_NAMESPACE}" ]; then
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ FactoryBot.define do
|
|||
external_parameters do
|
||||
{
|
||||
repository: "https://gitlab.com/tanuki/hello-world",
|
||||
ref: "refs/heads/main"
|
||||
ref: "refs/heads/main",
|
||||
variables: { CI_PIPELINE: "test", ANOTHER_UPPERCASED_VAR: "test" }
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ describe('projects/settings/components/default_branch_selector', () => {
|
|||
it('displays a RefSelector component', () => {
|
||||
expect(findRefSelector().props()).toEqual({
|
||||
disabled,
|
||||
defaultBranch: null,
|
||||
value: persistedDefaultBranch,
|
||||
enabledRefTypes: [REF_TYPE_BRANCHES],
|
||||
projectId,
|
||||
|
|
|
|||
|
|
@ -886,4 +886,23 @@ describe('Ref selector component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('default branch handling', () => {
|
||||
const defaultBranchName = 'my-branch';
|
||||
|
||||
beforeEach(() => {
|
||||
branchesApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_OK, []]); // Mock branches API without the default branch
|
||||
});
|
||||
|
||||
it('adds default branch to dropdown when not in initial API response', async () => {
|
||||
createComponent({ propsData: { defaultBranch: defaultBranchName } });
|
||||
await waitForRequests();
|
||||
|
||||
const defaultBranchItem = findBranchDropdownItems().filter(
|
||||
(item) => item.text().includes(defaultBranchName) && item.text().includes('default'),
|
||||
);
|
||||
|
||||
expect(defaultBranchItem.length).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ const defaultMockRoute = {
|
|||
},
|
||||
};
|
||||
|
||||
const mockRootRef = 'root-ref';
|
||||
|
||||
describe('HeaderArea', () => {
|
||||
let wrapper;
|
||||
|
||||
|
|
@ -50,7 +52,9 @@ describe('HeaderArea', () => {
|
|||
const createComponent = ({
|
||||
props = {},
|
||||
route = { name: 'blobPathDecoded' },
|
||||
provided = {},
|
||||
provided = {
|
||||
rootRef: mockRootRef,
|
||||
},
|
||||
} = {}) => {
|
||||
return shallowMountExtended(HeaderArea, {
|
||||
provide: {
|
||||
|
|
@ -92,7 +96,7 @@ describe('HeaderArea', () => {
|
|||
|
||||
describe('Ref selector', () => {
|
||||
it('renders correctly', () => {
|
||||
expect(findRefSelector().exists()).toBe(true);
|
||||
expect(findRefSelector().props('defaultBranch')).toBe(mockRootRef);
|
||||
});
|
||||
|
||||
it('renders correctly when branch names ending with .json', () => {
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Snippet Description Edit component rendering matches the snapshot 1`] = `
|
||||
<div
|
||||
class="form-group"
|
||||
>
|
||||
<label
|
||||
for="reference-0"
|
||||
>
|
||||
Description (optional)
|
||||
</label>
|
||||
<div
|
||||
class="gfm-form js-vue-markdown-field md-area position-relative"
|
||||
data-uploads-path=""
|
||||
>
|
||||
<markdown-header-stub
|
||||
data-testid="markdownHeader"
|
||||
enablepreview="true"
|
||||
linecontent=""
|
||||
markdownpreviewpath="foo/"
|
||||
newcommenttemplatepathsprop=""
|
||||
restrictedtoolbaritems=""
|
||||
suggestionstartindex="0"
|
||||
uploadspath=""
|
||||
/>
|
||||
<div
|
||||
class="md-write-holder"
|
||||
>
|
||||
<div
|
||||
class="div-dropzone-wrapper zen-backdrop"
|
||||
>
|
||||
<div
|
||||
class="div-dropzone js-invalid-dropzone"
|
||||
>
|
||||
<textarea
|
||||
aria-label="Description"
|
||||
class="!gl-min-h-0 js-autosize js-gfm-input js-gfm-input-initialized markdown-area note-textarea"
|
||||
data-supports-quick-actions="false"
|
||||
data-testid="snippet-description-field"
|
||||
dir="auto"
|
||||
id="reference-0"
|
||||
placeholder="Describe what your snippet does or how to use it…"
|
||||
rows="3"
|
||||
style="overflow-x: hidden; word-wrap: break-word;"
|
||||
/>
|
||||
<div
|
||||
class="div-dropzone-hover"
|
||||
>
|
||||
<svg
|
||||
class="div-dropzone-icon s24"
|
||||
>
|
||||
<use
|
||||
xlink:href="undefined#paperclip"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
aria-label="Exit full screen"
|
||||
class="btn-default-tertiary btn-icon btn-sm gl-button js-zen-leave zen-control zen-control-leave"
|
||||
href="#"
|
||||
title="Exit full screen"
|
||||
>
|
||||
<gl-icon-stub
|
||||
name="minimize"
|
||||
size="24"
|
||||
variant="subtle"
|
||||
/>
|
||||
</a>
|
||||
<markdown-toolbar-stub
|
||||
canattachfile="true"
|
||||
markdowndocspath="help/"
|
||||
showcommenttoolbar="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="gl-px-5 js-vue-md-preview md-preview-holder"
|
||||
style="display: none;"
|
||||
>
|
||||
<div
|
||||
class="md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -27,10 +27,6 @@ describe('Snippet Description Edit component', () => {
|
|||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
it('matches the snapshot', () => {
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the description field', () => {
|
||||
createComponent('');
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import WorkItemDescriptionTemplatesListbox from '~/work_items/components/work_it
|
|||
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
|
||||
import workItemDescriptionTemplateQuery from '~/work_items/graphql/work_item_description_template.query.graphql';
|
||||
import projectPermissionsQuery from '~/work_items/graphql/ai_permissions_for_project.query.graphql';
|
||||
import { autocompleteDataSources, markdownPreviewPath, newWorkItemId } from '~/work_items/utils';
|
||||
import { ROUTES, NEW_WORK_ITEM_IID, NEW_WORK_ITEM_GID } from '~/work_items/constants';
|
||||
import {
|
||||
|
|
@ -60,6 +61,22 @@ describe('WorkItemDescription', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const mockWorkspacePermissionsResponse = {
|
||||
data: {
|
||||
workspace: {
|
||||
id: 'gid://gitlab/Project/1',
|
||||
userPermissions: {
|
||||
generateDescription: true,
|
||||
},
|
||||
__typename: 'Project',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockWorkspacePermissionsHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockWorkspacePermissionsResponse);
|
||||
|
||||
const mockFullPath = 'test-project-path';
|
||||
|
||||
const createComponent = async ({
|
||||
|
|
@ -81,6 +98,7 @@ describe('WorkItemDescription', () => {
|
|||
routeQuery = {},
|
||||
fullPath = mockFullPath,
|
||||
hideFullscreenMarkdownButton = false,
|
||||
workspacePermissionsHandler = mockWorkspacePermissionsHandler,
|
||||
} = {}) => {
|
||||
router = {
|
||||
replace: jest.fn(),
|
||||
|
|
@ -91,6 +109,7 @@ describe('WorkItemDescription', () => {
|
|||
[workItemByIidQuery, workItemResponseHandler],
|
||||
[updateWorkItemMutation, mutationHandler],
|
||||
[workItemDescriptionTemplateQuery, descriptionTemplateHandler],
|
||||
[projectPermissionsQuery, workspacePermissionsHandler],
|
||||
]),
|
||||
propsData: {
|
||||
fullPath,
|
||||
|
|
@ -798,4 +817,103 @@ describe('WorkItemDescription', () => {
|
|||
expect(findMarkdownEditor().props('restrictedToolBarItems')).toEqual(['full-screen']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('workspacePermissions query', () => {
|
||||
it('is not called for groups', async () => {
|
||||
const workspacePermissionsHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockWorkspacePermissionsResponse);
|
||||
|
||||
await createComponent({
|
||||
isGroup: true,
|
||||
isEditing: true,
|
||||
workspacePermissionsHandler,
|
||||
});
|
||||
|
||||
expect(workspacePermissionsHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('is called for projects', async () => {
|
||||
const workspacePermissionsHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockWorkspacePermissionsResponse);
|
||||
|
||||
await createComponent({
|
||||
isGroup: false,
|
||||
isEditing: true,
|
||||
workspacePermissionsHandler,
|
||||
});
|
||||
|
||||
expect(workspacePermissionsHandler).toHaveBeenCalledWith({
|
||||
fullPath: mockFullPath,
|
||||
});
|
||||
});
|
||||
|
||||
describe('user has generateDescription permission', () => {
|
||||
beforeEach(async () => {
|
||||
const workspacePermissionsHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockWorkspacePermissionsResponse);
|
||||
|
||||
await createComponent({
|
||||
isGroup: false,
|
||||
isEditing: true,
|
||||
workspacePermissionsHandler,
|
||||
});
|
||||
});
|
||||
|
||||
it('passes editorAiActions prop to MarkdownEditor', () => {
|
||||
const editorAiActions = findMarkdownEditor().props('editorAiActions');
|
||||
expect(editorAiActions).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('user does not have generateDescription permission', () => {
|
||||
beforeEach(async () => {
|
||||
const workspacePermissionsHandlerNoPermission = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
workspace: {
|
||||
id: 'gid://gitlab/Project/1',
|
||||
userPermissions: {
|
||||
generateDescription: false,
|
||||
},
|
||||
__typename: 'Project',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await createComponent({
|
||||
isGroup: false,
|
||||
isEditing: true,
|
||||
workspacePermissionsHandler: workspacePermissionsHandlerNoPermission,
|
||||
});
|
||||
});
|
||||
|
||||
it('passes empty editorAiActions prop to MarkdownEditor', () => {
|
||||
const editorAiActions = findMarkdownEditor().props('editorAiActions');
|
||||
expect(editorAiActions).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when workspace is null', () => {
|
||||
beforeEach(async () => {
|
||||
const workspacePermissionsHandlerNullWorkspace = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
workspace: null,
|
||||
},
|
||||
});
|
||||
|
||||
await createComponent({
|
||||
isGroup: false,
|
||||
isEditing: true,
|
||||
workspacePermissionsHandler: workspacePermissionsHandlerNullWorkspace,
|
||||
});
|
||||
});
|
||||
|
||||
it('passes empty editorAiActions prop to MarkdownEditor', () => {
|
||||
const editorAiActions = findMarkdownEditor().props('editorAiActions');
|
||||
expect(editorAiActions).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5342,13 +5342,24 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
|
|||
it { is_expected.to eq false }
|
||||
end
|
||||
|
||||
context 'when metadata does not exist' do
|
||||
context 'when metadata does not exist but job is not degenerated' do
|
||||
before do
|
||||
build.metadata.destroy!
|
||||
# Very old jobs populated this column instead of metadata
|
||||
build.update_column(:options, { my_config: 'value' })
|
||||
build.metadata.delete
|
||||
build.reload
|
||||
end
|
||||
|
||||
it { is_expected.to eq false }
|
||||
end
|
||||
|
||||
context 'when job is degenerated' do
|
||||
before do
|
||||
build.degenerate!
|
||||
end
|
||||
|
||||
it { is_expected.to eq true }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#drop_with_exit_code!' do
|
||||
|
|
|
|||
|
|
@ -102,15 +102,45 @@ RSpec.describe Ci::Slsa::ProvenanceStatement, type: :model, feature_category: :c
|
|||
expect(subject[0]['digest']['sha256']).to eq('3d4a07bcbf2eaec380ad707451832924bee1197fbdf43d20d6d4bc96c8284268')
|
||||
end
|
||||
|
||||
it 'has the correct predicate build definition' do
|
||||
build_definition = parsed['predicate']['buildDefinition']
|
||||
context 'when a build definition is generated' do
|
||||
let(:build_definition) { parsed['predicate']['buildDefinition'] }
|
||||
|
||||
# TODO: update buildType as part of https://gitlab.com/gitlab-org/gitlab/-/issues/426764
|
||||
expect(build_definition['buildType']).to eq('https://gitlab.com/gitlab-org/gitlab/-/issues/546150')
|
||||
expect(build_definition['externalParameters']['variables']).to include("GITLAB_CI")
|
||||
expect(build_definition['internalParameters']['name']).to start_with("My runner")
|
||||
it 'has the correct predicate build definition' do
|
||||
# TODO: update buildType as part of https://gitlab.com/gitlab-org/gitlab/-/issues/426764
|
||||
expect(build_definition['buildType']).to eq('https://gitlab.com/gitlab-org/gitlab/-/issues/546150')
|
||||
expect(build_definition['internalParameters']['name']).to start_with("My runner")
|
||||
expect(build_definition['resolvedDependencies'].length).to eq(1)
|
||||
end
|
||||
|
||||
expect(build_definition['resolvedDependencies'].length).to eq(1)
|
||||
it 'has the correct external parameters' do
|
||||
statement_variables = build_definition['externalParameters']['variables']
|
||||
expect(statement_variables).to be_an_instance_of(Hash)
|
||||
expect(statement_variables.length).to eq(build.variables.to_a.length)
|
||||
|
||||
non_masked = build.variables.filter { |variable| !variable.masked? }.map(&:key)
|
||||
masked = build.variables.filter(&:masked?).map(&:key)
|
||||
|
||||
expect(non_masked.length).to be > 1
|
||||
expect(masked.length).to be > 1
|
||||
|
||||
non_masked.each do |variable|
|
||||
expect(statement_variables[variable]).to eq(build.variables[variable].value)
|
||||
end
|
||||
|
||||
masked.each do |variable|
|
||||
expect(statement_variables[variable]).to eq("[MASKED]")
|
||||
end
|
||||
end
|
||||
|
||||
it 'has the right entry point' do
|
||||
entry_point = build_definition['externalParameters']['entryPoint']
|
||||
expect(entry_point).to eq('test')
|
||||
end
|
||||
|
||||
it 'has the right source' do
|
||||
source = build_definition['externalParameters']['source']
|
||||
expect(source).to eq(Gitlab::Routing.url_helpers.project_url(build.project))
|
||||
end
|
||||
end
|
||||
|
||||
it 'has the correct run details' do
|
||||
|
|
@ -159,4 +189,27 @@ RSpec.describe Ci::Slsa::ProvenanceStatement, type: :model, feature_category: :c
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deep_change_case' do
|
||||
subject(:provenance_statement) { create(:provenance_statement) }
|
||||
|
||||
it 'camelizes fields appropriately' do
|
||||
expect(parsed).to include('predicateType')
|
||||
expect(parsed).to include('predicate')
|
||||
end
|
||||
|
||||
it 'does not camelize exceptions' do
|
||||
expect(parsed).to include('_type')
|
||||
end
|
||||
|
||||
it 'camelizes recursively' do
|
||||
expect(parsed['predicate']).to include('buildDefinition')
|
||||
expect(parsed['predicate']['buildDefinition']).to include('buildType')
|
||||
expect(parsed['predicate']['buildDefinition']).to include('externalParameters')
|
||||
end
|
||||
|
||||
it 'does not recurse through exception keys' do
|
||||
expect(parsed['predicate']['buildDefinition']['externalParameters']['variables']).to include('CI_PIPELINE')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -63,6 +63,14 @@ RSpec.describe Admin::ProjectsController, "routing" do
|
|||
expect(get("/admin/projects")).to route_to('admin/projects#index')
|
||||
end
|
||||
|
||||
it "to #active" do
|
||||
expect(get("/admin/projects/active")).to route_to('admin/projects#index')
|
||||
end
|
||||
|
||||
it "to #inactive" do
|
||||
expect(get("/admin/projects/inactive")).to route_to('admin/projects#index')
|
||||
end
|
||||
|
||||
it "to #show" do
|
||||
expect(get("/admin/projects/gitlab/gitlab-ce")).to route_to('admin/projects#show', namespace_id: 'gitlab', id: 'gitlab-ce')
|
||||
expect(get("/admin/projects/gitlab/subgroup/gitlab-ce")).to route_to('admin/projects#show', namespace_id: 'gitlab/subgroup', id: 'gitlab-ce')
|
||||
|
|
|
|||
Loading…
Reference in New Issue