summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-25 12:08:19 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-25 12:08:19 +0000
commite6baeabaa9651d90b03bb64ffce75a2c3cb89aab (patch)
tree85f3cbd6e437b17be59505cf3ac4794c1838609e
parent5064bf8c5647d4c4430cbb4d097cf1592416de29 (diff)
downloadgitlab-ce-e6baeabaa9651d90b03bb64ffce75a2c3cb89aab.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/registry/explorer/constants.js2
-rw-r--r--app/assets/javascripts/registry/explorer/index.js2
-rw-r--r--app/assets/javascripts/registry/explorer/pages/details.vue30
-rw-r--r--app/assets/javascripts/registry/explorer/pages/index.vue41
-rw-r--r--app/assets/javascripts/registry/explorer/pages/list.vue75
-rw-r--r--app/assets/javascripts/registry/explorer/stores/actions.js19
-rw-r--r--app/assets/javascripts/registry/explorer/stores/getters.js4
-rw-r--r--app/assets/javascripts/registry/explorer/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/registry/explorer/stores/mutations.js5
-rw-r--r--app/assets/javascripts/registry/explorer/stores/state.js1
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss4
-rw-r--r--app/helpers/events_helper.rb21
-rw-r--r--app/models/project_services/buildkite_service.rb8
-rw-r--r--app/models/project_services/drone_ci_service.rb8
-rw-r--r--app/models/snippet.rb8
-rw-r--r--app/services/projects/container_repository/cleanup_tags_service.rb40
-rw-r--r--app/views/events/_event.html.haml4
-rw-r--r--app/views/events/event/_wiki.html.haml10
-rw-r--r--app/views/groups/registry/repositories/index.html.haml2
-rw-r--r--app/views/projects/registry/repositories/index.html.haml2
-rw-r--r--app/views/shared/_event_filter.html.haml2
-rw-r--r--changelogs/unreleased/119092-update-detected-languages-for-sast-no-dind-mode.yml5
-rw-r--r--changelogs/unreleased/195165-replace-eol-ruby-versions-in-specs.yml5
-rw-r--r--changelogs/unreleased/208220-improve-performance-of-the-container-repository-cleanup-tags-servi.yml5
-rw-r--r--changelogs/unreleased/210570-add-cost-factors-to-ci-runners.yml5
-rw-r--r--changelogs/unreleased/30146-let-s-encrypt-integration-doesn-t-scale-and-does-not-give-any-feedb.yml5
-rw-r--r--changelogs/unreleased/30526-be-wiki-activity-on-project-group-user.yml5
-rw-r--r--changelogs/unreleased/fj-212399-fix-bug-tracking-snippet-shard-name.yml5
-rw-r--r--db/migrate/20200318140400_create_vulnerability_user_mentions.rb19
-rw-r--r--db/migrate/20200320112455_add_cost_factor_fileds_to_ci_runners.rb19
-rw-r--r--db/migrate/20200320123839_add_letsencrypt_errors_to_pages_domains.rb20
-rw-r--r--db/structure.sql45
-rw-r--r--doc/administration/high_availability/object_storage.md2
-rw-r--r--doc/administration/packages/dependency_proxy.md2
-rw-r--r--doc/development/integrations/secure.md10
-rw-r--r--doc/raketasks/backup_restore.md3
-rw-r--r--doc/topics/airgap/index.md58
-rw-r--r--doc/user/packages/dependency_proxy/index.md2
-rw-r--r--doc/user/packages/nuget_repository/index.md1
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml2
-rw-r--r--lib/gitlab/import_export/snippet_repo_restorer.rb2
-rw-r--r--locale/gitlab.pot12
-rw-r--r--spec/controllers/dashboard_controller_spec.rb4
-rw-r--r--spec/factories/ci/builds.rb4
-rw-r--r--spec/factories/snippets.rb2
-rw-r--r--spec/frontend/registry/explorer/pages/details_spec.js63
-rw-r--r--spec/frontend/registry/explorer/pages/index_spec.js62
-rw-r--r--spec/frontend/registry/explorer/pages/list_spec.js41
-rw-r--r--spec/frontend/registry/explorer/stores/actions_spec.js67
-rw-r--r--spec/frontend/registry/explorer/stores/getters_spec.js18
-rw-r--r--spec/frontend/registry/explorer/stores/mutations_spec.js16
-rw-r--r--spec/frontend/registry/shared/mocks.js4
-rw-r--r--spec/helpers/events_helper_spec.rb79
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb20
-rw-r--r--spec/lib/gitlab/config/loader/yaml_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb13
-rw-r--r--spec/models/ci/build_spec.rb6
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb18
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb4
-rw-r--r--spec/models/snippet_repository_spec.rb2
-rw-r--r--spec/models/snippet_spec.rb34
-rw-r--r--spec/requests/api/lint_spec.rb2
-rw-r--r--spec/requests/api/runner_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb10
-rw-r--r--spec/services/projects/container_repository/cleanup_tags_service_spec.rb93
72 files changed, 907 insertions, 290 deletions
diff --git a/app/assets/javascripts/registry/explorer/constants.js b/app/assets/javascripts/registry/explorer/constants.js
index f5d935a2d70..586231d19c7 100644
--- a/app/assets/javascripts/registry/explorer/constants.js
+++ b/app/assets/javascripts/registry/explorer/constants.js
@@ -34,7 +34,7 @@ export const LIST_KEY_CHECKBOX = 'checkbox';
export const LIST_LABEL_TAG = s__('ContainerRegistry|Tag');
export const LIST_LABEL_IMAGE_ID = s__('ContainerRegistry|Image ID');
-export const LIST_LABEL_SIZE = s__('ContainerRegistry|Size');
+export const LIST_LABEL_SIZE = s__('ContainerRegistry|Compressed Size');
export const LIST_LABEL_LAST_UPDATED = s__('ContainerRegistry|Last Updated');
export const EXPIRATION_POLICY_ALERT_TITLE = s__(
diff --git a/app/assets/javascripts/registry/explorer/index.js b/app/assets/javascripts/registry/explorer/index.js
index a36978303c6..9269aa074f8 100644
--- a/app/assets/javascripts/registry/explorer/index.js
+++ b/app/assets/javascripts/registry/explorer/index.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { GlToast } from '@gitlab/ui';
import Translate from '~/vue_shared/translate';
import RegistryExplorer from './pages/index.vue';
import RegistryBreadcrumb from './components/registry_breadcrumb.vue';
@@ -6,6 +7,7 @@ import { createStore } from './stores';
import createRouter from './router';
Vue.use(Translate);
+Vue.use(GlToast);
export default () => {
const el = document.getElementById('js-container-registry');
diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue
index be9a27e2c42..68a066f97e1 100644
--- a/app/assets/javascripts/registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/registry/explorer/pages/details.vue
@@ -31,6 +31,10 @@ import {
LIST_LABEL_IMAGE_ID,
LIST_LABEL_SIZE,
LIST_LABEL_LAST_UPDATED,
+ DELETE_TAG_SUCCESS_MESSAGE,
+ DELETE_TAG_ERROR_MESSAGE,
+ DELETE_TAGS_SUCCESS_MESSAGE,
+ DELETE_TAGS_ERROR_MESSAGE,
} from '../constants';
export default {
@@ -176,17 +180,37 @@ export default {
},
handleSingleDelete(itemToDelete) {
this.itemsToBeDeleted = [];
- this.requestDeleteTag({ tag: itemToDelete, params: this.$route.params.id });
+ return this.requestDeleteTag({ tag: itemToDelete, params: this.$route.params.id })
+ .then(() =>
+ this.$toast.show(DELETE_TAG_SUCCESS_MESSAGE, {
+ type: 'success',
+ }),
+ )
+ .catch(() =>
+ this.$toast.show(DELETE_TAG_ERROR_MESSAGE, {
+ type: 'error',
+ }),
+ );
},
handleMultipleDelete() {
const { itemsToBeDeleted } = this;
this.itemsToBeDeleted = [];
this.selectedItems = [];
- this.requestDeleteTags({
+ return this.requestDeleteTags({
ids: itemsToBeDeleted.map(x => this.tags[x].name),
params: this.$route.params.id,
- });
+ })
+ .then(() =>
+ this.$toast.show(DELETE_TAGS_SUCCESS_MESSAGE, {
+ type: 'success',
+ }),
+ )
+ .catch(() =>
+ this.$toast.show(DELETE_TAGS_ERROR_MESSAGE, {
+ type: 'error',
+ }),
+ );
},
onDeletionConfirmed() {
this.track('confirm_delete');
diff --git a/app/assets/javascripts/registry/explorer/pages/index.vue b/app/assets/javascripts/registry/explorer/pages/index.vue
index 19ae3bee640..95d83c82987 100644
--- a/app/assets/javascripts/registry/explorer/pages/index.vue
+++ b/app/assets/javascripts/registry/explorer/pages/index.vue
@@ -1,11 +1,48 @@
<script>
-export default {};
+import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
+import { mapState, mapActions, mapGetters } from 'vuex';
+import { s__ } from '~/locale';
+
+export default {
+ components: {
+ GlAlert,
+ GlSprintf,
+ GlLink,
+ },
+ i18n: {
+ garbageCollectionTipText: s__(
+ 'ContainerRegistry|This Registry contains deleted image tag data. Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage.',
+ ),
+ },
+ computed: {
+ ...mapState(['config']),
+ ...mapGetters(['showGarbageCollection']),
+ },
+ methods: {
+ ...mapActions(['setShowGarbageCollectionTip']),
+ },
+};
</script>
<template>
<div>
+ <gl-alert
+ v-if="showGarbageCollection"
+ variant="tip"
+ class="my-2"
+ @dismiss="setShowGarbageCollectionTip(false)"
+ >
+ <gl-sprintf :message="$options.i18n.garbageCollectionTipText">
+ <template #docLink="{content}">
+ <gl-link :href="config.garbageCollectionHelpPagePath" target="_blank">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-alert>
+
<transition name="slide">
- <router-view />
+ <router-view ref="router-view" />
</transition>
</div>
</template>
diff --git a/app/assets/javascripts/registry/explorer/pages/list.vue b/app/assets/javascripts/registry/explorer/pages/list.vue
index 7e321e927d3..1ed24fbb59f 100644
--- a/app/assets/javascripts/registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/registry/explorer/pages/list.vue
@@ -12,11 +12,13 @@ import {
GlSkeletonLoader,
} from '@gitlab/ui';
import Tracking from '~/tracking';
+import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ProjectEmptyState from '../components/project_empty_state.vue';
import GroupEmptyState from '../components/group_empty_state.vue';
import ProjectPolicyAlert from '../components/project_policy_alert.vue';
import QuickstartDropdown from '../components/quickstart_dropdown.vue';
+import { DELETE_IMAGE_SUCCESS_MESSAGE, DELETE_IMAGE_ERROR_MESSAGE } from '../constants';
export default {
name: 'RegistryListApp',
@@ -44,6 +46,23 @@ export default {
width: 1000,
height: 40,
},
+ i18n: {
+ containerRegistryTitle: s__('ContainerRegistry|Container Registry'),
+ connectionErrorTitle: s__('ContainerRegistry|Docker connection error'),
+ connectionErrorMessage: s__(
+ `ContainerRegistry|We are having trouble connecting to Docker, which could be due to an issue with your project name or path. %{docLinkStart}More Information%{docLinkEnd}`,
+ ),
+ introText: s__(
+ `ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}`,
+ ),
+ deleteButtonDisabled: s__(
+ 'ContainerRegistry|Missing or insufficient permission, delete button disabled',
+ ),
+ removeRepositoryLabel: s__('ContainerRegistry|Remove repository'),
+ removeRepositoryModalText: s__(
+ 'ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted.',
+ ),
+ },
data() {
return {
itemToDelete: {},
@@ -76,10 +95,22 @@ export default {
this.itemToDelete = item;
this.$refs.deleteModal.show();
},
- handleDeleteRepository() {
+ handleDeleteImage() {
this.track('confirm_delete');
- this.requestDeleteImage(this.itemToDelete.destroy_path);
- this.itemToDelete = {};
+ return this.requestDeleteImage(this.itemToDelete.destroy_path)
+ .then(() =>
+ this.$toast.show(DELETE_IMAGE_SUCCESS_MESSAGE, {
+ type: 'success',
+ }),
+ )
+ .catch(() =>
+ this.$toast.show(DELETE_IMAGE_ERROR_MESSAGE, {
+ type: 'error',
+ }),
+ )
+ .finally(() => {
+ this.itemToDelete = {};
+ });
},
encodeListItem(item) {
const params = JSON.stringify({ name: item.path, tags_path: item.tags_path, id: item.id });
@@ -95,18 +126,12 @@ export default {
<gl-empty-state
v-if="config.characterError"
- :title="s__('ContainerRegistry|Docker connection error')"
+ :title="$options.i18n.connectionErrorTitle"
:svg-path="config.containersErrorImage"
>
<template #description>
<p>
- <gl-sprintf
- :message="
- s__(`ContainerRegistry|We are having trouble connecting to Docker, which could be due to an
- issue with your project name or path.
- %{docLinkStart}More Information%{docLinkEnd}`)
- "
- >
+ <gl-sprintf :message="$options.i18n.connectionErrorMessage">
<template #docLink="{content}">
<gl-link :href="`${config.helpPagePath}#docker-connection-error`" target="_blank">
{{ content }}
@@ -120,17 +145,11 @@ export default {
<template v-else>
<div>
<div class="d-flex justify-content-between align-items-center">
- <h4>{{ s__('ContainerRegistry|Container Registry') }}</h4>
+ <h4>{{ $options.i18n.containerRegistryTitle }}</h4>
<quickstart-dropdown v-if="showQuickStartDropdown" class="d-none d-sm-block" />
</div>
<p>
- <gl-sprintf
- :message="
- s__(`ContainerRegistry|With the Docker Container Registry integrated into GitLab, every
- project can have its own space to store its Docker images.
- %{docLinkStart}More Information%{docLinkEnd}`)
- "
- >
+ <gl-sprintf :message="$options.i18n.introText">
<template #docLink="{content}">
<gl-link :href="config.helpPagePath" target="_blank">
{{ content }}
@@ -180,16 +199,14 @@ export default {
<div
v-gl-tooltip="{ disabled: listItem.destroy_path }"
class="d-none d-sm-block"
- :title="
- s__('ContainerRegistry|Missing or insufficient permission, delete button disabled')
- "
+ :title="$options.i18n.deleteButtonDisabled"
>
<gl-button
ref="deleteImageButton"
v-gl-tooltip
:disabled="!listItem.destroy_path"
- :title="s__('ContainerRegistry|Remove repository')"
- :aria-label="s__('ContainerRegistry|Remove repository')"
+ :title="$options.i18n.removeRepositoryLabel"
+ :aria-label="$options.i18n.removeRepositoryLabel"
class="btn-inverted"
variant="danger"
@click="deleteImage(listItem)"
@@ -217,16 +234,12 @@ export default {
ref="deleteModal"
modal-id="delete-image-modal"
ok-variant="danger"
- @ok="handleDeleteRepository"
+ @ok="handleDeleteImage"
@cancel="track('cancel_delete')"
>
- <template #modal-title>{{ s__('ContainerRegistry|Remove repository') }}</template>
+ <template #modal-title>{{ $options.i18n.removeRepositoryLabel }}</template>
<p>
- <gl-sprintf
- :message=" s__(
- 'ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted.',
- ),"
- >
+ <gl-sprintf :message="$options.i18n.removeRepositoryModalText">
<template #title>
<b>{{ itemToDelete.path }}</b>
</template>
diff --git a/app/assets/javascripts/registry/explorer/stores/actions.js b/app/assets/javascripts/registry/explorer/stores/actions.js
index 7d8201949f4..2abd72cb9a8 100644
--- a/app/assets/javascripts/registry/explorer/stores/actions.js
+++ b/app/assets/javascripts/registry/explorer/stores/actions.js
@@ -6,16 +6,12 @@ import {
DEFAULT_PAGE,
DEFAULT_PAGE_SIZE,
FETCH_TAGS_LIST_ERROR_MESSAGE,
- DELETE_TAG_SUCCESS_MESSAGE,
- DELETE_TAG_ERROR_MESSAGE,
- DELETE_TAGS_SUCCESS_MESSAGE,
- DELETE_TAGS_ERROR_MESSAGE,
- DELETE_IMAGE_ERROR_MESSAGE,
- DELETE_IMAGE_SUCCESS_MESSAGE,
} from '../constants';
import { decodeAndParse } from '../utils';
export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data);
+export const setShowGarbageCollectionTip = ({ commit }, data) =>
+ commit(types.SET_SHOW_GARBAGE_COLLECTION_TIP, data);
export const receiveImagesListSuccess = ({ commit }, { data, headers }) => {
commit(types.SET_IMAGES_LIST_SUCCESS, data);
@@ -67,11 +63,10 @@ export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) =
return axios
.delete(tag.destroy_path)
.then(() => {
- createFlash(DELETE_TAG_SUCCESS_MESSAGE, 'success');
+ dispatch('setShowGarbageCollectionTip', true);
return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
})
.catch(() => {
- createFlash(DELETE_TAG_ERROR_MESSAGE);
commit(types.SET_MAIN_LOADING, false);
});
};
@@ -85,11 +80,10 @@ export const requestDeleteTags = ({ commit, dispatch, state }, { ids, params })
return axios
.delete(url, { params: { ids } })
.then(() => {
- createFlash(DELETE_TAGS_SUCCESS_MESSAGE, 'success');
+ dispatch('setShowGarbageCollectionTip', true);
return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
})
.catch(() => {
- createFlash(DELETE_TAGS_ERROR_MESSAGE);
commit(types.SET_MAIN_LOADING, false);
});
};
@@ -100,11 +94,8 @@ export const requestDeleteImage = ({ commit, dispatch, state }, destroyPath) =>
return axios
.delete(destroyPath)
.then(() => {
+ dispatch('setShowGarbageCollectionTip', true);
dispatch('requestImagesList', { pagination: state.pagination });
- createFlash(DELETE_IMAGE_SUCCESS_MESSAGE, 'success');
- })
- .catch(() => {
- createFlash(DELETE_IMAGE_ERROR_MESSAGE);
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
diff --git a/app/assets/javascripts/registry/explorer/stores/getters.js b/app/assets/javascripts/registry/explorer/stores/getters.js
index 1136257a024..a371d0e6356 100644
--- a/app/assets/javascripts/registry/explorer/stores/getters.js
+++ b/app/assets/javascripts/registry/explorer/stores/getters.js
@@ -18,3 +18,7 @@ export const dockerLoginCommand = state => {
/* eslint-disable @gitlab/require-i18n-strings */
return `docker login ${state.config.registryHostUrlWithPort}`;
};
+
+export const showGarbageCollection = state => {
+ return state.showGarbageCollectionTip && state.config.isAdmin;
+};
diff --git a/app/assets/javascripts/registry/explorer/stores/mutation_types.js b/app/assets/javascripts/registry/explorer/stores/mutation_types.js
index 92b747dffc5..86eaa0dd2f1 100644
--- a/app/assets/javascripts/registry/explorer/stores/mutation_types.js
+++ b/app/assets/javascripts/registry/explorer/stores/mutation_types.js
@@ -5,3 +5,4 @@ export const SET_PAGINATION = 'SET_PAGINATION';
export const SET_MAIN_LOADING = 'SET_MAIN_LOADING';
export const SET_TAGS_PAGINATION = 'SET_TAGS_PAGINATION';
export const SET_TAGS_LIST_SUCCESS = 'SET_TAGS_LIST_SUCCESS';
+export const SET_SHOW_GARBAGE_COLLECTION_TIP = 'SET_SHOW_GARBAGE_COLLECTION_TIP';
diff --git a/app/assets/javascripts/registry/explorer/stores/mutations.js b/app/assets/javascripts/registry/explorer/stores/mutations.js
index 6055efcbd46..fda788051c0 100644
--- a/app/assets/javascripts/registry/explorer/stores/mutations.js
+++ b/app/assets/javascripts/registry/explorer/stores/mutations.js
@@ -7,6 +7,7 @@ export default {
...config,
expirationPolicy: config.expirationPolicy ? JSON.parse(config.expirationPolicy) : undefined,
isGroupPage: config.isGroupPage !== undefined,
+ isAdmin: config.isAdmin !== undefined,
};
},
@@ -22,6 +23,10 @@ export default {
state.isLoading = isLoading;
},
+ [types.SET_SHOW_GARBAGE_COLLECTION_TIP](state, showGarbageCollectionTip) {
+ state.showGarbageCollectionTip = showGarbageCollectionTip;
+ },
+
[types.SET_PAGINATION](state, headers) {
const normalizedHeaders = normalizeHeaders(headers);
state.pagination = parseIntPagination(normalizedHeaders);
diff --git a/app/assets/javascripts/registry/explorer/stores/state.js b/app/assets/javascripts/registry/explorer/stores/state.js
index 91a378f139b..694006aac81 100644
--- a/app/assets/javascripts/registry/explorer/stores/state.js
+++ b/app/assets/javascripts/registry/explorer/stores/state.js
@@ -1,5 +1,6 @@
export default () => ({
isLoading: false,
+ showGarbageCollectionTip: false,
config: {},
images: [],
tags: [],
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 62fc7311d94..f357d508d5d 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -7,6 +7,10 @@
a {
color: $gl-text-color;
+
+ &.link {
+ color: $blue-600;
+ }
}
.author-link {
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 09866ca75ff..ba8e046f504 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -68,10 +68,12 @@ module EventsHelper
end
def event_preposition(event)
- if event.push_action? || event.commented_action? || event.target
- "at"
+ if event.wiki_page?
+ 'in the wiki for'
elsif event.milestone?
- "in"
+ 'in'
+ elsif event.push_action? || event.commented_action? || event.target
+ 'at'
end
end
@@ -172,6 +174,19 @@ module EventsHelper
end
end
+ def event_wiki_title_html(event)
+ capture do
+ concat content_tag(:span, _('wiki page'), class: "event-target-type append-right-4")
+ concat link_to(event.target_title, event_wiki_page_target_url(event),
+ title: event.target_title,
+ class: 'has-tooltip event-target-link append-right-4')
+ end
+ end
+
+ def event_wiki_page_target_url(event)
+ project_wiki_url(event.project, event.target.canonical_slug)
+ end
+
def event_note_title_html(event)
if event.note_target
capture do
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index d058904dd9e..fc7a0180786 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -73,10 +73,10 @@ class BuildkiteService < CiService
end
def calculate_reactive_cache(sha, ref)
- response = Gitlab::HTTP.get(commit_status_path(sha), verify: false)
+ response = Gitlab::HTTP.try_get(commit_status_path(sha), request_options)
status =
- if response.code == 200 && response['status']
+ if response&.code == 200 && response['status']
response['status']
else
:error
@@ -117,4 +117,8 @@ class BuildkiteService < CiService
ENDPOINT
end
end
+
+ def request_options
+ { verify: false, extra_log_info: { project_id: project_id } }
+ end
end
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index d2660051343..4e4955b45d8 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -50,10 +50,12 @@ class DroneCiService < CiService
end
def calculate_reactive_cache(sha, ref)
- response = Gitlab::HTTP.get(commit_status_path(sha, ref), verify: enable_ssl_verification)
+ response = Gitlab::HTTP.try_get(commit_status_path(sha, ref),
+ verify: enable_ssl_verification,
+ extra_log_info: { project_id: project_id })
status =
- if response.code == 200 && response['status']
+ if response && response.code == 200 && response['status']
case response['status']
when 'killed'
:canceled
@@ -68,8 +70,6 @@ class DroneCiService < CiService
end
{ commit_status: status }
- rescue *Gitlab::HTTP::HTTP_ERRORS
- { commit_status: :error }
end
def build_page(sha, ref)
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index c7b5d7c8278..edf524190e4 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -293,12 +293,12 @@ class Snippet < ApplicationRecord
return if repository_exists? && snippet_repository
repository.create_if_not_exists
- track_snippet_repository
+ track_snippet_repository(repository.storage)
end
- def track_snippet_repository
- repository = snippet_repository || build_snippet_repository
- repository.update!(shard_name: repository_storage, disk_path: disk_path)
+ def track_snippet_repository(shard)
+ snippet_repo = snippet_repository || build_snippet_repository
+ snippet_repo.update!(shard_name: shard, disk_path: disk_path)
end
def can_cache_field?(field)
diff --git a/app/services/projects/container_repository/cleanup_tags_service.rb b/app/services/projects/container_repository/cleanup_tags_service.rb
index 6eb8f5c27d9..fc09d14ba4d 100644
--- a/app/services/projects/container_repository/cleanup_tags_service.rb
+++ b/app/services/projects/container_repository/cleanup_tags_service.rb
@@ -8,53 +8,30 @@ module Projects
return error('access denied') unless can_destroy?
tags = container_repository.tags
- tags_by_digest = group_by_digest(tags)
-
tags = without_latest(tags)
tags = filter_by_name(tags)
- tags = with_manifest(tags)
- tags = order_by_date(tags)
tags = filter_keep_n(tags)
tags = filter_by_older_than(tags)
- deleted_tags = delete_tags(tags, tags_by_digest)
-
- success(deleted: deleted_tags.map(&:name))
+ delete_tags(container_repository, tags)
end
private
- def delete_tags(tags_to_delete, tags_by_digest)
- deleted_digests = group_by_digest(tags_to_delete).select do |digest, tags|
- delete_tag_digest(tags, tags_by_digest[digest])
- end
-
- deleted_digests.values.flatten
- end
-
- def delete_tag_digest(tags, other_tags)
- # Issue: https://gitlab.com/gitlab-org/gitlab-foss/issues/21405
- # we have to remove all tags due
- # to Docker Distribution bug unable
- # to delete single tag
- return unless tags.count == other_tags.count
+ def delete_tags(container_repository, tags)
+ return success(deleted: []) unless tags.any?
- # delete all tags
- tags.map(&:unsafe_delete)
- end
+ tag_names = tags.map(&:name)
- def group_by_digest(tags)
- tags.group_by(&:digest)
+ Projects::ContainerRepository::DeleteTagsService
+ .new(container_repository.project, current_user, tags: tag_names)
+ .execute(container_repository)
end
def without_latest(tags)
tags.reject(&:latest?)
end
- def with_manifest(tags)
- tags.select(&:valid?)
- end
-
def order_by_date(tags)
now = DateTime.now
tags.sort_by { |tag| tag.created_at || now }.reverse
@@ -74,6 +51,9 @@ module Projects
end
def filter_keep_n(tags)
+ return tags unless params['keep_n']
+
+ tags = order_by_date(tags)
tags.drop(params['keep_n'].to_i)
end
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 647f0597adb..c042cd2c3e3 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -5,7 +5,9 @@
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
- - if event.created_project_action?
+ - if event.wiki_page?
+ = render "events/event/wiki", event: event
+ - elsif event.created_project_action?
= render "events/event/created_project", event: event
- elsif event.push_action?
= render "events/event/push", event: event
diff --git a/app/views/events/event/_wiki.html.haml b/app/views/events/event/_wiki.html.haml
new file mode 100644
index 00000000000..7ca98294521
--- /dev/null
+++ b/app/views/events/event/_wiki.html.haml
@@ -0,0 +1,10 @@
+= icon_for_profile_event(event)
+
+= event_user_info(event)
+
+.event-title.d-flex.flex-wrap
+ = inline_event_icon(event)
+ %span.event-type.d-inline-block.append-right-4{ class: event.action_name }
+ = event.action_name
+ = event_wiki_title_html(event)
+ = render "events/event_scope", event: event
diff --git a/app/views/groups/registry/repositories/index.html.haml b/app/views/groups/registry/repositories/index.html.haml
index eff8d77ac72..996582e0ae8 100644
--- a/app/views/groups/registry/repositories/index.html.haml
+++ b/app/views/groups/registry/repositories/index.html.haml
@@ -12,6 +12,8 @@
"no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
"containers_error_image" => image_path('illustrations/docker-error-state.svg'),
"registry_host_url_with_port" => escape_once(registry_config.host_port),
+ "garbage_collection_help_page_path" => help_page_path('administration/packages/container_registry', anchor: 'container-registry-garbage-collection'),
+ "is_admin": current_user&.admin,
is_group_page: true,
character_error: @character_error.to_s } }
- else
diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml
index 810830fd0c4..80dd5eaecb2 100644
--- a/app/views/projects/registry/repositories/index.html.haml
+++ b/app/views/projects/registry/repositories/index.html.haml
@@ -16,6 +16,8 @@
"repository_url" => escape_once(@project.container_registry_url),
"registry_host_url_with_port" => escape_once(registry_config.host_port),
"expiration_policy_help_page_path" => help_page_path('user/packages/container_registry/index', anchor: 'expiration-policy'),
+ "garbage_collection_help_page_path" => help_page_path('administration/packages/container_registry', anchor: 'container-registry-garbage-collection'),
+ "is_admin": current_user&.admin,
character_error: @character_error.to_s } }
- else
#js-vue-registry-images{ data: { endpoint: project_container_registry_index_path(@project, format: :json),
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index ad9eb325ff0..1b2e8d3799d 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -15,4 +15,6 @@
= render_if_exists 'events/epics_filter'
- if comments_visible?
= event_filter_link EventFilter::COMMENTS, _('Comments'), s_('EventFilterBy|Filter by comments')
+ - if Feature.enabled?(:wiki_events) && (@project.nil? || @project.has_wiki?)
+ = event_filter_link EventFilter::WIKI, _('Wiki'), s_('EventFilterBy|Filter by wiki')
= event_filter_link EventFilter::TEAM, _('Team'), s_('EventFilterBy|Filter by team')
diff --git a/changelogs/unreleased/119092-update-detected-languages-for-sast-no-dind-mode.yml b/changelogs/unreleased/119092-update-detected-languages-for-sast-no-dind-mode.yml
new file mode 100644
index 00000000000..38f4c7333e5
--- /dev/null
+++ b/changelogs/unreleased/119092-update-detected-languages-for-sast-no-dind-mode.yml
@@ -0,0 +1,5 @@
+---
+title: Update detected languages for sast in no dind mode
+merge_request: 27831
+author:
+type: fixed
diff --git a/changelogs/unreleased/195165-replace-eol-ruby-versions-in-specs.yml b/changelogs/unreleased/195165-replace-eol-ruby-versions-in-specs.yml
new file mode 100644
index 00000000000..dc3babbf2e6
--- /dev/null
+++ b/changelogs/unreleased/195165-replace-eol-ruby-versions-in-specs.yml
@@ -0,0 +1,5 @@
+---
+title: Use Ruby 2.7 in specs to remove Ruby 2.1/2.2/2.3
+merge_request: 27269
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/208220-improve-performance-of-the-container-repository-cleanup-tags-servi.yml b/changelogs/unreleased/208220-improve-performance-of-the-container-repository-cleanup-tags-servi.yml
new file mode 100644
index 00000000000..777e681ef83
--- /dev/null
+++ b/changelogs/unreleased/208220-improve-performance-of-the-container-repository-cleanup-tags-servi.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of the container repository cleanup tags service
+merge_request: 27441
+author:
+type: performance
diff --git a/changelogs/unreleased/210570-add-cost-factors-to-ci-runners.yml b/changelogs/unreleased/210570-add-cost-factors-to-ci-runners.yml
new file mode 100644
index 00000000000..947a9271369
--- /dev/null
+++ b/changelogs/unreleased/210570-add-cost-factors-to-ci-runners.yml
@@ -0,0 +1,5 @@
+---
+title: Add cost factor fields to ci runners
+merge_request: 27666
+author:
+type: added
diff --git a/changelogs/unreleased/30146-let-s-encrypt-integration-doesn-t-scale-and-does-not-give-any-feedb.yml b/changelogs/unreleased/30146-let-s-encrypt-integration-doesn-t-scale-and-does-not-give-any-feedb.yml
new file mode 100644
index 00000000000..d9737db8ee6
--- /dev/null
+++ b/changelogs/unreleased/30146-let-s-encrypt-integration-doesn-t-scale-and-does-not-give-any-feedb.yml
@@ -0,0 +1,5 @@
+---
+title: Add auto_ssl_failed to pages_domains
+merge_request: 27671
+author:
+type: added
diff --git a/changelogs/unreleased/30526-be-wiki-activity-on-project-group-user.yml b/changelogs/unreleased/30526-be-wiki-activity-on-project-group-user.yml
new file mode 100644
index 00000000000..022fd8efb77
--- /dev/null
+++ b/changelogs/unreleased/30526-be-wiki-activity-on-project-group-user.yml
@@ -0,0 +1,5 @@
+---
+title: Support wiki events in activity streams
+merge_request: 23869
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-212399-fix-bug-tracking-snippet-shard-name.yml b/changelogs/unreleased/fj-212399-fix-bug-tracking-snippet-shard-name.yml
new file mode 100644
index 00000000000..6310a64da3d
--- /dev/null
+++ b/changelogs/unreleased/fj-212399-fix-bug-tracking-snippet-shard-name.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug tracking snippet shard name
+merge_request: 27979
+author:
+type: fixed
diff --git a/db/migrate/20200318140400_create_vulnerability_user_mentions.rb b/db/migrate/20200318140400_create_vulnerability_user_mentions.rb
new file mode 100644
index 00000000000..919bc94cfa6
--- /dev/null
+++ b/db/migrate/20200318140400_create_vulnerability_user_mentions.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class CreateVulnerabilityUserMentions < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ create_table :vulnerability_user_mentions do |t|
+ t.references :vulnerability, type: :bigint, index: false, null: false, foreign_key: { on_delete: :cascade }
+ t.references :note, type: :integer,
+ index: { where: 'note_id IS NOT NULL', unique: true }, null: true, foreign_key: { on_delete: :cascade }
+ t.integer :mentioned_users_ids, array: true
+ t.integer :mentioned_projects_ids, array: true
+ t.integer :mentioned_groups_ids, array: true
+ end
+
+ add_index :vulnerability_user_mentions, [:vulnerability_id], where: 'note_id is null', unique: true, name: 'index_vulns_user_mentions_on_vulnerability_id'
+ add_index :vulnerability_user_mentions, [:vulnerability_id, :note_id], unique: true, name: 'index_vulns_user_mentions_on_vulnerability_id_and_note_id'
+ end
+end
diff --git a/db/migrate/20200320112455_add_cost_factor_fileds_to_ci_runners.rb b/db/migrate/20200320112455_add_cost_factor_fileds_to_ci_runners.rb
new file mode 100644
index 00000000000..472baa32340
--- /dev/null
+++ b/db/migrate/20200320112455_add_cost_factor_fileds_to_ci_runners.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddCostFactorFiledsToCiRunners < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:ci_runners, :public_projects_minutes_cost_factor, :float, allow_null: false, default: 0.0)
+ add_column_with_default(:ci_runners, :private_projects_minutes_cost_factor, :float, allow_null: false, default: 1.0)
+ end
+
+ def down
+ remove_column(:ci_runners, :public_projects_minutes_cost_factor)
+ remove_column(:ci_runners, :private_projects_minutes_cost_factor)
+ end
+end
diff --git a/db/migrate/20200320123839_add_letsencrypt_errors_to_pages_domains.rb b/db/migrate/20200320123839_add_letsencrypt_errors_to_pages_domains.rb
new file mode 100644
index 00000000000..ad4debf9a1e
--- /dev/null
+++ b/db/migrate/20200320123839_add_letsencrypt_errors_to_pages_domains.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# See https://docs.gitlab.com/ee/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLetsencryptErrorsToPagesDomains < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :pages_domains, :auto_ssl_failed, :boolean, default: false
+ end
+
+ def down
+ remove_column :pages_domains, :auto_ssl_failed
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index 9ad3711dd32..92671eb5ae4 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -1310,7 +1310,9 @@ CREATE TABLE public.ci_runners (
ip_address character varying,
maximum_timeout integer,
runner_type smallint NOT NULL,
- token_encrypted character varying
+ token_encrypted character varying,
+ public_projects_minutes_cost_factor double precision DEFAULT 0.0 NOT NULL,
+ private_projects_minutes_cost_factor double precision DEFAULT 1.0 NOT NULL
);
CREATE SEQUENCE public.ci_runners_id_seq
@@ -4449,7 +4451,8 @@ CREATE TABLE public.pages_domains (
certificate_source smallint DEFAULT 0 NOT NULL,
wildcard boolean DEFAULT false NOT NULL,
usage smallint DEFAULT 0 NOT NULL,
- scope smallint DEFAULT 2 NOT NULL
+ scope smallint DEFAULT 2 NOT NULL,
+ auto_ssl_failed boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE public.pages_domains_id_seq
@@ -6611,6 +6614,24 @@ CREATE SEQUENCE public.vulnerability_scanners_id_seq
ALTER SEQUENCE public.vulnerability_scanners_id_seq OWNED BY public.vulnerability_scanners.id;
+CREATE TABLE public.vulnerability_user_mentions (
+ id bigint NOT NULL,
+ vulnerability_id bigint NOT NULL,
+ note_id integer,
+ mentioned_users_ids integer[],
+ mentioned_projects_ids integer[],
+ mentioned_groups_ids integer[]
+);
+
+CREATE SEQUENCE public.vulnerability_user_mentions_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE public.vulnerability_user_mentions_id_seq OWNED BY public.vulnerability_user_mentions.id;
+
CREATE TABLE public.web_hook_logs (
id integer NOT NULL,
web_hook_id integer NOT NULL,
@@ -7358,6 +7379,8 @@ ALTER TABLE ONLY public.vulnerability_occurrences ALTER COLUMN id SET DEFAULT ne
ALTER TABLE ONLY public.vulnerability_scanners ALTER COLUMN id SET DEFAULT nextval('public.vulnerability_scanners_id_seq'::regclass);
+ALTER TABLE ONLY public.vulnerability_user_mentions ALTER COLUMN id SET DEFAULT nextval('public.vulnerability_user_mentions_id_seq'::regclass);
+
ALTER TABLE ONLY public.web_hook_logs ALTER COLUMN id SET DEFAULT nextval('public.web_hook_logs_id_seq'::regclass);
ALTER TABLE ONLY public.web_hooks ALTER COLUMN id SET DEFAULT nextval('public.web_hooks_id_seq'::regclass);
@@ -8286,6 +8309,9 @@ ALTER TABLE ONLY public.vulnerability_occurrences
ALTER TABLE ONLY public.vulnerability_scanners
ADD CONSTRAINT vulnerability_scanners_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY public.vulnerability_user_mentions
+ ADD CONSTRAINT vulnerability_user_mentions_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY public.web_hook_logs
ADD CONSTRAINT web_hook_logs_pkey PRIMARY KEY (id);
@@ -10128,6 +10154,12 @@ CREATE INDEX index_vulnerability_occurrences_on_vulnerability_id ON public.vulne
CREATE UNIQUE INDEX index_vulnerability_scanners_on_project_id_and_external_id ON public.vulnerability_scanners USING btree (project_id, external_id);
+CREATE UNIQUE INDEX index_vulnerability_user_mentions_on_note_id ON public.vulnerability_user_mentions USING btree (note_id) WHERE (note_id IS NOT NULL);
+
+CREATE UNIQUE INDEX index_vulns_user_mentions_on_vulnerability_id ON public.vulnerability_user_mentions USING btree (vulnerability_id) WHERE (note_id IS NULL);
+
+CREATE UNIQUE INDEX index_vulns_user_mentions_on_vulnerability_id_and_note_id ON public.vulnerability_user_mentions USING btree (vulnerability_id, note_id);
+
CREATE INDEX index_web_hook_logs_on_created_at_and_web_hook_id ON public.web_hook_logs USING btree (created_at, web_hook_id);
CREATE INDEX index_web_hook_logs_on_web_hook_id ON public.web_hook_logs USING btree (web_hook_id);
@@ -10843,6 +10875,9 @@ ALTER TABLE ONLY public.open_project_tracker_data
ALTER TABLE ONLY public.gpg_signatures
ADD CONSTRAINT fk_rails_19d4f1c6f9 FOREIGN KEY (gpg_key_subkey_id) REFERENCES public.gpg_key_subkeys(id) ON DELETE SET NULL;
+ALTER TABLE ONLY public.vulnerability_user_mentions
+ ADD CONSTRAINT fk_rails_1a41c485cd FOREIGN KEY (vulnerability_id) REFERENCES public.vulnerabilities(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.board_assignees
ADD CONSTRAINT fk_rails_1c0ff59e82 FOREIGN KEY (assignee_id) REFERENCES public.users(id) ON DELETE CASCADE;
@@ -11380,6 +11415,9 @@ ALTER TABLE ONLY public.namespace_root_storage_statistics
ALTER TABLE ONLY public.project_aliases
ADD CONSTRAINT fk_rails_a1804f74a7 FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY public.vulnerability_user_mentions
+ ADD CONSTRAINT fk_rails_a18600f210 FOREIGN KEY (note_id) REFERENCES public.notes(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY public.todos
ADD CONSTRAINT fk_rails_a27c483435 FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
@@ -12742,6 +12780,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20200316162648'),
('20200316173312'),
('20200317142110'),
+('20200318140400'),
('20200318152134'),
('20200318162148'),
('20200318163148'),
@@ -12750,6 +12789,8 @@ INSERT INTO "schema_migrations" (version) VALUES
('20200318175008'),
('20200319123041'),
('20200319203901'),
+('20200320112455'),
+('20200320123839'),
('20200323075043'),
('20200323122201'),
('20200324115359');
diff --git a/doc/administration/high_availability/object_storage.md b/doc/administration/high_availability/object_storage.md
index dc451757a1c..148753657cf 100644
--- a/doc/administration/high_availability/object_storage.md
+++ b/doc/administration/high_availability/object_storage.md
@@ -22,7 +22,7 @@ For configuring GitLab to use Object Storage refer to the following guides:
1. Configure [object storage for container registry](../packages/container_registry.md#container-registry-storage-driver) (optional feature).
1. Configure [object storage for Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage) (optional feature).
1. Configure [object storage for packages](../packages/index.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
-1. Configure [object storage for dependency proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(ULTIMATE ONLY)**
+1. Configure [object storage for dependency proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature). **(PREMIUM ONLY)**
1. Configure [object storage for Pseudonymizer](../pseudonymizer.md#configuration) (optional feature). **(ULTIMATE ONLY)**
NOTE: **Note:**
diff --git a/doc/administration/packages/dependency_proxy.md b/doc/administration/packages/dependency_proxy.md
index b489d829ba7..ff3c24d6162 100644
--- a/doc/administration/packages/dependency_proxy.md
+++ b/doc/administration/packages/dependency_proxy.md
@@ -1,4 +1,4 @@
-# GitLab Dependency Proxy administration **(ULTIMATE ONLY)**
+# GitLab Dependency Proxy administration **(PREMIUM ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11.
diff --git a/doc/development/integrations/secure.md b/doc/development/integrations/secure.md
index 5792ce303e1..e5392af1c2e 100644
--- a/doc/development/integrations/secure.md
+++ b/doc/development/integrations/secure.md
@@ -70,7 +70,7 @@ mysec_dependency_scanning:
`gl-sast-report.json` is an example file path. See [the Output file section](#output-file) for more details.
It is processed as a SAST report because it is declared as such in the job definition.
-### Rules
+### Policies
Scanning jobs should be skipped unless the corresponding feature is listed
in the `GITLAB_FEATURES` variable (comma-separated list of values).
@@ -103,11 +103,9 @@ mysec_dependency_scanning:
$CI_PROJECT_REPOSITORY_LANGUAGES =~ /\bjava\b/
```
-The [`only/except`](../../ci/yaml/README.md#onlyexcept-basic) keywords
-as well as the new [`rules`](../../ci/yaml/README.md#rules) keyword
-make possible to trigger the job depending on the branch, or when some particular file changes.
-Such rules should be defined by users based on their needs,
-and should not be predefined in the job definition of the scanner.
+Any additional job policy should only be configured by users based on their needs.
+For instance, predefined policies should not trigger the scanning job
+for a particular branch or when a particular set of files changes.
## Docker image
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 4245822a8fc..1a1afe7b555 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -170,6 +170,9 @@ back up the volume where the configuration files are stored. If you have created
the GitLab container according to the documentation, it should be under
`/srv/gitlab/config`.
+For [GitLab Helm chart Installations](https://gitlab.com/gitlab-org/charts/gitlab) on a
+Kubernetes cluster, you must follow the [Backup the secrets](https://docs.gitlab.com/charts/backup-restore/backup.html#backup-the-secrets) instructions.
+
You may also want to back up any TLS keys and certificates, and your
[SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
diff --git a/doc/topics/airgap/index.md b/doc/topics/airgap/index.md
index fc92dd5519d..77c01863d47 100644
--- a/doc/topics/airgap/index.md
+++ b/doc/topics/airgap/index.md
@@ -13,3 +13,61 @@ If you plan to deploy a GitLab instance on a physically-isolated and offline net
Follow these best practices to use GitLab's features in an offline environment:
- [Operating the GitLab Secure scanners in an offline environment](../../user/application_security/offline_deployments/index.md).
+
+## Loading Docker images onto your air-gapped host
+
+To use many GitLab features, including
+[security scans](../../user/application_security/index.md#working-in-an-offline-environment)
+and [Auto Devops](../autodevops/), the GitLab Runner must be able to fetch the
+relevant Docker images.
+
+The process for making these images available without direct access to the public internet
+involves downloading the images then packaging and transferring them to the air-gapped host.
+Here's an example of such a transfer:
+
+1. Download Docker images from public internet.
+1. Package Docker images as tar archives.
+1. Transfer images to air-gapped environment.
+1. Load transferred images into air-gapped Docker registry.
+
+### Example image packager script
+
+```sh
+#!/bin/bash
+set -ux
+
+# Specify needed analyzer images
+analyzers=${SAST_ANALYZERS:-"bandit eslint gosec"}
+gitlab=registry.gitlab.com/gitlab-org/security-products/analyzers/
+
+for i in "${analyzers[@]}"
+do
+ tarname="${i}_2.tar"
+ docker pull $gitlab$i:2
+ docker save $gitlab$i:2 -o ./analyzers/${tarname}
+ chmod +r ./analyzers/${tarname}
+done
+```
+
+### Example image loader script
+
+This example loads the images from a bastion host to an air-gapped host. In certain configurations,
+physical media may be needed for such a transfer:
+
+```sh
+#!/bin/bash
+set -ux
+
+# Specify needed analyzer images
+analyzers=${SAST_ANALYZERS:-"bandit eslint gosec"}
+registry=$GITLAB_HOST:4567
+
+for i in "${analyzers[@]}"
+do
+ tarname="${i}_2.tar"
+ scp ./analyzers/${tarname} ${GITLAB_HOST}:~/${tarname}
+ ssh $GITLAB_HOST "sudo docker load -i ${tarname}"
+ ssh $GITLAB_HOST "sudo docker tag $(sudo docker images | grep $i | awk '{print $3}') ${registry}/analyzers/${i}:2"
+ ssh $GITLAB_HOST "sudo docker push ${registry}/analyzers/${i}:2"
+done
+```
diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md
index 7e15df7b433..26a7936f8fa 100644
--- a/doc/user/packages/dependency_proxy/index.md
+++ b/doc/user/packages/dependency_proxy/index.md
@@ -1,4 +1,4 @@
-# Dependency Proxy **(ULTIMATE ONLY)**
+# Dependency Proxy **(PREMIUM ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/7934) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.11.
diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md
index fdc4c6894b9..15772d1303d 100644
--- a/doc/user/packages/nuget_repository/index.md
+++ b/doc/user/packages/nuget_repository/index.md
@@ -178,6 +178,7 @@ CAUTION: **Warning:**
By default, `nuget` checks the official source at `nuget.org` first. If you have a package in the
GitLab NuGet Repository with the same name as a package at `nuget.org`, you must specify the source
name or the wrong package will be installed.
+
Install the latest version of a package using the following command:
```shell
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
index 262c52b2484..b710ad3d0db 100644
--- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
@@ -184,7 +184,7 @@ spotbugs-sast:
variables:
- $GITLAB_FEATURES =~ /\bsast\b/ &&
$SAST_DEFAULT_ANALYZERS =~ /spotbugs/ &&
- $CI_PROJECT_REPOSITORY_LANGUAGES =~ /java\b/
+ $CI_PROJECT_REPOSITORY_LANGUAGES =~ /\b(groovy|java|scala)\b/
tslint-sast:
extends: .sast-analyzer
diff --git a/lib/gitlab/import_export/snippet_repo_restorer.rb b/lib/gitlab/import_export/snippet_repo_restorer.rb
index 079681dfac5..b58ea14a3a8 100644
--- a/lib/gitlab/import_export/snippet_repo_restorer.rb
+++ b/lib/gitlab/import_export/snippet_repo_restorer.rb
@@ -30,7 +30,7 @@ module Gitlab
def create_repository_from_bundle
repository.create_from_bundle(path_to_bundle)
- snippet.track_snippet_repository
+ snippet.track_snippet_repository(repository.storage)
end
def create_repository_from_db
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index cffc43a7dea..9a0a68488bf 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5291,6 +5291,9 @@ msgstr ""
msgid "ContainerRegistry|Build an image"
msgstr ""
+msgid "ContainerRegistry|Compressed Size"
+msgstr ""
+
msgid "ContainerRegistry|Container Registry"
msgstr ""
@@ -5431,6 +5434,9 @@ msgstr ""
msgid "ContainerRegistry|There are no container images stored for this project"
msgstr ""
+msgid "ContainerRegistry|This Registry contains deleted image tag data. Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage."
+msgstr ""
+
msgid "ContainerRegistry|This image has no active tags"
msgstr ""
@@ -8178,6 +8184,9 @@ msgstr ""
msgid "EventFilterBy|Filter by team"
msgstr ""
+msgid "EventFilterBy|Filter by wiki"
+msgstr ""
+
msgid "Events"
msgstr ""
@@ -24705,6 +24714,9 @@ msgstr ""
msgid "vulnerability|dismissed"
msgstr ""
+msgid "wiki page"
+msgstr ""
+
msgid "with %{additions} additions, %{deletions} deletions."
msgstr ""
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index 305419efe96..a280d829d83 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -31,6 +31,8 @@ describe DashboardController do
before do
create(:event, :created, project: project, target: create(:issue))
+ create(:wiki_page_event, :created, project: project)
+ create(:wiki_page_event, :updated, project: project)
sign_in(user)
@@ -45,7 +47,7 @@ describe DashboardController do
it 'returns count' do
get :activity, params: { format: :json }
- expect(json_response['count']).to eq(1)
+ expect(json_response['count']).to eq(3)
end
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 446c1c59030..5bb7853a154 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -16,7 +16,7 @@ FactoryBot.define do
options do
{
- image: 'ruby:2.1',
+ image: 'ruby:2.7',
services: ['postgres'],
script: ['ls -a']
}
@@ -336,7 +336,7 @@ FactoryBot.define do
trait :extended_options do
options do
{
- image: { name: 'ruby:2.1', entrypoint: '/bin/sh' },
+ image: { name: 'ruby:2.7', entrypoint: '/bin/sh' },
services: ['postgres', { name: 'docker:stable-dind', entrypoint: '/bin/sh', command: 'sleep 30', alias: 'docker' }],
script: %w(echo),
after_script: %w(ls date),
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index 3d99a04ea1a..21e1d911f96 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -28,7 +28,7 @@ FactoryBot.define do
bare_repo: TestEnv.factory_repo_path_bare,
refs: TestEnv::BRANCH_SHA)
- snippet.track_snippet_repository
+ snippet.track_snippet_repository(snippet.repository.storage)
end
end
diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js
index 660004e5eea..60e509e4a88 100644
--- a/spec/frontend/registry/explorer/pages/details_spec.js
+++ b/spec/frontend/registry/explorer/pages/details_spec.js
@@ -5,8 +5,15 @@ import stubChildren from 'helpers/stub_children';
import component from '~/registry/explorer/pages/details.vue';
import store from '~/registry/explorer/stores/';
import { SET_MAIN_LOADING } from '~/registry/explorer/stores/mutation_types/';
+import {
+ DELETE_TAG_SUCCESS_MESSAGE,
+ DELETE_TAG_ERROR_MESSAGE,
+ DELETE_TAGS_SUCCESS_MESSAGE,
+ DELETE_TAGS_ERROR_MESSAGE,
+} from '~/registry/explorer/constants';
import { tagsListResponse } from '../mock_data';
import { GlModal } from '../stubs';
+import { $toast } from '../../shared/mocks';
describe('Details Page', () => {
let wrapper;
@@ -40,6 +47,7 @@ describe('Details Page', () => {
id: routeId,
},
},
+ $toast,
},
});
dispatchSpy = jest.spyOn(store, 'dispatch');
@@ -249,13 +257,11 @@ describe('Details Page', () => {
});
});
- it('when only one element is selected', () => {
- const deleteModal = findDeleteModal();
-
- wrapper.setData({ itemsToBeDeleted: [0] });
- deleteModal.vm.$emit('ok');
+ describe('when only one element is selected', () => {
+ it('execute the delete and remove selection', () => {
+ wrapper.setData({ itemsToBeDeleted: [0] });
+ findDeleteModal().vm.$emit('ok');
- return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).toHaveBeenCalledWith('requestDeleteTag', {
tag: store.state.tags[0],
params: wrapper.vm.$route.params.id,
@@ -264,15 +270,33 @@ describe('Details Page', () => {
expect(wrapper.vm.itemsToBeDeleted).toEqual([]);
expect(findCheckedCheckboxes()).toHaveLength(0);
});
+
+ it('show success toast on successful delete', () => {
+ return wrapper.vm.handleSingleDelete(0).then(() => {
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAG_SUCCESS_MESSAGE, {
+ type: 'success',
+ });
+ });
+ });
+
+ it('show error toast on erred delete', () => {
+ dispatchSpy.mockRejectedValue();
+ return wrapper.vm.handleSingleDelete(0).then(() => {
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAG_ERROR_MESSAGE, {
+ type: 'error',
+ });
+ });
+ });
});
- it('when multiple elements are selected', () => {
- const deleteModal = findDeleteModal();
+ describe('when multiple elements are selected', () => {
+ beforeEach(() => {
+ wrapper.setData({ itemsToBeDeleted: [0, 1] });
+ });
- wrapper.setData({ itemsToBeDeleted: [0, 1] });
- deleteModal.vm.$emit('ok');
+ it('execute the delete and remove selection', () => {
+ findDeleteModal().vm.$emit('ok');
- return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).toHaveBeenCalledWith('requestDeleteTags', {
ids: store.state.tags.map(t => t.name),
params: wrapper.vm.$route.params.id,
@@ -281,6 +305,23 @@ describe('Details Page', () => {
expect(wrapper.vm.itemsToBeDeleted).toEqual([]);
expect(findCheckedCheckboxes()).toHaveLength(0);
});
+
+ it('show success toast on successful delete', () => {
+ return wrapper.vm.handleMultipleDelete(0).then(() => {
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAGS_SUCCESS_MESSAGE, {
+ type: 'success',
+ });
+ });
+ });
+
+ it('show error toast on erred delete', () => {
+ dispatchSpy.mockRejectedValue();
+ return wrapper.vm.handleMultipleDelete(0).then(() => {
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAGS_ERROR_MESSAGE, {
+ type: 'error',
+ });
+ });
+ });
});
});
diff --git a/spec/frontend/registry/explorer/pages/index_spec.js b/spec/frontend/registry/explorer/pages/index_spec.js
new file mode 100644
index 00000000000..f52e7d67866
--- /dev/null
+++ b/spec/frontend/registry/explorer/pages/index_spec.js
@@ -0,0 +1,62 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
+import component from '~/registry/explorer/pages/index.vue';
+import store from '~/registry/explorer/stores/';
+
+describe('List Page', () => {
+ let wrapper;
+ let dispatchSpy;
+
+ const findRouterView = () => wrapper.find({ ref: 'router-view' });
+ const findAlert = () => wrapper.find(GlAlert);
+ const findLink = () => wrapper.find(GlLink);
+
+ const mountComponent = () => {
+ wrapper = shallowMount(component, {
+ store,
+ stubs: {
+ RouterView: true,
+ GlSprintf,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ dispatchSpy = jest.spyOn(store, 'dispatch');
+ mountComponent();
+ });
+
+ it('has a router view', () => {
+ expect(findRouterView().exists()).toBe(true);
+ });
+
+ describe('garbageCollectionTip alert', () => {
+ beforeEach(() => {
+ store.dispatch('setInitialState', { isAdmin: true, garbageCollectionHelpPagePath: 'foo' });
+ store.dispatch('setShowGarbageCollectionTip', true);
+ });
+
+ afterEach(() => {
+ store.dispatch('setInitialState', {});
+ store.dispatch('setShowGarbageCollectionTip', false);
+ });
+
+ it('is visible when the user is an admin and the user performed a delete action', () => {
+ expect(findAlert().exists()).toBe(true);
+ });
+
+ it('on dismiss disappears ', () => {
+ findAlert().vm.$emit('dismiss');
+ expect(dispatchSpy).toHaveBeenCalledWith('setShowGarbageCollectionTip', false);
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+
+ it('contains a link to the docs', () => {
+ const link = findLink();
+ expect(link.exists()).toBe(true);
+ expect(link.attributes('href')).toBe(store.state.config.garbageCollectionHelpPagePath);
+ });
+ });
+});
diff --git a/spec/frontend/registry/explorer/pages/list_spec.js b/spec/frontend/registry/explorer/pages/list_spec.js
index 5b713778495..3e46a29f776 100644
--- a/spec/frontend/registry/explorer/pages/list_spec.js
+++ b/spec/frontend/registry/explorer/pages/list_spec.js
@@ -8,8 +8,13 @@ import GroupEmptyState from '~/registry/explorer/components/group_empty_state.vu
import ProjectEmptyState from '~/registry/explorer/components/project_empty_state.vue';
import store from '~/registry/explorer/stores/';
import { SET_MAIN_LOADING } from '~/registry/explorer/stores/mutation_types/';
+import {
+ DELETE_IMAGE_SUCCESS_MESSAGE,
+ DELETE_IMAGE_ERROR_MESSAGE,
+} from '~/registry/explorer/constants';
import { imagesListResponse } from '../mock_data';
import { GlModal, GlEmptyState } from '../stubs';
+import { $toast } from '../../shared/mocks';
const localVue = createLocalVue();
localVue.use(VueRouter);
@@ -40,6 +45,9 @@ describe('List Page', () => {
GlEmptyState,
GlSprintf,
},
+ mocks: {
+ $toast,
+ },
});
dispatchSpy = jest.spyOn(store, 'dispatch');
store.dispatch('receiveImagesListSuccess', imagesListResponse);
@@ -174,11 +182,29 @@ describe('List Page', () => {
const itemToDelete = wrapper.vm.images[0];
wrapper.setData({ itemToDelete });
findDeleteModal().vm.$emit('ok');
- return wrapper.vm.$nextTick().then(() => {
- expect(store.dispatch).toHaveBeenCalledWith(
- 'requestDeleteImage',
- itemToDelete.destroy_path,
- );
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'requestDeleteImage',
+ itemToDelete.destroy_path,
+ );
+ });
+
+ it('should show a success toast when delete request is successful', () => {
+ dispatchSpy.mockResolvedValue();
+ return wrapper.vm.handleDeleteImage().then(() => {
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_IMAGE_SUCCESS_MESSAGE, {
+ type: 'success',
+ });
+ expect(wrapper.vm.itemToDelete).toEqual({});
+ });
+ });
+
+ it('should show a error toast when delete request fails', () => {
+ dispatchSpy.mockRejectedValue();
+ return wrapper.vm.handleDeleteImage().then(() => {
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_IMAGE_ERROR_MESSAGE, {
+ type: 'error',
+ });
+ expect(wrapper.vm.itemToDelete).toEqual({});
});
});
});
@@ -227,7 +253,7 @@ describe('List Page', () => {
beforeEach(() => {
jest.spyOn(Tracking, 'event');
- dispatchSpy.mockReturnValue();
+ dispatchSpy.mockResolvedValue();
});
it('send an event when delete button is clicked', () => {
@@ -235,13 +261,14 @@ describe('List Page', () => {
deleteBtn.vm.$emit('click');
testTrackingCall('click_button');
});
+
it('send an event when cancel is pressed on modal', () => {
const deleteModal = findDeleteModal();
deleteModal.vm.$emit('cancel');
testTrackingCall('cancel_delete');
});
+
it('send an event when confirm is clicked on modal', () => {
- dispatchSpy.mockReturnValue();
const deleteModal = findDeleteModal();
deleteModal.vm.$emit('ok');
testTrackingCall('confirm_delete');
diff --git a/spec/frontend/registry/explorer/stores/actions_spec.js b/spec/frontend/registry/explorer/stores/actions_spec.js
index 3e22621058e..b39c79dd1ab 100644
--- a/spec/frontend/registry/explorer/stores/actions_spec.js
+++ b/spec/frontend/registry/explorer/stores/actions_spec.js
@@ -38,6 +38,17 @@ describe('Actions RegistryExplorer Store', () => {
);
});
+ it('setShowGarbageCollectionTip', done => {
+ testAction(
+ actions.setShowGarbageCollectionTip,
+ true,
+ null,
+ [{ type: types.SET_SHOW_GARBAGE_COLLECTION_TIP, payload: true }],
+ [],
+ done,
+ );
+ });
+
describe('receives api responses', () => {
const response = {
data: [1, 2, 3],
@@ -183,18 +194,19 @@ describe('Actions RegistryExplorer Store', () => {
[{ type: types.SET_MAIN_LOADING, payload: true }],
[
{
+ type: 'setShowGarbageCollectionTip',
+ payload: true,
+ },
+ {
type: 'requestTagsList',
payload: { pagination: {}, params },
},
],
- () => {
- expect(createFlash).toHaveBeenCalled();
- done();
- },
+ done,
);
});
- it('should show flash message on error', done => {
+ it('should turn off loading on error', done => {
testAction(
actions.requestDeleteTag,
{
@@ -208,10 +220,7 @@ describe('Actions RegistryExplorer Store', () => {
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
- () => {
- expect(createFlash).toHaveBeenCalled();
- done();
- },
+ done,
);
});
});
@@ -235,18 +244,19 @@ describe('Actions RegistryExplorer Store', () => {
[{ type: types.SET_MAIN_LOADING, payload: true }],
[
{
+ type: 'setShowGarbageCollectionTip',
+ payload: true,
+ },
+ {
type: 'requestTagsList',
payload: { pagination: {}, params },
},
],
- () => {
- expect(createFlash).toHaveBeenCalled();
- done();
- },
+ done,
);
});
- it('should show flash message on error', done => {
+ it('should turn off loading on error', done => {
mock.onDelete(url).replyOnce(500);
testAction(
@@ -263,17 +273,14 @@ describe('Actions RegistryExplorer Store', () => {
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
- () => {
- expect(createFlash).toHaveBeenCalled();
- done();
- },
+ done,
);
});
});
describe('request delete single image', () => {
+ const deletePath = 'delete/path';
it('successfully performs the delete request', done => {
- const deletePath = 'delete/path';
mock.onDelete(deletePath).replyOnce(200);
testAction(
@@ -288,32 +295,32 @@ describe('Actions RegistryExplorer Store', () => {
],
[
{
+ type: 'setShowGarbageCollectionTip',
+ payload: true,
+ },
+ {
type: 'requestImagesList',
payload: { pagination: {} },
},
],
- () => {
- expect(createFlash).toHaveBeenCalled();
- done();
- },
+ done,
);
});
- it('should show flash message on error', done => {
+ it('should turn off loading on error', done => {
+ mock.onDelete(deletePath).replyOnce(400);
testAction(
actions.requestDeleteImage,
- null,
+ deletePath,
{},
[
{ type: types.SET_MAIN_LOADING, payload: true },
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
- () => {
- expect(createFlash).toHaveBeenCalled();
- done();
- },
- );
+ ).catch(() => {
+ done();
+ });
});
});
});
diff --git a/spec/frontend/registry/explorer/stores/getters_spec.js b/spec/frontend/registry/explorer/stores/getters_spec.js
index 211b8169d82..cd053ea8edc 100644
--- a/spec/frontend/registry/explorer/stores/getters_spec.js
+++ b/spec/frontend/registry/explorer/stores/getters_spec.js
@@ -49,4 +49,22 @@ describe('Getters RegistryExplorer store', () => {
expect(getters[getter](state)).toBe(expectedPieces.join(' '));
});
});
+
+ describe('showGarbageCollection', () => {
+ it.each`
+ result | showGarbageCollectionTip | isAdmin
+ ${true} | ${true} | ${true}
+ ${false} | ${true} | ${false}
+ ${false} | ${false} | ${true}
+ `(
+ 'return $result when showGarbageCollectionTip $showGarbageCollectionTip and isAdmin is $isAdmin',
+ ({ result, showGarbageCollectionTip, isAdmin }) => {
+ state = {
+ config: { isAdmin },
+ showGarbageCollectionTip,
+ };
+ expect(getters.showGarbageCollection(state)).toBe(result);
+ },
+ );
+ });
});
diff --git a/spec/frontend/registry/explorer/stores/mutations_spec.js b/spec/frontend/registry/explorer/stores/mutations_spec.js
index 1d5055c02d2..029fd23f7ce 100644
--- a/spec/frontend/registry/explorer/stores/mutations_spec.js
+++ b/spec/frontend/registry/explorer/stores/mutations_spec.js
@@ -10,7 +10,12 @@ describe('Mutations Registry Explorer Store', () => {
describe('SET_INITIAL_STATE', () => {
it('should set the initial state', () => {
- const payload = { endpoint: 'foo', isGroupPage: true, expirationPolicy: { foo: 'bar' } };
+ const payload = {
+ endpoint: 'foo',
+ isGroupPage: true,
+ expirationPolicy: { foo: 'bar' },
+ isAdmin: true,
+ };
const expectedState = { ...mockState, config: payload };
mutations[types.SET_INITIAL_STATE](mockState, {
...payload,
@@ -50,6 +55,15 @@ describe('Mutations Registry Explorer Store', () => {
});
});
+ describe('SET_SHOW_GARBAGE_COLLECTION_TIP', () => {
+ it('should set the showGarbageCollectionTip', () => {
+ const expectedState = { ...mockState, showGarbageCollectionTip: true };
+ mutations[types.SET_SHOW_GARBAGE_COLLECTION_TIP](mockState, true);
+
+ expect(mockState).toEqual(expectedState);
+ });
+ });
+
describe('SET_PAGINATION', () => {
const generatePagination = () => [
{
diff --git a/spec/frontend/registry/shared/mocks.js b/spec/frontend/registry/shared/mocks.js
new file mode 100644
index 00000000000..e33d06e7499
--- /dev/null
+++ b/spec/frontend/registry/shared/mocks.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line import/prefer-default-export
+export const $toast = {
+ show: jest.fn(),
+};
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index 61229127770..0109525bcac 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -88,6 +88,85 @@ describe EventsHelper do
end
end
+ describe '#event_preposition' do
+ context 'for wiki page events' do
+ let(:event) { create(:wiki_page_event) }
+
+ it 'returns a suitable phrase' do
+ expect(helper.event_preposition(event)).to eq('in the wiki for')
+ end
+ end
+
+ context 'for push action events' do
+ let(:event) { create(:push_event) }
+
+ it 'returns a suitable phrase' do
+ expect(helper.event_preposition(event)).to eq('at')
+ end
+ end
+
+ context 'for commented actions' do
+ let(:event) { create(:event, :commented) }
+
+ it 'returns a suitable phrase' do
+ expect(helper.event_preposition(event)).to eq('at')
+ end
+ end
+
+ context 'for any event with a target' do
+ let(:event) { create(:event, target: create(:issue)) }
+
+ it 'returns a suitable phrase' do
+ expect(helper.event_preposition(event)).to eq('at')
+ end
+ end
+
+ context 'for milestone events' do
+ let(:event) { create(:event, target: create(:milestone)) }
+
+ it 'returns a suitable phrase' do
+ expect(helper.event_preposition(event)).to eq('in')
+ end
+ end
+
+ context 'for non-matching events' do
+ let(:event) { create(:event, :created) }
+
+ it 'returns no preposition' do
+ expect(helper.event_preposition(event)).to be_nil
+ end
+ end
+ end
+
+ describe 'event_wiki_page_target_url' do
+ let(:project) { create(:project) }
+ let(:wiki_page) { create(:wiki_page, wiki: create(:project_wiki, project: project)) }
+ let(:event) { create(:wiki_page_event, project: project, wiki_page: wiki_page) }
+
+ it 'links to the wiki page' do
+ url = helper.project_wiki_url(project, wiki_page.slug)
+
+ expect(helper.event_wiki_page_target_url(event)).to eq(url)
+ end
+ end
+
+ describe '#event_wiki_title_html' do
+ let(:event) { create(:wiki_page_event) }
+
+ it 'produces a suitable title chunk' do
+ url = helper.event_wiki_page_target_url(event)
+ title = event.target_title
+ html = [
+ "<span class=\"event-target-type append-right-4\">wiki page</span>",
+ "<a title=\"#{title}\" class=\"has-tooltip event-target-link append-right-4\" href=\"#{url}\">",
+ title,
+ "</a>"
+ ].join
+
+ expect(helper.event_wiki_title_html(event)).to eq(html)
+ end
+ end
+
describe '#event_note_target_url' do
let(:project) { create(:project, :public, :repository) }
let(:event) { create(:event, project: project) }
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
index 04bab9c58b8..4f7cfc9783a 100644
--- a/spec/lib/gitlab/ci/build/image_spec.rb
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Ci::Build::Image do
subject { described_class.from_image(job) }
context 'when image is defined in job' do
- let(:image_name) { 'ruby:2.1' }
+ let(:image_name) { 'ruby:2.7' }
let(:job) { create(:ci_build, options: { image: image_name } ) }
context 'when image is defined as string' do
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index 8de2e5de724..de3e887a6ed 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -6,11 +6,11 @@ describe Gitlab::Ci::Config::Entry::Image do
let(:entry) { described_class.new(config) }
context 'when configuration is a string' do
- let(:config) { 'ruby:2.2' }
+ let(:config) { 'ruby:2.7' }
describe '#value' do
it 'returns image hash' do
- expect(entry.value).to eq({ name: 'ruby:2.2' })
+ expect(entry.value).to eq({ name: 'ruby:2.7' })
end
end
@@ -28,7 +28,7 @@ describe Gitlab::Ci::Config::Entry::Image do
describe '#image' do
it "returns image's name" do
- expect(entry.name).to eq 'ruby:2.2'
+ expect(entry.name).to eq 'ruby:2.7'
end
end
@@ -46,7 +46,7 @@ describe Gitlab::Ci::Config::Entry::Image do
end
context 'when configuration is a hash' do
- let(:config) { { name: 'ruby:2.2', entrypoint: %w(/bin/sh run) } }
+ let(:config) { { name: 'ruby:2.7', entrypoint: %w(/bin/sh run) } }
describe '#value' do
it 'returns image hash' do
@@ -68,7 +68,7 @@ describe Gitlab::Ci::Config::Entry::Image do
describe '#image' do
it "returns image's name" do
- expect(entry.name).to eq 'ruby:2.2'
+ expect(entry.name).to eq 'ruby:2.7'
end
end
@@ -80,7 +80,7 @@ describe Gitlab::Ci::Config::Entry::Image do
context 'when configuration has ports' do
let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
- let(:config) { { name: 'ruby:2.2', entrypoint: %w(/bin/sh run), ports: ports } }
+ let(:config) { { name: 'ruby:2.7', entrypoint: %w(/bin/sh run), ports: ports } }
let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
let(:image_ports) { false }
@@ -112,7 +112,7 @@ describe Gitlab::Ci::Config::Entry::Image do
end
context 'when entry value is not correct' do
- let(:config) { ['ruby:2.2'] }
+ let(:config) { ['ruby:2.7'] }
describe '#errors' do
it 'saves errors' do
@@ -129,7 +129,7 @@ describe Gitlab::Ci::Config::Entry::Image do
end
context 'when unexpected key is specified' do
- let(:config) { { name: 'ruby:2.2', non_existing: 'test' } }
+ let(:config) { { name: 'ruby:2.7', non_existing: 'test' } }
describe '#errors' do
it 'saves errors' do
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index c3871b6b3cf..ba2dbf72fba 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -29,7 +29,7 @@ describe Gitlab::Ci::Config::Entry::Root do
let(:hash) do
{
before_script: %w(ls pwd),
- image: 'ruby:2.2',
+ image: 'ruby:2.7',
default: {},
services: ['postgres:9.1', 'mysql:5.5'],
variables: { VAR: 'root' },
@@ -124,7 +124,7 @@ describe Gitlab::Ci::Config::Entry::Root do
{ name: :rspec,
script: %w[rspec ls],
before_script: %w(ls pwd),
- image: { name: 'ruby:2.2' },
+ image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' },
@@ -138,7 +138,7 @@ describe Gitlab::Ci::Config::Entry::Root do
{ name: :spinach,
before_script: [],
script: %w[spinach],
- image: { name: 'ruby:2.2' },
+ image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' },
@@ -154,7 +154,7 @@ describe Gitlab::Ci::Config::Entry::Root do
before_script: [],
script: ["make changelog | tee release_changelog.txt"],
release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" },
- image: { name: "ruby:2.2" },
+ image: { name: "ruby:2.7" },
services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push" },
only: { refs: %w(branches tags) },
@@ -173,7 +173,7 @@ describe Gitlab::Ci::Config::Entry::Root do
{ before_script: %w(ls pwd),
after_script: ['make clean'],
default: {
- image: 'ruby:2.1',
+ image: 'ruby:2.7',
services: ['postgres:9.1', 'mysql:5.5']
},
variables: { VAR: 'root' },
@@ -200,7 +200,7 @@ describe Gitlab::Ci::Config::Entry::Root do
rspec: { name: :rspec,
script: %w[rspec ls],
before_script: %w(ls pwd),
- image: { name: 'ruby:2.1' },
+ image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: "pull-push" },
@@ -212,7 +212,7 @@ describe Gitlab::Ci::Config::Entry::Root do
spinach: { name: :spinach,
before_script: [],
script: %w[spinach],
- image: { name: 'ruby:2.1' },
+ image: { name: 'ruby:2.7' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: "pull-push" },
diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
index b2924ae9d91..4d8f0dbc861 100644
--- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -71,7 +71,7 @@ describe Gitlab::Ci::Config::External::File::Project do
let(:root_ref_sha) { project.repository.root_ref_sha }
before do
- stub_project_blob(root_ref_sha, '/file.yml') { 'image: ruby:2.1' }
+ stub_project_blob(root_ref_sha, '/file.yml') { 'image: ruby:2.7' }
end
it 'returns true' do
@@ -96,7 +96,7 @@ describe Gitlab::Ci::Config::External::File::Project do
let(:ref_sha) { project.commit('master').sha }
before do
- stub_project_blob(ref_sha, '/file.yml') { 'image: ruby:2.1' }
+ stub_project_blob(ref_sha, '/file.yml') { 'image: ruby:2.7' }
end
it 'returns true' do
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 6839002c3ab..fa358f36527 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::Ci::Config::External::Mapper do
let(:file_content) do
<<~HEREDOC
- image: 'ruby:2.2'
+ image: 'ruby:2.7'
HEREDOC
end
@@ -34,7 +34,7 @@ describe Gitlab::Ci::Config::External::Mapper do
context 'when the string is a local file' do
let(:values) do
{ include: local_file,
- image: 'ruby:2.2' }
+ image: 'ruby:2.7' }
end
it 'returns File instances' do
@@ -46,7 +46,7 @@ describe Gitlab::Ci::Config::External::Mapper do
context 'when the key is a local file hash' do
let(:values) do
{ include: { 'local' => local_file },
- image: 'ruby:2.2' }
+ image: 'ruby:2.7' }
end
it 'returns File instances' do
@@ -57,7 +57,7 @@ describe Gitlab::Ci::Config::External::Mapper do
context 'when the string is a remote file' do
let(:values) do
- { include: remote_url, image: 'ruby:2.2' }
+ { include: remote_url, image: 'ruby:2.7' }
end
it 'returns File instances' do
@@ -69,7 +69,7 @@ describe Gitlab::Ci::Config::External::Mapper do
context 'when the key is a remote file hash' do
let(:values) do
{ include: { 'remote' => remote_url },
- image: 'ruby:2.2' }
+ image: 'ruby:2.7' }
end
it 'returns File instances' do
@@ -81,7 +81,7 @@ describe Gitlab::Ci::Config::External::Mapper do
context 'when the key is a template file hash' do
let(:values) do
{ include: { 'template' => template_file },
- image: 'ruby:2.2' }
+ image: 'ruby:2.7' }
end
it 'returns File instances' do
@@ -93,7 +93,7 @@ describe Gitlab::Ci::Config::External::Mapper do
context 'when the key is a hash of file and remote' do
let(:values) do
{ include: { 'local' => local_file, 'remote' => remote_url },
- image: 'ruby:2.2' }
+ image: 'ruby:2.7' }
end
it 'returns ambigious specification error' do
@@ -105,7 +105,7 @@ describe Gitlab::Ci::Config::External::Mapper do
context "when 'include' is defined as an array" do
let(:values) do
{ include: [remote_url, local_file],
- image: 'ruby:2.2' }
+ image: 'ruby:2.7' }
end
it 'returns Files instances' do
@@ -117,7 +117,7 @@ describe Gitlab::Ci::Config::External::Mapper do
context "when 'include' is defined as an array of hashes" do
let(:values) do
{ include: [{ remote: remote_url }, { local: local_file }],
- image: 'ruby:2.2' }
+ image: 'ruby:2.7' }
end
it 'returns Files instances' do
@@ -128,7 +128,7 @@ describe Gitlab::Ci::Config::External::Mapper do
context 'when it has ambigious match' do
let(:values) do
{ include: [{ remote: remote_url, local: local_file }],
- image: 'ruby:2.2' }
+ image: 'ruby:2.7' }
end
it 'returns ambigious specification error' do
@@ -140,7 +140,7 @@ describe Gitlab::Ci::Config::External::Mapper do
context "when 'include' is not defined" do
let(:values) do
{
- image: 'ruby:2.2'
+ image: 'ruby:2.7'
}
end
@@ -155,7 +155,7 @@ describe Gitlab::Ci::Config::External::Mapper do
{ 'local' => local_file },
{ 'local' => local_file }
],
- image: 'ruby:2.2' }
+ image: 'ruby:2.7' }
end
it 'raises an exception' do
@@ -169,7 +169,7 @@ describe Gitlab::Ci::Config::External::Mapper do
{ 'local' => local_file },
{ 'remote' => remote_url }
],
- image: 'ruby:2.2' }
+ image: 'ruby:2.7' }
end
before do
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index ff08d4703fe..45f646660a7 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -24,7 +24,7 @@ describe Gitlab::Ci::Config::External::Processor do
subject { processor.perform }
context 'when no external files defined' do
- let(:values) { { image: 'ruby:2.2' } }
+ let(:values) { { image: 'ruby:2.7' } }
it 'returns the same values' do
expect(processor.perform).to eq(values)
@@ -32,7 +32,7 @@ describe Gitlab::Ci::Config::External::Processor do
end
context 'when an invalid local file is defined' do
- let(:values) { { include: '/lib/gitlab/ci/templates/non-existent-file.yml', image: 'ruby:2.2' } }
+ let(:values) { { include: '/lib/gitlab/ci/templates/non-existent-file.yml', image: 'ruby:2.7' } }
it 'raises an error' do
expect { processor.perform }.to raise_error(
@@ -44,7 +44,7 @@ describe Gitlab::Ci::Config::External::Processor do
context 'when an invalid remote file is defined' do
let(:remote_file) { 'http://doesntexist.com/.gitlab-ci-1.yml' }
- let(:values) { { include: remote_file, image: 'ruby:2.2' } }
+ let(:values) { { include: remote_file, image: 'ruby:2.7' } }
before do
stub_full_request(remote_file).and_raise(SocketError.new('Some HTTP error'))
@@ -60,7 +60,7 @@ describe Gitlab::Ci::Config::External::Processor do
context 'with a valid remote external file is defined' do
let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
- let(:values) { { include: remote_file, image: 'ruby:2.2' } }
+ let(:values) { { include: remote_file, image: 'ruby:2.7' } }
let(:external_file_content) do
<<-HEREDOC
before_script:
@@ -94,7 +94,7 @@ describe Gitlab::Ci::Config::External::Processor do
end
context 'with a valid local external file is defined' do
- let(:values) { { include: '/lib/gitlab/ci/templates/template.yml', image: 'ruby:2.2' } }
+ let(:values) { { include: '/lib/gitlab/ci/templates/template.yml', image: 'ruby:2.7' } }
let(:local_file_content) do
<<-HEREDOC
before_script:
@@ -131,7 +131,7 @@ describe Gitlab::Ci::Config::External::Processor do
let(:values) do
{
include: external_files,
- image: 'ruby:2.2'
+ image: 'ruby:2.7'
}
end
@@ -163,7 +163,7 @@ describe Gitlab::Ci::Config::External::Processor do
end
context 'when external files are defined but not valid' do
- let(:values) { { include: '/lib/gitlab/ci/templates/template.yml', image: 'ruby:2.2' } }
+ let(:values) { { include: '/lib/gitlab/ci/templates/template.yml', image: 'ruby:2.7' } }
let(:local_file_content) { 'invalid content file ////' }
@@ -185,7 +185,7 @@ describe Gitlab::Ci::Config::External::Processor do
let(:values) do
{
include: remote_file,
- image: 'ruby:2.2'
+ image: 'ruby:2.7'
}
end
@@ -198,7 +198,7 @@ describe Gitlab::Ci::Config::External::Processor do
it 'takes precedence' do
stub_full_request(remote_file).to_return(body: remote_file_content)
- expect(processor.perform[:image]).to eq('ruby:2.2')
+ expect(processor.perform[:image]).to eq('ruby:2.7')
end
end
@@ -208,7 +208,7 @@ describe Gitlab::Ci::Config::External::Processor do
include: [
{ local: '/local/file.yml' }
],
- image: 'ruby:2.2'
+ image: 'ruby:2.7'
}
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index d8101e8a621..3b65dbe11ec 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::Ci::Config do
context 'when config is valid' do
let(:yml) do
<<-EOS
- image: ruby:2.2
+ image: ruby:2.7
rspec:
script:
@@ -32,7 +32,7 @@ describe Gitlab::Ci::Config do
describe '#to_hash' do
it 'returns hash created from string' do
hash = {
- image: 'ruby:2.2',
+ image: 'ruby:2.7',
rspec: {
script: ['gem install rspec',
'rspec']
@@ -85,7 +85,7 @@ describe Gitlab::Ci::Config do
context 'when using extendable hash' do
let(:yml) do
<<-EOS
- image: ruby:2.2
+ image: ruby:2.7
rspec:
script: rspec
@@ -98,7 +98,7 @@ describe Gitlab::Ci::Config do
it 'correctly extends the hash' do
hash = {
- image: 'ruby:2.2',
+ image: 'ruby:2.7',
rspec: { script: 'rspec' },
test: {
extends: 'rspec',
@@ -188,7 +188,7 @@ describe Gitlab::Ci::Config do
let(:yml) do
<<-EOS
image:
- name: ruby:2.2
+ name: ruby:2.7
ports:
- 80
EOS
@@ -202,12 +202,12 @@ describe Gitlab::Ci::Config do
context 'in the job image' do
let(:yml) do
<<-EOS
- image: ruby:2.2
+ image: ruby:2.7
test:
script: rspec
image:
- name: ruby:2.2
+ name: ruby:2.7
ports:
- 80
EOS
@@ -221,11 +221,11 @@ describe Gitlab::Ci::Config do
context 'in the services' do
let(:yml) do
<<-EOS
- image: ruby:2.2
+ image: ruby:2.7
test:
script: rspec
- image: ruby:2.2
+ image: ruby:2.7
services:
- name: test
alias: test
@@ -266,7 +266,7 @@ describe Gitlab::Ci::Config do
- #{local_location}
- #{remote_location}
- image: ruby:2.2
+ image: ruby:2.7
HEREDOC
end
@@ -296,7 +296,7 @@ describe Gitlab::Ci::Config do
}
composed_hash = {
before_script: before_script_values,
- image: "ruby:2.2",
+ image: "ruby:2.7",
rspec: { script: ["bundle exec rspec"] },
variables: variables
}
@@ -381,7 +381,7 @@ describe Gitlab::Ci::Config do
include:
- #{remote_location}
- image: ruby:2.2
+ image: ruby:2.7
HEREDOC
end
@@ -392,7 +392,7 @@ describe Gitlab::Ci::Config do
end
it 'takes precedence' do
- expect(config.to_hash).to eq({ image: 'ruby:2.2' })
+ expect(config.to_hash).to eq({ image: 'ruby:2.7' })
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index af0a85f6c4e..62adba4319e 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -665,7 +665,7 @@ module Gitlab
describe "Image and service handling" do
context "when extended docker configuration is used" do
it "returns image and service when defined" do
- config = YAML.dump({ image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
+ config = YAML.dump({ image: { name: "ruby:2.7", entrypoint: ["/usr/local/bin/init", "run"] },
services: ["mysql", { name: "docker:dind", alias: "docker",
entrypoint: ["/usr/local/bin/init", "run"],
command: ["/usr/local/bin/init", "run"] }],
@@ -683,7 +683,7 @@ module Gitlab
options: {
before_script: ["pwd"],
script: ["rspec"],
- image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
+ image: { name: "ruby:2.7", entrypoint: ["/usr/local/bin/init", "run"] },
services: [{ name: "mysql" },
{ name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"],
command: ["/usr/local/bin/init", "run"] }]
@@ -696,7 +696,7 @@ module Gitlab
end
it "returns image and service when overridden for job" do
- config = YAML.dump({ image: "ruby:2.1",
+ config = YAML.dump({ image: "ruby:2.7",
services: ["mysql"],
before_script: ["pwd"],
rspec: { image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
@@ -731,7 +731,7 @@ module Gitlab
context "when etended docker configuration is not used" do
it "returns image and service when defined" do
- config = YAML.dump({ image: "ruby:2.1",
+ config = YAML.dump({ image: "ruby:2.7",
services: ["mysql", "docker:dind"],
before_script: ["pwd"],
rspec: { script: "rspec" } })
@@ -747,7 +747,7 @@ module Gitlab
options: {
before_script: ["pwd"],
script: ["rspec"],
- image: { name: "ruby:2.1" },
+ image: { name: "ruby:2.7" },
services: [{ name: "mysql" }, { name: "docker:dind" }]
},
allow_failure: false,
@@ -758,7 +758,7 @@ module Gitlab
end
it "returns image and service when overridden for job" do
- config = YAML.dump({ image: "ruby:2.1",
+ config = YAML.dump({ image: "ruby:2.7",
services: ["mysql"],
before_script: ["pwd"],
rspec: { image: "ruby:2.5", services: ["postgresql", "docker:dind"], script: "rspec" } })
@@ -1292,7 +1292,7 @@ module Gitlab
describe "Artifacts" do
it "returns artifacts when defined" do
config = YAML.dump({
- image: "ruby:2.1",
+ image: "ruby:2.7",
services: ["mysql"],
before_script: ["pwd"],
rspec: {
@@ -1318,7 +1318,7 @@ module Gitlab
options: {
before_script: ["pwd"],
script: ["rspec"],
- image: { name: "ruby:2.1" },
+ image: { name: "ruby:2.7" },
services: [{ name: "mysql" }],
artifacts: {
name: "custom_name",
@@ -1945,7 +1945,7 @@ module Gitlab
context 'when hidden job have a script definition' do
let(:config) do
YAML.dump({
- '.hidden_job' => { image: 'ruby:2.1', script: 'test' },
+ '.hidden_job' => { image: 'ruby:2.7', script: 'test' },
'normal_job' => { script: 'test' }
})
end
@@ -1956,7 +1956,7 @@ module Gitlab
context "when hidden job doesn't have a script definition" do
let(:config) do
YAML.dump({
- '.hidden_job' => { image: 'ruby:2.1' },
+ '.hidden_job' => { image: 'ruby:2.7' },
'normal_job' => { script: 'test' }
})
end
diff --git a/spec/lib/gitlab/config/loader/yaml_spec.rb b/spec/lib/gitlab/config/loader/yaml_spec.rb
index 28039e99916..a52c1c362e1 100644
--- a/spec/lib/gitlab/config/loader/yaml_spec.rb
+++ b/spec/lib/gitlab/config/loader/yaml_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Config::Loader::Yaml do
let(:loader) { described_class.new(yml) }
context 'when yaml syntax is correct' do
- let(:yml) { 'image: ruby:2.2' }
+ let(:yml) { 'image: ruby:2.7' }
describe '#valid?' do
it 'returns true' do
@@ -16,7 +16,7 @@ describe Gitlab::Config::Loader::Yaml do
describe '#load!' do
it 'returns a valid hash' do
- expect(loader.load!).to eq(image: 'ruby:2.2')
+ expect(loader.load!).to eq(image: 'ruby:2.7')
end
end
end
diff --git a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
index d72d41ddf38..3ce950d6a64 100644
--- a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
@@ -55,9 +55,11 @@ describe Gitlab::ImportExport::SnippetRepoRestorer do
let(:snippet_bundle_path) { File.join(bundle_path, "#{snippet_with_repo.hexdigest}.bundle") }
let(:result) { exporter.save }
- it 'creates the repository from the bundle' do
+ before do
expect(exporter.save).to be_truthy
+ end
+ it 'creates the repository from the bundle' do
expect(snippet.repository_exists?).to be_falsey
expect(snippet.snippet_repository).to be_nil
expect(snippet.repository).to receive(:create_from_bundle).and_call_original
@@ -66,5 +68,14 @@ describe Gitlab::ImportExport::SnippetRepoRestorer do
expect(snippet.repository_exists?).to be_truthy
expect(snippet.snippet_repository).not_to be_nil
end
+
+ it 'sets same shard in snippet repository as in the repository storage' do
+ expect(snippet).to receive(:repository_storage).and_return('picked')
+ expect(snippet.repository).to receive(:create_from_bundle)
+
+ restorer.restore
+
+ expect(snippet.snippet_repository.shard_name).to eq 'picked'
+ end
end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 8c7969af177..88c26c92417 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1882,7 +1882,7 @@ describe Ci::Build do
describe '#options' do
let(:options) do
{
- image: "ruby:2.1",
+ image: "ruby:2.7",
services: ["postgres"],
script: ["ls -a"]
}
@@ -1893,11 +1893,11 @@ describe Ci::Build do
end
it 'allows to access with keys' do
- expect(build.options[:image]).to eq('ruby:2.1')
+ expect(build.options[:image]).to eq('ruby:2.7')
end
it 'allows to access with strings' do
- expect(build.options['image']).to eq('ruby:2.1')
+ expect(build.options['image']).to eq('ruby:2.7')
end
context 'when ci_build_metadata_config is set' do
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index c622b7706c6..8b6fa36eaa5 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -84,6 +84,10 @@ describe BuildkiteService, :use_clean_rails_memory_store_caching do
describe '#calculate_reactive_cache' do
describe '#commit_status' do
+ let(:buildkite_full_url) do
+ 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123'
+ end
+
subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] }
it 'sets commit status to :error when status is 500' do
@@ -103,13 +107,25 @@ describe BuildkiteService, :use_clean_rails_memory_store_caching do
is_expected.to eq('Great Success')
end
+
+ Gitlab::HTTP::HTTP_ERRORS.each do |http_error|
+ it "sets commit status to :error with a #{http_error.name} error" do
+ WebMock.stub_request(:get, buildkite_full_url)
+ .to_raise(http_error)
+
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(instance_of(http_error), project_id: project.id)
+
+ is_expected.to eq(:error)
+ end
+ end
end
end
end
def stub_request(status: 200, body: nil)
body ||= %q({"status":"success"})
- buildkite_full_url = 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123'
stub_full_request(buildkite_full_url)
.to_return(status: status,
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 0639a4c1f23..1ee9c5f90c6 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -106,6 +106,10 @@ describe DroneCiService, :use_clean_rails_memory_store_caching do
WebMock.stub_request(:get, commit_status_path)
.to_raise(http_error)
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(instance_of(http_error), project_id: project.id)
+
is_expected.to eq(:error)
end
end
diff --git a/spec/models/snippet_repository_spec.rb b/spec/models/snippet_repository_spec.rb
index c31fe192367..dc9f9a95d24 100644
--- a/spec/models/snippet_repository_spec.rb
+++ b/spec/models/snippet_repository_spec.rb
@@ -16,7 +16,7 @@ describe SnippetRepository do
describe '.find_snippet' do
it 'finds snippet by disk path' do
snippet = create(:snippet, author: user)
- snippet.track_snippet_repository
+ snippet.track_snippet_repository(snippet.repository.storage)
expect(described_class.find_snippet(snippet.disk_path)).to eq(snippet)
end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 0e19dfc147b..8ce3718e3c1 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -567,18 +567,21 @@ describe Snippet do
describe '#track_snippet_repository' do
let(:snippet) { create(:snippet) }
+ let(:shard_name) { 'foo' }
+
+ subject { snippet.track_snippet_repository(shard_name) }
context 'when a snippet repository entry does not exist' do
it 'creates a new entry' do
- expect { snippet.track_snippet_repository }.to change(snippet, :snippet_repository)
+ expect { subject }.to change(snippet, :snippet_repository)
end
it 'tracks the snippet storage location' do
- snippet.track_snippet_repository
+ subject
expect(snippet.snippet_repository).to have_attributes(
disk_path: snippet.disk_path,
- shard_name: snippet.repository_storage
+ shard_name: shard_name
)
end
end
@@ -586,21 +589,20 @@ describe Snippet do
context 'when a tracking entry exists' do
let!(:snippet) { create(:snippet, :repository) }
let(:snippet_repository) { snippet.snippet_repository }
- let!(:shard) { create(:shard, name: 'foo') }
+ let(:shard_name) { 'bar' }
it 'does not create a new entry in the database' do
- expect { snippet.track_snippet_repository }.not_to change(snippet, :snippet_repository)
+ expect { subject }.not_to change(snippet, :snippet_repository)
end
it 'updates the snippet storage location' do
allow(snippet).to receive(:disk_path).and_return('fancy/new/path')
- allow(snippet).to receive(:repository_storage).and_return('foo')
- snippet.track_snippet_repository
+ subject
expect(snippet.snippet_repository).to have_attributes(
disk_path: 'fancy/new/path',
- shard_name: 'foo'
+ shard_name: shard_name
)
end
end
@@ -609,19 +611,31 @@ describe Snippet do
describe '#create_repository' do
let(:snippet) { create(:snippet) }
+ subject { snippet.create_repository }
+
it 'creates the repository' do
expect(snippet.repository).to receive(:after_create).and_call_original
- expect(snippet.create_repository).to be_truthy
+ expect(subject).to be_truthy
expect(snippet.repository.exists?).to be_truthy
end
it 'tracks snippet repository' do
expect do
- snippet.create_repository
+ subject
end.to change(SnippetRepository, :count).by(1)
end
+ it 'sets same shard in snippet repository as in the repository storage' do
+ expect(snippet).to receive(:repository_storage).and_return('picked')
+ expect(snippet).to receive(:repository_exists?).and_return(false)
+ expect(snippet.repository).to receive(:create_if_not_exists)
+
+ subject
+
+ expect(snippet.snippet_repository.shard_name).to eq 'picked'
+ end
+
context 'when repository exists' do
let!(:snippet) { create(:snippet, :repository) }
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
index 5815ce07125..71c2619d898 100644
--- a/spec/requests/api/lint_spec.rb
+++ b/spec/requests/api/lint_spec.rb
@@ -29,7 +29,7 @@ describe API::Lint do
end
it "responds with errors about invalid configuration" do
- post api('/ci/lint'), params: { content: '{ image: "ruby:2.1", services: ["postgres"] }' }
+ post api('/ci/lint'), params: { content: '{ image: "ruby:2.7", services: ["postgres"] }' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['status']).to eq('invalid')
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 5a8add1e9db..03560cae775 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -523,7 +523,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(json_response['token']).to eq(job.token)
expect(json_response['job_info']).to eq(expected_job_info)
expect(json_response['git_info']).to eq(expected_git_info)
- expect(json_response['image']).to eq({ 'name' => 'ruby:2.1', 'entrypoint' => '/bin/sh', 'ports' => [] })
+ expect(json_response['image']).to eq({ 'name' => 'ruby:2.7', 'entrypoint' => '/bin/sh', 'ports' => [] })
expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil,
'alias' => nil, 'command' => nil, 'ports' => [] },
{ 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh',
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 3b819c795b2..681ce9669e2 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -555,7 +555,7 @@ describe Ci::CreatePipelineService do
let(:ci_yaml) do
<<-EOS
image:
- name: ruby:2.2
+ name: ruby:2.7
ports:
- 80
EOS
@@ -567,12 +567,12 @@ describe Ci::CreatePipelineService do
context 'in the job image' do
let(:ci_yaml) do
<<-EOS
- image: ruby:2.2
+ image: ruby:2.7
test:
script: rspec
image:
- name: ruby:2.2
+ name: ruby:2.7
ports:
- 80
EOS
@@ -584,11 +584,11 @@ describe Ci::CreatePipelineService do
context 'in the service' do
let(:ci_yaml) do
<<-EOS
- image: ruby:2.2
+ image: ruby:2.7
test:
script: rspec
- image: ruby:2.2
+ image: ruby:2.7
services:
- name: test
ports:
diff --git a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
index 96cddef4628..01f09f208fd 100644
--- a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
+++ b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
@@ -41,7 +41,8 @@ describe Projects::ContainerRepository::CleanupTagsService do
let(:params) { {} }
it 'does not remove anything' do
- expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_digest)
+ expect_any_instance_of(Projects::ContainerRepository::DeleteTagsService)
+ .not_to receive(:execute)
is_expected.to include(status: :success, deleted: [])
end
@@ -49,15 +50,10 @@ describe Projects::ContainerRepository::CleanupTagsService do
context 'when regex matching everything is specified' do
shared_examples 'removes all matches' do
- it 'does remove B* and C' do
- # The :A cannot be removed as config is shared with :latest
- # The :E cannot be removed as it does not have valid manifest
+ it 'does remove all tags except latest' do
+ expect_delete(%w(A Ba Bb C D E))
- expect_delete('sha256:configB').twice
- expect_delete('sha256:configC')
- expect_delete('sha256:configD')
-
- is_expected.to include(status: :success, deleted: %w(D Bb Ba C))
+ is_expected.to include(status: :success, deleted: %w(A Ba Bb C D E))
end
end
@@ -82,10 +78,9 @@ describe Projects::ContainerRepository::CleanupTagsService do
end
it 'does remove C and D' do
- expect_delete('sha256:configC')
- expect_delete('sha256:configD')
+ expect_delete(%w(C D))
- is_expected.to include(status: :success, deleted: %w(D C))
+ is_expected.to include(status: :success, deleted: %w(C D))
end
context 'with overriding allow regex' do
@@ -95,7 +90,7 @@ describe Projects::ContainerRepository::CleanupTagsService do
end
it 'does not remove C' do
- expect_delete('sha256:configD')
+ expect_delete(%w(D))
is_expected.to include(status: :success, deleted: %w(D))
end
@@ -108,36 +103,52 @@ describe Projects::ContainerRepository::CleanupTagsService do
end
it 'does not remove C' do
- expect_delete('sha256:configD')
+ expect_delete(%w(D))
is_expected.to include(status: :success, deleted: %w(D))
end
end
end
- context 'when removing a tagged image that is used by another tag' do
+ context 'with allow regex value' do
let(:params) do
- { 'name_regex_delete' => 'Ba' }
+ { 'name_regex_delete' => '.*',
+ 'name_regex_keep' => 'B.*' }
end
- it 'does not remove the tag' do
- # Issue: https://gitlab.com/gitlab-org/gitlab-foss/issues/21405
+ it 'does not remove B*' do
+ expect_delete(%w(A C D E))
- is_expected.to include(status: :success, deleted: [])
+ is_expected.to include(status: :success, deleted: %w(A C D E))
end
end
- context 'with allow regex value' do
+ context 'when keeping only N tags' do
let(:params) do
- { 'name_regex_delete' => '.*',
- 'name_regex_keep' => 'B.*' }
+ { 'name_regex' => 'A|B.*|C',
+ 'keep_n' => 1 }
end
- it 'does not remove B*' do
- expect_delete('sha256:configC')
- expect_delete('sha256:configD')
+ it 'sorts tags by date' do
+ expect_delete(%w(Bb Ba C))
+
+ expect(service).to receive(:order_by_date).and_call_original
- is_expected.to include(status: :success, deleted: %w(D C))
+ is_expected.to include(status: :success, deleted: %w(Bb Ba C))
+ end
+ end
+
+ context 'when not keeping N tags' do
+ let(:params) do
+ { 'name_regex' => 'A|B.*|C' }
+ end
+
+ it 'does not sort tags by date' do
+ expect_delete(%w(A Ba Bb C))
+
+ expect(service).not_to receive(:order_by_date)
+
+ is_expected.to include(status: :success, deleted: %w(A Ba Bb C))
end
end
@@ -147,10 +158,10 @@ describe Projects::ContainerRepository::CleanupTagsService do
'keep_n' => 3 }
end
- it 'does remove C as it is oldest' do
- expect_delete('sha256:configC')
+ it 'does remove B* and C as they are the oldest' do
+ expect_delete(%w(Bb Ba C))
- is_expected.to include(status: :success, deleted: %w(C))
+ is_expected.to include(status: :success, deleted: %w(Bb Ba C))
end
end
@@ -161,10 +172,9 @@ describe Projects::ContainerRepository::CleanupTagsService do
end
it 'does remove B* and C as they are older than 1 day' do
- expect_delete('sha256:configB').twice
- expect_delete('sha256:configC')
+ expect_delete(%w(Ba Bb C))
- is_expected.to include(status: :success, deleted: %w(Bb Ba C))
+ is_expected.to include(status: :success, deleted: %w(Ba Bb C))
end
end
@@ -176,8 +186,7 @@ describe Projects::ContainerRepository::CleanupTagsService do
end
it 'does remove B* and C' do
- expect_delete('sha256:configB').twice
- expect_delete('sha256:configC')
+ expect_delete(%w(Bb Ba C))
is_expected.to include(status: :success, deleted: %w(Bb Ba C))
end
@@ -195,8 +204,7 @@ describe Projects::ContainerRepository::CleanupTagsService do
end
it 'succeeds without a user' do
- expect_delete('sha256:configB').twice
- expect_delete('sha256:configC')
+ expect_delete(%w(Bb Ba C))
is_expected.to include(status: :success, deleted: %w(Bb Ba C))
end
@@ -238,9 +246,14 @@ describe Projects::ContainerRepository::CleanupTagsService do
end
end
- def expect_delete(digest)
- expect_any_instance_of(ContainerRegistry::Client)
- .to receive(:delete_repository_tag_by_digest)
- .with(repository.path, digest) { true }
+ def expect_delete(tags)
+ expect(Projects::ContainerRepository::DeleteTagsService)
+ .to receive(:new)
+ .with(repository.project, user, tags: tags)
+ .and_call_original
+
+ expect_any_instance_of(Projects::ContainerRepository::DeleteTagsService)
+ .to receive(:execute)
+ .with(repository) { { status: :success, deleted: tags } }
end
end