diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-18 18:09:08 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-18 18:09:08 +0000 |
commit | 7ea5ca0bb5aa9792c514a22d59217dffa3800581 (patch) | |
tree | 753d90cbdb990d5b4889990fe7e8534d030480b3 | |
parent | e26bf16ed06dd7fc959961cfe16621c19f0e6acf (diff) | |
download | gitlab-ce-7ea5ca0bb5aa9792c514a22d59217dffa3800581.tar.gz |
Add latest changes from gitlab-org/gitlab@master
97 files changed, 1718 insertions, 687 deletions
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml index b258eb73515..06fe4b7ff5e 100644 --- a/.gitlab/ci/docs.gitlab-ci.yml +++ b/.gitlab/ci/docs.gitlab-ci.yml @@ -53,7 +53,7 @@ docs-lint links: extends: - .default-retry - .docs:rules:docs-lint - image: "registry.gitlab.com/gitlab-org/gitlab-docs/lint:ruby-2.7.2-alpine-3.12-vale-2.4.3-markdownlint-0.24.0" + image: "registry.gitlab.com/gitlab-org/gitlab-docs/lint-html:alpine-3.12-ruby-2.7.2" stage: test needs: [] script: diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 092ab0d71d5..ca6c1ec391c 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -6373f0f90637d752a50a56c94edc57cc6e8a8566 +3aa577d5e9ad572a22966775126076ec4dce2bfb diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js index bd39aa2e16f..2532aeea989 100644 --- a/app/assets/javascripts/blob/file_template_selector.js +++ b/app/assets/javascripts/blob/file_template_selector.js @@ -12,7 +12,10 @@ export default class FileTemplateSelector { this.$dropdown = $(cfg.dropdown); this.$wrapper = $(cfg.wrapper); - this.$loadingIcon = this.$wrapper.find('.fa-chevron-down'); + this.$dropdownIcon = this.$wrapper.find('.dropdown-menu-toggle-icon'); + this.$loadingIcon = $( + '<div class="gl-spinner gl-spinner-orange gl-spinner-sm gl-absolute gl-top-3 gl-right-3 gl-display-none"></div>', + ).insertAfter(this.$dropdownIcon); this.$dropdownToggleText = this.$wrapper.find('.dropdown-toggle-text'); this.initDropdown(); @@ -45,15 +48,13 @@ export default class FileTemplateSelector { } renderLoading() { - this.$loadingIcon - .addClass('gl-spinner gl-spinner-orange gl-spinner-sm') - .removeClass('fa-chevron-down'); + this.$loadingIcon.removeClass('gl-display-none'); + this.$dropdownIcon.addClass('gl-display-none'); } renderLoaded() { - this.$loadingIcon - .addClass('fa-chevron-down') - .removeClass('gl-spinner gl-spinner-orange gl-spinner-sm'); + this.$loadingIcon.addClass('gl-display-none'); + this.$dropdownIcon.removeClass('gl-display-none'); } reportSelection(options) { diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js index 257458138dc..ae9bb3455f0 100644 --- a/app/assets/javascripts/blob/template_selector.js +++ b/app/assets/javascripts/blob/template_selector.js @@ -10,7 +10,10 @@ export default class TemplateSelector { this.dropdown = dropdown; this.$dropdownContainer = wrapper; this.$filenameInput = $input || $('#file_name'); - this.$dropdownIcon = $('.fa-chevron-down', dropdown); + this.$dropdownIcon = $('.dropdown-menu-toggle-icon', dropdown); + this.$loadingIcon = $( + '<div class="gl-spinner gl-spinner-orange gl-spinner-sm gl-absolute gl-top-3 gl-right-3 gl-display-none"></div>', + ).insertAfter(this.$dropdownIcon); this.initDropdown(dropdown, data); this.listenForFilenameInput(); @@ -92,10 +95,12 @@ export default class TemplateSelector { } startLoadingSpinner() { - this.$dropdownIcon.addClass('spinner').removeClass('fa-chevron-down'); + this.$loadingIcon.removeClass('gl-display-none'); + this.$dropdownIcon.addClass('gl-display-none'); } stopLoadingSpinner() { - this.$dropdownIcon.addClass('fa-chevron-down').removeClass('spinner'); + this.$loadingIcon.addClass('gl-display-none'); + this.$dropdownIcon.removeClass('gl-display-none'); } } diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index e1d2895831a..f1dc855362b 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -5,10 +5,8 @@ import { WEBIDE_MARK_APP_START, WEBIDE_MARK_FILE_FINISH, WEBIDE_MARK_FILE_CLICKED, - WEBIDE_MARK_TREE_FINISH, - WEBIDE_MEASURE_TREE_FROM_REQUEST, - WEBIDE_MEASURE_FILE_FROM_REQUEST, WEBIDE_MEASURE_FILE_AFTER_INTERACTION, + WEBIDE_MEASURE_BEFORE_VUE, } from '~/performance/constants'; import { performanceMarkAndMeasure } from '~/performance/utils'; import { modalTypes } from '../constants'; @@ -19,12 +17,6 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { measurePerformance } from '../utils'; -eventHub.$on(WEBIDE_MEASURE_TREE_FROM_REQUEST, () => - measurePerformance(WEBIDE_MARK_TREE_FINISH, WEBIDE_MEASURE_TREE_FROM_REQUEST), -); -eventHub.$on(WEBIDE_MEASURE_FILE_FROM_REQUEST, () => - measurePerformance(WEBIDE_MARK_FILE_FINISH, WEBIDE_MEASURE_FILE_FROM_REQUEST), -); eventHub.$on(WEBIDE_MEASURE_FILE_AFTER_INTERACTION, () => measurePerformance( WEBIDE_MARK_FILE_FINISH, @@ -84,7 +76,14 @@ export default { document.querySelector('.navbar-gitlab').classList.add(`theme-${this.themeName}`); }, beforeCreate() { - performanceMarkAndMeasure({ mark: WEBIDE_MARK_APP_START }); + performanceMarkAndMeasure({ + mark: WEBIDE_MARK_APP_START, + measures: [ + { + name: WEBIDE_MEASURE_BEFORE_VUE, + }, + ], + }); }, methods: { ...mapActions(['toggleFileFinder']), diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue index e7e94f5b5da..b67881b14f4 100644 --- a/app/assets/javascripts/ide/components/ide_tree_list.vue +++ b/app/assets/javascripts/ide/components/ide_tree_list.vue @@ -2,17 +2,13 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; import FileTree from '~/vue_shared/components/file_tree.vue'; -import { - WEBIDE_MARK_TREE_START, - WEBIDE_MEASURE_TREE_FROM_REQUEST, - WEBIDE_MARK_FILE_CLICKED, -} from '~/performance/constants'; +import { WEBIDE_MARK_FILE_CLICKED } from '~/performance/constants'; import { performanceMarkAndMeasure } from '~/performance/utils'; -import eventHub from '../eventhub'; import IdeFileRow from './ide_file_row.vue'; import NavDropdown from './nav_dropdown.vue'; export default { + name: 'IdeTreeList', components: { GlSkeletonLoading, NavDropdown, @@ -39,14 +35,6 @@ export default { } }, }, - beforeCreate() { - performanceMarkAndMeasure({ mark: WEBIDE_MARK_TREE_START }); - }, - updated() { - if (this.currentTree?.tree?.length) { - eventHub.$emit(WEBIDE_MEASURE_TREE_FROM_REQUEST); - } - }, methods: { ...mapActions(['toggleTreeOpen']), clickedFile() { diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index c8a825065f1..1f029612c29 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -6,9 +6,10 @@ import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue'; import { WEBIDE_MARK_FILE_CLICKED, - WEBIDE_MARK_FILE_START, + WEBIDE_MARK_REPO_EDITOR_START, + WEBIDE_MARK_REPO_EDITOR_FINISH, + WEBIDE_MEASURE_REPO_EDITOR, WEBIDE_MEASURE_FILE_AFTER_INTERACTION, - WEBIDE_MEASURE_FILE_FROM_REQUEST, } from '~/performance/constants'; import { performanceMarkAndMeasure } from '~/performance/utils'; import eventHub from '../eventhub'; @@ -28,6 +29,7 @@ import { getRulesWithTraversal } from '../lib/editorconfig/parser'; import mapRulesToMonaco from '../lib/editorconfig/rules_mapper'; export default { + name: 'RepoEditor', components: { ContentViewer, DiffViewer, @@ -175,9 +177,6 @@ export default { } }, }, - beforeCreate() { - performanceMarkAndMeasure({ mark: WEBIDE_MARK_FILE_START }); - }, beforeDestroy() { this.editor.dispose(); }, @@ -204,6 +203,7 @@ export default { ]), ...mapActions('editor', ['updateFileEditor']), initEditor() { + performanceMarkAndMeasure({ mark: WEBIDE_MARK_REPO_EDITOR_START }); if (this.shouldHideEditor && (this.file.content || this.file.raw)) { return; } @@ -305,7 +305,15 @@ export default { if (performance.getEntriesByName(WEBIDE_MARK_FILE_CLICKED).length) { eventHub.$emit(WEBIDE_MEASURE_FILE_AFTER_INTERACTION); } else { - eventHub.$emit(WEBIDE_MEASURE_FILE_FROM_REQUEST); + performanceMarkAndMeasure({ + mark: WEBIDE_MARK_REPO_EDITOR_FINISH, + measures: [ + { + name: WEBIDE_MEASURE_REPO_EDITOR, + start: WEBIDE_MARK_REPO_EDITOR_START, + }, + ], + }); } }, refreshEditorDimensions() { diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js index 396aedbfa10..b9ebacef7e1 100644 --- a/app/assets/javascripts/ide/ide_router.js +++ b/app/assets/javascripts/ide/ide_router.js @@ -3,6 +3,12 @@ import IdeRouter from '~/ide/ide_router_extension'; import { joinPaths } from '~/lib/utils/url_utility'; import { deprecatedCreateFlash as flash } from '~/flash'; import { __ } from '~/locale'; +import { performanceMarkAndMeasure } from '~/performance/utils'; +import { + WEBIDE_MARK_FETCH_PROJECT_DATA_START, + WEBIDE_MARK_FETCH_PROJECT_DATA_FINISH, + WEBIDE_MEASURE_FETCH_PROJECT_DATA, +} from '~/performance/constants'; import { syncRouterAndStore } from './sync_router_and_store'; Vue.use(IdeRouter); @@ -69,6 +75,7 @@ export const createRouter = store => { router.beforeEach((to, from, next) => { if (to.params.namespace && to.params.project) { + performanceMarkAndMeasure({ mark: WEBIDE_MARK_FETCH_PROJECT_DATA_START }); store .dispatch('getProjectData', { namespace: to.params.namespace, @@ -81,6 +88,15 @@ export const createRouter = store => { const mergeRequestId = to.params.mrid; if (branchId) { + performanceMarkAndMeasure({ + mark: WEBIDE_MARK_FETCH_PROJECT_DATA_FINISH, + measures: [ + { + name: WEBIDE_MEASURE_FETCH_PROJECT_DATA, + start: WEBIDE_MARK_FETCH_PROJECT_DATA_START, + }, + ], + }); store.dispatch('openBranch', { projectId, branchId, diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js index 56d48e87c18..62f49ba56b1 100644 --- a/app/assets/javascripts/ide/index.js +++ b/app/assets/javascripts/ide/index.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import { mapActions } from 'vuex'; import { identity } from 'lodash'; import Translate from '~/vue_shared/translate'; +import PerformancePlugin from '~/performance/vue_performance_plugin'; import ide from './components/ide.vue'; import { createStore } from './stores'; import { createRouter } from './ide_router'; @@ -11,6 +12,10 @@ import { DEFAULT_THEME } from './lib/themes'; Vue.use(Translate); +Vue.use(PerformancePlugin, { + components: ['FileTree'], +}); + /** * Function that receives the default store and returns an extended one. * @callback extendStoreCallback diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index 1496170447d..710256b6377 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -3,6 +3,12 @@ import { escape } from 'lodash'; import { __, sprintf } from '~/locale'; import { visitUrl } from '~/lib/utils/url_utility'; import { deprecatedCreateFlash as flash } from '~/flash'; +import { performanceMarkAndMeasure } from '~/performance/utils'; +import { + WEBIDE_MARK_FETCH_BRANCH_DATA_START, + WEBIDE_MARK_FETCH_BRANCH_DATA_FINISH, + WEBIDE_MEASURE_FETCH_BRANCH_DATA, +} from '~/performance/constants'; import * as types from './mutation_types'; import { decorateFiles } from '../lib/files'; import { stageKeys, commitActionTypes } from '../constants'; @@ -245,13 +251,23 @@ export const renameEntry = ({ dispatch, commit, state, getters }, { path, name, dispatch('triggerFilesChange', { type: commitActionTypes.move, path, newPath }); }; -export const getBranchData = ({ commit, state }, { projectId, branchId, force = false } = {}) => - new Promise((resolve, reject) => { +export const getBranchData = ({ commit, state }, { projectId, branchId, force = false } = {}) => { + return new Promise((resolve, reject) => { + performanceMarkAndMeasure({ mark: WEBIDE_MARK_FETCH_BRANCH_DATA_START }); const currentProject = state.projects[projectId]; if (!currentProject || !currentProject.branches[branchId] || force) { service .getBranchData(projectId, branchId) .then(({ data }) => { + performanceMarkAndMeasure({ + mark: WEBIDE_MARK_FETCH_BRANCH_DATA_FINISH, + measures: [ + { + name: WEBIDE_MEASURE_FETCH_BRANCH_DATA, + start: WEBIDE_MARK_FETCH_BRANCH_DATA_START, + }, + ], + }); const { id } = data.commit; commit(types.SET_BRANCH, { projectPath: projectId, @@ -291,6 +307,7 @@ export const getBranchData = ({ commit, state }, { projectId, branchId, force = resolve(currentProject.branches[branchId]); } }); +}; export * from './actions/tree'; export * from './actions/file'; diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index 4b9b958ddd6..d96c245d912 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -1,5 +1,11 @@ import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; +import { performanceMarkAndMeasure } from '~/performance/utils'; +import { + WEBIDE_MARK_FETCH_FILE_DATA_START, + WEBIDE_MARK_FETCH_FILE_DATA_FINISH, + WEBIDE_MEASURE_FETCH_FILE_DATA, +} from '~/performance/constants'; import eventHub from '../../eventhub'; import service from '../../services'; import * as types from '../mutation_types'; @@ -61,6 +67,7 @@ export const getFileData = ( { state, commit, dispatch, getters }, { path, makeFileActive = true, openFile = makeFileActive, toggleLoading = true }, ) => { + performanceMarkAndMeasure({ mark: WEBIDE_MARK_FETCH_FILE_DATA_START }); const file = state.entries[path]; const fileDeletedAndReadded = getters.isFileDeletedAndReadded(path); @@ -81,6 +88,15 @@ export const getFileData = ( return service .getFileData(url) .then(({ data }) => { + performanceMarkAndMeasure({ + mark: WEBIDE_MARK_FETCH_FILE_DATA_FINISH, + measures: [ + { + name: WEBIDE_MEASURE_FETCH_FILE_DATA, + start: WEBIDE_MARK_FETCH_FILE_DATA_START, + }, + ], + }); if (data) commit(types.SET_FILE_DATA, { data, file }); if (openFile) commit(types.TOGGLE_FILE_OPEN, path); diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js index 3a7daf30cc4..23a5e26bc1c 100644 --- a/app/assets/javascripts/ide/stores/actions/tree.js +++ b/app/assets/javascripts/ide/stores/actions/tree.js @@ -1,4 +1,10 @@ import { defer } from 'lodash'; +import { performanceMarkAndMeasure } from '~/performance/utils'; +import { + WEBIDE_MARK_FETCH_FILES_FINISH, + WEBIDE_MEASURE_FETCH_FILES, + WEBIDE_MARK_FETCH_FILES_START, +} from '~/performance/constants'; import { __ } from '../../../locale'; import service from '../../services'; import * as types from '../mutation_types'; @@ -46,8 +52,9 @@ export const setDirectoryData = ({ state, commit }, { projectId, branchId, treeL }); }; -export const getFiles = ({ state, commit, dispatch }, payload = {}) => - new Promise((resolve, reject) => { +export const getFiles = ({ state, commit, dispatch }, payload = {}) => { + performanceMarkAndMeasure({ mark: WEBIDE_MARK_FETCH_FILES_START }); + return new Promise((resolve, reject) => { const { projectId, branchId, ref = branchId } = payload; if ( @@ -61,6 +68,15 @@ export const getFiles = ({ state, commit, dispatch }, payload = {}) => service .getFiles(selectedProject.path_with_namespace, ref) .then(({ data }) => { + performanceMarkAndMeasure({ + mark: WEBIDE_MARK_FETCH_FILES_FINISH, + measures: [ + { + name: WEBIDE_MEASURE_FETCH_FILES, + start: WEBIDE_MARK_FETCH_FILES_START, + }, + ], + }); const { entries, treeList } = decorateFiles({ data }); commit(types.SET_ENTRIES, entries); @@ -85,6 +101,7 @@ export const getFiles = ({ state, commit, dispatch }, payload = {}) => resolve(); } }); +}; export const restoreTree = ({ dispatch, commit, state }, path) => { const entry = state.entries[path]; diff --git a/app/assets/javascripts/performance/constants.js b/app/assets/javascripts/performance/constants.js index 816eb9b3a66..069f3c265f3 100644 --- a/app/assets/javascripts/performance/constants.js +++ b/app/assets/javascripts/performance/constants.js @@ -19,16 +19,27 @@ export const SNIPPET_MEASURE_BLOBS_CONTENT = 'snippet-blobs-content'; // Marks export const WEBIDE_MARK_APP_START = 'webide-app-start'; -export const WEBIDE_MARK_TREE_START = 'webide-tree-start'; -export const WEBIDE_MARK_TREE_FINISH = 'webide-tree-finished'; -export const WEBIDE_MARK_FILE_START = 'webide-file-start'; export const WEBIDE_MARK_FILE_CLICKED = 'webide-file-clicked'; export const WEBIDE_MARK_FILE_FINISH = 'webide-file-finished'; +export const WEBIDE_MARK_REPO_EDITOR_START = 'webide-init-editor-start'; +export const WEBIDE_MARK_REPO_EDITOR_FINISH = 'webide-init-editor-finish'; +export const WEBIDE_MARK_FETCH_BRANCH_DATA_START = 'webide-getBranchData-start'; +export const WEBIDE_MARK_FETCH_BRANCH_DATA_FINISH = 'webide-getBranchData-finish'; +export const WEBIDE_MARK_FETCH_FILE_DATA_START = 'webide-getFileData-start'; +export const WEBIDE_MARK_FETCH_FILE_DATA_FINISH = 'webide-getFileData-finish'; +export const WEBIDE_MARK_FETCH_FILES_START = 'webide-getFiles-start'; +export const WEBIDE_MARK_FETCH_FILES_FINISH = 'webide-getFiles-finish'; +export const WEBIDE_MARK_FETCH_PROJECT_DATA_START = 'webide-getProjectData-start'; +export const WEBIDE_MARK_FETCH_PROJECT_DATA_FINISH = 'webide-getProjectData-finish'; // Measures -export const WEBIDE_MEASURE_TREE_FROM_REQUEST = 'webide-tree-loading-from-request'; -export const WEBIDE_MEASURE_FILE_FROM_REQUEST = 'webide-file-loading-from-request'; export const WEBIDE_MEASURE_FILE_AFTER_INTERACTION = 'webide-file-loading-after-interaction'; +export const WEBIDE_MEASURE_FETCH_PROJECT_DATA = 'WebIDE: Project data'; +export const WEBIDE_MEASURE_FETCH_BRANCH_DATA = 'WebIDE: Branch data'; +export const WEBIDE_MEASURE_FETCH_FILE_DATA = 'WebIDE: File data'; +export const WEBIDE_MEASURE_BEFORE_VUE = 'WebIDE: Before Vue app'; +export const WEBIDE_MEASURE_REPO_EDITOR = 'WebIDE: Repo Editor'; +export const WEBIDE_MEASURE_FETCH_FILES = 'WebIDE: Fetch Files'; // // MR Diffs namespace diff --git a/app/assets/javascripts/reports/constants.js b/app/assets/javascripts/reports/constants.js index b3905cbfcfb..b5552fd5054 100644 --- a/app/assets/javascripts/reports/constants.js +++ b/app/assets/javascripts/reports/constants.js @@ -18,9 +18,9 @@ export const ICON_SUCCESS = 'success'; export const ICON_NOTFOUND = 'notfound'; export const status = { - LOADING: 'LOADING', - ERROR: 'ERROR', - SUCCESS: 'SUCCESS', + LOADING, + ERROR, + SUCCESS, }; export const ACCESSIBILITY_ISSUE_ERROR = 'error'; diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue index 51719df313f..1e3e870ec83 100644 --- a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue +++ b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue @@ -1,19 +1,18 @@ <script> -import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { __ } from '~/locale'; -import tooltip from '~/vue_shared/directives/tooltip'; const MARK_TEXT = __('Mark as done'); const TODO_TEXT = __('Add a To-Do'); export default { - directives: { - tooltip, - }, components: { GlIcon, GlLoadingIcon, }, + directives: { + GlTooltip: GlTooltipDirective, + }, props: { issuableId: { type: Number, @@ -71,16 +70,13 @@ export default { <template> <button - v-tooltip + v-gl-tooltip.left.viewport :class="buttonClasses" :title="buttonTooltip" :aria-label="buttonLabel" :data-issuable-id="issuableId" :data-issuable-type="issuableType" type="button" - data-container="body" - data-placement="left" - data-boundary="viewport" @click="handleButtonClick" > <gl-icon diff --git a/app/assets/javascripts/vue_shared/components/awards_list.vue b/app/assets/javascripts/vue_shared/components/awards_list.vue index 7a687ea4ad0..6d5912df96b 100644 --- a/app/assets/javascripts/vue_shared/components/awards_list.vue +++ b/app/assets/javascripts/vue_shared/components/awards_list.vue @@ -1,7 +1,7 @@ <script> /* eslint-disable vue/no-v-html */ import { groupBy } from 'lodash'; -import { GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; +import { GlIcon, GlButton, GlTooltipDirective } from '@gitlab/ui'; import { glEmojiTag } from '../../emoji'; import { __, sprintf } from '~/locale'; @@ -10,8 +10,8 @@ const NO_USER_ID = -1; export default { components: { + GlButton, GlIcon, - GlLoadingIcon, }, directives: { GlTooltip: GlTooltipDirective, @@ -64,7 +64,7 @@ export default { methods: { getAwardClassBindings(awardList) { return { - active: this.hasReactionByCurrentUser(awardList), + selected: this.hasReactionByCurrentUser(awardList), disabled: this.currentUserId === NO_USER_ID, }; }, @@ -150,40 +150,39 @@ export default { <template> <div class="awards js-awards-block"> - <button + <gl-button v-for="awardList in groupedAwards" :key="awardList.name" v-gl-tooltip.viewport + class="gl-mr-3" :class="awardList.classes" :title="awardList.title" data-testid="award-button" - class="btn award-control" - type="button" @click="handleAward(awardList.name)" > - <span data-testid="award-html" v-html="awardList.html"></span> - <span class="award-control-text js-counter">{{ awardList.list.length }}</span> - </button> + <template #emoji> + <span class="award-emoji-block" data-testid="award-html" v-html="awardList.html"></span> + </template> + <span class="js-counter">{{ awardList.list.length }}</span> + </gl-button> <div v-if="canAwardEmoji" class="award-menu-holder"> - <button + <gl-button v-gl-tooltip.viewport :class="addButtonClass" - class="award-control btn js-add-award" + class="add-reaction-button js-add-award" title="Add reaction" :aria-label="__('Add reaction')" - type="button" > - <span class="award-control-icon award-control-icon-neutral"> + <span class="reaction-control-icon reaction-control-icon-neutral"> <gl-icon aria-hidden="true" name="slight-smile" /> </span> - <span class="award-control-icon award-control-icon-positive"> + <span class="reaction-control-icon reaction-control-icon-positive"> <gl-icon aria-hidden="true" name="smiley" /> </span> - <span class="award-control-icon award-control-icon-super-positive"> - <gl-icon aria-hidden="true" name="smiley" /> + <span class="reaction-control-icon reaction-control-icon-super-positive"> + <gl-icon aria-hidden="true" name="smile" /> </span> - <gl-loading-icon size="md" color="dark" class="award-control-icon-loading" /> - </button> + </gl-button> </div> </div> </template> diff --git a/app/assets/javascripts/vue_shared/security_reports/store/constants.js b/app/assets/javascripts/vue_shared/security_reports/store/constants.js new file mode 100644 index 00000000000..6aeab56eea2 --- /dev/null +++ b/app/assets/javascripts/vue_shared/security_reports/store/constants.js @@ -0,0 +1,7 @@ +/** + * Vuex module names corresponding to security scan types. These are similar to + * the snake_case report types from the backend, but should not be considered + * to be equivalent. + */ +export const MODULE_SAST = 'sast'; +export const MODULE_SECRET_DETECTION = 'secretDetection'; diff --git a/app/assets/javascripts/vue_shared/security_reports/store/getters.js b/app/assets/javascripts/vue_shared/security_reports/store/getters.js new file mode 100644 index 00000000000..1e5a60c32fd --- /dev/null +++ b/app/assets/javascripts/vue_shared/security_reports/store/getters.js @@ -0,0 +1,66 @@ +import { s__, sprintf } from '~/locale'; +import { countVulnerabilities, groupedTextBuilder } from './utils'; +import { LOADING, ERROR, SUCCESS } from '~/reports/constants'; +import { TRANSLATION_IS_LOADING } from './messages'; + +export const summaryCounts = state => + countVulnerabilities( + state.reportTypes.reduce((acc, reportType) => { + acc.push(...state[reportType].newIssues); + return acc; + }, []), + ); + +export const groupedSummaryText = (state, getters) => { + const reportType = s__('ciReport|Security scanning'); + let status = ''; + + // All reports are loading + if (getters.areAllReportsLoading) { + return { message: sprintf(TRANSLATION_IS_LOADING, { reportType }) }; + } + + // All reports returned error + if (getters.allReportsHaveError) { + return { message: s__('ciReport|Security scanning failed loading any results') }; + } + + if (getters.areReportsLoading && getters.anyReportHasError) { + status = s__('ciReport|is loading, errors when loading results'); + } else if (getters.areReportsLoading && !getters.anyReportHasError) { + status = s__('ciReport|is loading'); + } else if (!getters.areReportsLoading && getters.anyReportHasError) { + status = s__('ciReport|: Loading resulted in an error'); + } + + const { critical, high, other } = getters.summaryCounts; + + return groupedTextBuilder({ reportType, status, critical, high, other }); +}; + +export const summaryStatus = (state, getters) => { + if (getters.areReportsLoading) { + return LOADING; + } + + if (getters.anyReportHasError || getters.anyReportHasIssues) { + return ERROR; + } + + return SUCCESS; +}; + +export const areReportsLoading = state => + state.reportTypes.some(reportType => state[reportType].isLoading); + +export const areAllReportsLoading = state => + state.reportTypes.every(reportType => state[reportType].isLoading); + +export const allReportsHaveError = state => + state.reportTypes.every(reportType => state[reportType].hasError); + +export const anyReportHasError = state => + state.reportTypes.some(reportType => state[reportType].hasError); + +export const anyReportHasIssues = state => + state.reportTypes.some(reportType => state[reportType].newIssues.length > 0); diff --git a/app/assets/javascripts/vue_shared/security_reports/store/index.js b/app/assets/javascripts/vue_shared/security_reports/store/index.js new file mode 100644 index 00000000000..10705e04a21 --- /dev/null +++ b/app/assets/javascripts/vue_shared/security_reports/store/index.js @@ -0,0 +1,16 @@ +import Vuex from 'vuex'; +import * as getters from './getters'; +import state from './state'; +import { MODULE_SAST, MODULE_SECRET_DETECTION } from './constants'; +import sast from './modules/sast'; +import secretDetection from './modules/secret_detection'; + +export default () => + new Vuex.Store({ + modules: { + [MODULE_SAST]: sast, + [MODULE_SECRET_DETECTION]: secretDetection, + }, + getters, + state, + }); diff --git a/app/assets/javascripts/vue_shared/security_reports/store/messages.js b/app/assets/javascripts/vue_shared/security_reports/store/messages.js new file mode 100644 index 00000000000..c25e252a768 --- /dev/null +++ b/app/assets/javascripts/vue_shared/security_reports/store/messages.js @@ -0,0 +1,4 @@ +import { s__ } from '~/locale'; + +export const TRANSLATION_IS_LOADING = s__('ciReport|%{reportType} is loading'); +export const TRANSLATION_HAS_ERROR = s__('ciReport|%{reportType}: Loading resulted in an error'); diff --git a/app/assets/javascripts/vue_shared/security_reports/store/state.js b/app/assets/javascripts/vue_shared/security_reports/store/state.js new file mode 100644 index 00000000000..5dc4d1ad2fb --- /dev/null +++ b/app/assets/javascripts/vue_shared/security_reports/store/state.js @@ -0,0 +1,5 @@ +import { MODULE_SAST, MODULE_SECRET_DETECTION } from './constants'; + +export default () => ({ + reportTypes: [MODULE_SAST, MODULE_SECRET_DETECTION], +}); diff --git a/app/assets/javascripts/vue_shared/security_reports/store/utils.js b/app/assets/javascripts/vue_shared/security_reports/store/utils.js index 6e50efae741..c5e786c92b1 100644 --- a/app/assets/javascripts/vue_shared/security_reports/store/utils.js +++ b/app/assets/javascripts/vue_shared/security_reports/store/utils.js @@ -1,5 +1,7 @@ import pollUntilComplete from '~/lib/utils/poll_until_complete'; import axios from '~/lib/utils/axios_utils'; +import { __, n__, sprintf } from '~/locale'; +import { CRITICAL, HIGH } from '~/vulnerabilities/constants'; import { FEEDBACK_TYPE_DISMISSAL, FEEDBACK_TYPE_ISSUE, @@ -73,3 +75,79 @@ export const parseDiff = (diff, enrichData) => { existing: diff.existing ? diff.existing.map(enrichVulnerability) : [], }; }; + +const createCountMessage = ({ critical, high, other, total }) => { + const otherMessage = n__('%d Other', '%d Others', other); + const countMessage = __( + '%{criticalStart}%{critical} Critical%{criticalEnd} %{highStart}%{high} High%{highEnd} and %{otherStart}%{otherMessage}%{otherEnd}', + ); + return total ? sprintf(countMessage, { critical, high, otherMessage }) : ''; +}; + +const createStatusMessage = ({ reportType, status, total }) => { + const vulnMessage = n__('vulnerability', 'vulnerabilities', total); + let message; + if (status) { + message = __('%{reportType} %{status}'); + } else if (!total) { + message = __('%{reportType} detected %{totalStart}no%{totalEnd} vulnerabilities.'); + } else { + message = __( + '%{reportType} detected %{totalStart}%{total}%{totalEnd} potential %{vulnMessage}', + ); + } + return sprintf(message, { reportType, status, total, vulnMessage }); +}; + +/** + * Counts vulnerabilities. + * Returns the amount of critical, high, and other vulnerabilities. + * + * @param {Array} vulnerabilities The raw vulnerabilities to parse + * @returns {{critical: number, high: number, other: number}} + */ +export const countVulnerabilities = (vulnerabilities = []) => + vulnerabilities.reduce( + (acc, { severity }) => { + if (severity === CRITICAL) { + acc.critical += 1; + } else if (severity === HIGH) { + acc.high += 1; + } else { + acc.other += 1; + } + + return acc; + }, + { critical: 0, high: 0, other: 0 }, + ); + +/** + * Takes an object of options and returns the object with an externalized string representing + * the critical, high, and other severity vulnerabilities for a given report. + * + * The resulting string _may_ still contain sprintf-style placeholders. These + * are left in place so they can be replaced with markup, via the + * SecuritySummary component. + * @param {{reportType: string, status: string, critical: number, high: number, other: number}} options + * @returns {Object} the parameters with an externalized string + */ +export const groupedTextBuilder = ({ + reportType = '', + status = '', + critical = 0, + high = 0, + other = 0, +} = {}) => { + const total = critical + high + other; + + return { + countMessage: createCountMessage({ critical, high, other, total }), + message: createStatusMessage({ reportType, status, total }), + critical, + high, + other, + status, + total, + }; +}; diff --git a/app/assets/javascripts/vulnerabilities/constants.js b/app/assets/javascripts/vulnerabilities/constants.js new file mode 100644 index 00000000000..42fb38e8e7e --- /dev/null +++ b/app/assets/javascripts/vulnerabilities/constants.js @@ -0,0 +1,15 @@ +/** + * Vulnerability severities as provided by the backend on vulnerability + * objects. + */ +export const CRITICAL = 'critical'; +export const HIGH = 'high'; +export const MEDIUM = 'medium'; +export const LOW = 'low'; +export const INFO = 'info'; +export const UNKNOWN = 'unknown'; + +/** + * All vulnerability severities in decreasing order. + */ +export const SEVERITIES = [CRITICAL, HIGH, MEDIUM, LOW, INFO, UNKNOWN]; diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss index 4f09f1a394b..d9ad4992458 100644 --- a/app/assets/stylesheets/framework/awards.scss +++ b/app/assets/stylesheets/framework/awards.scss @@ -253,3 +253,111 @@ vertical-align: middle; } } + + +// The following encompasses the "add reaction" button redesign to +// align properly within GitLab UI's gl-button. The implementation +// above will be deprecated once all instances of "award emoji" are +// migrated to Vue. + +.gl-button .award-emoji-block gl-emoji { + top: -1px; + margin-top: -1px; + margin-bottom: -1px; +} + +.add-reaction-button { + position: relative; + + // This forces the height and width of the inner content to match + // other gl-buttons despite all child elements being set to + // `position:absolute` + &::after { + content: '\a0'; + width: 1em; + } + + .reaction-control-icon { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + + // center the icon vertically and horizontally within the button + display: flex; + align-items: center; + justify-content: center; + + @include transition(opacity, transform); + + .gl-icon { + height: $default-icon-size; + width: $default-icon-size; + } + } + + .reaction-control-icon-neutral { + opacity: 1; + } + + .reaction-control-icon-positive, + .reaction-control-icon-super-positive { + opacity: 0; + } + + &:hover, + &.active, + &:active, + &.is-active { + // extra specificty added to override another selector + .reaction-control-icon .gl-icon { + color: $blue-500; + transform: scale(1.15); + } + + .reaction-control-icon-neutral { + opacity: 0; + } + } + + &:hover { + .reaction-control-icon-positive { + opacity: 1; + } + } + + &.active, + &:active, + &.is-active { + .reaction-control-icon-positive { + opacity: 0; + } + + .reaction-control-icon-super-positive { + opacity: 1; + } + } + + &.disabled { + cursor: default; + + &:hover, + &:focus, + &:active { + .reaction-control-icon .gl-icon { + color: inherit; + transform: scale(1); + } + + .reaction-control-icon-neutral { + opacity: 1; + } + + .reaction-control-icon-positive, + .reaction-control-icon-super-positive { + opacity: 0; + } + } + } +} diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 680f3623eba..df7e96d7b8c 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -388,11 +388,8 @@ img.emoji { 🚨 Do not use these classes — they are deprecated and being removed. 🚨 See https://gitlab.com/gitlab-org/gitlab/-/issues/217418 for more details. **/ -.prepend-top-15 { margin-top: 15px; } .prepend-top-20 { margin-top: 20px; } -.prepend-left-15 { margin-left: 15px; } .prepend-left-20 { margin-left: 20px; } -.append-right-20 { margin-right: 20px; } .append-bottom-20 { margin-bottom: 20px; } .ml-10 { margin-left: 4.5rem; } .inline { display: inline-block; } diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index e10e9a83b05..45f5281b515 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -51,7 +51,7 @@ module DropdownsHelper default_label = data_attr[:default_label] content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") - output << icon('chevron-down') + output << sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3") output.html_safe end end diff --git a/app/models/exported_protected_branch.rb b/app/models/exported_protected_branch.rb new file mode 100644 index 00000000000..6e8abbc2389 --- /dev/null +++ b/app/models/exported_protected_branch.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ExportedProtectedBranch < ProtectedBranch + has_many :push_access_levels, -> { where(deploy_key_id: nil) }, class_name: "ProtectedBranch::PushAccessLevel", foreign_key: :protected_branch_id +end diff --git a/app/models/project.rb b/app/models/project.rb index ebd8e56246d..acdacd357c2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -222,6 +222,7 @@ class Project < ApplicationRecord has_many :snippets, class_name: 'ProjectSnippet' has_many :hooks, class_name: 'ProjectHook' has_many :protected_branches + has_many :exported_protected_branches has_many :protected_tags has_many :repository_languages, -> { order "share DESC" } has_many :designs, inverse_of: :project, class_name: 'DesignManagement::Design' diff --git a/app/models/snippet.rb b/app/models/snippet.rb index dc370b46bda..2e1a2e8e2b2 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -213,7 +213,8 @@ class Snippet < ApplicationRecord def blobs return [] unless repository_exists? - repository.ls_files(default_branch).map { |file| Blob.lazy(repository, default_branch, file) } + branch = default_branch + list_files(branch).map { |file| Blob.lazy(repository, branch, file) } end def hook_attrs diff --git a/app/presenters/projects/import_export/project_export_presenter.rb b/app/presenters/projects/import_export/project_export_presenter.rb index 8f3fc53af10..b52f3411c49 100644 --- a/app/presenters/projects/import_export/project_export_presenter.rb +++ b/app/presenters/projects/import_export/project_export_presenter.rb @@ -15,6 +15,10 @@ module Projects self.respond_to?(:override_description) ? override_description : super end + def protected_branches + project.exported_protected_branches + end + private def converted_group_members diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 569255ec2e5..685c540def2 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -63,7 +63,7 @@ .home-panel-home-desc.mt-1 - if @project.description.present? .home-panel-description.text-break - .home-panel-description-markdown.read-more-container{ itemprop: 'abstract' } + .home-panel-description-markdown.read-more-container{ itemprop: 'description' } = markdown_field(@project, :description) %button.btn.btn-blank.btn-link.js-read-more-trigger.d-lg-none{ type: "button" } = _("Read more") diff --git a/changelogs/unreleased/eread-migrate-awards-list-buttons.yml b/changelogs/unreleased/eread-migrate-awards-list-buttons.yml new file mode 100644 index 00000000000..8b7883537fa --- /dev/null +++ b/changelogs/unreleased/eread-migrate-awards-list-buttons.yml @@ -0,0 +1,5 @@ +--- +title: Migrate awards list buttons to new buttons +merge_request: 43061 +author: +type: other diff --git a/changelogs/unreleased/mk-add-verification-state-machine.yml b/changelogs/unreleased/mk-add-verification-state-machine.yml new file mode 100644 index 00000000000..ed179af4832 --- /dev/null +++ b/changelogs/unreleased/mk-add-verification-state-machine.yml @@ -0,0 +1,5 @@ +--- +title: 'Geo: Add verification state machine fields to package files table' +merge_request: 47260 +author: +type: added diff --git a/changelogs/unreleased/mw-replace-fa-chevron-down-in-template-selectors.yml b/changelogs/unreleased/mw-replace-fa-chevron-down-in-template-selectors.yml new file mode 100644 index 00000000000..a4b7b1eed3f --- /dev/null +++ b/changelogs/unreleased/mw-replace-fa-chevron-down-in-template-selectors.yml @@ -0,0 +1,5 @@ +--- +title: Replace fa-chevron-down in template selector dropdown +merge_request: 48015 +author: +type: changed diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 58bf3f6013c..6cc4fe25765 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -1,191 +1,3 @@ # frozen_string_literal: true -# Specs for this file can be found on: -# * spec/lib/gitlab/throttle_spec.rb -# * spec/requests/rack_attack_global_spec.rb -module Gitlab::Throttle - def self.settings - Gitlab::CurrentSettings.current_application_settings - end - - # Returns true if we should use the Admin Area protected paths throttle - def self.protected_paths_enabled? - self.settings.throttle_protected_paths_enabled? - end - - def self.omnibus_protected_paths_present? - Rack::Attack.throttles.key?('protected paths') - end - - def self.bypass_header - env_value = ENV['GITLAB_THROTTLE_BYPASS_HEADER'] - return unless env_value.present? - - "HTTP_#{env_value.upcase.tr('-', '_')}" - end - - def self.unauthenticated_options - limit_proc = proc { |req| settings.throttle_unauthenticated_requests_per_period } - period_proc = proc { |req| settings.throttle_unauthenticated_period_in_seconds.seconds } - { limit: limit_proc, period: period_proc } - end - - def self.authenticated_api_options - limit_proc = proc { |req| settings.throttle_authenticated_api_requests_per_period } - period_proc = proc { |req| settings.throttle_authenticated_api_period_in_seconds.seconds } - { limit: limit_proc, period: period_proc } - end - - def self.authenticated_web_options - limit_proc = proc { |req| settings.throttle_authenticated_web_requests_per_period } - period_proc = proc { |req| settings.throttle_authenticated_web_period_in_seconds.seconds } - { limit: limit_proc, period: period_proc } - end - - def self.protected_paths_options - limit_proc = proc { |req| settings.throttle_protected_paths_requests_per_period } - period_proc = proc { |req| settings.throttle_protected_paths_period_in_seconds.seconds } - - { limit: limit_proc, period: period_proc } - end -end - -class Rack::Attack - # Order conditions by how expensive they are: - # 1. The most expensive is the `req.unauthenticated?` and - # `req.authenticated_user_id` as it performs an expensive - # DB/Redis query to validate the request - # 2. Slightly less expensive is the need to query DB/Redis - # to unmarshal settings (`Gitlab::Throttle.settings`) - # - # We deliberately skip `/-/health|liveness|readiness` - # from Rack Attack as they need to always be accessible - # by Load Balancer and additional measure is implemented - # (token and whitelisting) to prevent abuse. - throttle('throttle_unauthenticated', Gitlab::Throttle.unauthenticated_options) do |req| - if !req.should_be_skipped? && - Gitlab::Throttle.settings.throttle_unauthenticated_enabled && - req.unauthenticated? - req.ip - end - end - - throttle('throttle_authenticated_api', Gitlab::Throttle.authenticated_api_options) do |req| - if req.api_request? && - Gitlab::Throttle.settings.throttle_authenticated_api_enabled - req.authenticated_user_id([:api]) - end - end - - # Product analytics feature is in experimental stage. - # At this point we want to limit amount of events registered - # per application (aid stands for application id). - throttle('throttle_product_analytics_collector', limit: 100, period: 60) do |req| - if req.product_analytics_collector_request? - req.params['aid'] - end - end - - throttle('throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req| - if req.web_request? && - Gitlab::Throttle.settings.throttle_authenticated_web_enabled - req.authenticated_user_id([:api, :rss, :ics]) - end - end - - throttle('throttle_unauthenticated_protected_paths', Gitlab::Throttle.protected_paths_options) do |req| - if req.post? && - !req.should_be_skipped? && - req.protected_path? && - Gitlab::Throttle.protected_paths_enabled? && - req.unauthenticated? - req.ip - end - end - - throttle('throttle_authenticated_protected_paths_api', Gitlab::Throttle.protected_paths_options) do |req| - if req.post? && - req.api_request? && - req.protected_path? && - Gitlab::Throttle.protected_paths_enabled? - req.authenticated_user_id([:api]) - end - end - - throttle('throttle_authenticated_protected_paths_web', Gitlab::Throttle.protected_paths_options) do |req| - if req.post? && - req.web_request? && - req.protected_path? && - Gitlab::Throttle.protected_paths_enabled? - req.authenticated_user_id([:api, :rss, :ics]) - end - end - - safelist('throttle_bypass_header') do |req| - Gitlab::Throttle.bypass_header.present? && - req.get_header(Gitlab::Throttle.bypass_header) == '1' - end - - class Request - def unauthenticated? - !(authenticated_user_id([:api, :rss, :ics]) || authenticated_runner_id) - end - - def authenticated_user_id(request_formats) - request_authenticator.user(request_formats)&.id - end - - def authenticated_runner_id - request_authenticator.runner&.id - end - - def api_request? - path.start_with?('/api') - end - - def api_internal_request? - path =~ %r{^/api/v\d+/internal/} - end - - def health_check_request? - path =~ %r{^/-/(health|liveness|readiness)} - end - - def product_analytics_collector_request? - path.start_with?('/-/collector/i') - end - - def should_be_skipped? - api_internal_request? || health_check_request? - end - - def web_request? - !api_request? && !health_check_request? - end - - def protected_path? - !protected_path_regex.nil? - end - - def protected_path_regex - path =~ protected_paths_regex - end - - private - - def request_authenticator - @request_authenticator ||= Gitlab::Auth::RequestAuthenticator.new(self) - end - - def protected_paths - Gitlab::CurrentSettings.current_application_settings.protected_paths - end - - def protected_paths_regex - Regexp.union(protected_paths.map { |path| /\A#{Regexp.escape(path)}/ }) - end - end -end - -::Rack::Attack.extend_if_ee('::EE::Gitlab::Rack::Attack') -::Rack::Attack::Request.prepend_if_ee('::EE::Gitlab::Rack::Attack::Request') +Gitlab::RackAttack.configure(::Rack::Attack) diff --git a/config/initializers/rack_attack_logging.rb b/config/initializers/rack_attack_logging.rb index e89c6b1b794..7b0a8f0d7dd 100644 --- a/config/initializers/rack_attack_logging.rb +++ b/config/initializers/rack_attack_logging.rb @@ -6,7 +6,7 @@ ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, r req = payload[:request] case req.env['rack.attack.match_type'] - when :throttle, :blocklist + when :throttle, :blocklist, :track rack_attack_info = { message: 'Rack_Attack', env: req.env['rack.attack.match_type'], diff --git a/db/migrate/20201112173532_add_verification_state_to_package_files.rb b/db/migrate/20201112173532_add_verification_state_to_package_files.rb new file mode 100644 index 00000000000..61f526bd77e --- /dev/null +++ b/db/migrate/20201112173532_add_verification_state_to_package_files.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddVerificationStateToPackageFiles < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :packages_package_files, :verification_state, :integer, default: 0, limit: 2, null: false + add_column :packages_package_files, :verification_started_at, :datetime_with_timezone + end +end diff --git a/db/migrate/20201112173911_add_index_on_verification_state_on_package_files.rb b/db/migrate/20201112173911_add_index_on_verification_state_on_package_files.rb new file mode 100644 index 00000000000..17e6b7d01f6 --- /dev/null +++ b/db/migrate/20201112173911_add_index_on_verification_state_on_package_files.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddIndexOnVerificationStateOnPackageFiles < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + INDEX_NAME = 'index_packages_package_files_on_verification_state' + + disable_ddl_transaction! + + def up + add_concurrent_index :packages_package_files, :verification_state, name: INDEX_NAME + end + + def down + remove_concurrent_index_by_name :packages_package_files, INDEX_NAME + end +end diff --git a/db/schema_migrations/20201112173532 b/db/schema_migrations/20201112173532 new file mode 100644 index 00000000000..a0c879e5b36 --- /dev/null +++ b/db/schema_migrations/20201112173532 @@ -0,0 +1 @@ +d88a47333a4cc2b6c4aafa817c766822728d14b947a195c7c40b39e0c8b41610
\ No newline at end of file diff --git a/db/schema_migrations/20201112173911 b/db/schema_migrations/20201112173911 new file mode 100644 index 00000000000..aa98ecb7f14 --- /dev/null +++ b/db/schema_migrations/20201112173911 @@ -0,0 +1 @@ +dde78a32d53a695e82b44574458b3670dce4803ffc6f34a1216f3671cca470ed
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index f29f9178a26..3153f3f6a3e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14485,6 +14485,8 @@ CREATE TABLE packages_package_files ( verification_failure character varying(255), verification_retry_count integer, verification_checksum bytea, + verification_state smallint DEFAULT 0 NOT NULL, + verification_started_at timestamp with time zone, CONSTRAINT check_4c5e6bb0b3 CHECK ((file_store IS NOT NULL)) ); @@ -21453,6 +21455,8 @@ CREATE INDEX index_packages_package_files_on_file_store ON packages_package_file CREATE INDEX index_packages_package_files_on_package_id_and_file_name ON packages_package_files USING btree (package_id, file_name); +CREATE INDEX index_packages_package_files_on_verification_state ON packages_package_files USING btree (verification_state); + CREATE INDEX index_packages_packages_on_creator_id ON packages_packages USING btree (creator_id); CREATE INDEX index_packages_packages_on_id_and_created_at ON packages_packages USING btree (id, created_at); diff --git a/doc/administration/external_pipeline_validation.md b/doc/administration/external_pipeline_validation.md index cb1d35f24d5..bc1a57e8bcd 100644 --- a/doc/administration/external_pipeline_validation.md +++ b/doc/administration/external_pipeline_validation.md @@ -14,9 +14,9 @@ This is an experimental feature and subject to change without notice. ## Usage -GitLab will send a POST request to the external service URL with the pipeline -data as payload. GitLab will then invalidate the pipeline based on the response -code. If there's an error or the request times out, the pipeline will not be +GitLab sends a POST request to the external service URL with the pipeline +data as payload. GitLab then invalidates the pipeline based on the response +code. If there's an error or the request times out, the pipeline is not invalidated. Response Code Legend: diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index a010903013e..aca5b321262 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -118,14 +118,14 @@ This section describes the earlier configuration format. For source installations the following settings are nested under `artifacts:` and then `object_store:`. On Omnibus GitLab installs they are prefixed by `artifacts_object_store_`. -| Setting | Description | Default | -|---------|-------------|---------| -| `enabled` | Enable/disable object storage | `false` | -| `remote_directory` | The bucket name where Artifacts will be stored| | -| `direct_upload` | Set to `true` to enable direct upload of Artifacts without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. | `false` | -| `background_upload` | Set to `false` to disable automatic upload. Option may be removed once upload is direct to S3 | `true` | -| `proxy_download` | Set to `true` to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | -| `connection` | Various connection options described below | | +| Setting | Default | Description | +|---------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `enabled` | `false` | Enable/disable object storage | +| `remote_directory` | | The bucket name where Artifacts are stored | +| `direct_upload` | `false` | Set to `true` to enable direct upload of Artifacts without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. | +| `background_upload` | `true` | Set to `false` to disable automatic upload. Option may be removed once upload is direct to S3 | +| `proxy_download` | `false` | Set to `true` to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data. | +| `connection` | | Various connection options described below | #### Connection settings @@ -336,7 +336,7 @@ To migrate back to local storage: If [`artifacts:expire_in`](../ci/yaml/README.md#artifactsexpire_in) is used to set an expiry for the artifacts, they are marked for deletion right after that date passes. -Otherwise, they will expire per the [default artifacts expiration setting](../user/admin_area/settings/continuous_integration.md). +Otherwise, they expire per the [default artifacts expiration setting](../user/admin_area/settings/continuous_integration.md). Artifacts are cleaned up by the `expire_build_artifacts_worker` cron job which Sidekiq runs every hour at 50 minutes (`50 * * * *`). @@ -367,7 +367,7 @@ steps below. 1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect. -If the `expire` directive is not set explicitly in your pipeline, artifacts will expire per the +If the `expire` directive is not set explicitly in your pipeline, artifacts expire per the default artifacts expiration setting, which you can find in the [CI/CD Admin settings](../user/admin_area/settings/continuous_integration.md). ## Validation for dependencies @@ -444,7 +444,7 @@ reasons are: - The number of jobs run, and hence artifacts generated, is higher than expected. - Job logs are larger than expected, and have accumulated over time. -In these and other cases, you'll need to identify the projects most responsible +In these and other cases, identify the projects most responsible for disk space usage, figure out what types of artifacts are using the most space, and in some cases, manually delete job artifacts to reclaim disk space. @@ -508,7 +508,7 @@ If you need to manually remove job artifacts associated with multiple jobs while 1. Delete job artifacts older than a specific date: NOTE: **Note:** - This step will also erase artifacts that users have chosen to + This step also erases artifacts that users have chosen to ["keep"](../ci/pipelines/job_artifacts.md#browsing-artifacts). ```ruby @@ -552,7 +552,7 @@ If you need to manually remove **all** job artifacts associated with multiple jo builds_with_artifacts = Ci::Build.with_existing_job_artifacts(Ci::JobArtifact.trace) ``` -1. Select the user which will be mentioned in the web UI as erasing the job: +1. Select the user which is mentioned in the web UI as erasing the job: ```ruby admin_user = User.find_by(username: 'username') diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md index 9f522e0d599..d1afcd0343e 100644 --- a/doc/administration/reference_architectures/10k_users.md +++ b/doc/administration/reference_architectures/10k_users.md @@ -1356,19 +1356,17 @@ and improved designed. [Gitaly](../gitaly/index.md) server node requirements are dependent on data, specifically the number of projects and those projects' sizes. It's recommended -that a Gitaly server node stores no more than 5 TB of data. Although this -reference architecture includes a recommendation for the number of Gitaly server -nodes to use, depending on your storage requirements, you may require additional -Gitaly server nodes. +that a Gitaly server node stores no more than 5 TB of data. Depending on your +repository storage requirements, you may require additional Gitaly server nodes. Due to Gitaly having notable input and output requirements, we strongly -recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs should -have a throughput of at least 8,000 input/output operations per second (IOPS) -for read operations and 2,000 IOPS for write operations. These IOPS values are -initial recommendations, and may be adjusted to greater or lesser values -depending on the scale of your environment's workload. If you're running the -environment on a Cloud provider, refer to their documentation about how to -configure IOPS correctly. +recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs +should have a throughput of at least 8,000 +input/output operations per second (IOPS) for read operations and 2,000 IOPS for +write operations. These IOPS values are initial recommendations, and may be +adjusted to greater or lesser values depending on the scale of your +environment's workload. If you're running the environment on a Cloud provider, +refer to their documentation about how to configure IOPS correctly. Be sure to note the following items: @@ -1376,8 +1374,8 @@ Be sure to note the following items: [repository storage paths](../repository_storage_paths.md). - A Gitaly server can host one or more storage paths. - A GitLab server can use one or more Gitaly server nodes. -- Gitaly addresses must be specified to be correctly resolvable for _all_ - Gitaly clients. +- Gitaly addresses must be specified to be correctly resolvable for all Gitaly + clients. - Gitaly servers must not be exposed to the public internet, as Gitaly's network traffic is unencrypted by default. The use of a firewall is highly recommended to restrict access to the Gitaly server. Another option is to @@ -1406,8 +1404,8 @@ On each node: 1. [Download and install](https://about.gitlab.com/install/) the Omnibus GitLab package of your choice. Be sure to follow _only_ installation steps 1 and 2 on the page, and _do not_ provide the `EXTERNAL_URL` value. -1. Edit `/etc/gitlab/gitlab.rb` to configure the storage paths, enable - the network listener, and configure the token: +1. Edit the Gitaly server node's `/etc/gitlab/gitlab.rb` file to configure + storage paths, enable the network listener, and to configure the token: <!-- updates to following example must also be made at diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md index b106f7bced1..cf86bf15333 100644 --- a/doc/administration/reference_architectures/25k_users.md +++ b/doc/administration/reference_architectures/25k_users.md @@ -1356,19 +1356,17 @@ and improved designed. [Gitaly](../gitaly/index.md) server node requirements are dependent on data, specifically the number of projects and those projects' sizes. It's recommended -that a Gitaly server node stores no more than 5 TB of data. Although this -reference architecture includes a recommendation for the number of Gitaly server -nodes to use, depending on your storage requirements, you may require additional -Gitaly server nodes. +that a Gitaly server node stores no more than 5 TB of data. Depending on your +repository storage requirements, you may require additional Gitaly server nodes. Due to Gitaly having notable input and output requirements, we strongly -recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs should -have a throughput of at least 8,000 input/output operations per second (IOPS) -for read operations and 2,000 IOPS for write operations. These IOPS values are -initial recommendations, and may be adjusted to greater or lesser values -depending on the scale of your environment's workload. If you're running the -environment on a Cloud provider, refer to their documentation about how to -configure IOPS correctly. +recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs +should have a throughput of at least 8,000 +input/output operations per second (IOPS) for read operations and 2,000 IOPS for +write operations. These IOPS values are initial recommendations, and may be +adjusted to greater or lesser values depending on the scale of your +environment's workload. If you're running the environment on a Cloud provider, +refer to their documentation about how to configure IOPS correctly. Be sure to note the following items: @@ -1376,8 +1374,8 @@ Be sure to note the following items: [repository storage paths](../repository_storage_paths.md). - A Gitaly server can host one or more storage paths. - A GitLab server can use one or more Gitaly server nodes. -- Gitaly addresses must be specified to be correctly resolvable for _all_ - Gitaly clients. +- Gitaly addresses must be specified to be correctly resolvable for all Gitaly + clients. - Gitaly servers must not be exposed to the public internet, as Gitaly's network traffic is unencrypted by default. The use of a firewall is highly recommended to restrict access to the Gitaly server. Another option is to @@ -1406,8 +1404,8 @@ On each node: 1. [Download and install](https://about.gitlab.com/install/) the Omnibus GitLab package of your choice. Be sure to follow _only_ installation steps 1 and 2 on the page, and _do not_ provide the `EXTERNAL_URL` value. -1. Edit `/etc/gitlab/gitlab.rb` to configure the storage paths, enable - the network listener, and configure the token: +1. Edit the Gitaly server node's `/etc/gitlab/gitlab.rb` file to configure + storage paths, enable the network listener, and to configure the token: <!-- updates to following example must also be made at diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md index b5b3e4e0300..c0c1d566255 100644 --- a/doc/administration/reference_architectures/3k_users.md +++ b/doc/administration/reference_architectures/3k_users.md @@ -1067,19 +1067,17 @@ and improved designed. [Gitaly](../gitaly/index.md) server node requirements are dependent on data, specifically the number of projects and those projects' sizes. It's recommended -that a Gitaly server node stores no more than 5 TB of data. Although this -reference architecture includes a recommendation for the number of Gitaly server -nodes to use, depending on your storage requirements, you may require additional -Gitaly server nodes. +that a Gitaly server node stores no more than 5 TB of data. Depending on your +repository storage requirements, you may require additional Gitaly server nodes. Due to Gitaly having notable input and output requirements, we strongly -recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs should -have a throughput of at least 8,000 input/output operations per second (IOPS) -for read operations and 2,000 IOPS for write operations. These IOPS values are -initial recommendations, and may be adjusted to greater or lesser values -depending on the scale of your environment's workload. If you're running the -environment on a Cloud provider, refer to their documentation about how to -configure IOPS correctly. +recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs +should have a throughput of at least 8,000 +input/output operations per second (IOPS) for read operations and 2,000 IOPS for +write operations. These IOPS values are initial recommendations, and may be +adjusted to greater or lesser values depending on the scale of your +environment's workload. If you're running the environment on a Cloud provider, +refer to their documentation about how to configure IOPS correctly. Be sure to note the following items: @@ -1087,8 +1085,8 @@ Be sure to note the following items: [repository storage paths](../repository_storage_paths.md). - A Gitaly server can host one or more storage paths. - A GitLab server can use one or more Gitaly server nodes. -- Gitaly addresses must be specified to be correctly resolvable for _all_ - Gitaly clients. +- Gitaly addresses must be specified to be correctly resolvable for all Gitaly + clients. - Gitaly servers must not be exposed to the public internet, as Gitaly's network traffic is unencrypted by default. The use of a firewall is highly recommended to restrict access to the Gitaly server. Another option is to @@ -1117,8 +1115,8 @@ On each node: 1. [Download and install](https://about.gitlab.com/install/) the Omnibus GitLab package of your choice. Be sure to follow _only_ installation steps 1 and 2 on the page, and _do not_ provide the `EXTERNAL_URL` value. -1. Edit `/etc/gitlab/gitlab.rb` to configure the storage paths, enable - the network listener, and configure the token: +1. Edit the Gitaly server node's `/etc/gitlab/gitlab.rb` file to configure + storage paths, enable the network listener, and to configure the token: <!-- updates to following example must also be made at diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md index 152eb9cb90d..3149bbb0934 100644 --- a/doc/administration/reference_architectures/50k_users.md +++ b/doc/administration/reference_architectures/50k_users.md @@ -1356,19 +1356,17 @@ and improved designed. [Gitaly](../gitaly/index.md) server node requirements are dependent on data, specifically the number of projects and those projects' sizes. It's recommended -that a Gitaly server node stores no more than 5 TB of data. Although this -reference architecture includes a recommendation for the number of Gitaly server -nodes to use, depending on your storage requirements, you may require additional -Gitaly server nodes. +that a Gitaly server node stores no more than 5 TB of data. Depending on your +repository storage requirements, you may require additional Gitaly server nodes. Due to Gitaly having notable input and output requirements, we strongly -recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs should -have a throughput of at least 8,000 input/output operations per second (IOPS) -for read operations and 2,000 IOPS for write operations. These IOPS values are -initial recommendations, and may be adjusted to greater or lesser values -depending on the scale of your environment's workload. If you're running the -environment on a Cloud provider, refer to their documentation about how to -configure IOPS correctly. +recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs +should have a throughput of at least 8,000 +input/output operations per second (IOPS) for read operations and 2,000 IOPS for +write operations. These IOPS values are initial recommendations, and may be +adjusted to greater or lesser values depending on the scale of your +environment's workload. If you're running the environment on a Cloud provider, +refer to their documentation about how to configure IOPS correctly. Be sure to note the following items: @@ -1376,8 +1374,8 @@ Be sure to note the following items: [repository storage paths](../repository_storage_paths.md). - A Gitaly server can host one or more storage paths. - A GitLab server can use one or more Gitaly server nodes. -- Gitaly addresses must be specified to be correctly resolvable for _all_ - Gitaly clients. +- Gitaly addresses must be specified to be correctly resolvable for all Gitaly + clients. - Gitaly servers must not be exposed to the public internet, as Gitaly's network traffic is unencrypted by default. The use of a firewall is highly recommended to restrict access to the Gitaly server. Another option is to @@ -1406,8 +1404,8 @@ On each node: 1. [Download and install](https://about.gitlab.com/install/) the Omnibus GitLab package of your choice. Be sure to follow _only_ installation steps 1 and 2 on the page, and _do not_ provide the `EXTERNAL_URL` value. -1. Edit `/etc/gitlab/gitlab.rb` to configure the storage paths, enable - the network listener, and configure the token: +1. Edit the Gitaly server node's `/etc/gitlab/gitlab.rb` file to configure + storage paths, enable the network listener, and to configure the token: <!-- updates to following example must also be made at diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md index f023971bdc0..96aa3f327e7 100644 --- a/doc/administration/reference_architectures/5k_users.md +++ b/doc/administration/reference_architectures/5k_users.md @@ -1066,19 +1066,17 @@ and improved designed. [Gitaly](../gitaly/index.md) server node requirements are dependent on data, specifically the number of projects and those projects' sizes. It's recommended -that a Gitaly server node stores no more than 5 TB of data. Although this -reference architecture includes a recommendation for the number of Gitaly server -nodes to use, depending on your storage requirements, you may require additional -Gitaly server nodes. +that a Gitaly server node stores no more than 5 TB of data. Depending on your +repository storage requirements, you may require additional Gitaly server nodes. Due to Gitaly having notable input and output requirements, we strongly -recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs should -have a throughput of at least 8,000 input/output operations per second (IOPS) -for read operations and 2,000 IOPS for write operations. These IOPS values are -initial recommendations, and may be adjusted to greater or lesser values -depending on the scale of your environment's workload. If you're running the -environment on a Cloud provider, refer to their documentation about how to -configure IOPS correctly. +recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs +should have a throughput of at least 8,000 +input/output operations per second (IOPS) for read operations and 2,000 IOPS for +write operations. These IOPS values are initial recommendations, and may be +adjusted to greater or lesser values depending on the scale of your +environment's workload. If you're running the environment on a Cloud provider, +refer to their documentation about how to configure IOPS correctly. Be sure to note the following items: @@ -1086,8 +1084,8 @@ Be sure to note the following items: [repository storage paths](../repository_storage_paths.md). - A Gitaly server can host one or more storage paths. - A GitLab server can use one or more Gitaly server nodes. -- Gitaly addresses must be specified to be correctly resolvable for _all_ - Gitaly clients. +- Gitaly addresses must be specified to be correctly resolvable for all Gitaly + clients. - Gitaly servers must not be exposed to the public internet, as Gitaly's network traffic is unencrypted by default. The use of a firewall is highly recommended to restrict access to the Gitaly server. Another option is to @@ -1116,8 +1114,8 @@ On each node: 1. [Download and install](https://about.gitlab.com/install/) the Omnibus GitLab package of your choice. Be sure to follow _only_ installation steps 1 and 2 on the page, and _do not_ provide the `EXTERNAL_URL` value. -1. Edit `/etc/gitlab/gitlab.rb` to configure the storage paths, enable - the network listener, and configure the token: +1. Edit the Gitaly server node's `/etc/gitlab/gitlab.rb` file to configure + storage paths, enable the network listener, and to configure the token: <!-- updates to following example must also be made at diff --git a/doc/api/job_artifacts.md b/doc/api/job_artifacts.md index 54085e6f508..d451cdc7b20 100644 --- a/doc/api/job_artifacts.md +++ b/doc/api/job_artifacts.md @@ -1,6 +1,6 @@ --- -stage: none -group: unassigned +stage: Verify +group: Continuous Integration 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/#designated-technical-writers --- @@ -16,11 +16,11 @@ Get the job's artifacts zipped archive of a project. GET /projects/:id/jobs/:job_id/artifacts ``` -| Attribute | Type | Required | Description | -|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | -| `job_id` | integer | yes | ID of a job. | -| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/triggers/README.md#when-a-pipeline-depends-on-the-artifacts-of-another-pipeline) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. | +| Attribute | Type | Required | Description | +|-------------|----------------|----------|--------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `job_id` | integer | yes | ID of a job. | +| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/triggers/README.md#when-a-pipeline-depends-on-the-artifacts-of-another-pipeline) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. | Example request using the `PRIVATE-TOKEN` header: @@ -32,7 +32,7 @@ To use this in a [`script` definition](../ci/yaml/README.md#script) inside `.gitlab-ci.yml` **(PREMIUM)**, you can use either: - The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable. - For example, the following job will download the artifacts of the job with ID + For example, the following job downloads the artifacts of the job with ID `42`. Note that the command is wrapped into single quotes since it contains a colon (`:`): @@ -44,7 +44,7 @@ To use this in a [`script` definition](../ci/yaml/README.md#script) inside ``` - Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable. - For example, the following job will download the artifacts of the job with ID `42`: + For example, the following job downloads the artifacts of the job with ID `42`: ```yaml artifact_download: @@ -72,7 +72,7 @@ defining the job's name instead of its ID. NOTE: **Note:** If a pipeline is [parent of other child pipelines](../ci/parent_child_pipelines.md), artifacts are searched in hierarchical order from parent to child. For example, if both parent and -child pipelines have a job with the same name, the artifact from the parent pipeline will be returned. +child pipelines have a job with the same name, the artifact from the parent pipeline is returned. ```plaintext GET /projects/:id/jobs/artifacts/:ref_name/download?job=name @@ -80,12 +80,12 @@ GET /projects/:id/jobs/artifacts/:ref_name/download?job=name Parameters -| Attribute | Type | Required | Description | -|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | -| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. | -| `job` | string | yes | The name of the job. | -| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/triggers/README.md#when-a-pipeline-depends-on-the-artifacts-of-another-pipeline) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. | +| Attribute | Type | Required | Description | +|-------------|----------------|----------|--------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. | +| `job` | string | yes | The name of the job. | +| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/triggers/README.md#when-a-pipeline-depends-on-the-artifacts-of-another-pipeline) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. | Example request using the `PRIVATE-TOKEN` header: @@ -97,7 +97,7 @@ To use this in a [`script` definition](../ci/yaml/README.md#script) inside `.gitlab-ci.yml` **(PREMIUM)**, you can use either: - The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable. - For example, the following job will download the artifacts of the `test` job + For example, the following job downloads the artifacts of the `test` job of the `master` branch. Note that the command is wrapped into single quotes since it contains a colon (`:`): @@ -109,7 +109,7 @@ To use this in a [`script` definition](../ci/yaml/README.md#script) inside ``` - Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable. - For example, the following job will download the artifacts of the `test` job + For example, the following job downloads the artifacts of the `test` job of the `master` branch: ```yaml @@ -179,12 +179,12 @@ GET /projects/:id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name Parameters: -| Attribute | Type | Required | Description | -|-----------------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| Attribute | Type | Required | Description | +|-----------------|----------------|----------|--------------------------------------------------------------------------------------------------------------| | `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | -| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. | -| `artifact_path` | string | yes | Path to a file inside the artifacts archive. | -| `job` | string | yes | The name of the job. | +| `ref_name` | string | yes | Branch or tag name in repository. `HEAD` or `SHA` references are not supported. | +| `artifact_path` | string | yes | Path to a file inside the artifacts archive. | +| `job` | string | yes | The name of the job. | Example request: @@ -210,8 +210,8 @@ POST /projects/:id/jobs/:job_id/artifacts/keep Parameters -| Attribute | Type | Required | Description | -|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| Attribute | Type | Required | Description | +|-----------|----------------|----------|--------------------------------------------------------------------------------------------------------------| | `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | | `job_id` | integer | yes | ID of a job. | @@ -264,8 +264,8 @@ Delete artifacts of a job. DELETE /projects/:id/jobs/:job_id/artifacts ``` -| Attribute | Type | Required | Description | -|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| Attribute | Type | Required | Description | +|-----------|----------------|----------|-----------------------------------------------------------------------------| | `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `job_id` | integer | yes | ID of a job. | diff --git a/doc/api/merge_trains.md b/doc/api/merge_trains.md index 3cfef3864ad..9dc1acec16c 100644 --- a/doc/api/merge_trains.md +++ b/doc/api/merge_trains.md @@ -11,9 +11,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w Every API call to merge trains must be authenticated with Developer or higher [permissions](../user/permissions.md). -If a user is not a member of a project and the project is private, a `GET` request on that project will result to a `404` status code. +If a user is not a member of a project and the project is private, a `GET` request on that project returns a `404` status code. -If Merge Trains is not available for the project, a `403` status code will return. +If Merge Trains is not available for the project, a `403` status code is returned. ## Merge Trains API pagination diff --git a/doc/api/pipeline_schedules.md b/doc/api/pipeline_schedules.md index 1faa6ef56db..fcbc36f83a8 100644 --- a/doc/api/pipeline_schedules.md +++ b/doc/api/pipeline_schedules.md @@ -109,14 +109,14 @@ Create a new pipeline schedule of a project. POST /projects/:id/pipeline_schedules ``` -| Attribute | Type | required | Description | -|---------------|---------|----------|--------------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `description` | string | yes | The description of pipeline schedule | -| `ref` | string | yes | The branch/tag name will be triggered | -| `cron` | string | yes | The cron (e.g. `0 1 * * *`) ([Cron syntax](https://en.wikipedia.org/wiki/Cron)) | -| `cron_timezone` | string | no | The timezone supported by `ActiveSupport::TimeZone` (e.g. `Pacific Time (US & Canada)`) (default: `'UTC'`) | -| `active` | boolean | no | The activation of pipeline schedule. If false is set, the pipeline schedule will deactivated initially (default: `true`) | +| Attribute | Type | required | Description | +|-----------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `description` | string | yes | The description of the pipeline schedule. | +| `ref` | string | yes | The branch or tag name that is triggered. | +| `cron` | string | yes | The [cron](https://en.wikipedia.org/wiki/Cron) schedule, for example: `0 1 * * *`. | +| `cron_timezone` | string | no | The timezone supported by `ActiveSupport::TimeZone`, for example: `Pacific Time (US & Canada)` (default: `'UTC'`). | +| `active` | boolean | no | The activation of pipeline schedule. If false is set, the pipeline schedule is initially deactivated (default: `true`). | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form description="Build packages" --form ref="master" --form cron="0 1 * * 5" --form cron_timezone="UTC" --form active="true" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules" @@ -147,21 +147,21 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form descrip ## Edit a pipeline schedule -Updates the pipeline schedule of a project. Once the update is done, it will be rescheduled automatically. +Updates the pipeline schedule of a project. Once the update is done, it is rescheduled automatically. ```plaintext PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id ``` -| Attribute | Type | required | Description | -|---------------|---------|----------|--------------------------| -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `pipeline_schedule_id` | integer | yes | The pipeline schedule ID | -| `description` | string | no | The description of pipeline schedule | -| `ref` | string | no | The branch/tag name will be triggered | -| `cron` | string | no | The cron (e.g. `0 1 * * *`) ([Cron syntax](https://en.wikipedia.org/wiki/Cron)) | -| `cron_timezone` | string | no | The timezone supported by `ActiveSupport::TimeZone` (e.g. `Pacific Time (US & Canada)`) or `TZInfo::Timezone` (e.g. `America/Los_Angeles`) | -| `active` | boolean | no | The activation of pipeline schedule. If false is set, the pipeline schedule will deactivated initially. | +| Attribute | Type | required | Description | +|------------------------|----------------|----------|------------------------------------------------------------------------------------------------------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | +| `pipeline_schedule_id` | integer | yes | The pipeline schedule ID. | +| `description` | string | no | The description of the pipeline schedule. | +| `ref` | string | no | The branch or tag name that is triggered. | +| `cron` | string | no | The [cron](https://en.wikipedia.org/wiki/Cron) schedule, for example: `0 1 * * *`. | +| `cron_timezone` | string | no | The timezone supported by `ActiveSupport::TimeZone` (for example `Pacific Time (US & Canada)`), or `TZInfo::Timezone` (for example `America/Los_Angeles`). | +| `active` | boolean | no | The activation of pipeline schedule. If false is set, the pipeline schedule is initially deactivated. | ```shell curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form cron="0 2 * * *" "https://gitlab.example.com/api/v4/projects/29/pipeline_schedules/13" diff --git a/doc/ci/README.md b/doc/ci/README.md index 45cf56df894..a363721bd73 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -96,6 +96,7 @@ GitLab CI/CD uses a number of concepts to describe and run your build and deploy | [Cache dependencies](caching/index.md) | Cache your dependencies for a faster execution. | | [GitLab Runner](https://docs.gitlab.com/runner/) | Configure your own runners to execute your scripts. | | [Pipeline efficiency](pipelines/pipeline_efficiency.md) | Configure your pipelines to run quickly and efficiently. | +| [Test cases](test_cases/index.md) | Configure your pipelines to run quickly and efficiently. | ## Configuration @@ -121,38 +122,38 @@ Note that certain operations can only be performed according to the Use the vast GitLab CI/CD to easily configure it for specific purposes. Its feature set is listed on the table below according to DevOps stages. -| Feature | Description | -|:--------|:------------| -| **Configure** || -| [Auto DevOps](../topics/autodevops/index.md) | Set up your app's entire lifecycle. | -| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. | -|---+---| -| **Verify** || -| [Browser Performance Testing](../user/project/merge_requests/browser_performance_testing.md) | Quickly determine the browser performance impact of pending code changes. | -| [Load Performance Testing](../user/project/merge_requests/load_performance_testing.md) | Quickly determine the server performance impact of pending code changes. | -| [CI services](services/README.md) | Link Docker containers with your base image.| -| [Code Quality](../user/project/merge_requests/code_quality.md) | Analyze your source code quality. | -| [GitLab CI/CD for external repositories](ci_cd_for_external_repos/index.md) **(PREMIUM)** | Get the benefits of GitLab CI/CD combined with repositories in GitHub and Bitbucket Cloud. | -| [Interactive Web Terminals](interactive_web_terminal/index.md) **(CORE ONLY)** | Open an interactive web terminal to debug the running jobs. | -| [Unit test reports](unit_test_reports.md) | Identify script failures directly on merge requests. | -| [Using Docker images](docker/using_docker_images.md) | Use GitLab and GitLab Runner with Docker to build and test applications. | -|---+---| -| **Release** || -| [Auto Deploy](../topics/autodevops/stages.md#auto-deploy) | Deploy your application to a production environment in a Kubernetes cluster. | -| [Building Docker images](docker/using_docker_build.md) | Maintain Docker-based projects using GitLab CI/CD. | -| [Canary Deployments](../user/project/canary_deployments.md) **(PREMIUM)** | Ship features to only a portion of your pods and let a percentage of your user base to visit the temporarily deployed feature. | -| [Deploy Boards](../user/project/deploy_boards.md) **(PREMIUM)** | Check the current health and status of each CI/CD environment running on Kubernetes. | -| [Feature Flags](../operations/feature_flags.md) **(PREMIUM)** | Deploy your features behind Feature Flags. | -| [GitLab Pages](../user/project/pages/index.md) | Deploy static websites. | -| [GitLab Releases](../user/project/releases/index.md) | Add release notes to Git tags. | -| [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes. | -| [Cloud deployment](cloud_deployment/index.md) | Deploy your application to a main cloud provider. | -|---+---| -| **Secure** || -| [Container Scanning](../user/application_security/container_scanning/index.md) **(ULTIMATE)** | Check your Docker containers for known vulnerabilities.| -| [Dependency Scanning](../user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. | -| [License Compliance](../user/compliance/license_compliance/index.md) **(ULTIMATE)** | Search your project dependencies for their licenses. | -| [Security Test reports](../user/application_security/index.md) **(ULTIMATE)** | Check for app vulnerabilities. | +| Feature | Description | +|:------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------| +| **Configure** | | +| [Auto DevOps](../topics/autodevops/index.md) | Set up your app's entire lifecycle. | +| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. | +|-------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------| +| **Verify** | | +| [Browser Performance Testing](../user/project/merge_requests/browser_performance_testing.md) | Quickly determine the browser performance impact of pending code changes. | +| [Load Performance Testing](../user/project/merge_requests/load_performance_testing.md) | Quickly determine the server performance impact of pending code changes. | +| [CI services](services/README.md) | Link Docker containers with your base image. | +| [Code Quality](../user/project/merge_requests/code_quality.md) | Analyze your source code quality. | +| [GitLab CI/CD for external repositories](ci_cd_for_external_repos/index.md) **(PREMIUM)** | Get the benefits of GitLab CI/CD combined with repositories in GitHub and Bitbucket Cloud. | +| [Interactive Web Terminals](interactive_web_terminal/index.md) **(CORE ONLY)** | Open an interactive web terminal to debug the running jobs. | +| [Unit test reports](unit_test_reports.md) | Identify script failures directly on merge requests. | +| [Using Docker images](docker/using_docker_images.md) | Use GitLab and GitLab Runner with Docker to build and test applications. | +|-------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------| +| **Release** | | +| [Auto Deploy](../topics/autodevops/stages.md#auto-deploy) | Deploy your application to a production environment in a Kubernetes cluster. | +| [Building Docker images](docker/using_docker_build.md) | Maintain Docker-based projects using GitLab CI/CD. | +| [Canary Deployments](../user/project/canary_deployments.md) **(PREMIUM)** | Ship features to only a portion of your pods and let a percentage of your user base to visit the temporarily deployed feature. | +| [Deploy Boards](../user/project/deploy_boards.md) **(PREMIUM)** | Check the current health and status of each CI/CD environment running on Kubernetes. | +| [Feature Flags](../operations/feature_flags.md) **(PREMIUM)** | Deploy your features behind Feature Flags. | +| [GitLab Pages](../user/project/pages/index.md) | Deploy static websites. | +| [GitLab Releases](../user/project/releases/index.md) | Add release notes to Git tags. | +| [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes. | +| [Cloud deployment](cloud_deployment/index.md) | Deploy your application to a main cloud provider. | +|-------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------| +| **Secure** | | +| [Container Scanning](../user/application_security/container_scanning/index.md) **(ULTIMATE)** | Check your Docker containers for known vulnerabilities. | +| [Dependency Scanning](../user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. | +| [License Compliance](../user/compliance/license_compliance/index.md) **(ULTIMATE)** | Search your project dependencies for their licenses. | +| [Security Test reports](../user/application_security/index.md) **(ULTIMATE)** | Check for app vulnerabilities. | ## Examples diff --git a/doc/ci/enable_or_disable_ci.md b/doc/ci/enable_or_disable_ci.md index 8b88ec509e7..189281635fd 100644 --- a/doc/ci/enable_or_disable_ci.md +++ b/doc/ci/enable_or_disable_ci.md @@ -31,7 +31,7 @@ either: - Site-wide by modifying the settings in `gitlab.yml` and `gitlab.rb` for source and Omnibus installations respectively. -This only applies to pipelines run as part of GitLab CI/CD. This will not enable or disable +This only applies to pipelines run as part of GitLab CI/CD. This doesn't enable or disable pipelines that are run from an [external integration](../user/project/integrations/overview.md#integrations-listing). ## Per-project user setting @@ -42,7 +42,7 @@ To enable or disable GitLab CI/CD Pipelines in your project: 1. Expand the **Repository** section 1. Enable or disable the **Pipelines** toggle as required. -**Project visibility** will also affect pipeline visibility. If set to: +**Project visibility** also affects pipeline visibility. If set to: - **Private**: Only project members can access pipelines. - **Internal** or **Public**: Pipelines can be set to either **Only Project Members** @@ -57,9 +57,9 @@ for source installations, and `gitlab.rb` for Omnibus installations. Two things to note: -- Disabling GitLab CI/CD, will affect only newly-created projects. Projects that - had it enabled prior to this modification, will work as before. -- Even if you disable GitLab CI/CD, users will still be able to enable it in the +- Disabling GitLab CI/CD affects only newly-created projects. Projects that + had it enabled prior to this modification work as before. +- Even if you disable GitLab CI/CD, users can still enable it in the project's settings. For installations from source, open `gitlab.yml` with your editor and set diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md index c28febd15d7..48434c02dfd 100644 --- a/doc/ci/git_submodules.md +++ b/doc/ci/git_submodules.md @@ -21,7 +21,7 @@ type: reference ## Configuring the `.gitmodules` file -If dealing with [Git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules), your project will probably have a file +If dealing with [Git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules), your project probably has a file named `.gitmodules`. Let's consider the following example: @@ -44,11 +44,11 @@ for all your local checkouts. The `.gitmodules` would look like: url = ../../group/project.git ``` -The above configuration will instruct Git to automatically deduce the URL that -should be used when cloning sources. Whether you use HTTP(S) or SSH, Git will use -that same channel and it will allow to make all your CI jobs use HTTP(S) -(because GitLab CI/CD only uses HTTP(S) for cloning your sources), and all your local -clones will continue using SSH. +The above configuration instructs Git to automatically deduce the URL that +should be used when cloning sources. Whether you use HTTP(S) or SSH, Git uses +that same channel and it makes all your CI jobs use HTTP(S). +GitLab CI/CD only uses HTTP(S) for cloning your sources, and all your local +clones continue using SSH. For all other submodules not located on the same GitLab server, use the full HTTP(S) protocol URL: @@ -94,7 +94,7 @@ correctly with your CI jobs: whether you have recursive submodules. The rationale to set the `sync` and `update` in `before_script` is because of -the way Git submodules work. On a fresh runner workspace, Git will set the +the way Git submodules work. On a fresh runner workspace, Git sets the submodule URL including the token in `.git/config` (or `.git/modules/<submodule>/config`) based on `.gitmodules` and the current remote URL. On subsequent jobs on the same runner, `.git/config` is cached diff --git a/doc/ci/metrics_reports.md b/doc/ci/metrics_reports.md index dbc0397bb0b..d66ab2b297a 100644 --- a/doc/ci/metrics_reports.md +++ b/doc/ci/metrics_reports.md @@ -11,7 +11,7 @@ type: reference GitLab provides a lot of great reporting tools for [merge requests](../user/project/merge_requests/index.md) - [Unit test reports](unit_test_reports.md), [code quality](../user/project/merge_requests/code_quality.md), performance tests, etc. While JUnit is a great open framework for tests that "pass" or "fail", it is also important to see other types of metrics from a given change. -You can configure your job to use custom Metrics Reports, and GitLab will display a report on the merge request so that it's easier and faster to identify changes without having to check the entire log. +You can configure your job to use custom Metrics Reports, and GitLab displays a report on the merge request so that it's easier and faster to identify changes without having to check the entire log. ![Metrics Reports](img/metrics_reports_v13_0.png) diff --git a/doc/ci/migration/circleci.md b/doc/ci/migration/circleci.md index 13190c15cca..5bb8e76831a 100644 --- a/doc/ci/migration/circleci.md +++ b/doc/ci/migration/circleci.md @@ -200,7 +200,7 @@ deploy_prod: ### Filter job by branch -[Rules](../yaml/README.md#rules) are a mechanism to determine if the job will or will not run for a specific branch. +[Rules](../yaml/README.md#rules) are a mechanism to determine if the job runs for a specific branch. CircleCI example of a job filtered by branch: diff --git a/doc/ci/migration/jenkins.md b/doc/ci/migration/jenkins.md index afec94ca91c..19df310d1a2 100644 --- a/doc/ci/migration/jenkins.md +++ b/doc/ci/migration/jenkins.md @@ -33,7 +33,7 @@ For an example of how to convert a Jenkins pipeline into a GitLab CI/CD pipeline or how to use Auto DevOps to test your code automatically, watch the [Migrating from Jenkins to GitLab](https://www.youtube.com/watch?v=RlEVGOpYF5Y) video. -Otherwise, read on for important information that will help you get the ball rolling. Welcome +Otherwise, read on for important information that helps you get the ball rolling. Welcome to GitLab! If you have questions that are not answered here, the [GitLab community forum](https://forum.gitlab.com/) @@ -46,22 +46,22 @@ changes that comes with the move, and successfully managing them. There are a fe things we have found that helps this: - Setting and communicating a clear vision of what your migration goals are helps - your users understand why the effort is worth it. The value will be clear when + your users understand why the effort is worth it. The value is clear when the work is done, but people need to be aware while it's in progress too. - Sponsorship and alignment from the relevant leadership team helps with the point above. - Spending time educating your users on what's different, sharing this document with them, - and so on will help ensure you are successful. + and so on helps ensure you are successful. - Finding ways to sequence or delay parts of the migration can help a lot, but you don't want to leave things in a non-migrated (or partially-migrated) state for too long. To gain all the benefits of GitLab, moving your existing Jenkins setup over - as-is, including any current problems, will not be enough. You need to take advantage + as-is, including any current problems, isn't enough. You need to take advantage of the improvements that GitLab offers, and this requires (eventually) updating your implementation as part of the transition. ## JenkinsFile Wrapper -We are building a [JenkinsFile Wrapper](https://gitlab.com/gitlab-org/jfr-container-builder/) which will allow -you to run a complete Jenkins instance inside of a GitLab job, including plugins. This can help ease the process +We are building a [JenkinsFile Wrapper](https://gitlab.com/gitlab-org/jfr-container-builder/) which +you can use to run a complete Jenkins instance inside of a GitLab job, including plugins. This can help ease the process of transition, by letting you delay the migration of less urgent pipelines for a period of time. If you are interested in helping GitLab test the wrapper, join our [public testing issue](https://gitlab.com/gitlab-org/gitlab/-/issues/215675) for instructions and to provide your feedback. @@ -103,8 +103,8 @@ There are some high level differences between the products worth mentioning: - One important difference is that jobs run independently of each other and have a fresh environment in each job. Passing artifacts between jobs is controlled using the [`artifacts`](../yaml/README.md#artifacts) and [`dependencies`](../yaml/README.md#dependencies) - keywords. When finished, the planned [Workspaces](https://gitlab.com/gitlab-org/gitlab/-/issues/29265) - feature will allow you to more easily persist a common workspace between serial jobs. + keywords. When finished, use the planned [Workspaces](https://gitlab.com/gitlab-org/gitlab/-/issues/29265) + feature to more easily persist a common workspace between serial jobs. - The `.gitlab-ci.yml` file is checked in to the root of your repository, much like a Jenkinsfile, but is in the YAML format (see [complete reference](../yaml/README.md)) instead of a Groovy DSL. It's most analogous to the declarative Jenkinsfile format. @@ -114,7 +114,7 @@ There are some high level differences between the products worth mentioning: - GitLab comes with a [container registry](../../user/packages/container_registry/index.md), and we recommend using container images to set up your build environment. For example, set up one pipeline that builds your build environment itself and publish that to the container registry. Then, have your pipelines use this instead of each building their - own environment, which will be slower and may be less consistent. We have extensive docs on [how to use the Container Registry](../../user/packages/container_registry/index.md). + own environment, which is slower and may be less consistent. We have extensive docs on [how to use the Container Registry](../../user/packages/container_registry/index.md). - A central utilities repository can be a great place to put assorted scheduled jobs or other manual jobs that function like utilities. Jenkins installations tend to have a few of these. @@ -129,12 +129,12 @@ agents you were using. There are some important differences in the way runners work in comparison to agents: - Runners can be set up as [shared across an instance, be added at the group level, or set up at the project level](../runners/README.md#types-of-runners). - They will self-select jobs from the scopes you've defined automatically. + They self-select jobs from the scopes you've defined automatically. - You can also [use tags](../runners/README.md#use-tags-to-limit-the-number-of-jobs-using-the-runner) for finer control, and associate runners with specific jobs. For example, you can use a tag for jobs that require dedicated, more powerful, or specific hardware. -- GitLab has [autoscaling for runners](https://docs.gitlab.com/runner/configuration/autoscale.html) - which will let you configure them to be provisioned as needed, and scaled down when not. +- GitLab has [autoscaling for runners](https://docs.gitlab.com/runner/configuration/autoscale.html). + Use autoscaling to provision runners only when needed, and scale down when not needed. This is similar to ephemeral agents in Jenkins. If you are using `gitlab.com`, you can take advantage of our [shared runner fleet](../../user/gitlab_com/index.md#shared-runners) @@ -227,15 +227,15 @@ and is meant to be a mapping of concepts there to concepts in GitLab. #### `agent` -The agent section is used to define how a pipeline will be executed. For GitLab, we use [runners](../runners/README.md) +The agent section is used to define how a pipeline executes. For GitLab, we use [runners](../runners/README.md) to provide this capability. You can configure your own runners in Kubernetes or on any host, or take advantage -of our shared runner fleet (note that the shared runner fleet is only available for GitLab.com users.) The link above will bring you to the documentation which will describe how to get -up and running quickly. We also support using [tags](../runners/README.md#use-tags-to-limit-the-number-of-jobs-using-the-runner) to direct different jobs +of our shared runner fleet (note that the shared runner fleet is only available for GitLab.com users). +We also support using [tags](../runners/README.md#use-tags-to-limit-the-number-of-jobs-using-the-runner) to direct different jobs to different runners (execution agents). The `agent` section also allows you to define which Docker images should be used for execution, for which we use the [`image`](../yaml/README.md#image) keyword. The `image` can be set on a single job or at the top level, in which -case it will apply to all jobs in the pipeline: +case it applies to all jobs in the pipeline: ```yaml my_job: @@ -246,7 +246,7 @@ my_job: The `post` section defines the actions that should be performed at the end of the pipeline. GitLab also supports this through the use of stages. You can define your stages as follows, and any jobs assigned to the `before_pipeline` -or `after_pipeline` stages will run as expected. You can call these stages anything you like: +or `after_pipeline` stages run as expected. You can call these stages anything you like: ```yaml stages: diff --git a/doc/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md index 5837bf6cf6b..c06eea20f6c 100644 --- a/doc/ci/multi_project_pipelines.md +++ b/doc/ci/multi_project_pipelines.md @@ -46,7 +46,7 @@ When you configure GitLab CI/CD for your project, you can visualize the stages o ![Multi-project pipeline graph](img/multi_project_pipeline_graph.png) In the Merge Request Widget, multi-project pipeline mini-graphs are displayed, -and when hovering or tapping (on touchscreen devices) they will expand and be shown adjacent to each other. +and when hovering or tapping (on touchscreen devices) they expand and are shown adjacent to each other. ![Multi-project mini graph](img/multi_pipeline_mini_graph.gif) @@ -97,16 +97,16 @@ staging: trigger: my/deployment ``` -In the example above, as soon as `rspec` job succeeds in the `test` stage, -the `staging` bridge job is going to be started. The initial status of this -job will be `pending`. GitLab will create a downstream pipeline in the -`my/deployment` project and, as soon as the pipeline gets created, the -`staging` job will succeed. `my/deployment` is a full path to that project. +In the example above, as soon as the `rspec` job succeeds in the `test` stage, +the `staging` bridge job is started. The initial status of this +job is `pending`. GitLab then creates a downstream pipeline in the +`my/deployment` project and, as soon as the pipeline is created, the +`staging` job succeeds. `my/deployment` is a full path to that project. The user that created the upstream pipeline needs to have access rights to the -downstream project (`my/deployment` in this case). If a downstream project can -not be found, or a user does not have access rights to create pipeline there, -the `staging` job is going to be marked as _failed_. +downstream project (`my/deployment` in this case). If a downstream project is +not found, or a user does not have access rights to create a pipeline there, +the `staging` job is marked as _failed_. When using: @@ -117,21 +117,18 @@ When using: - [`only/except`](yaml/README.md#onlyexcept-basic) to control job behavior, use the `pipelines` keyword. -CAUTION: **Caution:** -In the example, `staging` will be marked as succeeded as soon as a downstream pipeline +In the example, `staging` is marked as successful as soon as a downstream pipeline gets created. If you want to display the downstream pipeline's status instead, see [Mirroring status from triggered pipeline](#mirroring-status-from-triggered-pipeline). NOTE: **Note:** -Bridge jobs do not support every configuration entry that a user can use -in the case of regular jobs. Bridge jobs will not be picked by a runner, -so there is no point in adding support for `script`, for example. If a user -tries to use unsupported configuration syntax, YAML validation will fail upon -pipeline creation. +Bridge jobs [do not support every configuration keyword](#limitations) that can be used +with other jobs. If a user tries to use unsupported configuration keywords, YAML +validation fails on pipeline creation. ### Specifying a downstream pipeline branch -It is possible to specify a branch name that a downstream pipeline will use: +It is possible to specify a branch name that a downstream pipeline uses: ```yaml rspec: @@ -152,7 +149,7 @@ Use: [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/issues/10126), variable expansion is supported. -GitLab will use a commit that is currently on the HEAD of the branch when +GitLab uses a commit that is on the head of the branch when creating a downstream pipeline. NOTE: **Note:** @@ -181,10 +178,10 @@ staging: trigger: my/deployment ``` -The `ENVIRONMENT` variable will be passed to every job defined in a downstream -pipeline. It will be available as an environment variable when GitLab Runner picks a job. +The `ENVIRONMENT` variable is passed to every job defined in a downstream +pipeline. It is available as an environment variable when GitLab Runner picks a job. -In the following configuration, the `MY_VARIABLE` variable will be passed to the downstream pipeline +In the following configuration, the `MY_VARIABLE` variable is passed to the downstream pipeline that is created when the `trigger-downstream` job is queued. This is because `trigger-downstream` job inherits variables declared in global variables blocks, and then we pass these variables to a downstream pipeline. @@ -210,7 +207,7 @@ downstream-job: ``` In this scenario, the `UPSTREAM_BRANCH` variable with a value related to the -upstream pipeline will be passed to the `downstream-job` job, and will be available +upstream pipeline is passed to the `downstream-job` job, and is available within the context of all downstream builds. Upstream pipelines take precedence over downstream ones. If there are two @@ -289,9 +286,9 @@ upstream_bridge: ### Limitations -Because bridge jobs are a little different to regular jobs, it is not -possible to use exactly the same configuration syntax here, as one would -normally do when defining a regular job that will be picked by a runner. +Bridge jobs are a little different from regular jobs. It is not +possible to use exactly the same configuration syntax as when defining regular jobs +that are picked by a runner. Some features are not implemented yet. For example, support for environments. @@ -318,7 +315,7 @@ tag in a different project: 1. Click subscribe. Any pipelines that complete successfully for new tags in the subscribed project -will now trigger a pipeline on the current project's default branch. The maximum +now trigger a pipeline on the current project's default branch. The maximum number of upstream pipeline subscriptions is 2 by default, for both the upstream and downstream projects. This [application limit](../administration/instance_limits.md#number-of-cicd-subscriptions-to-a-project) can be changed on self-managed instances by a GitLab administrator. diff --git a/doc/ci/parent_child_pipelines.md b/doc/ci/parent_child_pipelines.md index b177751d5da..f5a39a8b7c8 100644 --- a/doc/ci/parent_child_pipelines.md +++ b/doc/ci/parent_child_pipelines.md @@ -52,8 +52,8 @@ For an overview, see [Parent-Child Pipelines feature demo](https://youtu.be/n8Kp ## Examples The simplest case is [triggering a child pipeline](yaml/README.md#trigger) using a -local YAML file to define the pipeline configuration. In this case, the parent pipeline will -trigger the child pipeline, and continue without waiting: +local YAML file to define the pipeline configuration. In this case, the parent pipeline +triggers the child pipeline, and continues without waiting: ```yaml microservice_a: diff --git a/doc/ci/pipelines/pipeline_architectures.md b/doc/ci/pipelines/pipeline_architectures.md index 77614424b33..32636f5df51 100644 --- a/doc/ci/pipelines/pipeline_architectures.md +++ b/doc/ci/pipelines/pipeline_architectures.md @@ -22,8 +22,8 @@ any of the keywords used below, check out our [CI YAML reference](../yaml/README ## Basic Pipelines -This is the simplest pipeline in GitLab. It will run everything in the build stage concurrently, -and once all of those finish, it will run everything in the test stage the same way, and so on. +This is the simplest pipeline in GitLab. It runs everything in the build stage concurrently, +and once all of those finish, it runs everything in the test stage the same way, and so on. It's not the most efficient, and if you have lots of steps it can grow quite complex, but it's easier to maintain: @@ -101,7 +101,7 @@ your jobs. When GitLab knows the relationships between your jobs, it can run eve as fast as possible, and even skips into subsequent stages when possible. In the example below, if `build_a` and `test_a` are much faster than `build_b` and -`test_b`, GitLab will start `deploy_a` even if `build_b` is still running. +`test_b`, GitLab starts `deploy_a` even if `build_b` is still running. ```mermaid graph LR @@ -163,7 +163,7 @@ deploy_b: In the examples above, it's clear we've got two types of things that could be built independently. This is an ideal case for using [Child / Parent Pipelines](../parent_child_pipelines.md)) via -the [`trigger` keyword](../yaml/README.md#trigger). It will separate out the configuration +the [`trigger` keyword](../yaml/README.md#trigger). It separates out the configuration into multiple files, keeping things very simple. You can also combine this with: - The [`rules` keyword](../yaml/README.md#rules): For example, have the child pipelines triggered only diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index f3e60fae13a..851336b77ab 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -53,7 +53,7 @@ must [install GitLab Runner](https://docs.gitlab.com/runner/install/) and [register](https://docs.gitlab.com/runner/register/) at least one runner. If you are testing CI/CD, you can install GitLab Runner and register runners on your local machine. -When your CI/CD jobs run, they will run on your local machine. +When your CI/CD jobs run, they run on your local machine. ### Create a `.gitlab-ci.yml` file diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index a329331df08..8f00db69e51 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -36,7 +36,7 @@ with any type of [executor](https://docs.gitlab.com/runner/executors/) `~/.ssh/authorized_keys`) or add it as a [deploy key](../../ssh/README.md#deploy-keys) if you are accessing a private GitLab repository. -The private key will not be displayed in the job log, unless you enable +The private key is displayed in the job log, unless you enable [debug logging](../variables/README.md#debug-logging). You might also want to check the [visibility of your pipelines](../pipelines/settings.md#visibility-of-pipelines). @@ -46,7 +46,7 @@ When your CI/CD jobs run inside Docker containers (meaning the environment is contained) and you want to deploy your code in a private server, you need a way to access it. This is where an SSH key pair comes in handy. -1. You will first need to create an SSH key pair. For more information, follow +1. You first need to create an SSH key pair. For more information, follow the instructions to [generate an SSH key](../../ssh/README.md#generating-a-new-ssh-key-pair). **Do not** add a passphrase to the SSH key, or the `before_script` will prompt for it. @@ -144,9 +144,9 @@ For accessing repositories on GitLab.com, you would use `git@gitlab.com`. ## Verifying the SSH host keys It is a good practice to check the private server's own public key to make sure -you are not being targeted by a man-in-the-middle attack. In case anything -suspicious happens, you will notice it since the job would fail (the SSH -connection would fail if the public keys would not match). +you are not being targeted by a man-in-the-middle attack. If anything +suspicious happens, you notice it because the job fails (the SSH +connection fails when the public keys don't match). To find out the host keys of your server, run the `ssh-keyscan` command from a trusted network (ideally, from the private server itself): @@ -169,8 +169,8 @@ TIP: **Tip:** By using a variable instead of `ssh-keyscan` directly inside `.gitlab-ci.yml`, it has the benefit that you don't have to change `.gitlab-ci.yml` if the host domain name changes for some reason. Also, the values are predefined -by you, meaning that if the host keys suddenly change, the CI/CD job will fail, -and you'll know there's something wrong with the server or the network. +by you, meaning that if the host keys suddenly change, the CI/CD job doesn't fail, +so there's something wrong with the server or the network. Now that the `SSH_KNOWN_HOSTS` variable is created, in addition to the [content of `.gitlab-ci.yml`](#ssh-keys-when-using-the-docker-executor) @@ -209,4 +209,4 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available [shared runners](../runners/README.md). Want to hack on it? Simply fork it, commit and push your changes. Within a few -moments the changes will be picked by a public runner and the job will begin. +moments the changes is picked by a public runner and the job starts. diff --git a/doc/ci/test_cases/img/test_case_list_v13_6.png b/doc/ci/test_cases/img/test_case_list_v13_6.png Binary files differnew file mode 100644 index 00000000000..b5d89582558 --- /dev/null +++ b/doc/ci/test_cases/img/test_case_list_v13_6.png diff --git a/doc/ci/test_cases/img/test_case_show_v13_6.png b/doc/ci/test_cases/img/test_case_show_v13_6.png Binary files differnew file mode 100644 index 00000000000..bbd7601cf30 --- /dev/null +++ b/doc/ci/test_cases/img/test_case_show_v13_6.png diff --git a/doc/ci/test_cases/index.md b/doc/ci/test_cases/index.md new file mode 100644 index 00000000000..5defbf632e2 --- /dev/null +++ b/doc/ci/test_cases/index.md @@ -0,0 +1,80 @@ +--- +stage: Plan +group: Certify +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/#designated-technical-writers +description: Test cases in GitLab can help your teams create testing scenarios in their existing development platform. +type: reference +--- + +# Test Cases **(ULTIMATE)** + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233479) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.6. +> - It's [deployed behind a feature flag](../../user/feature_flags.md), enabled by default. +> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/241983) on GitLab 13.6. +> - It's enabled on GitLab.com. +> - It's recommended for production use. +> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-test-cases). **(ULTIMATE ONLY)** + +Test cases in GitLab can help your teams create testing scenarios in their existing development platform. + +This can help the Implementation and Testing teams collaborate, because they no longer have to +use external test planning tools, which require additional overhead, context switching, and expense. + +## Create a test case + +To create a test case in a GitLab project: + +1. Navigate to **CI/CD > Test Cases**. +1. Select the **New test case** button. You are taken to the new test case form. Here you can enter + the new case's title, [description](../../user/markdown.md), attach a file, and assign [labels](../../user/project/labels.md). +1. Select the **Submit test case** button. You are taken to view the new test case. + +## View a test case + +You can view all test cases in the project in the Test Cases list. Filter the +issue list with a search query, including labels or the test case's title. + +![Test case list page](img/test_case_list_v13_6.png) + +To view a test case: + +1. In a project, navigate to **CI/CD > Test Cases**. +1. Select the title of the test case you want to view. You are taken to the test case page. + +![An example test case page](img/test_case_show_v13_6.png) + +## Archive a test case + +When you want to stop using a test case, you can archive it. You can [reopen an archived test case](#reopen-an-archived-test-case) later. + +To archive a test case, on the test case's page, select the **Archive test case** button. + +To view archived test cases: + +1. Navigate to **CI/CD > Test Cases**. +1. Select **Archived**. + +## Reopen an archived test case + +If you decide to start using an archived test case again, you can reopen it. + +To reopen an archived test case, on the test case's page, select the **Reopen test case** button. + +### Enable or disable Test Cases **(ULTIMATE ONLY)** + +The GitLab Test Cases feature is under development but ready for production use. +It is deployed behind a feature flag that is **enabled by default**. +[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) +can opt to disable it. + +To enable it: + +```ruby +Feature.enable(:quality_test_cases, Project.find_by_full_path('<project_path>')) +``` + +To disable it: + +```ruby +Feature.disable(:quality_test_cases, Project.find_by_full_path('<project_path>')) +``` diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index 6fca482975c..9356e9f4614 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -268,5 +268,5 @@ This behavior can also be achieved through GitLab's UI with Old triggers, created before GitLab 9.0 are marked as legacy. Triggers with the legacy label do not have an associated user and only have -access to the current project. They are considered deprecated and will be +access to the current project. They are considered deprecated and might be removed with one of the future versions of GitLab. diff --git a/doc/ci/variables/deprecated_variables.md b/doc/ci/variables/deprecated_variables.md index 71e2b5b2e44..efb89499c1c 100644 --- a/doc/ci/variables/deprecated_variables.md +++ b/doc/ci/variables/deprecated_variables.md @@ -17,7 +17,7 @@ To follow conventions of naming across GitLab, and to further move away from the release. Starting with GitLab 9.0, we have deprecated the `$CI_BUILD_*` variables. **You are -strongly advised to use the new variables as we will remove the old ones in +strongly advised to use the new variables as we might remove the old ones in future GitLab releases.** | 8.x name | 9.0+ name | diff --git a/doc/ci/variables/where_variables_can_be_used.md b/doc/ci/variables/where_variables_can_be_used.md index b914f3db572..4562fc75a55 100644 --- a/doc/ci/variables/where_variables_can_be_used.md +++ b/doc/ci/variables/where_variables_can_be_used.md @@ -57,8 +57,8 @@ There are three expansion mechanisms: ### GitLab internal variable expansion mechanism The expanded part needs to be in a form of `$variable`, or `${variable}` or `%variable%`. -Each form is handled in the same way, no matter which OS/shell will finally handle the job, -since the expansion is done in GitLab before any runner will get the job. +Each form is handled in the same way, no matter which OS/shell handles the job, +because the expansion is done in GitLab before any runner gets the job. ### GitLab Runner internal variable expansion mechanism @@ -66,7 +66,7 @@ since the expansion is done in GitLab before any runner will get the job. variables from triggers, pipeline schedules, and manual pipelines. - Not supported: variables defined inside of scripts (e.g., `export MY_VARIABLE="test"`). -The runner uses Go's `os.Expand()` method for variable expansion. It means that it will handle +The runner uses Go's `os.Expand()` method for variable expansion. It means that it handles only variables defined as `$variable` and `${variable}`. What's also important, is that the expansion is done only once, so nested variables may or may not work, depending on the ordering of variables definitions. @@ -77,7 +77,7 @@ This is an expansion that takes place during the `script` execution. How it works depends on the used shell (`bash`, `sh`, `cmd`, PowerShell). For example, if the job's `script` contains a line `echo $MY_VARIABLE-${MY_VARIABLE_2}`, it should be properly handled by bash/sh (leaving empty strings or some values depending whether the variables were -defined or not), but will not work with Windows' `cmd` or PowerShell, since these shells +defined or not), but don't work with Windows' `cmd` or PowerShell, since these shells are using a different variables syntax. Supported: @@ -87,10 +87,10 @@ Supported: `.gitlab-ci.yml` variables, `config.toml` variables, and variables from triggers and pipeline schedules). - The `script` may also use all variables defined in the lines before. So, for example, if you define a variable `export MY_VARIABLE="test"`: - - In `before_script`, it will work in the following lines of `before_script` and + - In `before_script`, it works in the following lines of `before_script` and all lines of the related `script`. - - In `script`, it will work in the following lines of `script`. - - In `after_script`, it will work in following lines of `after_script`. + - In `script`, it works in the following lines of `script`. + - In `after_script`, it works in following lines of `after_script`. In the case of `after_script` scripts, they can: @@ -133,7 +133,7 @@ due to security reasons. Variables defined with an environment scope are supported. Given that there is a variable `$STAGING_SECRET` defined in a scope of `review/staging/*`, the following job that is using dynamic environments -is going to be created, based on the matching variable expression: +is created, based on the matching variable expression: ```yaml my-job: diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 1057a1389de..fa67916cc8f 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -13,15 +13,14 @@ This document lists the configuration options for your GitLab `.gitlab-ci.yml` f - For a collection of examples, see [GitLab CI/CD Examples](../examples/README.md). - To view a large `.gitlab-ci.yml` file used in an enterprise, see the [`.gitlab-ci.yml` file for `gitlab`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab-ci.yml). -While you are authoring your `.gitlab-ci.yml` file, you can validate it -by using the [CI Lint](../lint.md) tool. -project namespace. For example, `https://gitlab.example.com/gitlab-org/project-123/-/ci/lint`. +When you are editing your `.gitlab-ci.yml` file, you can validate it with the +[CI Lint](../lint.md) tool. ## Job keywords A job is defined as a list of keywords that define the job's behavior. -The following table lists available keywords for jobs: +The keywords available for jobs are: | Keyword | Description | |:---------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -69,20 +68,20 @@ can't be used as job names**: - `cache` - `include` -## Global keywords - -Some keywords must be defined at a global level, affecting all jobs in the pipeline. +### Reserved keywords -### Using reserved keywords - -If you get validation error when using specific values (for example, `true` or `false`), try to: +If you get a validation error when using specific values (for example, `true` or `false`), try to: - Quote them. - Change them to a different form. For example, `/bin/true`. +## Global keywords + +Some keywords are defined at a global level and affect all jobs in the pipeline. + ### Global defaults -Some keywords can be set globally as the default for all jobs using the +Some keywords can be set globally as the default for all jobs with the `default:` keyword. Default keywords can then be overridden by job-specific configuration. @@ -121,7 +120,7 @@ rspec 2.6: You can disable inheritance of globally defined defaults and variables with the `inherit:` keyword. -To enable or disable the inheritance of all `variables:` or `default:` keywords, use the following format: +To enable or disable the inheritance of all `default:` or `variables:` keywords, use: - `default: true` or `default: false` - `variables: true` or `variables: false` @@ -201,14 +200,13 @@ karma: `stages` is used to define stages that contain jobs and is defined globally for the pipeline. -The specification of `stages` allows for having flexible multi stage pipelines. -The ordering of elements in `stages` defines the ordering of jobs' execution: +The order of the `stages` items defines the execution order for jobs: -1. Jobs of the same stage are run in parallel. -1. Jobs of the next stage are run after the jobs from the previous stage +1. Jobs in the same stage are run in parallel. +1. Jobs in the next stage are run after the jobs from the previous stage complete successfully. -Let's consider the following example, which defines 3 stages: +For example: ```yaml stages: @@ -341,17 +339,17 @@ include: > - Available for Starter, Premium, and Ultimate in GitLab 10.6 and later. > - [Moved](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/42861) to GitLab Core in 11.4. -Using the `include` keyword allows the inclusion of external YAML files. This helps -to break down the CI/CD configuration into multiple files and increases readability for long configuration files. +Use the `include` keyword to include external YAML files in your CI/CD configuration. This +breaks down the CI/CD configuration into multiple files and increases readability for long configuration files. It's also possible to have template files stored in a central repository and projects include their configuration files. This helps avoid duplicated configuration, for example, global default variables for all projects. `include` requires the external YAML file to have the extensions `.yml` or `.yaml`, otherwise the external file is not included. -Using [YAML anchors](#anchors) across different YAML files sourced by `include` is not -supported. You must only refer to anchors in the same file. Instead -of using YAML anchors, you can use the [`extends` keyword](#extends). +You can't use [YAML anchors](#anchors) across different YAML files sourced by `include`. +You can only refer to anchors in the same file. Instead of YAML anchors, you can +use the [`extends` keyword](#extends). `include` supports the following inclusion methods: @@ -381,13 +379,13 @@ definitions. Local definitions in `.gitlab-ci.yml` override included definitions #### `include:local` `include:local` includes a file from the same repository as `.gitlab-ci.yml`. -It's referenced using full paths relative to the root directory (`/`). +It's referenced with full paths relative to the root directory (`/`). You can only use files that are tracked by Git on the same branch -your configuration file is on. In other words, when using a `include:local`, make +your configuration file is on. If you `include:local`, make sure that both `.gitlab-ci.yml` and the local file are on the same branch. -Including local files through Git submodules paths is not supported. +You can't include local files through Git submodules paths. All [nested includes](#nested-includes) are executed in the scope of the same project, so it's possible to use local, project, remote, or template includes. @@ -412,7 +410,7 @@ include: '.gitlab-ci-production.yml' > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53903) in GitLab 11.7. To include files from another private project under the same GitLab instance, -use `include:file`. This file is referenced using full paths relative to the +use `include:file`. This file is referenced with full paths relative to the root directory (`/`). For example: ```yaml diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md index d7945617dc9..ab5d61cdfc8 100644 --- a/doc/ci/yaml/includes.md +++ b/doc/ci/yaml/includes.md @@ -67,7 +67,7 @@ include: ## Re-using a `before_script` template -In the following example, the content of `.before-script-template.yml` will be +In the following example, the content of `.before-script-template.yml` is automatically fetched and evaluated along with the content of `.gitlab-ci.yml`. Content of `https://gitlab.com/awesome-project/raw/master/.before-script-template.yml`: diff --git a/doc/development/cicd/index.md b/doc/development/cicd/index.md index 30ccc52ec5e..d1f22a559c6 100644 --- a/doc/development/cicd/index.md +++ b/doc/development/cicd/index.md @@ -32,8 +32,8 @@ On the left side we have the events that can trigger a pipeline based on various - When GitHub integration is used with [external pull requests](../../ci/ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). - When an upstream pipeline contains a [bridge job](../../ci/yaml/README.md#trigger) which triggers a downstream pipeline. -Triggering any of these events will invoke the [`CreatePipelineService`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/ci/create_pipeline_service.rb) -which takes as input event data and the user triggering it, then will attempt to create a pipeline. +Triggering any of these events invokes the [`CreatePipelineService`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/ci/create_pipeline_service.rb) +which takes as input event data and the user triggering it, then attempts to create a pipeline. The `CreatePipelineService` relies heavily on the [`YAML Processor`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/yaml_processor.rb) component, which is responsible for taking in a YAML blob as input and returns the abstract data structure of a @@ -65,20 +65,20 @@ the `Runner API Gateway`. We can register, delete, and verify runners, which also causes read/write queries to the database. After a runner is connected, it keeps asking for the next job to execute. This invokes the [`RegisterJobService`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/services/ci/register_job_service.rb) -which will pick the next job and assign it to the runner. At this point the job will transition to a +which picks the next job and assigns it to the runner. At this point the job transitions to a `running` state, which again triggers `ProcessPipelineService` due to the status change. For more details read [Job scheduling](#job-scheduling)). While a job is being executed, the runner sends logs back to the server as well any possible artifacts that need to be stored. Also, a job may depend on artifacts from previous jobs in order to run. In this -case the runner will download them using a dedicated API endpoint. +case the runner downloads them using a dedicated API endpoint. Artifacts are stored in object storage, while metadata is kept in the database. An important example of artifacts are reports (JUnit, SAST, DAST, etc.) which are parsed and rendered in the merge request. Job status transitions are not all automated. A user may run [manual jobs](../../ci/yaml/README.md#whenmanual), cancel a pipeline, retry specific failed jobs or the entire pipeline. Anything that -causes a job to change status will trigger `ProcessPipelineService`, as it's responsible for +causes a job to change status triggers `ProcessPipelineService`, as it's responsible for tracking the status of the entire pipeline. A special type of job is the [bridge job](../../ci/yaml/README.md#trigger) which is executed server-side @@ -90,7 +90,7 @@ from the `CreatePipelineService` every time a downstream pipeline is triggered. When a Pipeline is created all its jobs are created at once for all stages, with an initial state of `created`. This makes it possible to visualize the full content of a pipeline. -A job with the `created` state won't be seen by the runner yet. To make it possible to assign a job to a runner, the job must transition first into the `pending` state, which can happen if: +A job with the `created` state isn't seen by the runner yet. To make it possible to assign a job to a runner, the job must transition first into the `pending` state, which can happen if: 1. The job is created in the very first stage of the pipeline. 1. The job required a manual start and it has been triggered. @@ -135,7 +135,7 @@ There are 3 top level queries that this service uses to gather the majority of t This list of jobs is then filtered further by matching tags between job and runner tags. NOTE: **Note:** -If a job contains tags, the runner will not pick the job if it does not match **all** the tags. +If a job contains tags, the runner doesn't pick the job if it does not match **all** the tags. The runner may have more tags than defined for the job, but not vice-versa. Finally if the runner can only pick jobs that are tagged, all untagged jobs are filtered out. diff --git a/doc/development/geo/framework.md b/doc/development/geo/framework.md index e440e324c4a..2e3f61278e0 100644 --- a/doc/development/geo/framework.md +++ b/doc/development/geo/framework.md @@ -393,6 +393,8 @@ can track verification state. def change change_table(:widgets) do |t| + t.integer :verification_state, default: 0, limit: 2, null: false + t.column :verification_started_at, :datetime_with_timezone t.integer :verification_retry_count, limit: 2 t.column :verification_retry_at, :datetime_with_timezone t.column :verified_at, :datetime_with_timezone @@ -431,13 +433,12 @@ can track verification state. end ``` -1. Add a partial index on `verification_failure` and `verification_checksum` to ensure - re-verification can be performed efficiently: +1. Add an index on `verification_state` to ensure verification can be performed efficiently: ```ruby # frozen_string_literal: true - class AddVerificationFailureIndexToWidgets < ActiveRecord::Migration[6.0] + class AddVerificationStateIndexToWidgets < ActiveRecord::Migration[6.0] include Gitlab::Database::MigrationHelpers DOWNTIME = false @@ -445,22 +446,28 @@ can track verification state. disable_ddl_transaction! def up - add_concurrent_index :widgets, :verification_failure, where: "(verification_failure IS NOT NULL)", name: "widgets_verification_failure_partial" - add_concurrent_index :widgets, :verification_checksum, where: "(verification_checksum IS NOT NULL)", name: "widgets_verification_checksum_partial" + add_concurrent_index :widgets, :verification_state, name: "index_widgets_on_verification_state" end def down - remove_concurrent_index :widgets, :verification_failure - remove_concurrent_index :widgets, :verification_checksum + remove_concurrent_index :widgets, :verification_state end end ``` +1. Add the `Gitlab::Geo::VerificationState` concern to the `widget` model if it is not already included in `Gitlab::Geo::ReplicableModel`: + + ```ruby + class Widget < ApplicationRecord + ... + include ::Gitlab::Geo::VerificationState + ... + end + ``` + ##### Option 2: Create a separate `widget_states` table with verification state fields -1. Create a `widget_states` table and add a partial index on `verification_failure` and - `verification_checksum` to ensure re-verification can be performed efficiently. Order - the columns according to [the guidelines](../ordering_table_columns.md): +1. Create a `widget_states` table and add an index on `verification_state` to ensure verification can be performed efficiently. Order the columns according to [the guidelines](../ordering_table_columns.md): ```ruby # frozen_string_literal: true @@ -477,14 +484,15 @@ can track verification state. with_lock_retries do create_table :widget_states, id: false do |t| t.references :widget, primary_key: true, null: false, foreign_key: { on_delete: :cascade } + t.integer :verification_state, default: 0, limit: 2, null: false + t.column :verification_started_at, :datetime_with_timezone t.datetime_with_timezone :verification_retry_at t.datetime_with_timezone :verified_at t.integer :verification_retry_count, limit: 2 t.binary :verification_checksum, using: 'verification_checksum::bytea' t.text :verification_failure - t.index :verification_failure, where: "(verification_failure IS NOT NULL)", name: "widgets_verification_failure_partial" - t.index :verification_checksum, where: "(verification_checksum IS NOT NULL)", name: "widgets_verification_checksum_partial" + t.index :verification_state, name: "index_widget_states_on_verification_state" end end end @@ -498,6 +506,20 @@ can track verification state. end ``` +1. Add the following lines to the `widget_state.rb` model: + + ```ruby + class WidgetState < ApplicationRecord + ... + self.primary_key = :widget_id + + include ::Gitlab::Geo::VerificationState + + belongs_to :widget, inverse_of: :widget_state + ... + end + ``` + 1. Add the following lines to the `widget` model: ```ruby @@ -547,14 +569,16 @@ Metrics are gathered by `Geo::MetricsUpdateWorker`, persisted in 1. Add the following to `spec/factories/widgets.rb`: ```ruby - trait(:checksummed) do + trait(:verification_succeeded) do with_file verification_checksum { 'abc' } + verification_state { Widget.verification_state_value(:verification_succeeded) } end - trait(:checksum_failure) do + trait(:verification_failed) do with_file verification_failure { 'Could not calculate the checksum' } + verification_state { Widget.verification_state_value(:verification_failed) } end ``` diff --git a/doc/topics/autodevops/upgrading_auto_deploy_dependencies.md b/doc/topics/autodevops/upgrading_auto_deploy_dependencies.md index 16536e5b586..2baa409be54 100644 --- a/doc/topics/autodevops/upgrading_auto_deploy_dependencies.md +++ b/doc/topics/autodevops/upgrading_auto_deploy_dependencies.md @@ -73,7 +73,9 @@ please proceed with the following upgrade guide. Otherwise, you can skip this pr #### Kubernetes 1.16+ -The v2 auto-deploy-image also drops support for Kubernetes 1.15 and lower. +The v2 auto-deploy-image drops support for Kubernetes 1.15 and lower. If you need to upgrade your +Kubernetes cluster, follow your cloud provider's instructions. Here's +[an example on GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/upgrading-a-cluster). #### Helm 3 @@ -114,6 +116,12 @@ If your Auto DevOps project has an active environment that was deployed with the `kubectl apply -f $backup`. 1. Remove the `MIGRATE_HELM_2TO3` variable. +#### In-Cluster PostgreSQL Channel 2 + +The v2 auto-deploy-image drops support for [legacy in-cluster PostgreSQL](upgrading_postgresql.md). +If your Kubernetes cluster still depends on it, [upgrade and migrate your data](upgrading_postgresql.md) +with the [v1 auto-deploy-image](#use-a-specific-version-of-auto-deploy-dependencies). + #### Traffic routing change for canary deployments and incremental rollouts > [Introduced](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image/-/merge_requests/109) in GitLab 13.4. diff --git a/doc/user/application_security/dependency_list/index.md b/doc/user/application_security/dependency_list/index.md index ddd059707d4..8c5bf638244 100644 --- a/doc/user/application_security/dependency_list/index.md +++ b/doc/user/application_security/dependency_list/index.md @@ -55,6 +55,7 @@ dependencies, but the UI only shows one of the shortest paths. Dependency Paths are supported for the following package managers: - [NuGet](https://www.nuget.org/) +- [Yarn 1.x](https://classic.yarnpkg.com/lang/en/) ## Licenses diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index a7a72ca4d82..401039fa9b5 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -34,9 +34,9 @@ The reasons to do it like that are: With the new behavior, any job that is triggered by the user, is also marked with their read permissions. When a user does a `git push` or changes files through -the web UI, a new pipeline will be usually created. This pipeline will be marked +the web UI, a new pipeline is usually created. This pipeline is marked as created by the pusher (local push or via the UI) and any job created in this -pipeline will have the read permissions of the pusher but not write permissions. +pipeline has the read permissions of the pusher but not write permissions. This allows us to make it really easy to evaluate the access for all projects that have [Git submodules](../../ci/git_submodules.md) or are using container images that the pusher @@ -47,14 +47,14 @@ is running. The access is revoked after the job is finished.** It is important to note that we have a few types of users: -- **Administrators**: CI jobs created by Administrators will not have access +- **Administrators**: CI jobs created by Administrators don't have access to all GitLab projects, but only to projects and container images of projects that the administrator is a member of. That means that if a project is either public or internal users have access anyway, but if a project is private, the - Administrator will have to be a member of it in order to have access to it + Administrator has to be a member of it in order to have access to it via another project's job. -- **External users**: CI jobs created by [external users](../permissions.md#external-users) will have +- **External users**: CI jobs created by [external users](../permissions.md#external-users) have access only to projects to which the user has at least Reporter access. This rules out accessing all internal projects by default. @@ -149,8 +149,8 @@ the container registry. ### Prerequisites to use the new permissions model -With the new permissions model in place, there may be times that your job will -fail. This is most likely because your project tries to access other project's +With the new permissions model in place, there may be times that your job +fails. This is most likely because your project tries to access other project's sources, and you don't have the appropriate permissions. In the job log look for information about 403 or forbidden access messages. @@ -158,7 +158,7 @@ In short here's what you need to do should you encounter any issues. As an administrator: -- **500 errors**: You will need to update [GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) to at +- **500 errors**: You need to update [GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) to at least 0.8.2. This is done automatically for Omnibus installations, you need to [check manually](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/doc/update) for installations from source. - **500 errors**: Check if you have another web proxy sitting in front of NGINX (HAProxy, @@ -185,7 +185,7 @@ git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/<user>/<mydependent ``` It can also be used for system-wide authentication -(only do this in a Docker container, it will overwrite ~/.netrc): +(only do this in a Docker container, it overwrites `~/.netrc`): ```shell echo -e "machine gitlab.com\nlogin gitlab-ci-token\npassword ${CI_JOB_TOKEN}" > ~/.netrc diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index 3e493f02392..417ad75a5b9 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -51,6 +51,9 @@ Note the following: then new branches associated with such merge requests will be created within a project during the import/export. Thus, the number of branches in the exported project could be bigger than in the original project. +- Deploy keys allowed to push to protected branches are not exported. Therefore, + you will need to recreate this association by first enabling these deploy keys in your + imported project and then updating your protected branches accordingly. ## Version history @@ -114,6 +117,7 @@ The following items will be exported: - LFS objects - Issue boards - Pipelines history +- Push Rules The following items will NOT be exported: @@ -123,7 +127,6 @@ The following items will NOT be exported: - Webhooks - Any encrypted tokens - Merge Request Approvers -- Push Rules - Awards NOTE: **Note:** diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml index ae7ddbc5eba..8c094603d53 100644 --- a/lib/gitlab/import_export/project/import_export.yml +++ b/lib/gitlab/import_export/project/import_export.yml @@ -169,6 +169,7 @@ excluded_attributes: - :compliance_framework_setting - :show_default_award_emojis - :services + - :exported_protected_branches namespaces: - :runners_token - :runners_token_encrypted diff --git a/lib/gitlab/rack_attack.rb b/lib/gitlab/rack_attack.rb new file mode 100644 index 00000000000..222f5d57adf --- /dev/null +++ b/lib/gitlab/rack_attack.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +# Integration specs for throttling can be found in: +# spec/requests/rack_attack_global_spec.rb +module Gitlab + module RackAttack + def self.configure(rack_attack) + # This adds some methods used by our throttles to the `Rack::Request` + rack_attack::Request.include(Gitlab::RackAttack::Request) + # Configure the throttles + configure_throttles(rack_attack) + end + + def self.configure_throttles(rack_attack) + throttle_or_track(rack_attack, 'throttle_unauthenticated', Gitlab::Throttle.unauthenticated_options) do |req| + if !req.should_be_skipped? && + Gitlab::Throttle.settings.throttle_unauthenticated_enabled && + req.unauthenticated? + req.ip + end + end + + throttle_or_track(rack_attack, 'throttle_authenticated_api', Gitlab::Throttle.authenticated_api_options) do |req| + if req.api_request? && + Gitlab::Throttle.settings.throttle_authenticated_api_enabled + req.authenticated_user_id([:api]) + end + end + + # Product analytics feature is in experimental stage. + # At this point we want to limit amount of events registered + # per application (aid stands for application id). + throttle_or_track(rack_attack, 'throttle_product_analytics_collector', limit: 100, period: 60) do |req| + if req.product_analytics_collector_request? + req.params['aid'] + end + end + + throttle_or_track(rack_attack, 'throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req| + if req.web_request? && + Gitlab::Throttle.settings.throttle_authenticated_web_enabled + req.authenticated_user_id([:api, :rss, :ics]) + end + end + + throttle_or_track(rack_attack, 'throttle_unauthenticated_protected_paths', Gitlab::Throttle.protected_paths_options) do |req| + if req.post? && + !req.should_be_skipped? && + req.protected_path? && + Gitlab::Throttle.protected_paths_enabled? && + req.unauthenticated? + req.ip + end + end + + throttle_or_track(rack_attack, 'throttle_authenticated_protected_paths_api', Gitlab::Throttle.protected_paths_options) do |req| + if req.post? && + req.api_request? && + req.protected_path? && + Gitlab::Throttle.protected_paths_enabled? + req.authenticated_user_id([:api]) + end + end + + throttle_or_track(rack_attack, 'throttle_authenticated_protected_paths_web', Gitlab::Throttle.protected_paths_options) do |req| + if req.post? && + req.web_request? && + req.protected_path? && + Gitlab::Throttle.protected_paths_enabled? + req.authenticated_user_id([:api, :rss, :ics]) + end + end + + rack_attack.safelist('throttle_bypass_header') do |req| + Gitlab::Throttle.bypass_header.present? && + req.get_header(Gitlab::Throttle.bypass_header) == '1' + end + end + + def self.throttle_or_track(rack_attack, throttle_name, *args, &block) + if track?(throttle_name) + rack_attack.track(throttle_name, *args, &block) + else + rack_attack.throttle(throttle_name, *args, &block) + end + end + + def self.track?(name) + dry_run_config = ENV['GITLAB_THROTTLE_DRY_RUN'].to_s.strip + + return false if dry_run_config.empty? + return true if dry_run_config == '*' + + dry_run_config.split(',').map(&:strip).include?(name) + end + end +end +::Gitlab::RackAttack.prepend_if_ee('::EE::Gitlab::RackAttack') diff --git a/lib/gitlab/rack_attack/request.rb b/lib/gitlab/rack_attack/request.rb new file mode 100644 index 00000000000..a75f66af263 --- /dev/null +++ b/lib/gitlab/rack_attack/request.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module Gitlab + module RackAttack + module Request + def unauthenticated? + !(authenticated_user_id([:api, :rss, :ics]) || authenticated_runner_id) + end + + def authenticated_user_id(request_formats) + request_authenticator.user(request_formats)&.id + end + + def authenticated_runner_id + request_authenticator.runner&.id + end + + def api_request? + path.start_with?('/api') + end + + def api_internal_request? + path =~ %r{^/api/v\d+/internal/} + end + + def health_check_request? + path =~ %r{^/-/(health|liveness|readiness|metrics)} + end + + def product_analytics_collector_request? + path.start_with?('/-/collector/i') + end + + def should_be_skipped? + api_internal_request? || health_check_request? + end + + def web_request? + !api_request? && !health_check_request? + end + + def protected_path? + !protected_path_regex.nil? + end + + def protected_path_regex + path =~ protected_paths_regex + end + + private + + def request_authenticator + @request_authenticator ||= Gitlab::Auth::RequestAuthenticator.new(self) + end + + def protected_paths + Gitlab::CurrentSettings.current_application_settings.protected_paths + end + + def protected_paths_regex + Regexp.union(protected_paths.map { |path| /\A#{Regexp.escape(path)}/ }) + end + end + end +end +::Gitlab::RackAttack::Request.prepend_if_ee('::EE::Gitlab::RackAttack::Request') diff --git a/lib/gitlab/throttle.rb b/lib/gitlab/throttle.rb new file mode 100644 index 00000000000..aebf8d92cb3 --- /dev/null +++ b/lib/gitlab/throttle.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Gitlab + class Throttle + def self.settings + Gitlab::CurrentSettings.current_application_settings + end + + # Returns true if we should use the Admin Area protected paths throttle + def self.protected_paths_enabled? + self.settings.throttle_protected_paths_enabled? + end + + def self.omnibus_protected_paths_present? + Rack::Attack.throttles.key?('protected paths') + end + + def self.bypass_header + env_value = ENV['GITLAB_THROTTLE_BYPASS_HEADER'] + return unless env_value.present? + + "HTTP_#{env_value.upcase.tr('-', '_')}" + end + + def self.unauthenticated_options + limit_proc = proc { |req| settings.throttle_unauthenticated_requests_per_period } + period_proc = proc { |req| settings.throttle_unauthenticated_period_in_seconds.seconds } + { limit: limit_proc, period: period_proc } + end + + def self.authenticated_api_options + limit_proc = proc { |req| settings.throttle_authenticated_api_requests_per_period } + period_proc = proc { |req| settings.throttle_authenticated_api_period_in_seconds.seconds } + { limit: limit_proc, period: period_proc } + end + + def self.authenticated_web_options + limit_proc = proc { |req| settings.throttle_authenticated_web_requests_per_period } + period_proc = proc { |req| settings.throttle_authenticated_web_period_in_seconds.seconds } + { limit: limit_proc, period: period_proc } + end + + def self.protected_paths_options + limit_proc = proc { |req| settings.throttle_protected_paths_requests_per_period } + period_proc = proc { |req| settings.throttle_protected_paths_period_in_seconds.seconds } + + { limit: limit_proc, period: period_proc } + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9b400d6832e..84da7259be9 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -11300,6 +11300,9 @@ msgstr "" msgid "Failed to create a branch for this issue. Please try again." msgstr "" +msgid "Failed to create framework" +msgstr "" + msgid "Failed to create import label for jira import." msgstr "" @@ -11525,6 +11528,9 @@ msgstr "" msgid "Feature flag was successfully removed." msgstr "" +msgid "Feature not available" +msgstr "" + msgid "FeatureFlags|%d user" msgid_plural "FeatureFlags|%d users" msgstr[0] "" diff --git a/spec/factories/exported_protected_branches.rb b/spec/factories/exported_protected_branches.rb new file mode 100644 index 00000000000..7ad49b12e5b --- /dev/null +++ b/spec/factories/exported_protected_branches.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :exported_protected_branch, class: 'ExportedProtectedBranch', parent: :protected_branch +end diff --git a/spec/factories/packages/package_file.rb b/spec/factories/packages/package_file.rb index 643ab8e4f95..bee1b2076df 100644 --- a/spec/factories/packages/package_file.rb +++ b/spec/factories/packages/package_file.rb @@ -152,14 +152,6 @@ FactoryBot.define do file_store { Packages::PackageFileUploader::Store::REMOTE } end - trait(:checksummed) do - verification_checksum { 'abc' } - end - - trait(:checksum_failure) do - verification_failure { 'Could not calculate the checksum' } - end - factory :package_file_with_file, traits: [:jar] end end diff --git a/spec/features/projects/show/schema_markup_spec.rb b/spec/features/projects/show/schema_markup_spec.rb index e651798af23..1777b72cbf5 100644 --- a/spec/features/projects/show/schema_markup_spec.rb +++ b/spec/features/projects/show/schema_markup_spec.rb @@ -14,7 +14,7 @@ RSpec.describe 'Projects > Show > Schema Markup' do expect(page).to have_selector('img[itemprop="image"]') expect(page).to have_selector('[itemprop="name"]', text: project.name) expect(page).to have_selector('[itemprop="identifier"]', text: "Project ID: #{project.id}") - expect(page).to have_selector('[itemprop="abstract"]', text: project.description) + expect(page).to have_selector('[itemprop="description"]', text: project.description) expect(page).to have_selector('[itemprop="license"]', text: project.repository.license.name) expect(find_all('[itemprop="keywords"]').map(&:text)).to match_array(project.tag_list.map(&:capitalize)) expect(page).to have_selector('[itemprop="about"]') diff --git a/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap b/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap index 6640c0844e2..e295c587d70 100644 --- a/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap +++ b/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap @@ -4,13 +4,8 @@ exports[`SidebarTodo template renders component container element with proper da <button aria-label="Mark as done" class="btn btn-default btn-todo issuable-header-btn float-right" - data-boundary="viewport" - data-container="body" data-issuable-id="1" data-issuable-type="epic" - data-original-title="" - data-placement="left" - title="" type="button" > <gl-icon-stub diff --git a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap index 04ae2a0f34d..20ea897e29c 100644 --- a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap +++ b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap @@ -5,12 +5,17 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` class="awards js-awards-block" > <button - class="btn award-control" + class="btn gl-mr-3 btn-default btn-md gl-button" data-testid="award-button" title="Ada, Leonardo, and Marie" type="button" > + <!----> + + <!----> + <span + class="award-emoji-block" data-testid="award-html" > @@ -23,18 +28,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` </span> <span - class="award-control-text js-counter" + class="gl-button-text" > - 3 + + <span + class="js-counter" + > + 3 + </span> </span> </button> <button - class="btn award-control active" + class="btn gl-mr-3 btn-default btn-md gl-button selected" data-testid="award-button" title="You, Ada, and Marie" type="button" > + <!----> + + <!----> + <span + class="award-emoji-block" data-testid="award-html" > @@ -47,18 +62,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` </span> <span - class="award-control-text js-counter" + class="gl-button-text" > - 3 + + <span + class="js-counter" + > + 3 + </span> </span> </button> <button - class="btn award-control" + class="btn gl-mr-3 btn-default btn-md gl-button" data-testid="award-button" title="Ada and Jane" type="button" > + <!----> + + <!----> + <span + class="award-emoji-block" data-testid="award-html" > @@ -71,18 +96,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` </span> <span - class="award-control-text js-counter" + class="gl-button-text" > - 2 + + <span + class="js-counter" + > + 2 + </span> </span> </button> <button - class="btn award-control active" + class="btn gl-mr-3 btn-default btn-md gl-button selected" data-testid="award-button" title="You, Ada, Jane, and Leonardo" type="button" > + <!----> + + <!----> + <span + class="award-emoji-block" data-testid="award-html" > @@ -95,18 +130,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` </span> <span - class="award-control-text js-counter" + class="gl-button-text" > - 4 + + <span + class="js-counter" + > + 4 + </span> </span> </button> <button - class="btn award-control active" + class="btn gl-mr-3 btn-default btn-md gl-button selected" data-testid="award-button" title="You" type="button" > + <!----> + + <!----> + <span + class="award-emoji-block" data-testid="award-html" > @@ -119,18 +164,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` </span> <span - class="award-control-text js-counter" + class="gl-button-text" > - 1 + + <span + class="js-counter" + > + 1 + </span> </span> </button> <button - class="btn award-control" + class="btn gl-mr-3 btn-default btn-md gl-button" data-testid="award-button" title="Marie" type="button" > + <!----> + + <!----> + <span + class="award-emoji-block" data-testid="award-html" > @@ -143,18 +198,28 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` </span> <span - class="award-control-text js-counter" + class="gl-button-text" > - 1 + + <span + class="js-counter" + > + 1 + </span> </span> </button> <button - class="btn award-control active" + class="btn gl-mr-3 btn-default btn-md gl-button selected" data-testid="award-button" title="You" type="button" > + <!----> + + <!----> + <span + class="award-emoji-block" data-testid="award-html" > @@ -167,9 +232,14 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` </span> <span - class="award-control-text js-counter" + class="gl-button-text" > - 1 + + <span + class="js-counter" + > + 1 + </span> </span> </button> @@ -178,46 +248,59 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` > <button aria-label="Add reaction" - class="award-control btn js-add-award js-test-add-button-class" + class="btn add-reaction-button js-add-award btn-default btn-md gl-button js-test-add-button-class" title="Add reaction" type="button" > - <span - class="award-control-icon award-control-icon-neutral" - > - <gl-icon-stub - aria-hidden="true" - name="slight-smile" - size="16" - /> - </span> + <!----> + <!----> + <span - class="award-control-icon award-control-icon-positive" + class="gl-button-text" > - <gl-icon-stub - aria-hidden="true" - name="smiley" - size="16" - /> + <span + class="reaction-control-icon reaction-control-icon-neutral" + > + <svg + aria-hidden="true" + class="gl-icon s16" + data-testid="slight-smile-icon" + > + <use + href="#slight-smile" + /> + </svg> + </span> + + <span + class="reaction-control-icon reaction-control-icon-positive" + > + <svg + aria-hidden="true" + class="gl-icon s16" + data-testid="smiley-icon" + > + <use + href="#smiley" + /> + </svg> + </span> + + <span + class="reaction-control-icon reaction-control-icon-super-positive" + > + <svg + aria-hidden="true" + class="gl-icon s16" + data-testid="smile-icon" + > + <use + href="#smile" + /> + </svg> + </span> </span> - - <span - class="award-control-icon award-control-icon-super-positive" - > - <gl-icon-stub - aria-hidden="true" - name="smiley" - size="16" - /> - </span> - - <gl-loading-icon-stub - class="award-control-icon-loading" - color="dark" - label="Loading" - size="md" - /> </button> </div> </div> diff --git a/spec/frontend/vue_shared/components/awards_list_spec.js b/spec/frontend/vue_shared/components/awards_list_spec.js index 63fc8a5749d..d20de81c446 100644 --- a/spec/frontend/vue_shared/components/awards_list_spec.js +++ b/spec/frontend/vue_shared/components/awards_list_spec.js @@ -1,4 +1,4 @@ -import { shallowMount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import AwardsList from '~/vue_shared/components/awards_list.vue'; const createUser = (id, name) => ({ id, name }); @@ -41,6 +41,8 @@ const TEST_AWARDS = [ ]; const TEST_ADD_BUTTON_CLASS = 'js-test-add-button-class'; +const REACTION_CONTROL_CLASSES = ['btn', 'gl-mr-3', 'btn-default', 'btn-md', 'gl-button']; + describe('vue_shared/components/awards_list', () => { let wrapper; @@ -54,16 +56,16 @@ describe('vue_shared/components/awards_list', () => { throw new Error('There should only be one wrapper created per test'); } - wrapper = shallowMount(AwardsList, { propsData: props }); + wrapper = mount(AwardsList, { propsData: props }); }; const matchingEmojiTag = name => expect.stringMatching(`gl-emoji data-name="${name}"`); - const findAwardButtons = () => wrapper.findAll('[data-testid="award-button"'); + const findAwardButtons = () => wrapper.findAll('[data-testid="award-button"]'); const findAwardsData = () => findAwardButtons().wrappers.map(x => { return { classes: x.classes(), title: x.attributes('title'), - html: x.find('[data-testid="award-html"]').element.innerHTML, + html: x.find('[data-testid="award-html"]').html(), count: Number(x.find('.js-counter').text()), }; }); @@ -86,43 +88,43 @@ describe('vue_shared/components/awards_list', () => { it('shows awards in correct order', () => { expect(findAwardsData()).toEqual([ { - classes: ['btn', 'award-control'], + classes: REACTION_CONTROL_CLASSES, count: 3, html: matchingEmojiTag(EMOJI_THUMBSUP), title: 'Ada, Leonardo, and Marie', }, { - classes: ['btn', 'award-control', 'active'], + classes: [...REACTION_CONTROL_CLASSES, 'selected'], count: 3, html: matchingEmojiTag(EMOJI_THUMBSDOWN), title: 'You, Ada, and Marie', }, { - classes: ['btn', 'award-control'], + classes: REACTION_CONTROL_CLASSES, count: 2, html: matchingEmojiTag(EMOJI_SMILE), title: 'Ada and Jane', }, { - classes: ['btn', 'award-control', 'active'], + classes: [...REACTION_CONTROL_CLASSES, 'selected'], count: 4, html: matchingEmojiTag(EMOJI_OK), title: 'You, Ada, Jane, and Leonardo', }, { - classes: ['btn', 'award-control', 'active'], + classes: [...REACTION_CONTROL_CLASSES, 'selected'], count: 1, html: matchingEmojiTag(EMOJI_CACTUS), title: 'You', }, { - classes: ['btn', 'award-control'], + classes: REACTION_CONTROL_CLASSES, count: 1, html: matchingEmojiTag(EMOJI_A), title: 'Marie', }, { - classes: ['btn', 'award-control', 'active'], + classes: [...REACTION_CONTROL_CLASSES, 'selected'], count: 1, html: matchingEmojiTag(EMOJI_B), title: 'You', @@ -135,7 +137,7 @@ describe('vue_shared/components/awards_list', () => { findAwardButtons() .at(2) - .trigger('click'); + .vm.$emit('click'); expect(wrapper.emitted().award).toEqual([[EMOJI_SMILE]]); }); @@ -162,7 +164,7 @@ describe('vue_shared/components/awards_list', () => { findAwardButtons() .at(0) - .trigger('click'); + .vm.$emit('click'); expect(wrapper.emitted().award).toEqual([[Number(EMOJI_100)]]); }); @@ -225,26 +227,26 @@ describe('vue_shared/components/awards_list', () => { it('shows awards in correct order', () => { expect(findAwardsData()).toEqual([ { - classes: ['btn', 'award-control'], + classes: REACTION_CONTROL_CLASSES, count: 0, html: matchingEmojiTag(EMOJI_THUMBSUP), title: '', }, { - classes: ['btn', 'award-control'], + classes: REACTION_CONTROL_CLASSES, count: 0, html: matchingEmojiTag(EMOJI_THUMBSDOWN), title: '', }, // We expect the EMOJI_100 before the EMOJI_SMILE because it was given as a defaultAward { - classes: ['btn', 'award-control'], + classes: REACTION_CONTROL_CLASSES, count: 1, html: matchingEmojiTag(EMOJI_100), title: 'Marie', }, { - classes: ['btn', 'award-control'], + classes: REACTION_CONTROL_CLASSES, count: 1, html: matchingEmojiTag(EMOJI_SMILE), title: 'Marie', diff --git a/spec/frontend/vue_shared/security_reports/store/getters_spec.js b/spec/frontend/vue_shared/security_reports/store/getters_spec.js new file mode 100644 index 00000000000..8de704be455 --- /dev/null +++ b/spec/frontend/vue_shared/security_reports/store/getters_spec.js @@ -0,0 +1,182 @@ +import createState from '~/vue_shared/security_reports/store/state'; +import createSastState from '~/vue_shared/security_reports/store/modules/sast/state'; +import createSecretScanningState from '~/vue_shared/security_reports/store/modules/secret_detection/state'; +import { groupedTextBuilder } from '~/vue_shared/security_reports/store/utils'; +import { + groupedSummaryText, + allReportsHaveError, + areReportsLoading, + anyReportHasError, + areAllReportsLoading, + anyReportHasIssues, + summaryCounts, +} from '~/vue_shared/security_reports/store/getters'; +import { CRITICAL, HIGH, LOW } from '~/vulnerabilities/constants'; + +const generateVuln = severity => ({ severity }); + +describe('Security reports getters', () => { + let state; + + beforeEach(() => { + state = createState(); + state.sast = createSastState(); + state.secretDetection = createSecretScanningState(); + }); + + describe('summaryCounts', () => { + it('returns 0 count for empty state', () => { + expect(summaryCounts(state)).toEqual({ + critical: 0, + high: 0, + other: 0, + }); + }); + + describe('combines all reports', () => { + it('of the same severity', () => { + state.sast.newIssues = [generateVuln(CRITICAL)]; + state.secretDetection.newIssues = [generateVuln(CRITICAL)]; + + expect(summaryCounts(state)).toEqual({ + critical: 2, + high: 0, + other: 0, + }); + }); + + it('of different severities', () => { + state.sast.newIssues = [generateVuln(CRITICAL)]; + state.secretDetection.newIssues = [generateVuln(HIGH), generateVuln(LOW)]; + + expect(summaryCounts(state)).toEqual({ + critical: 1, + high: 1, + other: 1, + }); + }); + }); + }); + + describe('groupedSummaryText', () => { + it('returns failed text', () => { + expect( + groupedSummaryText(state, { + allReportsHaveError: true, + areReportsLoading: false, + summaryCounts: {}, + }), + ).toEqual({ message: 'Security scanning failed loading any results' }); + }); + + it('returns `is loading` as status text', () => { + expect( + groupedSummaryText(state, { + allReportsHaveError: false, + areReportsLoading: true, + summaryCounts: {}, + }), + ).toEqual( + groupedTextBuilder({ + reportType: 'Security scanning', + critical: 0, + high: 0, + other: 0, + status: 'is loading', + }), + ); + }); + + it('returns no new status text if there are existing ones', () => { + expect( + groupedSummaryText(state, { + allReportsHaveError: false, + areReportsLoading: false, + summaryCounts: {}, + }), + ).toEqual( + groupedTextBuilder({ + reportType: 'Security scanning', + critical: 0, + high: 0, + other: 0, + status: '', + }), + ); + }); + }); + + describe('areReportsLoading', () => { + it('returns true when any report is loading', () => { + state.sast.isLoading = true; + + expect(areReportsLoading(state)).toEqual(true); + }); + + it('returns false when none of the reports are loading', () => { + expect(areReportsLoading(state)).toEqual(false); + }); + }); + + describe('areAllReportsLoading', () => { + it('returns true when all reports are loading', () => { + state.sast.isLoading = true; + state.secretDetection.isLoading = true; + + expect(areAllReportsLoading(state)).toEqual(true); + }); + + it('returns false when some of the reports are loading', () => { + state.sast.isLoading = true; + + expect(areAllReportsLoading(state)).toEqual(false); + }); + + it('returns false when none of the reports are loading', () => { + expect(areAllReportsLoading(state)).toEqual(false); + }); + }); + + describe('allReportsHaveError', () => { + it('returns true when all reports have error', () => { + state.sast.hasError = true; + state.secretDetection.hasError = true; + + expect(allReportsHaveError(state)).toEqual(true); + }); + + it('returns false when none of the reports have error', () => { + expect(allReportsHaveError(state)).toEqual(false); + }); + + it('returns false when one of the reports does not have error', () => { + state.secretDetection.hasError = true; + + expect(allReportsHaveError(state)).toEqual(false); + }); + }); + + describe('anyReportHasError', () => { + it('returns true when any of the reports has error', () => { + state.sast.hasError = true; + + expect(anyReportHasError(state)).toEqual(true); + }); + + it('returns false when none of the reports has error', () => { + expect(anyReportHasError(state)).toEqual(false); + }); + }); + + describe('anyReportHasIssues', () => { + it('returns true when any of the reports has new issues', () => { + state.sast.newIssues.push(generateVuln(LOW)); + + expect(anyReportHasIssues(state)).toEqual(true); + }); + + it('returns false when none of the reports has error', () => { + expect(anyReportHasIssues(state)).toEqual(false); + }); + }); +}); diff --git a/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb b/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb index bfb3ce91d58..96ca679620d 100644 --- a/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb +++ b/spec/graphql/resolvers/project_merge_requests_resolver_spec.rb @@ -50,7 +50,7 @@ RSpec.describe Resolvers::ProjectMergeRequestsResolver do end end - def resolve_mr(project, args, resolver: described_class, user: current_user) + def resolve_mr(project, resolver: described_class, user: current_user, **args) resolve(resolver, obj: project, args: args, ctx: { current_user: user }) end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 38fe2781331..81d14fba4ff 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -552,6 +552,7 @@ project: - pipeline_artifacts - terraform_states - alert_management_http_integrations +- exported_protected_branches award_emoji: - awardable - user diff --git a/spec/lib/gitlab/rack_attack_spec.rb b/spec/lib/gitlab/rack_attack_spec.rb new file mode 100644 index 00000000000..ac24bdf3a62 --- /dev/null +++ b/spec/lib/gitlab/rack_attack_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::RackAttack, :aggregate_failures do + describe '.configure' do + let(:fake_rack_attack) { class_double("Rack::Attack") } + let(:fake_rack_attack_request) { class_double("Rack::Attack::Request") } + + let(:throttles) do + { + throttle_unauthenticated: Gitlab::Throttle.unauthenticated_options, + throttle_authenticated_api: Gitlab::Throttle.authenticated_api_options, + throttle_product_analytics_collector: { limit: 100, period: 60 }, + throttle_unauthenticated_protected_paths: Gitlab::Throttle.unauthenticated_options, + throttle_authenticated_protected_paths_api: Gitlab::Throttle.authenticated_api_options, + throttle_authenticated_protected_paths_web: Gitlab::Throttle.authenticated_web_options + } + end + + before do + stub_const("Rack::Attack", fake_rack_attack) + stub_const("Rack::Attack::Request", fake_rack_attack_request) + + allow(fake_rack_attack).to receive(:throttle) + allow(fake_rack_attack).to receive(:track) + allow(fake_rack_attack).to receive(:safelist) + allow(fake_rack_attack).to receive(:blocklist) + end + + it 'extends the request class' do + described_class.configure(fake_rack_attack) + + expect(fake_rack_attack_request).to include(described_class::Request) + end + + it 'configures the safelist' do + described_class.configure(fake_rack_attack) + + expect(fake_rack_attack).to have_received(:safelist).with('throttle_bypass_header') + end + + it 'configures throttles if no dry-run was configured' do + described_class.configure(fake_rack_attack) + + throttles.each do |throttle, options| + expect(fake_rack_attack).to have_received(:throttle).with(throttle.to_s, options) + end + end + + it 'configures tracks if dry-run was configured for all throttles' do + stub_env('GITLAB_THROTTLE_DRY_RUN', '*') + + described_class.configure(fake_rack_attack) + + throttles.each do |throttle, options| + expect(fake_rack_attack).to have_received(:track).with(throttle.to_s, options) + end + expect(fake_rack_attack).not_to have_received(:throttle) + end + + it 'configures tracks and throttles with a selected set of dry-runs' do + dry_run_throttles = throttles.each_key.first(2) + regular_throttles = throttles.keys[2..-1] + stub_env('GITLAB_THROTTLE_DRY_RUN', dry_run_throttles.join(',')) + + described_class.configure(fake_rack_attack) + + dry_run_throttles.each do |throttle| + expect(fake_rack_attack).to have_received(:track).with(throttle.to_s, throttles[throttle]) + end + regular_throttles.each do |throttle| + expect(fake_rack_attack).to have_received(:throttle).with(throttle.to_s, throttles[throttle]) + end + end + end +end diff --git a/spec/models/exported_protected_branch_spec.rb b/spec/models/exported_protected_branch_spec.rb new file mode 100644 index 00000000000..7886a522741 --- /dev/null +++ b/spec/models/exported_protected_branch_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ExportedProtectedBranch do + describe 'Associations' do + it { is_expected.to have_many(:push_access_levels) } + end + + describe '.push_access_levels' do + it 'returns the correct push access levels' do + exported_branch = create(:exported_protected_branch, :developers_can_push) + deploy_key = create(:deploy_key) + create(:deploy_keys_project, :write_access, project: exported_branch.project, deploy_key: deploy_key ) + create(:protected_branch_push_access_level, protected_branch: exported_branch, deploy_key: deploy_key) + dev_push_access_level = exported_branch.push_access_levels.first + + expect(exported_branch.push_access_levels).to contain_exactly(dev_push_access_level) + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c8b96963d5d..b4da57ed89b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -33,6 +33,7 @@ RSpec.describe Project, factory_default: :keep do it { is_expected.to have_many(:deploy_keys) } it { is_expected.to have_many(:hooks) } it { is_expected.to have_many(:protected_branches) } + it { is_expected.to have_many(:exported_protected_branches) } it { is_expected.to have_one(:slack_service) } it { is_expected.to have_one(:microsoft_teams_service) } it { is_expected.to have_one(:mattermost_service) } diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index d74f5faab7f..9cf22a9392d 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -481,17 +481,9 @@ RSpec.describe Snippet do end describe '#blobs' do - let(:snippet) { create(:snippet) } - - it 'returns a blob representing the snippet data' do - blob = snippet.blob - - expect(blob).to be_a(Blob) - expect(blob.path).to eq(snippet.file_name) - expect(blob.data).to eq(snippet.content) - end - context 'when repository does not exist' do + let(:snippet) { create(:snippet) } + it 'returns empty array' do expect(snippet.blobs).to be_empty end diff --git a/spec/presenters/projects/import_export/project_export_presenter_spec.rb b/spec/presenters/projects/import_export/project_export_presenter_spec.rb index 8463d01d95b..b2b2ce35f34 100644 --- a/spec/presenters/projects/import_export/project_export_presenter_spec.rb +++ b/spec/presenters/projects/import_export/project_export_presenter_spec.rb @@ -45,6 +45,14 @@ RSpec.describe Projects::ImportExport::ProjectExportPresenter do end end + describe '#protected_branches' do + it 'returns the project exported protected branches' do + expect(project).to receive(:exported_protected_branches) + + subject.protected_branches + end + end + describe '#project_members' do let(:user2) { create(:user, email: 'group@member.com') } let(:member_emails) do diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index 805ac5a9118..c2e68df2c40 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -106,7 +106,7 @@ RSpec.describe 'Rack Attack global throttles' do let(:request_jobs_url) { '/api/v4/jobs/request' } let(:runner) { create(:ci_runner) } - it 'does not cont as unauthenticated' do + it 'does not count as unauthenticated' do (1 + requests_per_period).times do post request_jobs_url, params: { token: runner.token } expect(response).to have_gitlab_http_status(:no_content) @@ -114,6 +114,17 @@ RSpec.describe 'Rack Attack global throttles' do end end + context 'when the request is to a health endpoint' do + let(:health_endpoint) { '/-/metrics' } + + it 'does not throttle the requests' do + (1 + requests_per_period).times do + get health_endpoint + expect(response).to have_gitlab_http_status(:ok) + end + end + end + it 'logs RackAttack info into structured logs' do requests_per_period.times do get url_that_does_not_require_authentication @@ -133,6 +144,14 @@ RSpec.describe 'Rack Attack global throttles' do get url_that_does_not_require_authentication end + + it_behaves_like 'tracking when dry-run mode is set' do + let(:throttle_name) { 'throttle_unauthenticated' } + + def do_request + get url_that_does_not_require_authentication + end + end end context 'when the throttle is disabled' do @@ -231,6 +250,10 @@ RSpec.describe 'Rack Attack global throttles' do let(:post_params) { { user: { login: 'username', password: 'password' } } } + def do_request + post protected_path_that_does_not_require_authentication, params: post_params + end + before do settings_to_set[:throttle_protected_paths_requests_per_period] = requests_per_period # 1 settings_to_set[:throttle_protected_paths_period_in_seconds] = period_in_seconds # 10_000 @@ -244,7 +267,7 @@ RSpec.describe 'Rack Attack global throttles' do it 'allows requests over the rate limit' do (1 + requests_per_period).times do - post protected_path_that_does_not_require_authentication, params: post_params + do_request expect(response).to have_gitlab_http_status(:ok) end end @@ -258,12 +281,16 @@ RSpec.describe 'Rack Attack global throttles' do it 'rejects requests over the rate limit' do requests_per_period.times do - post protected_path_that_does_not_require_authentication, params: post_params + do_request expect(response).to have_gitlab_http_status(:ok) end expect_rejection { post protected_path_that_does_not_require_authentication, params: post_params } end + + it_behaves_like 'tracking when dry-run mode is set' do + let(:throttle_name) { 'throttle_unauthenticated_protected_paths' } + end end end diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb index d4ee68309ff..323ac50808d 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -110,6 +110,14 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do expect { make_request(request_args) }.not_to exceed_query_limit(control_count) end end + + it_behaves_like 'tracking when dry-run mode is set' do + let(:throttle_name) { throttle_types[throttle_setting_prefix] } + + def do_request + make_request(request_args) + end + end end context 'when the throttle is disabled' do @@ -245,6 +253,14 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once expect { request_authenticated_web_url }.not_to exceed_query_limit(control_count) end + + it_behaves_like 'tracking when dry-run mode is set' do + let(:throttle_name) { throttle_types[throttle_setting_prefix] } + + def do_request + request_authenticated_web_url + end + end end context 'when the throttle is disabled' do @@ -269,3 +285,63 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do end end end + +# Requires: +# - #do_request - This needs to be a method so the result isn't memoized +# - throttle_name +RSpec.shared_examples 'tracking when dry-run mode is set' do + let(:dry_run_config) { '*' } + + # we can't use `around` here, because stub_env isn't supported outside of the + # example itself + before do + stub_env('GITLAB_THROTTLE_DRY_RUN', dry_run_config) + reset_rack_attack + end + + after do + stub_env('GITLAB_THROTTLE_DRY_RUN', '') + reset_rack_attack + end + + def reset_rack_attack + Rack::Attack.reset! + Rack::Attack.clear_configuration + Gitlab::RackAttack.configure(Rack::Attack) + end + + it 'does not throttle the requests when `*` is configured' do + (1 + requests_per_period).times do + do_request + expect(response).not_to have_gitlab_http_status(:too_many_requests) + end + end + + it 'logs RackAttack info into structured logs' do + arguments = a_hash_including({ + message: 'Rack_Attack', + env: :track, + remote_ip: '127.0.0.1', + matched: throttle_name + }) + + expect(Gitlab::AuthLogger).to receive(:error).with(arguments) + + (1 + requests_per_period).times do + do_request + end + end + + context 'when configured with the the throttled name in a list' do + let(:dry_run_config) do + "throttle_list, #{throttle_name}, other_throttle" + end + + it 'does not throttle' do + (1 + requests_per_period).times do + do_request + expect(response).not_to have_gitlab_http_status(:too_many_requests) + end + end + end +end |