Add latest changes from gitlab-org/gitlab@master
10
CHANGELOG.md
|
@ -2,6 +2,16 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 13.5.4 (2020-11-13)
|
||||
|
||||
### Fixed (4 changes)
|
||||
|
||||
- Fix Vue Labels Select dropdown keyboard scroll. !43874
|
||||
- Hashed Storage: make migration and rollback resilient to exceptions. !46178
|
||||
- Fix compliance framework database migration on CE instances. !46761
|
||||
- Resolve problem when namespace_settings were not created for groups created via admin panel. !46875
|
||||
|
||||
|
||||
## 13.5.3 (2020-11-03)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
|
|
@ -66,7 +66,7 @@ export default {
|
|||
<template>
|
||||
<div class="js-file-title file-title-flex-parent">
|
||||
<blob-filepath :blob="blob">
|
||||
<template #filepathPrepend>
|
||||
<template #filepath-prepend>
|
||||
<slot name="prepend"></slot>
|
||||
</template>
|
||||
</blob-filepath>
|
||||
|
|
|
@ -26,7 +26,7 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div class="file-header-content d-flex align-items-center lh-100">
|
||||
<slot name="filepathPrepend"></slot>
|
||||
<slot name="filepath-prepend"></slot>
|
||||
|
||||
<template v-if="blob.path">
|
||||
<file-icon :file-name="blob.path" :size="18" aria-hidden="true" css-classes="mr-2" />
|
||||
|
|
|
@ -343,7 +343,7 @@ export default {
|
|||
>
|
||||
<span v-else class="js-cluster-application-title">{{ title }}</span>
|
||||
</strong>
|
||||
<slot name="installedVia"></slot>
|
||||
<slot name="installed-via"></slot>
|
||||
<div>
|
||||
<slot name="description"></slot>
|
||||
</div>
|
||||
|
|
|
@ -549,8 +549,8 @@ export default {
|
|||
@set="setKnativeDomain"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="cloudRun" #installedVia>
|
||||
<span data-testid="installedVia">
|
||||
<template v-if="cloudRun" #installed-via>
|
||||
<span data-testid="installed-via">
|
||||
<gl-sprintf
|
||||
:message="s__('ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}')"
|
||||
>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import setupToggleButtons from '~/toggle_buttons';
|
||||
|
||||
export default () => {
|
||||
setupToggleButtons(document.querySelector('.js-dependency-proxy-toggle-area'));
|
||||
};
|
|
@ -210,7 +210,7 @@ export default {
|
|||
:class="{ 'gl-bg-blue-50': isDiscussionActive }"
|
||||
@error="$emit('update-note-error', $event)"
|
||||
>
|
||||
<template v-if="discussion.resolvable" #resolveDiscussion>
|
||||
<template v-if="discussion.resolvable" #resolve-discussion>
|
||||
<button
|
||||
v-gl-tooltip
|
||||
:class="{ 'is-active': discussion.resolved }"
|
||||
|
@ -224,7 +224,7 @@ export default {
|
|||
<gl-loading-icon v-else inline />
|
||||
</button>
|
||||
</template>
|
||||
<template v-if="discussion.resolved" #resolvedStatus>
|
||||
<template v-if="discussion.resolved" #resolved-status>
|
||||
<p class="gl-text-gray-500 gl-font-sm gl-m-0 gl-mt-5" data-testid="resolved-message">
|
||||
{{ __('Resolved by') }}
|
||||
<gl-link
|
||||
|
@ -277,7 +277,7 @@ export default {
|
|||
@submit-form="mutate"
|
||||
@cancel-form="hideForm"
|
||||
>
|
||||
<template v-if="discussion.resolvable" #resolveCheckbox>
|
||||
<template v-if="discussion.resolvable" #resolve-checkbox>
|
||||
<label data-testid="resolve-checkbox">
|
||||
<input v-model="shouldChangeResolvedStatus" type="checkbox" />
|
||||
{{ resolveCheckboxText }}
|
||||
|
|
|
@ -108,7 +108,7 @@ export default {
|
|||
</span>
|
||||
</div>
|
||||
<div class="gl-display-flex gl-align-items-baseline">
|
||||
<slot name="resolveDiscussion"></slot>
|
||||
<slot name="resolve-discussion"></slot>
|
||||
<button
|
||||
v-if="isEditButtonVisible"
|
||||
v-gl-tooltip
|
||||
|
@ -127,7 +127,7 @@ export default {
|
|||
class="note-text js-note-text md"
|
||||
data-qa-selector="note_content"
|
||||
></div>
|
||||
<slot name="resolvedStatus"></slot>
|
||||
<slot name="resolved-status"></slot>
|
||||
</template>
|
||||
<apollo-mutation
|
||||
v-else
|
||||
|
|
|
@ -110,7 +110,7 @@ export default {
|
|||
</textarea>
|
||||
</template>
|
||||
</markdown-field>
|
||||
<slot name="resolveCheckbox"></slot>
|
||||
<slot name="resolve-checkbox"></slot>
|
||||
<div class="note-form-actions gl-display-flex gl-justify-content-space-between">
|
||||
<gl-button
|
||||
ref="submitButton"
|
||||
|
|
|
@ -207,6 +207,6 @@ export default {
|
|||
/>
|
||||
</gl-collapse>
|
||||
</template>
|
||||
<slot name="replyForm"></slot>
|
||||
<slot name="reply-form"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -383,7 +383,7 @@ export default {
|
|||
@toggleResolvedComments="toggleResolvedComments"
|
||||
@todoError="onTodoError"
|
||||
>
|
||||
<template #replyForm>
|
||||
<template #reply-form>
|
||||
<apollo-mutation
|
||||
v-if="isAnnotating"
|
||||
#default="{ mutate, loading }"
|
||||
|
|
|
@ -69,7 +69,7 @@ export default {
|
|||
<div class="environments-container">
|
||||
<gl-loading-icon v-if="isLoading" size="md" class="gl-mt-3" label="Loading environments" />
|
||||
|
||||
<slot name="emptyState"></slot>
|
||||
<slot name="empty-state"></slot>
|
||||
|
||||
<div v-if="!isLoading && environments.length > 0" class="table-holder">
|
||||
<environment-table
|
||||
|
|
|
@ -228,7 +228,7 @@ export default {
|
|||
:deploy-boards-help-path="deployBoardsHelpPath"
|
||||
@onChangePage="onChangePage"
|
||||
>
|
||||
<template v-if="!isLoading && state.environments.length === 0" #emptyState>
|
||||
<template v-if="!isLoading && state.environments.length === 0" #empty-state>
|
||||
<empty-state :help-path="helpPagePath" />
|
||||
</template>
|
||||
</container>
|
||||
|
|
|
@ -7,11 +7,14 @@ import { visitUrl, objectToQuery } from '~/lib/utils/url_utility';
|
|||
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import httpStatusCodes from '~/lib/utils/http_status';
|
||||
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
|
||||
|
||||
let eTagPoll;
|
||||
|
||||
const hasRedirectInError = e => e?.response?.data?.error?.redirect;
|
||||
const redirectToUrlInError = e => visitUrl(e.response.data.error.redirect);
|
||||
const tooManyRequests = e => e.response.status === httpStatusCodes.TOO_MANY_REQUESTS;
|
||||
const pathWithParams = ({ path, ...params }) => {
|
||||
const filteredParams = Object.fromEntries(
|
||||
Object.entries(params).filter(([, value]) => value !== ''),
|
||||
|
@ -71,6 +74,14 @@ const fetchReposFactory = ({ reposPath = isRequired() }) => ({ state, commit })
|
|||
|
||||
if (hasRedirectInError(e)) {
|
||||
redirectToUrlInError(e);
|
||||
} else if (tooManyRequests(e)) {
|
||||
createFlash(
|
||||
sprintf(s__('ImportProjects|%{provider} rate limit exceeded. Try again later'), {
|
||||
provider: capitalizeFirstCharacter(provider),
|
||||
}),
|
||||
);
|
||||
|
||||
commit(types.RECEIVE_REPOS_ERROR);
|
||||
} else {
|
||||
createFlash(
|
||||
sprintf(s__('ImportProjects|Requesting your %{provider} repositories failed'), {
|
||||
|
|
|
@ -419,7 +419,7 @@ export default {
|
|||
</template>
|
||||
</gl-table>
|
||||
</template>
|
||||
<template #emtpy-state>
|
||||
<template #empty-state>
|
||||
<gl-empty-state
|
||||
:title="emptyStateData.title"
|
||||
:svg-path="emptyListSvgPath"
|
||||
|
|
|
@ -22,6 +22,7 @@ const httpStatusCodes = {
|
|||
CONFLICT: 409,
|
||||
GONE: 410,
|
||||
UNPROCESSABLE_ENTITY: 422,
|
||||
TOO_MANY_REQUESTS: 429,
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
SERVICE_UNAVAILABLE: 503,
|
||||
};
|
||||
|
|
|
@ -423,7 +423,7 @@ export default {
|
|||
:prometheus-alerts-available="prometheusAlertsAvailable"
|
||||
@timerangezoom="onTimeRangeZoom"
|
||||
>
|
||||
<template #topLeft>
|
||||
<template #top-left>
|
||||
<gl-button
|
||||
ref="goBackBtn"
|
||||
v-gl-tooltip
|
||||
|
|
|
@ -365,7 +365,7 @@ export default {
|
|||
<template>
|
||||
<div v-gl-resize-observer="onResize" class="prometheus-graph">
|
||||
<div class="d-flex align-items-center">
|
||||
<slot name="topLeft"></slot>
|
||||
<slot name="top-left"></slot>
|
||||
<h5
|
||||
ref="graphTitle"
|
||||
class="prometheus-graph-title gl-font-lg font-weight-bold text-truncate gl-mr-3"
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import $ from 'jquery';
|
||||
import initDependencyProxy from '~/dependency_proxy';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initDependencyProxy();
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const form = document.querySelector('form.edit_dependency_proxy_group_setting');
|
||||
const toggleInput = $('input.js-project-feature-toggle-input');
|
||||
|
||||
if (form && toggleInput) {
|
||||
toggleInput.on('trigger-change', () => {
|
||||
form.submit();
|
||||
});
|
||||
}
|
||||
});
|
|
@ -138,7 +138,7 @@ export default {
|
|||
href="#related-issues"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<slot name="headerText">{{ __('Linked issues') }}</slot>
|
||||
<slot name="header-text">{{ __('Linked issues') }}</slot>
|
||||
<gl-link
|
||||
v-if="hasHelpPath"
|
||||
:href="helpPath"
|
||||
|
@ -167,7 +167,7 @@ export default {
|
|||
/>
|
||||
</div>
|
||||
</h3>
|
||||
<slot name="headerActions"></slot>
|
||||
<slot name="header-actions"></slot>
|
||||
</div>
|
||||
<div
|
||||
class="linked-issues-card-body bg-gray-light"
|
||||
|
|
|
@ -134,7 +134,7 @@ export default {
|
|||
class="mr-widget-section grouped-security-reports mr-report"
|
||||
@toggleEvent="handleToggleEvent"
|
||||
>
|
||||
<template v-if="showViewFullReport" #actionButtons>
|
||||
<template v-if="showViewFullReport" #action-buttons>
|
||||
<gl-button
|
||||
:href="testTabURL"
|
||||
target="_blank"
|
||||
|
@ -145,7 +145,7 @@ export default {
|
|||
{{ s__('ciReport|View full report') }}
|
||||
</gl-button>
|
||||
</template>
|
||||
<template v-if="hasRecentFailures(summary)" #subHeading>
|
||||
<template v-if="hasRecentFailures(summary)" #sub-heading>
|
||||
{{ recentFailuresText(summary) }}
|
||||
</template>
|
||||
<template #body>
|
||||
|
|
|
@ -181,10 +181,10 @@ export default {
|
|||
<slot :name="slotName"></slot>
|
||||
<popover v-if="hasPopover" :options="popoverOptions" class="gl-ml-2" />
|
||||
</div>
|
||||
<slot name="subHeading"></slot>
|
||||
<slot name="sub-heading"></slot>
|
||||
</div>
|
||||
|
||||
<slot name="actionButtons"></slot>
|
||||
<slot name="action-buttons"></slot>
|
||||
|
||||
<button
|
||||
v-if="isCollapsible"
|
||||
|
|
|
@ -308,6 +308,6 @@ export default {
|
|||
@input="handlePageChange"
|
||||
/>
|
||||
|
||||
<slot v-if="!showItems" name="emtpy-state"></slot>
|
||||
<slot v-if="!showItems" name="empty-state"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -147,7 +147,7 @@ export default {
|
|||
class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white"
|
||||
>
|
||||
<div v-show="!isDragDataValid" class="mw-50 gl-text-center">
|
||||
<slot name="invalidDragDataSlot">
|
||||
<slot name="invalid-drag-data-slot">
|
||||
<h3 :class="{ 'gl-font-base gl-display-inline': !displayAsCard }">
|
||||
{{ __('Oh no!') }}
|
||||
</h3>
|
||||
|
@ -159,7 +159,7 @@ export default {
|
|||
</slot>
|
||||
</div>
|
||||
<div v-show="isDragDataValid" class="mw-50 gl-text-center">
|
||||
<slot name="validDragDataSlot">
|
||||
<slot name="valid-drag-data-slot">
|
||||
<h3 :class="{ 'gl-font-base gl-display-inline': !displayAsCard }">
|
||||
{{ __('Incoming!') }}
|
||||
</h3>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DependencyProxyAccess
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :verify_dependency_proxy_enabled!
|
||||
before_action :authorize_read_dependency_proxy!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify_dependency_proxy_enabled!
|
||||
render_404 unless group.dependency_proxy_feature_available?
|
||||
end
|
||||
|
||||
def authorize_read_dependency_proxy!
|
||||
access_denied! unless can?(current_user, :read_dependency_proxy, group)
|
||||
end
|
||||
|
||||
def authorize_admin_dependency_proxy!
|
||||
access_denied! unless can?(current_user, :admin_dependency_proxy, group)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Groups
|
||||
class DependencyProxiesController < Groups::ApplicationController
|
||||
include DependencyProxyAccess
|
||||
|
||||
before_action :authorize_admin_dependency_proxy!, only: :update
|
||||
before_action :dependency_proxy
|
||||
|
||||
feature_category :package_registry
|
||||
|
||||
def show
|
||||
@blobs_count = group.dependency_proxy_blobs.count
|
||||
@blobs_total_size = group.dependency_proxy_blobs.total_size
|
||||
end
|
||||
|
||||
def update
|
||||
dependency_proxy.update(dependency_proxy_params)
|
||||
|
||||
redirect_to group_dependency_proxy_path(group)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dependency_proxy
|
||||
@dependency_proxy ||=
|
||||
group.dependency_proxy_setting || group.create_dependency_proxy_setting
|
||||
end
|
||||
|
||||
def dependency_proxy_params
|
||||
params.require(:dependency_proxy_group_setting).permit(:enabled)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Groups::DependencyProxyForContainersController < Groups::ApplicationController
|
||||
include DependencyProxyAccess
|
||||
include SendFileUpload
|
||||
|
||||
before_action :ensure_token_granted!
|
||||
before_action :ensure_feature_enabled!
|
||||
|
||||
attr_reader :token
|
||||
|
||||
feature_category :package_registry
|
||||
|
||||
def manifest
|
||||
result = DependencyProxy::PullManifestService.new(image, tag, token).execute
|
||||
|
||||
if result[:status] == :success
|
||||
render json: result[:manifest]
|
||||
else
|
||||
render status: result[:http_status], json: result[:message]
|
||||
end
|
||||
end
|
||||
|
||||
def blob
|
||||
result = DependencyProxy::FindOrCreateBlobService
|
||||
.new(group, image, token, params[:sha]).execute
|
||||
|
||||
if result[:status] == :success
|
||||
send_upload(result[:blob].file)
|
||||
else
|
||||
head result[:http_status]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def image
|
||||
params[:image]
|
||||
end
|
||||
|
||||
def tag
|
||||
params[:tag]
|
||||
end
|
||||
|
||||
def dependency_proxy
|
||||
@dependency_proxy ||=
|
||||
group.dependency_proxy_setting || group.create_dependency_proxy_setting
|
||||
end
|
||||
|
||||
def ensure_feature_enabled!
|
||||
render_404 unless dependency_proxy.enabled
|
||||
end
|
||||
|
||||
def ensure_token_granted!
|
||||
result = DependencyProxy::RequestTokenService.new(image).execute
|
||||
|
||||
if result[:status] == :success
|
||||
@token = result[:token]
|
||||
else
|
||||
render status: result[:http_status], json: result[:message]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,6 +15,7 @@ class Import::GithubController < Import::BaseController
|
|||
rescue_from OAuthConfigMissingError, with: :missing_oauth_config
|
||||
rescue_from Octokit::Unauthorized, with: :provider_unauthorized
|
||||
rescue_from Octokit::TooManyRequests, with: :provider_rate_limit
|
||||
rescue_from Gitlab::GithubImport::RateLimitError, with: :rate_limit_threshold_exceeded
|
||||
|
||||
def new
|
||||
if !ci_cd_only? && github_import_configured? && logged_in_with_provider?
|
||||
|
@ -114,7 +115,7 @@ class Import::GithubController < Import::BaseController
|
|||
|
||||
def client_repos
|
||||
@client_repos ||= if Feature.enabled?(:remove_legacy_github_client)
|
||||
filtered(concatenated_repos)
|
||||
concatenated_repos
|
||||
else
|
||||
filtered(client.repos)
|
||||
end
|
||||
|
@ -122,8 +123,15 @@ class Import::GithubController < Import::BaseController
|
|||
|
||||
def concatenated_repos
|
||||
return [] unless client.respond_to?(:each_page)
|
||||
return client.each_page(:repos).flat_map(&:objects) unless sanitized_filter_param
|
||||
|
||||
client.each_page(:repos).flat_map(&:objects)
|
||||
client.search_repos_by_name(sanitized_filter_param).flat_map(&:objects).flat_map(&:items)
|
||||
end
|
||||
|
||||
def sanitized_filter_param
|
||||
super
|
||||
|
||||
@filter = @filter&.tr(' ', '')&.tr(':', '')
|
||||
end
|
||||
|
||||
def oauth_client
|
||||
|
@ -245,6 +253,10 @@ class Import::GithubController < Import::BaseController
|
|||
def extra_import_params
|
||||
{}
|
||||
end
|
||||
|
||||
def rate_limit_threshold_exceeded
|
||||
head :too_many_requests
|
||||
end
|
||||
end
|
||||
|
||||
Import::GithubController.prepend_if_ee('EE::Import::GithubController')
|
||||
|
|
|
@ -170,6 +170,10 @@ module GroupsHelper
|
|||
group_container_registry_nav?
|
||||
end
|
||||
|
||||
def group_dependency_proxy_nav?
|
||||
@group.dependency_proxy_feature_available?
|
||||
end
|
||||
|
||||
def group_packages_list_nav?
|
||||
@group.packages_feature_enabled?
|
||||
end
|
||||
|
|
|
@ -103,6 +103,10 @@ module Ci
|
|||
)
|
||||
end
|
||||
|
||||
scope :in_pipelines, ->(pipelines) do
|
||||
where(pipeline: pipelines)
|
||||
end
|
||||
|
||||
scope :with_existing_job_artifacts, ->(query) do
|
||||
where('EXISTS (?)', ::Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').merge(query))
|
||||
end
|
||||
|
|
|
@ -355,6 +355,14 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def self.latest_running_for_ref(ref)
|
||||
newest_first(ref: ref).running.take
|
||||
end
|
||||
|
||||
def self.latest_failed_for_ref(ref)
|
||||
newest_first(ref: ref).failed.take
|
||||
end
|
||||
|
||||
# Returns a Hash containing the latest pipeline for every given
|
||||
# commit.
|
||||
#
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
module DependencyProxy
|
||||
def self.table_name_prefix
|
||||
'dependency_proxy_'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DependencyProxy::Blob < ApplicationRecord
|
||||
include FileStoreMounter
|
||||
|
||||
belongs_to :group
|
||||
|
||||
validates :group, presence: true
|
||||
validates :file, presence: true
|
||||
validates :file_name, presence: true
|
||||
|
||||
mount_file_store_uploader DependencyProxy::FileUploader
|
||||
|
||||
def self.total_size
|
||||
sum(:size)
|
||||
end
|
||||
|
||||
def self.find_or_build(file_name)
|
||||
find_or_initialize_by(file_name: file_name)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DependencyProxy::GroupSetting < ApplicationRecord
|
||||
belongs_to :group
|
||||
|
||||
validates :group, presence: true
|
||||
|
||||
default_value_for :enabled, true
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DependencyProxy::Registry
|
||||
AUTH_URL = 'https://auth.docker.io'.freeze
|
||||
LIBRARY_URL = 'https://registry-1.docker.io/v2'.freeze
|
||||
|
||||
class << self
|
||||
def auth_url(image)
|
||||
"#{AUTH_URL}/token?service=registry.docker.io&scope=repository:#{image_path(image)}:pull"
|
||||
end
|
||||
|
||||
def manifest_url(image, tag)
|
||||
"#{LIBRARY_URL}/#{image_path(image)}/manifests/#{tag}"
|
||||
end
|
||||
|
||||
def blob_url(image, blob_sha)
|
||||
"#{LIBRARY_URL}/#{image_path(image)}/blobs/#{blob_sha}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def image_path(image)
|
||||
if image.include?('/')
|
||||
image
|
||||
else
|
||||
"library/#{image}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -71,6 +71,9 @@ class Group < Namespace
|
|||
has_many :group_deploy_tokens
|
||||
has_many :deploy_tokens, through: :group_deploy_tokens
|
||||
|
||||
has_one :dependency_proxy_setting, class_name: 'DependencyProxy::GroupSetting'
|
||||
has_many :dependency_proxy_blobs, class_name: 'DependencyProxy::Blob'
|
||||
|
||||
accepts_nested_attributes_for :variables, allow_destroy: true
|
||||
|
||||
validate :visibility_level_allowed_by_projects
|
||||
|
@ -203,6 +206,10 @@ class Group < Namespace
|
|||
::Gitlab.config.packages.enabled
|
||||
end
|
||||
|
||||
def dependency_proxy_feature_available?
|
||||
::Gitlab.config.dependency_proxy.enabled
|
||||
end
|
||||
|
||||
def notification_email_for(user)
|
||||
# Finds the closest notification_setting with a `notification_email`
|
||||
notification_settings = notification_settings_for(user, hierarchy_order: :asc)
|
||||
|
|
|
@ -46,6 +46,10 @@ class GroupPolicy < BasePolicy
|
|||
group_projects_for(user: @user, group: @subject, only_owned: false).any? { |p| p.design_management_enabled? }
|
||||
end
|
||||
|
||||
condition(:dependency_proxy_available) do
|
||||
@subject.dependency_proxy_feature_available?
|
||||
end
|
||||
|
||||
desc "Deploy token with read_package_registry scope"
|
||||
condition(:read_package_registry_deploy_token) do
|
||||
@user.is_a?(DeployToken) && @user.groups.include?(@subject) && @user.read_package_registry
|
||||
|
@ -193,6 +197,12 @@ class GroupPolicy < BasePolicy
|
|||
enable :read_group
|
||||
end
|
||||
|
||||
rule { can?(:read_group) & dependency_proxy_available }
|
||||
.enable :read_dependency_proxy
|
||||
|
||||
rule { developer & dependency_proxy_available }
|
||||
.enable :admin_dependency_proxy
|
||||
|
||||
rule { resource_access_token_available & can?(:admin_group) }.policy do
|
||||
enable :admin_resource_access_tokens
|
||||
end
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DependencyProxy
|
||||
class BaseService < ::BaseService
|
||||
private
|
||||
|
||||
def registry
|
||||
DependencyProxy::Registry
|
||||
end
|
||||
|
||||
def auth_headers
|
||||
{
|
||||
Authorization: "Bearer #{@token}"
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DependencyProxy
|
||||
class DownloadBlobService < DependencyProxy::BaseService
|
||||
class DownloadError < StandardError
|
||||
attr_reader :http_status
|
||||
|
||||
def initialize(message, http_status)
|
||||
@http_status = http_status
|
||||
|
||||
super(message)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(image, blob_sha, token)
|
||||
@image = image
|
||||
@blob_sha = blob_sha
|
||||
@token = token
|
||||
@temp_file = Tempfile.new
|
||||
end
|
||||
|
||||
def execute
|
||||
File.open(@temp_file.path, "wb") do |file|
|
||||
Gitlab::HTTP.get(blob_url, headers: auth_headers, stream_body: true) do |fragment|
|
||||
if [301, 302, 307].include?(fragment.code)
|
||||
# do nothing
|
||||
elsif fragment.code == 200
|
||||
file.write(fragment)
|
||||
else
|
||||
raise DownloadError.new('Non-success response code on downloading blob fragment', fragment.code)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
success(file: @temp_file)
|
||||
rescue DownloadError => exception
|
||||
error(exception.message, exception.http_status)
|
||||
rescue Timeout::Error => exception
|
||||
error(exception.message, 599)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def blob_url
|
||||
registry.blob_url(@image, @blob_sha)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DependencyProxy
|
||||
class FindOrCreateBlobService < DependencyProxy::BaseService
|
||||
def initialize(group, image, token, blob_sha)
|
||||
@group = group
|
||||
@image = image
|
||||
@token = token
|
||||
@blob_sha = blob_sha
|
||||
end
|
||||
|
||||
def execute
|
||||
file_name = @blob_sha.sub('sha256:', '') + '.gz'
|
||||
blob = @group.dependency_proxy_blobs.find_or_build(file_name)
|
||||
|
||||
unless blob.persisted?
|
||||
result = DependencyProxy::DownloadBlobService
|
||||
.new(@image, @blob_sha, @token).execute
|
||||
|
||||
if result[:status] == :error
|
||||
log_failure(result)
|
||||
|
||||
return error('Failed to download the blob', result[:http_status])
|
||||
end
|
||||
|
||||
blob.file = result[:file]
|
||||
blob.size = result[:file].size
|
||||
blob.save!
|
||||
end
|
||||
|
||||
success(blob: blob)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def log_failure(result)
|
||||
log_error(
|
||||
"Dependency proxy: Failed to download the blob." \
|
||||
"Blob sha: #{@blob_sha}." \
|
||||
"Error message: #{result[:message][0, 100]}" \
|
||||
"HTTP status: #{result[:http_status]}"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DependencyProxy
|
||||
class PullManifestService < DependencyProxy::BaseService
|
||||
def initialize(image, tag, token)
|
||||
@image = image
|
||||
@tag = tag
|
||||
@token = token
|
||||
end
|
||||
|
||||
def execute
|
||||
response = Gitlab::HTTP.get(manifest_url, headers: auth_headers)
|
||||
|
||||
if response.success?
|
||||
success(manifest: response.body)
|
||||
else
|
||||
error(response.body, response.code)
|
||||
end
|
||||
rescue Timeout::Error => exception
|
||||
error(exception.message, 599)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def manifest_url
|
||||
registry.manifest_url(@image, @tag)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DependencyProxy
|
||||
class RequestTokenService < DependencyProxy::BaseService
|
||||
def initialize(image)
|
||||
@image = image
|
||||
end
|
||||
|
||||
def execute
|
||||
response = Gitlab::HTTP.get(auth_url)
|
||||
|
||||
if response.success?
|
||||
success(token: Gitlab::Json.parse(response.body)['token'])
|
||||
else
|
||||
error('Expected 200 response code for an access token', response.code)
|
||||
end
|
||||
rescue Timeout::Error => exception
|
||||
error(exception.message, 599)
|
||||
rescue JSON::ParserError
|
||||
error('Failed to parse a response body for an access token', 500)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def auth_url
|
||||
registry.auth_url(@image)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DependencyProxy::FileUploader < GitlabUploader
|
||||
include ObjectStorage::Concern
|
||||
|
||||
storage_options Gitlab.config.dependency_proxy
|
||||
|
||||
alias_method :upload, :model
|
||||
|
||||
def filename
|
||||
model.file_name
|
||||
end
|
||||
|
||||
def store_dir
|
||||
dynamic_segment
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dynamic_segment
|
||||
Gitlab::HashedPath.new('dependency_proxy', model.group_id, 'files', model.id, root_hash: model.group_id)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
- proxy_url = "#{group_url(@group)}/dependency_proxy/containers"
|
||||
|
||||
%h5.prepend-top-20= _('Dependency proxy URL')
|
||||
|
||||
.row
|
||||
.col-lg-8.col-md-12.input-group
|
||||
= text_field_tag :url, "#{proxy_url}", class: 'js-dependency-proxy-url form-control', readonly: true
|
||||
= clipboard_button(text: "#{proxy_url}", title: _("Copy %{proxy_url}") % { proxy_url: proxy_url })
|
||||
|
||||
.row
|
||||
.col-12.help-block.gl-mt-3
|
||||
= _('Contains %{count} blobs of images (%{size})') % { count: @blobs_count, size: number_to_human_size(@blobs_total_size) }
|
|
@ -0,0 +1,28 @@
|
|||
- page_title _("Dependency Proxy")
|
||||
|
||||
.settings-header
|
||||
%h4= _('Dependency proxy')
|
||||
|
||||
%p
|
||||
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('user/packages/dependency_proxy/index') }
|
||||
= _('Create a local proxy for storing frequently used upstream images. %{link_start}Learn more%{link_end} about dependency proxies.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
||||
|
||||
- if @group.public?
|
||||
- if can?(current_user, :admin_dependency_proxy, @group)
|
||||
= form_for(@dependency_proxy, method: :put, url: group_dependency_proxy_path(@group)) do |f|
|
||||
.form-group
|
||||
%h5.prepend-top-20= _('Enable proxy')
|
||||
.js-dependency-proxy-toggle-area
|
||||
= render "shared/buttons/project_feature_toggle", is_checked: @dependency_proxy.enabled?, label: s_("DependencyProxy|Toggle Dependency Proxy") do
|
||||
= f.hidden_field :enabled, { class: 'js-project-feature-toggle-input'}
|
||||
|
||||
- if @dependency_proxy.enabled
|
||||
= render 'groups/dependency_proxies/url'
|
||||
|
||||
- else
|
||||
- if @dependency_proxy.enabled
|
||||
= render 'groups/dependency_proxies/url'
|
||||
- else
|
||||
.gl-alert.gl-alert-info
|
||||
= sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
= _('Dependency proxy feature is limited to public groups for now.')
|
|
@ -1,7 +1,7 @@
|
|||
- packages_link = group_packages_list_nav? ? group_packages_path(@group) : group_container_registries_path(@group)
|
||||
|
||||
- if group_packages_nav?
|
||||
= nav_link(controller: ['groups/packages', 'groups/registry/repositories']) do
|
||||
= nav_link(controller: ['groups/packages', 'groups/registry/repositories', 'groups/dependency_proxies']) do
|
||||
= link_to packages_link, title: _('Packages') do
|
||||
.nav-icon-container
|
||||
= sprite_icon('package')
|
||||
|
@ -21,3 +21,7 @@
|
|||
= nav_link(controller: 'groups/registry/repositories') do
|
||||
= link_to group_container_registries_path(@group), title: _('Container Registry') do
|
||||
%span= _('Container Registry')
|
||||
- if group_dependency_proxy_nav?
|
||||
= nav_link(controller: 'groups/dependency_proxies') do
|
||||
= link_to group_dependency_proxy_path(@group), title: _('Dependency Proxy') do
|
||||
%span= _('Dependency Proxy')
|
||||
|
|
|
@ -451,6 +451,14 @@
|
|||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: dependency_proxy:purge_dependency_proxy_cache
|
||||
:feature_category: :dependency_proxy
|
||||
:has_external_dependencies:
|
||||
:urgency: :low
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: deployment:deployments_drop_older_deployments
|
||||
:feature_category: :continuous_delivery
|
||||
:has_external_dependencies:
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PurgeDependencyProxyCacheWorker
|
||||
include ApplicationWorker
|
||||
include Gitlab::Allowable
|
||||
idempotent!
|
||||
|
||||
queue_namespace :dependency_proxy
|
||||
feature_category :dependency_proxy
|
||||
|
||||
def perform(current_user_id, group_id)
|
||||
@current_user = User.find_by_id(current_user_id)
|
||||
@group = Group.find_by_id(group_id)
|
||||
|
||||
return unless valid?
|
||||
|
||||
@group.dependency_proxy_blobs.destroy_all # rubocop:disable Cop/DestroyAll
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid?
|
||||
return unless @group
|
||||
|
||||
can?(@current_user, :admin_group, @group) && @group.dependency_proxy_feature_available?
|
||||
end
|
||||
end
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fix Vue Labels Select dropdown keyboard scroll
|
||||
merge_request: 43874
|
||||
author:
|
||||
type: fixed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: "Hashed Storage: make migration and rollback resilient to exceptions"
|
||||
merge_request: 46178
|
||||
author:
|
||||
type: fixed
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
title: Resolve problem when namespace_settings were not created for groups created
|
||||
via admin panel
|
||||
merge_request: 46875
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Dependency proxy feature is moved to GitLab core
|
||||
merge_request: 47471
|
||||
author:
|
||||
type: changed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fix compliance framework database migration on CE instances
|
||||
merge_request: 46761
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Filter GitHub projects to import using GitHub Search API
|
||||
merge_request: 47002
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Expand scope of coverage badge query to all successful builds
|
||||
merge_request: 45321
|
||||
author:
|
||||
type: changed
|
|
@ -362,18 +362,16 @@ Settings.packages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.p
|
|||
#
|
||||
# Dependency Proxy
|
||||
#
|
||||
Gitlab.ee do
|
||||
Settings['dependency_proxy'] ||= Settingslogic.new({})
|
||||
Settings.dependency_proxy['enabled'] = true if Settings.dependency_proxy['enabled'].nil?
|
||||
Settings.dependency_proxy['storage_path'] = Settings.absolute(Settings.dependency_proxy['storage_path'] || File.join(Settings.shared['path'], "dependency_proxy"))
|
||||
Settings.dependency_proxy['object_store'] = ObjectStoreSettings.legacy_parse(Settings.dependency_proxy['object_store'])
|
||||
Settings['dependency_proxy'] ||= Settingslogic.new({})
|
||||
Settings.dependency_proxy['enabled'] = true if Settings.dependency_proxy['enabled'].nil?
|
||||
Settings.dependency_proxy['storage_path'] = Settings.absolute(Settings.dependency_proxy['storage_path'] || File.join(Settings.shared['path'], "dependency_proxy"))
|
||||
Settings.dependency_proxy['object_store'] = ObjectStoreSettings.legacy_parse(Settings.dependency_proxy['object_store'])
|
||||
|
||||
# For first iteration dependency proxy uses Rails server to download blobs.
|
||||
# To ensure acceptable performance we only allow feature to be used with
|
||||
# multithreaded web-server Puma. This will be removed once download logic is moved
|
||||
# to GitLab workhorse
|
||||
Settings.dependency_proxy['enabled'] = false unless Gitlab::Runtime.puma?
|
||||
end
|
||||
# For first iteration dependency proxy uses Rails server to download blobs.
|
||||
# To ensure acceptable performance we only allow feature to be used with
|
||||
# multithreaded web-server Puma. This will be removed once download logic is moved
|
||||
# to GitLab workhorse
|
||||
Settings.dependency_proxy['enabled'] = false unless Gitlab::Runtime.puma?
|
||||
|
||||
#
|
||||
# Terraform state
|
||||
|
|
|
@ -107,6 +107,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
|
|||
end
|
||||
|
||||
resources :container_registries, only: [:index, :show], controller: 'registry/repositories'
|
||||
resource :dependency_proxy, only: [:show, :update]
|
||||
end
|
||||
|
||||
scope(path: '*id',
|
||||
|
@ -119,3 +120,14 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
|
|||
delete '/', action: :destroy
|
||||
end
|
||||
end
|
||||
|
||||
# Dependency proxy for containers
|
||||
# Because docker adds v2 prefix to URI this need to be outside of usual group routes
|
||||
scope format: false do
|
||||
get 'v2', to: proc { [200, {}, ['']] } # rubocop:disable Cop/PutGroupRoutesUnderScope
|
||||
|
||||
constraints image: Gitlab::PathRegex.container_image_regex, sha: Gitlab::PathRegex.container_image_blob_sha_regex do
|
||||
get 'v2/*group_id/dependency_proxy/containers/*image/manifests/*tag' => 'groups/dependency_proxy_for_containers#manifest' # rubocop:todo Cop/PutGroupRoutesUnderScope
|
||||
get 'v2/*group_id/dependency_proxy/containers/*image/blobs/:sha' => 'groups/dependency_proxy_for_containers#blob' # rubocop:todo Cop/PutGroupRoutesUnderScope
|
||||
end
|
||||
end
|
||||
|
|
|
@ -232,6 +232,7 @@ Laravel
|
|||
LDAP
|
||||
ldapsearch
|
||||
Leiningen
|
||||
Lefthook
|
||||
Libravatar
|
||||
liveness
|
||||
Lograge
|
||||
|
|
|
@ -269,7 +269,7 @@ The following documentation relates to the DevOps **Package** stage:
|
|||
| Package topics | Description |
|
||||
|:----------------------------------------------------------------|:-------------------------------------------------------|
|
||||
| [Container Registry](user/packages/container_registry/index.md) | The GitLab Container Registry enables every project in GitLab to have its own space to store [Docker](https://www.docker.com/) images. |
|
||||
| [Dependency Proxy](user/packages/dependency_proxy/index.md) **(PREMIUM)** | The GitLab Dependency Proxy sets up a local proxy for frequently used upstream images/packages. |
|
||||
| [Dependency Proxy](user/packages/dependency_proxy/index.md) | The GitLab Dependency Proxy sets up a local proxy for frequently used upstream images/packages. |
|
||||
| [Package Registry](user/packages/package_registry/index.md) | Use GitLab as a private or public registry for a variety of common package managers, including [NPM](user/packages/npm_registry/index.md), [Maven](user/packages/maven_repository/index.md), [PyPI](user/packages/pypi_repository/index.md), and others. You can also store generic files. |
|
||||
|
||||
<div align="right">
|
||||
|
|
|
@ -180,6 +180,12 @@ the steps bellow.
|
|||
Feature.enable(:repository_push_audit_event)
|
||||
```
|
||||
|
||||
## Retention policy
|
||||
|
||||
On GitLab.com, Audit Event records become subject to deletion after 400 days, or when your license is downgraded to a tier that does not include access to Audit Events. Data that is subject to deletion will be deleted at GitLab's discretion, possibly without additional notice.
|
||||
|
||||
If you require a longer retention period, you should independently archive your Audit Event data, which you can retrieve through the [Audit Events API](../api/audit_events.md).
|
||||
|
||||
## Export to CSV **(PREMIUM ONLY)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1449) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
|
||||
|
|
|
@ -143,7 +143,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
|
|||
|
||||
- [Container Registry](packages/container_registry.md): Configure Container Registry with GitLab.
|
||||
- [Package Registry](packages/index.md): Enable GitLab to act as an NPM Registry and a Maven Repository.
|
||||
- [Dependency Proxy](packages/dependency_proxy.md): Configure the Dependency Proxy, a local proxy for frequently used upstream images/packages. **(PREMIUM ONLY)**
|
||||
- [Dependency Proxy](packages/dependency_proxy.md): Configure the Dependency Proxy, a local proxy for frequently used upstream images/packages.
|
||||
|
||||
### Repository settings
|
||||
|
||||
|
|
|
@ -4,9 +4,10 @@ group: Package
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# GitLab Dependency Proxy administration **(PREMIUM ONLY)**
|
||||
# GitLab Dependency Proxy administration
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) to [GitLab Core](https://about.gitlab.com/pricing/) in GitLab 13.6.
|
||||
|
||||
GitLab can be utilized as a dependency proxy for a variety of common package managers.
|
||||
|
||||
|
|
|
@ -2007,7 +2007,7 @@ on what features you intend to use:
|
|||
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
|
||||
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
|
||||
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
|
||||
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
|
||||
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
|
||||
|
|
|
@ -2007,7 +2007,7 @@ on what features you intend to use:
|
|||
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
|
||||
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
|
||||
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
|
||||
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
|
||||
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
|
||||
|
|
|
@ -866,7 +866,7 @@ on what features you intend to use:
|
|||
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
|
||||
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
|
||||
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
|
||||
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
|
||||
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
|
||||
|
|
|
@ -1742,7 +1742,7 @@ on what features you intend to use:
|
|||
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
|
||||
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
|
||||
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
|
||||
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
|
||||
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
|
||||
|
|
|
@ -2007,7 +2007,7 @@ on what features you intend to use:
|
|||
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
|
||||
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
|
||||
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
|
||||
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
|
||||
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
|
||||
|
|
|
@ -1741,7 +1741,7 @@ on what features you intend to use:
|
|||
| [Merge request diffs](../merge_request_diffs.md#using-object-storage) | Yes |
|
||||
| [Mattermost](https://docs.mattermost.com/administration/config-settings.html#file-storage)| No |
|
||||
| [Packages](../packages/index.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) **(PREMIUM ONLY)** | Yes |
|
||||
| [Dependency Proxy](../packages/dependency_proxy.md#using-object-storage) (optional feature) | Yes |
|
||||
| [Pseudonymizer](../pseudonymizer.md#configuration) (optional feature) **(ULTIMATE ONLY)** | No |
|
||||
| [Autoscale runner caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching) (optional for improved performance) | No |
|
||||
| [Terraform state files](../terraform_state.md#using-object-storage) | Yes |
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Plan
|
||||
group: Product Planning
|
||||
group: Certify
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
|
|
|
@ -4,11 +4,12 @@ group: unassigned
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Dependency Proxy API **(PREMIUM)**
|
||||
# Dependency Proxy API
|
||||
|
||||
## Purge the dependency proxy for a group
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11631) in GitLab 12.10.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11631) in GitLab 12.10.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) to [GitLab Core](https://about.gitlab.com/pricing/) in GitLab 13.6.
|
||||
|
||||
Deletes the cached blobs for a group. This endpoint requires group admin access.
|
||||
|
||||
|
|
|
@ -14967,8 +14967,7 @@ type Project {
|
|||
): ClusterAgentConnection
|
||||
|
||||
"""
|
||||
Code coverages summary associated with the project. Available only when
|
||||
feature flag `group_coverage_data_report` is enabled
|
||||
Code coverage summary associated with the project
|
||||
"""
|
||||
codeCoverageSummary: CodeCoverageSummary
|
||||
|
||||
|
|
|
@ -44149,7 +44149,7 @@
|
|||
},
|
||||
{
|
||||
"name": "codeCoverageSummary",
|
||||
"description": "Code coverages summary associated with the project. Available only when feature flag `group_coverage_data_report` is enabled",
|
||||
"description": "Code coverage summary associated with the project",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
|
|
@ -2257,7 +2257,7 @@ Autogenerated return type of PipelineRetry.
|
|||
| `boards` | BoardConnection | Boards of the project |
|
||||
| `clusterAgent` | ClusterAgent | Find a single cluster agent by name |
|
||||
| `clusterAgents` | ClusterAgentConnection | Cluster agents associated with the project |
|
||||
| `codeCoverageSummary` | CodeCoverageSummary | Code coverages summary associated with the project. Available only when feature flag `group_coverage_data_report` is enabled |
|
||||
| `codeCoverageSummary` | CodeCoverageSummary | Code coverage summary associated with the project |
|
||||
| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks associated with the project |
|
||||
| `containerExpirationPolicy` | ContainerExpirationPolicy | The container expiration policy of the project |
|
||||
| `containerRegistryEnabled` | Boolean | Indicates if the project stores Docker container images in a container registry |
|
||||
|
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 39 KiB |
|
@ -48,14 +48,16 @@ for changed files. This saves you time as you don't have to wait for the same er
|
|||
by CI/CD.
|
||||
|
||||
Lefthook relies on a pre-push hook to prevent commits that violate its ruleset.
|
||||
If you wish to override this behavior, pass the environment variable `LEFTHOOK=0`.
|
||||
That is, `LEFTHOOK=0 git push`.
|
||||
To override this behavior, pass the environment variable `LEFTHOOK=0`. That is,
|
||||
`LEFTHOOK=0 git push`.
|
||||
|
||||
You can also:
|
||||
|
||||
- Define [local configuration](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#local-config).
|
||||
- Skip [checks per tag on the fly](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#skip-some-tags-on-the-fly), e.g. `LEFTHOOK_EXCLUDE=frontend git push origin`.
|
||||
- Run [hooks manually](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#run-githook-group-directly), e.g. `lefthook run pre-push`.
|
||||
- Skip [checks per tag on the fly](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#skip-some-tags-on-the-fly).
|
||||
For example, `LEFTHOOK_EXCLUDE=frontend git push origin`.
|
||||
- Run [hooks manually](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#run-githook-group-directly).
|
||||
For example, `lefthook run pre-push`.
|
||||
|
||||
## Ruby, Rails, RSpec
|
||||
|
||||
|
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 24 KiB |
|
@ -181,7 +181,9 @@ SAST and Dependency Scanning scanners must scan the files in the project directo
|
|||
|
||||
In order to be consistent with the official Container Scanning for GitLab,
|
||||
scanners must scan the Docker image whose name and tag are given by
|
||||
`CI_APPLICATION_REPOSITORY` and `CI_APPLICATION_TAG`, respectively.
|
||||
`CI_APPLICATION_REPOSITORY` and `CI_APPLICATION_TAG`, respectively. If the `DOCKER_IMAGE`
|
||||
variable is provided, then the `CI_APPLICATION_REPOSITORY` and `CI_APPLICATION_TAG` variables
|
||||
are ignored, and the image specified in the `DOCKER_IMAGE` variable is scanned instead.
|
||||
|
||||
If not provided, `CI_APPLICATION_REPOSITORY` should default to
|
||||
`$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG`, which is a combination of predefined CI variables.
|
||||
|
|
|
@ -325,39 +325,69 @@ In general, we use an `expect` statement to check that something _is_ as we expe
|
|||
|
||||
```ruby
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
expect(pipeline).to have_job("a_job")
|
||||
expect(pipeline).to have_job('a_job')
|
||||
end
|
||||
```
|
||||
|
||||
### Ensure `expect` checks for negation efficiently
|
||||
### Create negatable matchers to speed `expect` checks
|
||||
|
||||
However, sometimes we want to check that something is _not_ as we _don't_ want it to be. In other
|
||||
words, we want to make sure something is absent. In such a case we should use an appropriate
|
||||
predicate method that returns quickly, rather than waiting for a state that won't appear.
|
||||
|
||||
It's most efficient to use a predicate method that returns immediately when there is no job, or waits
|
||||
until it disappears:
|
||||
words, we want to make sure something is absent. For unit tests and feature specs,
|
||||
we commonly use `not_to`
|
||||
because RSpec's built-in matchers are negatable, as are Capybara's, which means the following two statements are
|
||||
equivalent.
|
||||
|
||||
```ruby
|
||||
# Good
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
expect(pipeline).to have_no_job("a_job")
|
||||
except(page).not_to have_text('hidden')
|
||||
except(page).to have_no_text('hidden')
|
||||
```
|
||||
|
||||
Unfortunately, that's not automatically the case for the predicate methods that we add to our
|
||||
[page objects](page_objects.md). We need to [create our own negatable matchers](https://relishapp.com/rspec/rspec-expectations/v/3-9/docs/custom-matchers/define-a-custom-matcher#matcher-with-separate-logic-for-expect().to-and-expect().not-to).
|
||||
|
||||
The initial example uses the `have_job` matcher which is derived from the [`has_job?` predicate
|
||||
method of the `Page::Project::Pipeline::Show` page object](https://gitlab.com/gitlab-org/gitlab/-/blob/87864b3047c23b4308f59c27a3757045944af447/qa/qa/page/project/pipeline/show.rb#L53).
|
||||
To create a negatable matcher, we use `has_no_job?` for the negative case:
|
||||
|
||||
```ruby
|
||||
RSpec::Matchers.define :have_job do |job_name|
|
||||
match do |page_object|
|
||||
page_object.has_job?(job_name)
|
||||
end
|
||||
|
||||
match_when_negated do |page_object|
|
||||
page_object.has_no_job?(job_name)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Problematic alternatives
|
||||
And then the two `expect` statements in the following example are equivalent:
|
||||
|
||||
Alternatively, if we want to check that a job doesn't exist it might be tempting to use `not_to`:
|
||||
```ruby
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
expect(pipeline).not_to have_job('a_job')
|
||||
expect(pipeline).to have_no_job('a_job')
|
||||
end
|
||||
```
|
||||
|
||||
[See this merge request for a real example of adding a custom matcher](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46302).
|
||||
|
||||
NOTE: **Note:**
|
||||
We need to create custom negatable matchers only for the predicate methods we've added to the test framework, and only if we're using `not_to`. If we use `to have_no_*` a negatable matcher is not necessary.
|
||||
|
||||
### Why we need negatable matchers
|
||||
|
||||
Consider the following code, but assume that we _don't_ have a custom negatable matcher for `have_job`.
|
||||
|
||||
```ruby
|
||||
# Bad
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
expect(pipeline).not_to have_job("a_job")
|
||||
expect(pipeline).not_to have_job('a_job')
|
||||
end
|
||||
```
|
||||
|
||||
For this statement to pass, `have_job("a_job")` has to return `false` so that `not_to` can negate it.
|
||||
The problem is that `have_job("a_job")` waits up to ten seconds for `"a job"` to appear before
|
||||
For this statement to pass, `have_job('a_job')` has to return `false` so that `not_to` can negate it.
|
||||
The problem is that `have_job('a_job')` waits up to ten seconds for `'a job'` to appear before
|
||||
returning `false`. Under the expected condition this test will take ten seconds longer than it needs to.
|
||||
|
||||
Instead, we could force no wait:
|
||||
|
@ -365,9 +395,13 @@ Instead, we could force no wait:
|
|||
```ruby
|
||||
# Not as bad but potentially flaky
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
expect(pipeline).not_to have_job("a_job", wait: 0)
|
||||
expect(pipeline).not_to have_job('a_job', wait: 0)
|
||||
end
|
||||
```
|
||||
|
||||
The problem is that if `"a_job"` is present and we're waiting for it to disappear, this statement
|
||||
will fail.
|
||||
The problem is that if `'a_job'` is present and we're waiting for it to disappear, this statement will fail.
|
||||
|
||||
Neither problem is present if we create a custom negatable matcher because the `has_no_job?` predicate method
|
||||
would be used, which would wait only as long as necessary for the job to disappear.
|
||||
|
||||
Lastly, negatable matchers are preferred over using matchers of the form `have_no_*` because it's a common and familiar practice to negate matchers using `not_to`. If we facilitate that practice by adding negatable matchers, we make it easier for subsequent test authors to write efficient tests.
|
||||
|
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 45 KiB |
|
@ -186,6 +186,7 @@ scanning by using the following environment variables:
|
|||
| `CI_APPLICATION_REPOSITORY` | `$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG` | Docker repository URL for the image to be scanned. |
|
||||
| `CI_APPLICATION_TAG` | `$CI_COMMIT_SHA` | Docker repository tag for the image to be scanned. |
|
||||
| `CS_MAJOR_VERSION` | `3` | The major version of the Docker image tag. |
|
||||
| `DOCKER_IMAGE` | `$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG` | The Docker image to be scanned. If set, this variable overrides the `$CI_APPLICATION_REPOSITORY` and `$CI_APPLICATION_TAG` variables. |
|
||||
| `DOCKER_INSECURE` | `"false"` | Allow [Klar](https://github.com/optiopay/klar) to access secure Docker registries using HTTPS with bad (or self-signed) SSL certificates. |
|
||||
| `DOCKER_PASSWORD` | `$CI_REGISTRY_PASSWORD` | Password for accessing a Docker registry requiring authentication. |
|
||||
| `DOCKER_USER` | `$CI_REGISTRY_USER` | Username for accessing a Docker registry requiring authentication. |
|
||||
|
|
|
@ -412,11 +412,56 @@ spec:
|
|||
|
||||
## Example projects
|
||||
|
||||
The following example projects can help you get started with the Kubernetes Agent.
|
||||
|
||||
### Simple NGINX deployment
|
||||
|
||||
This basic GitOps example deploys NGINX:
|
||||
|
||||
- [Configuration repository](https://gitlab.com/gitlab-org/configure/examples/kubernetes-agent)
|
||||
- [Manifest repository](https://gitlab.com/gitlab-org/configure/examples/gitops-project)
|
||||
- [Install GitLab Runner](https://gitlab.com/gitlab-examples/install-runner-via-k8s-agent)
|
||||
|
||||
### Deploying GitLab Runner with the Agent
|
||||
|
||||
These instructions assume that the Agent is already set up as described in the
|
||||
[Get started with GitOps](#get-started-with-gitops-and-the-gitlab-agent):
|
||||
|
||||
1. Check the possible
|
||||
[Runner chart YAML values](https://gitlab.com/gitlab-org/charts/gitlab-runner/blob/master/values.yaml)
|
||||
on the Runner chart documentation, and create a `runner-chart-values.yaml` file
|
||||
with the configuration that fits your needs, such as:
|
||||
|
||||
```yaml
|
||||
## The GitLab Server URL (with protocol) that want to register the runner against
|
||||
## ref: https://docs.gitlab.com/runner/commands/README.html#gitlab-runner-register
|
||||
##
|
||||
gitlabUrl: https://gitlab.my.domain.com/
|
||||
|
||||
## The Registration Token for adding new Runners to the GitLab Server. This must
|
||||
## be retrieved from your GitLab Instance.
|
||||
## ref: https://docs.gitlab.com/ce/ci/runners/README.html
|
||||
##
|
||||
runnerRegistrationToken: "XXXXXXYYYYYYZZZZZZ"
|
||||
|
||||
## For RBAC support:
|
||||
rbac:
|
||||
create: true
|
||||
|
||||
## Run all containers with the privileged flag enabled
|
||||
## This will allow the docker:dind image to run if you need to run Docker
|
||||
## commands. Please read the docs before turning this on:
|
||||
## ref: https://docs.gitlab.com/runner/executors/kubernetes.html#using-dockerdind
|
||||
runners:
|
||||
privileged: true
|
||||
```
|
||||
|
||||
1. Create a single manifest file to install the Runner chart with your cluster agent:
|
||||
|
||||
```shell
|
||||
helm template --namespace gitlab gitlab-runner -f runner-chart-values.yaml gitlab/gitlab-runner > manifest.yaml
|
||||
```
|
||||
|
||||
1. Push your `manifest.yaml` to your manifest repository.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
@ -479,7 +524,7 @@ but KAS on the server side is not available via `wss`. To fix it, make sure the
|
|||
same schemes are configured on both sides.
|
||||
|
||||
It's not possible to set the `grpc` scheme due to the issue
|
||||
[It is not possible to configure KAS to work with grpc without directly editing GitLab KAS deployment](https://gitlab.com/gitlab-org/gitlab/-/issues/276888). To use `grpc` while the
|
||||
[It is not possible to configure KAS to work with `grpc` without directly editing GitLab KAS deployment](https://gitlab.com/gitlab-org/gitlab/-/issues/276888). To use `grpc` while the
|
||||
issue is in progress, directly edit the deployment with the
|
||||
`kubectl edit deployment gitlab-kas` command, and change `--listen-websocket=true` to `--listen-websocket=false`. After running that command, you should be able to use
|
||||
`grpc://gitlab-kas.<YOUR-NAMESPACE>:5005`.
|
||||
|
|
Before Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 63 KiB |
|
@ -17,7 +17,7 @@ for merging into production.
|
|||
|
||||
To access the Compliance Dashboard for a group, navigate to **{shield}** **Security & Compliance > Compliance** on the group's menu.
|
||||
|
||||
![Compliance Dashboard](img/compliance_dashboard_v13_3_1.png)
|
||||
![Compliance Dashboard](img/compliance_dashboard_v13_6.png)
|
||||
|
||||
NOTE: **Note:**
|
||||
The Compliance Dashboard shows only the latest MR on each project.
|
||||
|
@ -63,7 +63,9 @@ This column has four states:
|
|||
If you do not see the success icon in your Compliance dashboard; please review the above criteria for the Merge Requests
|
||||
project to make sure it complies with the separation of duties described above.
|
||||
|
||||
## Chain of Custody report
|
||||
## Chain of Custody report **(ULTIMATE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213364) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.3.
|
||||
|
||||
The Chain of Custody report allows customers to export a list of merge commits within the group.
|
||||
The data provides a comprehensive view with respect to merge commits. It includes the merge commit SHA,
|
||||
|
@ -72,6 +74,13 @@ Depending on the merge strategy, the merge commit SHA can either be a merge comm
|
|||
|
||||
To download the Chain of Custody report, navigate to **{shield}** **Security & Compliance > Compliance** on the group's menu and click **List of all merge commits**
|
||||
|
||||
### Commit-specific Chain of Custody Report **(ULTIMATE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267629) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.6.
|
||||
|
||||
You can generate a commit-specific Chain of Custody report for a given commit SHA. To do so, select
|
||||
the dropdown next to the **List of all merge commits** button at the top of the Compliance Dashboard.
|
||||
|
||||
NOTE: **Note:**
|
||||
The Chain of Custody report download is a CSV file, with a maximum size of 15 MB.
|
||||
The remaining records are truncated when this limit is reached.
|
||||
|
|
|
@ -798,7 +798,7 @@ With [GitLab Issue Analytics](issues_analytics/index.md), you can see a bar char
|
|||
|
||||
With [GitLab Repositories Analytics](repositories_analytics/index.md), you can download a CSV of the latest coverage data for all the projects in your group.
|
||||
|
||||
## Dependency Proxy **(PREMIUM)**
|
||||
## Dependency Proxy
|
||||
|
||||
Use GitLab as a [dependency proxy](../packages/dependency_proxy/index.md) for upstream Docker images.
|
||||
|
||||
|
|
|
@ -15,22 +15,12 @@ This feature might not be available to you. Check the **version history** note a
|
|||
## Latest project test coverage list
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267624) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.6.
|
||||
> - It's [deployed behind a feature flag](../../../user/feature_flags.md), disabled by default.
|
||||
> - It's disabled on GitLab.com
|
||||
> - It can be enabled or disabled per-group.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-latest-project-test-coverage).
|
||||
|
||||
To see the latest code coverage for each project in your group:
|
||||
|
||||
1. Go to **Analytics > Repositories** in the group (not from a project).
|
||||
1. In the **Latest test coverage results** section, use the **Select projects** dropdown to choose the projects you want to check.
|
||||
|
||||
### Enable or disable latest project test coverage
|
||||
|
||||
This feature comes with the `:group_coverage_data_report` feature flag disabled by default. It is disabled on GitLab.com.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can enable it for your instance.
|
||||
The group test coverage table can be enabled or disabled per-group.
|
||||
|
||||
## Download historic test coverage data
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215104) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
|
||||
|
|
|
@ -4,9 +4,10 @@ group: Package
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Dependency Proxy **(PREMIUM)**
|
||||
# Dependency Proxy
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) to [GitLab Core](https://about.gitlab.com/pricing/) in GitLab 13.6.
|
||||
|
||||
The GitLab Dependency Proxy is a local proxy you can use for your frequently-accessed
|
||||
upstream images.
|
||||
|
|
|
@ -256,7 +256,7 @@ group.
|
|||
| Share (invite) groups with groups | | | | | ✓ |
|
||||
| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
|
||||
| Create/edit/delete iterations | | | ✓ | ✓ | ✓ |
|
||||
| Enable/disable a dependency proxy **(PREMIUM)** | | | ✓ | ✓ | ✓ |
|
||||
| Enable/disable a dependency proxy | | | ✓ | ✓ | ✓ |
|
||||
| Create and edit group wiki pages **(PREMIUM)** | | | ✓ | ✓ | ✓ |
|
||||
| Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
|
||||
| Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ |
|
||||
|
|
After Width: | Height: | Size: 40 KiB |
|
@ -188,17 +188,22 @@ To set your current status:
|
|||
1. Set the desired emoji and/or status message.
|
||||
1. Click **Set status**. Alternatively, you can click **Remove status** to remove your user status entirely.
|
||||
|
||||
![Busy status indicator](img/busy_status_indicator_v13_6.png)
|
||||
|
||||
or
|
||||
|
||||
1. Click your avatar.
|
||||
1. Select **Profile**.
|
||||
1. Click **Edit profile** (pencil icon).
|
||||
1. Enter your status message in the **Your status** text field.
|
||||
1. Alternatively, select the **Busy** checkbox ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259649) in GitLab 13.6}.
|
||||
1. Click **Add status emoji** (smiley face), and select the desired emoji.
|
||||
1. Click **Update profile settings**.
|
||||
|
||||
You can also set your current status [using the API](../../api/users.md#user-status).
|
||||
|
||||
If you previously selected the "Busy" checkbox, remember to deselect it when you become available again.
|
||||
|
||||
## Commit email
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/21598) in GitLab 11.4.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
type: reference, howto
|
||||
stage: Plan
|
||||
group: Product Planning
|
||||
group: Certify
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Plan
|
||||
group: Product Planning
|
||||
group: Certify
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
|
|
|
@ -162,6 +162,7 @@ module API
|
|||
mount ::API::CommitStatuses
|
||||
mount ::API::ContainerRegistryEvent
|
||||
mount ::API::ContainerRepositories
|
||||
mount ::API::DependencyProxy
|
||||
mount ::API::DeployKeys
|
||||
mount ::API::DeployTokens
|
||||
mount ::API::Deployments
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
class DependencyProxy < ::API::Base
|
||||
helpers ::API::Helpers::PackagesHelpers
|
||||
|
||||
feature_category :dependency_proxy
|
||||
|
||||
helpers do
|
||||
def obtain_new_purge_cache_lease
|
||||
Gitlab::ExclusiveLease
|
||||
.new("dependency_proxy:delete_group_blobs:#{user_group.id}",
|
||||
timeout: 1.hour)
|
||||
.try_obtain
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
authorize! :admin_group, user_group
|
||||
end
|
||||
|
||||
params do
|
||||
requires :id, type: String, desc: 'The ID of a group'
|
||||
end
|
||||
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
|
||||
desc 'Deletes all dependency_proxy_blobs for a group' do
|
||||
detail 'This feature was introduced in GitLab 12.10'
|
||||
end
|
||||
delete ':id/dependency_proxy/cache' do
|
||||
not_found! unless user_group.dependency_proxy_feature_available?
|
||||
|
||||
message = 'This request has already been made. It may take some time to purge the cache. You can run this at most once an hour for a given group'
|
||||
render_api_error!(message, 409) unless obtain_new_purge_cache_lease
|
||||
|
||||
# rubocop:disable CodeReuse/Worker
|
||||
PurgeDependencyProxyCacheWorker.perform_async(current_user.id, user_group.id)
|
||||
# rubocop:enable CodeReuse/Worker
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -40,23 +40,35 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def pipeline
|
||||
@pipeline ||= @project.ci_pipelines.latest_successful_for_ref(@ref)
|
||||
def successful_pipeline
|
||||
@successful_pipeline ||= @project.ci_pipelines.latest_successful_for_ref(@ref)
|
||||
end
|
||||
|
||||
def failed_pipeline
|
||||
@failed_pipeline ||= @project.ci_pipelines.latest_failed_for_ref(@ref)
|
||||
end
|
||||
|
||||
def running_pipeline
|
||||
@running_pipeline ||= @project.ci_pipelines.latest_running_for_ref(@ref)
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def raw_coverage
|
||||
return unless pipeline
|
||||
latest =
|
||||
if @job.present?
|
||||
builds = ::Ci::Build
|
||||
.in_pipelines([successful_pipeline, running_pipeline, failed_pipeline])
|
||||
.latest
|
||||
.success
|
||||
.for_ref(@ref)
|
||||
.by_name(@job)
|
||||
|
||||
if @job.blank?
|
||||
pipeline.coverage
|
||||
else
|
||||
pipeline.builds
|
||||
.find_by(name: @job)
|
||||
.try(:coverage)
|
||||
end
|
||||
builds.max_by(&:created_at)
|
||||
else
|
||||
successful_pipeline
|
||||
end
|
||||
|
||||
latest&.coverage
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,6 +18,8 @@ module Gitlab
|
|||
|
||||
attr_reader :octokit
|
||||
|
||||
SEARCH_MAX_REQUESTS_PER_MINUTE = 30
|
||||
|
||||
# A single page of data and the corresponding page number.
|
||||
Page = Struct.new(:objects, :number)
|
||||
|
||||
|
@ -28,6 +30,7 @@ module Gitlab
|
|||
# rate limit at once. The threshold is put in place to not hit the limit
|
||||
# in most cases.
|
||||
RATE_LIMIT_THRESHOLD = 50
|
||||
SEARCH_RATE_LIMIT_THRESHOLD = 3
|
||||
|
||||
# token - The GitHub API token to use.
|
||||
#
|
||||
|
@ -152,8 +155,26 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def search_repos_by_name(name)
|
||||
each_page(:search_repositories, search_query(str: name, type: :name))
|
||||
end
|
||||
|
||||
def search_query(str:, type:, include_collaborations: true, include_orgs: true)
|
||||
query = "#{str} in:#{type} is:public,private user:#{octokit.user.login}"
|
||||
|
||||
query = [query, collaborations_subquery].join(' ') if include_collaborations
|
||||
query = [query, organizations_subquery].join(' ') if include_orgs
|
||||
|
||||
query
|
||||
end
|
||||
|
||||
# Returns `true` if we're still allowed to perform API calls.
|
||||
# Search API has rate limit of 30, use lowered threshold when search is used.
|
||||
def requests_remaining?
|
||||
if requests_limit == SEARCH_MAX_REQUESTS_PER_MINUTE
|
||||
return remaining_requests > SEARCH_RATE_LIMIT_THRESHOLD
|
||||
end
|
||||
|
||||
remaining_requests > RATE_LIMIT_THRESHOLD
|
||||
end
|
||||
|
||||
|
@ -161,6 +182,10 @@ module Gitlab
|
|||
octokit.rate_limit.remaining
|
||||
end
|
||||
|
||||
def requests_limit
|
||||
octokit.rate_limit.limit
|
||||
end
|
||||
|
||||
def raise_or_wait_for_rate_limit
|
||||
rate_limit_counter.increment
|
||||
|
||||
|
@ -221,6 +246,20 @@ module Gitlab
|
|||
'The number of GitHub API calls performed when importing projects'
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def collaborations_subquery
|
||||
each_object(:repos, nil, { affiliation: 'collaborator' })
|
||||
.map { |repo| "repo:#{repo.full_name}" }
|
||||
.join(' ')
|
||||
end
|
||||
|
||||
def organizations_subquery
|
||||
each_object(:organizations)
|
||||
.map { |org| "org:#{org.login}" }
|
||||
.join(' ')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|