Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6364c14cc1
commit
d755061465
70 changed files with 985 additions and 570 deletions
|
@ -1,13 +1,9 @@
|
|||
<script>
|
||||
import { GlIcon, GlPopover } from '@gitlab/ui';
|
||||
import { __, sprintf } from '../../../locale';
|
||||
import popover from '../../../vue_shared/directives/popover';
|
||||
import { MAX_TITLE_LENGTH, MAX_BODY_LENGTH } from '../../constants';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
popover,
|
||||
},
|
||||
components: {
|
||||
GlIcon,
|
||||
GlPopover,
|
||||
|
|
|
@ -1,14 +1,29 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
|
||||
import {
|
||||
GlDropdown,
|
||||
GlDropdownDivider,
|
||||
GlDropdownItem,
|
||||
GlFormGroup,
|
||||
GlFormInput,
|
||||
GlIcon,
|
||||
GlModal,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import lockState from '../graphql/mutations/lock_state.mutation.graphql';
|
||||
import unlockState from '../graphql/mutations/unlock_state.mutation.graphql';
|
||||
import removeState from '../graphql/mutations/remove_state.mutation.graphql';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownDivider,
|
||||
GlDropdownItem,
|
||||
GlFormGroup,
|
||||
GlFormInput,
|
||||
GlIcon,
|
||||
GlModal,
|
||||
GlSprintf,
|
||||
},
|
||||
props: {
|
||||
state: {
|
||||
|
@ -19,20 +34,59 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
showRemoveModal: false,
|
||||
removeConfirmText: '',
|
||||
};
|
||||
},
|
||||
i18n: {
|
||||
downloadJSON: s__('Terraform|Download JSON'),
|
||||
lock: s__('Terraform|Lock'),
|
||||
modalBody: s__(
|
||||
'Terraform|You are about to remove the State file %{name}. This will permanently delete all the State versions and history. The infrastructure provisioned previously will remain intact, only the state file with all its versions are to be removed. This action is non-revertible.',
|
||||
),
|
||||
modalCancel: s__('Terraform|Cancel'),
|
||||
modalHeader: s__('Terraform|Are you sure you want to remove the Terraform State %{name}?'),
|
||||
modalInputLabel: s__(
|
||||
'Terraform|To remove the State file and its versions, type %{name} to confirm:',
|
||||
),
|
||||
modalRemove: s__('Terraform|Remove'),
|
||||
remove: s__('Terraform|Remove state file and versions'),
|
||||
unlock: s__('Terraform|Unlock'),
|
||||
},
|
||||
computed: {
|
||||
cancelModalProps() {
|
||||
return {
|
||||
text: this.$options.i18n.modalCancel,
|
||||
attributes: [],
|
||||
};
|
||||
},
|
||||
disableModalSubmit() {
|
||||
return this.removeConfirmText !== this.state.name;
|
||||
},
|
||||
primaryModalProps() {
|
||||
return {
|
||||
text: this.$options.i18n.modalRemove,
|
||||
attributes: [{ disabled: this.disableModalSubmit }, { variant: 'danger' }],
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hideModal() {
|
||||
this.showRemoveModal = false;
|
||||
this.removeConfirmText = '';
|
||||
},
|
||||
lock() {
|
||||
this.stateMutation(lockState);
|
||||
},
|
||||
unlock() {
|
||||
this.stateMutation(unlockState);
|
||||
},
|
||||
remove() {
|
||||
if (!this.disableModalSubmit) {
|
||||
this.hideModal();
|
||||
this.stateMutation(removeState);
|
||||
}
|
||||
},
|
||||
stateMutation(mutation) {
|
||||
this.loading = true;
|
||||
this.$apollo
|
||||
|
@ -83,6 +137,56 @@ export default {
|
|||
<gl-dropdown-item v-else data-testid="terraform-state-lock" @click="lock">
|
||||
{{ $options.i18n.lock }}
|
||||
</gl-dropdown-item>
|
||||
|
||||
<gl-dropdown-divider />
|
||||
|
||||
<gl-dropdown-item data-testid="terraform-state-remove" @click="showRemoveModal = true">
|
||||
{{ $options.i18n.remove }}
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
|
||||
<gl-modal
|
||||
:modal-id="`terraform-state-actions-remove-modal-${state.name}`"
|
||||
:visible="showRemoveModal"
|
||||
:action-primary="primaryModalProps"
|
||||
:action-cancel="cancelModalProps"
|
||||
@ok="remove"
|
||||
@cancel="hideModal"
|
||||
@close="hideModal"
|
||||
@hide="hideModal"
|
||||
>
|
||||
<template #modal-title>
|
||||
<gl-sprintf :message="$options.i18n.modalHeader">
|
||||
<template #name>
|
||||
<span>{{ state.name }}</span>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</template>
|
||||
|
||||
<p>
|
||||
<gl-sprintf :message="$options.i18n.modalBody">
|
||||
<template #name>
|
||||
<span>{{ state.name }}</span>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
|
||||
<gl-form-group>
|
||||
<template #label>
|
||||
<gl-sprintf :message="$options.i18n.modalInputLabel">
|
||||
<template #name>
|
||||
<code>{{ state.name }}</code>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</template>
|
||||
<gl-form-input
|
||||
:id="`terraform-state-remove-input-${state.name}`"
|
||||
ref="input"
|
||||
v-model="removeConfirmText"
|
||||
type="text"
|
||||
@keyup.enter="remove"
|
||||
/>
|
||||
</gl-form-group>
|
||||
</gl-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
mutation removeState($stateID: TerraformStateID!) {
|
||||
terraformStateDelete(input: { id: $stateID }) {
|
||||
errors
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
/**
|
||||
* Helper to user bootstrap popover in vue.js.
|
||||
* Follow docs for html attributes: https://getbootstrap.com/docs/3.3/javascript/#static-popover
|
||||
*
|
||||
* @example
|
||||
* import popover from 'vue_shared/directives/popover.js';
|
||||
* {
|
||||
* directives: [popover]
|
||||
* }
|
||||
* <a v-popover="{options}">popover</a>
|
||||
*/
|
||||
export default {
|
||||
bind(el, binding) {
|
||||
$(el).popover(binding.value);
|
||||
},
|
||||
|
||||
unbind(el) {
|
||||
$(el).popover('dispose');
|
||||
},
|
||||
};
|
|
@ -1,111 +0,0 @@
|
|||
.popover {
|
||||
max-width: $popover-max-width;
|
||||
border: 1px solid $gray-100;
|
||||
box-shadow: $popover-box-shadow;
|
||||
font-size: $gl-font-size-small;
|
||||
|
||||
/**
|
||||
* Blue popover variation
|
||||
*/
|
||||
&.blue {
|
||||
background-color: $blue-600;
|
||||
border-color: $blue-600;
|
||||
|
||||
.popover-body {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&.bs-popover-bottom {
|
||||
.arrow::before,
|
||||
.arrow::after {
|
||||
border-bottom-color: $blue-600;
|
||||
}
|
||||
}
|
||||
|
||||
&.bs-popover-top {
|
||||
.arrow::before,
|
||||
.arrow::after {
|
||||
border-top-color: $blue-600;
|
||||
}
|
||||
}
|
||||
|
||||
&.bs-popover-right {
|
||||
.arrow::after,
|
||||
.arrow::before {
|
||||
border-right-color: $blue-600;
|
||||
}
|
||||
}
|
||||
|
||||
&.bs-popover-left {
|
||||
.arrow::before,
|
||||
.arrow::after {
|
||||
border-left-color: $blue-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bs-popover-top {
|
||||
/* When popover position is top, the arrow is translated 1 pixel
|
||||
* due to the box-shadow include in our custom styles.
|
||||
*/
|
||||
> .arrow::before {
|
||||
border-top-color: $gray-100;
|
||||
bottom: 1px;
|
||||
}
|
||||
|
||||
> .arrow::after {
|
||||
bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.bs-popover-bottom {
|
||||
> .arrow::before {
|
||||
border-bottom-color: $gray-100;
|
||||
}
|
||||
|
||||
> .popover-header::before {
|
||||
border-color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.bs-popover-right > .arrow::before {
|
||||
border-right-color: $gray-100;
|
||||
}
|
||||
|
||||
.bs-popover-left > .arrow::before {
|
||||
border-left-color: $gray-100;
|
||||
}
|
||||
|
||||
.popover-header {
|
||||
background-color: $white;
|
||||
font-size: $gl-font-size-small;
|
||||
}
|
||||
|
||||
.popover-body {
|
||||
padding: $gl-padding $gl-padding-12;
|
||||
|
||||
> .popover-hr {
|
||||
margin: $gl-padding 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mr_popover component
|
||||
*/
|
||||
.mr-popover {
|
||||
.text-secondary {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
}
|
||||
}
|
||||
|
||||
.suggest-gitlab-ci-yml {
|
||||
margin-top: -1em;
|
||||
|
||||
.popover-header {
|
||||
padding: $gl-padding;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
|
@ -146,7 +146,11 @@
|
|||
}
|
||||
|
||||
@mixin green-status-color {
|
||||
@include status-color($green-100, $green-500, $green-700);
|
||||
@include status-color(
|
||||
var(--green-100, $green-100),
|
||||
var(--green-500, $green-500),
|
||||
var(--green-700, $green-700)
|
||||
);
|
||||
}
|
||||
|
||||
@mixin fade($gradient-direction, $gradient-color) {
|
||||
|
@ -254,9 +258,9 @@
|
|||
@mixin build-trace-bar($height) {
|
||||
height: $height;
|
||||
min-height: $height;
|
||||
background: $gray-light;
|
||||
border: 1px solid $border-color;
|
||||
color: $gl-text-color;
|
||||
background: var(--gray-50, $gray-50);
|
||||
border: 1px solid var(--border-color, $border-color);
|
||||
color: var(--gl-text-color, $gl-text-color);
|
||||
padding: $grid-size;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
position: absolute;
|
||||
top: 48%;
|
||||
left: -$length;
|
||||
border-top: 2px solid $border-color;
|
||||
border-top: 2px solid var(--border-color, $border-color);
|
||||
width: $length;
|
||||
height: 1px;
|
||||
}
|
||||
|
@ -14,14 +14,14 @@
|
|||
display: inline-block;
|
||||
padding: 8px 10px 9px;
|
||||
width: 100%;
|
||||
border: 1px solid $border-color;
|
||||
border: 1px solid var(--border-color, $border-color);
|
||||
border-radius: $border-radius;
|
||||
background-color: $white;
|
||||
background-color: var(--white, $white);
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-darker;
|
||||
background-color: var(--gray-50, $gray-50);
|
||||
border: 1px solid $dropdown-toggle-active-border-color;
|
||||
color: $gl-text-color;
|
||||
color: var(--gl-text-color, $gl-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
}
|
||||
|
||||
.environment-information {
|
||||
border: 1px solid $border-color;
|
||||
border: 1px solid var(--border-color, $border-color);
|
||||
padding: 8px $gl-padding 12px;
|
||||
border-radius: $border-radius-default;
|
||||
|
||||
|
@ -219,9 +219,9 @@
|
|||
}
|
||||
|
||||
.builds-container {
|
||||
background-color: $white;
|
||||
border-top: 1px solid $border-color;
|
||||
border-bottom: 1px solid $border-color;
|
||||
background-color: var(--white, $white);
|
||||
border-top: 1px solid var(--border-color, $border-color);
|
||||
border-bottom: 1px solid var(--border-color, $border-color);
|
||||
max-height: 300px;
|
||||
width: 289px;
|
||||
overflow: auto;
|
||||
|
@ -237,7 +237,7 @@
|
|||
width: 270px;
|
||||
|
||||
&:hover {
|
||||
color: $gl-text-color;
|
||||
color: var(--gl-text-color, $gl-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,13 +256,13 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-darker;
|
||||
background-color: var(--gray-50, $gray-50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.link-commit {
|
||||
color: $blue-600;
|
||||
color: var(--blue-600, $blue-600);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
.ci-status {
|
||||
padding: 2px 7px 4px;
|
||||
border: 1px solid $gray-darker;
|
||||
border: 1px solid var(--border-color, $border-color);
|
||||
white-space: nowrap;
|
||||
border-radius: 4px;
|
||||
|
||||
|
@ -18,7 +18,11 @@
|
|||
}
|
||||
|
||||
&.ci-failed {
|
||||
@include status-color($red-100, $red-500, $red-600);
|
||||
@include status-color(
|
||||
var(--red-100, $red-100),
|
||||
var(--red-500, $red-500),
|
||||
var(--red-600, $red-600)
|
||||
);
|
||||
}
|
||||
|
||||
&.ci-success {
|
||||
|
@ -30,8 +34,8 @@
|
|||
&.ci-disabled,
|
||||
&.ci-scheduled,
|
||||
&.ci-manual {
|
||||
color: $gl-text-color;
|
||||
border-color: $gl-text-color;
|
||||
color: var(--gl-text-color, $gl-text-color);
|
||||
border-color: currentColor;
|
||||
|
||||
&:not(span):hover {
|
||||
background-color: rgba($gl-text-color, 0.07);
|
||||
|
@ -39,25 +43,37 @@
|
|||
}
|
||||
|
||||
&.ci-preparing {
|
||||
@include status-color($gray-100, $gray-300, $gray-400);
|
||||
@include status-color(
|
||||
var(--gray-100, $gray-100),
|
||||
var(--gray-300, $gray-300),
|
||||
var(--gray-400, $gray-400)
|
||||
);
|
||||
}
|
||||
|
||||
&.ci-pending,
|
||||
&.ci-waiting-for-resource,
|
||||
&.ci-failed-with-warnings,
|
||||
&.ci-success-with-warnings {
|
||||
@include status-color($orange-50, $orange-500, $orange-700);
|
||||
@include status-color(
|
||||
var(--orange-50, $orange-50),
|
||||
var(--orange-500, $orange-500),
|
||||
var(--orange-700, $orange-700)
|
||||
);
|
||||
}
|
||||
|
||||
&.ci-info,
|
||||
&.ci-running {
|
||||
@include status-color($blue-100, $blue-500, $blue-600);
|
||||
@include status-color(
|
||||
var(--blue-100, $blue-100),
|
||||
var(--blue-500, $blue-500),
|
||||
var(--blue-600, $blue-600)
|
||||
);
|
||||
}
|
||||
|
||||
&.ci-created,
|
||||
&.ci-skipped {
|
||||
color: $gl-text-color-secondary;
|
||||
border-color: $gl-text-color-secondary;
|
||||
color: var(--gray-500, $gray-500);
|
||||
border-color: currentColor;
|
||||
|
||||
&:not(span):hover {
|
||||
background-color: rgba($gl-text-color-secondary, 0.07);
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
//// Copied from roadmaps.scss - adapted for on-call schedules
|
||||
$header-item-height: 72px;
|
||||
$item-height: 40px;
|
||||
$details-cell-width: 150px;
|
||||
$details-cell-width: 180px;
|
||||
$timeline-cell-height: 32px;
|
||||
$timeline-cell-width: 180px;
|
||||
$border-style: 1px solid var(--gray-100, $gray-100);
|
||||
|
@ -98,7 +98,7 @@ $column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradi
|
|||
|
||||
.item-label {
|
||||
@include gl-py-4;
|
||||
@include gl-pl-7;
|
||||
@include gl-pl-4;
|
||||
border-right: $border-style;
|
||||
border-bottom: $border-style;
|
||||
}
|
||||
|
@ -147,7 +147,6 @@ $column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradi
|
|||
.timeline-cell {
|
||||
@include float-left;
|
||||
height: $item-height;
|
||||
border-bottom: $border-style;
|
||||
}
|
||||
|
||||
.details-cell {
|
||||
|
@ -181,3 +180,10 @@ $column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradi
|
|||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.gl-token {
|
||||
.gl-avatar-labeled-label {
|
||||
@include gl-text-white;
|
||||
@include gl-font-weight-normal;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
}
|
||||
|
||||
.stage {
|
||||
color: $gl-text-color-secondary;
|
||||
color: var(--gray-500, $gray-500);
|
||||
font-weight: $gl-font-weight-normal;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
@ -62,7 +62,7 @@
|
|||
|
||||
a {
|
||||
font-weight: $gl-font-weight-bold;
|
||||
color: $gl-text-color;
|
||||
color: var(--gl-text-color, $gl-text-color);
|
||||
text-decoration: none;
|
||||
|
||||
&:focus,
|
||||
|
@ -124,7 +124,7 @@
|
|||
display: flex;
|
||||
width: 100%;
|
||||
min-height: $dropdown-max-height-lg;
|
||||
background-color: $gray-light;
|
||||
background-color: var(--gray-50, $gray-50);
|
||||
padding: $gl-padding 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
@ -177,7 +177,7 @@
|
|||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $gl-text-color;
|
||||
color: var(--gl-text-color, $gl-text-color);
|
||||
}
|
||||
|
||||
svg {
|
||||
|
@ -249,18 +249,18 @@
|
|||
height: 25px;
|
||||
position: absolute;
|
||||
top: -31px;
|
||||
border-top: 2px solid $border-color;
|
||||
border-top: 2px solid var(--border-color, $border-color);
|
||||
}
|
||||
|
||||
&::after {
|
||||
left: -44px;
|
||||
border-right: 2px solid $border-color;
|
||||
border-right: 2px solid var(--border-color, $border-color);
|
||||
border-radius: 0 20px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
right: -44px;
|
||||
border-left: 2px solid $border-color;
|
||||
border-left: 2px solid var(--border-color, $border-color);
|
||||
border-radius: 20px 0 0;
|
||||
}
|
||||
}
|
||||
|
@ -316,7 +316,7 @@
|
|||
|
||||
a.build-content:hover,
|
||||
button.build-content:hover {
|
||||
background-color: $gray-darker;
|
||||
background-color: var(--gray-100, $gray-100);
|
||||
border: 1px solid $dropdown-toggle-active-border-color;
|
||||
}
|
||||
|
||||
|
@ -327,7 +327,7 @@
|
|||
position: absolute;
|
||||
top: 48%;
|
||||
right: -48px;
|
||||
border-top: 2px solid $border-color;
|
||||
border-top: 2px solid var(--border-color, $border-color);
|
||||
width: 48px;
|
||||
height: 1px;
|
||||
}
|
||||
|
@ -340,7 +340,7 @@
|
|||
content: '';
|
||||
top: -49px;
|
||||
position: absolute;
|
||||
border-bottom: 2px solid $border-color;
|
||||
border-bottom: 2px solid var(--border-color, $border-color);
|
||||
width: 25px;
|
||||
height: 69px;
|
||||
}
|
||||
|
@ -348,14 +348,14 @@
|
|||
// Right connecting curves
|
||||
&::after {
|
||||
right: -25px;
|
||||
border-right: 2px solid $border-color;
|
||||
border-right: 2px solid var(--border-color, $border-color);
|
||||
border-radius: 0 0 20px;
|
||||
}
|
||||
|
||||
// Left connecting curves
|
||||
&::before {
|
||||
left: -25px;
|
||||
border-left: 2px solid $border-color;
|
||||
border-left: 2px solid var(--border-color, $border-color);
|
||||
border-radius: 0 0 0 20px;
|
||||
}
|
||||
}
|
||||
|
@ -390,7 +390,7 @@
|
|||
line-height: 0;
|
||||
|
||||
svg {
|
||||
fill: $gl-text-color-secondary;
|
||||
fill: var(--gray-500, $gray-500);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
|
@ -488,13 +488,13 @@
|
|||
left: -6px;
|
||||
margin-top: 3px;
|
||||
border-width: 7px 5px 7px 0;
|
||||
border-right-color: $border-color;
|
||||
border-right-color: var(--border-color, $border-color);
|
||||
}
|
||||
|
||||
&::after {
|
||||
left: -5px;
|
||||
border-width: 10px 7px 10px 0;
|
||||
border-right-color: $white;
|
||||
border-right-color: var(--white, $white);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -519,5 +519,5 @@
|
|||
}
|
||||
|
||||
.progress-bar.bg-primary {
|
||||
background-color: $blue-500 !important;
|
||||
background-color: var(--blue-500, $blue-500) !important;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
min-width: 170px; //Guarantees buttons don't break in several lines.
|
||||
|
||||
.btn-default {
|
||||
color: $gl-text-color-secondary;
|
||||
color: var(--gray-500, $gray-500);
|
||||
}
|
||||
|
||||
.btn.btn-retry:hover,
|
||||
|
@ -32,7 +32,7 @@
|
|||
}
|
||||
|
||||
svg path {
|
||||
fill: $gl-text-color-secondary;
|
||||
fill: var(--gray-500, $gray-500);
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
|
@ -42,7 +42,7 @@
|
|||
|
||||
.dropdown-toggle,
|
||||
.dropdown-menu {
|
||||
color: $gl-text-color-secondary;
|
||||
color: var(--gray-500, $gray-500);
|
||||
}
|
||||
|
||||
.btn-group.open .btn-default {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
class Profiles::GpgKeysController < Profiles::ApplicationController
|
||||
before_action :set_gpg_key, only: [:destroy, :revoke]
|
||||
skip_before_action :authenticate_user!, only: [:get_keys]
|
||||
|
||||
feature_category :users
|
||||
|
||||
|
@ -40,24 +39,6 @@ class Profiles::GpgKeysController < Profiles::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# Get all gpg keys of a user(params[:username]) in a text format
|
||||
def get_keys
|
||||
if params[:username].present?
|
||||
begin
|
||||
user = UserFinder.new(params[:username]).find_by_username
|
||||
if user.present?
|
||||
render plain: user.gpg_keys.select(&:verified?).map(&:key).join("\n")
|
||||
else
|
||||
render_404
|
||||
end
|
||||
rescue => e
|
||||
render html: e.message
|
||||
end
|
||||
else
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gpg_key_params
|
||||
|
|
|
@ -31,7 +31,7 @@ module Projects
|
|||
end
|
||||
|
||||
def notify_service
|
||||
notify_service_class.new(project, current_user, notification_payload)
|
||||
notify_service_class.new(project, notification_payload)
|
||||
end
|
||||
|
||||
def notify_service_class
|
||||
|
|
|
@ -73,7 +73,7 @@ module Projects
|
|||
|
||||
def notify_service
|
||||
Projects::Prometheus::Alerts::NotifyService
|
||||
.new(project, current_user, params.permit!)
|
||||
.new(project, params.permit!)
|
||||
end
|
||||
|
||||
def create_service
|
||||
|
|
|
@ -58,6 +58,11 @@ class UsersController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# Get all gpg keys of a user(params[:username]) in a text format
|
||||
def gpg_keys
|
||||
render plain: user.gpg_keys.select(&:verified?).map(&:key).join("\n")
|
||||
end
|
||||
|
||||
def groups
|
||||
load_groups
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ module Ci
|
|||
date: start_date..end_date
|
||||
}
|
||||
|
||||
if ref_path
|
||||
if ref_path.present?
|
||||
params[:ref_path] = ref_path
|
||||
else
|
||||
params[:default_branch] = true
|
||||
|
|
|
@ -167,6 +167,8 @@ module Types
|
|||
description: 'Indicates if the merge request is mergeable'
|
||||
field :commits_without_merge_commits, Types::CommitType.connection_type, null: true,
|
||||
calls_gitaly: true, description: 'Merge request commits excluding merge commits'
|
||||
field :security_auto_fix, GraphQL::BOOLEAN_TYPE, null: true,
|
||||
description: 'Indicates if the merge request is created by @GitLab-Security-Bot.'
|
||||
|
||||
def approved_by
|
||||
object.approved_by_users
|
||||
|
@ -229,6 +231,10 @@ module Types
|
|||
def commits_without_merge_commits
|
||||
object.recent_commits.without_merge_commits
|
||||
end
|
||||
|
||||
def security_auto_fix
|
||||
object.author == User.security_bot
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module AlertManagement
|
||||
class ProcessPrometheusAlertService < BaseService
|
||||
class ProcessPrometheusAlertService
|
||||
include BaseServiceUtility
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include ::IncidentManagement::Settings
|
||||
|
||||
def initialize(project, payload)
|
||||
@project = project
|
||||
@payload = payload
|
||||
end
|
||||
|
||||
def execute
|
||||
return bad_request unless incoming_payload.has_required_attributes?
|
||||
|
||||
|
@ -19,6 +25,8 @@ module AlertManagement
|
|||
|
||||
private
|
||||
|
||||
attr_reader :project, :payload
|
||||
|
||||
def process_alert_management_alert
|
||||
if incoming_payload.resolved?
|
||||
process_resolved_alert_management_alert
|
||||
|
@ -127,7 +135,7 @@ module AlertManagement
|
|||
strong_memoize(:incoming_payload) do
|
||||
Gitlab::AlertManagement::Payload.parse(
|
||||
project,
|
||||
params,
|
||||
payload,
|
||||
monitoring_tool: Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus]
|
||||
)
|
||||
end
|
||||
|
|
|
@ -65,7 +65,7 @@ module Clusters
|
|||
notification_payload = build_notification_payload(project)
|
||||
integration = project.alert_management_http_integrations.active.first
|
||||
|
||||
Projects::Alerting::NotifyService.new(project, nil, notification_payload).execute(integration&.token, integration)
|
||||
Projects::Alerting::NotifyService.new(project, notification_payload).execute(integration&.token, integration)
|
||||
|
||||
@logger.info(message: 'Successfully notified of Prometheus newly unhealthy', cluster_id: @cluster.id, project_id: project.id)
|
||||
end
|
||||
|
|
|
@ -2,10 +2,16 @@
|
|||
|
||||
module Projects
|
||||
module Alerting
|
||||
class NotifyService < BaseService
|
||||
class NotifyService
|
||||
include BaseServiceUtility
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include ::IncidentManagement::Settings
|
||||
|
||||
def initialize(project, payload)
|
||||
@project = project
|
||||
@payload = payload
|
||||
end
|
||||
|
||||
def execute(token, integration = nil)
|
||||
@integration = integration
|
||||
|
||||
|
@ -24,7 +30,7 @@ module Projects
|
|||
|
||||
private
|
||||
|
||||
attr_reader :integration
|
||||
attr_reader :project, :payload, :integration
|
||||
|
||||
def process_alert
|
||||
if alert.persisted?
|
||||
|
@ -101,7 +107,7 @@ module Projects
|
|||
|
||||
def incoming_payload
|
||||
strong_memoize(:incoming_payload) do
|
||||
Gitlab::AlertManagement::Payload.parse(project, params.to_h)
|
||||
Gitlab::AlertManagement::Payload.parse(project, payload.to_h)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -110,7 +116,7 @@ module Projects
|
|||
end
|
||||
|
||||
def valid_payload_size?
|
||||
Gitlab::Utils::DeepSize.new(params).valid?
|
||||
Gitlab::Utils::DeepSize.new(payload).valid?
|
||||
end
|
||||
|
||||
def active_integration?
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Projects
|
||||
module Prometheus
|
||||
module Alerts
|
||||
class NotifyService < BaseService
|
||||
class NotifyService
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include ::IncidentManagement::Settings
|
||||
|
||||
|
@ -17,9 +17,14 @@ module Projects
|
|||
|
||||
SUPPORTED_VERSION = '4'
|
||||
|
||||
def initialize(project, payload)
|
||||
@project = project
|
||||
@payload = payload
|
||||
end
|
||||
|
||||
def execute(token, integration = nil)
|
||||
return bad_request unless valid_payload_size?
|
||||
return unprocessable_entity unless self.class.processable?(params)
|
||||
return unprocessable_entity unless self.class.processable?(payload)
|
||||
return unauthorized unless valid_alert_manager_token?(token, integration)
|
||||
|
||||
process_prometheus_alerts
|
||||
|
@ -27,18 +32,20 @@ module Projects
|
|||
ServiceResponse.success
|
||||
end
|
||||
|
||||
def self.processable?(params)
|
||||
def self.processable?(payload)
|
||||
# Workaround for https://gitlab.com/gitlab-org/gitlab/-/issues/220496
|
||||
return false unless params
|
||||
return false unless payload
|
||||
|
||||
REQUIRED_PAYLOAD_KEYS.subset?(params.keys.to_set) &&
|
||||
params['version'] == SUPPORTED_VERSION
|
||||
REQUIRED_PAYLOAD_KEYS.subset?(payload.keys.to_set) &&
|
||||
payload['version'] == SUPPORTED_VERSION
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project, :payload
|
||||
|
||||
def valid_payload_size?
|
||||
Gitlab::Utils::DeepSize.new(params).valid?
|
||||
Gitlab::Utils::DeepSize.new(payload).valid?
|
||||
end
|
||||
|
||||
def firings
|
||||
|
@ -50,7 +57,7 @@ module Projects
|
|||
end
|
||||
|
||||
def alerts
|
||||
params['alerts']
|
||||
payload['alerts']
|
||||
end
|
||||
|
||||
def valid_alert_manager_token?(token, integration)
|
||||
|
@ -121,7 +128,7 @@ module Projects
|
|||
def process_prometheus_alerts
|
||||
alerts.each do |alert|
|
||||
AlertManagement::ProcessPrometheusAlertService
|
||||
.new(project, nil, alert.to_h)
|
||||
.new(project, alert.to_h)
|
||||
.execute
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,4 +15,4 @@
|
|||
|
||||
%p
|
||||
%h4= _("Release notes:")
|
||||
= markdown_field(@release, :description)
|
||||
= markdown(@release.description, pipeline: :email, author: @release.author)
|
||||
|
|
5
changelogs/unreleased/258810-2-itteration-be.yml
Normal file
5
changelogs/unreleased/258810-2-itteration-be.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add MergeRequest to VulnerabilityType in GraphQL
|
||||
merge_request: 50082
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/273592-terraform-delete.yml
Normal file
5
changelogs/unreleased/273592-terraform-delete.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add delete button to terraform list vue
|
||||
merge_request: 48485
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move profiles/gpg_keys#get_keys to users#gpg_keys
|
||||
merge_request: 49448
|
||||
author: Takuya Noguchi
|
||||
type: other
|
5
changelogs/unreleased/psi-dark-pipelines.yml
Normal file
5
changelogs/unreleased/psi-dark-pipelines.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix pipeline page in dark mode
|
||||
merge_request: 49214
|
||||
author:
|
||||
type: fixed
|
5
changelogs/unreleased/sh-fix-release-markdown.yml
Normal file
5
changelogs/unreleased/sh-fix-release-markdown.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Markdown attachments in Releases not rendering with full URL
|
||||
merge_request: 50146
|
||||
author:
|
||||
type: fixed
|
|
@ -58,7 +58,7 @@ constraints(::Constraints::UserUrlConstrainer.new) do
|
|||
get ':username.keys' => 'users#ssh_keys', constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
|
||||
|
||||
# Get all GPG keys of user
|
||||
get ':username.gpg' => 'profiles/gpg_keys#get_keys', constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
|
||||
get ':username.gpg' => 'users#gpg_keys', constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
|
||||
|
||||
scope(path: ':username',
|
||||
as: :user,
|
||||
|
|
112
doc/administration/geo/glossary.md
Normal file
112
doc/administration/geo/glossary.md
Normal file
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
stage: Enablement
|
||||
group: Geo
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
type: howto
|
||||
---
|
||||
|
||||
|
||||
# Geo Glossary
|
||||
|
||||
NOTE:
|
||||
We are updating the Geo documentation, user interface and commands to reflect these changes. Not all pages comply with
|
||||
these definitions yet.
|
||||
|
||||
These are the defined terms to describe all aspects of Geo. Using a set of clearly
|
||||
defined terms helps us to communicate efficiently and avoids confusion. The language
|
||||
on this page aims to be [ubiquitous](https://about.gitlab.com/handbook/communication/#ubiquitous-language)
|
||||
and [as simple as possible](https://about.gitlab.com/handbook/communication/#simple-language).
|
||||
|
||||
We provide example diagrams and statements to demonstrate correct usage of terms.
|
||||
|
||||
| Term | Definition | Scope | Discouraged synonyms |
|
||||
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|-------------------------------------------------|
|
||||
| Node | An individual server that runs GitLab either with a specific role or as a whole (e.g. a Rails application node). In a cloud context this can be a specific machine type. | GitLab | instance, server |
|
||||
| Site | One or a collection of nodes running a single GitLab application. A site can be single-node or multi-node. | GitLab | deployment, installation instance |
|
||||
| Single-node site | A specific configuration of GitLab that uses exactly one node. | GitLab | single-server, single-instance
|
||||
| Multi-node site | A specific configuration of GitLab that uses more than one node. | GitLab | multi-server, multi-instance, high availability |
|
||||
| Primary site | A GitLab site that is configured to be read and writable. There can only be a single primary site. | Geo-specific | Geo deployment, Primary node |
|
||||
| Secondary site(s) | GitLab site that is configured to be read-only. There can be one or more secondary sites. | Geo-specific | Geo deployment, Secondary node |
|
||||
| Geo deployment | A collection of two or more GitLab sites with exactly one primary site being replicated by one or more secondary sites. | Geo-specific | |
|
||||
| Reference architecture(s) | A [specified configuration of GitLab for a number of users](../reference_architectures/index.md), possibly including multiple nodes and multiple sites. | GitLab | |
|
||||
| Promoting | Changing the role of a site from secondary to primary. | Geo-specific | |
|
||||
| Demoting | Changing the role of a site from primary to secondary. | Geo-specific | |
|
||||
| Failover | The entire process that shifts users from a primary Site to a secondary site. This includes promoting a secondary, but contains other parts as well e.g. scheduling maintenance. | Geo-specific | |
|
||||
|
||||
## Examples
|
||||
|
||||
### Single-node site
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph S-Site[Single-node site]
|
||||
Node_3[GitLab node]
|
||||
end
|
||||
```
|
||||
|
||||
### Multi-node site
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph MN-Site[Multi-node site]
|
||||
Node_1[Application node]
|
||||
Node_2[Database node]
|
||||
Node_3[Gitaly node]
|
||||
end
|
||||
```
|
||||
|
||||
### Geo deployment - Single-node sites
|
||||
|
||||
This Geo deployment has a single-node primary site, a single-node secondary site:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Geo deployment
|
||||
subgraph Primary[Primary site, single-node]
|
||||
Node_1[GitLab node]
|
||||
end
|
||||
subgraph Secondary1[Secondary site 1, single-node]
|
||||
Node_2[GitLab node]
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Geo deployment - Multi-node sites
|
||||
|
||||
This Geo deployment has a multi-node primary site, a multi-node secondary site:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Geo deployment
|
||||
subgraph Primary[Primary site, multi-node]
|
||||
Node_1[Application node]
|
||||
Node_2[Database node]
|
||||
end
|
||||
subgraph Secondary1[Secondary site 1, multi-node]
|
||||
Node_5[Application node]
|
||||
Node_6[Database node]
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Geo deployment - Mixed sites
|
||||
|
||||
This Geo deployment has a multi-node primary site, a multi-node secondary site and another single-node secondary site:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Geo deployment
|
||||
subgraph Primary[Primary site, multi-node]
|
||||
Node_1[Application node]
|
||||
Node_2[Database node]
|
||||
Node_3[Gitaly node]
|
||||
end
|
||||
subgraph Secondary1[Secondary site 1, multi-node]
|
||||
Node_5[Application node]
|
||||
Node_6[Database node]
|
||||
end
|
||||
subgraph Secondary2[Secondary site 2, single-node]
|
||||
Node_7[Single GitLab node]
|
||||
end
|
||||
end
|
||||
```
|
|
@ -1021,7 +1021,7 @@ previously listed components, it's helpful to simultaneously gather multiple log
|
|||
from a GitLab instance.
|
||||
|
||||
NOTE:
|
||||
GitLab Support will often ask for one of these, and maintains the required tools.
|
||||
GitLab Support often asks for one of these, and maintains the required tools.
|
||||
|
||||
### Briefly tail the main logs
|
||||
|
||||
|
|
|
@ -13456,6 +13456,11 @@ type MergeRequest implements CurrentUserTodos & Noteable {
|
|||
full: Boolean = false
|
||||
): String!
|
||||
|
||||
"""
|
||||
Indicates if the merge request is created by @GitLab-Security-Bot.
|
||||
"""
|
||||
securityAutoFix: Boolean
|
||||
|
||||
"""
|
||||
Indicates if the merge request will be rebased
|
||||
"""
|
||||
|
@ -25001,6 +25006,11 @@ type Vulnerability implements Noteable {
|
|||
"""
|
||||
location: VulnerabilityLocation
|
||||
|
||||
"""
|
||||
Merge request that fixes the vulnerability.
|
||||
"""
|
||||
mergeRequest: MergeRequest
|
||||
|
||||
"""
|
||||
All notes on this noteable
|
||||
"""
|
||||
|
|
|
@ -37121,6 +37121,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "securityAutoFix",
|
||||
"description": "Indicates if the merge request is created by @GitLab-Security-Bot.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "shouldBeRebased",
|
||||
"description": "Indicates if the merge request will be rebased",
|
||||
|
@ -72824,6 +72838,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergeRequest",
|
||||
"description": "Merge request that fixes the vulnerability.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequest",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "notes",
|
||||
"description": "All notes on this noteable",
|
||||
|
|
|
@ -2088,6 +2088,7 @@ Autogenerated return type of MarkAsSpamSnippet.
|
|||
| `rebaseCommitSha` | String | Rebase commit SHA of the merge request |
|
||||
| `rebaseInProgress` | Boolean! | Indicates if there is a rebase currently in progress for the merge request |
|
||||
| `reference` | String! | Internal reference of the merge request. Returned in shortened format by default |
|
||||
| `securityAutoFix` | Boolean | Indicates if the merge request is created by @GitLab-Security-Bot. |
|
||||
| `shouldBeRebased` | Boolean! | Indicates if the merge request will be rebased |
|
||||
| `shouldRemoveSourceBranch` | Boolean | Indicates if the source branch of the merge request will be deleted after merge |
|
||||
| `sourceBranch` | String! | Source branch of the merge request |
|
||||
|
@ -3772,6 +3773,7 @@ Represents a vulnerability.
|
|||
| `identifiers` | VulnerabilityIdentifier! => Array | Identifiers of the vulnerability. |
|
||||
| `issueLinks` | VulnerabilityIssueLinkConnection! | List of issue links related to the vulnerability |
|
||||
| `location` | VulnerabilityLocation | Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability |
|
||||
| `mergeRequest` | MergeRequest | Merge request that fixes the vulnerability. |
|
||||
| `notes` | NoteConnection! | All notes on this noteable |
|
||||
| `primaryIdentifier` | VulnerabilityIdentifier | Primary identifier of the vulnerability. |
|
||||
| `project` | Project | The project on which the vulnerability was found |
|
||||
|
|
|
@ -72,8 +72,8 @@ Complementary reads:
|
|||
|
||||
### Development guidelines review
|
||||
|
||||
When you submit a change to GitLab's development guidelines, the people
|
||||
you ask for reviews from depend on the level of change, as described below.
|
||||
When you submit a change to GitLab's development guidelines, who
|
||||
you ask for reviews depends on the level of change.
|
||||
|
||||
#### Wording, style, or link changes
|
||||
|
||||
|
@ -88,7 +88,7 @@ maintainer or Technical Writer. These can include:
|
|||
|
||||
#### Specific changes
|
||||
|
||||
If the MR proposes changes limited to a particular stage, group, or team,
|
||||
If the MR proposes changes that are limited to a particular stage, group, or team,
|
||||
request a review and approval from an experienced GitLab Team Member in that
|
||||
group. For example, if you're documenting a new internal API used exclusively by
|
||||
a given group, request an engineering review from one of the group's members.
|
||||
|
|
|
@ -803,6 +803,32 @@ overhead. If you are writing:
|
|||
- A `Mutation`, feel free to lookup objects directly.
|
||||
- A `Resolver` or methods on a `BaseObject`, then you want to allow for batching.
|
||||
|
||||
### Error handling
|
||||
|
||||
Resolvers may raise errors, which will be converted to top-level errors as
|
||||
appropriate. All anticipated errors should be caught and transformed to an
|
||||
appropriate GraphQL error (see
|
||||
[`Gitlab::Graphql::Errors`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/graphql/errors.rb)).
|
||||
Any uncaught errors will be suppressed and the client will receive the message
|
||||
`Internal service error`.
|
||||
|
||||
The one special case is permission errors. In the REST API we return
|
||||
`404 Not Found` for any resources that the user does not have permission to
|
||||
access. The equivalent behavior in GraphQL is for us to return `null` for
|
||||
all absent or unauthorized resources.
|
||||
Query resolvers **should not raise errors for unauthorized resources**.
|
||||
|
||||
The rationale for this is that clients must not be able to distinguish between
|
||||
the absence of a record and the presence of one they do not have access to. To
|
||||
do so is a security vulnerability, since it leaks information we want to keep
|
||||
hidden.
|
||||
|
||||
In most cases you don't need to worry about this - this is handled correctly by
|
||||
the resolver field authorization we declare with the `authorize` DSL calls. If
|
||||
you need to do something more custom however, remember, if you encounter an
|
||||
object the `current_user` does not have access to when resolving a field, then
|
||||
the entire field should resolve to `null`.
|
||||
|
||||
### Deriving resolvers (`BaseResolver.single` and `BaseResolver.last`)
|
||||
|
||||
For some simple use cases, we can derive resolvers from others.
|
||||
|
|
|
@ -4,28 +4,63 @@ group: Configure
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Chatops on GitLab.com
|
||||
# ChatOps on GitLab.com
|
||||
|
||||
ChatOps on GitLab.com allows GitLab team members to run various automation tasks on GitLab.com using Slack.
|
||||
|
||||
## Requesting access
|
||||
|
||||
GitLab team-members may need access to Chatops on GitLab.com for administration
|
||||
GitLab team-members may need access to ChatOps on GitLab.com for administration
|
||||
tasks such as:
|
||||
|
||||
- Configuring feature flags.
|
||||
- Running `EXPLAIN` queries against the GitLab.com production replica.
|
||||
- Get deployment status of all of our environments or for a specific commit: `/chatops run auto_deploy status [commit_sha]`
|
||||
|
||||
To request access to Chatops on GitLab.com:
|
||||
To request access to ChatOps on GitLab.com:
|
||||
|
||||
1. Log into <https://ops.gitlab.net/users/sign_in> **using the same username** as for GitLab.com (you may have to rename it).
|
||||
1. You could also use the "Sign in with" Google button to sign in, with your GitLab.com email address.
|
||||
1. Ask one of your team members to add you to the `chatops` project in Ops. They can do it by running `/chatops run member add <username> gitlab-com/chatops --ops` command in the `#chat-ops-test` Slack channel.
|
||||
1. If you had to change your username for GitLab.com on the first step, make sure [to reflect this information](https://gitlab.com/gitlab-com/www-gitlab-com#adding-yourself-to-the-team-page) on [the team page](https://about.gitlab.com/company/team/).
|
||||
1. Sign in to [Internal GitLab for Operations](https://ops.gitlab.net/users/sign_in)
|
||||
with one of the following methods:
|
||||
|
||||
- The same username you use on GitLab.com. You may have to choose a different
|
||||
username later.
|
||||
- Clicking the **Sign in with Google** button to sign in with your GitLab.com email address.
|
||||
|
||||
1. Confirm that your username in [Internal GitLab for Operations](https://ops.gitlab.net/)
|
||||
is the same as your username in [GitLab.com](https://gitlab.com/). If the usernames
|
||||
don't match, update the username at [Internal GitLab for Operations](https://ops.gitlab.net/).
|
||||
|
||||
1. Comment in your onboarding issue, and tag your onboarding buddy and your manager.
|
||||
Request they add you to the `ops` ChatOps project by running this command
|
||||
in the `#chat-ops-test` Slack channel, replacing `<username>` with your username:
|
||||
`/chatops run member add <username> gitlab-com/chatops --ops`
|
||||
|
||||
<!-- vale gitlab.FirstPerson = NO -->
|
||||
|
||||
> Hi `__BUDDY_HANDLE__` and `__MANAGER_HANDLE__`, could you please add me to
|
||||
> the ChatOps project in Ops by running this command:
|
||||
> `/chatops run member add <username> gitlab-com/chatops --ops` in the
|
||||
> `#chat-ops-test` Slack channel? Thanks in advance.
|
||||
|
||||
<!-- vale gitlab.FirstPerson = YES -->
|
||||
|
||||
1. Ensure you've set up two-factor authentication.
|
||||
1. After you're added to the ChatOps project, run this command to check your user
|
||||
status and ensure you can execute commands in the `#chat-ops-test` Slack channel:
|
||||
|
||||
```plaintext
|
||||
/chatops run user find <username>
|
||||
```
|
||||
|
||||
The bot guides you through the process of allowing your user to execute
|
||||
commands in the `#chat-ops-test` Slack channel.
|
||||
|
||||
1. If you had to change your username for GitLab.com on the first step, make sure
|
||||
[to reflect this information](https://gitlab.com/gitlab-com/www-gitlab-com#adding-yourself-to-the-team-page)
|
||||
on [the team page](https://about.gitlab.com/company/team/).
|
||||
|
||||
## See also
|
||||
|
||||
- [Chatops Usage](../ci/chatops/README.md)
|
||||
- [ChatOps Usage](../ci/chatops/README.md)
|
||||
- [Understanding EXPLAIN plans](understanding_explain_plans.md)
|
||||
- [Feature Groups](feature_flags/development.md#feature-groups)
|
||||
|
|
|
@ -6,6 +6,31 @@ info: "See the Technical Writers assigned to Development Guidelines: https://abo
|
|||
|
||||
# Feature flags in development of GitLab
|
||||
|
||||
## When to use feature flags
|
||||
|
||||
Starting with GitLab 11.4, developers are required to use feature flags for
|
||||
non-trivial changes. Such changes include:
|
||||
|
||||
- New features (e.g. a new merge request widget, epics, etc).
|
||||
- Complex performance improvements that may require additional testing in
|
||||
production, such as rewriting complex queries.
|
||||
- Invasive changes to the user interface, such as a new navigation bar or the
|
||||
removal of a sidebar.
|
||||
- Adding support for importing projects from a third-party service.
|
||||
- Risk of data loss
|
||||
|
||||
In all cases, those working on the changes can best decide if a feature flag is
|
||||
necessary. For example, changing the color of a button doesn't need a feature
|
||||
flag, while changing the navigation bar definitely needs one. In case you are
|
||||
uncertain if a feature flag is necessary, simply ask about this in the merge
|
||||
request, and those reviewing the changes will likely provide you with an answer.
|
||||
|
||||
When using a feature flag for UI elements, make sure to _also_ use a feature
|
||||
flag for the underlying backend code, if there is any. This ensures there is
|
||||
absolutely no way to use the feature until it is enabled.
|
||||
|
||||
## How to use Feature Flags
|
||||
|
||||
Feature flags can be used to gradually deploy changes, regardless of whether
|
||||
they are new features or performance improvements. By using feature flags,
|
||||
you can determine the impact of GitLab-directed changes, while still being able
|
||||
|
|
|
@ -53,27 +53,6 @@ problems, such as outages.
|
|||
|
||||
Please also read the [development guide for feature flags](development.md).
|
||||
|
||||
### When to use feature flags
|
||||
|
||||
Starting with GitLab 11.4, developers are required to use feature flags for
|
||||
non-trivial changes. Such changes include:
|
||||
|
||||
- New features (e.g. a new merge request widget, epics, etc).
|
||||
- Complex performance improvements that may require additional testing in
|
||||
production, such as rewriting complex queries.
|
||||
- Invasive changes to the user interface, such as a new navigation bar or the
|
||||
removal of a sidebar.
|
||||
- Adding support for importing projects from a third-party service.
|
||||
|
||||
In all cases, those working on the changes can best decide if a feature flag is
|
||||
necessary. For example, changing the color of a button doesn't need a feature
|
||||
flag, while changing the navigation bar definitely needs one. In case you are
|
||||
uncertain if a feature flag is necessary, simply ask about this in the merge
|
||||
request, and those reviewing the changes will likely provide you with an answer.
|
||||
|
||||
When using a feature flag for UI elements, make sure to _also_ use a feature
|
||||
flag for the underlying backend code, if there is any. This ensures there is
|
||||
absolutely no way to use the feature until it is enabled.
|
||||
|
||||
### Including a feature behind feature flag in the final release
|
||||
|
||||
|
|
|
@ -661,9 +661,10 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
|
|||
#### `if:` conditions
|
||||
|
||||
<!-- vale gitlab.Substitutions = NO -->
|
||||
|
||||
| `if:` conditions | Description | Notes |
|
||||
|------------------|-------------|-------|
|
||||
| `if-not-canonical-namespace` | Matches if the project isn't in the canonical (`gitlab-org/`) or security (`gitlab-org/security`) namespace. | Use to create a job for forks (by using `when: on_success\|manual`), or **not** create a job for forks (by using `when: never`). |
|
||||
| `if-not-canonical-namespace` | Matches if the project isn't in the canonical (`gitlab-org/`) or security (`gitlab-org/security`) namespace. | Use to create a job for forks (by using `when: on_success|manual`), or **not** create a job for forks (by using `when: never`). |
|
||||
| `if-not-ee` | Matches if the project isn't EE (i.e. project name isn't `gitlab` or `gitlab-ee`). | Use to create a job only in the FOSS project (by using `when: on_success|manual`), or **not** create a job if the project is EE (by using `when: never`). |
|
||||
| `if-not-foss` | Matches if the project isn't FOSS (i.e. project name isn't `gitlab-foss`, `gitlab-ce`, or `gitlabhq`). | Use to create a job only in the EE project (by using `when: on_success|manual`), or **not** create a job if the project is FOSS (by using `when: never`). |
|
||||
| `if-default-refs` | Matches if the pipeline is for `master`, `/^[\d-]+-stable(-ee)?$/` (stable branches), `/^\d+-\d+-auto-deploy-\d+$/` (auto-deploy branches), `/^security\//` (security branches), merge requests, and tags. | Note that jobs aren't created for branches with this default configuration. |
|
||||
|
@ -691,6 +692,7 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
|
|||
| `if-rspec-fail-fast-disabled` | Limits jobs to pipelines with `$RSPEC_FAIL_FAST_ENABLED` variable not set to `"true"`. | |
|
||||
| `if-rspec-fail-fast-skipped` | Matches if the pipeline is for a merge request and the MR title includes "SKIP RSPEC FAIL-FAST". | |
|
||||
| `if-security-pipeline-merge-result` | Matches if the pipeline is for a security merge request triggered by `@gitlab-release-tools-bot`. | |
|
||||
|
||||
<!-- vale gitlab.Substitutions = YES -->
|
||||
|
||||
#### `changes:` patterns
|
||||
|
|
|
@ -385,48 +385,48 @@ The following table lists variables used to disable jobs.
|
|||
|
||||
| **Job Name** | **Variable** | **GitLab version** | **Description** |
|
||||
|----------------------------------------|---------------------------------|-----------------------|-----------------|
|
||||
| `.fuzz_base` | `COVFUZZ_DISABLED` | [From GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34984) | [Read more](../../user/application_security/coverage_fuzzing/) about how `.fuzz_base` provide capability for your own jobs. If the variable is present, your jobs won't be created. |
|
||||
| `apifuzzer_fuzz` | `API_FUZZING_DISABLED` | [From GitLab 13.3](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39135) | If the variable is present, the job won't be created. |
|
||||
| `bandit-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `brakeman-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `bundler-audit-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `.fuzz_base` | `COVFUZZ_DISABLED` | [From GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34984) | [Read more](../../user/application_security/coverage_fuzzing/) about how `.fuzz_base` provide capability for your own jobs. If the variable is present, your jobs aren't created. |
|
||||
| `apifuzzer_fuzz` | `API_FUZZING_DISABLED` | [From GitLab 13.3](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39135) | If the variable is present, the job isn't created. |
|
||||
| `bandit-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `brakeman-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `bundler-audit-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `canary` | `CANARY_ENABLED` | | This manual job is created if the variable is present. |
|
||||
| `code_intelligence` | `CODE_INTELLIGENCE_DISABLED` | From GitLab 13.6 | If the variable is present, the job isn't created. |
|
||||
| `codequality` | `CODE_QUALITY_DISABLED` | Until GitLab 11.0 | If the variable is present, the job won't be created. |
|
||||
| `code_quality` | `CODE_QUALITY_DISABLED` | [From GitLab 11.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/5773) | If the variable is present, the job won't be created. |
|
||||
| `container_scanning` | `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
|
||||
| `dast` | `DAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
|
||||
| `dast_environment_deploy` | `DAST_DISABLED_FOR_DEFAULT_BRANCH` or `DAST_DISABLED` | [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17789) | If either variable is present, the job won't be created. |
|
||||
| `dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
|
||||
| `eslint-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `flawfinder-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `gemnasium-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `gemnasium-maven-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `gemnasium-python-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `gosec-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `kubesec-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `license_management` | `LICENSE_MANAGEMENT_DISABLED` | GitLab 11.0 to 12.7 | If the variable is present, the job won't be created. Job deprecated [from GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) |
|
||||
| `license_scanning` | `LICENSE_MANAGEMENT_DISABLED` | [From GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | If the variable is present, the job won't be created. |
|
||||
| `load_performance` | `LOAD_PERFORMANCE_DISABLED` | From GitLab 13.2 | If the variable is present, the job won't be created. |
|
||||
| `nodejs-scan-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `performance` | `PERFORMANCE_DISABLED` | From GitLab 11.0 | Browser performance. If the variable is present, the job won't be created. |
|
||||
| `phpcs-security-audit-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `pmd-apex-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `retire-js-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `review` | `REVIEW_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
|
||||
| `review:stop` | `REVIEW_DISABLED` | From GitLab 11.0 | Manual job. If the variable is present, the job won't be created. |
|
||||
| `sast` | `SAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
|
||||
| `sast:container` | `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
|
||||
| `secret_detection` | `SECRET_DETECTION_DISABLED` | From GitLab 13.1 | If the variable is present, the job won't be created. |
|
||||
| `secret_detection_default_branch` | `SECRET_DETECTION_DISABLED` | [From GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | If the variable is present, the job won't be created. |
|
||||
| `security-code-scan-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `secrets-sast` | `SAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
|
||||
| `sobelaw-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `stop_dast_environment` | `DAST_DISABLED_FOR_DEFAULT_BRANCH` or `DAST_DISABLED` | [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17789) | If either variable is present, the job won't be created. |
|
||||
| `spotbugs-sast` | `SAST_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `test` | `TEST_DISABLED` | From GitLab 11.0 | If the variable is present, the job won't be created. |
|
||||
| `codequality` | `CODE_QUALITY_DISABLED` | Until GitLab 11.0 | If the variable is present, the job isn't created. |
|
||||
| `code_quality` | `CODE_QUALITY_DISABLED` | [From GitLab 11.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/5773) | If the variable is present, the job isn't created. |
|
||||
| `container_scanning` | `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
|
||||
| `dast` | `DAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
|
||||
| `dast_environment_deploy` | `DAST_DISABLED_FOR_DEFAULT_BRANCH` or `DAST_DISABLED` | [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17789) | If either variable is present, the job isn't created. |
|
||||
| `dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
|
||||
| `eslint-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `flawfinder-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `gemnasium-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `gemnasium-maven-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `gemnasium-python-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `gosec-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `kubesec-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `license_management` | `LICENSE_MANAGEMENT_DISABLED` | GitLab 11.0 to 12.7 | If the variable is present, the job isn't created. Job deprecated [from GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) |
|
||||
| `license_scanning` | `LICENSE_MANAGEMENT_DISABLED` | [From GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | If the variable is present, the job isn't created. |
|
||||
| `load_performance` | `LOAD_PERFORMANCE_DISABLED` | From GitLab 13.2 | If the variable is present, the job isn't created. |
|
||||
| `nodejs-scan-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `performance` | `PERFORMANCE_DISABLED` | From GitLab 11.0 | Browser performance. If the variable is present, the job isn't created. |
|
||||
| `phpcs-security-audit-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `pmd-apex-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `retire-js-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `review` | `REVIEW_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
|
||||
| `review:stop` | `REVIEW_DISABLED` | From GitLab 11.0 | Manual job. If the variable is present, the job isn't created. |
|
||||
| `sast` | `SAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
|
||||
| `sast:container` | `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
|
||||
| `secret_detection` | `SECRET_DETECTION_DISABLED` | From GitLab 13.1 | If the variable is present, the job isn't created. |
|
||||
| `secret_detection_default_branch` | `SECRET_DETECTION_DISABLED` | [From GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | If the variable is present, the job isn't created. |
|
||||
| `security-code-scan-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `secrets-sast` | `SAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
|
||||
| `sobelaw-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `stop_dast_environment` | `DAST_DISABLED_FOR_DEFAULT_BRANCH` or `DAST_DISABLED` | [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17789) | If either variable is present, the job isn't created. |
|
||||
| `spotbugs-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
| `test` | `TEST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. |
|
||||
| `staging` | `STAGING_ENABLED` | | The job is created if the variable is present. |
|
||||
| `stop_review` | `REVIEW_DISABLED` | | If the variable is present, the job won't be created. |
|
||||
| `stop_review` | `REVIEW_DISABLED` | | If the variable is present, the job isn't created. |
|
||||
|
||||
### Application secret variables
|
||||
|
||||
|
|
|
@ -720,45 +720,43 @@ Repeat this configuration for each profile as needed.
|
|||
|
||||
## Running your first scan
|
||||
|
||||
When configured correctly, a CI/CD pipeline contains a `Fuzz` stage and a `apifuzzer_fuzz` job. The
|
||||
job only fails when an invalid configuration is provided. During normal operation, the job always
|
||||
succeeds even if faults are identified during fuzz testing.
|
||||
When configured correctly, a CI/CD pipeline contains a `fuzz` stage and an `apifuzzer_fuzz` or
|
||||
`apifuzzer_fuzz_dnd` job. The job only fails when an invalid configuration is provided. During
|
||||
normal operation, the job always succeeds even if faults are identified during fuzz testing.
|
||||
|
||||
Faults are displayed on the **Tests** pipeline tab with the suite name **API-Fuzzing**. The **Name**
|
||||
field on the **Tests** page includes the fuzz-tested operation and parameter. The **Trace** field
|
||||
contains a writeup of the identified fault. This writeup contains information on what the fuzzer
|
||||
tested and how it detected something wrong.
|
||||
Faults are displayed on the **Security** pipeline tab with the suite name. When testing against the
|
||||
repositories default branch, the fuzzing faults are also shown on the Security & Compliance's
|
||||
Vulnerability Report page.
|
||||
|
||||
To prevent an excessive number of reported faults, the API fuzzing scanner limits the number of
|
||||
faults it reports to one per parameter.
|
||||
faults it reports.
|
||||
|
||||
### Fault Writeup
|
||||
## Viewing fuzzing faults
|
||||
|
||||
The faults that API fuzzing finds aren't associated with a specific vulnerability type. They require
|
||||
investigation to determine what type of issue they are and if they should be fixed. See
|
||||
[handling false positives](#handling-false-positives) for information about configuration changes
|
||||
you can make to limit the number of false positives reported.
|
||||
The API Fuzzing analyzer produces a JSON report that is collected and used
|
||||
[to populate the faults into GitLab's vulnerability screens](../index.md#view-details-of-an-api-fuzzing-vulnerability).
|
||||
Fuzzing faults show up as vulnerabilities with a severity of Unknown.
|
||||
|
||||
This table contains a description of fields in an API fuzzing fault writeup.
|
||||
The faults that API fuzzing finds require manual investigation and aren't associated with a specific
|
||||
vulnerability type. They require investigation to determine if they are a security issue, and if
|
||||
they should be fixed. See [handling false positives](#handling-false-positives)
|
||||
for information about configuration changes you can make to limit the number of false positives
|
||||
reported.
|
||||
|
||||
| Writeup Item | Description |
|
||||
|:-------------|:------------|
|
||||
| Operation | The operation tested. |
|
||||
| Parameter | The field modified. This can be a path segment, header, query string, or body element. |
|
||||
| Endpoint | The endpoint being tested. |
|
||||
| Check | Check module producing the test. Checks can be turned on and off. |
|
||||
| Assert | Assert module that detected a failure. Assertions can be configured and turned on and off. |
|
||||
| CWE | Fuzzing faults always have the same CWE. |
|
||||
| OWASP | Fuzzing faults always have the same OWASP ID. |
|
||||
| Exploitability | Fuzzing faults always have an `unknown` exploitability. |
|
||||
| Impact | Fuzzing faults always have an `unknown` risk impact. |
|
||||
| Description | Verbose description of what the check did. Includes the original parameter value and the modified (mutated) value. |
|
||||
| Detection | Why a failure was detected and reported. This is related to the Assert that was used. |
|
||||
| Original Request | The original, unmodified HTTP request. Useful when reviewing the actual request to see what changes were made. |
|
||||
| Actual Request | The request that produced the failure. This request has been modified in some way by the Check logic. |
|
||||
| Actual Response | The response to the actual request. |
|
||||
| Recorded Request | An unmodified request. |
|
||||
| Recorded Response | The response to the unmodified request. You can compare this with the actual request when triaging this fault. |
|
||||
For additional information, see
|
||||
[View details of an API Fuzzing vulnerability](../index.md#view-details-of-an-api-fuzzing-vulnerability).
|
||||
|
||||
### Security Dashboard
|
||||
|
||||
Fuzzing faults show up as vulnerabilities with a severity of Unknown. The Security Dashboard is a
|
||||
good place to get an overview of all the security vulnerabilities in your groups, projects and
|
||||
pipelines. For more information, see the [Security Dashboard documentation](../security_dashboard/index.md).
|
||||
|
||||
### Interacting with the vulnerabilities
|
||||
|
||||
Fuzzing faults show up as vulnerabilities with a severity of Unknown.
|
||||
Once a fault is found, you can interact with it. Read more on how to
|
||||
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
|
||||
|
||||
## Handling False Positives
|
||||
|
||||
|
|
|
@ -201,6 +201,43 @@ authorization credentials. By default, content of specific headers are masked in
|
|||
reports. You can specify the list of all headers to be masked. For details, see
|
||||
[Hide sensitive information](dast/index.md#hide-sensitive-information).
|
||||
|
||||
### View details of an API Fuzzing vulnerability
|
||||
|
||||
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.7.
|
||||
|
||||
Faults detected by API Fuzzing occur in the live web application, and require manual investigation
|
||||
to determine if they are vulnerabilities. Fuzzing faults are included as vulnerabilities with a
|
||||
severity of Unknown. To facilitate investigation of the fuzzing faults, detailed information is
|
||||
provided about the HTTP messages sent and received along with a description of the modification(s)
|
||||
made.
|
||||
|
||||
Follow these steps to view details of a fuzzing fault:
|
||||
|
||||
1. You can view faults in a project, or a merge request:
|
||||
|
||||
- In a project, go to the project's **{shield}** **Security & Compliance > Vulnerability Report**
|
||||
page. This page shows all vulnerabilities from the default branch only.
|
||||
- In a merge request, go the merge request's **Security** section and click the **Expand**
|
||||
button. API Fuzzing faults are available in a section labeled
|
||||
**API Fuzzing detected N potential vulnerabilities**. Click the title to display the fault
|
||||
details.
|
||||
|
||||
1. Click the fault's title to display the fault's details. The table below describes these details.
|
||||
|
||||
| Field | Description |
|
||||
|:-----------------|:------------------------------------------------------------------ |
|
||||
| Description | Description of the fault including what was modified. |
|
||||
| Project | Namespace and project in which the vulnerability was detected. |
|
||||
| Method | HTTP method used to detect the vulnerability. |
|
||||
| URL | URL at which the vulnerability was detected. |
|
||||
| Request | The HTTP request that caused the fault. |
|
||||
| Unmodified Response | Response from an unmodified request. This is what a normal working response looks like. |
|
||||
| Actual Response | Response received from fuzzed request. |
|
||||
| Evidence | How we determined a fault occurred. |
|
||||
| Identifiers | The fuzzing check used to find this fault. |
|
||||
| Severity | Severity of the finding is always Unknown. |
|
||||
| Scanner Type | Scanner used to perform testing. |
|
||||
|
||||
### Dismissing a vulnerability
|
||||
|
||||
To dismiss a vulnerability, you must set its status to Dismissed. This dismisses the vulnerability
|
||||
|
|
|
@ -509,50 +509,38 @@ NOTE:
|
|||
See [Rate limits](../../security/rate_limits.md) for administrator
|
||||
documentation.
|
||||
|
||||
IP blocks usually happen when GitLab.com receives unusual traffic from a single
|
||||
IP address that the system views as potentially malicious based on rate limit
|
||||
settings. After the unusual traffic ceases, the IP address is automatically
|
||||
released depending on the type of block, as described below.
|
||||
When a request is rate limited, GitLab responds with a `429` status
|
||||
code. The client should wait before attempting the request again. There
|
||||
are also informational headers with this response detailed in [rate
|
||||
limiting responses](#rate-limiting-responses).
|
||||
|
||||
If you receive a `403 Forbidden` error for all requests to GitLab.com, please
|
||||
check for any automated processes that may be triggering a block. For
|
||||
assistance, contact [GitLab Support](https://support.gitlab.com/hc/en-us)
|
||||
with details, such as the affected IP address.
|
||||
The following table describes the rate limits for GitLab.com, both before and
|
||||
after the limits change in January, 2021:
|
||||
|
||||
### HAProxy API throttle
|
||||
| Rate limit | Before 2021-01-18 | From 2021-01-18 |
|
||||
|:--------------------------------------------------------------------------|:----------------------------|:------------------------------|
|
||||
| **Protected paths** (for a given **IP address**) | **10** requests per minute | **10** requests per minute |
|
||||
| **Raw endpoint** traffic (for a given **project, commit, and file path**) | **300** requests per minute | **300** requests per minute |
|
||||
| **Unauthenticated** traffic (from a given **IP address**) | No specific limit | **500** requests per minute |
|
||||
| **Authenticated** API traffic (for a given **user**) | No specific limit | **2,000** requests per minute |
|
||||
| **Authenticated** non-API HTTP traffic (for a given **user**) | No specific limit | **1,000** requests per minute |
|
||||
| **All** traffic (from a given **IP address**) | **600** requests per minute | **2,000** requests per minute |
|
||||
|
||||
GitLab.com responds with HTTP status code `429` to API requests that exceed 10
|
||||
requests
|
||||
per second per IP address.
|
||||
More details are available on the rate limits for [protected
|
||||
paths](#protected-paths-throttle) and [raw
|
||||
endpoints](../../user/admin_area/settings/rate_limits_on_raw_endpoints.md).
|
||||
|
||||
The following example headers are included for all API requests:
|
||||
### Rate limiting responses
|
||||
|
||||
```plaintext
|
||||
RateLimit-Limit: 600
|
||||
RateLimit-Observed: 6
|
||||
RateLimit-Remaining: 594
|
||||
RateLimit-Reset: 1563325137
|
||||
RateLimit-ResetTime: Wed, 17 Jul 2019 00:58:57 GMT
|
||||
```
|
||||
The [`Retry-After`
|
||||
header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After)
|
||||
indicates when the client should retry.
|
||||
|
||||
Source:
|
||||
Rate limits applied by HAProxy (instead of Cloudflare or the
|
||||
GitLab application) have `RateLimit-Reset` and `RateLimit-ResetTime`
|
||||
headers.
|
||||
|
||||
- Search for `rate_limit_http_rate_per_minute` and `rate_limit_sessions_per_second` in [GitLab.com's current HAProxy settings](https://gitlab.com/gitlab-cookbooks/gitlab-haproxy/blob/master/attributes/default.rb).
|
||||
|
||||
### Pagination response headers
|
||||
|
||||
For performance reasons, if a query returns more than 10,000 records, GitLab
|
||||
doesn't return the following headers:
|
||||
|
||||
- `x-total`.
|
||||
- `x-total-pages`.
|
||||
- `rel="last"` `link`.
|
||||
|
||||
### Rack Attack initializer
|
||||
|
||||
Details of rate limits enforced by [Rack Attack](../../security/rack_attack.md).
|
||||
|
||||
#### Protected paths throttle
|
||||
### Protected paths throttle
|
||||
|
||||
GitLab.com responds with HTTP status code `429` to POST requests at protected
|
||||
paths that exceed 10 requests per **minute** per IP address.
|
||||
|
@ -568,6 +556,18 @@ Retry-After: 60
|
|||
|
||||
See [Protected Paths](../admin_area/settings/protected_paths.md) for more details.
|
||||
|
||||
### IP blocks
|
||||
|
||||
IP blocks can occur when GitLab.com receives unusual traffic from a single
|
||||
IP address that the system views as potentially malicious, based on rate limit
|
||||
settings. After the unusual traffic ceases, the IP address is automatically
|
||||
released depending on the type of block, as described in a following section.
|
||||
|
||||
If you receive a `403 Forbidden` error for all requests to GitLab.com,
|
||||
check for any automated processes that may be triggering a block. For
|
||||
assistance, contact [GitLab Support](https://support.gitlab.com/hc/en-us)
|
||||
with details, such as the affected IP address.
|
||||
|
||||
#### Git and container registry failed authentication ban
|
||||
|
||||
GitLab.com responds with HTTP status code `403` for 1 hour, if 30 failed
|
||||
|
@ -585,13 +585,14 @@ This limit:
|
|||
|
||||
No response headers are provided.
|
||||
|
||||
### Admin Area settings
|
||||
### Pagination response headers
|
||||
|
||||
GitLab.com:
|
||||
For performance reasons, if a query returns more than 10,000 records, GitLab
|
||||
doesn't return the following headers:
|
||||
|
||||
- Has [rate limits on raw endpoints](../../user/admin_area/settings/rate_limits_on_raw_endpoints.md)
|
||||
set to the default.
|
||||
- Does not have the user and IP rate limits settings enabled.
|
||||
- `x-total`.
|
||||
- `x-total-pages`.
|
||||
- `rel="last"` `link`.
|
||||
|
||||
### Visibility settings
|
||||
|
||||
|
|
|
@ -57,8 +57,6 @@ You can use GitLab as a source for your Docker images.
|
|||
Prerequisites:
|
||||
|
||||
- Your images must be stored on [Docker Hub](https://hub.docker.com/).
|
||||
- Docker Hub must be available. Follow [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/241639)
|
||||
for progress on accessing images when Docker Hub is down.
|
||||
|
||||
### Authenticate with the Dependency Proxy
|
||||
|
||||
|
@ -119,6 +117,12 @@ dependency-proxy-pull-master:
|
|||
- docker pull "$CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX"/alpine:latest
|
||||
```
|
||||
|
||||
`CI_DEPENDENCY_PROXY_SERVER` and `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` include the server port. So if you use `CI_DEPENDENCY_PROXY_SERVER` to log in, for example, you must explicitly include the port in your pull command and vice-versa:
|
||||
|
||||
```shell
|
||||
docker pull gitlab.example.com:443/my-group/dependency_proxy/containers/alpine:latest
|
||||
```
|
||||
|
||||
You can also use [custom environment variables](../../../ci/variables/README.md#custom-environment-variables) to store and access your personal access token or other valid credentials.
|
||||
|
||||
##### Authenticate with `DOCKER_AUTH_CONFIG`
|
||||
|
@ -157,11 +161,23 @@ named `DOCKER_AUTH_CONFIG` with a value of:
|
|||
}
|
||||
```
|
||||
|
||||
To use `$CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` when referencing images, you must explicitly include the port in your `DOCKER_AUTH_CONFIG` value:
|
||||
|
||||
```json
|
||||
{
|
||||
"auths": {
|
||||
"https://gitlab.example.com:443": {
|
||||
"auth": "(Base64 content from above)"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. Now reference the Dependency Proxy in your base image:
|
||||
|
||||
```yaml
|
||||
# .gitlab-ci.yml
|
||||
image: "$CI_SERVER_HOST":"$CI_SERVER_PORT"/groupname/dependency_proxy/containers/node:latest
|
||||
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/node:latest
|
||||
...
|
||||
```
|
||||
|
||||
|
|
|
@ -10581,6 +10581,9 @@ msgstr ""
|
|||
msgid "End Time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ends at %{endsAt}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ends at (UTC)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19349,9 +19352,15 @@ msgstr ""
|
|||
msgid "OnCallSchedules|Create on-call schedules in GitLab"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|Delete rotation"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|Delete schedule"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|Edit rotation"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|Edit schedule"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19367,7 +19376,7 @@ msgstr ""
|
|||
msgid "OnCallSchedules|On-call schedule"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|On-call schedule for the %{tzShort}"
|
||||
msgid "OnCallSchedules|On-call schedule for the %{timezone}"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|Rotation length"
|
||||
|
@ -19720,9 +19729,6 @@ msgstr ""
|
|||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
msgid "PST"
|
||||
msgstr ""
|
||||
|
||||
msgid "Package Registry"
|
||||
msgstr ""
|
||||
|
||||
|
@ -26399,6 +26405,9 @@ msgstr ""
|
|||
msgid "Starts %{startsIn}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Starts at %{startsAt}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Starts at (UTC)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -27201,6 +27210,12 @@ msgstr ""
|
|||
msgid "Terraform|An error occurred while loading your Terraform States"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Are you sure you want to remove the Terraform State %{name}?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Details"
|
||||
msgstr ""
|
||||
|
||||
|
@ -27234,6 +27249,12 @@ msgstr ""
|
|||
msgid "Terraform|Pipeline"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Remove"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Remove state file and versions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
|
||||
msgstr ""
|
||||
|
||||
|
@ -27246,12 +27267,18 @@ msgstr ""
|
|||
msgid "Terraform|The Terraform report %{name} was generated in your pipelines."
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|To remove the State file and its versions, type %{name} to confirm:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Unknown User"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Unlock"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|You are about to remove the State file %{name}. This will permanently delete all the State versions and history. The infrastructure provisioned previously\twill remain intact, only the state file with all its versions are to be removed. This action is non-revertible."
|
||||
msgstr ""
|
||||
|
||||
msgid "Test"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -16,121 +16,4 @@ RSpec.describe Profiles::GpgKeysController do
|
|||
end.to change { GpgKey.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#get_keys" do
|
||||
describe "non existent user" do
|
||||
it "does not generally work" do
|
||||
get :get_keys, params: { username: 'not-existent' }
|
||||
|
||||
expect(response).not_to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
describe "user with no keys" do
|
||||
it "does generally work" do
|
||||
get :get_keys, params: { username: user.username }
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it "renders all keys separated with a new line" do
|
||||
get :get_keys, params: { username: user.username }
|
||||
|
||||
expect(response.body).to eq("")
|
||||
end
|
||||
|
||||
it "responds with text/plain content type" do
|
||||
get :get_keys, params: { username: user.username }
|
||||
|
||||
expect(response.content_type).to eq("text/plain")
|
||||
end
|
||||
end
|
||||
|
||||
describe "user with keys" do
|
||||
let!(:gpg_key) { create(:gpg_key, user: user) }
|
||||
let!(:another_gpg_key) { create(:another_gpg_key, user: user) }
|
||||
|
||||
describe "while signed in" do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "does generally work" do
|
||||
get :get_keys, params: { username: user.username }
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it "renders all verified keys separated with a new line" do
|
||||
get :get_keys, params: { username: user.username }
|
||||
|
||||
expect(response.body).not_to eq('')
|
||||
expect(response.body).to eq(user.gpg_keys.select(&:verified?).map(&:key).join("\n"))
|
||||
|
||||
expect(response.body).to include(gpg_key.key)
|
||||
expect(response.body).to include(another_gpg_key.key)
|
||||
end
|
||||
|
||||
it "responds with text/plain content type" do
|
||||
get :get_keys, params: { username: user.username }
|
||||
|
||||
expect(response.content_type).to eq("text/plain")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when logged out' do
|
||||
before do
|
||||
sign_out(user)
|
||||
end
|
||||
|
||||
it "still does generally work" do
|
||||
get :get_keys, params: { username: user.username }
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it "renders all verified keys separated with a new line" do
|
||||
get :get_keys, params: { username: user.username }
|
||||
|
||||
expect(response.body).not_to eq('')
|
||||
expect(response.body).to eq(user.gpg_keys.map(&:key).join("\n"))
|
||||
|
||||
expect(response.body).to include(gpg_key.key)
|
||||
expect(response.body).to include(another_gpg_key.key)
|
||||
end
|
||||
|
||||
it "responds with text/plain content type" do
|
||||
get :get_keys, params: { username: user.username }
|
||||
|
||||
expect(response.content_type).to eq("text/plain")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when revoked' do
|
||||
before do
|
||||
sign_in(user)
|
||||
another_gpg_key.revoke
|
||||
end
|
||||
|
||||
it "doesn't render revoked keys" do
|
||||
get :get_keys, params: { username: user.username }
|
||||
|
||||
expect(response.body).not_to eq('')
|
||||
|
||||
expect(response.body).to include(gpg_key.key)
|
||||
expect(response.body).not_to include(another_gpg_key.key)
|
||||
end
|
||||
|
||||
it "doesn't render revoked keys for non-authorized users" do
|
||||
sign_out(user)
|
||||
get :get_keys, params: { username: user.username }
|
||||
|
||||
expect(response.body).not_to eq('')
|
||||
|
||||
expect(response.body).to include(gpg_key.key)
|
||||
expect(response.body).not_to include(another_gpg_key.key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,7 +38,7 @@ RSpec.describe Projects::Alerting::NotificationsController do
|
|||
|
||||
expect(notify_service_class)
|
||||
.to have_received(:new)
|
||||
.with(project, nil, permitted_params)
|
||||
.with(project, permitted_params)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -168,7 +168,7 @@ RSpec.describe Projects::Prometheus::AlertsController do
|
|||
|
||||
expect(Projects::Prometheus::Alerts::NotifyService)
|
||||
.to receive(:new)
|
||||
.with(project, nil, duck_type(:permitted?))
|
||||
.with(project, duck_type(:permitted?))
|
||||
.and_return(notify_service)
|
||||
end
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe UsersController do
|
||||
let(:user) { create(:user) }
|
||||
# This user should have the same e-mail address associated with the GPG key prepared for tests
|
||||
let(:user) { create(:user, email: GpgHelpers::User1.emails[0]) }
|
||||
let(:private_user) { create(:user, private_profile: true) }
|
||||
let(:public_user) { create(:user) }
|
||||
|
||||
|
@ -326,6 +327,123 @@ RSpec.describe UsersController do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#gpg_keys" do
|
||||
describe "non existent user" do
|
||||
it "does not generally work" do
|
||||
get :gpg_keys, params: { username: 'not-existent' }
|
||||
|
||||
expect(response).not_to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
describe "user with no keys" do
|
||||
it "does generally work" do
|
||||
get :gpg_keys, params: { username: user.username }
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it "renders all keys separated with a new line" do
|
||||
get :gpg_keys, params: { username: user.username }
|
||||
|
||||
expect(response.body).to eq("")
|
||||
end
|
||||
|
||||
it "responds with text/plain content type" do
|
||||
get :gpg_keys, params: { username: user.username }
|
||||
|
||||
expect(response.content_type).to eq("text/plain")
|
||||
end
|
||||
end
|
||||
|
||||
describe "user with keys" do
|
||||
let!(:gpg_key) { create(:gpg_key, user: user) }
|
||||
let!(:another_gpg_key) { create(:another_gpg_key, user: user) }
|
||||
|
||||
describe "while signed in" do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "does generally work" do
|
||||
get :gpg_keys, params: { username: user.username }
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it "renders all verified keys separated with a new line" do
|
||||
get :gpg_keys, params: { username: user.username }
|
||||
|
||||
expect(response.body).not_to eq('')
|
||||
expect(response.body).to eq(user.gpg_keys.select(&:verified?).map(&:key).join("\n"))
|
||||
|
||||
expect(response.body).to include(gpg_key.key)
|
||||
expect(response.body).to include(another_gpg_key.key)
|
||||
end
|
||||
|
||||
it "responds with text/plain content type" do
|
||||
get :gpg_keys, params: { username: user.username }
|
||||
|
||||
expect(response.content_type).to eq("text/plain")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when logged out' do
|
||||
before do
|
||||
sign_out(user)
|
||||
end
|
||||
|
||||
it "still does generally work" do
|
||||
get :gpg_keys, params: { username: user.username }
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it "renders all verified keys separated with a new line" do
|
||||
get :gpg_keys, params: { username: user.username }
|
||||
|
||||
expect(response.body).not_to eq('')
|
||||
expect(response.body).to eq(user.gpg_keys.map(&:key).join("\n"))
|
||||
|
||||
expect(response.body).to include(gpg_key.key)
|
||||
expect(response.body).to include(another_gpg_key.key)
|
||||
end
|
||||
|
||||
it "responds with text/plain content type" do
|
||||
get :gpg_keys, params: { username: user.username }
|
||||
|
||||
expect(response.content_type).to eq("text/plain")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when revoked' do
|
||||
before do
|
||||
sign_in(user)
|
||||
another_gpg_key.revoke
|
||||
end
|
||||
|
||||
it "doesn't render revoked keys" do
|
||||
get :gpg_keys, params: { username: user.username }
|
||||
|
||||
expect(response.body).not_to eq('')
|
||||
|
||||
expect(response.body).to include(gpg_key.key)
|
||||
expect(response.body).not_to include(another_gpg_key.key)
|
||||
end
|
||||
|
||||
it "doesn't render revoked keys for non-authorized users" do
|
||||
sign_out(user)
|
||||
get :gpg_keys, params: { username: user.username }
|
||||
|
||||
expect(response.body).not_to eq('')
|
||||
|
||||
expect(response.body).to include(gpg_key.key)
|
||||
expect(response.body).not_to include(another_gpg_key.key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #calendar' do
|
||||
context 'for user' do
|
||||
let(:project) { create(:project) }
|
||||
|
|
|
@ -54,6 +54,24 @@ RSpec.describe 'Terraform', :js do
|
|||
expect(page).to have_content(terraform_state.name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when clicking on the delete button' do
|
||||
let(:additional_state) { create(:terraform_state, project: project) }
|
||||
|
||||
it 'removes the state', :aggregate_failures do
|
||||
visit project_terraform_index_path(project)
|
||||
|
||||
expect(page).to have_content(additional_state.name)
|
||||
|
||||
find("[data-testid='terraform-state-actions-#{additional_state.name}']").click
|
||||
find('[data-testid="terraform-state-remove"]').click
|
||||
fill_in "terraform-state-remove-input-#{additional_state.name}", with: additional_state.name
|
||||
click_button 'Remove'
|
||||
|
||||
expect(page).not_to have_content(additional_state.name)
|
||||
expect { additional_state.reload }.to raise_error ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
12
spec/frontend/helpers/stub_component.js
Normal file
12
spec/frontend/helpers/stub_component.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
export function stubComponent(Component, options = {}) {
|
||||
return {
|
||||
props: Component.props,
|
||||
model: Component.model,
|
||||
// Do not render any slots/scoped slots except default
|
||||
// This differs from VTU behavior which renders all slots
|
||||
template: '<div><slot></slot></div>',
|
||||
// allows wrapper.find(Component) to work for stub
|
||||
$_vueTestUtils_original: Component,
|
||||
...options,
|
||||
};
|
||||
}
|
|
@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
|
|||
import { nextTick } from 'vue';
|
||||
import { GlTokenSelector } from '@gitlab/ui';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import Api from '~/api';
|
||||
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
|
||||
|
||||
|
@ -17,6 +18,9 @@ const createComponent = () => {
|
|||
ariaLabelledby: label,
|
||||
placeholder,
|
||||
},
|
||||
stubs: {
|
||||
GlTokenSelector: stubComponent(GlTokenSelector),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import { GlEmptyState } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
|
||||
import { metricStates } from '~/monitoring/constants';
|
||||
|
||||
const MockGlEmptyState = {
|
||||
props: GlEmptyState.props,
|
||||
template: '<div><slot name="description"></slot></div>',
|
||||
};
|
||||
|
||||
function createComponent(props) {
|
||||
return shallowMount(GroupEmptyState, {
|
||||
propsData: {
|
||||
|
@ -17,7 +13,9 @@ function createComponent(props) {
|
|||
svgPath: '/path/to/empty-group-illustration.svg',
|
||||
},
|
||||
stubs: {
|
||||
GlEmptyState: MockGlEmptyState,
|
||||
GlEmptyState: stubComponent(GlEmptyState, {
|
||||
template: '<div><slot name="description"></slot></div>',
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -47,7 +45,7 @@ describe('GroupEmptyState', () => {
|
|||
});
|
||||
|
||||
it('passes the expected props to GlEmptyState', () => {
|
||||
expect(wrapper.find(MockGlEmptyState).props()).toMatchSnapshot();
|
||||
expect(wrapper.find(GlEmptyState).props()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
|
||||
import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants';
|
||||
|
@ -21,10 +22,9 @@ describe('Package History', () => {
|
|||
wrapper = shallowMount(component, {
|
||||
propsData: { ...defaultProps, ...props },
|
||||
stubs: {
|
||||
HistoryItem: {
|
||||
props: HistoryItem.props,
|
||||
HistoryItem: stubComponent(HistoryItem, {
|
||||
template: '<div data-testid="history-element"><slot></slot></div>',
|
||||
},
|
||||
}),
|
||||
GlSprintf,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlIcon } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import PipelineStatusToken from '~/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue';
|
||||
|
||||
describe('Pipeline Status Token', () => {
|
||||
let wrapper;
|
||||
|
||||
const stubs = {
|
||||
GlFilteredSearchToken: {
|
||||
props: GlFilteredSearchToken.props,
|
||||
template: `<div><slot name="suggestions"></slot></div>`,
|
||||
},
|
||||
};
|
||||
|
||||
const findFilteredSearchToken = () => wrapper.find(stubs.GlFilteredSearchToken);
|
||||
const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
|
||||
const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
|
||||
const findAllGlIcons = () => wrapper.findAll(GlIcon);
|
||||
|
||||
|
@ -33,7 +27,11 @@ describe('Pipeline Status Token', () => {
|
|||
propsData: {
|
||||
...defaultProps,
|
||||
},
|
||||
stubs,
|
||||
stubs: {
|
||||
GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
|
||||
template: `<div><slot name="suggestions"></slot></div>`,
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import Api from '~/api';
|
||||
import PipelineTriggerAuthorToken from '~/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue';
|
||||
|
@ -7,14 +8,7 @@ import { users } from '../mock_data';
|
|||
describe('Pipeline Trigger Author Token', () => {
|
||||
let wrapper;
|
||||
|
||||
const stubs = {
|
||||
GlFilteredSearchToken: {
|
||||
props: GlFilteredSearchToken.props,
|
||||
template: `<div><slot name="suggestions"></slot></div>`,
|
||||
},
|
||||
};
|
||||
|
||||
const findFilteredSearchToken = () => wrapper.find(stubs.GlFilteredSearchToken);
|
||||
const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
|
||||
const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion);
|
||||
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
|
||||
|
||||
|
@ -42,7 +36,11 @@ describe('Pipeline Trigger Author Token', () => {
|
|||
...data,
|
||||
};
|
||||
},
|
||||
stubs,
|
||||
stubs: {
|
||||
GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, {
|
||||
template: `<div><slot name="suggestions"></slot></div>`,
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
exports[`TagsLoader component has the correct markup 1`] = `
|
||||
<div>
|
||||
<div
|
||||
preserve-aspect-ratio="xMinYMax meet"
|
||||
>
|
||||
<div>
|
||||
<rect
|
||||
height="15"
|
||||
rx="4"
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Registry Group Empty state to match the default snapshot 1`] = `
|
||||
<div
|
||||
svg-path="foo"
|
||||
title="There are no container images available in this group"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here.
|
||||
<gl-link-stub
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Registry Project Empty state to match the default snapshot 1`] = `
|
||||
<div
|
||||
svg-path="bazFoo"
|
||||
title="There are no container images stored for this project"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
With the Container Registry, every project can have its own space to store its Docker images.
|
||||
<gl-link-stub
|
||||
|
|
|
@ -135,13 +135,13 @@ describe('List Page', () => {
|
|||
it('empty state should have an svg-path', () => {
|
||||
mountComponent({ config });
|
||||
|
||||
expect(findEmptyState().attributes('svg-path')).toBe(config.containersErrorImage);
|
||||
expect(findEmptyState().props('svgPath')).toBe(config.containersErrorImage);
|
||||
});
|
||||
|
||||
it('empty state should have a description', () => {
|
||||
mountComponent({ config });
|
||||
|
||||
expect(findEmptyState().html()).toContain('connection error');
|
||||
expect(findEmptyState().props('title')).toContain('connection error');
|
||||
});
|
||||
|
||||
it('should not show the loading or default state', () => {
|
||||
|
|
|
@ -1,35 +1,33 @@
|
|||
import {
|
||||
GlModal as RealGlModal,
|
||||
GlEmptyState as RealGlEmptyState,
|
||||
GlSkeletonLoader as RealGlSkeletonLoader,
|
||||
} from '@gitlab/ui';
|
||||
import { RouterLinkStub } from '@vue/test-utils';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import RealDeleteModal from '~/registry/explorer/components/details_page/delete_modal.vue';
|
||||
import RealListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
|
||||
export const GlModal = {
|
||||
export const GlModal = stubComponent(RealGlModal, {
|
||||
template: '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-ok"></slot></div>',
|
||||
methods: {
|
||||
show: jest.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const GlEmptyState = {
|
||||
export const GlEmptyState = stubComponent(RealGlEmptyState, {
|
||||
template: '<div><slot name="description"></slot></div>',
|
||||
name: 'GlEmptyStateSTub',
|
||||
};
|
||||
});
|
||||
|
||||
export const RouterLink = {
|
||||
template: `<div><slot></slot></div>`,
|
||||
props: ['to'],
|
||||
};
|
||||
export const RouterLink = RouterLinkStub;
|
||||
|
||||
export const DeleteModal = {
|
||||
template: '<div></div>',
|
||||
export const DeleteModal = stubComponent(RealDeleteModal, {
|
||||
methods: {
|
||||
show: jest.fn(),
|
||||
},
|
||||
props: RealDeleteModal.props,
|
||||
};
|
||||
});
|
||||
|
||||
export const GlSkeletonLoader = {
|
||||
template: `<div><slot></slot></div>`,
|
||||
props: ['width', 'height'],
|
||||
};
|
||||
export const GlSkeletonLoader = stubComponent(RealGlSkeletonLoader);
|
||||
|
||||
export const ListItem = {
|
||||
...RealListItem,
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { GlDropdown } from '@gitlab/ui';
|
||||
import { GlDropdown, GlModal, GlSprintf } from '@gitlab/ui';
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import createMockApollo from 'jest/helpers/mock_apollo_helper';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import StateActions from '~/terraform/components/states_table_actions.vue';
|
||||
import lockStateMutation from '~/terraform/graphql/mutations/lock_state.mutation.graphql';
|
||||
import removeStateMutation from '~/terraform/graphql/mutations/remove_state.mutation.graphql';
|
||||
import unlockStateMutation from '~/terraform/graphql/mutations/unlock_state.mutation.graphql';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
@ -11,6 +12,7 @@ localVue.use(VueApollo);
|
|||
|
||||
describe('StatesTableActions', () => {
|
||||
let lockResponse;
|
||||
let removeResponse;
|
||||
let unlockResponse;
|
||||
let wrapper;
|
||||
|
||||
|
@ -26,12 +28,17 @@ describe('StatesTableActions', () => {
|
|||
const createMockApolloProvider = () => {
|
||||
lockResponse = jest.fn().mockResolvedValue({ data: { terraformStateLock: { errors: [] } } });
|
||||
|
||||
removeResponse = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ data: { terraformStateDelete: { errors: [] } } });
|
||||
|
||||
unlockResponse = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ data: { terraformStateUnlock: { errors: [] } } });
|
||||
|
||||
return createMockApollo([
|
||||
[lockStateMutation, lockResponse],
|
||||
[removeStateMutation, removeResponse],
|
||||
[unlockStateMutation, unlockResponse],
|
||||
]);
|
||||
};
|
||||
|
@ -43,7 +50,7 @@ describe('StatesTableActions', () => {
|
|||
apolloProvider,
|
||||
localVue,
|
||||
propsData,
|
||||
stubs: { GlDropdown },
|
||||
stubs: { GlDropdown, GlModal, GlSprintf },
|
||||
});
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
|
@ -52,6 +59,8 @@ describe('StatesTableActions', () => {
|
|||
const findLockBtn = () => wrapper.find('[data-testid="terraform-state-lock"]');
|
||||
const findUnlockBtn = () => wrapper.find('[data-testid="terraform-state-unlock"]');
|
||||
const findDownloadBtn = () => wrapper.find('[data-testid="terraform-state-download"]');
|
||||
const findRemoveBtn = () => wrapper.find('[data-testid="terraform-state-remove"]');
|
||||
const findRemoveModal = () => wrapper.find(GlModal);
|
||||
|
||||
beforeEach(() => {
|
||||
return createComponent();
|
||||
|
@ -59,6 +68,7 @@ describe('StatesTableActions', () => {
|
|||
|
||||
afterEach(() => {
|
||||
lockResponse = null;
|
||||
removeResponse = null;
|
||||
unlockResponse = null;
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
@ -137,4 +147,43 @@ describe('StatesTableActions', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove button', () => {
|
||||
it('displays a remove button', () => {
|
||||
expect(findRemoveBtn().text()).toBe(StateActions.i18n.remove);
|
||||
});
|
||||
|
||||
describe('when clicking the remove button', () => {
|
||||
beforeEach(() => {
|
||||
findRemoveBtn().vm.$emit('click');
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('displays a remove modal', () => {
|
||||
expect(findRemoveModal().text()).toContain(
|
||||
`You are about to remove the State file ${defaultProps.state.name}`,
|
||||
);
|
||||
});
|
||||
|
||||
describe('when submitting the remove modal', () => {
|
||||
it('does not call the remove mutation when state name is missing', async () => {
|
||||
findRemoveModal().vm.$emit('ok');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(removeResponse).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('calls the remove mutation when state name is present', async () => {
|
||||
await wrapper.setData({ removeConfirmText: defaultProps.state.name });
|
||||
|
||||
findRemoveModal().vm.$emit('ok');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(removeResponse).toHaveBeenCalledWith({
|
||||
stateID: defaultProps.state.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,7 +29,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
|
|||
total_time_spent reference author merged_at commit_count current_user_todos
|
||||
conflicts auto_merge_enabled approved_by source_branch_protected
|
||||
default_merge_commit_message_with_description squash_on_merge available_auto_merge_strategies
|
||||
has_ci mergeable commits_without_merge_commits
|
||||
has_ci mergeable commits_without_merge_commits security_auto_fix
|
||||
]
|
||||
|
||||
if Gitlab.ee?
|
||||
|
|
|
@ -49,5 +49,14 @@ RSpec.describe Emails::Releases do
|
|||
is_expected.to have_body_text('Release notes:')
|
||||
is_expected.to have_body_text(release.description)
|
||||
end
|
||||
|
||||
context 'release notes with attachment' do
|
||||
let(:upload_path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' }
|
||||
let(:release) { create(:release, project: project, description: "Attachment: [Test file](#{upload_path})") }
|
||||
|
||||
it 'renders absolute links' do
|
||||
is_expected.to have_body_text(%Q(<a href="#{project.web_url}#{upload_path}" data-link="true" class="gfm">Test file</a>))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
|||
|
||||
# user GET /:username
|
||||
# user_ssh_keys GET /:username.keys
|
||||
# user_gpg_keys GET /:username.gpg
|
||||
# user_groups GET /users/:username/groups(.:format)
|
||||
# user_projects GET /users/:username/projects(.:format)
|
||||
# user_contributed_projects GET /users/:username/contributed(.:format)
|
||||
|
@ -17,6 +18,12 @@ RSpec.describe UsersController, "routing" do
|
|||
expect(get("/User")).to route_to('users#show', username: 'User')
|
||||
end
|
||||
|
||||
it "to #gpg_keys" do
|
||||
allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
|
||||
|
||||
expect(get("/User.gpg")).to route_to('users#gpg_keys', username: 'User')
|
||||
end
|
||||
|
||||
it "to #groups" do
|
||||
expect(get("/users/User/groups")).to route_to('users#groups', username: 'User')
|
||||
end
|
||||
|
@ -197,12 +204,6 @@ RSpec.describe Profiles::GpgKeysController, "routing" do
|
|||
it "to #destroy" do
|
||||
expect(delete("/profile/gpg_keys/1")).to route_to('profiles/gpg_keys#destroy', id: '1')
|
||||
end
|
||||
|
||||
it "to #get_keys" do
|
||||
allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true)
|
||||
|
||||
expect(get("/foo.gpg")).to route_to('profiles/gpg_keys#get_keys', username: 'foo')
|
||||
end
|
||||
end
|
||||
|
||||
# emails GET /emails(.:format) emails#index
|
||||
|
|
|
@ -10,7 +10,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
|
|||
end
|
||||
|
||||
describe '#execute' do
|
||||
let(:service) { described_class.new(project, nil, payload) }
|
||||
let(:service) { described_class.new(project, payload) }
|
||||
let(:auto_close_incident) { true }
|
||||
let(:create_issue) { true }
|
||||
let(:send_email) { true }
|
||||
|
|
|
@ -13,7 +13,7 @@ RSpec.describe Projects::Alerting::NotifyService do
|
|||
let(:token) { 'invalid-token' }
|
||||
let(:starts_at) { Time.current.change(usec: 0) }
|
||||
let(:fingerprint) { 'testing' }
|
||||
let(:service) { described_class.new(project, nil, payload) }
|
||||
let(:service) { described_class.new(project, payload) }
|
||||
let_it_be(:environment) { create(:environment, project: project) }
|
||||
let(:environment) { create(:environment, project: project) }
|
||||
let(:ended_at) { nil }
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
|
|||
|
||||
let_it_be(:project, reload: true) { create(:project) }
|
||||
|
||||
let(:service) { described_class.new(project, nil, payload) }
|
||||
let(:service) { described_class.new(project, payload) }
|
||||
let(:token_input) { 'token' }
|
||||
|
||||
let!(:setting) do
|
||||
|
@ -218,7 +218,7 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
|
|||
it 'processes Prometheus alerts' do
|
||||
expect(AlertManagement::ProcessPrometheusAlertService)
|
||||
.to receive(:new)
|
||||
.with(project, nil, kind_of(Hash))
|
||||
.with(project, kind_of(Hash))
|
||||
.exactly(3).times
|
||||
.and_return(process_service)
|
||||
expect(process_service).to receive(:execute).exactly(3).times
|
||||
|
|
|
@ -125,6 +125,9 @@ RSpec.shared_examples 'write access for a read-only GitLab instance' do
|
|||
where(:description, :path) do
|
||||
'LFS request to batch' | '/root/rouge.git/info/lfs/objects/batch'
|
||||
'request to git-upload-pack' | '/root/rouge.git/git-upload-pack'
|
||||
'user sign out' | '/users/sign_out'
|
||||
'admin session' | '/admin/session'
|
||||
'admin session destroy' | '/admin/session/destroy'
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
Loading…
Reference in a new issue