Skip to content
Snippets Groups Projects
Commit 1c8fa70f authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 736d36d8
No related branches found
No related tags found
No related merge requests found
Showing
with 408 additions and 215 deletions
Loading
Loading
@@ -374,6 +374,7 @@ linters:
- 'app/views/shared/boards/components/sidebar/_due_date.html.haml'
- 'app/views/shared/boards/components/sidebar/_labels.html.haml'
- 'app/views/shared/boards/components/sidebar/_milestone.html.haml'
- 'app/views/shared/deploy_tokens/_revoke_modal.html.haml'
- 'app/views/shared/empty_states/_priority_labels.html.haml'
- 'app/views/shared/hook_logs/_content.html.haml'
- 'app/views/shared/issuable/_assignees.html.haml'
Loading
Loading
import initSettingsPanels from '~/settings_panels';
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
import initVariableList from '~/ci_variable_list';
import DueDateSelectors from '~/due_date_select';
 
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
// eslint-disable-next-line no-new
new DueDateSelectors();
 
if (gon.features.newVariablesUi) {
initVariableList();
Loading
Loading
Loading
Loading
@@ -3,6 +3,7 @@ import SecretValues from '~/behaviors/secret_values';
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
import registrySettingsApp from '~/registry/settings/registry_settings_bundle';
import initVariableList from '~/ci_variable_list';
import DueDateSelectors from '~/due_date_select';
 
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
Loading
Loading
@@ -39,5 +40,8 @@ document.addEventListener('DOMContentLoaded', () => {
autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked);
});
 
