Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c47ade2adb
commit
3462d7613f
|
@ -1 +1 @@
|
|||
d12fb69a841d91d843f392a124865f6d47d3bc22
|
||||
747d61c17a51f361cc883c9d4700e174219088a5
|
||||
|
|
|
@ -93,12 +93,12 @@ export default {
|
|||
return {
|
||||
name,
|
||||
list,
|
||||
title: this.getAwardListTitle(list),
|
||||
title: this.getAwardListTitle(list, name),
|
||||
classes: this.getAwardClassBindings(list),
|
||||
html: glEmojiTag(name),
|
||||
};
|
||||
},
|
||||
getAwardListTitle(awardsList) {
|
||||
getAwardListTitle(awardsList, name) {
|
||||
if (!awardsList.length) {
|
||||
return '';
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ export default {
|
|||
// We have 10+ awarded user, join them with comma and add `and x more`.
|
||||
if (remainingAwardList.length) {
|
||||
title = sprintf(
|
||||
__(`%{listToShow}, and %{awardsListLength} more.`),
|
||||
__(`%{listToShow}, and %{awardsListLength} more`),
|
||||
{
|
||||
listToShow: namesToShow.join(', '),
|
||||
awardsListLength: remainingAwardList.length,
|
||||
|
@ -146,7 +146,7 @@ export default {
|
|||
title = namesToShow.join(__(' and '));
|
||||
}
|
||||
|
||||
return title;
|
||||
return title + sprintf(__(' reacted with :%{name}:'), { name });
|
||||
},
|
||||
handleAward(awardName) {
|
||||
if (!this.canAwardEmoji) {
|
||||
|
|
|
@ -16,6 +16,7 @@ module Environments
|
|||
environments = project.environments
|
||||
environments = by_name(environments)
|
||||
environments = by_search(environments)
|
||||
environments = by_ids(environments)
|
||||
|
||||
# Raises InvalidStatesError if params[:states] contains invalid states.
|
||||
by_states(environments)
|
||||
|
@ -47,6 +48,14 @@ module Environments
|
|||
end
|
||||
end
|
||||
|
||||
def by_ids(environments)
|
||||
if params[:environment_ids].present?
|
||||
environments.for_id(params[:environment_ids])
|
||||
else
|
||||
environments
|
||||
end
|
||||
end
|
||||
|
||||
def environments_with_states(environments)
|
||||
# Convert to array of strings
|
||||
states = Array(params[:states]).map(&:to_s)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: one_megabyte_file_size_limit
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65167
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334916
|
||||
milestone: '14.1'
|
||||
type: development
|
||||
group: group::code review
|
||||
default_enabled: false
|
|
@ -49,7 +49,7 @@ if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
|
|||
Gitlab::Metrics::Samplers::PumaSampler.instance.start
|
||||
end
|
||||
|
||||
Gitlab::Metrics.gauge(:deployments, 'GitLab Version', {}, :max).set({ version: Gitlab::VERSION }, 1)
|
||||
Gitlab::Metrics.gauge(:deployments, 'GitLab Version', {}, :max).set({ version: Gitlab::VERSION, revision: Gitlab.revision }, 1)
|
||||
|
||||
unless Gitlab::Runtime.sidekiq?
|
||||
Gitlab::Metrics::RequestsRackMiddleware.initialize_metrics
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
1b74312f59f6f8937cd0dd754d22dc72e9bdc7302e6254a2fda5762afebe303c
|
|
@ -1,35 +1,11 @@
|
|||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = heap;
|
||||
|
||||
--
|
||||
-- Name: ar_internal_metadata; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.ar_internal_metadata (
|
||||
CREATE TABLE ar_internal_metadata (
|
||||
key character varying NOT NULL,
|
||||
value character varying,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ci_instance_variables; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.ci_instance_variables (
|
||||
CREATE TABLE ci_instance_variables (
|
||||
id bigint NOT NULL,
|
||||
variable_type smallint DEFAULT 1 NOT NULL,
|
||||
masked boolean DEFAULT false,
|
||||
|
@ -42,80 +18,28 @@ CREATE TABLE public.ci_instance_variables (
|
|||
CONSTRAINT check_956afd70f1 CHECK ((char_length(encrypted_value) <= 13579))
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ci_instance_variables_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.ci_instance_variables_id_seq
|
||||
CREATE SEQUENCE ci_instance_variables_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE ci_instance_variables_id_seq OWNED BY ci_instance_variables.id;
|
||||
|
||||
--
|
||||
-- Name: ci_instance_variables_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.ci_instance_variables_id_seq OWNED BY public.ci_instance_variables.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.schema_migrations (
|
||||
CREATE TABLE schema_migrations (
|
||||
version character varying NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE ONLY ci_instance_variables ALTER COLUMN id SET DEFAULT nextval('ci_instance_variables_id_seq'::regclass);
|
||||
|
||||
--
|
||||
-- Name: ci_instance_variables id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.ci_instance_variables ALTER COLUMN id SET DEFAULT nextval('public.ci_instance_variables_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ar_internal_metadata ar_internal_metadata_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.ar_internal_metadata
|
||||
ALTER TABLE ONLY ar_internal_metadata
|
||||
ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ci_instance_variables ci_instance_variables_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.ci_instance_variables
|
||||
ALTER TABLE ONLY ci_instance_variables
|
||||
ADD CONSTRAINT ci_instance_variables_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.schema_migrations
|
||||
ALTER TABLE ONLY schema_migrations
|
||||
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_ci_instance_variables_on_key; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX index_ci_instance_variables_on_key ON public.ci_instance_variables USING btree (key);
|
||||
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
SET search_path TO "$user", public;
|
||||
|
||||
INSERT INTO "schema_migrations" (version) VALUES
|
||||
('20210617101848');
|
||||
|
||||
|
||||
CREATE UNIQUE INDEX index_ci_instance_variables_on_key ON ci_instance_variables USING btree (key);
|
||||
|
|
|
@ -9,6 +9,8 @@ class FinalizePushEventPayloadsBigintConversion < ActiveRecord::Migration[6.1]
|
|||
INDEX_NAME = 'index_push_event_payloads_on_event_id_convert_to_bigint'
|
||||
|
||||
def up
|
||||
return unless should_run?
|
||||
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
|
||||
table_name: TABLE_NAME,
|
||||
|
@ -20,11 +22,17 @@ class FinalizePushEventPayloadsBigintConversion < ActiveRecord::Migration[6.1]
|
|||
end
|
||||
|
||||
def down
|
||||
return unless should_run?
|
||||
|
||||
swap_columns
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def should_run?
|
||||
Gitlab.dev_or_test_env? || Gitlab.com?
|
||||
end
|
||||
|
||||
def swap_columns
|
||||
add_concurrent_index TABLE_NAME, :event_id_convert_to_bigint, unique: true, name: INDEX_NAME
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require Rails.root.join('db', 'post_migrate', '20210622041846_finalize_push_event_payloads_bigint_conversion')
|
||||
|
||||
class MigratePushEventPayloadsEventIdBackToIntegerForGitlabCom < ActiveRecord::Migration[6.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
FinalizePushEventPayloadsBigintConversion.new.down
|
||||
end
|
||||
|
||||
def down
|
||||
FinalizePushEventPayloadsBigintConversion.new.up
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
71ad8c8f2419721f8fdf6c6bbd1265c4a7ca277972c59319e155bc6dfc46aa48
|
|
@ -17376,7 +17376,7 @@ ALTER SEQUENCE protected_tags_id_seq OWNED BY protected_tags.id;
|
|||
|
||||
CREATE TABLE push_event_payloads (
|
||||
commit_count bigint NOT NULL,
|
||||
event_id_convert_to_bigint integer DEFAULT 0 NOT NULL,
|
||||
event_id integer NOT NULL,
|
||||
action smallint NOT NULL,
|
||||
ref_type smallint NOT NULL,
|
||||
commit_from bytea,
|
||||
|
@ -17384,7 +17384,7 @@ CREATE TABLE push_event_payloads (
|
|||
ref text,
|
||||
commit_title character varying(70),
|
||||
ref_count integer,
|
||||
event_id bigint NOT NULL
|
||||
event_id_convert_to_bigint bigint DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE push_rules (
|
||||
|
@ -27974,6 +27974,4 @@ ALTER TABLE ONLY user_follow_users
|
|||
ADD CONSTRAINT user_follow_users_followee_id_fkey FOREIGN KEY (followee_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY user_follow_users
|
||||
ADD CONSTRAINT user_follow_users_follower_id_fkey FOREIGN KEY (follower_id) REFERENCES users(id) ON DELETE CASCADE;-- schema_migrations.version information is no longer stored in this file,
|
||||
-- but instead tracked in the db/schema_migrations directory
|
||||
-- see https://gitlab.com/gitlab-org/gitlab/-/issues/218590 for details
|
||||
ADD CONSTRAINT user_follow_users_follower_id_fkey FOREIGN KEY (follower_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
|
|
@ -10949,6 +10949,7 @@ Represents the network policy.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="networkpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. |
|
||||
| <a id="networkpolicyenvironments"></a>`environments` | [`EnvironmentConnection`](#environmentconnection) | Environments where this policy is applied. (see [Connections](#connections)) |
|
||||
| <a id="networkpolicyfromautodevops"></a>`fromAutoDevops` | [`Boolean!`](#boolean) | Indicates whether this policy is created from AutoDevops. |
|
||||
| <a id="networkpolicyname"></a>`name` | [`String!`](#string) | Name of the policy. |
|
||||
| <a id="networkpolicynamespace"></a>`namespace` | [`String!`](#string) | Namespace of the policy. |
|
||||
|
|
|
@ -7,10 +7,7 @@ module Gitlab
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
def dump_schema_information # :nodoc:
|
||||
return super unless ActiveRecord::Base.configurations.primary?(pool.db_config.name)
|
||||
|
||||
versions = schema_migration.all_versions
|
||||
Gitlab::Database::SchemaVersionFiles.touch_all(versions) if versions.any?
|
||||
Gitlab::Database::SchemaMigrations.touch_all(self)
|
||||
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -7,13 +7,9 @@ module Gitlab
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
def structure_load(...)
|
||||
result = super(...)
|
||||
super(...)
|
||||
|
||||
if ActiveRecord::Base.configurations.primary?(connection.pool.db_config.name)
|
||||
Gitlab::Database::SchemaVersionFiles.load_all
|
||||
else
|
||||
result
|
||||
end
|
||||
Gitlab::Database::SchemaMigrations.load_all(connection)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,11 +30,7 @@ module Gitlab
|
|||
structure.gsub!(/\n{3,}/, "\n\n")
|
||||
|
||||
io << structure.strip
|
||||
io << <<~MSG
|
||||
-- schema_migrations.version information is no longer stored in this file,
|
||||
-- but instead tracked in the db/schema_migrations directory
|
||||
-- see https://gitlab.com/gitlab-org/gitlab/-/issues/218590 for details
|
||||
MSG
|
||||
io << "\n"
|
||||
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Database
|
||||
module SchemaMigrations
|
||||
def self.touch_all(connection)
|
||||
context = Gitlab::Database::SchemaMigrations::Context.new(connection)
|
||||
|
||||
Gitlab::Database::SchemaMigrations::Migrations.new(context).touch_all
|
||||
end
|
||||
|
||||
def self.load_all(connection)
|
||||
context = Gitlab::Database::SchemaMigrations::Context.new(connection)
|
||||
|
||||
Gitlab::Database::SchemaMigrations::Migrations.new(context).load_all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Database
|
||||
module SchemaMigrations
|
||||
class Context
|
||||
attr_reader :connection
|
||||
|
||||
def initialize(connection)
|
||||
@connection = connection
|
||||
end
|
||||
|
||||
def schema_directory
|
||||
@schema_directory ||=
|
||||
if ActiveRecord::Base.configurations.primary?(database_name)
|
||||
File.join(db_dir, 'schema_migrations')
|
||||
else
|
||||
File.join(db_dir, "#{database_name}_schema_migrations")
|
||||
end
|
||||
end
|
||||
|
||||
def versions_to_create
|
||||
versions_from_database = @connection.schema_migration.all_versions
|
||||
versions_from_migration_files = @connection.migration_context.migrations.map { |m| m.version.to_s }
|
||||
|
||||
versions_from_database & versions_from_migration_files
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def database_name
|
||||
@database_name ||= @connection.pool.db_config.name
|
||||
end
|
||||
|
||||
def db_dir
|
||||
@db_dir ||= Rails.application.config.paths["db"].first
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Database
|
||||
module SchemaMigrations
|
||||
class Migrations
|
||||
MIGRATION_VERSION_GLOB = '20[0-9][0-9]*'
|
||||
|
||||
def initialize(context)
|
||||
@context = context
|
||||
end
|
||||
|
||||
def touch_all
|
||||
return unless @context.versions_to_create.any?
|
||||
|
||||
version_filepaths = version_filenames.map { |f| File.join(schema_directory, f) }
|
||||
FileUtils.rm(version_filepaths)
|
||||
|
||||
@context.versions_to_create.each do |version|
|
||||
version_filepath = File.join(schema_directory, version)
|
||||
|
||||
File.open(version_filepath, 'w') do |file|
|
||||
file << Digest::SHA256.hexdigest(version)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_all
|
||||
return if version_filenames.empty?
|
||||
|
||||
values = version_filenames.map { |vf| "('#{@context.connection.quote_string(vf)}')" }
|
||||
|
||||
@context.connection.execute(<<~SQL)
|
||||
INSERT INTO schema_migrations (version)
|
||||
VALUES #{values.join(',')}
|
||||
ON CONFLICT DO NOTHING
|
||||
SQL
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def schema_directory
|
||||
@context.schema_directory
|
||||
end
|
||||
|
||||
def version_filenames
|
||||
@version_filenames ||= Dir.glob(MIGRATION_VERSION_GLOB, base: schema_directory)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,64 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Database
|
||||
class SchemaVersionFiles
|
||||
SCHEMA_DIRECTORY = 'db/schema_migrations'
|
||||
MIGRATION_DIRECTORIES = %w[db/migrate db/post_migrate].freeze
|
||||
MIGRATION_VERSION_GLOB = '20[0-9][0-9]*'
|
||||
|
||||
def self.touch_all(versions_from_database)
|
||||
versions_from_migration_files = find_versions_from_migration_files
|
||||
|
||||
version_filepaths = find_version_filenames.map { |f| schema_directory.join(f) }
|
||||
FileUtils.rm(version_filepaths)
|
||||
|
||||
versions_to_create = versions_from_database & versions_from_migration_files
|
||||
versions_to_create.each do |version|
|
||||
version_filepath = schema_directory.join(version)
|
||||
|
||||
File.open(version_filepath, 'w') do |file|
|
||||
file << Digest::SHA256.hexdigest(version)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.load_all
|
||||
version_filenames = find_version_filenames
|
||||
return if version_filenames.empty?
|
||||
|
||||
values = version_filenames.map { |vf| "('#{connection.quote_string(vf)}')" }
|
||||
connection.execute(<<~SQL)
|
||||
INSERT INTO schema_migrations (version)
|
||||
VALUES #{values.join(',')}
|
||||
ON CONFLICT DO NOTHING
|
||||
SQL
|
||||
end
|
||||
|
||||
def self.schema_directory
|
||||
@schema_directory ||= Rails.root.join(SCHEMA_DIRECTORY)
|
||||
end
|
||||
|
||||
def self.migration_directories
|
||||
@migration_directories ||= MIGRATION_DIRECTORIES.map { |dir| Rails.root.join(dir) }
|
||||
end
|
||||
|
||||
def self.find_version_filenames
|
||||
Dir.glob(MIGRATION_VERSION_GLOB, base: schema_directory)
|
||||
end
|
||||
|
||||
def self.find_versions_from_migration_files
|
||||
migration_directories.each_with_object([]) do |directory, migration_versions|
|
||||
directory_migrations = Dir.glob(MIGRATION_VERSION_GLOB, base: directory)
|
||||
directory_versions = directory_migrations.map! { |m| m.split('_').first }
|
||||
|
||||
migration_versions.concat(directory_versions)
|
||||
end
|
||||
end
|
||||
|
||||
def self.connection
|
||||
ActiveRecord::Base.connection
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,11 +11,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def self.too_large?(size)
|
||||
file_size_limit = Gitlab.config.extra['maximum_text_highlight_size_kilobytes']
|
||||
return false unless size.to_i > self.file_size_limit
|
||||
|
||||
return false unless size.to_i > file_size_limit
|
||||
|
||||
over_highlight_size_limit.increment(source: "file size: #{file_size_limit}") if Feature.enabled?(:track_file_size_over_highlight_limit)
|
||||
over_highlight_size_limit.increment(source: "file size: #{self.file_size_limit}") if Feature.enabled?(:track_file_size_over_highlight_limit)
|
||||
|
||||
true
|
||||
end
|
||||
|
@ -51,6 +49,16 @@ module Gitlab
|
|||
|
||||
attr_reader :context
|
||||
|
||||
def self.file_size_limit
|
||||
if Feature.enabled?(:one_megabyte_file_size_limit)
|
||||
1024.kilobytes
|
||||
else
|
||||
Gitlab.config.extra['maximum_text_highlight_size_kilobytes']
|
||||
end
|
||||
end
|
||||
|
||||
private_class_method :file_size_limit
|
||||
|
||||
def custom_language
|
||||
return unless @language
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ module Gitlab
|
|||
# We are modeling existing kubernetes resource and don't have
|
||||
# control over amount of parameters.
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(name:, namespace:, selector:, ingress:, resource_version: nil, description: nil, labels: nil, creation_timestamp: nil, egress: nil, annotations: nil)
|
||||
def initialize(name:, namespace:, selector:, ingress:, resource_version: nil, description: nil, labels: nil, creation_timestamp: nil, egress: nil, annotations: nil, environment_ids: [])
|
||||
@name = name
|
||||
@description = description
|
||||
@namespace = namespace
|
||||
|
@ -23,6 +23,7 @@ module Gitlab
|
|||
@ingress = ingress
|
||||
@egress = egress
|
||||
@annotations = annotations
|
||||
@environment_ids = environment_ids
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
|
@ -49,7 +50,7 @@ module Gitlab
|
|||
nil
|
||||
end
|
||||
|
||||
def self.from_resource(resource)
|
||||
def self.from_resource(resource, environment_ids = [])
|
||||
return unless resource
|
||||
return if !resource[:metadata] || !resource[:spec]
|
||||
|
||||
|
@ -65,7 +66,8 @@ module Gitlab
|
|||
creation_timestamp: metadata[:creationTimestamp],
|
||||
selector: spec[:endpointSelector],
|
||||
ingress: spec[:ingress],
|
||||
egress: spec[:egress]
|
||||
egress: spec[:egress],
|
||||
environment_ids: environment_ids
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -83,7 +85,7 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
attr_reader :name, :description, :namespace, :labels, :creation_timestamp, :resource_version, :ingress, :egress, :annotations
|
||||
attr_reader :name, :description, :namespace, :labels, :creation_timestamp, :resource_version, :ingress, :egress, :annotations, :environment_ids
|
||||
|
||||
def selector
|
||||
@selector ||= {}
|
||||
|
|
|
@ -8,7 +8,8 @@ module Gitlab
|
|||
|
||||
KIND = 'NetworkPolicy'
|
||||
|
||||
def initialize(name:, namespace:, selector:, ingress:, labels: nil, creation_timestamp: nil, policy_types: ["Ingress"], egress: nil)
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(name:, namespace:, selector:, ingress:, labels: nil, creation_timestamp: nil, policy_types: ["Ingress"], egress: nil, environment_ids: [])
|
||||
@name = name
|
||||
@namespace = namespace
|
||||
@labels = labels
|
||||
|
@ -17,7 +18,9 @@ module Gitlab
|
|||
@policy_types = policy_types
|
||||
@ingress = ingress
|
||||
@egress = egress
|
||||
@environment_ids = environment_ids
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
def self.from_yaml(manifest)
|
||||
return unless manifest
|
||||
|
@ -40,7 +43,7 @@ module Gitlab
|
|||
nil
|
||||
end
|
||||
|
||||
def self.from_resource(resource)
|
||||
def self.from_resource(resource, environment_ids = [])
|
||||
return unless resource
|
||||
return if !resource[:metadata] || !resource[:spec]
|
||||
|
||||
|
@ -54,7 +57,8 @@ module Gitlab
|
|||
selector: spec[:podSelector],
|
||||
policy_types: spec[:policyTypes],
|
||||
ingress: spec[:ingress],
|
||||
egress: spec[:egress]
|
||||
egress: spec[:egress],
|
||||
environment_ids: environment_ids
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -69,7 +73,7 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
attr_reader :name, :namespace, :labels, :creation_timestamp, :policy_types, :ingress, :egress
|
||||
attr_reader :name, :namespace, :labels, :creation_timestamp, :policy_types, :ingress, :egress, :environment_ids
|
||||
|
||||
def selector
|
||||
@selector ||= {}
|
||||
|
|
|
@ -16,7 +16,8 @@ module Gitlab
|
|||
creation_timestamp: creation_timestamp,
|
||||
manifest: manifest,
|
||||
is_autodevops: autodevops?,
|
||||
is_enabled: enabled?
|
||||
is_enabled: enabled?,
|
||||
environment_ids: environment_ids
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -177,7 +177,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def repository_route_regex
|
||||
@repository_route_regex ||= /#{full_namespace_route_regex}|#{personal_snippet_repository_path_regex}/.freeze
|
||||
@repository_route_regex ||= /(#{full_namespace_route_regex}|#{personal_snippet_repository_path_regex})\.*/.freeze
|
||||
end
|
||||
|
||||
def repository_git_route_regex
|
||||
|
@ -185,7 +185,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def repository_wiki_git_route_regex
|
||||
@repository_wiki_git_route_regex ||= /#{full_namespace_route_regex}\.wiki\.git/.freeze
|
||||
@repository_wiki_git_route_regex ||= /#{full_namespace_route_regex}\.*\.wiki\.git/.freeze
|
||||
end
|
||||
|
||||
def full_namespace_path_regex
|
||||
|
|
|
@ -90,11 +90,14 @@ namespace :gitlab do
|
|||
|
||||
desc 'This adjusts and cleans db/structure.sql - it runs after db:structure:dump'
|
||||
task :clean_structure_sql do |task_name|
|
||||
structure_file = 'db/structure.sql'
|
||||
schema = File.read(structure_file)
|
||||
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
|
||||
structure_file = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.name)
|
||||
|
||||
File.open(structure_file, 'wb+') do |io|
|
||||
Gitlab::Database::SchemaCleaner.new(schema).clean(io)
|
||||
schema = File.read(structure_file)
|
||||
|
||||
File.open(structure_file, 'wb+') do |io|
|
||||
Gitlab::Database::SchemaCleaner.new(schema).clean(io)
|
||||
end
|
||||
end
|
||||
|
||||
# Allow this task to be called multiple times, as happens when running db:migrate:redo
|
||||
|
|
|
@ -77,6 +77,9 @@ msgstr ""
|
|||
msgid " or references (e.g. path/to/project!merge_request_id)"
|
||||
msgstr ""
|
||||
|
||||
msgid " reacted with :%{name}:"
|
||||
msgstr ""
|
||||
|
||||
msgid "\"%{path}\" did not exist on \"%{ref}\""
|
||||
msgstr ""
|
||||
|
||||
|
@ -687,7 +690,7 @@ msgstr ""
|
|||
msgid "%{link_start}Start the title with %{draft_snippet}%{link_end} to prevent a merge request that is a work in progress from being merged before it's ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{listToShow}, and %{awardsListLength} more."
|
||||
msgid "%{listToShow}, and %{awardsListLength} more"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{location} is missing required keys: %{keys}"
|
||||
|
|
|
@ -63,7 +63,7 @@ RSpec.describe 'User interacts with awards' do
|
|||
page.within('.awards') do
|
||||
expect(page).to have_selector('[data-testid="award-button"]')
|
||||
expect(page.find('[data-testid="award-button"].selected .js-counter')).to have_content('1')
|
||||
expect(page).to have_css('[data-testid="award-button"].selected[title="You"]')
|
||||
expect(page).to have_css('[data-testid="award-button"].selected[title="You reacted with :8ball:"]')
|
||||
|
||||
expect do
|
||||
page.find('[data-testid="award-button"].selected').click
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Environments::EnvironmentsFinder do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { project.creator }
|
||||
let(:environment) { create(:environment, :available, project: project) }
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:user) { project.creator }
|
||||
let_it_be(:environment) { create(:environment, :available, project: project) }
|
||||
let_it_be(:environment_stopped) { create(:environment, :stopped, name: 'test2', project: project) }
|
||||
let_it_be(:environment_available) { create(:environment, :available, name: 'test3', project: project) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
@ -13,18 +15,18 @@ RSpec.describe Environments::EnvironmentsFinder do
|
|||
|
||||
describe '#execute' do
|
||||
context 'with states parameter' do
|
||||
let(:stopped_environment) { create(:environment, :stopped, project: project) }
|
||||
let_it_be(:stopped_environment) { create(:environment, :stopped, project: project) }
|
||||
|
||||
it 'returns environments with the requested state' do
|
||||
result = described_class.new(project, user, states: 'available').execute
|
||||
|
||||
expect(result).to contain_exactly(environment)
|
||||
expect(result).to contain_exactly(environment, environment_available)
|
||||
end
|
||||
|
||||
it 'returns environments with any of the requested states' do
|
||||
result = described_class.new(project, user, states: %w(available stopped)).execute
|
||||
|
||||
expect(result).to contain_exactly(environment, stopped_environment)
|
||||
expect(result).to contain_exactly(environment, environment_stopped, environment_available, stopped_environment)
|
||||
end
|
||||
|
||||
it 'raises exception when requested state is invalid' do
|
||||
|
@ -37,25 +39,30 @@ RSpec.describe Environments::EnvironmentsFinder do
|
|||
it 'returns environments with the requested state' do
|
||||
result = described_class.new(project, user, states: :available).execute
|
||||
|
||||
expect(result).to contain_exactly(environment)
|
||||
expect(result).to contain_exactly(environment, environment_available)
|
||||
end
|
||||
|
||||
it 'returns environments with any of the requested states' do
|
||||
result = described_class.new(project, user, states: [:available, :stopped]).execute
|
||||
|
||||
expect(result).to contain_exactly(environment, stopped_environment)
|
||||
expect(result).to contain_exactly(environment, environment_stopped, environment_available, stopped_environment)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with search and states' do
|
||||
let(:environment2) { create(:environment, :stopped, name: 'test2', project: project) }
|
||||
let(:environment3) { create(:environment, :available, name: 'test3', project: project) }
|
||||
|
||||
it 'searches environments by name and state' do
|
||||
result = described_class.new(project, user, search: 'test', states: :available).execute
|
||||
|
||||
expect(result).to contain_exactly(environment3)
|
||||
expect(result).to contain_exactly(environment_available)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with id' do
|
||||
it 'searches environments by name and state' do
|
||||
result = described_class.new(project, user, search: 'test', environment_ids: [environment_available.id]).execute
|
||||
|
||||
expect(result).to contain_exactly(environment_available)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,6 +23,4 @@ ALTER TABLE ONLY abuse_reports ALTER COLUMN id SET DEFAULT nextval('abuse_report
|
|||
ALTER TABLE ONLY abuse_reports
|
||||
ADD CONSTRAINT abuse_reports_pkey PRIMARY KEY (id);
|
||||
|
||||
CREATE INDEX index_abuse_reports_on_user_id ON abuse_reports USING btree (user_id);-- schema_migrations.version information is no longer stored in this file,
|
||||
-- but instead tracked in the db/schema_migrations directory
|
||||
-- see https://gitlab.com/gitlab-org/gitlab/-/issues/218590 for details
|
||||
CREATE INDEX index_abuse_reports_on_user_id ON abuse_reports USING btree (user_id);
|
||||
|
|
|
@ -7,7 +7,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
<button
|
||||
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button"
|
||||
data-testid="award-button"
|
||||
title="Ada, Leonardo, and Marie"
|
||||
title="Ada, Leonardo, and Marie reacted with :thumbsup:"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
@ -37,7 +37,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
<button
|
||||
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
|
||||
data-testid="award-button"
|
||||
title="You, Ada, and Marie"
|
||||
title="You, Ada, and Marie reacted with :thumbsdown:"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
@ -67,7 +67,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
<button
|
||||
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button"
|
||||
data-testid="award-button"
|
||||
title="Ada and Jane"
|
||||
title="Ada and Jane reacted with :smile:"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
@ -97,7 +97,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
<button
|
||||
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
|
||||
data-testid="award-button"
|
||||
title="You, Ada, Jane, and Leonardo"
|
||||
title="You, Ada, Jane, and Leonardo reacted with :ok_hand:"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
@ -127,7 +127,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
<button
|
||||
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
|
||||
data-testid="award-button"
|
||||
title="You"
|
||||
title="You reacted with :cactus:"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
@ -157,7 +157,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
<button
|
||||
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button"
|
||||
data-testid="award-button"
|
||||
title="Marie"
|
||||
title="Marie reacted with :a:"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
@ -187,7 +187,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = `
|
|||
<button
|
||||
class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected"
|
||||
data-testid="award-button"
|
||||
title="You"
|
||||
title="You reacted with :b:"
|
||||
type="button"
|
||||
>
|
||||
<!---->
|
||||
|
|
|
@ -98,43 +98,43 @@ describe('vue_shared/components/awards_list', () => {
|
|||
classes: REACTION_CONTROL_CLASSES,
|
||||
count: 3,
|
||||
html: matchingEmojiTag(EMOJI_THUMBSUP),
|
||||
title: 'Ada, Leonardo, and Marie',
|
||||
title: `Ada, Leonardo, and Marie reacted with :${EMOJI_THUMBSUP}:`,
|
||||
},
|
||||
{
|
||||
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
|
||||
count: 3,
|
||||
html: matchingEmojiTag(EMOJI_THUMBSDOWN),
|
||||
title: 'You, Ada, and Marie',
|
||||
title: `You, Ada, and Marie reacted with :${EMOJI_THUMBSDOWN}:`,
|
||||
},
|
||||
{
|
||||
classes: REACTION_CONTROL_CLASSES,
|
||||
count: 2,
|
||||
html: matchingEmojiTag(EMOJI_SMILE),
|
||||
title: 'Ada and Jane',
|
||||
title: `Ada and Jane reacted with :${EMOJI_SMILE}:`,
|
||||
},
|
||||
{
|
||||
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
|
||||
count: 4,
|
||||
html: matchingEmojiTag(EMOJI_OK),
|
||||
title: 'You, Ada, Jane, and Leonardo',
|
||||
title: `You, Ada, Jane, and Leonardo reacted with :${EMOJI_OK}:`,
|
||||
},
|
||||
{
|
||||
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
|
||||
count: 1,
|
||||
html: matchingEmojiTag(EMOJI_CACTUS),
|
||||
title: 'You',
|
||||
title: `You reacted with :${EMOJI_CACTUS}:`,
|
||||
},
|
||||
{
|
||||
classes: REACTION_CONTROL_CLASSES,
|
||||
count: 1,
|
||||
html: matchingEmojiTag(EMOJI_A),
|
||||
title: 'Marie',
|
||||
title: `Marie reacted with :${EMOJI_A}:`,
|
||||
},
|
||||
{
|
||||
classes: [...REACTION_CONTROL_CLASSES, 'selected'],
|
||||
count: 1,
|
||||
html: matchingEmojiTag(EMOJI_B),
|
||||
title: 'You',
|
||||
title: `You reacted with :${EMOJI_B}:`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -246,13 +246,13 @@ describe('vue_shared/components/awards_list', () => {
|
|||
classes: REACTION_CONTROL_CLASSES,
|
||||
count: 1,
|
||||
html: matchingEmojiTag(EMOJI_100),
|
||||
title: 'Marie',
|
||||
title: `Marie reacted with :${EMOJI_100}:`,
|
||||
},
|
||||
{
|
||||
classes: REACTION_CONTROL_CLASSES,
|
||||
count: 1,
|
||||
html: matchingEmojiTag(EMOJI_SMILE),
|
||||
title: 'Marie',
|
||||
title: `Marie reacted with :${EMOJI_SMILE}:`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::PostgresqlAdapter::DumpSchemaVersionsMixin do
|
||||
let(:schema_migration) { double('schema_migration', all_versions: versions) }
|
||||
let(:db_name) { 'primary' }
|
||||
let(:versions) { %w(5 2 1000 200 4 93 2) }
|
||||
|
||||
let(:instance_class) do
|
||||
klass = Class.new do
|
||||
def dump_schema_information
|
||||
|
@ -24,43 +20,10 @@ RSpec.describe Gitlab::Database::PostgresqlAdapter::DumpSchemaVersionsMixin do
|
|||
|
||||
let(:instance) { instance_class.new }
|
||||
|
||||
before do
|
||||
allow(instance).to receive(:schema_migration).and_return(schema_migration)
|
||||
it 'calls SchemaMigrations touch_all and skips original implementation' do
|
||||
expect(Gitlab::Database::SchemaMigrations).to receive(:touch_all).with(instance)
|
||||
expect(instance).not_to receive(:original_dump_schema_information)
|
||||
|
||||
# pool is from ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
||||
allow(instance).to receive_message_chain(:pool, :db_config, :name).and_return(db_name)
|
||||
end
|
||||
|
||||
context 'when database name is primary' do
|
||||
context 'when version files exist' do
|
||||
it 'touches version files' do
|
||||
expect(Gitlab::Database::SchemaVersionFiles).to receive(:touch_all).with(versions)
|
||||
expect(instance).not_to receive(:original_dump_schema_information)
|
||||
|
||||
instance.dump_schema_information
|
||||
end
|
||||
end
|
||||
|
||||
context 'when version files do not exist' do
|
||||
let(:versions) { [] }
|
||||
|
||||
it 'does not touch version files' do
|
||||
expect(Gitlab::Database::SchemaVersionFiles).not_to receive(:touch_all)
|
||||
expect(instance).not_to receive(:original_dump_schema_information)
|
||||
|
||||
instance.dump_schema_information
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when database name is ci' do
|
||||
let(:db_name) { 'ci' }
|
||||
|
||||
it 'does not touch version files' do
|
||||
expect(Gitlab::Database::SchemaVersionFiles).not_to receive(:touch_all)
|
||||
expect(instance).to receive(:original_dump_schema_information)
|
||||
|
||||
instance.dump_schema_information
|
||||
end
|
||||
instance.dump_schema_information
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::PostgresqlDatabaseTasks::LoadSchemaVersionsMixin do
|
||||
let(:db_name) { 'primary' }
|
||||
|
||||
let(:instance_class) do
|
||||
klass = Class.new do
|
||||
def structure_load
|
||||
|
@ -22,28 +20,13 @@ RSpec.describe Gitlab::Database::PostgresqlDatabaseTasks::LoadSchemaVersionsMixi
|
|||
|
||||
let(:instance) { instance_class.new }
|
||||
|
||||
before do
|
||||
# connection is available in ActiveRecord::Tasks::PostgreSQLDatabaseTasks
|
||||
allow(instance).to receive_message_chain(:connection, :pool, :db_config, :name).and_return(db_name)
|
||||
end
|
||||
it 'calls SchemaMigrations load_all' do
|
||||
connection = double('connection')
|
||||
allow(instance).to receive(:connection).and_return(connection)
|
||||
|
||||
context 'when database is primary' do
|
||||
it 'loads version files' do
|
||||
expect(Gitlab::Database::SchemaVersionFiles).to receive(:load_all)
|
||||
expect(instance).to receive(:original_structure_load)
|
||||
expect(instance).to receive(:original_structure_load).ordered
|
||||
expect(Gitlab::Database::SchemaMigrations).to receive(:load_all).with(connection).ordered
|
||||
|
||||
instance.structure_load
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the database is ci' do
|
||||
let(:db_name) { 'ci' }
|
||||
|
||||
it 'does not load version files' do
|
||||
expect(Gitlab::Database::SchemaVersionFiles).not_to receive(:load_all)
|
||||
expect(instance).to receive(:original_structure_load)
|
||||
|
||||
instance.structure_load
|
||||
end
|
||||
instance.structure_load
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::SchemaMigrations::Context do
|
||||
let(:connection) { ActiveRecord::Base.connection }
|
||||
|
||||
let(:context) { described_class.new(connection) }
|
||||
|
||||
describe '#schema_directory' do
|
||||
it 'returns db/schema_migrations' do
|
||||
expect(context.schema_directory).to eq(File.join(Rails.root, 'db/schema_migrations'))
|
||||
end
|
||||
|
||||
context 'multiple databases' do
|
||||
let(:connection) { Ci::BaseModel.connection }
|
||||
|
||||
it 'returns a directory path that is database specific' do
|
||||
skip_if_multiple_databases_not_setup
|
||||
|
||||
expect(context.schema_directory).to eq(File.join(Rails.root, 'db/ci_schema_migrations'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#versions_to_create' do
|
||||
before do
|
||||
allow(connection).to receive_message_chain(:schema_migration, :all_versions).and_return(migrated_versions)
|
||||
|
||||
migrations_struct = Struct.new(:version)
|
||||
migrations = file_versions.map { |version| migrations_struct.new(version) }
|
||||
allow(connection).to receive_message_chain(:migration_context, :migrations).and_return(migrations)
|
||||
end
|
||||
|
||||
let(:version1) { '20200123' }
|
||||
let(:version2) { '20200410' }
|
||||
let(:version3) { '20200602' }
|
||||
let(:version4) { '20200809' }
|
||||
|
||||
let(:migrated_versions) { file_versions }
|
||||
let(:file_versions) { [version1, version2, version3, version4] }
|
||||
|
||||
context 'migrated versions is the same as migration file versions' do
|
||||
it 'returns migrated versions' do
|
||||
expect(context.versions_to_create).to eq(migrated_versions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'migrated versions is subset of migration file versions' do
|
||||
let(:migrated_versions) { [version1, version2] }
|
||||
|
||||
it 'returns migrated versions' do
|
||||
expect(context.versions_to_create).to eq(migrated_versions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'migrated versions is superset of migration file versions' do
|
||||
let(:migrated_versions) { file_versions + ['20210809'] }
|
||||
|
||||
it 'returns file versions' do
|
||||
expect(context.versions_to_create).to eq(file_versions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'migrated versions has slightly different versions to migration file versions' do
|
||||
let(:migrated_versions) { [version1, version2, version3, version4, '20210101'] }
|
||||
let(:file_versions) { [version1, version2, version3, version4, '20210102'] }
|
||||
|
||||
it 'returns the common set' do
|
||||
expect(context.versions_to_create).to eq([version1, version2, version3, version4])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def skip_if_multiple_databases_not_setup
|
||||
skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
|
||||
end
|
||||
end
|
|
@ -2,43 +2,37 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::SchemaVersionFiles do
|
||||
describe '.touch_all' do
|
||||
RSpec.describe Gitlab::Database::SchemaMigrations::Migrations do
|
||||
let(:connection) { ApplicationRecord.connection }
|
||||
let(:context) { Gitlab::Database::SchemaMigrations::Context.new(connection) }
|
||||
|
||||
let(:migrations) { described_class.new(context) }
|
||||
|
||||
describe '#touch_all' do
|
||||
let(:version1) { '20200123' }
|
||||
let(:version2) { '20200410' }
|
||||
let(:version3) { '20200602' }
|
||||
let(:version4) { '20200809' }
|
||||
|
||||
let(:relative_schema_directory) { 'db/schema_migrations' }
|
||||
let(:relative_migrate_directory) { 'db/migrate' }
|
||||
let(:relative_post_migrate_directory) { 'db/post_migrate' }
|
||||
|
||||
it 'creates a file containing a checksum for each version with a matching migration' do
|
||||
Dir.mktmpdir do |tmpdir|
|
||||
schema_directory = Pathname.new(tmpdir).join(relative_schema_directory)
|
||||
migrate_directory = Pathname.new(tmpdir).join(relative_migrate_directory)
|
||||
post_migrate_directory = Pathname.new(tmpdir).join(relative_post_migrate_directory)
|
||||
|
||||
FileUtils.mkdir_p(migrate_directory)
|
||||
FileUtils.mkdir_p(post_migrate_directory)
|
||||
FileUtils.mkdir_p(schema_directory)
|
||||
|
||||
migration1_filepath = migrate_directory.join("#{version1}_migration.rb")
|
||||
FileUtils.touch(migration1_filepath)
|
||||
|
||||
migration2_filepath = post_migrate_directory.join("#{version2}_post_migration.rb")
|
||||
FileUtils.touch(migration2_filepath)
|
||||
|
||||
old_version_filepath = schema_directory.join('20200101')
|
||||
FileUtils.touch(old_version_filepath)
|
||||
|
||||
expect(File.exist?(old_version_filepath)).to be(true)
|
||||
|
||||
allow(described_class).to receive(:schema_directory).and_return(schema_directory)
|
||||
allow(described_class).to receive(:migration_directories).and_return([migrate_directory, post_migrate_directory])
|
||||
allow(context).to receive(:schema_directory).and_return(schema_directory)
|
||||
allow(context).to receive(:versions_to_create).and_return([version1, version2])
|
||||
|
||||
described_class.touch_all([version1, version2, version3, version4])
|
||||
migrations.touch_all
|
||||
|
||||
expect(File.exist?(old_version_filepath)).to be(false)
|
||||
|
||||
[version1, version2].each do |version|
|
||||
version_filepath = schema_directory.join(version)
|
||||
expect(File.exist?(version_filepath)).to be(true)
|
||||
|
@ -55,12 +49,9 @@ RSpec.describe Gitlab::Database::SchemaVersionFiles do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.load_all' do
|
||||
let(:connection) { double('connection') }
|
||||
|
||||
describe '#load_all' do
|
||||
before do
|
||||
allow(described_class).to receive(:connection).and_return(connection)
|
||||
allow(described_class).to receive(:find_version_filenames).and_return(filenames)
|
||||
allow(migrations).to receive(:version_filenames).and_return(filenames)
|
||||
end
|
||||
|
||||
context 'when there are no version files' do
|
||||
|
@ -70,7 +61,7 @@ RSpec.describe Gitlab::Database::SchemaVersionFiles do
|
|||
expect(connection).not_to receive(:quote_string)
|
||||
expect(connection).not_to receive(:execute)
|
||||
|
||||
described_class.load_all
|
||||
migrations.load_all
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -88,7 +79,7 @@ RSpec.describe Gitlab::Database::SchemaVersionFiles do
|
|||
ON CONFLICT DO NOTHING
|
||||
SQL
|
||||
|
||||
described_class.load_all
|
||||
migrations.load_all
|
||||
end
|
||||
end
|
||||
end
|
|
@ -50,9 +50,16 @@ RSpec.describe Gitlab::Highlight do
|
|||
let(:result) { described_class.highlight(file_name, content) } # content is 44 bytes
|
||||
|
||||
before do
|
||||
stub_feature_flags(one_megabyte_file_size_limit: false)
|
||||
stub_config(extra: { 'maximum_text_highlight_size_kilobytes' => 0.0001 } ) # 1.024 bytes
|
||||
end
|
||||
|
||||
it 'confirm file size is 1MB when `one_megabyte_file_size_limit` is enabled' do
|
||||
stub_feature_flags(one_megabyte_file_size_limit: true)
|
||||
expect(described_class.too_large?(1024.kilobytes)).to eq(false)
|
||||
expect(described_class.too_large?(1025.kilobytes)).to eq(true)
|
||||
end
|
||||
|
||||
it 'increments the metric for oversized files' do
|
||||
expect { result }.to change { over_highlight_size_limit('file size: 0.0001') }.by(1)
|
||||
end
|
||||
|
|
|
@ -206,6 +206,14 @@ RSpec.describe Gitlab::Kubernetes::CiliumNetworkPolicy do
|
|||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'with environment_ids' do
|
||||
subject { Gitlab::Kubernetes::CiliumNetworkPolicy.from_resource(resource, [1, 2, 3]) }
|
||||
|
||||
it 'includes environment_ids in as_json result' do
|
||||
expect(subject.as_json).to include(environment_ids: [1, 2, 3])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#resource' do
|
||||
|
|
|
@ -196,6 +196,14 @@ RSpec.describe Gitlab::Kubernetes::NetworkPolicy do
|
|||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
context 'with environment_ids' do
|
||||
subject { Gitlab::Kubernetes::NetworkPolicy.from_resource(resource, [1, 2, 3]) }
|
||||
|
||||
it 'includes environment_ids in as_json result' do
|
||||
expect(subject.as_json).to include(environment_ids: [1, 2, 3])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#resource' do
|
||||
|
|
|
@ -445,6 +445,8 @@ RSpec.describe Gitlab::PathRegex do
|
|||
[
|
||||
'gitlab-org',
|
||||
'gitlab-org/gitlab-test',
|
||||
'gitlab-org/foo.',
|
||||
'gitlab-org/bar..',
|
||||
'gitlab-org/gitlab-test/snippets/1',
|
||||
'gitlab-org/gitlab-test/snippets/foo', # ambiguous, we allow creating a sub-group called 'snippets'
|
||||
'snippets/1'
|
||||
|
|
|
@ -966,6 +966,722 @@ RSpec.describe 'Git HTTP requests' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the project path ends with a dot" do
|
||||
let(:path) { "#{project.full_path}.git" }
|
||||
|
||||
context "when the project is public" do
|
||||
let(:project) { create(:project, :repository, :public, path: 'foo.') }
|
||||
|
||||
it_behaves_like 'pushes require Basic HTTP Authentication'
|
||||
|
||||
context 'when not authenticated' do
|
||||
let(:env) { {} }
|
||||
|
||||
it_behaves_like 'pulls are allowed'
|
||||
end
|
||||
|
||||
context "when authenticated" do
|
||||
let(:env) { { user: user.username, password: user.password } }
|
||||
|
||||
context 'as a developer on the team' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'pulls are allowed'
|
||||
it_behaves_like 'pushes are allowed'
|
||||
|
||||
context 'but git-receive-pack over HTTP is disabled in config' do
|
||||
before do
|
||||
allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
|
||||
end
|
||||
|
||||
it 'rejects pushes with 403 Forbidden' do
|
||||
upload(path, **env) do |response|
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response.body).to eq(git_access_error(:receive_pack_disabled_over_http))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'but git-upload-pack over HTTP is disabled in config' do
|
||||
it "rejects pushes with 403 Forbidden" do
|
||||
allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
|
||||
|
||||
download(path, **env) do |response|
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response.body).to eq(git_access_error(:upload_pack_disabled_over_http))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'but the service parameter is missing' do
|
||||
it 'rejects clones with 403 Forbidden' do
|
||||
get("/#{path}/info/refs", headers: auth_env(*env.values_at(:user, :password), nil))
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and not a member of the team' do
|
||||
it_behaves_like 'pulls are allowed'
|
||||
|
||||
it 'rejects pushes with 403 Forbidden' do
|
||||
upload(path, **env) do |response|
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response.body).to eq('You are not allowed to push code to this project.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge requests are open that allow maintainer access' do
|
||||
let(:canonical_project) { create(:project, :public, :repository) }
|
||||
let(:project) { fork_project(canonical_project, nil, repository: true) }
|
||||
|
||||
before do
|
||||
canonical_project.add_maintainer(user)
|
||||
create(:merge_request,
|
||||
source_project: project,
|
||||
target_project: canonical_project,
|
||||
source_branch: 'fixes',
|
||||
allow_collaboration: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'pushes are allowed'
|
||||
end
|
||||
|
||||
context 'but the service parameter is missing' do
|
||||
it 'rejects clones with 401 Unauthorized' do
|
||||
get("/#{path}/info/refs")
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the request is not from gitlab-workhorse' do
|
||||
it 'raises an exception' do
|
||||
expect do
|
||||
get("/#{project.full_path}.git/info/refs?service=git-upload-pack")
|
||||
end.to raise_error(JWT::DecodeError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the repo is public' do
|
||||
context 'but the repo is disabled' do
|
||||
let(:project) { create(:project, :public, :repository, :repository_disabled) }
|
||||
let(:path) { "#{project.full_path}.git" }
|
||||
let(:env) { {} }
|
||||
|
||||
it_behaves_like 'pulls require Basic HTTP Authentication'
|
||||
it_behaves_like 'pushes require Basic HTTP Authentication'
|
||||
it_behaves_like 'operations are not allowed with expired password'
|
||||
end
|
||||
|
||||
context 'but the repo is enabled' do
|
||||
let(:project) { create(:project, :public, :repository, :repository_enabled) }
|
||||
let(:path) { "#{project.full_path}.git" }
|
||||
let(:env) { {} }
|
||||
|
||||
it_behaves_like 'pulls are allowed'
|
||||
end
|
||||
|
||||
context 'but only project members are allowed' do
|
||||
let(:project) { create(:project, :public, :repository, :repository_private) }
|
||||
|
||||
it_behaves_like 'pulls require Basic HTTP Authentication'
|
||||
it_behaves_like 'pushes require Basic HTTP Authentication'
|
||||
it_behaves_like 'operations are not allowed with expired password'
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the user requests a redirected path' do
|
||||
let!(:redirect) { project.route.create_redirect('foo/bar') }
|
||||
let(:path) { "#{redirect.path}.git" }
|
||||
|
||||
it 'downloads get status 200 for redirects' do
|
||||
clone_get(path)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the project is private" do
|
||||
let(:project) { create(:project, :repository, :private, path: 'foo.') }
|
||||
|
||||
it_behaves_like 'pulls require Basic HTTP Authentication'
|
||||
it_behaves_like 'pushes require Basic HTTP Authentication'
|
||||
it_behaves_like 'operations are not allowed with expired password'
|
||||
|
||||
context "when username and password are provided" do
|
||||
let(:env) { { user: user.username, password: 'nope' } }
|
||||
|
||||
context "when authentication fails" do
|
||||
context "when the user is IP banned" do
|
||||
before do
|
||||
stub_rack_attack_setting(enabled: true, ip_whitelist: [])
|
||||
end
|
||||
|
||||
it "responds with status 403" do
|
||||
expect(Rack::Attack::Allow2Ban).to receive(:banned?).and_return(true)
|
||||
expect(Gitlab::AuthLogger).to receive(:error).with({
|
||||
message: 'Rack_Attack',
|
||||
env: :blocklist,
|
||||
remote_ip: '127.0.0.1',
|
||||
request_method: 'GET',
|
||||
path: "/#{path}/info/refs?service=git-upload-pack"
|
||||
})
|
||||
|
||||
clone_get(path, **env)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when authentication succeeds" do
|
||||
let(:env) { { user: user.username, password: user.password } }
|
||||
|
||||
context "when the user has access to the project" do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
context "when the user is blocked" do
|
||||
it "rejects pulls with 401 Unauthorized" do
|
||||
user.block
|
||||
project.add_maintainer(user)
|
||||
|
||||
download(path, **env) do |response|
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
it "rejects pulls with 401 Unauthorized for unknown projects (no project existence information leak)" do
|
||||
user.block
|
||||
|
||||
download('doesnt/exist.git', **env) do |response|
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the user isn't blocked" do
|
||||
before do
|
||||
stub_rack_attack_setting(enabled: true, bantime: 1.minute, findtime: 5.minutes, maxretry: 2, ip_whitelist: [])
|
||||
end
|
||||
|
||||
it "resets the IP in Rack Attack on download" do
|
||||
expect(Rack::Attack::Allow2Ban).to receive(:reset).twice
|
||||
|
||||
download(path, **env) do
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
|
||||
end
|
||||
end
|
||||
|
||||
it "resets the IP in Rack Attack on upload" do
|
||||
expect(Rack::Attack::Allow2Ban).to receive(:reset).twice
|
||||
|
||||
upload(path, **env) do
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates the user last activity', :clean_gitlab_redis_shared_state do
|
||||
expect(user.last_activity_on).to be_nil
|
||||
|
||||
download(path, **env) do |response|
|
||||
expect(user.reload.last_activity_on).to eql(Date.today)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when an oauth token is provided" do
|
||||
before do
|
||||
application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
|
||||
@token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api")
|
||||
end
|
||||
|
||||
let(:path) { "#{project.full_path}.git" }
|
||||
let(:env) { { user: 'oauth2', password: @token.token } }
|
||||
|
||||
it_behaves_like 'pulls are allowed'
|
||||
it_behaves_like 'pushes are allowed'
|
||||
|
||||
context "when password is expired" do
|
||||
it "responds to downloads with status 401 unauthorized" do
|
||||
user.update!(password_expires_at: 2.days.ago)
|
||||
|
||||
download(path, **env) do |response|
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has 2FA enabled' do
|
||||
let(:user) { create(:user, :two_factor) }
|
||||
let(:access_token) { create(:personal_access_token, user: user) }
|
||||
let(:path) { "#{project.full_path}.git" }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
context 'when username and password are provided' do
|
||||
it 'rejects pulls with personal access token error message' do
|
||||
download(path, user: user.username, password: user.password) do |response|
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects the push attempt with personal access token error message' do
|
||||
upload(path, user: user.username, password: user.password) do |response|
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when username and personal access token are provided' do
|
||||
let(:env) { { user: user.username, password: access_token.token } }
|
||||
|
||||
it_behaves_like 'pulls are allowed'
|
||||
it_behaves_like 'pushes are allowed'
|
||||
|
||||
it 'rejects the push attempt for read_repository scope' do
|
||||
read_access_token = create(:personal_access_token, user: user, scopes: [:read_repository])
|
||||
|
||||
upload(path, user: user.username, password: read_access_token.token) do |response|
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response.body).to include('You are not allowed to upload code')
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts the push attempt for write_repository scope' do
|
||||
write_access_token = create(:personal_access_token, user: user, scopes: [:write_repository])
|
||||
|
||||
upload(path, user: user.username, password: write_access_token.token) do |response|
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts the pull attempt for read_repository scope' do
|
||||
read_access_token = create(:personal_access_token, user: user, scopes: [:read_repository])
|
||||
|
||||
download(path, user: user.username, password: read_access_token.token) do |response|
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts the pull attempt for api scope' do
|
||||
read_access_token = create(:personal_access_token, user: user, scopes: [:api])
|
||||
|
||||
download(path, user: user.username, password: read_access_token.token) do |response|
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts the push attempt for api scope' do
|
||||
write_access_token = create(:personal_access_token, user: user, scopes: [:api])
|
||||
|
||||
upload(path, user: user.username, password: write_access_token.token) do |response|
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context "when password is expired" do
|
||||
it "responds to uploads with status 401 unauthorized" do
|
||||
user.update!(password_expires_at: 2.days.ago)
|
||||
|
||||
write_access_token = create(:personal_access_token, user: user, scopes: [:write_repository])
|
||||
|
||||
upload(path, user: user.username, password: write_access_token.token) do |response|
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when internal auth is disabled' do
|
||||
before do
|
||||
allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled_for_git?) { false }
|
||||
end
|
||||
|
||||
it 'rejects pulls with personal access token error message' do
|
||||
download(path, user: 'foo', password: 'bar') do |response|
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects pushes with personal access token error message' do
|
||||
upload(path, user: 'foo', password: 'bar') do |response|
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(response.body).to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when LDAP is configured' do
|
||||
before do
|
||||
allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true)
|
||||
allow_any_instance_of(Gitlab::Auth::Ldap::Authentication)
|
||||
.to receive(:login).and_return(nil)
|
||||
end
|
||||
|
||||
it 'does not display the personal access token error message' do
|
||||
upload(path, user: 'foo', password: 'bar') do |response|
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(response.body).not_to include('You must use a personal access token with \'read_repository\' or \'write_repository\' scope for Git over HTTP')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when blank password attempts follow a valid login" do
|
||||
def attempt_login(include_password)
|
||||
password = include_password ? user.password : ""
|
||||
clone_get path, user: user.username, password: password
|
||||
response.status
|
||||
end
|
||||
|
||||
include_context 'rack attack cache store'
|
||||
|
||||
it "repeated attempts followed by successful attempt" do
|
||||
options = Gitlab.config.rack_attack.git_basic_auth
|
||||
maxretry = options[:maxretry]
|
||||
ip = '1.2.3.4'
|
||||
|
||||
allow_any_instance_of(ActionDispatch::Request).to receive(:ip).and_return(ip)
|
||||
Rack::Attack::Allow2Ban.reset(ip, options)
|
||||
|
||||
maxretry.times.each do
|
||||
expect(attempt_login(false)).to eq(401)
|
||||
end
|
||||
|
||||
expect(attempt_login(true)).to eq(200)
|
||||
expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the user requests a redirected path' do
|
||||
let!(:redirect) { project.route.create_redirect('foo/bar') }
|
||||
let(:path) { "#{redirect.path}.git" }
|
||||
let(:project_moved_message) do
|
||||
<<-MSG.strip_heredoc
|
||||
Project '#{redirect.path}' was moved to '#{project.full_path}'.
|
||||
|
||||
Please update your Git remote:
|
||||
|
||||
git remote set-url origin #{project.http_url_to_repo}.
|
||||
MSG
|
||||
end
|
||||
|
||||
it 'downloads get status 200' do
|
||||
clone_get(path, **env)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'uploads get status 404 with "project was moved" message' do
|
||||
upload(path, **env) do |response|
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the user doesn't have access to the project" do
|
||||
it "pulls get status 404" do
|
||||
download(path, user: user.username, password: user.password) do |response|
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
it "uploads get status 404" do
|
||||
upload(path, user: user.username, password: user.password) do |response|
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when a gitlab ci token is provided" do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:build) { create(:ci_build, :running) }
|
||||
let(:other_project) { create(:project, :repository) }
|
||||
|
||||
before do
|
||||
build.update!(project: project) # can't associate it on factory create
|
||||
end
|
||||
|
||||
context 'when build created by system is authenticated' do
|
||||
let(:path) { "#{project.full_path}.git" }
|
||||
let(:env) { { user: 'gitlab-ci-token', password: build.token } }
|
||||
|
||||
it_behaves_like 'pulls are allowed'
|
||||
|
||||
# A non-401 here is not an information leak since the system is
|
||||
# "authenticated" as CI using the correct token. It does not have
|
||||
# push access, so pushes should be rejected as forbidden, and giving
|
||||
# a reason is fine.
|
||||
#
|
||||
# We know for sure it is not an information leak since pulls using
|
||||
# the build token must be allowed.
|
||||
it "rejects pushes with 403 Forbidden" do
|
||||
push_get(path, **env)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response.body).to eq(git_access_error(:auth_upload))
|
||||
end
|
||||
|
||||
# We are "authenticated" as CI using a valid token here. But we are
|
||||
# not authorized to see any other project, so return "not found".
|
||||
it "rejects pulls for other project with 404 Not Found" do
|
||||
clone_get("#{other_project.full_path}.git", **env)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(response.body).to eq(git_access_error(:project_not_found))
|
||||
end
|
||||
end
|
||||
|
||||
context 'and build created by' do
|
||||
before do
|
||||
build.update!(user: user)
|
||||
project.add_reporter(user)
|
||||
end
|
||||
|
||||
shared_examples 'can download code only' do
|
||||
let(:path) { "#{project.full_path}.git" }
|
||||
let(:env) { { user: 'gitlab-ci-token', password: build.token } }
|
||||
|
||||
it_behaves_like 'pulls are allowed'
|
||||
|
||||
context 'when the repo does not exist' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it 'rejects pulls with 404 Not Found' do
|
||||
clone_get(path, **env)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(response.body).to eq(git_access_error(:no_repo))
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects pushes with 403 Forbidden' do
|
||||
push_get(path, **env)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response.body).to eq(git_access_error(:auth_upload))
|
||||
end
|
||||
end
|
||||
|
||||
context 'administrator' do
|
||||
let(:user) { create(:admin) }
|
||||
|
||||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it_behaves_like 'can download code only'
|
||||
|
||||
it 'downloads from other project get status 404' do
|
||||
clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it_behaves_like 'can download code only'
|
||||
|
||||
it 'downloads from other project get status 404' do
|
||||
clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'regular user' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it_behaves_like 'can download code only'
|
||||
|
||||
it 'downloads from other project get status 404' do
|
||||
clone_get "#{other_project.full_path}.git", user: 'gitlab-ci-token', password: build.token
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
context 'when users password is expired' do
|
||||
it 'rejects pulls with 401 unauthorized' do
|
||||
user.update!(password_expires_at: 2.days.ago)
|
||||
|
||||
download(path, user: 'gitlab-ci-token', password: build.token) do |response|
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'project path without .git suffix' do
|
||||
let(:repository_path) { create(:project, :repository, :public, path: 'project.').full_path }
|
||||
end
|
||||
|
||||
context "retrieving an info/refs file" do
|
||||
let(:project) { create(:project, :repository, :public, path: 'project.') }
|
||||
|
||||
context "when the file exists" do
|
||||
before do
|
||||
# Provide a dummy file in its place
|
||||
allow_any_instance_of(Repository).to receive(:blob_at).and_call_original
|
||||
allow_any_instance_of(Repository).to receive(:blob_at).with('b83d6e391c22777fca1ed3012fce84f633d7fed0', 'info/refs') do
|
||||
Blob.decorate(Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt'), project)
|
||||
end
|
||||
|
||||
get "/#{project.full_path}/-/blob/master/info/refs"
|
||||
end
|
||||
|
||||
it "returns the file" do
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the file does not exist" do
|
||||
before do
|
||||
get "/#{project.full_path}/-/blob/master/info/refs"
|
||||
end
|
||||
|
||||
it "redirects" do
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the Wiki path ends with a dot" do
|
||||
let(:wiki) { ProjectWiki.new(project) }
|
||||
let(:path) { "/#{wiki.repository.full_path}.git" }
|
||||
|
||||
context "when the project is public" do
|
||||
let(:project) { create(:project, :wiki_repo, :public, :wiki_enabled, path: 'foo.') }
|
||||
|
||||
it_behaves_like 'pushes require Basic HTTP Authentication'
|
||||
|
||||
context 'when unauthenticated' do
|
||||
let(:env) { {} }
|
||||
|
||||
it_behaves_like 'pulls are allowed'
|
||||
|
||||
it "responds to pulls with the wiki's repo" do
|
||||
download(path) do |response|
|
||||
json_body = ActiveSupport::JSON.decode(response.body)
|
||||
|
||||
expect(json_body['Repository']['relative_path']).to eq(wiki.repository.relative_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated' do
|
||||
let(:env) { { user: user.username, password: user.password } }
|
||||
|
||||
context 'and as a developer on the team' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
context 'but the repo is disabled' do
|
||||
let(:project) { create(:project, :wiki_repo, :public, :repository_disabled, :wiki_enabled, path: 'foo.') }
|
||||
|
||||
it_behaves_like 'pulls are allowed'
|
||||
it_behaves_like 'pushes are allowed'
|
||||
end
|
||||
end
|
||||
|
||||
context 'and not on the team' do
|
||||
it_behaves_like 'pulls are allowed'
|
||||
|
||||
it 'rejects pushes with 403 Forbidden' do
|
||||
upload(path, **env) do |response|
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response.body).to eq(git_access_wiki_error(:write_to_wiki))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the project is private" do
|
||||
let(:project) { create(:project, :wiki_repo, :private, :wiki_enabled, path: 'foo.') }
|
||||
|
||||
it_behaves_like 'pulls require Basic HTTP Authentication'
|
||||
it_behaves_like 'pushes require Basic HTTP Authentication'
|
||||
it_behaves_like 'operations are not allowed with expired password'
|
||||
|
||||
context 'when authenticated' do
|
||||
context 'and as a developer on the team' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
context 'when user is using credentials with special characters' do
|
||||
context 'with password with special characters' do
|
||||
before do
|
||||
user.update!(password: 'RKszEwéC5kFnû∆f243fycGu§Gh9ftDj!U')
|
||||
end
|
||||
|
||||
it 'allows clones' do
|
||||
download(path, user: user.username, password: user.password) do |response|
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'but the repo is disabled' do
|
||||
let(:project) { create(:project, :wiki_repo, :private, :repository_disabled, :wiki_enabled, path: 'foo.') }
|
||||
|
||||
it 'allows clones' do
|
||||
download(path, user: user.username, password: user.password) do |response|
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
it 'pushes are allowed' do
|
||||
upload(path, user: user.username, password: user.password) do |response|
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and not on the team' do
|
||||
it 'rejects clones with 404 Not Found' do
|
||||
download(path, user: user.username, password: user.password) do |response|
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(response.body).to eq(git_access_wiki_error(:not_found))
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects pushes with 404 Not Found' do
|
||||
upload(path, user: user.username, password: user.password) do |response|
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(response.body).to eq(git_access_wiki_error(:not_found))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "User with LDAP identity" do
|
||||
|
|
|
@ -19,7 +19,8 @@ RSpec.shared_examples 'network policy common specs' do
|
|||
creation_timestamp: nil,
|
||||
manifest: YAML.dump(policy.resource.deep_stringify_keys),
|
||||
is_autodevops: false,
|
||||
is_enabled: true
|
||||
is_enabled: true,
|
||||
environment_ids: []
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -124,14 +124,19 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
|
|||
describe 'clean_structure_sql' do
|
||||
let_it_be(:clean_rake_task) { 'gitlab:db:clean_structure_sql' }
|
||||
let_it_be(:test_task_name) { 'gitlab:db:_test_multiple_structure_cleans' }
|
||||
let_it_be(:structure_file) { 'db/structure.sql' }
|
||||
let_it_be(:input) { 'this is structure data' }
|
||||
|
||||
let(:output) { StringIO.new }
|
||||
|
||||
before do
|
||||
stub_file_read(structure_file, content: input)
|
||||
allow(File).to receive(:open).with(structure_file, any_args).and_yield(output)
|
||||
structure_files = %w[db/structure.sql db/ci_structure.sql]
|
||||
|
||||
allow(File).to receive(:open).and_call_original
|
||||
|
||||
structure_files.each do |structure_file|
|
||||
stub_file_read(structure_file, content: input)
|
||||
allow(File).to receive(:open).with(Rails.root.join(structure_file).to_s, any_args).and_yield(output)
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
|
@ -139,8 +144,10 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
|
|||
end
|
||||
|
||||
it 'can be executed multiple times within another rake task' do
|
||||
expect_multiple_executions_of_task(test_task_name, clean_rake_task) do
|
||||
expect_next_instance_of(Gitlab::Database::SchemaCleaner) do |cleaner|
|
||||
expect_multiple_executions_of_task(test_task_name, clean_rake_task, count: 2) do
|
||||
database_count = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).size
|
||||
|
||||
expect_next_instances_of(Gitlab::Database::SchemaCleaner, database_count) do |cleaner|
|
||||
expect(cleaner).to receive(:clean).with(output)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue