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
|
||||
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)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
@ -368,6 +379,17 @@ entry.
|
|||
- 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)
|
||||
|
||||
### Fixed (2 changes, 1 of them is from the community)
|
||||
|
@ -878,6 +900,17 @@ entry.
|
|||
- 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)
|
||||
|
||||
### Security (1 change)
|
||||
|
|
|
@ -1 +1 @@
|
|||
9ed0124f6cdfc359521feae325420549781d883e
|
||||
aeec1e34a8f0fc6b453b7f091e3712f17956b580
|
||||
|
|
|
@ -25,19 +25,17 @@ initPageShortcuts();
|
|||
initCollapseSidebarOnWindowResize();
|
||||
initSelect2Dropdowns();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.requestIdleCallback(
|
||||
() => {
|
||||
// Check if we have to Load GFM Input
|
||||
const $gfmInputs = $('.js-gfm-input:not(.js-gfm-input-initialized)');
|
||||
if ($gfmInputs.length) {
|
||||
import(/* webpackChunkName: 'initGFMInput' */ './markdown/gfm_auto_complete')
|
||||
.then(({ default: initGFMInput }) => {
|
||||
initGFMInput($gfmInputs);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
{ timeout: 500 },
|
||||
);
|
||||
});
|
||||
window.requestIdleCallback(
|
||||
() => {
|
||||
// Check if we have to Load GFM Input
|
||||
const $gfmInputs = $('.js-gfm-input:not(.js-gfm-input-initialized)');
|
||||
if ($gfmInputs.length) {
|
||||
import(/* webpackChunkName: 'initGFMInput' */ './markdown/gfm_auto_complete')
|
||||
.then(({ default: initGFMInput }) => {
|
||||
initGFMInput($gfmInputs);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
{ timeout: 500 },
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { isNumber } from 'lodash';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import ArtifactsApp from './artifacts_list_app.vue';
|
||||
import MrWidgetContainer from './mr_widget_container.vue';
|
||||
|
@ -40,7 +41,7 @@ export default {
|
|||
return this.isPostMerge ? this.mr.targetBranch : this.mr.sourceBranch;
|
||||
},
|
||||
branchLink() {
|
||||
return this.isPostMerge ? this.mr.targetBranch : this.mr.sourceBranchLink;
|
||||
return this.isPostMerge ? sanitize(this.mr.targetBranch) : this.mr.sourceBranchLink;
|
||||
},
|
||||
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 :release, only: %i[edit show update downloads]
|
||||
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
|
||||
push_frontend_feature_flag(:graphql_release_data, project, default_enabled: true)
|
||||
push_frontend_feature_flag(:graphql_milestone_stats, project, default_enabled: true)
|
||||
|
|
|
@ -85,10 +85,18 @@ module TokenAuthenticatableStrategies
|
|||
end
|
||||
|
||||
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)
|
||||
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
|
||||
@insecure_strategy ||= TokenAuthenticatableStrategies::Insecure
|
||||
.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
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
||||
def self_url
|
||||
return unless can_download_code?
|
||||
|
||||
project_release_url(project, release)
|
||||
end
|
||||
|
||||
|
|
|
@ -56,11 +56,25 @@ module Projects
|
|||
raise ValidationError.new(s_('UpdateProject|Cannot rename project because it contains container registry tags!'))
|
||||
end
|
||||
|
||||
if changing_default_branch?
|
||||
raise ValidationError.new(s_("UpdateProject|Could not set the default branch")) unless project.change_head(params[:default_branch])
|
||||
validate_default_branch_change
|
||||
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
|
||||
|
||||
def after_default_branch_change(previous_default_branch)
|
||||
# overridden by EE module
|
||||
end
|
||||
|
||||
def remove_unallowed_params
|
||||
params.delete(:emails_disabled) unless can?(current_user, :set_emails_disabled, project)
|
||||
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
|
||||
- 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 }
|
||||
= 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_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
|
||||
= 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() {}
|
||||
}
|
||||
|
||||
class IncrementalWebpackCompiler extends NoopCompiler {
|
||||
class IncrementalWebpackCompiler {
|
||||
constructor(historyFilePath) {
|
||||
super();
|
||||
this.enabled = true;
|
||||
this.history = {};
|
||||
this.compiledEntryPoints = new Set([
|
||||
// Login page
|
||||
|
@ -32,8 +32,7 @@ class IncrementalWebpackCompiler extends NoopCompiler {
|
|||
'pages.root',
|
||||
]);
|
||||
this.historyFilePath = historyFilePath;
|
||||
this.loadFromHistory();
|
||||
this.enabled = true;
|
||||
this._loadFromHistory();
|
||||
}
|
||||
|
||||
filterEntryPoints(entrypoints) {
|
||||
|
@ -65,7 +64,7 @@ class IncrementalWebpackCompiler extends NoopCompiler {
|
|||
if (fileName.startsWith('pages.') && fileName.endsWith('.chunk.js')) {
|
||||
const chunk = fileName.replace(/\.chunk\.js$/, '');
|
||||
|
||||
this.addToHistory(chunk);
|
||||
this._addToHistory(chunk);
|
||||
|
||||
if (!this.compiledEntryPoints.has(chunk)) {
|
||||
log(`First time we are seeing ${chunk}. Adding to compilation.`);
|
||||
|
@ -88,7 +87,7 @@ class IncrementalWebpackCompiler extends NoopCompiler {
|
|||
|
||||
// private methods
|
||||
|
||||
addToHistory(chunk) {
|
||||
_addToHistory(chunk) {
|
||||
if (!this.history[chunk]) {
|
||||
this.history[chunk] = { lastVisit: null, count: 0 };
|
||||
}
|
||||
|
@ -102,7 +101,7 @@ class IncrementalWebpackCompiler extends NoopCompiler {
|
|||
}
|
||||
}
|
||||
|
||||
loadFromHistory() {
|
||||
_loadFromHistory() {
|
||||
try {
|
||||
this.history = JSON.parse(fs.readFileSync(this.historyFilePath, 'utf8'));
|
||||
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']['cron'] ||= '0 * * * *'
|
||||
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 * * *'
|
||||
Settings.cron_jobs['namespaces_in_product_marketing_emails_worker']['job_class'] = 'Namespaces::InProductMarketingEmailsWorker'
|
||||
|
||||
Gitlab.com do
|
||||
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
|
||||
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({})
|
||||
|
|
|
@ -277,6 +277,7 @@ Rails.application.routes.draw do
|
|||
draw :dashboard
|
||||
draw :user
|
||||
draw :project
|
||||
draw :unmatched_project
|
||||
|
||||
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/210024
|
||||
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
|
||||
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|
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ class EncryptDeployTokensTokens < ActiveRecord::Migration[5.1]
|
|||
def up
|
||||
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|
|
||||
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)
|
||||
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;
|
||||
|
||||
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 (
|
||||
package_id bigint 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;
|
||||
|
||||
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 (
|
||||
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_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_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 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 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
|
||||
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
|
||||
ADD CONSTRAINT packages_composer_metadata_pkey PRIMARY KEY (package_id);
|
||||
|
||||
|
@ -20689,6 +20733,9 @@ ALTER TABLE ONLY timelogs
|
|||
ALTER TABLE ONLY todos
|
||||
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
|
||||
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 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_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 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 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
|
||||
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
|
||||
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 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)
|
||||
- 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).
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
- [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).
|
||||
- [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.
|
||||
|
|
|
@ -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.
|
||||
---
|
||||
|
||||
# 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)
|
||||
and the [Documentation Style Guide](styleguide/index.md).
|
||||
- [Concept](#concept)
|
||||
- [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
|
||||
involves one or more features, potentially in conjunction with third-party tools.
|
||||
GitLab also uses high-level landing pages.
|
||||
|
||||
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
|
||||
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.
|
||||
Landing pages are topics that group other topics and help a user to navigate a section.
|
||||
|
||||
You can include additional subsections, as appropriate, such as *How it Works*,
|
||||
or *Architecture*. You can also include other logical divisions, such as
|
||||
pre-deployment and post-deployment tasks.
|
||||
Users who are using the in-product help do not have a left nav,
|
||||
and need these topics to navigate the documentation.
|
||||
|
||||
## 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)
|
||||
and create a new topic by using this template:
|
||||
Landing page topics should be in this format:
|
||||
|
||||
```markdown
|
||||
<!--Follow the Style Guide when working on this document.
|
||||
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
|
||||
---
|
||||
# Title (a noun, like "CI/CD or "Analytics")
|
||||
|
||||
# Feature or Use Case Name **[TIER]** (1)
|
||||
<!--If you are writing about a use case, start with a verb,
|
||||
for example, "Configure", "Implement", + the goal/scenario-->
|
||||
Brief introduction to the concept or product area.
|
||||
Include the reason why someone would use this thing.
|
||||
|
||||
<!--For pages on newly-introduced features, add the following line.
|
||||
If only some aspects of the feature have been introduced, specify which parts of the feature.-->
|
||||
> [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).
|
||||
- Bulleted list of important related topics.
|
||||
- These links are needed because users of in-product help do not have left navigation.
|
||||
```
|
||||
|
||||
## 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)
|
||||
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
|
||||
must check with a technical writer before doing so.
|
||||
|
||||
### Disqus
|
||||
#### Disqus
|
||||
|
||||
We also have integrated the docs site with Disqus (introduced by
|
||||
[!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
|
||||
**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.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
Use this guide for standards on grammar, formatting, word usage, and more.
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
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)
|
||||
|
||||
### Why a single source of truth
|
||||
|
||||
The documentation of GitLab products and features is the SSOT for all
|
||||
information related to implementation, usage, and troubleshooting. It evolves
|
||||
The GitLab documentation is the SSOT for all
|
||||
information related to GitLab implementation, usage, and troubleshooting. It evolves
|
||||
continuously, in keeping with new products and features, and with improvements
|
||||
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
|
||||
documentation.
|
||||
|
||||
### All information
|
||||
### The documentation includes all information
|
||||
|
||||
Include problem-solving actions that may address rare cases or be considered
|
||||
_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.
|
||||
|
||||
GitLab adds all troubleshooting information to the documentation, no matter how
|
||||
unlikely a user is to encounter a situation. For the [Troubleshooting sections](#troubleshooting),
|
||||
people in GitLab Support can merge additions themselves.
|
||||
unlikely a user is to encounter a situation.
|
||||
|
||||
### 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
|
||||
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
|
||||
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
|
||||
different types. For example, [Divio recommends](https://www.divio.com/blog/documentation/):
|
||||
different types. For example:
|
||||
|
||||
- Tutorials
|
||||
- How-to guides
|
||||
- Explanation
|
||||
- Reference (for example, a glossary)
|
||||
- Concepts
|
||||
- Tasks
|
||||
- Reference
|
||||
- Troubleshooting
|
||||
|
||||
At GitLab, we have so many product changes in our monthly releases that we can't
|
||||
afford to continuously update multiple types of information. If we have multiple
|
||||
types, the information becomes outdated. Therefore, we have a
|
||||
[single template](../structure.md) for documentation.
|
||||
At GitLab, we have not traditionally used topic types. However, we are starting to
|
||||
move in this direction, so we can address these issues:
|
||||
|
||||
GitLab documentation does not distinguish specific document types. We are open to
|
||||
reconsidering this policy after the documentation has reached a future stage of
|
||||
maturity and quality. If you are reading this, then despite our continuous
|
||||
improvement efforts, that point hasn't been reached.
|
||||
- **Content is hard to find.** Our docs are comprehensive and include a large amount of
|
||||
useful information. Topic types create repeatable patterns that make our content easier
|
||||
to scan and parse.
|
||||
- **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
|
||||
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.
|
||||
### Link instead of repeating text
|
||||
|
||||
### Organize by topic, not by type
|
||||
|
||||
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.
|
||||
Rather than repeating information from another topic, link to the single source
|
||||
of truth and explain why it is important.
|
||||
|
||||
### 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
|
||||
documentation. You can then share the MR to communicate this information.
|
||||
|
||||
New information about the future usage or troubleshooting
|
||||
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
|
||||
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.
|
||||
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,
|
||||
but added to a documentation MR and then referenced, as described above.
|
||||
|
||||
The more we reflexively add information to the documentation, the more
|
||||
the documentation helps others efficiently accomplish tasks and solve problems.
|
||||
|
@ -217,8 +196,11 @@ included in backticks. For example:
|
|||
|
||||
## Structure
|
||||
|
||||
Because we want documentation to be a SSOT, we should [organize by topic, not by
|
||||
type](#organize-by-topic-not-by-type).
|
||||
We include concept and task topic types in the same larger topic.
|
||||
|
||||
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
|
||||
|
||||
|
@ -299,7 +281,7 @@ place for it.
|
|||
### Avoid duplication
|
||||
|
||||
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
|
||||
|
||||
|
@ -966,8 +948,8 @@ this option.
|
|||
|
||||
## Links
|
||||
|
||||
Links are important in GitLab documentation. They allow you to [link instead of
|
||||
summarizing](#link-instead-of-summarize) to help preserve a [single source of truth](#why-a-single-source-of-truth)
|
||||
Links are important in GitLab documentation. Use links instead of
|
||||
summarizing to help preserve a [single source of truth](#documentation-is-the-single-source-of-truth-ssot)
|
||||
in GitLab documentation.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
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:
|
||||
|
||||
- **For individuals**:
|
||||
1. Create a user account for yourself using our
|
||||
[sign up page](https://gitlab.com/users/sign_up).
|
||||
1. Visit the [billing page](https://gitlab.com/profile/billings)
|
||||
under your profile.
|
||||
1. Select the **Premium** or **Ultimate** 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.
|
||||
- **For groups**:
|
||||
1. Create a user account for yourself using our
|
||||
[sign up page](https://gitlab.com/users/sign_up).
|
||||
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.
|
||||
1. Create a user account for yourself using our
|
||||
[sign up page](https://gitlab.com/users/sign_up).
|
||||
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:
|
||||
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
|
||||
|
||||
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)
|
||||
under your profile.
|
||||
- **For groups**: From the group page (*not* from a project in the group), go to **Settings > Billing**.
|
||||
NOTE:
|
||||
You must have Owner level [permissions](../../user/permissions.md) to view the billing page.
|
||||
|
||||
NOTE:
|
||||
You must have Owner level [permissions](../../user/permissions.md) to view a group's billing page.
|
||||
The following table describes details of your subscription:
|
||||
|
||||
The following table describes details of your subscription for groups:
|
||||
|
||||
| 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). |
|
||||
| **Max seats used** | Highest number of seats you've used. |
|
||||
| **Seats owed** | _Seats owed_ = _Max seats used_ - _Seats in subscription_. |
|
||||
| **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. |
|
||||
| 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). |
|
||||
| **Max seats used** | Highest number of seats you've used. |
|
||||
| **Seats owed** | _Seats owed_ = _Max seats used_ - _Seats in subscription_. |
|
||||
| **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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
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
|
||||
within the GitLab interface.
|
||||
in the GitLab interface.
|
||||
|
||||
![Environment Dashboard](img/prometheus_dashboard.png)
|
||||
|
||||
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 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).
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -36,7 +40,7 @@ GitLab can seamlessly deploy and manage Prometheus on a [connected Kubernetes cl
|
|||
|
||||
#### 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. 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
|
||||
|
||||
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/port` to define the port of the metrics endpoint.
|
||||
- `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
|
||||
|
||||
|
@ -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. 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
|
||||
|
||||
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
|
||||
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
|
||||
GitLab can use to access the resource. More information about authentication from a
|
||||
you can pass information like Client ID and Service Account credentials.
|
||||
GitLab can use these to access the resource. More information about authentication from a
|
||||
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).
|
||||
|
||||
|
@ -189,12 +205,13 @@ service account can be found at Google's documentation for
|
|||
#### Thanos configuration in GitLab
|
||||
|
||||
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.
|
||||
|
||||
1. Navigate to the [Integrations page](overview.md#accessing-integrations).
|
||||
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**.
|
||||
|
||||
### 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.
|
||||
> - 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.
|
||||
|
||||
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?
|
||||
end
|
||||
|
||||
def self.com
|
||||
yield if com?
|
||||
end
|
||||
|
||||
def self.staging?
|
||||
Gitlab.config.gitlab.url == STAGING_COM_URL
|
||||
end
|
||||
|
@ -118,6 +122,7 @@ module Gitlab
|
|||
|
||||
def self.maintenance_mode?
|
||||
return false unless ::Feature.enabled?(:maintenance_mode)
|
||||
return false unless ::Gitlab::CurrentSettings.current_application_settings?
|
||||
|
||||
::Gitlab::CurrentSettings.maintenance_mode
|
||||
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
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,25 +6,44 @@ module Gitlab
|
|||
|
||||
AES256_GCM_OPTIONS = {
|
||||
algorithm: 'aes-256-gcm',
|
||||
key: Settings.attr_encrypted_db_key_base_32,
|
||||
iv: Settings.attr_encrypted_db_key_base_12
|
||||
key: Settings.attr_encrypted_db_key_base_32
|
||||
}.freeze
|
||||
|
||||
AES256_GCM_IV_STATIC = Settings.attr_encrypted_db_key_base_12
|
||||
|
||||
def sha256(value)
|
||||
salt = Settings.attr_encrypted_db_key_base_truncated
|
||||
::Digest::SHA256.base64digest("#{value}#{salt}")
|
||||
end
|
||||
|
||||
def aes256_gcm_encrypt(value)
|
||||
encrypted_token = Encryptor.encrypt(AES256_GCM_OPTIONS.merge(value: value))
|
||||
Base64.strict_encode64(encrypted_token)
|
||||
def aes256_gcm_encrypt(value, nonce: nil)
|
||||
aes256_gcm_encrypt_using_static_nonce(value)
|
||||
end
|
||||
|
||||
def aes256_gcm_decrypt(value)
|
||||
return unless value
|
||||
|
||||
nonce = Feature.enabled?(:dynamic_nonce_creation) ? dynamic_nonce(value) : AES256_GCM_IV_STATIC
|
||||
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
|
||||
|
|
|
@ -7,6 +7,10 @@ module Gitlab
|
|||
Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
|
||||
end
|
||||
|
||||
def current_application_settings?
|
||||
Gitlab::SafeRequestStore.exist?(:current_application_settings) || ::ApplicationSetting.current.present?
|
||||
end
|
||||
|
||||
def expire_current_application_settings
|
||||
::ApplicationSetting.expire
|
||||
Gitlab::SafeRequestStore.delete(:current_application_settings)
|
||||
|
|
|
@ -49,13 +49,21 @@ module Gitlab
|
|||
private
|
||||
|
||||
def process_variables(variables)
|
||||
if variables.respond_to?(:to_s)
|
||||
variables.to_s
|
||||
filtered_variables = filter_sensitive_variables(variables)
|
||||
|
||||
if filtered_variables.respond_to?(:to_s)
|
||||
filtered_variables.to_s
|
||||
else
|
||||
variables
|
||||
filtered_variables
|
||||
end
|
||||
end
|
||||
|
||||
def filter_sensitive_variables(variables)
|
||||
ActiveSupport::ParameterFilter
|
||||
.new(::Rails.application.config.filter_parameters)
|
||||
.filter(variables)
|
||||
end
|
||||
|
||||
def duration(time_started)
|
||||
Gitlab::Metrics::System.monotonic_time - time_started
|
||||
end
|
||||
|
|
|
@ -49,10 +49,12 @@ module Gitlab
|
|||
return [uri, nil] unless 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)
|
||||
|
||||
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
|
||||
return protected_uri_with_hostname if internal?(uri)
|
||||
|
||||
|
|
|
@ -9618,6 +9618,9 @@ msgstr ""
|
|||
msgid "Deploy to..."
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9627,15 +9630,12 @@ msgstr ""
|
|||
msgid "DeployFreeze|No deploy freezes exist for this project. To add one, click %{strongStart}Add deploy freeze%{strongEnd}"
|
||||
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 ""
|
||||
|
||||
msgid "DeployFreeze|Time zone"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@ RSpec.describe Admin::RunnersController do
|
|||
|
||||
# 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
|
||||
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.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(:developer) { create(:user) }
|
||||
let_it_be(:reporter) { create(:user) }
|
||||
let_it_be(:guest) { create(:user) }
|
||||
let_it_be(:user) { developer }
|
||||
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')) }
|
||||
|
@ -16,6 +17,7 @@ RSpec.describe Projects::ReleasesController do
|
|||
before do
|
||||
project.add_developer(developer)
|
||||
project.add_reporter(reporter)
|
||||
project.add_guest(guest)
|
||||
end
|
||||
|
||||
shared_examples_for 'successful request' do
|
||||
|
@ -199,6 +201,13 @@ RSpec.describe Projects::ReleasesController do
|
|||
|
||||
it_behaves_like 'not found'
|
||||
end
|
||||
|
||||
context 'when user is a guest' do
|
||||
let(:project) { private_project }
|
||||
let(:user) { guest }
|
||||
|
||||
it_behaves_like 'not found'
|
||||
end
|
||||
end
|
||||
|
||||
# `GET #downloads` is addressed in spec/requests/projects/releases_controller_spec.rb
|
||||
|
|
|
@ -176,6 +176,24 @@ FactoryBot.define do
|
|||
composer_json { { name: 'foo' } }
|
||||
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
|
||||
association :package, package_type: :maven
|
||||
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', () => {
|
||||
const expectedProps = mockStore.postMergeDeployments.map((dep) =>
|
||||
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] }
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -32,10 +34,16 @@ RSpec.describe Gitlab::Composer::VersionIndex do
|
|||
end
|
||||
|
||||
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))
|
||||
expect(packages['2.0.0']).to eq(expected_json(package2))
|
||||
context 'with an unordered list of packages' do
|
||||
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
|
||||
|
||||
|
|
|
@ -19,21 +19,85 @@ RSpec.describe Gitlab::CryptoHelper do
|
|||
expect(encrypted).to match %r{\A[A-Za-z0-9+/=]+\z}
|
||||
expect(encrypted).not_to include "\n"
|
||||
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
|
||||
|
||||
describe '.aes256_gcm_decrypt' do
|
||||
let(:encrypted) { described_class.aes256_gcm_encrypt('some-value') }
|
||||
|
||||
it 'correctly decrypts encrypted string' do
|
||||
decrypted = described_class.aes256_gcm_decrypt(encrypted)
|
||||
|
||||
expect(decrypted).to eq 'some-value'
|
||||
before do
|
||||
stub_feature_flags(dynamic_nonce_creation: false)
|
||||
end
|
||||
|
||||
it 'decrypts a value when it ends with a new line character' do
|
||||
decrypted = described_class.aes256_gcm_decrypt(encrypted + "\n")
|
||||
context 'when token was encrypted using static nonce' do
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
@ -194,4 +194,32 @@ RSpec.describe Gitlab::CurrentSettings do
|
|||
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
|
||||
|
|
|
@ -40,4 +40,22 @@ RSpec.describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
|
|||
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
|
||||
|
|
|
@ -91,6 +91,21 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
|
|||
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
|
||||
subject { described_class.validate!(import_url, dns_rebind_protection: false) }
|
||||
|
||||
|
|
|
@ -95,6 +95,26 @@ RSpec.describe Gitlab do
|
|||
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
|
||||
subject { described_class.staging? }
|
||||
|
||||
|
@ -332,13 +352,13 @@ RSpec.describe Gitlab do
|
|||
|
||||
describe '.maintenance_mode?' 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)
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ RSpec.describe EncryptFeatureFlagsClientsTokens do
|
|||
let(:feature_flags_clients) { table(:operations_feature_flags_clients) }
|
||||
let(:projects) { table(:projects) }
|
||||
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
|
||||
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
|
||||
expect(ActiveSession).to(
|
||||
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
|
||||
end
|
||||
|
|
|
@ -54,7 +54,7 @@ RSpec.describe ApplicationSetting, 'TokenAuthenticatable' do
|
|||
it 'persists new token as an encrypted string' do
|
||||
expect(subject).to eq settings.reload.runners_registration_token
|
||||
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
|
||||
end
|
||||
|
||||
|
@ -243,7 +243,7 @@ RSpec.describe Ci::Build, 'TokenAuthenticatable' do
|
|||
it 'persists new token as an encrypted string' do
|
||||
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
|
||||
end
|
||||
|
|
|
@ -68,6 +68,10 @@ RSpec.describe TokenAuthenticatableStrategies::Encrypted do
|
|||
context 'when using optional strategy' do
|
||||
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
|
||||
allow(instance).to receive(:read_attribute)
|
||||
.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
|
||||
expect(instance).to receive(:[]=)
|
||||
.with('some_field_encrypted', encrypted)
|
||||
.with('some_field_encrypted', any_args)
|
||||
expect(instance).to receive(:[]=)
|
||||
.with('some_field', nil)
|
||||
|
||||
|
@ -137,7 +141,7 @@ RSpec.describe TokenAuthenticatableStrategies::Encrypted do
|
|||
|
||||
it 'writes encrypted token and writes plaintext token' do
|
||||
expect(instance).to receive(:[]=)
|
||||
.with('some_field_encrypted', encrypted)
|
||||
.with('some_field_encrypted', any_args)
|
||||
expect(instance).to receive(:[]=)
|
||||
.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(:composer_json) }
|
||||
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
|
||||
|
|
|
@ -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
|
||||
is_expected.to eq(project_release_url(project, release))
|
||||
end
|
||||
|
||||
context 'when user is guest' do
|
||||
let(:user) { guest }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#opened_merge_requests_url' do
|
||||
|
|
|
@ -159,13 +159,17 @@ RSpec.describe 'Git HTTP requests' do
|
|||
|
||||
context "POST git-upload-pack" 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
|
||||
|
||||
context "POST git-receive-pack" 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
|
||||
|
|
|
@ -7,6 +7,10 @@ RSpec.describe 'git_http routing' do
|
|||
it_behaves_like 'git repository routes' do
|
||||
let(:path) { '/gitlab-org/gitlab-test.git' }
|
||||
end
|
||||
|
||||
it_behaves_like 'git repository routes with fallback for git-upload-pack' do
|
||||
let(:path) { '/gitlab-org/gitlab-test.git' }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'wiki repositories' do
|
||||
|
@ -14,6 +18,7 @@ RSpec.describe 'git_http routing' do
|
|||
let(:path) { '/gitlab-org/gitlab-test.wiki.git' }
|
||||
|
||||
it_behaves_like 'git repository routes'
|
||||
it_behaves_like 'git repository routes with fallback for git-upload-pack'
|
||||
|
||||
describe 'redirects', type: :request do
|
||||
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
|
||||
let(:path) { '/gitlab-org.wiki.git' }
|
||||
end
|
||||
|
||||
it_behaves_like 'git repository routes with fallback for git-upload-pack' do
|
||||
let(:path) { '/gitlab-org.wiki.git' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'in child group' do
|
||||
it_behaves_like 'git repository routes' do
|
||||
let(:path) { '/gitlab-org/child.wiki.git' }
|
||||
end
|
||||
|
||||
it_behaves_like 'git repository routes with fallback for git-upload-pack' do
|
||||
let(:path) { '/gitlab-org/child.wiki.git' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -51,12 +64,20 @@ RSpec.describe 'git_http routing' do
|
|||
it_behaves_like 'git repository routes' do
|
||||
let(:path) { '/snippets/123.git' }
|
||||
end
|
||||
|
||||
it_behaves_like 'git repository routes without fallback' do
|
||||
let(:path) { '/snippets/123.git' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'project snippet' do
|
||||
it_behaves_like 'git repository routes' do
|
||||
let(:path) { '/gitlab-org/gitlab-test/snippets/123.git' }
|
||||
end
|
||||
|
||||
it_behaves_like 'git repository routes with fallback' do
|
||||
let(:path) { '/gitlab-org/gitlab-test/snippets/123.git' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -876,4 +876,73 @@ RSpec.describe 'project routing' do
|
|||
)
|
||||
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
|
||||
|
|
|
@ -284,6 +284,8 @@ RSpec.configure do |config|
|
|||
current_user_mode.send(:user)&.admin?
|
||||
end
|
||||
end
|
||||
|
||||
allow(Gitlab::CurrentSettings).to receive(:current_application_settings?).and_return(false)
|
||||
end
|
||||
|
||||
config.around(:example, :quarantine) do |example|
|
||||
|
|
|
@ -121,6 +121,12 @@ module StubConfiguration
|
|||
allow(::Gitlab.config.packages).to receive_messages(to_settings(messages))
|
||||
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
|
||||
|
||||
# Modifies stubbed messages to also stub possible predicate versions
|
||||
|
|
|
@ -85,6 +85,13 @@ module StubObjectStorage
|
|||
**params)
|
||||
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)
|
||||
stub_object_storage_uploader(config: Gitlab.config.uploads.object_store,
|
||||
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-receive-pack")).to redirect_to("#{container_path}.git/info/refs?service=git-receive-pack")
|
||||
end
|
||||
|
||||
it 'does not redirect other requests' do
|
||||
expect(post("#{container_path}/git-upload-pack")).not_to be_routable
|
||||
end
|
||||
end
|
||||
|
||||
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(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)
|
||||
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/#{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