Commit e6baeaba authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖

Add latest changes from gitlab-org/[email protected]

parent 5064bf8c
Pipeline #129598232 passed with stages
in 109 minutes and 54 seconds
......@@ -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__(
......
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');
......
......@@ -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');
......
<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>
......@@ -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>
......
......@@ -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);
......
......@@ -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;
};
......@@ -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';
......@@ -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);
......
export default () => ({
isLoading: false,
showGarbageCollectionTip: false,
config: {},
images: [],
tags: [],
......
......@@ -7,6 +7,10 @@
a {
color: $gl-text-color;
&.link {
color: $blue-600;
}
}
.author-link {
......
......@@ -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
......
......@@ -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
......@@ -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)
......
......@@ -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)
......
......@@ -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
......
......@@ -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
......
= 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
......@@ -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
......
......@@ -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),
......
......@@ -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')
---
title: Update detected languages for sast in no dind mode
merge_request: 27831
author:
type: fixed
---
title: Use Ruby 2.7 in specs to remove Ruby 2.1/2.2/2.3
merge_request: 27269
author: Takuya Noguchi
type: other
---
title: Improve performance of the container repository cleanup tags service
merge_request: 27441
author:
type: performance
---
title: Add cost factor fields to ci runners
merge_request: 27666
author:
type: added
---
title: Add auto_ssl_failed to pages_domains
merge_request: 27671
author:
type: added
---
title: Support wiki events in activity streams
merge_request: 23869
author:
type: changed
---
title: Fix bug tracking snippet shard name
merge_request: 27979
author:
type: fixed
# 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'