diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-25 12:08:19 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-25 12:08:19 +0000 |
commit | e6baeabaa9651d90b03bb64ffce75a2c3cb89aab (patch) | |
tree | 85f3cbd6e437b17be59505cf3ac4794c1838609e | |
parent | 5064bf8c5647d4c4430cbb4d097cf1592416de29 (diff) | |
download | gitlab-ce-e6baeabaa9651d90b03bb64ffce75a2c3cb89aab.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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 |