// eslint-disable-next-line no-new
new DueDateSelectors();
registrySettingsApp();
});
<script>
import { mapState, mapActions } from 'vuex';
import { mapState, mapActions, mapGetters } from 'vuex';
import {
GlTable,
GlFormCheckbox,
Loading
Loading
@@ -8,10 +8,10 @@ import {
GlTooltipDirective,
GlPagination,
GlModal,
GlLoadingIcon,
GlSprintf,
GlEmptyState,
GlResizeObserverDirective,
GlSkeletonLoader,
} from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { n__, s__ } from '~/locale';
Loading
Loading
@@ -42,7 +42,7 @@ export default {
ClipboardButton,
GlPagination,
GlModal,
GlLoadingIcon,
GlSkeletonLoader,
GlSprintf,
GlEmptyState,
},
Loading
Loading
@@ -51,6 +51,11 @@ export default {
GlResizeObserver: GlResizeObserverDirective,
},
mixins: [timeagoMixin, Tracking.mixin()],
loader: {
repeat: 10,
width: 1000,
height: 40,
},
data() {
return {
selectedItems: [],
Loading
Loading
@@ -61,15 +66,16 @@ export default {
};
},
computed: {
...mapState(['tags', 'tagsPagination', 'isLoading', 'config']),
...mapGetters(['tags']),
...mapState(['tagsPagination', 'isLoading', 'config']),
imageName() {
const { name } = decodeAndParse(this.$route.params.id);
return name;
},
fields() {
return [
{ key: LIST_KEY_CHECKBOX, label: '' },
{ key: LIST_KEY_TAG, label: LIST_LABEL_TAG },
{ key: LIST_KEY_CHECKBOX, label: '', class: 'gl-w-16' },
{ key: LIST_KEY_TAG, label: LIST_LABEL_TAG, class: 'w-25' },
{ key: LIST_KEY_IMAGE_ID, label: LIST_LABEL_IMAGE_ID },
{ key: LIST_KEY_SIZE, label: LIST_LABEL_SIZE },
{ key: LIST_KEY_LAST_UPDATED, label: LIST_LABEL_LAST_UPDATED },
Loading
Loading
@@ -209,122 +215,142 @@ export default {
</gl-sprintf>
</h4>
</div>
<gl-loading-icon v-if="isLoading" />
<template v-else-if="tags.length > 0">
<gl-table :items="tags" :fields="fields" :stacked="!isDesktop">
<template v-if="isDesktop" #head(checkbox)>
<gl-form-checkbox
ref="mainCheckbox"
:checked="selectAllChecked"
@change="onSelectAllChange"
/>
</template>
<template #head(actions)>
<gl-button
ref="bulkDeleteButton"
v-gl-tooltip
:disabled="!selectedItems || selectedItems.length === 0"
class="float-right"
variant="danger"
:title="s__('ContainerRegistry|Remove selected tags')"
:aria-label="s__('ContainerRegistry|Remove selected tags')"
@click="deleteMultipleItems()"
>
<gl-icon name="remove" />
</gl-button>
</template>
 
<template #cell(checkbox)="{index}">
<gl-form-checkbox
ref="rowCheckbox"
class="js-row-checkbox"
:checked="selectedItems.includes(index)"
@change="updateSelectedItems(index)"
/>
</template>
<template #cell(name)="{item}">
<span ref="rowName">
{{ item.name }}
</span>
<clipboard-button
v-if="item.location"
ref="rowClipboardButton"
:title="item.location"
:text="item.location"
css-class="btn-default btn-transparent btn-clipboard"
/>
</template>
<template #cell(short_revision)="{value}">
<span ref="rowShortRevision">
{{ value }}
</span>
</template>
<template #cell(total_size)="{item}">
<span ref="rowSize">
{{ formatSize(item.total_size) }}
<template v-if="item.total_size && item.layers">
&middot;
</template>
{{ layers(item.layers) }}
</span>
</template>
<template #cell(created_at)="{value}">
<span ref="rowTime">
{{ timeFormatted(value) }}
</span>
</template>
<template #cell(actions)="{index, item}">
<gl-button
ref="singleDeleteButton"
:title="s__('ContainerRegistry|Remove tag')"
:aria-label="s__('ContainerRegistry|Remove tag')"
:disabled="!item.destroy_path"
variant="danger"
:class="['js-delete-registry float-right btn-inverted btn-border-color btn-icon']"
@click="deleteSingleItem(index)"
<gl-table :items="tags" :fields="fields" :stacked="!isDesktop" show-empty>
<template v-if="isDesktop" #head(checkbox)>
<gl-form-checkbox
ref="mainCheckbox"
:checked="selectAllChecked"
@change="onSelectAllChange"
/>
</template>
<template #head(actions)>
<gl-button
ref="bulkDeleteButton"
v-gl-tooltip
:disabled="!selectedItems || selectedItems.length === 0"
class="float-right"
variant="danger"
:title="s__('ContainerRegistry|Remove selected tags')"
:aria-label="s__('ContainerRegistry|Remove selected tags')"
@click="deleteMultipleItems()"
>
<gl-icon name="remove" />
</gl-button>
</template>
<template #cell(checkbox)="{index}">
<gl-form-checkbox
ref="rowCheckbox"
class="js-row-checkbox"
:checked="selectedItems.includes(index)"
@change="updateSelectedItems(index)"
/>
</template>
<template #cell(name)="{item}">
<span ref="rowName">
{{ item.name }}
</span>
<clipboard-button
v-if="item.location"
ref="rowClipboardButton"
:title="item.location"
:text="item.location"
css-class="btn-default btn-transparent btn-clipboard"
/>
</template>
<template #cell(short_revision)="{value}">
<span ref="rowShortRevision">
{{ value }}
</span>
</template>
<template #cell(total_size)="{item}">
<span ref="rowSize">
{{ formatSize(item.total_size) }}
<template v-if="item.total_size && item.layers">
&middot;
</template>
{{ layers(item.layers) }}
</span>
</template>
<template #cell(created_at)="{value}">
<span ref="rowTime">
{{ timeFormatted(value) }}
</span>
</template>
<template #cell(actions)="{index, item}">
<gl-button
ref="singleDeleteButton"
:title="s__('ContainerRegistry|Remove tag')"
:aria-label="s__('ContainerRegistry|Remove tag')"
:disabled="!item.destroy_path"
variant="danger"
class="js-delete-registry float-right btn-inverted btn-border-color btn-icon"
@click="deleteSingleItem(index)"
>
<gl-icon name="remove" />
</gl-button>
</template>
<template #empty>
<template v-if="isLoading">
<gl-skeleton-loader
v-for="index in $options.loader.repeat"
:key="index"
:width="$options.loader.width"
:height="$options.loader.height"
preserve-aspect-ratio="xMinYMax meet"
>
<gl-icon name="remove" />
</gl-button>
<rect width="15" x="0" y="12.5" height="15" rx="4" />
<rect width="250" x="25" y="10" height="20" rx="4" />
<circle cx="290" cy="20" r="10" />
<rect width="100" x="315" y="10" height="20" rx="4" />
<rect width="100" x="500" y="10" height="20" rx="4" />
<rect width="100" x="630" y="10" height="20" rx="4" />
<rect x="960" y="0" width="40" height="40" rx="4" />
</gl-skeleton-loader>
</template>
</gl-table>
<gl-pagination
ref="pagination"
v-model="currentPage"
:per-page="tagsPagination.perPage"
:total-items="tagsPagination.total"
align="center"
class="w-100"
/>
<gl-modal
ref="deleteModal"
modal-id="delete-tag-modal"
ok-variant="danger"
@ok="onDeletionConfirmed"
@cancel="track('cancel_delete')"
>
<template #modal-title>{{ modalAction }}</template>
<template #modal-ok>{{ modalAction }}</template>
<p v-if="modalDescription">
<gl-sprintf :message="modalDescription.message">
<template #item>
<b>{{ modalDescription.item }}</b>
</template>
</gl-sprintf>
</p>
</gl-modal>
</template>
<gl-empty-state
v-else
:title="s__('ContainerRegistry|This image has no active tags')"
:svg-path="config.noContainersImage"
:description="
s__(
`ContainerRegistry|The last tag related to this image was recently removed.
<gl-empty-state
v-else
:title="s__('ContainerRegistry|This image has no active tags')"
:svg-path="config.noContainersImage"
:description="
s__(
`ContainerRegistry|The last tag related to this image was recently removed.
This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process.
If you have any questions, contact your administrator.`,
)
"
class="mx-auto my-0"
)
"
class="mx-auto my-0"
/>
</template>
</gl-table>
<gl-pagination
ref="pagination"
v-model="currentPage"
:per-page="tagsPagination.perPage"
:total-items="tagsPagination.total"
align="center"
class="w-100"
/>
<gl-modal
ref="deleteModal"
modal-id="delete-tag-modal"
ok-variant="danger"
@ok="onDeletionConfirmed"
@cancel="track('cancel_delete')"
>
<template #modal-title>{{ modalAction }}</template>
<template #modal-ok>{{ modalAction }}</template>
<p v-if="modalDescription">
<gl-sprintf :message="modalDescription.message">
<template #item>
<b>{{ modalDescription.item }}</b>
</template>
</gl-sprintf>
</p>
</gl-modal>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import {
GlLoadingIcon,
GlEmptyState,
GlPagination,
GlTooltipDirective,
Loading
Loading
@@ -10,6 +9,7 @@ import {
GlModal,
GlSprintf,
GlLink,
GlSkeletonLoader,
} from '@gitlab/ui';
import Tracking from '~/tracking';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
Loading
Loading
@@ -20,7 +20,6 @@ export default {
name: 'RegistryListApp',
components: {
GlEmptyState,
GlLoadingIcon,
GlPagination,
ProjectEmptyState,
GroupEmptyState,
Loading
Loading
@@ -30,11 +29,17 @@ export default {
GlModal,
GlSprintf,
GlLink,
GlSkeletonLoader,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [Tracking.mixin()],
loader: {
repeat: 10,
width: 1000,
height: 40,
},
data() {
return {
itemToDelete: {},
Loading
Loading
@@ -104,74 +109,81 @@ export default {
</gl-empty-state>
 
<template v-else>
<gl-loading-icon v-if="isLoading" size="md" class="prepend-top-16" />
<template v-else>
<div v-if="images.length" ref="imagesList">
<h4>{{ s__('ContainerRegistry|Container Registry') }}</h4>
<p>
<gl-sprintf
:message="
s__(`ContainerRegistry|With the Docker Container Registry integrated into GitLab, every
<div>
<h4>{{ s__('ContainerRegistry|Container Registry') }}</h4>
<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}`)
"
>
<template #docLink="{content}">
<gl-link :href="config.helpPagePath" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
"
>
<template #docLink="{content}">
<gl-link :href="config.helpPagePath" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
</div>
 
<div class="d-flex flex-column">
<div v-if="isLoading" class="mt-2">
<gl-skeleton-loader
v-for="index in $options.loader.repeat"
:key="index"
:width="$options.loader.width"
:height="$options.loader.height"
preserve-aspect-ratio="xMinYMax meet"
>
<rect width="500" x="10" y="10" height="20" rx="4" />
<circle cx="525" cy="20" r="10" />
<rect x="960" y="0" width="40" height="40" rx="4" />
</gl-skeleton-loader>
</div>
<template v-else>
<div v-if="images.length" ref="imagesList" class="d-flex flex-column">
<div
v-for="(listItem, index) in images"
:key="index"
ref="rowItem"
:class="{ 'border-top': index === 0 }"
class="d-flex justify-content-between align-items-center py-2 border-bottom"
>
<div>
<router-link
ref="detailsLink"
:to="{ name: 'details', params: { id: encodeListItem(listItem) } }"
>
{{ listItem.path }}
</router-link>
<clipboard-button
v-if="listItem.location"
ref="clipboardButton"
:text="listItem.location"
:title="listItem.location"
css-class="btn-default btn-transparent btn-clipboard"
/>
</div>
<div
v-for="(listItem, index) in images"
:key="index"
ref="rowItem"
:class="[
'd-flex justify-content-between align-items-center py-2 border-bottom',
{ 'border-top': index === 0 },
]"
v-gl-tooltip="{ disabled: listItem.destroy_path }"
class="d-none d-sm-block"
:title="
s__('ContainerRegistry|Missing or insufficient permission, delete button disabled')
"
>
<div>
<router-link
ref="detailsLink"
:to="{ name: 'details', params: { id: encodeListItem(listItem) } }"
>
{{ listItem.path }}
</router-link>
<clipboard-button
v-if="listItem.location"
ref="clipboardButton"
:text="listItem.location"
:title="listItem.location"
css-class="btn-default btn-transparent btn-clipboard"
/>
</div>
<div
v-gl-tooltip="{ disabled: listItem.destroy_path }"
class="d-none d-sm-block"
:title="
s__(
'ContainerRegistry|Missing or insufficient permission, delete button disabled',
)
"
<gl-button
ref="deleteImageButton"
v-gl-tooltip
:disabled="!listItem.destroy_path"
:title="s__('ContainerRegistry|Remove repository')"
:aria-label="s__('ContainerRegistry|Remove repository')"
class="btn-inverted"
variant="danger"
@click="deleteImage(listItem)"
>
<gl-button
ref="deleteImageButton"
v-gl-tooltip
:disabled="!listItem.destroy_path"
:title="s__('ContainerRegistry|Remove repository')"
:aria-label="s__('ContainerRegistry|Remove repository')"
class="btn-inverted"
variant="danger"
@click="deleteImage(listItem)"
>
<gl-icon name="remove" />
</gl-button>
</div>
<gl-icon name="remove" />
</gl-button>
</div>
</div>
<gl-pagination
Loading
Loading
@@ -182,6 +194,7 @@ export default {
class="w-100 mt-2"
/>
</div>
<template v-else>
<project-empty-state v-if="!config.isGroupPage" />
<group-empty-state v-else />
Loading
Loading
Loading
Loading
@@ -68,31 +68,28 @@ export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) =
.delete(tag.destroy_path)
.then(() => {
createFlash(DELETE_TAG_SUCCESS_MESSAGE, 'success');
dispatch('requestTagsList', { pagination: state.tagsPagination, params });
return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
})
.catch(() => {
createFlash(DELETE_TAG_ERROR_MESSAGE);
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
 
export const requestDeleteTags = ({ commit, dispatch, state }, { ids, params }) => {
commit(types.SET_MAIN_LOADING, true);
const { id } = decodeAndParse(params);
const url = `/${state.config.projectPath}/registry/repository/${id}/tags/bulk_destroy`;
const { tags_path } = decodeAndParse(params);
const url = tags_path.replace('?format=json', '/bulk_destroy');
 
return axios
.delete(url, { params: { ids } })
.then(() => {
createFlash(DELETE_TAGS_SUCCESS_MESSAGE, 'success');
dispatch('requestTagsList', { pagination: state.tagsPagination, params });
return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
})
.catch(() => {
createFlash(DELETE_TAGS_ERROR_MESSAGE);
})
.finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
Loading
Loading
// eslint-disable-next-line import/prefer-default-export
export const tags = state => {
// to show the loader inside the table we need to pass an empty array to gl-table whenever the table is loading
// this is to take in account isLoading = true and state.tags =[1,2,3] during pagination and delete
return state.isLoading ? [] : state.tags;
};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
 
Loading
Loading
@@ -9,6 +10,7 @@ Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
state,
getters,
actions,
mutations,
});
Loading
Loading
Loading
Loading
@@ -54,7 +54,7 @@
.mh-50vh { max-height: 50vh; }
 
.font-size-inherit { font-size: inherit; }
.gl-w-16 { width: px-to-rem($grid-size * 2); }
.gl-w-64 { width: px-to-rem($grid-size * 8); }
.gl-h-32 { height: px-to-rem($grid-size * 4); }
.gl-h-64 { height: px-to-rem($grid-size * 8); }
Loading
Loading
# frozen_string_literal: true
class Groups::DeployTokensController < Groups::ApplicationController
before_action :authorize_admin_group!
def revoke
@token = @group.deploy_tokens.find(params[:id])
@token.revoke!
redirect_to group_settings_ci_cd_path(@group, anchor: 'js-deploy-tokens')
end
end
Loading
Loading
@@ -7,11 +7,11 @@ module Groups
before_action :authorize_admin_group!
before_action :authorize_update_max_artifacts_size!, only: [:update]
before_action do
push_frontend_feature_flag(:new_variables_ui, @group, default_enabled: true)
push_frontend_feature_flag(:new_variables_ui, @group)
end
before_action :define_variables, only: [:show, :create_deploy_token]
 
def show
define_ci_variables
end
 
def update
Loading
Loading
@@ -41,8 +41,23 @@ module Groups
redirect_to group_settings_ci_cd_path
end
 
def create_deploy_token
@new_deploy_token = Groups::DeployTokens::CreateService.new(@group, current_user, deploy_token_params).execute
if @new_deploy_token.persisted?
flash.now[:notice] = s_('DeployTokens|Your new group deploy token has been created.')
end
render 'show'
end
private
 
def define_variables
define_ci_variables
define_deploy_token_variables
end
def define_ci_variables
@variable = Ci::GroupVariable.new(group: group)
.present(current_user: current_user)
Loading
Loading
@@ -50,6 +65,12 @@ module Groups
.map { |variable| variable.present(current_user: current_user) }
end
 
def define_deploy_token_variables
@deploy_tokens = @group.deploy_tokens.active
@new_deploy_token = DeployToken.new
end
def authorize_admin_group!
return render_404 unless can?(current_user, :admin_group, group)
end
Loading
Loading
@@ -73,6 +94,10 @@ module Groups
def update_group_params
params.require(:group).permit(:max_artifacts_size)
end
def deploy_token_params
params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :username)
end
end
end
end
Loading
Loading
@@ -7,6 +7,6 @@ class Projects::DeployTokensController < Projects::ApplicationController
@token = @project.deploy_tokens.find(params[:id])
@token.revoke!
 
redirect_to project_settings_repository_path(project, anchor: 'js-deploy-tokens')
redirect_to project_settings_ci_cd_path(project, anchor: 'js-deploy-tokens')
end
end
Loading
Loading
@@ -6,7 +6,7 @@ module Projects
before_action :authorize_admin_pipeline!
before_action :define_variables
before_action do
push_frontend_feature_flag(:new_variables_ui, @project, default_enabled: true)
push_frontend_feature_flag(:new_variables_ui, @project)
end
 
def show
Loading
Loading
@@ -46,6 +46,16 @@ module Projects
redirect_to namespace_project_settings_ci_cd_path
end
 
def create_deploy_token
@new_deploy_token = Projects::DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
if @new_deploy_token.persisted?
flash.now[:notice] = s_('DeployTokens|Your new project deploy token has been created.')
end
render 'show'
end
private
 
def update_params
Loading
Loading
@@ -64,6 +74,10 @@ module Projects
end
end
 
def deploy_token_params
params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :username)
end
def run_autodevops_pipeline(service)
return unless service.run_auto_devops_pipeline?
 
Loading
Loading
@@ -83,6 +97,7 @@ module Projects
def define_variables
define_runners_variables
define_ci_variables
define_deploy_token_variables
define_triggers_variables
define_badges_variables
define_auto_devops_variables
Loading
Loading
@@ -132,6 +147,12 @@ module Projects
def define_auto_devops_variables
@auto_devops = @project.auto_devops || ProjectAutoDevops.new
end
def define_deploy_token_variables
@deploy_tokens = @project.deploy_tokens.active
@new_deploy_token = DeployToken.new
end
end
end
end
Loading
Loading
Loading
Loading
@@ -10,16 +10,6 @@ module Projects
render_show
end
 
def create_deploy_token
@new_deploy_token = DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
if @new_deploy_token.persisted?
flash.now[:notice] = s_('DeployTokens|Your new project deploy token has been created.')
end
render_show
end
def cleanup
cleanup_params = params.require(:project).permit(:bfg_object_map)
result = Projects::UpdateService.new(project, current_user, cleanup_params).execute
Loading
Loading
@@ -38,9 +28,7 @@ module Projects
 
def render_show
@deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user)
@deploy_tokens = @project.deploy_tokens.active
 
define_deploy_token
define_protected_refs
remote_mirror
 
Loading
Loading
@@ -93,14 +81,6 @@ module Projects
gon.push(protectable_branches_for_dropdown)
gon.push(access_levels_options)
end
def define_deploy_token
@new_deploy_token ||= DeployToken.new
end
def deploy_token_params
params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :username)
end
end
end
end
Loading
Loading
Loading
Loading
@@ -5,6 +5,22 @@ module CiVariablesHelper
Gitlab::CurrentSettings.current_application_settings.protected_ci_variables
end
 
def create_deploy_token_path(entity, opts = {})
if entity.is_a?(Group)
create_deploy_token_group_settings_ci_cd_path(entity, opts)
else
create_deploy_token_project_settings_repository_path(entity, opts)
end
end
def revoke_deploy_token_path(entity, token)
if entity.is_a?(Group)
revoke_group_deploy_token_path(entity, token)
else
revoke_project_deploy_token_path(entity, token)
end
end
def ci_variable_protected?(variable, only_key_value)
if variable && !only_key_value
variable.protected
Loading
Loading
Loading
Loading
@@ -59,6 +59,9 @@ class Group < Namespace
 
has_many :import_failures, inverse_of: :group
 
has_many :group_deploy_tokens
has_many :deploy_tokens, through: :group_deploy_tokens
accepts_nested_attributes_for :variables, allow_destroy: true
 
validate :visibility_level_allowed_by_projects
Loading
Loading
Loading
Loading
@@ -9,7 +9,7 @@ class GroupDeployToken < ApplicationRecord
validates :deploy_token_id, uniqueness: { scope: [:group_id] }
 
def has_access_to?(requested_project)
return false unless Feature.enabled?(:allow_group_deploy_token, default: true)
return false unless Feature.enabled?(:allow_group_deploy_token, default_enabled: true)
 
requested_project_group = requested_project&.group
return false unless requested_project_group
Loading
Loading
Loading
Loading
@@ -2343,6 +2343,14 @@ class Project < ApplicationRecord
Gitlab::CurrentSettings.self_monitoring_project_id == id
end
 
def deploy_token_create_url(opts = {})
Gitlab::Routing.url_helpers.create_deploy_token_project_settings_ci_cd_path(self, opts)
end
def deploy_token_revoke_url_for(token)
Gitlab::Routing.url_helpers.revoke_project_deploy_token_path(self, token)
end
private
 
def closest_namespace_setting(name)
Loading
Loading
Loading
Loading
@@ -3,11 +3,76 @@
class SnippetRepository < ApplicationRecord
include Shardable
 
DEFAULT_EMPTY_FILE_NAME = 'snippetfile'
EMPTY_FILE_PATTERN = /^#{DEFAULT_EMPTY_FILE_NAME}(\d)\.txt$/.freeze
CommitError = Class.new(StandardError)
belongs_to :snippet, inverse_of: :snippet_repository
 
delegate :repository, to: :snippet
class << self
def find_snippet(disk_path)
find_by(disk_path: disk_path)&.snippet
end
end
def multi_files_action(user, files = [], **options)
return if files.nil? || files.empty?
lease_key = "multi_files_action:#{snippet_id}"
lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 120)
raise CommitError, 'Snippet is already being updated' unless uuid = lease.try_obtain
options[:actions] = transform_file_entries(files)
capture_git_error { repository.multi_action(user, **options) }
ensure
Gitlab::ExclusiveLease.cancel(lease_key, uuid)
end
private
def capture_git_error(&block)
yield block
rescue Gitlab::Git::Index::IndexError,
Gitlab::Git::CommitError,
Gitlab::Git::PreReceiveError,
Gitlab::Git::CommandError => e
raise CommitError, e.message
end
def transform_file_entries(files)
last_index = get_last_empty_file_index
files.each do |file_entry|
file_entry[:action] = infer_action(file_entry) unless file_entry[:action]
if file_entry[:file_path].blank?
file_entry[:file_path] = build_empty_file_name(last_index)
last_index += 1
end
end
end
def infer_action(file_entry)
return :create if file_entry[:previous_path].blank?
file_entry[:previous_path] != file_entry[:file_path] ? :move : :update
end
def get_last_empty_file_index
last_file = repository.ls_files(nil)
.map! { |file| file.match(EMPTY_FILE_PATTERN) }
.compact
.max_by { |element| element[1] }
last_file ? (last_file[1].to_i + 1) : 1
end
def build_empty_file_name(index)
"#{DEFAULT_EMPTY_FILE_NAME}#{index}.txt"
end
end
# frozen_string_literal: true
module DeployTokenMethods
def create_deploy_token_for(entity, params)
params[:deploy_token_type] = DeployToken.deploy_token_types["#{entity.class.name.downcase}_type".to_sym]
entity.deploy_tokens.create(params) do |deploy_token|
deploy_token.username = params[:username].presence
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment