Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3feea9b607
commit
d8714cf67c
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -2,6 +2,17 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 13.8.2 (2021-02-01)
|
||||||
|
|
||||||
|
### Security (5 changes)
|
||||||
|
|
||||||
|
- Filter sensitive GraphQL variables from logs.
|
||||||
|
- Avoid exposing release links when the user cannot read git-tag/repository.
|
||||||
|
- Sanitize target branch on MR page.
|
||||||
|
- Fix DNS rebinding protection bypass when allowing an IP address in Outbound Requests setting.
|
||||||
|
- Add routes for unmatched url for not-get requests.
|
||||||
|
|
||||||
|
|
||||||
## 13.8.1 (2021-01-26)
|
## 13.8.1 (2021-01-26)
|
||||||
|
|
||||||
### Fixed (3 changes)
|
### Fixed (3 changes)
|
||||||
|
@ -368,6 +379,17 @@ entry.
|
||||||
- Add verbiage + link sast to show it's in core. !51935
|
- Add verbiage + link sast to show it's in core. !51935
|
||||||
|
|
||||||
|
|
||||||
|
## 13.7.6 (2021-02-01)
|
||||||
|
|
||||||
|
### Security (5 changes)
|
||||||
|
|
||||||
|
- Filter sensitive GraphQL variables from logs.
|
||||||
|
- Avoid exposing release links when the user cannot read git-tag/repository.
|
||||||
|
- Sanitize target branch on MR page.
|
||||||
|
- Fix DNS rebinding protection bypass when allowing an IP address in Outbound Requests setting.
|
||||||
|
- Add routes for unmatched url for not-get requests.
|
||||||
|
|
||||||
|
|
||||||
## 13.7.5 (2021-01-25)
|
## 13.7.5 (2021-01-25)
|
||||||
|
|
||||||
### Fixed (2 changes, 1 of them is from the community)
|
### Fixed (2 changes, 1 of them is from the community)
|
||||||
|
@ -878,6 +900,17 @@ entry.
|
||||||
- Update GitLab Workhorse to v8.57.0.
|
- Update GitLab Workhorse to v8.57.0.
|
||||||
|
|
||||||
|
|
||||||
|
## 13.6.6 (2021-02-01)
|
||||||
|
|
||||||
|
### Security (5 changes)
|
||||||
|
|
||||||
|
- Filter sensitive GraphQL variables from logs.
|
||||||
|
- Avoid exposing release links when the user cannot read git-tag/repository.
|
||||||
|
- Sanitize target branch on MR page.
|
||||||
|
- Fix DNS rebinding protection bypass when allowing an IP address in Outbound Requests setting.
|
||||||
|
- Add routes for unmatched url for not-get requests.
|
||||||
|
|
||||||
|
|
||||||
## 13.6.5 (2021-01-13)
|
## 13.6.5 (2021-01-13)
|
||||||
|
|
||||||
### Security (1 change)
|
### Security (1 change)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
9ed0124f6cdfc359521feae325420549781d883e
|
aeec1e34a8f0fc6b453b7f091e3712f17956b580
|
||||||
|
|
|
@ -25,19 +25,17 @@ initPageShortcuts();
|
||||||
initCollapseSidebarOnWindowResize();
|
initCollapseSidebarOnWindowResize();
|
||||||
initSelect2Dropdowns();
|
initSelect2Dropdowns();
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
window.requestIdleCallback(
|
||||||
window.requestIdleCallback(
|
() => {
|
||||||
() => {
|
// Check if we have to Load GFM Input
|
||||||
// Check if we have to Load GFM Input
|
const $gfmInputs = $('.js-gfm-input:not(.js-gfm-input-initialized)');
|
||||||
const $gfmInputs = $('.js-gfm-input:not(.js-gfm-input-initialized)');
|
if ($gfmInputs.length) {
|
||||||
if ($gfmInputs.length) {
|
import(/* webpackChunkName: 'initGFMInput' */ './markdown/gfm_auto_complete')
|
||||||
import(/* webpackChunkName: 'initGFMInput' */ './markdown/gfm_auto_complete')
|
.then(({ default: initGFMInput }) => {
|
||||||
.then(({ default: initGFMInput }) => {
|
initGFMInput($gfmInputs);
|
||||||
initGFMInput($gfmInputs);
|
})
|
||||||
})
|
.catch(() => {});
|
||||||
.catch(() => {});
|
}
|
||||||
}
|
},
|
||||||
},
|
{ timeout: 500 },
|
||||||
{ timeout: 500 },
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { isNumber } from 'lodash';
|
import { isNumber } from 'lodash';
|
||||||
|
import { sanitize } from '~/lib/dompurify';
|
||||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
import ArtifactsApp from './artifacts_list_app.vue';
|
import ArtifactsApp from './artifacts_list_app.vue';
|
||||||
import MrWidgetContainer from './mr_widget_container.vue';
|
import MrWidgetContainer from './mr_widget_container.vue';
|
||||||
|
@ -40,7 +41,7 @@ export default {
|
||||||
return this.isPostMerge ? this.mr.targetBranch : this.mr.sourceBranch;
|
return this.isPostMerge ? this.mr.targetBranch : this.mr.sourceBranch;
|
||||||
},
|
},
|
||||||
branchLink() {
|
branchLink() {
|
||||||
return this.isPostMerge ? this.mr.targetBranch : this.mr.sourceBranchLink;
|
return this.isPostMerge ? sanitize(this.mr.targetBranch) : this.mr.sourceBranchLink;
|
||||||
},
|
},
|
||||||
deployments() {
|
deployments() {
|
||||||
return this.isPostMerge ? this.mr.postMergeDeployments : this.mr.deployments;
|
return this.isPostMerge ? this.mr.postMergeDeployments : this.mr.deployments;
|
||||||
|
|
|
@ -5,6 +5,9 @@ class Projects::ReleasesController < Projects::ApplicationController
|
||||||
before_action :require_non_empty_project, except: [:index]
|
before_action :require_non_empty_project, except: [:index]
|
||||||
before_action :release, only: %i[edit show update downloads]
|
before_action :release, only: %i[edit show update downloads]
|
||||||
before_action :authorize_read_release!
|
before_action :authorize_read_release!
|
||||||
|
# We have to check `download_code` permission because detail URL path
|
||||||
|
# contains git-tag name.
|
||||||
|
before_action :authorize_download_code!, except: [:index]
|
||||||
before_action do
|
before_action do
|
||||||
push_frontend_feature_flag(:graphql_release_data, project, default_enabled: true)
|
push_frontend_feature_flag(:graphql_release_data, project, default_enabled: true)
|
||||||
push_frontend_feature_flag(:graphql_milestone_stats, project, default_enabled: true)
|
push_frontend_feature_flag(:graphql_milestone_stats, project, default_enabled: true)
|
||||||
|
|
|
@ -85,10 +85,18 @@ module TokenAuthenticatableStrategies
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_by_encrypted_token(token, unscoped)
|
def find_by_encrypted_token(token, unscoped)
|
||||||
encrypted_value = Gitlab::CryptoHelper.aes256_gcm_encrypt(token)
|
nonce = Feature.enabled?(:dynamic_nonce_creation) ? find_hashed_iv(token) : Gitlab::CryptoHelper::AES256_GCM_IV_STATIC
|
||||||
|
encrypted_value = Gitlab::CryptoHelper.aes256_gcm_encrypt(token, nonce: nonce)
|
||||||
|
|
||||||
relation(unscoped).find_by(encrypted_field => encrypted_value)
|
relation(unscoped).find_by(encrypted_field => encrypted_value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_hashed_iv(token)
|
||||||
|
token_record = TokenWithIv.find_by_plaintext_token(token)
|
||||||
|
|
||||||
|
token_record&.iv || Gitlab::CryptoHelper::AES256_GCM_IV_STATIC
|
||||||
|
end
|
||||||
|
|
||||||
def insecure_strategy
|
def insecure_strategy
|
||||||
@insecure_strategy ||= TokenAuthenticatableStrategies::Insecure
|
@insecure_strategy ||= TokenAuthenticatableStrategies::Insecure
|
||||||
.new(klass, token_field, options)
|
.new(klass, token_field, options)
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Packages
|
||||||
|
module Composer
|
||||||
|
class CacheFile < ApplicationRecord
|
||||||
|
include FileStoreMounter
|
||||||
|
|
||||||
|
self.table_name = 'packages_composer_cache_files'
|
||||||
|
|
||||||
|
mount_file_store_uploader Packages::Composer::CacheUploader
|
||||||
|
|
||||||
|
belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id'
|
||||||
|
belongs_to :namespace
|
||||||
|
|
||||||
|
validates :namespace, presence: true
|
||||||
|
|
||||||
|
scope :with_namespace, ->(namespace) { where(namespace: namespace) }
|
||||||
|
scope :with_sha, ->(sha) { where(file_sha256: sha) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -9,6 +9,9 @@ module Packages
|
||||||
belongs_to :package, -> { where(package_type: :composer) }, inverse_of: :composer_metadatum
|
belongs_to :package, -> { where(package_type: :composer) }, inverse_of: :composer_metadatum
|
||||||
|
|
||||||
validates :package, :target_sha, :composer_json, presence: true
|
validates :package, :target_sha, :composer_json, presence: true
|
||||||
|
|
||||||
|
scope :for_package, ->(name, project_id) { joins(:package).where(packages_packages: { name: name, project_id: project_id, package_type: Packages::Package.package_types[:composer] }) }
|
||||||
|
scope :locked_for_update, -> { lock('FOR UPDATE') }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# rubocop: todo Gitlab/NamespacedClass
|
||||||
|
class TokenWithIv < ApplicationRecord
|
||||||
|
validates :hashed_token, presence: true
|
||||||
|
validates :iv, presence: true
|
||||||
|
validates :hashed_plaintext_token, presence: true
|
||||||
|
|
||||||
|
def self.find_by_hashed_token(value)
|
||||||
|
find_by(hashed_token: ::Digest::SHA256.digest(value))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.find_by_plaintext_token(value)
|
||||||
|
find_by(hashed_plaintext_token: ::Digest::SHA256.digest(value))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.find_nonce_by_hashed_token(value)
|
||||||
|
return unless table_exists?
|
||||||
|
|
||||||
|
token_record = find_by_hashed_token(value)
|
||||||
|
token_record&.iv
|
||||||
|
end
|
||||||
|
end
|
|
@ -20,6 +20,8 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
|
||||||
end
|
end
|
||||||
|
|
||||||
def self_url
|
def self_url
|
||||||
|
return unless can_download_code?
|
||||||
|
|
||||||
project_release_url(project, release)
|
project_release_url(project, release)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -56,11 +56,25 @@ module Projects
|
||||||
raise ValidationError.new(s_('UpdateProject|Cannot rename project because it contains container registry tags!'))
|
raise ValidationError.new(s_('UpdateProject|Cannot rename project because it contains container registry tags!'))
|
||||||
end
|
end
|
||||||
|
|
||||||
if changing_default_branch?
|
validate_default_branch_change
|
||||||
raise ValidationError.new(s_("UpdateProject|Could not set the default branch")) unless project.change_head(params[:default_branch])
|
end
|
||||||
|
|
||||||
|
def validate_default_branch_change
|
||||||
|
return unless changing_default_branch?
|
||||||
|
|
||||||
|
previous_default_branch = project.default_branch
|
||||||
|
|
||||||
|
if project.change_head(params[:default_branch])
|
||||||
|
after_default_branch_change(previous_default_branch)
|
||||||
|
else
|
||||||
|
raise ValidationError.new(s_("UpdateProject|Could not set the default branch"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def after_default_branch_change(previous_default_branch)
|
||||||
|
# overridden by EE module
|
||||||
|
end
|
||||||
|
|
||||||
def remove_unallowed_params
|
def remove_unallowed_params
|
||||||
params.delete(:emails_disabled) unless can?(current_user, :set_emails_disabled, project)
|
params.delete(:emails_disabled) unless can?(current_user, :set_emails_disabled, project)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class Packages::Composer::CacheUploader < GitlabUploader
|
||||||
|
include ObjectStorage::Concern
|
||||||
|
|
||||||
|
storage_options Gitlab.config.packages
|
||||||
|
|
||||||
|
after :store, :schedule_background_upload
|
||||||
|
|
||||||
|
alias_method :upload, :model
|
||||||
|
|
||||||
|
def filename
|
||||||
|
"#{model.file_sha256}.json"
|
||||||
|
end
|
||||||
|
|
||||||
|
def store_dir
|
||||||
|
dynamic_segment
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def dynamic_segment
|
||||||
|
raise ObjectNotReadyError, 'Package model not ready' unless model.id
|
||||||
|
|
||||||
|
Gitlab::HashedPath.new("packages", "composer_cache", model.namespace_id, root_hash: model.namespace_id)
|
||||||
|
end
|
||||||
|
end
|
|
@ -99,11 +99,11 @@
|
||||||
%p
|
%p
|
||||||
- freeze_period_docs = help_page_path('user/project/releases/index', anchor: 'prevent-unintentional-releases-by-setting-a-deploy-freeze')
|
- freeze_period_docs = help_page_path('user/project/releases/index', anchor: 'prevent-unintentional-releases-by-setting-a-deploy-freeze')
|
||||||
- freeze_period_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: freeze_period_docs }
|
- freeze_period_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: freeze_period_docs }
|
||||||
= html_escape(s_('DeployFreeze|Specify times when deployments are not allowed for an environment. The %{filename} file must be updated to make deployment jobs aware of the %{freeze_period_link_start}freeze period%{freeze_period_link_end}.')) % { freeze_period_link_start: freeze_period_link_start, freeze_period_link_end: '</a>'.html_safe, filename: tag.code('gitlab-ci.yml') }
|
= html_escape(s_('DeployFreeze|Add a freeze period to prevent unintended releases during a period of time for a given environment. You must update the deployment jobs in %{filename} according to the deploy freezes added here. %{freeze_period_link_start}Learn more.%{freeze_period_link_end}')) % { freeze_period_link_start: freeze_period_link_start, freeze_period_link_end: '</a>'.html_safe, filename: tag.code('.gitlab-ci.yml') }
|
||||||
|
|
||||||
- cron_syntax_url = 'https://crontab.guru/'
|
- cron_syntax_url = 'https://crontab.guru/'
|
||||||
- cron_syntax_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: cron_syntax_url }
|
- cron_syntax_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: cron_syntax_url }
|
||||||
= s_('DeployFreeze|You can specify deploy freezes using only %{cron_syntax_link_start}cron syntax%{cron_syntax_link_end}.').html_safe % { cron_syntax_link_start: cron_syntax_link_start, cron_syntax_link_end: "</a>".html_safe }
|
= s_('DeployFreeze|Specify deploy freezes using %{cron_syntax_link_start}cron syntax%{cron_syntax_link_end}.').html_safe % { cron_syntax_link_start: cron_syntax_link_start, cron_syntax_link_end: "</a>".html_safe }
|
||||||
|
|
||||||
.settings-content
|
.settings-content
|
||||||
= render 'ci/deploy_freeze/index'
|
= render 'ci/deploy_freeze/index'
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add Composer cache classes and table
|
||||||
|
merge_request: 51509
|
||||||
|
author:
|
||||||
|
type: other
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Review UI text - deploy freezes
|
||||||
|
merge_request: 52884
|
||||||
|
author:
|
||||||
|
type: other
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add token_with_iv table
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: security
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
name: dynamic_nonce_creation
|
||||||
|
introduced_by_url:
|
||||||
|
rollout_issue_url:
|
||||||
|
milestone: '13.9'
|
||||||
|
type: development
|
||||||
|
group: group::manage
|
||||||
|
default_enabled: false
|
|
@ -21,9 +21,9 @@ class NoopCompiler {
|
||||||
setupMiddleware() {}
|
setupMiddleware() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IncrementalWebpackCompiler extends NoopCompiler {
|
class IncrementalWebpackCompiler {
|
||||||
constructor(historyFilePath) {
|
constructor(historyFilePath) {
|
||||||
super();
|
this.enabled = true;
|
||||||
this.history = {};
|
this.history = {};
|
||||||
this.compiledEntryPoints = new Set([
|
this.compiledEntryPoints = new Set([
|
||||||
// Login page
|
// Login page
|
||||||
|
@ -32,8 +32,7 @@ class IncrementalWebpackCompiler extends NoopCompiler {
|
||||||
'pages.root',
|
'pages.root',
|
||||||
]);
|
]);
|
||||||
this.historyFilePath = historyFilePath;
|
this.historyFilePath = historyFilePath;
|
||||||
this.loadFromHistory();
|
this._loadFromHistory();
|
||||||
this.enabled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filterEntryPoints(entrypoints) {
|
filterEntryPoints(entrypoints) {
|
||||||
|
@ -65,7 +64,7 @@ class IncrementalWebpackCompiler extends NoopCompiler {
|
||||||
if (fileName.startsWith('pages.') && fileName.endsWith('.chunk.js')) {
|
if (fileName.startsWith('pages.') && fileName.endsWith('.chunk.js')) {
|
||||||
const chunk = fileName.replace(/\.chunk\.js$/, '');
|
const chunk = fileName.replace(/\.chunk\.js$/, '');
|
||||||
|
|
||||||
this.addToHistory(chunk);
|
this._addToHistory(chunk);
|
||||||
|
|
||||||
if (!this.compiledEntryPoints.has(chunk)) {
|
if (!this.compiledEntryPoints.has(chunk)) {
|
||||||
log(`First time we are seeing ${chunk}. Adding to compilation.`);
|
log(`First time we are seeing ${chunk}. Adding to compilation.`);
|
||||||
|
@ -88,7 +87,7 @@ class IncrementalWebpackCompiler extends NoopCompiler {
|
||||||
|
|
||||||
// private methods
|
// private methods
|
||||||
|
|
||||||
addToHistory(chunk) {
|
_addToHistory(chunk) {
|
||||||
if (!this.history[chunk]) {
|
if (!this.history[chunk]) {
|
||||||
this.history[chunk] = { lastVisit: null, count: 0 };
|
this.history[chunk] = { lastVisit: null, count: 0 };
|
||||||
}
|
}
|
||||||
|
@ -102,7 +101,7 @@ class IncrementalWebpackCompiler extends NoopCompiler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFromHistory() {
|
_loadFromHistory() {
|
||||||
try {
|
try {
|
||||||
this.history = JSON.parse(fs.readFileSync(this.historyFilePath, 'utf8'));
|
this.history = JSON.parse(fs.readFileSync(this.historyFilePath, 'utf8'));
|
||||||
const entryPoints = Object.keys(this.history);
|
const entryPoints = Object.keys(this.history);
|
||||||
|
|
|
@ -544,9 +544,12 @@ Settings.cron_jobs['schedule_merge_request_cleanup_refs_worker']['job_class'] =
|
||||||
Settings.cron_jobs['manage_evidence_worker'] ||= Settingslogic.new({})
|
Settings.cron_jobs['manage_evidence_worker'] ||= Settingslogic.new({})
|
||||||
Settings.cron_jobs['manage_evidence_worker']['cron'] ||= '0 * * * *'
|
Settings.cron_jobs['manage_evidence_worker']['cron'] ||= '0 * * * *'
|
||||||
Settings.cron_jobs['manage_evidence_worker']['job_class'] = 'Releases::ManageEvidenceWorker'
|
Settings.cron_jobs['manage_evidence_worker']['job_class'] = 'Releases::ManageEvidenceWorker'
|
||||||
Settings.cron_jobs['namespaces_in_product_marketing_emails_worker'] ||= Settingslogic.new({})
|
|
||||||
Settings.cron_jobs['namespaces_in_product_marketing_emails_worker']['cron'] ||= '0 9 * * *'
|
Gitlab.com do
|
||||||
Settings.cron_jobs['namespaces_in_product_marketing_emails_worker']['job_class'] = 'Namespaces::InProductMarketingEmailsWorker'
|
Settings.cron_jobs['namespaces_in_product_marketing_emails_worker'] ||= Settingslogic.new({})
|
||||||
|
Settings.cron_jobs['namespaces_in_product_marketing_emails_worker']['cron'] ||= '0 9 * * *'
|
||||||
|
Settings.cron_jobs['namespaces_in_product_marketing_emails_worker']['job_class'] = 'Namespaces::InProductMarketingEmailsWorker'
|
||||||
|
end
|
||||||
|
|
||||||
Gitlab.ee do
|
Gitlab.ee do
|
||||||
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({})
|
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({})
|
||||||
|
|
|
@ -277,6 +277,7 @@ Rails.application.routes.draw do
|
||||||
draw :dashboard
|
draw :dashboard
|
||||||
draw :user
|
draw :user
|
||||||
draw :project
|
draw :project
|
||||||
|
draw :unmatched_project
|
||||||
|
|
||||||
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/210024
|
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/210024
|
||||||
scope as: 'deprecated' do
|
scope as: 'deprecated' do
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
scope(path: '*namespace_id',
|
||||||
|
as: :namespace,
|
||||||
|
namespace_id: Gitlab::PathRegex.full_namespace_route_regex) do
|
||||||
|
scope(path: ':project_id',
|
||||||
|
constraints: { project_id: Gitlab::PathRegex.project_route_regex },
|
||||||
|
as: :project) do
|
||||||
|
post '*all', to: 'application#route_not_found'
|
||||||
|
put '*all', to: 'application#route_not_found'
|
||||||
|
patch '*all', to: 'application#route_not_found'
|
||||||
|
delete '*all', to: 'application#route_not_found'
|
||||||
|
post '/', to: 'application#route_not_found'
|
||||||
|
put '/', to: 'application#route_not_found'
|
||||||
|
patch '/', to: 'application#route_not_found'
|
||||||
|
delete '/', to: 'application#route_not_found'
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateTokensWithIv < ActiveRecord::Migration[6.0]
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
def change
|
||||||
|
create_table :token_with_ivs do |t|
|
||||||
|
t.binary :hashed_token, null: false
|
||||||
|
t.binary :hashed_plaintext_token, null: false
|
||||||
|
t.binary :iv, null: false
|
||||||
|
|
||||||
|
t.index :hashed_token, name: 'index_token_with_ivs_on_hashed_token', unique: true, using: :btree
|
||||||
|
t.index :hashed_plaintext_token, name: 'index_token_with_ivs_on_hashed_plaintext_token', unique: true, using: :btree
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,34 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateComposerCacheFile < ActiveRecord::Migration[6.0]
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
def up
|
||||||
|
# rubocop:disable Migration/AddLimitToTextColumns
|
||||||
|
create_table_with_constraints :packages_composer_cache_files do |t|
|
||||||
|
t.timestamps_with_timezone
|
||||||
|
|
||||||
|
# record can be deleted after `delete_at`
|
||||||
|
t.datetime_with_timezone :delete_at
|
||||||
|
|
||||||
|
# which namespace it belongs to
|
||||||
|
t.integer :namespace_id, null: true
|
||||||
|
|
||||||
|
# file storage related fields
|
||||||
|
t.integer :file_store, limit: 2, null: false, default: 1
|
||||||
|
t.text :file, null: false
|
||||||
|
t.binary :file_sha256, null: false
|
||||||
|
|
||||||
|
t.index [:namespace_id, :file_sha256], name: "index_packages_composer_cache_namespace_and_sha", using: :btree, unique: true
|
||||||
|
t.foreign_key :namespaces, column: :namespace_id, on_delete: :nullify
|
||||||
|
|
||||||
|
t.text_limit :file, 255
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
drop_table :packages_composer_cache_files
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@ class EncryptFeatureFlagsClientsTokens < ActiveRecord::Migration[5.1]
|
||||||
def up
|
def up
|
||||||
say_with_time("Encrypting tokens from operations_feature_flags_clients") do
|
say_with_time("Encrypting tokens from operations_feature_flags_clients") do
|
||||||
FeatureFlagsClient.where('token_encrypted is NULL AND token IS NOT NULL').find_each do |feature_flags_client|
|
FeatureFlagsClient.where('token_encrypted is NULL AND token IS NOT NULL').find_each do |feature_flags_client|
|
||||||
token_encrypted = Gitlab::CryptoHelper.aes256_gcm_encrypt(feature_flags_client.token)
|
token_encrypted = Gitlab::CryptoHelper.aes256_gcm_encrypt(feature_flags_client.token, nonce: Gitlab::CryptoHelper::AES256_GCM_IV_STATIC)
|
||||||
feature_flags_client.update!(token_encrypted: token_encrypted)
|
feature_flags_client.update!(token_encrypted: token_encrypted)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ class EncryptDeployTokensTokens < ActiveRecord::Migration[5.1]
|
||||||
def up
|
def up
|
||||||
say_with_time("Encrypting tokens from deploy_tokens") do
|
say_with_time("Encrypting tokens from deploy_tokens") do
|
||||||
DeploymentTokens.where('token_encrypted is NULL AND token IS NOT NULL').find_each(batch_size: 10000) do |deploy_token|
|
DeploymentTokens.where('token_encrypted is NULL AND token IS NOT NULL').find_each(batch_size: 10000) do |deploy_token|
|
||||||
token_encrypted = Gitlab::CryptoHelper.aes256_gcm_encrypt(deploy_token.token)
|
token_encrypted = Gitlab::CryptoHelper.aes256_gcm_encrypt(deploy_token.token, nonce: Gitlab::CryptoHelper::AES256_GCM_IV_STATIC)
|
||||||
deploy_token.update!(token_encrypted: token_encrypted)
|
deploy_token.update!(token_encrypted: token_encrypted)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
dde424c434c78e22087123fa30eec75c07268a9079fea44339915747aae235e0
|
|
@ -0,0 +1 @@
|
||||||
|
56595e67e9e78a9558e6874d75bdcc295b89ab0096d1b37e4d9366e1574d241c
|
|
@ -14786,6 +14786,27 @@ CREATE SEQUENCE packages_build_infos_id_seq
|
||||||
|
|
||||||
ALTER SEQUENCE packages_build_infos_id_seq OWNED BY packages_build_infos.id;
|
ALTER SEQUENCE packages_build_infos_id_seq OWNED BY packages_build_infos.id;
|
||||||
|
|
||||||
|
CREATE TABLE packages_composer_cache_files (
|
||||||
|
id bigint NOT NULL,
|
||||||
|
created_at timestamp with time zone NOT NULL,
|
||||||
|
updated_at timestamp with time zone NOT NULL,
|
||||||
|
delete_at timestamp with time zone,
|
||||||
|
namespace_id integer,
|
||||||
|
file_store smallint DEFAULT 1 NOT NULL,
|
||||||
|
file text NOT NULL,
|
||||||
|
file_sha256 bytea NOT NULL,
|
||||||
|
CONSTRAINT check_84f5ba81f5 CHECK ((char_length(file) <= 255))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE packages_composer_cache_files_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
ALTER SEQUENCE packages_composer_cache_files_id_seq OWNED BY packages_composer_cache_files.id;
|
||||||
|
|
||||||
CREATE TABLE packages_composer_metadata (
|
CREATE TABLE packages_composer_metadata (
|
||||||
package_id bigint NOT NULL,
|
package_id bigint NOT NULL,
|
||||||
target_sha bytea NOT NULL,
|
target_sha bytea NOT NULL,
|
||||||
|
@ -17439,6 +17460,22 @@ CREATE SEQUENCE todos_id_seq
|
||||||
|
|
||||||
ALTER SEQUENCE todos_id_seq OWNED BY todos.id;
|
ALTER SEQUENCE todos_id_seq OWNED BY todos.id;
|
||||||
|
|
||||||
|
CREATE TABLE token_with_ivs (
|
||||||
|
id bigint NOT NULL,
|
||||||
|
hashed_token bytea NOT NULL,
|
||||||
|
hashed_plaintext_token bytea NOT NULL,
|
||||||
|
iv bytea NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE token_with_ivs_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
ALTER SEQUENCE token_with_ivs_id_seq OWNED BY token_with_ivs.id;
|
||||||
|
|
||||||
CREATE TABLE trending_projects (
|
CREATE TABLE trending_projects (
|
||||||
id integer NOT NULL,
|
id integer NOT NULL,
|
||||||
project_id integer NOT NULL
|
project_id integer NOT NULL
|
||||||
|
@ -18947,6 +18984,8 @@ ALTER TABLE ONLY operations_user_lists ALTER COLUMN id SET DEFAULT nextval('oper
|
||||||
|
|
||||||
ALTER TABLE ONLY packages_build_infos ALTER COLUMN id SET DEFAULT nextval('packages_build_infos_id_seq'::regclass);
|
ALTER TABLE ONLY packages_build_infos ALTER COLUMN id SET DEFAULT nextval('packages_build_infos_id_seq'::regclass);
|
||||||
|
|
||||||
|
ALTER TABLE ONLY packages_composer_cache_files ALTER COLUMN id SET DEFAULT nextval('packages_composer_cache_files_id_seq'::regclass);
|
||||||
|
|
||||||
ALTER TABLE ONLY packages_conan_file_metadata ALTER COLUMN id SET DEFAULT nextval('packages_conan_file_metadata_id_seq'::regclass);
|
ALTER TABLE ONLY packages_conan_file_metadata ALTER COLUMN id SET DEFAULT nextval('packages_conan_file_metadata_id_seq'::regclass);
|
||||||
|
|
||||||
ALTER TABLE ONLY packages_conan_metadata ALTER COLUMN id SET DEFAULT nextval('packages_conan_metadata_id_seq'::regclass);
|
ALTER TABLE ONLY packages_conan_metadata ALTER COLUMN id SET DEFAULT nextval('packages_conan_metadata_id_seq'::regclass);
|
||||||
|
@ -19161,6 +19200,8 @@ ALTER TABLE ONLY timelogs ALTER COLUMN id SET DEFAULT nextval('timelogs_id_seq':
|
||||||
|
|
||||||
ALTER TABLE ONLY todos ALTER COLUMN id SET DEFAULT nextval('todos_id_seq'::regclass);
|
ALTER TABLE ONLY todos ALTER COLUMN id SET DEFAULT nextval('todos_id_seq'::regclass);
|
||||||
|
|
||||||
|
ALTER TABLE ONLY token_with_ivs ALTER COLUMN id SET DEFAULT nextval('token_with_ivs_id_seq'::regclass);
|
||||||
|
|
||||||
ALTER TABLE ONLY trending_projects ALTER COLUMN id SET DEFAULT nextval('trending_projects_id_seq'::regclass);
|
ALTER TABLE ONLY trending_projects ALTER COLUMN id SET DEFAULT nextval('trending_projects_id_seq'::regclass);
|
||||||
|
|
||||||
ALTER TABLE ONLY u2f_registrations ALTER COLUMN id SET DEFAULT nextval('u2f_registrations_id_seq'::regclass);
|
ALTER TABLE ONLY u2f_registrations ALTER COLUMN id SET DEFAULT nextval('u2f_registrations_id_seq'::regclass);
|
||||||
|
@ -20311,6 +20352,9 @@ ALTER TABLE ONLY operations_user_lists
|
||||||
ALTER TABLE ONLY packages_build_infos
|
ALTER TABLE ONLY packages_build_infos
|
||||||
ADD CONSTRAINT packages_build_infos_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT packages_build_infos_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
ALTER TABLE ONLY packages_composer_cache_files
|
||||||
|
ADD CONSTRAINT packages_composer_cache_files_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
ALTER TABLE ONLY packages_composer_metadata
|
ALTER TABLE ONLY packages_composer_metadata
|
||||||
ADD CONSTRAINT packages_composer_metadata_pkey PRIMARY KEY (package_id);
|
ADD CONSTRAINT packages_composer_metadata_pkey PRIMARY KEY (package_id);
|
||||||
|
|
||||||
|
@ -20689,6 +20733,9 @@ ALTER TABLE ONLY timelogs
|
||||||
ALTER TABLE ONLY todos
|
ALTER TABLE ONLY todos
|
||||||
ADD CONSTRAINT todos_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT todos_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
ALTER TABLE ONLY token_with_ivs
|
||||||
|
ADD CONSTRAINT token_with_ivs_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
ALTER TABLE ONLY trending_projects
|
ALTER TABLE ONLY trending_projects
|
||||||
ADD CONSTRAINT trending_projects_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT trending_projects_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
@ -22549,6 +22596,8 @@ CREATE UNIQUE INDEX index_ops_strategies_user_lists_on_strategy_id_and_user_list
|
||||||
|
|
||||||
CREATE INDEX index_packages_build_infos_on_pipeline_id ON packages_build_infos USING btree (pipeline_id);
|
CREATE INDEX index_packages_build_infos_on_pipeline_id ON packages_build_infos USING btree (pipeline_id);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX index_packages_composer_cache_namespace_and_sha ON packages_composer_cache_files USING btree (namespace_id, file_sha256);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_packages_composer_metadata_on_package_id_and_target_sha ON packages_composer_metadata USING btree (package_id, target_sha);
|
CREATE UNIQUE INDEX index_packages_composer_metadata_on_package_id_and_target_sha ON packages_composer_metadata USING btree (package_id, target_sha);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_packages_conan_file_metadata_on_package_file_id ON packages_conan_file_metadata USING btree (package_file_id);
|
CREATE UNIQUE INDEX index_packages_conan_file_metadata_on_package_file_id ON packages_conan_file_metadata USING btree (package_file_id);
|
||||||
|
@ -23225,6 +23274,10 @@ CREATE INDEX index_todos_on_user_id_and_id_done ON todos USING btree (user_id, i
|
||||||
|
|
||||||
CREATE INDEX index_todos_on_user_id_and_id_pending ON todos USING btree (user_id, id) WHERE ((state)::text = 'pending'::text);
|
CREATE INDEX index_todos_on_user_id_and_id_pending ON todos USING btree (user_id, id) WHERE ((state)::text = 'pending'::text);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX index_token_with_ivs_on_hashed_plaintext_token ON token_with_ivs USING btree (hashed_plaintext_token);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX index_token_with_ivs_on_hashed_token ON token_with_ivs USING btree (hashed_token);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_trending_projects_on_project_id ON trending_projects USING btree (project_id);
|
CREATE UNIQUE INDEX index_trending_projects_on_project_id ON trending_projects USING btree (project_id);
|
||||||
|
|
||||||
CREATE INDEX index_u2f_registrations_on_key_handle ON u2f_registrations USING btree (key_handle);
|
CREATE INDEX index_u2f_registrations_on_key_handle ON u2f_registrations USING btree (key_handle);
|
||||||
|
@ -25532,6 +25585,9 @@ ALTER TABLE ONLY namespace_aggregation_schedules
|
||||||
ALTER TABLE ONLY approval_project_rules_protected_branches
|
ALTER TABLE ONLY approval_project_rules_protected_branches
|
||||||
ADD CONSTRAINT fk_rails_b7567b031b FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_rails_b7567b031b FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE ONLY packages_composer_cache_files
|
||||||
|
ADD CONSTRAINT fk_rails_b82cea43a0 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE SET NULL;
|
||||||
|
|
||||||
ALTER TABLE ONLY alerts_service_data
|
ALTER TABLE ONLY alerts_service_data
|
||||||
ADD CONSTRAINT fk_rails_b93215a42c FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_rails_b93215a42c FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,7 @@ From there, you can see the following actions:
|
||||||
- Project CI/CD variable added, removed, or protected status changed ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30857) in GitLab 13.4)
|
- Project CI/CD variable added, removed, or protected status changed ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30857) in GitLab 13.4)
|
||||||
- Project access token was successfully created or revoked ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230007) in GitLab 13.9)
|
- Project access token was successfully created or revoked ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230007) in GitLab 13.9)
|
||||||
- Failed attempt to create or revoke a project access token ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230007) in GitLab 13.9)
|
- Failed attempt to create or revoke a project access token ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230007) in GitLab 13.9)
|
||||||
|
- When default branch changes for a project ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/52339) in GitLab 13.9)
|
||||||
|
|
||||||
Project events can also be accessed via the [Project Audit Events API](../api/audit_events.md#project-audit-events).
|
Project events can also be accessed via the [Project Audit Events API](../api/audit_events.md#project-audit-events).
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ The GitLab documentation is [intended as the single source of truth (SSOT)](http
|
||||||
In addition to this page, the following resources can help you craft and contribute to documentation:
|
In addition to this page, the following resources can help you craft and contribute to documentation:
|
||||||
|
|
||||||
- [Style Guide](styleguide/index.md) - What belongs in the docs, language guidelines, Markdown standards to follow, links, and more.
|
- [Style Guide](styleguide/index.md) - What belongs in the docs, language guidelines, Markdown standards to follow, links, and more.
|
||||||
- [Structure and template](structure.md) - Learn the typical parts of a doc page and how to write each one.
|
- [Topic type template](structure.md) - Learn about the different types of topics.
|
||||||
- [Documentation process](workflow.md).
|
- [Documentation process](workflow.md).
|
||||||
- [Markdown Guide](../../user/markdown.md) - A reference for all Markdown syntax supported by GitLab.
|
- [Markdown Guide](../../user/markdown.md) - A reference for all Markdown syntax supported by GitLab.
|
||||||
- [Site architecture](site_architecture/index.md) - How <https://docs.gitlab.com> is built.
|
- [Site architecture](site_architecture/index.md) - How <https://docs.gitlab.com> is built.
|
||||||
|
|
|
@ -5,167 +5,165 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
description: What to include in GitLab documentation pages.
|
description: What to include in GitLab documentation pages.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Documentation structure and template
|
# Documentation topic types
|
||||||
|
|
||||||
Use these standards to contribute content to the GitLab documentation.
|
At GitLab, we have not traditionally used topic types. However, we are starting to
|
||||||
|
move in this direction, and we now use four topic types:
|
||||||
|
|
||||||
Before getting started, familiarize yourself with [Documentation guidelines for GitLab](index.md)
|
- [Concept](#concept)
|
||||||
and the [Documentation Style Guide](styleguide/index.md).
|
- [Task](#task)
|
||||||
|
- [Reference](#reference)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
## Components of a documentation page
|
Each page contains multiple topic types. For example,
|
||||||
|
a page with the title `Pipelines`, which is generated from a file called `index.md`,
|
||||||
|
can include a concept and multiple task and reference topics.
|
||||||
|
|
||||||
Most pages are dedicated to a specific GitLab feature or to a use case that
|
GitLab also uses high-level landing pages.
|
||||||
involves one or more features, potentially in conjunction with third-party tools.
|
|
||||||
|
|
||||||
In general, each topic should include the following content, in this sequence:
|
## Landing pages
|
||||||
|
|
||||||
- *Metadata*: Information about the stage, group, and how to find the technical
|
Landing pages are topics that group other topics and help a user to navigate a section.
|
||||||
writer for the topic. This information isn't visible in the published help.
|
|
||||||
- *Title*: A top-level heading with the feature or use case name. Choose a term
|
|
||||||
that defines the functionality and use the same term in all the resources
|
|
||||||
where the feature is mentioned.
|
|
||||||
- *Introduction*: In a few sentences beneath the title, describe what the
|
|
||||||
feature or topic is, what it does, and in what context it should be used.
|
|
||||||
- *Use cases*: Describe real user scenarios.
|
|
||||||
- *Prerequisites*: Describe the software, configuration, account, permissions,
|
|
||||||
or knowledge required to use this functionality.
|
|
||||||
- *Tasks*: Present detailed step-by-step instructions on how to use the feature.
|
|
||||||
- *Troubleshooting*: List errors and how to address them. Recommended but not
|
|
||||||
required.
|
|
||||||
|
|
||||||
You can include additional subsections, as appropriate, such as *How it Works*,
|
Users who are using the in-product help do not have a left nav,
|
||||||
or *Architecture*. You can also include other logical divisions, such as
|
and need these topics to navigate the documentation.
|
||||||
pre-deployment and post-deployment tasks.
|
|
||||||
|
|
||||||
## Template for new docs
|
These topics can also help other users find the most important topics
|
||||||
|
in a section.
|
||||||
|
|
||||||
Follow the [folder structure and filename guidelines](styleguide/index.md#folder-structure-overview)
|
Landing page topics should be in this format:
|
||||||
and create a new topic by using this template:
|
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
<!--Follow the Style Guide when working on this document.
|
# Title (a noun, like "CI/CD or "Analytics")
|
||||||
https://docs.gitlab.com/ee/development/documentation/styleguide.html
|
|
||||||
When done, remove all of this commented-out text, except a commented-out
|
|
||||||
Troubleshooting section, which, if empty, can be left in place to encourage future use.-->
|
|
||||||
---
|
|
||||||
description: "Short document description." # Up to ~200 chars long. This information is displayed
|
|
||||||
in Google Search snippets. It may help to write the page intro first, and then reuse it here.
|
|
||||||
stage: Add the stage name here
|
|
||||||
group: Add the group name here
|
|
||||||
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/#assignments
|
|
||||||
---
|
|
||||||
|
|
||||||
# Feature or Use Case Name **[TIER]** (1)
|
Brief introduction to the concept or product area.
|
||||||
<!--If you are writing about a use case, start with a verb,
|
Include the reason why someone would use this thing.
|
||||||
for example, "Configure", "Implement", + the goal/scenario-->
|
|
||||||
|
|
||||||
<!--For pages on newly-introduced features, add the following line.
|
- Bulleted list of important related topics.
|
||||||
If only some aspects of the feature have been introduced, specify which parts of the feature.-->
|
- These links are needed because users of in-product help do not have left navigation.
|
||||||
> [Introduced](link_to_issue_or_mr) in GitLab (Tier) X.Y (2).
|
|
||||||
|
|
||||||
Write a description of the feature or use case. This introduction should answer
|
|
||||||
these questions:
|
|
||||||
|
|
||||||
- What is this feature or use case?
|
|
||||||
- Who is it for?
|
|
||||||
- What is the context in which it is used and are there any prerequisites or
|
|
||||||
requirements?
|
|
||||||
- What can the audience do with this? (Be sure to consider all applicable
|
|
||||||
audiences, such as GitLab admin and developer-user.)
|
|
||||||
- What are the benefits of using this over any existing alternatives?
|
|
||||||
|
|
||||||
You can reuse this content, or part of it, for the front matter's `description`
|
|
||||||
at the top of this file.
|
|
||||||
|
|
||||||
## Use cases
|
|
||||||
|
|
||||||
Describe common use cases, typically in bulleted form. Include real-life examples
|
|
||||||
for each.
|
|
||||||
|
|
||||||
If the page itself is dedicated to a use case, this section usually includes more
|
|
||||||
specific scenarios for use (for example, variations on the main use case), but if
|
|
||||||
that's not applicable, you can omit this section.
|
|
||||||
|
|
||||||
Examples of use cases on feature pages:
|
|
||||||
|
|
||||||
- CE and EE: [Issues](../../user/project/issues/index.md#use-cases)
|
|
||||||
- CE and EE: [Merge Requests](../../user/project/merge_requests/index.md)
|
|
||||||
- EE-only: [Geo](../../administration/geo/index.md)
|
|
||||||
- EE-only: [Jenkins integration](../../integration/jenkins.md)
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
State any prerequisites for using the feature. These might include:
|
|
||||||
|
|
||||||
- Technical prereqs (for example, an account on a third-party service, an amount
|
|
||||||
of storage space, or prior configuration of another feature)
|
|
||||||
- Prerequisite knowledge (for example, familiarity with certain GitLab features
|
|
||||||
or other products and technologies).
|
|
||||||
|
|
||||||
Link each one to an appropriate place for more information.
|
|
||||||
|
|
||||||
## Tasks
|
|
||||||
|
|
||||||
Each topic should help users accomplish a specific task.
|
|
||||||
|
|
||||||
The heading should:
|
|
||||||
|
|
||||||
- Describe the task and start with a verb. For example, `Create a package` or
|
|
||||||
`Configure a pipeline`.
|
|
||||||
- Be short and descriptive (up to ~50 chars).
|
|
||||||
- Start from an `h2` (`##`), then go over `h3`, `h4`, `h5`, and `h6` as needed.
|
|
||||||
Never skip a hierarchy level (like `h2` > `h4`). It breaks the table of
|
|
||||||
contents and can affect the breadcrumbs.
|
|
||||||
|
|
||||||
Bigger tasks can have subsections that explain specific phases of the process.
|
|
||||||
|
|
||||||
Include example code or configurations when needed. Use Markdown to wrap code
|
|
||||||
blocks with [syntax highlighting](../../user/markdown.md#colored-code-and-syntax-highlighting).
|
|
||||||
|
|
||||||
Example topic:
|
|
||||||
|
|
||||||
## Create a teddy bear
|
|
||||||
|
|
||||||
Create a teddy bear when you need something to hug. (Include the reason why you
|
|
||||||
might do the task.)
|
|
||||||
|
|
||||||
To create a teddy bear:
|
|
||||||
|
|
||||||
1. Go to **Settings > CI/CD**.
|
|
||||||
1. Expand **This** and click **This**.
|
|
||||||
1. Do another step.
|
|
||||||
|
|
||||||
The teddy bear is now in the kitchen, in the cupboard above the sink. _(This is the result.)_
|
|
||||||
|
|
||||||
You can retrieve the teddy bear and put it on the couch with the other animals. _(These are next steps.)_
|
|
||||||
|
|
||||||
Screenshots are not necessary. They are difficult to keep up-to-date and can
|
|
||||||
clutter the page.
|
|
||||||
|
|
||||||
<!-- ## Troubleshooting
|
|
||||||
|
|
||||||
Include any troubleshooting steps that you can foresee. If you know beforehand
|
|
||||||
what issues one might have when setting this up, or when something is changed,
|
|
||||||
or on upgrading, it's important to describe those, too. Think of things that may
|
|
||||||
go wrong and include them here. This is important to minimize requests for
|
|
||||||
Support, and to avoid documentation comments with questions that you know
|
|
||||||
someone might ask.
|
|
||||||
|
|
||||||
Each scenario can be a third-level heading, for example, `### Getting error message X`.
|
|
||||||
If you have none to add when creating a doc, leave this section in place but
|
|
||||||
commented out to help encourage others to add to it in the future. -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
|
|
||||||
- (1): Apply the [tier badges](styleguide/index.md#product-badges) accordingly.
|
|
||||||
- (2): Apply the correct format for the
|
|
||||||
[GitLab version that introduces the feature](styleguide/index.md#gitlab-versions-and-tiers).
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Help and feedback section
|
## Concept
|
||||||
|
|
||||||
|
A concept topic introduces a single feature or concept.
|
||||||
|
|
||||||
|
A concept should answer the questions:
|
||||||
|
|
||||||
|
- What is this?
|
||||||
|
- Why would I use it?
|
||||||
|
|
||||||
|
Think of everything someone might want to know if they’ve never heard of this topic before.
|
||||||
|
|
||||||
|
Don’t tell them **how** to do this thing. Tell them **what it is**.
|
||||||
|
|
||||||
|
If you start describing another topic, start a new concept and link to it.
|
||||||
|
|
||||||
|
Concept topics should be in this format:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Title (a noun, like "Widgets")
|
||||||
|
|
||||||
|
A paragraph that explains what this thing is.
|
||||||
|
|
||||||
|
Another paragraph that explains what this thing is.
|
||||||
|
|
||||||
|
Remember, if you start to describe about another concept, stop yourself.
|
||||||
|
Each concept topic should be about one concept only.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Task
|
||||||
|
|
||||||
|
A task topic gives instructions for how to complete a procedure.
|
||||||
|
|
||||||
|
Task topics should be in this format:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Title (starts with an active verb, like "Create a widget" or "Delete a widget")
|
||||||
|
|
||||||
|
Do this task when you want to...
|
||||||
|
|
||||||
|
Prerequisites (optional):
|
||||||
|
|
||||||
|
- Thing 1
|
||||||
|
- Thing 2
|
||||||
|
- Thing 3
|
||||||
|
|
||||||
|
To do this task:
|
||||||
|
|
||||||
|
1. Location then action. (Go to this menu, then select this item.)
|
||||||
|
1. Another step.
|
||||||
|
1. Another step.
|
||||||
|
|
||||||
|
Task result (optional). Next steps (optional).
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is an example.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Create an issue
|
||||||
|
|
||||||
|
Create an issue when you want to track bugs or future work.
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- A minimum of Contributor access to a project in GitLab.
|
||||||
|
|
||||||
|
To create an issue:
|
||||||
|
|
||||||
|
1. Go to **Issues > List**.
|
||||||
|
1. In the top right, click **New issue**.
|
||||||
|
1. Complete the fields. (If you have a reference topic that lists each field, link to it here.)
|
||||||
|
1. Click **Submit issue**.
|
||||||
|
|
||||||
|
The issue is created. You can view it by going to **Issues > List**.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
A reference topic provides information in an easily-scannable format,
|
||||||
|
like a table or list. It's similar to a dictionary or encyclopedia entry.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Title (a noun, like "Pipeline settings" or "Administrator options")
|
||||||
|
|
||||||
|
Introductory sentence.
|
||||||
|
|
||||||
|
| Setting | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| **Name** | Descriptive sentence about the setting. |
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Troubleshooting topics can be one of two categories:
|
||||||
|
|
||||||
|
- **Troubleshooting task.** This topic is written the same as a [standard task topic](#task).
|
||||||
|
For example, "Run debug tools" or "Verify syntax."
|
||||||
|
- **Troubleshooting reference.** This topic has a specific format.
|
||||||
|
|
||||||
|
Troubleshooting reference topics should be in this format:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Title (the error message or a description of it)
|
||||||
|
|
||||||
|
You might get an error that states <error message>.
|
||||||
|
|
||||||
|
This issue occurs when...
|
||||||
|
|
||||||
|
The workaround is...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other information on a topic
|
||||||
|
|
||||||
|
Topics include other information.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
- Each topic must have a [tier badge](styleguide/index.md#product-tier-badges).
|
||||||
|
- New topics must have information about the
|
||||||
|
[GitLab version where the feature was introduced](styleguide/index.md#where-to-put-version-text).
|
||||||
|
|
||||||
|
### Help and feedback section
|
||||||
|
|
||||||
This section ([introduced](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/319) in GitLab 11.4)
|
This section ([introduced](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/319) in GitLab 11.4)
|
||||||
is displayed at the end of each document and can be omitted by adding a key into
|
is displayed at the end of each document and can be omitted by adding a key into
|
||||||
|
@ -180,7 +178,7 @@ feedback: false
|
||||||
The default is to leave it there. If you want to omit it from a document, you
|
The default is to leave it there. If you want to omit it from a document, you
|
||||||
must check with a technical writer before doing so.
|
must check with a technical writer before doing so.
|
||||||
|
|
||||||
### Disqus
|
#### Disqus
|
||||||
|
|
||||||
We also have integrated the docs site with Disqus (introduced by
|
We also have integrated the docs site with Disqus (introduced by
|
||||||
[!151](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/151)),
|
[!151](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/151)),
|
||||||
|
@ -206,7 +204,7 @@ The click events in the feedback section are tracked with Google Tag Manager.
|
||||||
The conversions can be viewed on Google Analytics by navigating to
|
The conversions can be viewed on Google Analytics by navigating to
|
||||||
**Behavior > Events > Top events > docs**.
|
**Behavior > Events > Top events > docs**.
|
||||||
|
|
||||||
## Guidelines for good practices
|
### Guidelines for good practices
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36576/) in GitLab 13.2 as GitLab Development documentation.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36576/) in GitLab 13.2 as GitLab Development documentation.
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,7 @@ For guidelines specific to text in the GitLab interface, see the Pajamas [Conten
|
||||||
|
|
||||||
For information on how to validate styles locally or by using GitLab CI/CD, see [Testing](../testing.md).
|
For information on how to validate styles locally or by using GitLab CI/CD, see [Testing](../testing.md).
|
||||||
|
|
||||||
Use this guide for standards on grammar, formatting, word usage, and more.
|
You can view a list of [recent updates to this guide](https://gitlab.com/dashboard/merge_requests?scope=all&utf8=%E2%9C%93&state=merged&label_name[]=tw-style¬[label_name][]=docs%3A%3Afix).
|
||||||
|
|
||||||
You can also view a list of [recent updates to this guide](https://gitlab.com/dashboard/merge_requests?scope=all&utf8=%E2%9C%93&state=merged&label_name[]=tw-style¬[label_name][]=docs%3A%3Afix).
|
|
||||||
|
|
||||||
If you can't find what you need:
|
If you can't find what you need:
|
||||||
|
|
||||||
|
@ -31,10 +29,8 @@ If you have questions about style, mention `@tw-style` in an issue or merge requ
|
||||||
|
|
||||||
## Documentation is the single source of truth (SSOT)
|
## Documentation is the single source of truth (SSOT)
|
||||||
|
|
||||||
### Why a single source of truth
|
The GitLab documentation is the SSOT for all
|
||||||
|
information related to GitLab implementation, usage, and troubleshooting. It evolves
|
||||||
The documentation of GitLab products and features is the SSOT for all
|
|
||||||
information related to implementation, usage, and troubleshooting. It evolves
|
|
||||||
continuously, in keeping with new products and features, and with improvements
|
continuously, in keeping with new products and features, and with improvements
|
||||||
for clarity, accuracy, and completeness.
|
for clarity, accuracy, and completeness.
|
||||||
|
|
||||||
|
@ -44,7 +40,7 @@ about GitLab products.
|
||||||
It also informs decisions about the kinds of content we include in our
|
It also informs decisions about the kinds of content we include in our
|
||||||
documentation.
|
documentation.
|
||||||
|
|
||||||
### All information
|
### The documentation includes all information
|
||||||
|
|
||||||
Include problem-solving actions that may address rare cases or be considered
|
Include problem-solving actions that may address rare cases or be considered
|
||||||
_risky_, but provide proper context through fully-detailed
|
_risky_, but provide proper context through fully-detailed
|
||||||
|
@ -54,10 +50,13 @@ If you think you have found an exception to this rule, contact the
|
||||||
Technical Writing team.
|
Technical Writing team.
|
||||||
|
|
||||||
GitLab adds all troubleshooting information to the documentation, no matter how
|
GitLab adds all troubleshooting information to the documentation, no matter how
|
||||||
unlikely a user is to encounter a situation. For the [Troubleshooting sections](#troubleshooting),
|
unlikely a user is to encounter a situation.
|
||||||
people in GitLab Support can merge additions themselves.
|
|
||||||
|
|
||||||
### All media types
|
GitLab Support maintains their own
|
||||||
|
[troubleshooting content](../../../administration/index.md#support-team-docs)
|
||||||
|
in the GitLab documentation.
|
||||||
|
|
||||||
|
### The documentation includes all media types
|
||||||
|
|
||||||
Include any media types/sources if the content is relevant to readers. You can
|
Include any media types/sources if the content is relevant to readers. You can
|
||||||
freely include or link presentations, diagrams, and videos. No matter who
|
freely include or link presentations, diagrams, and videos. No matter who
|
||||||
|
@ -71,48 +70,33 @@ include it.
|
||||||
quotation with the source cited. Typically it is better to either rephrase
|
quotation with the source cited. Typically it is better to either rephrase
|
||||||
relevant information in your own words or link out to the other source.
|
relevant information in your own words or link out to the other source.
|
||||||
|
|
||||||
### No special types
|
### Topic types
|
||||||
|
|
||||||
In the software industry, it is a best practice to organize documentation in
|
In the software industry, it is a best practice to organize documentation in
|
||||||
different types. For example, [Divio recommends](https://www.divio.com/blog/documentation/):
|
different types. For example:
|
||||||
|
|
||||||
- Tutorials
|
- Concepts
|
||||||
- How-to guides
|
- Tasks
|
||||||
- Explanation
|
- Reference
|
||||||
- Reference (for example, a glossary)
|
- Troubleshooting
|
||||||
|
|
||||||
At GitLab, we have so many product changes in our monthly releases that we can't
|
At GitLab, we have not traditionally used topic types. However, we are starting to
|
||||||
afford to continuously update multiple types of information. If we have multiple
|
move in this direction, so we can address these issues:
|
||||||
types, the information becomes outdated. Therefore, we have a
|
|
||||||
[single template](../structure.md) for documentation.
|
|
||||||
|
|
||||||
GitLab documentation does not distinguish specific document types. We are open to
|
- **Content is hard to find.** Our docs are comprehensive and include a large amount of
|
||||||
reconsidering this policy after the documentation has reached a future stage of
|
useful information. Topic types create repeatable patterns that make our content easier
|
||||||
maturity and quality. If you are reading this, then despite our continuous
|
to scan and parse.
|
||||||
improvement efforts, that point hasn't been reached.
|
- **Content is often written from the contributor's point of view.** Our docs
|
||||||
|
are written by contributors. Topic types (tasks specifically) help put
|
||||||
|
information into a format that is geared toward helping others, rather than
|
||||||
|
documenting how a feature was implemented.
|
||||||
|
|
||||||
### Link instead of summarize
|
GitLab uses these [topic type templates](../structure.md).
|
||||||
|
|
||||||
There is a temptation to summarize the information on another page, which
|
### Link instead of repeating text
|
||||||
causes the information to live in two places. Instead, link to the single source
|
|
||||||
of truth and explain why it is important to consume the information.
|
|
||||||
|
|
||||||
### Organize by topic, not by type
|
Rather than repeating information from another topic, link to the single source
|
||||||
|
of truth and explain why it is important.
|
||||||
We organize content by topic, not by type, so it can be located in the
|
|
||||||
single-source-of-truth (SSOT) section for the subject matter. Top-level audience-type
|
|
||||||
folders, like `administration`, are exceptions.
|
|
||||||
|
|
||||||
For example, do not create groupings of similar media types. For example:
|
|
||||||
|
|
||||||
- Glossaries.
|
|
||||||
- FAQs.
|
|
||||||
- Sets of all articles or videos.
|
|
||||||
|
|
||||||
Such grouping of content by type makes it difficult to browse for the information
|
|
||||||
you need and difficult to maintain up-to-date content. Instead, organize content
|
|
||||||
by its subject (for example, everything related to CI goes together) and
|
|
||||||
cross-link between any related content.
|
|
||||||
|
|
||||||
### Docs-first methodology
|
### Docs-first methodology
|
||||||
|
|
||||||
|
@ -127,14 +111,9 @@ of GitLab more efficient.
|
||||||
should be to create a merge request (MR) to add this information to the
|
should be to create a merge request (MR) to add this information to the
|
||||||
documentation. You can then share the MR to communicate this information.
|
documentation. You can then share the MR to communicate this information.
|
||||||
|
|
||||||
New information about the future usage or troubleshooting
|
New information that would be useful toward the future usage or troubleshooting
|
||||||
of GitLab should not be written directly in a forum or other messaging system.
|
of GitLab should not be written directly in a forum or other messaging system,
|
||||||
Instead, add it to a documentation merge request, then reference it. Note
|
but added to a documentation MR and then referenced, as described above.
|
||||||
that among any other documentation changes, you can either:
|
|
||||||
|
|
||||||
- Add a [Troubleshooting section](#troubleshooting) to a doc if none exists.
|
|
||||||
- Un-comment and use the placeholder Troubleshooting section included as part of
|
|
||||||
our [documentation template](../structure.md#template-for-new-docs), if present.
|
|
||||||
|
|
||||||
The more we reflexively add information to the documentation, the more
|
The more we reflexively add information to the documentation, the more
|
||||||
the documentation helps others efficiently accomplish tasks and solve problems.
|
the documentation helps others efficiently accomplish tasks and solve problems.
|
||||||
|
@ -217,8 +196,11 @@ included in backticks. For example:
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
Because we want documentation to be a SSOT, we should [organize by topic, not by
|
We include concept and task topic types in the same larger topic.
|
||||||
type](#organize-by-topic-not-by-type).
|
|
||||||
|
In general, we have one topic that's a [landing page](../structure.md#landing-pages).
|
||||||
|
Below that topic in the left nav are individual topics. Each of these include a concept
|
||||||
|
and multiple related tasks, reference, and troubleshooting topics.
|
||||||
|
|
||||||
### Folder structure overview
|
### Folder structure overview
|
||||||
|
|
||||||
|
@ -299,7 +281,7 @@ place for it.
|
||||||
### Avoid duplication
|
### Avoid duplication
|
||||||
|
|
||||||
Do not include the same information in multiple places.
|
Do not include the same information in multiple places.
|
||||||
[Link to a single source of truth instead.](#link-instead-of-summarize)
|
[Link to a single source of truth instead.](#link-instead-of-repeating-text)
|
||||||
|
|
||||||
### References across documents
|
### References across documents
|
||||||
|
|
||||||
|
@ -966,8 +948,8 @@ this option.
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
Links are important in GitLab documentation. They allow you to [link instead of
|
Links are important in GitLab documentation. Use links instead of
|
||||||
summarizing](#link-instead-of-summarize) to help preserve a [single source of truth](#why-a-single-source-of-truth)
|
summarizing to help preserve a [single source of truth](#documentation-is-the-single-source-of-truth-ssot)
|
||||||
in GitLab documentation.
|
in GitLab documentation.
|
||||||
|
|
||||||
We include guidance for links in these categories:
|
We include guidance for links in these categories:
|
||||||
|
@ -1895,21 +1877,6 @@ In this case:
|
||||||
- Use the [GitLab Restart](#gitlab-restart) section to explain any required
|
- Use the [GitLab Restart](#gitlab-restart) section to explain any required
|
||||||
restart or reconfigure of GitLab.
|
restart or reconfigure of GitLab.
|
||||||
|
|
||||||
### Troubleshooting
|
|
||||||
|
|
||||||
For troubleshooting sections, provide as much context as possible so
|
|
||||||
users can identify their problem and resolve it on their own. You
|
|
||||||
can facilitate this by making sure the troubleshooting content addresses:
|
|
||||||
|
|
||||||
1. The problem the user needs to solve.
|
|
||||||
1. How the user can confirm they have the problem.
|
|
||||||
1. Steps the user can take towards resolution of the problem.
|
|
||||||
|
|
||||||
If the contents of each category can be summarized in one line and a list of
|
|
||||||
steps aren't required, consider setting up a [table](#tables). Create headers of
|
|
||||||
_Problem_ \| _Cause_ \| _Solution_ (or _Workaround_ if the fix is temporary), or
|
|
||||||
_Error message_ \| _Solution_.
|
|
||||||
|
|
||||||
## Feature flags
|
## Feature flags
|
||||||
|
|
||||||
Learn how to [document features deployed behind flags](../feature_flags.md). For
|
Learn how to [document features deployed behind flags](../feature_flags.md). For
|
||||||
|
|
|
@ -13,20 +13,6 @@ install anything to use GitLab SaaS, you only need to
|
||||||
|
|
||||||
This page reviews the details of your GitLab SaaS subscription.
|
This page reviews the details of your GitLab SaaS subscription.
|
||||||
|
|
||||||
## Choose a GitLab SaaS group or personal subscription
|
|
||||||
|
|
||||||
On GitLab SaaS you can apply a subscription to either a group or a personal namespace.
|
|
||||||
|
|
||||||
When applied to:
|
|
||||||
|
|
||||||
- A **group**, the group, all subgroups, and all projects under the selected
|
|
||||||
group on GitLab SaaS contains the features of the associated tier. GitLab recommends
|
|
||||||
choosing a group plan when managing an organization's projects and users.
|
|
||||||
- A **personal user space**, all projects contain features with the
|
|
||||||
subscription applied, but as it's not a group, group features aren't available.
|
|
||||||
|
|
||||||
You can read more about [common misconceptions](https://about.gitlab.com/handbook/marketing/strategic-marketing/enablement/dotcom-subscriptions/#common-misconceptions) regarding a GitLab SaaS subscription to help avoid issues.
|
|
||||||
|
|
||||||
## Choose a GitLab SaaS tier
|
## Choose a GitLab SaaS tier
|
||||||
|
|
||||||
Pricing is [tier-based](https://about.gitlab.com/pricing/), allowing you to choose
|
Pricing is [tier-based](https://about.gitlab.com/pricing/), allowing you to choose
|
||||||
|
@ -57,36 +43,22 @@ source projects, GitLab grants access to **Ultimate** features for all GitLab Sa
|
||||||
|
|
||||||
To subscribe to GitLab SaaS:
|
To subscribe to GitLab SaaS:
|
||||||
|
|
||||||
- **For individuals**:
|
1. Create a user account for yourself using our
|
||||||
1. Create a user account for yourself using our
|
[sign up page](https://gitlab.com/users/sign_up).
|
||||||
[sign up page](https://gitlab.com/users/sign_up).
|
1. Create a [group](../../user/group/index.md). GitLab groups help assemble related
|
||||||
1. Visit the [billing page](https://gitlab.com/profile/billings)
|
projects together allowing you to grant members access to several projects
|
||||||
under your profile.
|
at once. A group is not required if you plan on having projects inside a personal
|
||||||
1. Select the **Premium** or **Ultimate** GitLab SaaS plan through the
|
namespace.
|
||||||
[Customers Portal](https://customers.gitlab.com/).
|
1. Create additional users and
|
||||||
1. Link your GitLab SaaS account with your Customers Portal account.
|
[add them to the group](../../user/group/index.md#add-users-to-a-group).
|
||||||
Once a plan has been selected, if your account is not
|
1. Select the GitLab SaaS plan through the
|
||||||
already linked, GitLab prompts you to link your account with a
|
[Customers Portal](https://customers.gitlab.com/).
|
||||||
**Sign in to GitLab.com** button.
|
1. Link your GitLab SaaS account with your Customers Portal account.
|
||||||
1. Select the namespace from the drop-down list to associate the subscription.
|
Once a plan has been selected, if your account is not
|
||||||
1. Proceed to checkout.
|
already linked, GitLab prompts you to link your account with a
|
||||||
- **For groups**:
|
**Sign in to GitLab.com** button.
|
||||||
1. Create a user account for yourself using our
|
1. Select the namespace from the drop-down list to associate the subscription.
|
||||||
[sign up page](https://gitlab.com/users/sign_up).
|
1. Proceed to checkout.
|
||||||
1. Create a [group](../../user/group/index.md). GitLab groups help assemble related
|
|
||||||
projects together allowing you to grant members access to several projects
|
|
||||||
at once. A group is not required if you plan on having projects inside a personal
|
|
||||||
namespace.
|
|
||||||
1. Create additional users and
|
|
||||||
[add them to the group](../../user/group/index.md#add-users-to-a-group).
|
|
||||||
1. Select the GitLab SaaS plan through the
|
|
||||||
[Customers Portal](https://customers.gitlab.com/).
|
|
||||||
1. Link your GitLab SaaS account with your Customers Portal account.
|
|
||||||
Once a plan has been selected, if your account is not
|
|
||||||
already linked, GitLab prompts you to link your account with a
|
|
||||||
**Sign in to GitLab.com** button.
|
|
||||||
1. Select the namespace from the drop-down list to associate the subscription.
|
|
||||||
1. Proceed to checkout.
|
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
You can also go to the [**My Account**](https://customers.gitlab.com/customers/edit)
|
You can also go to the [**My Account**](https://customers.gitlab.com/customers/edit)
|
||||||
|
@ -95,25 +67,21 @@ page to add or change the GitLab SaaS account link.
|
||||||
## View your GitLab SaaS subscription
|
## View your GitLab SaaS subscription
|
||||||
|
|
||||||
To see the status of your GitLab SaaS subscription, log in to GitLab SaaS and go
|
To see the status of your GitLab SaaS subscription, log in to GitLab SaaS and go
|
||||||
to the **Billing** section of the relevant namespace:
|
to the **Billing** section:
|
||||||
|
|
||||||
- **For individuals**: Visit the [billing page](https://gitlab.com/profile/billings)
|
NOTE:
|
||||||
under your profile.
|
You must have Owner level [permissions](../../user/permissions.md) to view the billing page.
|
||||||
- **For groups**: From the group page (*not* from a project in the group), go to **Settings > Billing**.
|
|
||||||
|
|
||||||
NOTE:
|
The following table describes details of your subscription:
|
||||||
You must have Owner level [permissions](../../user/permissions.md) to view a group's billing page.
|
|
||||||
|
|
||||||
The following table describes details of your subscription for groups:
|
| Field | Description |
|
||||||
|
|:----------------------------|:------------|
|
||||||
| Field | Description |
|
| **Seats in subscription** | If this is a paid plan, represents the number of seats you've bought for this group. |
|
||||||
|-----------------------------|-------------|
|
| **Seats currently in use** | Number of seats in use. Select **See usage** to see a list of the users using these seats. For more details, see [Seat usage](#seat-usage). |
|
||||||
| **Seats in subscription** | If this is a paid plan, represents the number of seats you've bought for this group. |
|
| **Max seats used** | Highest number of seats you've used. |
|
||||||
| **Seats currently in use** | Number of seats in use. Select **See usage** to see a list of the users using these seats. For more details, see [Seat usage](#seat-usage). |
|
| **Seats owed** | _Seats owed_ = _Max seats used_ - _Seats in subscription_. |
|
||||||
| **Max seats used** | Highest number of seats you've used. |
|
| **Subscription start date** | Date your subscription started. If this is for a Free plan, it's the date you transitioned off your group's paid plan. |
|
||||||
| **Seats owed** | _Seats owed_ = _Max seats used_ - _Seats in subscription_. |
|
| **Subscription end date** | Date your current subscription ends. Does not apply to Free plans. |
|
||||||
| **Subscription start date** | Date your subscription started. If this is for a Free plan, it's the date you transitioned off your group's paid plan. |
|
|
||||||
| **Subscription end date** | Date your current subscription ends. Does not apply to Free plans. |
|
|
||||||
|
|
||||||
## Seat usage
|
## Seat usage
|
||||||
|
|
||||||
|
|
|
@ -8,18 +8,21 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/8935) in GitLab 9.0.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/8935) in GitLab 9.0.
|
||||||
|
|
||||||
GitLab offers powerful integration with [Prometheus](https://prometheus.io) for monitoring key metrics of your apps, directly within GitLab.
|
GitLab offers powerful integration with [Prometheus](https://prometheus.io) for
|
||||||
|
monitoring key metrics of your apps, directly in GitLab.
|
||||||
Metrics for each environment are retrieved from Prometheus, and then displayed
|
Metrics for each environment are retrieved from Prometheus, and then displayed
|
||||||
within the GitLab interface.
|
in the GitLab interface.
|
||||||
|
|
||||||
![Environment Dashboard](img/prometheus_dashboard.png)
|
![Environment Dashboard](img/prometheus_dashboard.png)
|
||||||
|
|
||||||
There are two ways to set up Prometheus integration, depending on where your apps are running:
|
There are two ways to set up Prometheus integration, depending on where your apps are running:
|
||||||
|
|
||||||
- For deployments on Kubernetes, GitLab can automatically [deploy and manage Prometheus](#managed-prometheus-on-kubernetes).
|
- For deployments on Kubernetes, GitLab can automatically [deploy and manage Prometheus](#managed-prometheus-on-kubernetes).
|
||||||
- For other deployment targets, simply [specify the Prometheus server](#manual-configuration-of-prometheus).
|
- For other deployment targets, [specify the Prometheus server](#manual-configuration-of-prometheus).
|
||||||
|
|
||||||
Once enabled, GitLab detects metrics from known services in the [metric library](prometheus_library/index.md). You can also [add your own metrics](../../../operations/metrics/index.md#adding-custom-metrics) and create
|
Once enabled, GitLab detects metrics from known services in the
|
||||||
|
[metric library](prometheus_library/index.md). You can also
|
||||||
|
[add your own metrics](../../../operations/metrics/index.md#adding-custom-metrics) and create
|
||||||
[custom dashboards](../../../operations/metrics/dashboards/index.md).
|
[custom dashboards](../../../operations/metrics/dashboards/index.md).
|
||||||
|
|
||||||
## Enabling Prometheus Integration
|
## Enabling Prometheus Integration
|
||||||
|
@ -28,7 +31,8 @@ Once enabled, GitLab detects metrics from known services in the [metric library]
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/28916) in GitLab 10.5.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/28916) in GitLab 10.5.
|
||||||
|
|
||||||
GitLab can seamlessly deploy and manage Prometheus on a [connected Kubernetes cluster](../clusters/index.md), making monitoring of your apps easy.
|
GitLab can seamlessly deploy and manage Prometheus on a
|
||||||
|
[connected Kubernetes cluster](../clusters/index.md), to help you monitor your apps.
|
||||||
|
|
||||||
#### Requirements
|
#### Requirements
|
||||||
|
|
||||||
|
@ -36,7 +40,7 @@ GitLab can seamlessly deploy and manage Prometheus on a [connected Kubernetes cl
|
||||||
|
|
||||||
#### Getting started
|
#### Getting started
|
||||||
|
|
||||||
Once you have a connected Kubernetes cluster, deploying a managed Prometheus is as easy as a single click.
|
After you have a connected Kubernetes cluster, you can deploy a managed Prometheus with a single click.
|
||||||
|
|
||||||
1. Go to the **Operations > Kubernetes** page to view your connected clusters
|
1. Go to the **Operations > Kubernetes** page to view your connected clusters
|
||||||
1. Select the cluster you would like to deploy Prometheus to
|
1. Select the cluster you would like to deploy Prometheus to
|
||||||
|
@ -46,17 +50,28 @@ Once you have a connected Kubernetes cluster, deploying a managed Prometheus is
|
||||||
|
|
||||||
#### About managed Prometheus deployments
|
#### About managed Prometheus deployments
|
||||||
|
|
||||||
Prometheus is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/helm/charts/tree/master/stable/prometheus). Prometheus is only accessible within the cluster, with GitLab communicating through the [Kubernetes API](https://kubernetes.io/docs/concepts/overview/kubernetes-api/).
|
Prometheus is deployed into the `gitlab-managed-apps` namespace, using the
|
||||||
|
[official Helm chart](https://github.com/helm/charts/tree/master/stable/prometheus).
|
||||||
|
Prometheus is only accessible in the cluster, with GitLab communicating through the
|
||||||
|
[Kubernetes API](https://kubernetes.io/docs/concepts/overview/kubernetes-api/).
|
||||||
|
|
||||||
The Prometheus server [automatically detects and monitors](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config) nodes, pods, and endpoints. To configure a resource to be monitored by Prometheus, simply set the following [Kubernetes annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/):
|
The Prometheus server
|
||||||
|
[automatically detects and monitors](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config)
|
||||||
|
nodes, pods, and endpoints. To configure a resource to be monitored by Prometheus,
|
||||||
|
set the following [Kubernetes annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/):
|
||||||
|
|
||||||
- `prometheus.io/scrape` to `true` to enable monitoring of the resource.
|
- `prometheus.io/scrape` to `true` to enable monitoring of the resource.
|
||||||
- `prometheus.io/port` to define the port of the metrics endpoint.
|
- `prometheus.io/port` to define the port of the metrics endpoint.
|
||||||
- `prometheus.io/path` to define the path of the metrics endpoint. Defaults to `/metrics`.
|
- `prometheus.io/path` to define the path of the metrics endpoint. Defaults to `/metrics`.
|
||||||
|
|
||||||
CPU and Memory consumption is monitored, but requires [naming conventions](prometheus_library/kubernetes.md#specifying-the-environment) in order to determine the environment. If you are using [Auto DevOps](../../../topics/autodevops/index.md), this is handled automatically.
|
CPU and Memory consumption is monitored, but requires
|
||||||
|
[naming conventions](prometheus_library/kubernetes.md#specifying-the-environment)
|
||||||
|
to determine the environment. If you are using
|
||||||
|
[Auto DevOps](../../../topics/autodevops/index.md), this is handled automatically.
|
||||||
|
|
||||||
The [NGINX Ingress](../clusters/index.md#installing-applications) that is deployed by GitLab to clusters, is automatically annotated for monitoring providing key response metrics: latency, throughput, and error rates.
|
The [NGINX Ingress](../clusters/index.md#installing-applications) that is deployed
|
||||||
|
by GitLab to clusters, is automatically annotated for monitoring providing key
|
||||||
|
response metrics: latency, throughput, and error rates.
|
||||||
|
|
||||||
##### Example of Kubernetes service annotations and labels
|
##### Example of Kubernetes service annotations and labels
|
||||||
|
|
||||||
|
@ -161,15 +176,16 @@ Installing and configuring Prometheus to monitor applications is fairly straight
|
||||||
|
|
||||||
1. [Install Prometheus](https://prometheus.io/docs/prometheus/latest/installation/)
|
1. [Install Prometheus](https://prometheus.io/docs/prometheus/latest/installation/)
|
||||||
1. Set up one of the [supported monitoring targets](prometheus_library/index.md)
|
1. Set up one of the [supported monitoring targets](prometheus_library/index.md)
|
||||||
1. Configure the Prometheus server to [collect their metrics](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config)
|
1. Configure the Prometheus server to
|
||||||
|
[collect their metrics](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config)
|
||||||
|
|
||||||
#### Configuration in GitLab
|
#### Configuration in GitLab
|
||||||
|
|
||||||
The actual configuration of Prometheus integration within GitLab
|
The actual configuration of Prometheus integration in GitLab
|
||||||
requires the domain name or IP address of the Prometheus server you'd like
|
requires the domain name or IP address of the Prometheus server you'd like
|
||||||
to integrate with. If the Prometheus resource is secured with Google's Identity-Aware Proxy (IAP),
|
to integrate with. If the Prometheus resource is secured with Google's Identity-Aware Proxy (IAP),
|
||||||
additional information like Client ID and Service Account credentials can be passed which
|
you can pass information like Client ID and Service Account credentials.
|
||||||
GitLab can use to access the resource. More information about authentication from a
|
GitLab can use these to access the resource. More information about authentication from a
|
||||||
service account can be found at Google's documentation for
|
service account can be found at Google's documentation for
|
||||||
[Authenticating from a service account](https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_a_service_account).
|
[Authenticating from a service account](https://cloud.google.com/iap/docs/authentication-howto#authenticating_from_a_service_account).
|
||||||
|
|
||||||
|
@ -189,12 +205,13 @@ service account can be found at Google's documentation for
|
||||||
#### Thanos configuration in GitLab
|
#### Thanos configuration in GitLab
|
||||||
|
|
||||||
You can configure [Thanos](https://thanos.io/) as a drop-in replacement for Prometheus
|
You can configure [Thanos](https://thanos.io/) as a drop-in replacement for Prometheus
|
||||||
with GitLab, using the domain name or IP address of the Thanos server you'd like
|
with GitLab. Use the domain name or IP address of the Thanos server you'd like
|
||||||
to integrate with.
|
to integrate with.
|
||||||
|
|
||||||
1. Navigate to the [Integrations page](overview.md#accessing-integrations).
|
1. Navigate to the [Integrations page](overview.md#accessing-integrations).
|
||||||
1. Click the **Prometheus** service.
|
1. Click the **Prometheus** service.
|
||||||
1. Provide the domain name or IP address of your server, for example `http://thanos.example.com/` or `http://192.0.2.1/`.
|
1. Provide the domain name or IP address of your server, for example
|
||||||
|
`http://thanos.example.com/` or `http://192.0.2.1/`.
|
||||||
1. Click **Save changes**.
|
1. Click **Save changes**.
|
||||||
|
|
||||||
### Precedence with multiple Prometheus configurations
|
### Precedence with multiple Prometheus configurations
|
||||||
|
@ -221,7 +238,7 @@ can use only one:
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/10408) in GitLab 9.2.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/10408) in GitLab 9.2.
|
||||||
> - GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/27439) of the 30 minute averages.
|
> - GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/27439) of the 30 minute averages.
|
||||||
|
|
||||||
Developers can view the performance impact of their changes within the merge
|
Developers can view the performance impact of their changes in the merge
|
||||||
request workflow. This feature requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
|
request workflow. This feature requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
|
||||||
|
|
||||||
When a source branch has been deployed to an environment, a sparkline and
|
When a source branch has been deployed to an environment, a sparkline and
|
||||||
|
|
|
@ -48,6 +48,10 @@ module Gitlab
|
||||||
Gitlab.config.gitlab.url == COM_URL || gl_subdomain?
|
Gitlab.config.gitlab.url == COM_URL || gl_subdomain?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.com
|
||||||
|
yield if com?
|
||||||
|
end
|
||||||
|
|
||||||
def self.staging?
|
def self.staging?
|
||||||
Gitlab.config.gitlab.url == STAGING_COM_URL
|
Gitlab.config.gitlab.url == STAGING_COM_URL
|
||||||
end
|
end
|
||||||
|
@ -118,6 +122,7 @@ module Gitlab
|
||||||
|
|
||||||
def self.maintenance_mode?
|
def self.maintenance_mode?
|
||||||
return false unless ::Feature.enabled?(:maintenance_mode)
|
return false unless ::Feature.enabled?(:maintenance_mode)
|
||||||
|
return false unless ::Gitlab::CurrentSettings.current_application_settings?
|
||||||
|
|
||||||
::Gitlab::CurrentSettings.maintenance_mode
|
::Gitlab::CurrentSettings.maintenance_mode
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'tempfile'
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Composer
|
||||||
|
class Cache
|
||||||
|
def initialize(project:, name:, last_page_sha: nil)
|
||||||
|
@project = project
|
||||||
|
@name = name
|
||||||
|
@last_page_sha = last_page_sha
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
Packages::Composer::Metadatum.transaction do # rubocop: disable CodeReuse/ActiveRecord
|
||||||
|
# make sure we lock these records at the start
|
||||||
|
locked_package_metadata
|
||||||
|
|
||||||
|
if locked_package_metadata.any?
|
||||||
|
mark_pages_for_delete(shas_to_delete)
|
||||||
|
|
||||||
|
create_cache_page!
|
||||||
|
|
||||||
|
# assign the newest page SHA to the packages
|
||||||
|
locked_package_metadata.update_all(version_cache_sha: version_index.sha)
|
||||||
|
elsif @last_page_sha
|
||||||
|
mark_pages_for_delete([@last_page_sha])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def mark_pages_for_delete(shas)
|
||||||
|
Packages::Composer::CacheFile
|
||||||
|
.with_namespace(@project.namespace)
|
||||||
|
.with_sha(shas)
|
||||||
|
.update_all(delete_at: 1.day.from_now)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_cache_page!
|
||||||
|
Packages::Composer::CacheFile
|
||||||
|
.safe_find_or_create_by!(namespace_id: @project.namespace_id, file_sha256: version_index.sha) do |cache_file|
|
||||||
|
cache_file.file = CarrierWaveStringFile.new(version_index.to_json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def version_index
|
||||||
|
@version_index ||= ::Gitlab::Composer::VersionIndex.new(siblings)
|
||||||
|
end
|
||||||
|
|
||||||
|
def siblings
|
||||||
|
@siblings ||= locked_package_metadata.map(&:package)
|
||||||
|
end
|
||||||
|
|
||||||
|
# find all metadata of the package versions and lock it for update
|
||||||
|
def locked_package_metadata
|
||||||
|
@locked_package_metadata ||= Packages::Composer::Metadatum
|
||||||
|
.for_package(@name, @project.id)
|
||||||
|
.locked_for_update
|
||||||
|
end
|
||||||
|
|
||||||
|
def shas_to_delete
|
||||||
|
locked_package_metadata
|
||||||
|
.map(&:version_cache_sha)
|
||||||
|
.reject { |sha| sha == version_index.sha }
|
||||||
|
.compact
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -20,7 +20,7 @@ module Gitlab
|
||||||
private
|
private
|
||||||
|
|
||||||
def package_versions_map
|
def package_versions_map
|
||||||
@packages.each_with_object({}) do |package, map|
|
@packages.sort_by(&:version).each_with_object({}) do |package, map|
|
||||||
map[package.version] = package_metadata(package)
|
map[package.version] = package_metadata(package)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,25 +6,44 @@ module Gitlab
|
||||||
|
|
||||||
AES256_GCM_OPTIONS = {
|
AES256_GCM_OPTIONS = {
|
||||||
algorithm: 'aes-256-gcm',
|
algorithm: 'aes-256-gcm',
|
||||||
key: Settings.attr_encrypted_db_key_base_32,
|
key: Settings.attr_encrypted_db_key_base_32
|
||||||
iv: Settings.attr_encrypted_db_key_base_12
|
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
AES256_GCM_IV_STATIC = Settings.attr_encrypted_db_key_base_12
|
||||||
|
|
||||||
def sha256(value)
|
def sha256(value)
|
||||||
salt = Settings.attr_encrypted_db_key_base_truncated
|
salt = Settings.attr_encrypted_db_key_base_truncated
|
||||||
::Digest::SHA256.base64digest("#{value}#{salt}")
|
::Digest::SHA256.base64digest("#{value}#{salt}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def aes256_gcm_encrypt(value)
|
def aes256_gcm_encrypt(value, nonce: nil)
|
||||||
encrypted_token = Encryptor.encrypt(AES256_GCM_OPTIONS.merge(value: value))
|
aes256_gcm_encrypt_using_static_nonce(value)
|
||||||
Base64.strict_encode64(encrypted_token)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def aes256_gcm_decrypt(value)
|
def aes256_gcm_decrypt(value)
|
||||||
return unless value
|
return unless value
|
||||||
|
|
||||||
|
nonce = Feature.enabled?(:dynamic_nonce_creation) ? dynamic_nonce(value) : AES256_GCM_IV_STATIC
|
||||||
encrypted_token = Base64.decode64(value)
|
encrypted_token = Base64.decode64(value)
|
||||||
Encryptor.decrypt(AES256_GCM_OPTIONS.merge(value: encrypted_token))
|
decrypted_token = Encryptor.decrypt(AES256_GCM_OPTIONS.merge(value: encrypted_token, iv: nonce))
|
||||||
|
decrypted_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def dynamic_nonce(value)
|
||||||
|
TokenWithIv.find_nonce_by_hashed_token(value) || AES256_GCM_IV_STATIC
|
||||||
|
end
|
||||||
|
|
||||||
|
def aes256_gcm_encrypt_using_static_nonce(value)
|
||||||
|
create_encrypted_token(value, AES256_GCM_IV_STATIC)
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_only?
|
||||||
|
Gitlab::Database.read_only?
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_encrypted_token(value, iv)
|
||||||
|
encrypted_token = Encryptor.encrypt(AES256_GCM_OPTIONS.merge(value: value, iv: iv))
|
||||||
|
Base64.strict_encode64(encrypted_token)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,10 @@ module Gitlab
|
||||||
Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
|
Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def current_application_settings?
|
||||||
|
Gitlab::SafeRequestStore.exist?(:current_application_settings) || ::ApplicationSetting.current.present?
|
||||||
|
end
|
||||||
|
|
||||||
def expire_current_application_settings
|
def expire_current_application_settings
|
||||||
::ApplicationSetting.expire
|
::ApplicationSetting.expire
|
||||||
Gitlab::SafeRequestStore.delete(:current_application_settings)
|
Gitlab::SafeRequestStore.delete(:current_application_settings)
|
||||||
|
|
|
@ -49,13 +49,21 @@ module Gitlab
|
||||||
private
|
private
|
||||||
|
|
||||||
def process_variables(variables)
|
def process_variables(variables)
|
||||||
if variables.respond_to?(:to_s)
|
filtered_variables = filter_sensitive_variables(variables)
|
||||||
variables.to_s
|
|
||||||
|
if filtered_variables.respond_to?(:to_s)
|
||||||
|
filtered_variables.to_s
|
||||||
else
|
else
|
||||||
variables
|
filtered_variables
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filter_sensitive_variables(variables)
|
||||||
|
ActiveSupport::ParameterFilter
|
||||||
|
.new(::Rails.application.config.filter_parameters)
|
||||||
|
.filter(variables)
|
||||||
|
end
|
||||||
|
|
||||||
def duration(time_started)
|
def duration(time_started)
|
||||||
Gitlab::Metrics::System.monotonic_time - time_started
|
Gitlab::Metrics::System.monotonic_time - time_started
|
||||||
end
|
end
|
||||||
|
|
|
@ -49,10 +49,12 @@ module Gitlab
|
||||||
return [uri, nil] unless address_info
|
return [uri, nil] unless address_info
|
||||||
|
|
||||||
ip_address = ip_address(address_info)
|
ip_address = ip_address(address_info)
|
||||||
return [uri, nil] if domain_allowed?(uri) || ip_allowed?(ip_address, port: get_port(uri))
|
return [uri, nil] if domain_allowed?(uri)
|
||||||
|
|
||||||
protected_uri_with_hostname = enforce_uri_hostname(ip_address, uri, dns_rebind_protection)
|
protected_uri_with_hostname = enforce_uri_hostname(ip_address, uri, dns_rebind_protection)
|
||||||
|
|
||||||
|
return protected_uri_with_hostname if ip_allowed?(ip_address, port: get_port(uri))
|
||||||
|
|
||||||
# Allow url from the GitLab instance itself but only for the configured hostname and ports
|
# Allow url from the GitLab instance itself but only for the configured hostname and ports
|
||||||
return protected_uri_with_hostname if internal?(uri)
|
return protected_uri_with_hostname if internal?(uri)
|
||||||
|
|
||||||
|
|
|
@ -9618,6 +9618,9 @@ msgstr ""
|
||||||
msgid "Deploy to..."
|
msgid "Deploy to..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "DeployFreeze|Add a freeze period to prevent unintended releases during a period of time for a given environment. You must update the deployment jobs in %{filename} according to the deploy freezes added here. %{freeze_period_link_start}Learn more.%{freeze_period_link_end}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "DeployFreeze|Freeze end"
|
msgid "DeployFreeze|Freeze end"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -9627,15 +9630,12 @@ msgstr ""
|
||||||
msgid "DeployFreeze|No deploy freezes exist for this project. To add one, click %{strongStart}Add deploy freeze%{strongEnd}"
|
msgid "DeployFreeze|No deploy freezes exist for this project. To add one, click %{strongStart}Add deploy freeze%{strongEnd}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DeployFreeze|Specify times when deployments are not allowed for an environment. The %{filename} file must be updated to make deployment jobs aware of the %{freeze_period_link_start}freeze period%{freeze_period_link_end}."
|
msgid "DeployFreeze|Specify deploy freezes using %{cron_syntax_link_start}cron syntax%{cron_syntax_link_end}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DeployFreeze|Time zone"
|
msgid "DeployFreeze|Time zone"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "DeployFreeze|You can specify deploy freezes using only %{cron_syntax_link_start}cron syntax%{cron_syntax_link_end}."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "DeployKeys|+%{count} others"
|
msgid "DeployKeys|+%{count} others"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,8 @@ RSpec.describe Admin::RunnersController do
|
||||||
|
|
||||||
# There is still an N+1 query for `runner.builds.count`
|
# There is still an N+1 query for `runner.builds.count`
|
||||||
# We also need to add 1 because it takes 2 queries to preload tags
|
# We also need to add 1 because it takes 2 queries to preload tags
|
||||||
expect { get :index }.not_to exceed_query_limit(control_count + 6)
|
# also looking for token nonce requires database queries
|
||||||
|
expect { get :index }.not_to exceed_query_limit(control_count + 16)
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
expect(response.body).to have_content('tag1')
|
expect(response.body).to have_content('tag1')
|
||||||
|
|
|
@ -9,6 +9,7 @@ RSpec.describe Projects::ReleasesController do
|
||||||
let_it_be(:private_project) { create(:project, :repository, :private) }
|
let_it_be(:private_project) { create(:project, :repository, :private) }
|
||||||
let_it_be(:developer) { create(:user) }
|
let_it_be(:developer) { create(:user) }
|
||||||
let_it_be(:reporter) { create(:user) }
|
let_it_be(:reporter) { create(:user) }
|
||||||
|
let_it_be(:guest) { create(:user) }
|
||||||
let_it_be(:user) { developer }
|
let_it_be(:user) { developer }
|
||||||
let!(:release_1) { create(:release, project: project, released_at: Time.zone.parse('2018-10-18')) }
|
let!(:release_1) { create(:release, project: project, released_at: Time.zone.parse('2018-10-18')) }
|
||||||
let!(:release_2) { create(:release, project: project, released_at: Time.zone.parse('2019-10-19')) }
|
let!(:release_2) { create(:release, project: project, released_at: Time.zone.parse('2019-10-19')) }
|
||||||
|
@ -16,6 +17,7 @@ RSpec.describe Projects::ReleasesController do
|
||||||
before do
|
before do
|
||||||
project.add_developer(developer)
|
project.add_developer(developer)
|
||||||
project.add_reporter(reporter)
|
project.add_reporter(reporter)
|
||||||
|
project.add_guest(guest)
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples_for 'successful request' do
|
shared_examples_for 'successful request' do
|
||||||
|
@ -199,6 +201,13 @@ RSpec.describe Projects::ReleasesController do
|
||||||
|
|
||||||
it_behaves_like 'not found'
|
it_behaves_like 'not found'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when user is a guest' do
|
||||||
|
let(:project) { private_project }
|
||||||
|
let(:user) { guest }
|
||||||
|
|
||||||
|
it_behaves_like 'not found'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# `GET #downloads` is addressed in spec/requests/projects/releases_controller_spec.rb
|
# `GET #downloads` is addressed in spec/requests/projects/releases_controller_spec.rb
|
||||||
|
|
|
@ -176,6 +176,24 @@ FactoryBot.define do
|
||||||
composer_json { { name: 'foo' } }
|
composer_json { { name: 'foo' } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
factory :composer_cache_file, class: 'Packages::Composer::CacheFile' do
|
||||||
|
group
|
||||||
|
|
||||||
|
file_sha256 { '1' * 64 }
|
||||||
|
|
||||||
|
transient do
|
||||||
|
file_fixture { 'spec/fixtures/packages/composer/package.json' }
|
||||||
|
end
|
||||||
|
|
||||||
|
after(:build) do |cache_file, evaluator|
|
||||||
|
cache_file.file = fixture_file_upload(evaluator.file_fixture)
|
||||||
|
end
|
||||||
|
|
||||||
|
trait(:object_storage) do
|
||||||
|
file_store { Packages::Composer::CacheUploader::Store::REMOTE }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
factory :maven_metadatum, class: 'Packages::Maven::Metadatum' do
|
factory :maven_metadatum, class: 'Packages::Maven::Metadatum' do
|
||||||
association :package, package_type: :maven
|
association :package, package_type: :maven
|
||||||
path { 'my/company/app/my-app/1.0-SNAPSHOT' }
|
path { 'my/company/app/my-app/1.0-SNAPSHOT' }
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :token_with_iv do
|
||||||
|
hashed_token { ::Digest::SHA256.digest(SecureRandom.hex(50)) }
|
||||||
|
iv { ::Digest::SHA256.digest(SecureRandom.hex(50)) }
|
||||||
|
hashed_plaintext_token { ::Digest::SHA256.digest(SecureRandom.hex(50)) }
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -78,6 +78,18 @@ describe('MrWidgetPipelineContainer', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sanitizes the targetBranch', () => {
|
||||||
|
factory({
|
||||||
|
isPostMerge: true,
|
||||||
|
mr: {
|
||||||
|
...mockStore,
|
||||||
|
targetBranch: 'Foo<script>alert("XSS")</script>',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find(MrWidgetPipeline).props().sourceBranchLink).toBe('Foo');
|
||||||
|
});
|
||||||
|
|
||||||
it('renders deployments', () => {
|
it('renders deployments', () => {
|
||||||
const expectedProps = mockStore.postMergeDeployments.map((dep) =>
|
const expectedProps = mockStore.postMergeDeployments.map((dep) =>
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::Composer::Cache do
|
||||||
|
let_it_be(:package_name) { 'sample-project' }
|
||||||
|
let_it_be(:json) { { 'name' => package_name } }
|
||||||
|
let_it_be(:group) { create(:group) }
|
||||||
|
let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json }, group: group) }
|
||||||
|
let(:branch) { project.repository.find_branch('master') }
|
||||||
|
let(:sha_regex) { /^[A-Fa-f0-9]{64}$/ }
|
||||||
|
|
||||||
|
shared_examples 'Composer create cache page' do
|
||||||
|
let(:expected_json) { ::Gitlab::Composer::VersionIndex.new(packages).to_json }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_composer_cache_object_storage
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates the cached page' do
|
||||||
|
expect { subject }.to change { Packages::Composer::CacheFile.count }.by(1)
|
||||||
|
cache_file = Packages::Composer::CacheFile.last
|
||||||
|
expect(cache_file.file_sha256).to eq package.reload.composer_metadatum.version_cache_sha
|
||||||
|
expect(cache_file.file.read).to eq expected_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'Composer marks cache page for deletion' do
|
||||||
|
it 'marks the page for deletion' do
|
||||||
|
cache_file = Packages::Composer::CacheFile.last
|
||||||
|
|
||||||
|
freeze_time do
|
||||||
|
expect { subject }.to change { cache_file.reload.delete_at}.from(nil).to(1.day.from_now)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#execute' do
|
||||||
|
subject { described_class.new(project: project, name: package_name).execute }
|
||||||
|
|
||||||
|
context 'creating packages' do
|
||||||
|
context 'with a pre-existing package' do
|
||||||
|
let(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
|
||||||
|
let(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
|
||||||
|
let(:packages) { [package, package2] }
|
||||||
|
|
||||||
|
before do
|
||||||
|
package
|
||||||
|
described_class.new(project: project, name: package_name).execute
|
||||||
|
package.reload
|
||||||
|
package2
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates the sha and creates the cache page' do
|
||||||
|
expect { subject }.to change { package2.reload.composer_metadatum.version_cache_sha }.from(nil).to(sha_regex)
|
||||||
|
.and change { package.reload.composer_metadatum.version_cache_sha }.to(sha_regex)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'Composer create cache page'
|
||||||
|
it_behaves_like 'Composer marks cache page for deletion'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'first package' do
|
||||||
|
let!(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
|
||||||
|
let(:packages) { [package] }
|
||||||
|
|
||||||
|
it 'updates the sha and creates the cache page' do
|
||||||
|
expect { subject }.to change { package.reload.composer_metadatum.version_cache_sha }.from(nil).to(sha_regex)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'Composer create cache page'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'updating packages' do
|
||||||
|
let(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
|
||||||
|
let(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
|
||||||
|
let(:packages) { [package, package2] }
|
||||||
|
|
||||||
|
before do
|
||||||
|
packages
|
||||||
|
|
||||||
|
described_class.new(project: project, name: package_name).execute
|
||||||
|
|
||||||
|
package.update!(version: '1.2.0')
|
||||||
|
package.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'Composer create cache page'
|
||||||
|
it_behaves_like 'Composer marks cache page for deletion'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'deleting packages' do
|
||||||
|
context 'when it is not the last package' do
|
||||||
|
let(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
|
||||||
|
let(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }
|
||||||
|
let(:packages) { [package] }
|
||||||
|
|
||||||
|
before do
|
||||||
|
package
|
||||||
|
package2
|
||||||
|
|
||||||
|
described_class.new(project: project, name: package_name).execute
|
||||||
|
|
||||||
|
package2.destroy!
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'Composer create cache page'
|
||||||
|
it_behaves_like 'Composer marks cache page for deletion'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is the last package' do
|
||||||
|
let!(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
|
||||||
|
let!(:last_sha) do
|
||||||
|
described_class.new(project: project, name: package_name).execute
|
||||||
|
package.reload.composer_metadatum.version_cache_sha
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
package.destroy!
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { described_class.new(project: project, name: package_name, last_page_sha: last_sha).execute }
|
||||||
|
|
||||||
|
it_behaves_like 'Composer marks cache page for deletion'
|
||||||
|
|
||||||
|
it 'does not create a new page' do
|
||||||
|
expect { subject }.not_to change { Packages::Composer::CacheFile.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,7 +15,9 @@ RSpec.describe Gitlab::Composer::VersionIndex do
|
||||||
let(:packages) { [package1, package2] }
|
let(:packages) { [package1, package2] }
|
||||||
|
|
||||||
describe '#as_json' do
|
describe '#as_json' do
|
||||||
subject(:index) { described_class.new(packages).as_json }
|
subject(:package_index) { index['packages'][package_name] }
|
||||||
|
|
||||||
|
let(:index) { described_class.new(packages).as_json }
|
||||||
|
|
||||||
def expected_json(package)
|
def expected_json(package)
|
||||||
{
|
{
|
||||||
|
@ -32,10 +34,16 @@ RSpec.describe Gitlab::Composer::VersionIndex do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the packages json' do
|
it 'returns the packages json' do
|
||||||
packages = index['packages'][package_name]
|
expect(package_index['1.0.0']).to eq(expected_json(package1))
|
||||||
|
expect(package_index['2.0.0']).to eq(expected_json(package2))
|
||||||
|
end
|
||||||
|
|
||||||
expect(packages['1.0.0']).to eq(expected_json(package1))
|
context 'with an unordered list of packages' do
|
||||||
expect(packages['2.0.0']).to eq(expected_json(package2))
|
let(:packages) { [package2, package1] }
|
||||||
|
|
||||||
|
it 'returns the packages sorted by version' do
|
||||||
|
expect(package_index.keys).to eq ['1.0.0', '2.0.0']
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -19,21 +19,85 @@ RSpec.describe Gitlab::CryptoHelper do
|
||||||
expect(encrypted).to match %r{\A[A-Za-z0-9+/=]+\z}
|
expect(encrypted).to match %r{\A[A-Za-z0-9+/=]+\z}
|
||||||
expect(encrypted).not_to include "\n"
|
expect(encrypted).not_to include "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not save hashed token with iv value in database' do
|
||||||
|
expect { described_class.aes256_gcm_encrypt('some-value') }.not_to change { TokenWithIv.count }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'encrypts using static iv' do
|
||||||
|
expect(Encryptor).to receive(:encrypt).with(described_class::AES256_GCM_OPTIONS.merge(value: 'some-value', iv: described_class::AES256_GCM_IV_STATIC)).and_return('hashed_value')
|
||||||
|
|
||||||
|
described_class.aes256_gcm_encrypt('some-value')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.aes256_gcm_decrypt' do
|
describe '.aes256_gcm_decrypt' do
|
||||||
let(:encrypted) { described_class.aes256_gcm_encrypt('some-value') }
|
before do
|
||||||
|
stub_feature_flags(dynamic_nonce_creation: false)
|
||||||
it 'correctly decrypts encrypted string' do
|
|
||||||
decrypted = described_class.aes256_gcm_decrypt(encrypted)
|
|
||||||
|
|
||||||
expect(decrypted).to eq 'some-value'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'decrypts a value when it ends with a new line character' do
|
context 'when token was encrypted using static nonce' do
|
||||||
decrypted = described_class.aes256_gcm_decrypt(encrypted + "\n")
|
let(:encrypted) { described_class.aes256_gcm_encrypt('some-value', nonce: described_class::AES256_GCM_IV_STATIC) }
|
||||||
|
|
||||||
expect(decrypted).to eq 'some-value'
|
it 'correctly decrypts encrypted string' do
|
||||||
|
decrypted = described_class.aes256_gcm_decrypt(encrypted)
|
||||||
|
|
||||||
|
expect(decrypted).to eq 'some-value'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'decrypts a value when it ends with a new line character' do
|
||||||
|
decrypted = described_class.aes256_gcm_decrypt(encrypted + "\n")
|
||||||
|
|
||||||
|
expect(decrypted).to eq 'some-value'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not save hashed token with iv value in database' do
|
||||||
|
expect { described_class.aes256_gcm_decrypt(encrypted) }.not_to change { TokenWithIv.count }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with feature flag switched on' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(dynamic_nonce_creation: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'correctly decrypts encrypted string' do
|
||||||
|
decrypted = described_class.aes256_gcm_decrypt(encrypted)
|
||||||
|
|
||||||
|
expect(decrypted).to eq 'some-value'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when token was encrypted using random nonce' do
|
||||||
|
let(:value) { 'random-value' }
|
||||||
|
|
||||||
|
# for compatibility with tokens encrypted using dynamic nonce
|
||||||
|
let!(:encrypted) do
|
||||||
|
iv = create_nonce
|
||||||
|
encrypted_token = described_class.create_encrypted_token(value, iv)
|
||||||
|
TokenWithIv.create!(hashed_token: Digest::SHA256.digest(encrypted_token), hashed_plaintext_token: Digest::SHA256.digest(encrypted_token), iv: iv)
|
||||||
|
encrypted_token
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_feature_flags(dynamic_nonce_creation: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'correctly decrypts encrypted string' do
|
||||||
|
decrypted = described_class.aes256_gcm_decrypt(encrypted)
|
||||||
|
|
||||||
|
expect(decrypted).to eq value
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not save hashed token with iv value in database' do
|
||||||
|
expect { described_class.aes256_gcm_decrypt(encrypted) }.not_to change { TokenWithIv.count }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_nonce
|
||||||
|
cipher = OpenSSL::Cipher.new('aes-256-gcm')
|
||||||
|
cipher.encrypt # Required before '#random_iv' can be called
|
||||||
|
cipher.random_iv # Ensures that the IV is the correct length respective to the algorithm used.
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -194,4 +194,32 @@ RSpec.describe Gitlab::CurrentSettings do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#current_application_settings?', :use_clean_rails_memory_store_caching do
|
||||||
|
before do
|
||||||
|
allow(Gitlab::CurrentSettings).to receive(:current_application_settings?).and_call_original
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true when settings exist' do
|
||||||
|
create(:application_setting,
|
||||||
|
home_page_url: 'http://mydomain.com',
|
||||||
|
signup_enabled: false)
|
||||||
|
|
||||||
|
expect(described_class.current_application_settings?).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false when settings do not exist' do
|
||||||
|
expect(described_class.current_application_settings?).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with cache', :request_store do
|
||||||
|
include_context 'with settings in cache'
|
||||||
|
|
||||||
|
it 'returns an in-memory ApplicationSetting object' do
|
||||||
|
expect(ApplicationSetting).not_to receive(:current)
|
||||||
|
|
||||||
|
expect(described_class.current_application_settings?).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,4 +40,22 @@ RSpec.describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#initial_value' do
|
||||||
|
it 'filters out sensitive variables' do
|
||||||
|
doc = GraphQL.parse <<-GRAPHQL
|
||||||
|
mutation createNote($body: String!) {
|
||||||
|
createNote(input: {noteableId: "1", body: $body}) {
|
||||||
|
note {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GRAPHQL
|
||||||
|
|
||||||
|
query = GraphQL::Query.new(GitlabSchema, document: doc, context: {}, variables: { body: "some note" })
|
||||||
|
|
||||||
|
expect(subject.initial_value(query)[:variables]).to eq('{:body=>"[FILTERED]"}')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -91,6 +91,21 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'DNS rebinding protection with IP allowed' do
|
||||||
|
let(:import_url) { 'http://a.192.168.0.120.3times.127.0.0.1.1time.repeat.rebind.network:9121/scrape?target=unix:///var/opt/gitlab/redis/redis.socket&check-keys=*' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_dns(import_url, ip_address: '192.168.0.120')
|
||||||
|
|
||||||
|
allow(Gitlab::UrlBlockers::UrlAllowlist).to receive(:ip_allowed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'validates URI and hostname' do
|
||||||
|
let(:expected_uri) { 'http://192.168.0.120:9121/scrape?target=unix:///var/opt/gitlab/redis/redis.socket&check-keys=*' }
|
||||||
|
let(:expected_hostname) { 'a.192.168.0.120.3times.127.0.0.1.1time.repeat.rebind.network' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'disabled DNS rebinding protection' do
|
context 'disabled DNS rebinding protection' do
|
||||||
subject { described_class.validate!(import_url, dns_rebind_protection: false) }
|
subject { described_class.validate!(import_url, dns_rebind_protection: false) }
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,26 @@ RSpec.describe Gitlab do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.com' do
|
||||||
|
subject { described_class.com { true } }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:com?).and_return(gl_com)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when on GitLab.com' do
|
||||||
|
let(:gl_com) { true }
|
||||||
|
|
||||||
|
it { is_expected.to be true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not on GitLab.com' do
|
||||||
|
let(:gl_com) { false }
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '.staging?' do
|
describe '.staging?' do
|
||||||
subject { described_class.staging? }
|
subject { described_class.staging? }
|
||||||
|
|
||||||
|
@ -332,13 +352,13 @@ RSpec.describe Gitlab do
|
||||||
|
|
||||||
describe '.maintenance_mode?' do
|
describe '.maintenance_mode?' do
|
||||||
it 'returns true when maintenance mode is enabled' do
|
it 'returns true when maintenance mode is enabled' do
|
||||||
stub_application_setting(maintenance_mode: true)
|
stub_maintenance_mode_setting(true)
|
||||||
|
|
||||||
expect(described_class.maintenance_mode?).to eq(true)
|
expect(described_class.maintenance_mode?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns false when maintenance mode is disabled' do
|
it 'returns false when maintenance mode is disabled' do
|
||||||
stub_application_setting(maintenance_mode: false)
|
stub_maintenance_mode_setting(false)
|
||||||
|
|
||||||
expect(described_class.maintenance_mode?).to eq(false)
|
expect(described_class.maintenance_mode?).to eq(false)
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@ RSpec.describe EncryptFeatureFlagsClientsTokens do
|
||||||
let(:feature_flags_clients) { table(:operations_feature_flags_clients) }
|
let(:feature_flags_clients) { table(:operations_feature_flags_clients) }
|
||||||
let(:projects) { table(:projects) }
|
let(:projects) { table(:projects) }
|
||||||
let(:plaintext) { "secret-token" }
|
let(:plaintext) { "secret-token" }
|
||||||
let(:ciphertext) { Gitlab::CryptoHelper.aes256_gcm_encrypt(plaintext) }
|
let(:ciphertext) { Gitlab::CryptoHelper.aes256_gcm_encrypt(plaintext, nonce: Gitlab::CryptoHelper::AES256_GCM_IV_STATIC) }
|
||||||
|
|
||||||
describe '#up' do
|
describe '#up' do
|
||||||
it 'keeps plaintext token the same and populates token_encrypted if not present' do
|
it 'keeps plaintext token the same and populates token_encrypted if not present' do
|
||||||
|
|
|
@ -358,7 +358,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
|
||||||
it 'calls .destroy_sessions' do
|
it 'calls .destroy_sessions' do
|
||||||
expect(ActiveSession).to(
|
expect(ActiveSession).to(
|
||||||
receive(:destroy_sessions)
|
receive(:destroy_sessions)
|
||||||
.with(anything, user, [active_session.public_id, rack_session.public_id, rack_session.private_id]))
|
.with(anything, user, [encrypted_active_session_id, rack_session.public_id, rack_session.private_id]))
|
||||||
|
|
||||||
subject
|
subject
|
||||||
end
|
end
|
||||||
|
|
|
@ -54,7 +54,7 @@ RSpec.describe ApplicationSetting, 'TokenAuthenticatable' do
|
||||||
it 'persists new token as an encrypted string' do
|
it 'persists new token as an encrypted string' do
|
||||||
expect(subject).to eq settings.reload.runners_registration_token
|
expect(subject).to eq settings.reload.runners_registration_token
|
||||||
expect(settings.read_attribute('runners_registration_token_encrypted'))
|
expect(settings.read_attribute('runners_registration_token_encrypted'))
|
||||||
.to eq Gitlab::CryptoHelper.aes256_gcm_encrypt(subject)
|
.to eq Gitlab::CryptoHelper.aes256_gcm_encrypt(subject, nonce: Gitlab::CryptoHelper::AES256_GCM_IV_STATIC)
|
||||||
expect(settings).to be_persisted
|
expect(settings).to be_persisted
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ RSpec.describe Ci::Build, 'TokenAuthenticatable' do
|
||||||
it 'persists new token as an encrypted string' do
|
it 'persists new token as an encrypted string' do
|
||||||
build.ensure_token!
|
build.ensure_token!
|
||||||
|
|
||||||
encrypted = Gitlab::CryptoHelper.aes256_gcm_encrypt(build.token)
|
encrypted = Gitlab::CryptoHelper.aes256_gcm_encrypt(build.token, nonce: Gitlab::CryptoHelper::AES256_GCM_IV_STATIC)
|
||||||
|
|
||||||
expect(build.read_attribute('token_encrypted')).to eq encrypted
|
expect(build.read_attribute('token_encrypted')).to eq encrypted
|
||||||
end
|
end
|
||||||
|
|
|
@ -68,6 +68,10 @@ RSpec.describe TokenAuthenticatableStrategies::Encrypted do
|
||||||
context 'when using optional strategy' do
|
context 'when using optional strategy' do
|
||||||
let(:options) { { encrypted: :optional } }
|
let(:options) { { encrypted: :optional } }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_feature_flags(dynamic_nonce_creation: false)
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns decrypted token when an encrypted token is present' do
|
it 'returns decrypted token when an encrypted token is present' do
|
||||||
allow(instance).to receive(:read_attribute)
|
allow(instance).to receive(:read_attribute)
|
||||||
.with('some_field_encrypted')
|
.with('some_field_encrypted')
|
||||||
|
@ -124,7 +128,7 @@ RSpec.describe TokenAuthenticatableStrategies::Encrypted do
|
||||||
|
|
||||||
it 'writes encrypted token and removes plaintext token and returns it' do
|
it 'writes encrypted token and removes plaintext token and returns it' do
|
||||||
expect(instance).to receive(:[]=)
|
expect(instance).to receive(:[]=)
|
||||||
.with('some_field_encrypted', encrypted)
|
.with('some_field_encrypted', any_args)
|
||||||
expect(instance).to receive(:[]=)
|
expect(instance).to receive(:[]=)
|
||||||
.with('some_field', nil)
|
.with('some_field', nil)
|
||||||
|
|
||||||
|
@ -137,7 +141,7 @@ RSpec.describe TokenAuthenticatableStrategies::Encrypted do
|
||||||
|
|
||||||
it 'writes encrypted token and writes plaintext token' do
|
it 'writes encrypted token and writes plaintext token' do
|
||||||
expect(instance).to receive(:[]=)
|
expect(instance).to receive(:[]=)
|
||||||
.with('some_field_encrypted', encrypted)
|
.with('some_field_encrypted', any_args)
|
||||||
expect(instance).to receive(:[]=)
|
expect(instance).to receive(:[]=)
|
||||||
.with('some_field', 'my-value')
|
.with('some_field', 'my-value')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Packages::Composer::CacheFile, type: :model do
|
||||||
|
describe 'relationships' do
|
||||||
|
it { is_expected.to belong_to(:group) }
|
||||||
|
it { is_expected.to belong_to(:namespace) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'validations' do
|
||||||
|
it { is_expected.to validate_presence_of(:namespace) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'scopes' do
|
||||||
|
let_it_be(:group1) { create(:group) }
|
||||||
|
let_it_be(:group2) { create(:group) }
|
||||||
|
let_it_be(:cache_file1) { create(:composer_cache_file, file_sha256: '123456', group: group1) }
|
||||||
|
let_it_be(:cache_file2) { create(:composer_cache_file, file_sha256: '456778', group: group2) }
|
||||||
|
|
||||||
|
describe '.with_namespace' do
|
||||||
|
subject { described_class.with_namespace(group1) }
|
||||||
|
|
||||||
|
it { is_expected.to eq [cache_file1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.with_sha' do
|
||||||
|
subject { described_class.with_sha('123456') }
|
||||||
|
|
||||||
|
it { is_expected.to eq [cache_file1] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,4 +11,20 @@ RSpec.describe Packages::Composer::Metadatum, type: :model do
|
||||||
it { is_expected.to validate_presence_of(:target_sha) }
|
it { is_expected.to validate_presence_of(:target_sha) }
|
||||||
it { is_expected.to validate_presence_of(:composer_json) }
|
it { is_expected.to validate_presence_of(:composer_json) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'scopes' do
|
||||||
|
let_it_be(:package_name) { 'sample-project' }
|
||||||
|
let_it_be(:json) { { 'name' => package_name } }
|
||||||
|
let_it_be(:group) { create(:group) }
|
||||||
|
let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json }, group: group) }
|
||||||
|
let_it_be(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
|
||||||
|
let_it_be(:package2) { create(:composer_package, :with_metadatum, project: project, name: 'other-name', version: '1.0.0', json: json) }
|
||||||
|
let_it_be(:package3) { create(:pypi_package, name: package_name, project: project) }
|
||||||
|
|
||||||
|
describe '.for_package' do
|
||||||
|
subject { described_class.for_package(package_name, project.id) }
|
||||||
|
|
||||||
|
it { is_expected.to eq [package1.composer_metadatum] }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe TokenWithIv do
|
||||||
|
describe 'validations' do
|
||||||
|
it { is_expected.to validate_presence_of :hashed_token }
|
||||||
|
it { is_expected.to validate_presence_of :iv }
|
||||||
|
it { is_expected.to validate_presence_of :hashed_plaintext_token }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.find_by_hashed_token' do
|
||||||
|
it 'only includes matching record' do
|
||||||
|
matching_record = create(:token_with_iv, hashed_token: ::Digest::SHA256.digest('hashed-token'))
|
||||||
|
create(:token_with_iv)
|
||||||
|
|
||||||
|
expect(described_class.find_by_hashed_token('hashed-token')).to eq(matching_record)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.find_by_plaintext_token' do
|
||||||
|
it 'only includes matching record' do
|
||||||
|
matching_record = create(:token_with_iv, hashed_plaintext_token: ::Digest::SHA256.digest('hashed-token'))
|
||||||
|
create(:token_with_iv)
|
||||||
|
|
||||||
|
expect(described_class.find_by_plaintext_token('hashed-token')).to eq(matching_record)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -62,6 +62,12 @@ RSpec.describe ReleasePresenter do
|
||||||
it 'returns its own url' do
|
it 'returns its own url' do
|
||||||
is_expected.to eq(project_release_url(project, release))
|
is_expected.to eq(project_release_url(project, release))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when user is guest' do
|
||||||
|
let(:user) { guest }
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#opened_merge_requests_url' do
|
describe '#opened_merge_requests_url' do
|
||||||
|
|
|
@ -159,13 +159,17 @@ RSpec.describe 'Git HTTP requests' do
|
||||||
|
|
||||||
context "POST git-upload-pack" do
|
context "POST git-upload-pack" do
|
||||||
it "fails to find a route" do
|
it "fails to find a route" do
|
||||||
expect { clone_post(repository_path) }.to raise_error(ActionController::RoutingError)
|
clone_post(repository_path) do |response|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "POST git-receive-pack" do
|
context "POST git-receive-pack" do
|
||||||
it "fails to find a route" do
|
it "fails to find a route" do
|
||||||
expect { push_post(repository_path) }.to raise_error(ActionController::RoutingError)
|
push_post(repository_path) do |response|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,10 @@ RSpec.describe 'git_http routing' do
|
||||||
it_behaves_like 'git repository routes' do
|
it_behaves_like 'git repository routes' do
|
||||||
let(:path) { '/gitlab-org/gitlab-test.git' }
|
let(:path) { '/gitlab-org/gitlab-test.git' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'git repository routes with fallback for git-upload-pack' do
|
||||||
|
let(:path) { '/gitlab-org/gitlab-test.git' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'wiki repositories' do
|
describe 'wiki repositories' do
|
||||||
|
@ -14,6 +18,7 @@ RSpec.describe 'git_http routing' do
|
||||||
let(:path) { '/gitlab-org/gitlab-test.wiki.git' }
|
let(:path) { '/gitlab-org/gitlab-test.wiki.git' }
|
||||||
|
|
||||||
it_behaves_like 'git repository routes'
|
it_behaves_like 'git repository routes'
|
||||||
|
it_behaves_like 'git repository routes with fallback for git-upload-pack'
|
||||||
|
|
||||||
describe 'redirects', type: :request do
|
describe 'redirects', type: :request do
|
||||||
let(:web_path) { '/gitlab-org/gitlab-test/-/wikis' }
|
let(:web_path) { '/gitlab-org/gitlab-test/-/wikis' }
|
||||||
|
@ -37,12 +42,20 @@ RSpec.describe 'git_http routing' do
|
||||||
it_behaves_like 'git repository routes' do
|
it_behaves_like 'git repository routes' do
|
||||||
let(:path) { '/gitlab-org.wiki.git' }
|
let(:path) { '/gitlab-org.wiki.git' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'git repository routes with fallback for git-upload-pack' do
|
||||||
|
let(:path) { '/gitlab-org.wiki.git' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'in child group' do
|
context 'in child group' do
|
||||||
it_behaves_like 'git repository routes' do
|
it_behaves_like 'git repository routes' do
|
||||||
let(:path) { '/gitlab-org/child.wiki.git' }
|
let(:path) { '/gitlab-org/child.wiki.git' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'git repository routes with fallback for git-upload-pack' do
|
||||||
|
let(:path) { '/gitlab-org/child.wiki.git' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -51,12 +64,20 @@ RSpec.describe 'git_http routing' do
|
||||||
it_behaves_like 'git repository routes' do
|
it_behaves_like 'git repository routes' do
|
||||||
let(:path) { '/snippets/123.git' }
|
let(:path) { '/snippets/123.git' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'git repository routes without fallback' do
|
||||||
|
let(:path) { '/snippets/123.git' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'project snippet' do
|
context 'project snippet' do
|
||||||
it_behaves_like 'git repository routes' do
|
it_behaves_like 'git repository routes' do
|
||||||
let(:path) { '/gitlab-org/gitlab-test/snippets/123.git' }
|
let(:path) { '/gitlab-org/gitlab-test/snippets/123.git' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'git repository routes with fallback' do
|
||||||
|
let(:path) { '/gitlab-org/gitlab-test/snippets/123.git' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -876,4 +876,73 @@ RSpec.describe 'project routing' do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a non-existent project' do
|
||||||
|
it 'routes to 404 with get request' do
|
||||||
|
expect(get: "/gitlab/not_exist").to route_to(
|
||||||
|
'application#route_not_found',
|
||||||
|
unmatched_route: 'gitlab/not_exist'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'routes to 404 with delete request' do
|
||||||
|
expect(delete: "/gitlab/not_exist").to route_to(
|
||||||
|
'application#route_not_found',
|
||||||
|
namespace_id: 'gitlab',
|
||||||
|
project_id: 'not_exist'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'routes to 404 with post request' do
|
||||||
|
expect(post: "/gitlab/not_exist").to route_to(
|
||||||
|
'application#route_not_found',
|
||||||
|
namespace_id: 'gitlab',
|
||||||
|
project_id: 'not_exist'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'routes to 404 with put request' do
|
||||||
|
expect(put: "/gitlab/not_exist").to route_to(
|
||||||
|
'application#route_not_found',
|
||||||
|
namespace_id: 'gitlab',
|
||||||
|
project_id: 'not_exist'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with route to some action' do
|
||||||
|
it 'routes to 404 with get request to' do
|
||||||
|
expect(get: "/gitlab/not_exist/some_action").to route_to(
|
||||||
|
'application#route_not_found',
|
||||||
|
unmatched_route: 'gitlab/not_exist/some_action'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'routes to 404 with delete request' do
|
||||||
|
expect(delete: "/gitlab/not_exist/some_action").to route_to(
|
||||||
|
'application#route_not_found',
|
||||||
|
namespace_id: 'gitlab',
|
||||||
|
project_id: 'not_exist',
|
||||||
|
all: 'some_action'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'routes to 404 with post request' do
|
||||||
|
expect(post: "/gitlab/not_exist/some_action").to route_to(
|
||||||
|
'application#route_not_found',
|
||||||
|
namespace_id: 'gitlab',
|
||||||
|
project_id: 'not_exist',
|
||||||
|
all: 'some_action'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'routes to 404 with put request' do
|
||||||
|
expect(put: "/gitlab/not_exist/some_action").to route_to(
|
||||||
|
'application#route_not_found',
|
||||||
|
namespace_id: 'gitlab',
|
||||||
|
project_id: 'not_exist',
|
||||||
|
all: 'some_action'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -284,6 +284,8 @@ RSpec.configure do |config|
|
||||||
current_user_mode.send(:user)&.admin?
|
current_user_mode.send(:user)&.admin?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
allow(Gitlab::CurrentSettings).to receive(:current_application_settings?).and_return(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
config.around(:example, :quarantine) do |example|
|
config.around(:example, :quarantine) do |example|
|
||||||
|
|
|
@ -121,6 +121,12 @@ module StubConfiguration
|
||||||
allow(::Gitlab.config.packages).to receive_messages(to_settings(messages))
|
allow(::Gitlab.config.packages).to receive_messages(to_settings(messages))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def stub_maintenance_mode_setting(value)
|
||||||
|
allow(Gitlab::CurrentSettings).to receive(:current_application_settings?).and_return(true)
|
||||||
|
|
||||||
|
stub_application_setting(maintenance_mode: value)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Modifies stubbed messages to also stub possible predicate versions
|
# Modifies stubbed messages to also stub possible predicate versions
|
||||||
|
|
|
@ -85,6 +85,13 @@ module StubObjectStorage
|
||||||
**params)
|
**params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def stub_composer_cache_object_storage(**params)
|
||||||
|
stub_object_storage_uploader(config: Gitlab.config.packages.object_store,
|
||||||
|
uploader: ::Packages::Composer::CacheUploader,
|
||||||
|
remote_directory: 'packages',
|
||||||
|
**params)
|
||||||
|
end
|
||||||
|
|
||||||
def stub_uploads_object_storage(uploader = described_class, **params)
|
def stub_uploads_object_storage(uploader = described_class, **params)
|
||||||
stub_object_storage_uploader(config: Gitlab.config.uploads.object_store,
|
stub_object_storage_uploader(config: Gitlab.config.uploads.object_store,
|
||||||
uploader: uploader,
|
uploader: uploader,
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec::Matchers.define :route_to_route_not_found do
|
||||||
|
match do |actual|
|
||||||
|
expect(actual).to route_to(controller: 'application', action: 'route_not_found')
|
||||||
|
rescue RSpec::Expectations::ExpectationNotMetError => e
|
||||||
|
# `route_to` matcher requires providing all params for exact match. As we use it in shared examples and we provide different paths,
|
||||||
|
# this matcher checks if provided route matches controller and action, without checking params.
|
||||||
|
expect(e.message).to include("-{\"controller\"=>\"application\", \"action\"=>\"route_not_found\"}\n+{\"controller\"=>\"application\", \"action\"=>\"route_not_found\",")
|
||||||
|
end
|
||||||
|
|
||||||
|
failure_message do |_|
|
||||||
|
"expected #{actual} to route to route_not_found"
|
||||||
|
end
|
||||||
|
end
|
|
@ -16,10 +16,6 @@ RSpec.shared_examples 'git repository routes' do
|
||||||
expect(get("#{container_path}/info/refs?service=git-upload-pack")).to redirect_to("#{container_path}.git/info/refs?service=git-upload-pack")
|
expect(get("#{container_path}/info/refs?service=git-upload-pack")).to redirect_to("#{container_path}.git/info/refs?service=git-upload-pack")
|
||||||
expect(get("#{container_path}/info/refs?service=git-receive-pack")).to redirect_to("#{container_path}.git/info/refs?service=git-receive-pack")
|
expect(get("#{container_path}/info/refs?service=git-receive-pack")).to redirect_to("#{container_path}.git/info/refs?service=git-receive-pack")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not redirect other requests' do
|
|
||||||
expect(post("#{container_path}/git-upload-pack")).not_to be_routable
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'routes LFS endpoints' do
|
it 'routes LFS endpoints' do
|
||||||
|
@ -35,6 +31,56 @@ RSpec.shared_examples 'git repository routes' do
|
||||||
expect(get("#{path}/gitlab-lfs/objects/#{oid}")).to route_to('repositories/lfs_storage#download', oid: oid, **params)
|
expect(get("#{path}/gitlab-lfs/objects/#{oid}")).to route_to('repositories/lfs_storage#download', oid: oid, **params)
|
||||||
expect(put("#{path}/gitlab-lfs/objects/#{oid}/456/authorize")).to route_to('repositories/lfs_storage#upload_authorize', oid: oid, size: '456', **params)
|
expect(put("#{path}/gitlab-lfs/objects/#{oid}/456/authorize")).to route_to('repositories/lfs_storage#upload_authorize', oid: oid, size: '456', **params)
|
||||||
expect(put("#{path}/gitlab-lfs/objects/#{oid}/456")).to route_to('repositories/lfs_storage#upload_finalize', oid: oid, size: '456', **params)
|
expect(put("#{path}/gitlab-lfs/objects/#{oid}/456")).to route_to('repositories/lfs_storage#upload_finalize', oid: oid, size: '456', **params)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples 'git repository routes without fallback' do
|
||||||
|
let(:container_path) { path.delete_suffix('.git') }
|
||||||
|
|
||||||
|
context 'requests without .git format' do
|
||||||
|
it 'does not redirect other requests' do
|
||||||
|
expect(post("#{container_path}/git-upload-pack")).not_to be_routable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'routes LFS endpoints for unmatched routes' do
|
||||||
|
oid = generate(:oid)
|
||||||
|
|
||||||
|
expect(put("#{path}/gitlab-lfs/objects/foo")).not_to be_routable
|
||||||
|
expect(put("#{path}/gitlab-lfs/objects/#{oid}/foo")).not_to be_routable
|
||||||
|
expect(put("#{path}/gitlab-lfs/objects/#{oid}/foo/authorize")).not_to be_routable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples 'git repository routes with fallback' do
|
||||||
|
let(:container_path) { path.delete_suffix('.git') }
|
||||||
|
|
||||||
|
context 'requests without .git format' do
|
||||||
|
it 'does not redirect other requests' do
|
||||||
|
expect(post("#{container_path}/git-upload-pack")).to route_to_route_not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'routes LFS endpoints' do
|
||||||
|
oid = generate(:oid)
|
||||||
|
|
||||||
|
expect(put("#{path}/gitlab-lfs/objects/foo")).to route_to_route_not_found
|
||||||
|
expect(put("#{path}/gitlab-lfs/objects/#{oid}/foo")).to route_to_route_not_found
|
||||||
|
expect(put("#{path}/gitlab-lfs/objects/#{oid}/foo/authorize")).to route_to_route_not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples 'git repository routes with fallback for git-upload-pack' do
|
||||||
|
let(:container_path) { path.delete_suffix('.git') }
|
||||||
|
|
||||||
|
context 'requests without .git format' do
|
||||||
|
it 'does not redirect other requests' do
|
||||||
|
expect(post("#{container_path}/git-upload-pack")).to route_to_route_not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'routes LFS endpoints for unmatched routes' do
|
||||||
|
oid = generate(:oid)
|
||||||
|
|
||||||
expect(put("#{path}/gitlab-lfs/objects/foo")).not_to be_routable
|
expect(put("#{path}/gitlab-lfs/objects/foo")).not_to be_routable
|
||||||
expect(put("#{path}/gitlab-lfs/objects/#{oid}/foo")).not_to be_routable
|
expect(put("#{path}/gitlab-lfs/objects/#{oid}/foo")).not_to be_routable
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Packages::Composer::CacheUploader do
|
||||||
|
let(:cache_file) { create(:composer_cache_file) } # rubocop:disable Rails/SaveBang
|
||||||
|
let(:uploader) { described_class.new(cache_file, :file) }
|
||||||
|
let(:path) { Gitlab.config.packages.storage_path }
|
||||||
|
|
||||||
|
subject { uploader }
|
||||||
|
|
||||||
|
it_behaves_like "builds correct paths",
|
||||||
|
store_dir: %r[^\h{2}/\h{2}/\h{64}/packages/composer_cache/\d+$],
|
||||||
|
cache_dir: %r[/packages/tmp/cache],
|
||||||
|
work_dir: %r[/packages/tmp/work]
|
||||||
|
|
||||||
|
context 'object store is remote' do
|
||||||
|
before do
|
||||||
|
stub_composer_cache_object_storage
|
||||||
|
end
|
||||||
|
|
||||||
|
include_context 'with storage', described_class::Store::REMOTE
|
||||||
|
|
||||||
|
it_behaves_like "builds correct paths",
|
||||||
|
store_dir: %r[^\h{2}/\h{2}/\h{64}/packages/composer_cache/\d+$]
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'remote file' do
|
||||||
|
let(:cache_file) { create(:composer_cache_file, :object_storage) }
|
||||||
|
|
||||||
|
context 'with object storage enabled' do
|
||||||
|
before do
|
||||||
|
stub_composer_cache_object_storage
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can store file remotely' do
|
||||||
|
allow(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async)
|
||||||
|
|
||||||
|
cache_file
|
||||||
|
|
||||||
|
expect(cache_file.file_store).to eq(described_class::Store::REMOTE)
|
||||||
|
expect(cache_file.file.path).not_to be_blank
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue