diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 81c871de46b3eeef1d5f62bf65d62f16c97db309..1cac385c6cb864bab53f6846e112f5a93fd17401 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -1.10.0 +1.11.0 diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue index 7ed51068d42715373ff6ea48a19d4df8ca98f146..6f6b9ad025a2074a3f0f22134fd02c7d44b547e3 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue +++ b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue @@ -1,39 +1,92 @@ @@ -54,8 +107,52 @@ export default { :regions="regions" :error="loadingRegionsError" :loading="isLoadingRegions" - @input="setRegion({ region: $event })" + @input="setRegionAndFetchVpcs($event)" + /> + +
+ + +

+
+
+ + +

diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/subnet_dropdown.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/subnet_dropdown.vue deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/vpc_dropdown.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/vpc_dropdown.vue deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js b/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js index 5a13d32e0d26900ec5be27c05ec8cada9624a2a9..030b6b384b119c34cf79a7695d277262780e38ba 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js @@ -17,4 +17,45 @@ export const fetchRegions = () => .send(); }); +export const fetchVpcs = () => + new Promise((resolve, reject) => { + const ec2 = new EC2(); + + ec2 + .describeVpcs() + .on('success', ({ data: { Vpcs: vpcs } }) => { + const transformedVpcs = vpcs.map(({ VpcId: id }) => ({ id, name: id })); + + resolve(transformedVpcs); + }) + .on('error', error => { + reject(error); + }) + .send(); + }); + +export const fetchSubnets = ({ vpc }) => + new Promise((resolve, reject) => { + const ec2 = new EC2(); + + ec2 + .describeSubnets({ + Filters: [ + { + Name: 'vpc-id', + Values: [vpc.id], + }, + ], + }) + .on('success', ({ data: { Subnets: subnets } }) => { + const transformedSubnets = subnets.map(({ SubnetId: id }) => ({ id, name: id })); + + resolve(transformedSubnets); + }) + .on('error', error => { + reject(error); + }) + .send(); + }); + export default () => {}; diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js index 68493be37e016dfd67eb4c35793703a499c87536..0809fc2dfc4886d6f85219692098d3533b17b859 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js @@ -4,4 +4,12 @@ export const setRegion = ({ commit }, payload) => { commit(types.SET_REGION, payload); }; +export const setVpc = ({ commit }, payload) => { + commit(types.SET_VPC, payload); +}; + +export const setSubnet = ({ commit }, payload) => { + commit(types.SET_SUBNET, payload); +}; + export default () => {}; diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/cluster_dropdown/actions.js b/app/assets/javascripts/create_cluster/eks_cluster/store/cluster_dropdown/actions.js index 4f1ecb227192ec528878a757259cf4cc491045e5..5d250b2e29e14033a96a0d49baa2c8242ac38b1c 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/cluster_dropdown/actions.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/cluster_dropdown/actions.js @@ -4,10 +4,10 @@ export default fetchItems => ({ requestItems: ({ commit }) => commit(types.REQUEST_ITEMS), receiveItemsSuccess: ({ commit }, payload) => commit(types.RECEIVE_ITEMS_SUCCESS, payload), receiveItemsError: ({ commit }, payload) => commit(types.RECEIVE_ITEMS_ERROR, payload), - fetchItems: ({ dispatch }) => { + fetchItems: ({ dispatch }, payload) => { dispatch('requestItems'); - return fetchItems() + return fetchItems(payload) .then(items => dispatch('receiveItemsSuccess', { items })) .catch(error => dispatch('receiveItemsError', { error })); }, diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js b/app/assets/javascripts/create_cluster/eks_cluster/store/index.js index 608c4ec9e1ebae90586353f697419fa645191b4d..622095f2cc8fa897e35cc3bac3438b8e1df06654 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/index.js @@ -19,6 +19,14 @@ const createStore = () => namespaced: true, ...clusterDropdownStore(awsServices.fetchRegions), }, + vpcs: { + namespaced: true, + ...clusterDropdownStore(awsServices.fetchVpcs), + }, + subnets: { + namespaced: true, + ...clusterDropdownStore(awsServices.fetchSubnets), + }, }, }); diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js index 80c1354482b5402349dba28d77fbaa96709b065e..ada76b21f183aca09532f389ef706516cd5a4eda 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js @@ -1,2 +1,3 @@ -// eslint-disable-next-line import/prefer-default-export export const SET_REGION = 'SET_REGION'; +export const SET_VPC = 'SET_VPC'; +export const SET_SUBNET = 'SET_SUBNET'; diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js index 02f34d1bdcd938f1e6bae3dd2290afbf673a669f..346716bb0df789b9ae6752418c6473aa1132690a 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js @@ -4,4 +4,10 @@ export default { [types.SET_REGION](state, { region }) { state.selectedRegion = region; }, + [types.SET_VPC](state, { vpc }) { + state.selectedVpc = vpc; + }, + [types.SET_SUBNET](state, { subnet }) { + state.selectedSubnet = subnet; + }, }; diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb index 60a68cec3c381c56199dbb10a02acc824f8934ca..79bee6f89d8306cbf791db0d919a6ce55e66591d 100644 --- a/app/controllers/concerns/uploads_actions.rb +++ b/app/controllers/concerns/uploads_actions.rb @@ -91,7 +91,7 @@ module UploadsActions upload_paths = uploader.upload_paths(params[:filename]) upload = Upload.find_by(model: model, uploader: uploader_class.to_s, path: upload_paths) - upload&.build_uploader + upload&.retrieve_uploader end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index 42d4e86fe8d9b3295467174d2f1bce426548a92f..946241b7d4c3c3936378bed43a418dba1be34534 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -86,3 +86,5 @@ module Ci end end end + +Ci::PipelineSchedule.prepend_if_ee('EE::Ci::PipelineSchedule') diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index 8792c5cf98b34a4f78c8fea91241a549f47b9037..68548bd2fdc33dcd87bfedabd5a124a4243d5b41 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -45,3 +45,5 @@ module Ci end end end + +Ci::Trigger.prepend_if_ee('EE::Ci::Trigger') diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb index e748c0a855d0d76869a6a614597ece444c23ac13..979cf0645f512051ccef88eb0ac157b8e59fe7a9 100644 --- a/app/models/clusters/concerns/application_core.rb +++ b/app/models/clusters/concerns/application_core.rb @@ -64,3 +64,5 @@ module Clusters end end end + +Clusters::Concerns::ApplicationCore.prepend_if_ee('EE::Clusters::Concerns::ApplicationCore') diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb index 6b303c522838f9929df913672a0d85c839ce6bf4..d4c29aa295b5ab9f744e4534b2c6308fa680f8d7 100644 --- a/app/models/commit_collection.rb +++ b/app/models/commit_collection.rb @@ -72,8 +72,15 @@ class CommitCollection end.compact] # Replace the commits, keeping the same order - @commits = @commits.map do |c| - replacements.fetch(c.id, c) + @commits = @commits.map do |original_commit| + # Return the original instance: if it didn't need to be batchloaded, it was + # already enriched. + batch_loaded_commit = replacements.fetch(original_commit.id, original_commit) + + # If batch loading the commit failed, fall back to the original commit. + # We need to explicitly check `.nil?` since otherwise a `BatchLoader` instance + # that looks like `nil` is returned. + batch_loaded_commit.nil? ? original_commit : batch_loaded_commit end self diff --git a/app/models/upload.rb b/app/models/upload.rb index 7560002ada8ca134257732d1209f56a9b3793514..384949ddb86954c1ed87a6458d8ccd169c210264 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -15,7 +15,7 @@ class Upload < ApplicationRecord scope :with_files_stored_remotely, -> { where(store: ObjectStorage::Store::REMOTE) } before_save :calculate_checksum!, if: :foreground_checksummable? - after_commit :schedule_checksum, if: :checksummable? + after_commit :schedule_checksum, if: :needs_checksum? # as the FileUploader is not mounted, the default CarrierWave ActiveRecord # hooks are not executed and the file will not be deleted @@ -53,20 +53,41 @@ class Upload < ApplicationRecord def calculate_checksum! self.checksum = nil - return unless checksummable? + return unless needs_checksum? self.checksum = Digest::SHA256.file(absolute_path).hexdigest end + # Initialize the associated Uploader class with current model + # + # @param [String] mounted_as + # @return [GitlabUploader] one of the subclasses, defined at the model's uploader attribute def build_uploader(mounted_as = nil) uploader_class.new(model, mounted_as || mount_point).tap do |uploader| uploader.upload = self + end + end + + # Initialize the associated Uploader class with current model and + # retrieve existing file from the store to a local cache + # + # @param [String] mounted_as + # @return [GitlabUploader] one of the subclasses, defined at the model's uploader attribute + def retrieve_uploader(mounted_as = nil) + build_uploader(mounted_as).tap do |uploader| uploader.retrieve_from_store!(identifier) end end + # This checks for existence of the upload on storage + # + # @return [Boolean] whether upload exists on storage def exist? - exist = File.exist?(absolute_path) + exist = if local? + File.exist?(absolute_path) + else + retrieve_uploader.exists? + end # Help sysadmins find missing upload files if persisted? && !exist @@ -91,18 +112,24 @@ class Upload < ApplicationRecord store == ObjectStorage::Store::LOCAL end + # Returns whether generating checksum is needed + # + # This takes into account whether file exists, if any checksum exists + # or if the storage has checksum generation code implemented + # + # @return [Boolean] whether generating a checksum is needed + def needs_checksum? + checksum.nil? && local? && exist? + end + private def delete_file! - build_uploader.remove! - end - - def checksummable? - checksum.nil? && local? && exist? + retrieve_uploader.remove! end def foreground_checksummable? - checksummable? && size <= CHECKSUM_THRESHOLD + needs_checksum? && size <= CHECKSUM_THRESHOLD end def schedule_checksum diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index 9af59b0aceb1e3558accf6dc96ea302f47ad5856..d42c9dbedf4894911800cfb8485d292d575cf9cd 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -19,7 +19,7 @@ class AvatarUploader < GitlabUploader end def absolute_path - self.class.absolute_path(model.avatar.upload) + self.class.absolute_path(upload) end private diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb index cefcd3d3f5a129449d1ff099f3c934051d0e8a00..7dc211b14e44094c8b26b3f19ec21b98373b80f7 100644 --- a/app/uploaders/gitlab_uploader.rb +++ b/app/uploaders/gitlab_uploader.rb @@ -99,6 +99,17 @@ class GitlabUploader < CarrierWave::Uploader::Base end end + # Used to replace an existing upload with another +file+ without modifying stored metadata + # Use this method only to repair/replace an existing upload, or to upload to a Geo secondary node + # + # @param [CarrierWave::SanitizedFile] file that will replace existing upload + # @return CarrierWave::SanitizedFile + def replace_file_without_saving!(file) + raise ArgumentError, 'should be a CarrierWave::SanitizedFile' unless file.is_a? CarrierWave::SanitizedFile + + storage.store!(file) + end + private # Designed to be overridden by child uploaders that have a dynamic path diff --git a/app/workers/import_issues_csv_worker.rb b/app/workers/import_issues_csv_worker.rb index d44fdfec8ae3383284e4282d733bfc8e190a5aed..b9d7099af71a171a890036084db6485eab34e58a 100644 --- a/app/workers/import_issues_csv_worker.rb +++ b/app/workers/import_issues_csv_worker.rb @@ -12,7 +12,7 @@ class ImportIssuesCsvWorker @project = Project.find(project_id) @upload = Upload.find(upload_id) - importer = Issues::ImportCsvService.new(@user, @project, @upload.build_uploader) + importer = Issues::ImportCsvService.new(@user, @project, @upload.retrieve_uploader) importer.execute @upload.destroy diff --git a/app/workers/object_storage/background_move_worker.rb b/app/workers/object_storage/background_move_worker.rb index 8dff65e46e39d8283252747da3f40e6caa640cce..19ccae7739c33d0a4466ca6286a0c5f93f2e00e6 100644 --- a/app/workers/object_storage/background_move_worker.rb +++ b/app/workers/object_storage/background_move_worker.rb @@ -22,7 +22,7 @@ module ObjectStorage def build_uploader(subject, mount_point) case subject - when Upload then subject.build_uploader(mount_point) + when Upload then subject.retrieve_uploader(mount_point) else subject.send(mount_point) # rubocop:disable GitlabSecurity/PublicSend end diff --git a/app/workers/object_storage/migrate_uploads_worker.rb b/app/workers/object_storage/migrate_uploads_worker.rb index 55ac7cd9b3c18876fad008faab568848336c28c4..c9fd19cf9d7fc3c37c95be04bf6e2bd658003471 100644 --- a/app/workers/object_storage/migrate_uploads_worker.rb +++ b/app/workers/object_storage/migrate_uploads_worker.rb @@ -119,7 +119,7 @@ module ObjectStorage end def build_uploaders(uploads) - uploads.map { |upload| upload.build_uploader(@mounted_as) } + uploads.map { |upload| upload.retrieve_uploader(@mounted_as) } end def migrate(uploads) diff --git a/changelogs/unreleased/ac-pull-mirror-branch-prefix.yml b/changelogs/unreleased/ac-pull-mirror-branch-prefix.yml new file mode 100644 index 0000000000000000000000000000000000000000..b39308ea38ad9dc006537cda61b25b5efcd23e4a --- /dev/null +++ b/changelogs/unreleased/ac-pull-mirror-branch-prefix.yml @@ -0,0 +1,5 @@ +--- +title: Add pull_mirror_branch_prefix column on projects table +merge_request: 17368 +author: +type: added diff --git a/changelogs/unreleased/add-sorting-to-packages-list.yml b/changelogs/unreleased/add-sorting-to-packages-list.yml new file mode 100644 index 0000000000000000000000000000000000000000..8eab37326a124c2ca07b89c6923d3d20293fe0e1 --- /dev/null +++ b/changelogs/unreleased/add-sorting-to-packages-list.yml @@ -0,0 +1,5 @@ +--- +title: Adds sorting of packages at the project level +merge_request: 15448 +author: +type: added diff --git a/changelogs/unreleased/bvl-fix-view-mr-deleted-repo.yml b/changelogs/unreleased/bvl-fix-view-mr-deleted-repo.yml new file mode 100644 index 0000000000000000000000000000000000000000..ffd7135eec1d4733962918b08219c05514d8e352 --- /dev/null +++ b/changelogs/unreleased/bvl-fix-view-mr-deleted-repo.yml @@ -0,0 +1,5 @@ +--- +title: Fix viewing merge reqeust from a fork that's being deleted +merge_request: 17894 +author: +type: fixed diff --git a/changelogs/unreleased/pages-1-11-0.yml b/changelogs/unreleased/pages-1-11-0.yml new file mode 100644 index 0000000000000000000000000000000000000000..da798340ee2e94c7d84fe39b8814afb8cc168444 --- /dev/null +++ b/changelogs/unreleased/pages-1-11-0.yml @@ -0,0 +1,5 @@ +--- +title: Update Pages to v1.11.0 +merge_request: 18010 +author: +type: other diff --git a/com/lib/com/gitlab/patch/draw_route.rb b/com/lib/com/gitlab/patch/draw_route.rb new file mode 100644 index 0000000000000000000000000000000000000000..8626ff06c28cc18003672c6edd8e08b7737b8d54 --- /dev/null +++ b/com/lib/com/gitlab/patch/draw_route.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Com + module Gitlab + module Patch + module DrawRoute + extend ::Gitlab::Utils::Override + + override :draw_com + def draw_com(routes_name) + draw_route(route_path("com/config/routes/#{routes_name}.rb")) + end + end + end + end +end diff --git a/com/spec/lib/gitlab/patch/draw_route_spec.rb b/com/spec/lib/gitlab/patch/draw_route_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..823bebd5c0d0c6314a4d4363171c03cc2391674f --- /dev/null +++ b/com/spec/lib/gitlab/patch/draw_route_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'com_spec_helper' + +describe Gitlab::Patch::DrawRoute do + subject do + Class.new do + include Gitlab::Patch::DrawRoute + + def route_path(route_name) + File.expand_path("../../../../../#{route_name}", __dir__) + end + end.new + end + + before do + allow(subject).to receive(:instance_eval) + end + + it 'raises an error when nothing is drawn' do + expect { subject.draw(:non_existing) } + .to raise_error(described_class::RoutesNotFound) + end +end diff --git a/config/application.rb b/config/application.rb index 5d7c52c5d8118802e5fc947219e3057d6ad0b0be..192e836594add6ab49c0e1ef5738c58e21d6a8e9 100644 --- a/config/application.rb +++ b/config/application.rb @@ -22,6 +22,7 @@ module Gitlab require_dependency Rails.root.join('lib/gitlab/current_settings') require_dependency Rails.root.join('lib/gitlab/middleware/read_only') require_dependency Rails.root.join('lib/gitlab/middleware/basic_health_check') + require_dependency Rails.root.join('config/light_settings') # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers @@ -62,6 +63,15 @@ module Gitlab config.paths['app/views'].unshift "#{config.root}/ee/app/views" end + if LightSettings.com? + com_paths = config.eager_load_paths.each_with_object([]) do |path, memo| + com_path = config.root.join('com', Pathname.new(path).relative_path_from(config.root)) + memo << com_path.to_s + end + + config.eager_load_paths.push(*com_paths) + end + # Rake tasks ignore the eager loading settings, so we need to set the # autoload paths explicitly config.autoload_paths = config.eager_load_paths.dup diff --git a/config/initializers/0_inject_com_module.rb b/config/initializers/0_inject_com_module.rb new file mode 100644 index 0000000000000000000000000000000000000000..9802eb37ec38dafb07514b6b9387b1e35c46101c --- /dev/null +++ b/config/initializers/0_inject_com_module.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'active_support/inflector' + +module InjectComModule + def prepend_if_com(constant, with_descendants: false) + return unless Gitlab.com? + + com_module = constant.constantize + prepend(com_module) + + if with_descendants + descendants.each { |descendant| descendant.prepend(com_module) } + end + end + + def extend_if_com(constant) + extend(constant.constantize) if Gitlab.com? + end + + def include_if_com(constant) + include(constant.constantize) if Gitlab.com? + end +end + +Module.prepend(InjectComModule) diff --git a/config/light_settings.rb b/config/light_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..aa0dd04b3fad3d5d5d648912cb162965f234fdb3 --- /dev/null +++ b/config/light_settings.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class LightSettings + GL_HOST ||= 'gitlab.com' + GL_SUBDOMAIN_REGEX ||= %r{\A[a-z0-9]+\.gitlab\.com\z}.freeze + + class << self + def com? + return Thread.current[:is_com] unless Thread.current[:is_com].nil? + + Thread.current[:is_com] = host == GL_HOST || gl_subdomain? + end + + private + + def config + YAML.safe_load(File.read(settings_path), aliases: true)[Rails.env] + end + + def settings_path + Rails.root.join('config', 'gitlab.yml') + end + + def host + config['gitlab']['host'] + end + + def gl_subdomain? + GL_SUBDOMAIN_REGEX === host + end + end +end diff --git a/db/migrate/20190924124627_add_pull_mirror_branch_prefix_to_projects.rb b/db/migrate/20190924124627_add_pull_mirror_branch_prefix_to_projects.rb new file mode 100644 index 0000000000000000000000000000000000000000..b9f729d7d663c0504bba3f645383bec596644de2 --- /dev/null +++ b/db/migrate/20190924124627_add_pull_mirror_branch_prefix_to_projects.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddPullMirrorBranchPrefixToProjects < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + add_column :projects, :pull_mirror_branch_prefix, :string, limit: 50 + end +end diff --git a/db/schema.rb b/db/schema.rb index f5eb39d2087e91543781fc24a91ff4ad3718acde..8c22fe80381cc7798b86d6d9d24635cb178950eb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2922,6 +2922,7 @@ ActiveRecord::Schema.define(version: 2019_09_27_074328) do t.boolean "emails_disabled" t.integer "max_pages_size" t.integer "max_artifacts_size" + t.string "pull_mirror_branch_prefix", limit: 50 t.index "lower((name)::text)", name: "index_projects_on_lower_name" t.index ["archived", "pending_delete", "merge_requests_require_code_owner_approval"], name: "projects_requiring_code_owner_approval", where: "((pending_delete = false) AND (archived = false) AND (merge_requests_require_code_owner_approval = true))" t.index ["created_at"], name: "index_projects_on_created_at" diff --git a/lib/gitlab.rb b/lib/gitlab.rb index b337f5cbf2ccf61c902e334705fee852b9888ed0..43b3642fd6b00943bef5516a3bb5687c1836d024 100644 --- a/lib/gitlab.rb +++ b/lib/gitlab.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'pathname' +require_relative '../config/light_settings' module Gitlab def self.root @@ -37,24 +38,18 @@ module Gitlab COM_URL = 'https://gitlab.com' APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))}.freeze - SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}.freeze VERSION = File.read(root.join("VERSION")).strip.freeze INSTALLATION_TYPE = File.read(root.join("INSTALLATION_TYPE")).strip.freeze HTTP_PROXY_ENV_VARS = %w(http_proxy https_proxy HTTP_PROXY HTTPS_PROXY).freeze def self.com? - # Check `gl_subdomain?` as well to keep parity with gitlab.com - Gitlab.config.gitlab.url == COM_URL || gl_subdomain? + LightSettings.com? end def self.org? Gitlab.config.gitlab.url == 'https://dev.gitlab.org' end - def self.gl_subdomain? - SUBDOMAIN_REGEX === Gitlab.config.gitlab.url - end - def self.dev_env_org_or_com? dev_env_or_com? || org? end @@ -79,6 +74,10 @@ module Gitlab yield if ee? end + def self.com + yield if com? + end + def self.http_proxy_env? HTTP_PROXY_ENV_VARS.any? { |name| ENV[name] } end diff --git a/lib/gitlab/background_migration/legacy_upload_mover.rb b/lib/gitlab/background_migration/legacy_upload_mover.rb index 051c1176edbd898616a3ef821345807ab2c6e061..c9e47f210be61e754cf0df373e653aace2a4e955 100644 --- a/lib/gitlab/background_migration/legacy_upload_mover.rb +++ b/lib/gitlab/background_migration/legacy_upload_mover.rb @@ -92,7 +92,7 @@ module Gitlab def legacy_file_uploader strong_memoize(:legacy_file_uploader) do - uploader = upload.build_uploader + uploader = upload.retrieve_uploader uploader.retrieve_from_store!(File.basename(upload.path)) uploader end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 12a9f72b8cb29ba15e8720bad3010fd928572e93..141e73e6a4736de505e4595576681f586baa4935 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -139,6 +139,7 @@ excluded_attributes: - :mirror_trigger_builds - :only_mirror_protected_branches - :pull_mirror_available_overridden + - :pull_mirror_branch_prefix - :mirror_overwrites_diverged_branches - :packages_enabled - :mirror_last_update_at diff --git a/lib/gitlab/import_export/uploads_manager.rb b/lib/gitlab/import_export/uploads_manager.rb index e232198150a43094d609934cac6227e72ea7ec45..dca8e3a744946a3ab096ba9a8df74e60d5a2642e 100644 --- a/lib/gitlab/import_export/uploads_manager.rb +++ b/lib/gitlab/import_export/uploads_manager.rb @@ -68,7 +68,7 @@ module Gitlab yield(@project.avatar) else project_uploads_except_avatar(avatar_path).find_each(batch_size: UPLOADS_BATCH_SIZE) do |upload| - yield(upload.build_uploader) + yield(upload.retrieve_uploader) end end end diff --git a/lib/gitlab/patch/draw_route.rb b/lib/gitlab/patch/draw_route.rb index 4c8ca0159741f42c2fc8150bc73708874062d727..fc9d7ae805f23f36cd6b267c41cfb1c8d35d0842 100644 --- a/lib/gitlab/patch/draw_route.rb +++ b/lib/gitlab/patch/draw_route.rb @@ -6,11 +6,12 @@ module Gitlab module Patch module DrawRoute prepend_if_ee('EE::Gitlab::Patch::DrawRoute') # rubocop: disable Cop/InjectEnterpriseEditionModule + prepend_if_com('Com::Gitlab::Patch::DrawRoute') RoutesNotFound = Class.new(StandardError) def draw(routes_name) - drawn_any = draw_ce(routes_name) | draw_ee(routes_name) + drawn_any = draw_ce(routes_name) | draw_ee(routes_name) | draw_com(routes_name) drawn_any || raise(RoutesNotFound.new("Cannot find #{routes_name}")) end @@ -23,6 +24,10 @@ module Gitlab true end + def draw_com(_) + false + end + def route_path(routes_name) Rails.root.join(routes_name) end diff --git a/lib/gitlab/sanitizers/exif.rb b/lib/gitlab/sanitizers/exif.rb index 2f3d14ecebd04866da6e9e8f29a376c78670dfe5..5eeb8b00ff39cb993cdf46b0bef242c6881172ad 100644 --- a/lib/gitlab/sanitizers/exif.rb +++ b/lib/gitlab/sanitizers/exif.rb @@ -68,7 +68,7 @@ module Gitlab } relation.find_each(find_params) do |upload| - clean(upload.build_uploader, dry_run: dry_run) + clean(upload.retrieve_uploader, dry_run: dry_run) sleep sleep_time if sleep_time rescue => err logger.error "failed to sanitize #{upload_ref(upload)}: #{err.message}" diff --git a/lib/gitlab/verify/uploads.rb b/lib/gitlab/verify/uploads.rb index 875e8a120e90f9b769e9e0ecc2207179ec834825..afcdbd087d2ec62e44187de86c3adae761f3854f 100644 --- a/lib/gitlab/verify/uploads.rb +++ b/lib/gitlab/verify/uploads.rb @@ -32,7 +32,7 @@ module Gitlab end def remote_object_exists?(upload) - upload.build_uploader.file.exists? + upload.retrieve_uploader.file.exists? end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8a00584894b8c181866e120bb84368b6595ba90b..0c7ddc1e07e3d7897e43c8805051073f44150814 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2421,6 +2421,9 @@ msgstr "" msgid "Branch not loaded - %{branchId}" msgstr "" +msgid "Branch prefix" +msgstr "" + msgid "BranchSwitcherPlaceholder|Search branches" msgstr "" @@ -3351,6 +3354,9 @@ msgstr "" msgid "ClusterIntegration|Choose a prefix to be used for your namespaces. Defaults to your project path." msgstr "" +msgid "ClusterIntegration|Choose the %{startLink}subnets%{endLink} in your VPC where your worker nodes will run." +msgstr "" + msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications." msgstr "" @@ -3390,9 +3396,15 @@ msgstr "" msgid "ClusterIntegration|Copy Service Token" msgstr "" +msgid "ClusterIntegration|Could not load VPCs for the selected region" +msgstr "" + msgid "ClusterIntegration|Could not load regions from your AWS account" msgstr "" +msgid "ClusterIntegration|Could not load subnets for the selected VPC" +msgstr "" + msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" @@ -3579,6 +3591,12 @@ msgstr "" msgid "ClusterIntegration|Loading Regions" msgstr "" +msgid "ClusterIntegration|Loading VPCs" +msgstr "" + +msgid "ClusterIntegration|Loading subnets" +msgstr "" + msgid "ClusterIntegration|Machine type" msgstr "" @@ -3591,6 +3609,9 @@ msgstr "" msgid "ClusterIntegration|No IAM Roles found" msgstr "" +msgid "ClusterIntegration|No VPCs found" +msgstr "" + msgid "ClusterIntegration|No machine types matched your search" msgstr "" @@ -3603,6 +3624,9 @@ msgstr "" msgid "ClusterIntegration|No region found" msgstr "" +msgid "ClusterIntegration|No subnet found" +msgstr "" + msgid "ClusterIntegration|No zones matched your search" msgstr "" @@ -3672,6 +3696,9 @@ msgstr "" msgid "ClusterIntegration|Search IAM Roles" msgstr "" +msgid "ClusterIntegration|Search VPCs" +msgstr "" + msgid "ClusterIntegration|Search machine types" msgstr "" @@ -3681,12 +3708,24 @@ msgstr "" msgid "ClusterIntegration|Search regions" msgstr "" +msgid "ClusterIntegration|Search subnets" +msgstr "" + msgid "ClusterIntegration|Search zones" msgstr "" msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" +msgid "ClusterIntegration|Select a VPC to choose a subnet" +msgstr "" + +msgid "ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services%{endLink}." +msgstr "" + +msgid "ClusterIntegration|Select a region to choose a VPC" +msgstr "" + msgid "ClusterIntegration|Select machine type" msgstr "" @@ -3735,6 +3774,9 @@ msgstr "" msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain." msgstr "" +msgid "ClusterIntegration|Subnet" +msgstr "" + msgid "ClusterIntegration|The Kubernetes certificate used to authenticate to the cluster." msgstr "" @@ -3783,6 +3825,9 @@ msgstr "" msgid "ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster." msgstr "" +msgid "ClusterIntegration|VPC" +msgstr "" + msgid "ClusterIntegration|Validating project billing status" msgstr "" @@ -3831,9 +3876,15 @@ msgstr "" msgid "ClusterIntegration|sign up" msgstr "" +msgid "ClusterIntergation|Select a VPC" +msgstr "" + msgid "ClusterIntergation|Select a region" msgstr "" +msgid "ClusterIntergation|Select a subnet" +msgstr "" + msgid "ClusterIntergation|Select role name" msgstr "" @@ -8640,6 +8691,9 @@ msgstr "" msgid "Introducing Your Conversational Development Index" msgstr "" +msgid "Invalid Git ref" +msgstr "" + msgid "Invalid Insights config file detected" msgstr "" @@ -10120,6 +10174,9 @@ msgstr "" msgid "Mirror user" msgstr "" +msgid "Mirrored branches will have this prefix. If you enabled 'Only mirror protected branches' you need to include this prefix on protected branches in this project or nothing will be mirrored." +msgstr "" + msgid "Mirrored repositories" msgstr "" @@ -14890,6 +14947,12 @@ msgstr "" msgid "SortOptions|Start soon" msgstr "" +msgid "SortOptions|Type" +msgstr "" + +msgid "SortOptions|Version" +msgstr "" + msgid "SortOptions|Weight" msgstr "" diff --git a/spec/com_spec_helper.rb b/spec/com_spec_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..f71ccde950952f4f8d61a25d070d3603124da47f --- /dev/null +++ b/spec/com_spec_helper.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +Settings.gitlab[:url] = "https://test.gitlab.com" diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index bfe02c6010b2679aa810e435b73e2d5783d96c79..ef464d3d6e08344207c0460aa97a975d61a1f564 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -2,7 +2,7 @@ FactoryBot.define do factory :upload do - model { build(:project) } + model { create(:project) } size { 100.kilobytes } uploader { "AvatarUploader" } mount_point { :avatar } @@ -11,23 +11,27 @@ FactoryBot.define do # we should build a mount agnostic upload by default transient do - filename { 'myfile.jpg' } + filename { 'avatar.jpg' } end - # this needs to comply with RecordsUpload::Concern#upload_path - path { File.join("uploads/-/system", model.class.underscore, mount_point.to_s, 'avatar.jpg') } + path do + uploader_instance = Object.const_get(uploader.to_s).new(model, mount_point) + File.join(uploader_instance.store_dir, filename) + end trait :personal_snippet_upload do - uploader { "PersonalFileUploader" } + model { create(:personal_snippet) } path { File.join(secret, filename) } - model { build(:personal_snippet) } + uploader { "PersonalFileUploader" } secret { SecureRandom.hex } + mount_point { nil } end trait :issuable_upload do uploader { "FileUploader" } path { File.join(secret, filename) } secret { SecureRandom.hex } + mount_point { nil } end trait :with_file do @@ -42,22 +46,23 @@ FactoryBot.define do end trait :namespace_upload do - model { build(:group) } + model { create(:group) } path { File.join(secret, filename) } uploader { "NamespaceFileUploader" } secret { SecureRandom.hex } + mount_point { nil } end trait :favicon_upload do - model { build(:appearance) } - path { File.join(secret, filename) } + model { create(:appearance) } uploader { "FaviconUploader" } secret { SecureRandom.hex } + mount_point { :favicon } end trait :attachment_upload do mount_point { :attachment } - model { build(:note) } + model { create(:note) } uploader { "AttachmentUploader" } end end diff --git a/spec/fast_spec_helper.rb b/spec/fast_spec_helper.rb index f5a487b4d570b361c1924f5b5d7a2204ec01301e..a7810d7267156098c0056040db45fa7c5fd4ee0c 100644 --- a/spec/fast_spec_helper.rb +++ b/spec/fast_spec_helper.rb @@ -5,10 +5,12 @@ ENV['IN_MEMORY_APPLICATION_SETTINGS'] = 'true' require 'active_support/dependencies' require_relative '../config/initializers/0_inject_enterprise_edition_module' +require_relative '../config/initializers/0_inject_com_module' require_relative '../config/settings' require_relative 'support/rspec' require 'active_support/all' ActiveSupport::Dependencies.autoload_paths << 'lib' ActiveSupport::Dependencies.autoload_paths << 'ee/lib' +ActiveSupport::Dependencies.autoload_paths << 'com/lib' ActiveSupport::XmlMini.backend = 'Nokogiri' diff --git a/spec/features/merge_request/user_views_merge_request_from_deleted_fork_spec.rb b/spec/features/merge_request/user_views_merge_request_from_deleted_fork_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..26984a1fb5eb42d7137f92ece7c9dac329b7163c --- /dev/null +++ b/spec/features/merge_request/user_views_merge_request_from_deleted_fork_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true +require 'spec_helper' + +# This is a feature spec because the problems arrise when rendering the view for +# an actual project for which the repository is removed but the cached not +# updated. +# This can occur when the fork a merge request is created from is in the process +# of being destroyed. +describe 'User views merged merge request from deleted fork' do + include ProjectForksHelper + + let(:project) { create(:project, :repository) } + let(:source_project) { fork_project(project, nil, repository: true) } + let(:user) { project.owner } + let!(:merge_request) { create(:merge_request, :merged, source_project: source_project, target_project: project) } + + before do + sign_in user + + fork_owner = source_project.namespace.owners.first + # Place the source_project in the weird in between state + source_project.update_attribute(:pending_delete, true) + Projects::DestroyService.new(source_project, fork_owner, {}).__send__(:trash_repositories!) + end + + it 'correctly shows the merge request' do + visit(merge_request_path(merge_request)) + + expect(page).to have_content(merge_request.title) + end +end diff --git a/spec/frontend/commit/__snapshots__/commit_pipeline_status_component_spec.js.snap b/spec/frontend/commit/__snapshots__/commit_pipeline_status_component_spec.js.snap deleted file mode 100644 index 9199db69fede390e5122dd2bb858c2561ec2b575..0000000000000000000000000000000000000000 --- a/spec/frontend/commit/__snapshots__/commit_pipeline_status_component_spec.js.snap +++ /dev/null @@ -1,39 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Commit pipeline status component when polling is not successful renders not found CI icon without loader 1`] = ` - -`; - -exports[`Commit pipeline status component when polling is successful renders CI icon without loader 1`] = ` - -`; diff --git a/spec/frontend/commit/commit_pipeline_status_component_spec.js b/spec/frontend/commit/commit_pipeline_status_component_spec.js index 1768fd745c9e9d9f38ba8c3ee3e251841e65114e..1736d1d0df837e2a33e76a0814388eb50239a1b3 100644 --- a/spec/frontend/commit/commit_pipeline_status_component_spec.js +++ b/spec/frontend/commit/commit_pipeline_status_component_spec.js @@ -2,6 +2,7 @@ import Visibility from 'visibilityjs'; import { GlLoadingIcon } from '@gitlab/ui'; import Poll from '~/lib/utils/poll'; import flash from '~/flash'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; import { shallowMount } from '@vue/test-utils'; import { getJSONFixture } from '../helpers/fixtures'; @@ -36,6 +37,10 @@ describe('Commit pipeline status component', () => { }); }; + const findLoader = () => wrapper.find(GlLoadingIcon); + const findLink = () => wrapper.find('a'); + const findCiIcon = () => findLink().find(CiIcon); + afterEach(() => { wrapper.destroy(); wrapper = null; @@ -111,14 +116,14 @@ describe('Commit pipeline status component', () => { it('shows the loading icon at start', () => { createComponent(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(findLoader().exists()).toBe(true); pollConfig.successCallback({ data: { pipelines: [] }, }); return wrapper.vm.$nextTick().then(() => { - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(findLoader().exists()).toBe(false); }); }); @@ -130,8 +135,17 @@ describe('Commit pipeline status component', () => { return wrapper.vm.$nextTick(); }); - it('renders CI icon without loader', () => { - expect(wrapper.element).toMatchSnapshot(); + it('does not render loader', () => { + expect(findLoader().exists()).toBe(false); + }); + + it('renders link with href', () => { + expect(findLink().attributes('href')).toEqual(mockCiStatus.details_path); + }); + + it('renders CI icon', () => { + expect(findCiIcon().attributes('data-original-title')).toEqual('Pipeline: pending'); + expect(findCiIcon().props('status')).toEqual(mockCiStatus); }); }); @@ -140,8 +154,21 @@ describe('Commit pipeline status component', () => { pollConfig.errorCallback(); }); - it('renders not found CI icon without loader', () => { - expect(wrapper.element).toMatchSnapshot(); + it('does not render loader', () => { + expect(findLoader().exists()).toBe(false); + }); + + it('renders link with href', () => { + expect(findLink().attributes('href')).toBeUndefined(); + }); + + it('renders not found CI icon', () => { + expect(findCiIcon().attributes('data-original-title')).toEqual('Pipeline: not found'); + expect(findCiIcon().props('status')).toEqual({ + text: 'not found', + icon: 'status_notfound', + group: 'notfound', + }); }); it('displays flash error message', () => { diff --git a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js index 1bd16d356b954bd015201b25c1c834855b8fc41e..cc7c6735a80c8ed53c5a0e9f7d59752defa178e8 100644 --- a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js @@ -4,6 +4,7 @@ import Vue from 'vue'; import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue'; import RegionDropdown from '~/create_cluster/eks_cluster/components/region_dropdown.vue'; +import eksClusterFormState from '~/create_cluster/eks_cluster/store/state'; import clusterDropdownStoreState from '~/create_cluster/eks_cluster/store/cluster_dropdown/state'; const localVue = createLocalVue(); @@ -12,29 +13,59 @@ localVue.use(Vuex); describe('EksClusterConfigurationForm', () => { let store; let actions; + let state; let regionsState; + let vpcsState; + let subnetsState; + let vpcsActions; let regionsActions; + let subnetsActions; let vm; beforeEach(() => { + state = eksClusterFormState(); actions = { setRegion: jest.fn(), setVpc: jest.fn(), + setSubnet: jest.fn(), }; regionsActions = { fetchItems: jest.fn(), }; + vpcsActions = { + fetchItems: jest.fn(), + }; + subnetsActions = { + fetchItems: jest.fn(), + }; regionsState = { ...clusterDropdownStoreState(), }; + vpcsState = { + ...clusterDropdownStoreState(), + }; + subnetsState = { + ...clusterDropdownStoreState(), + }; store = new Vuex.Store({ + state, actions, modules: { + vpcs: { + namespaced: true, + state: vpcsState, + actions: vpcsActions, + }, regions: { namespaced: true, state: regionsState, actions: regionsActions, }, + subnets: { + namespaced: true, + state: subnetsState, + actions: subnetsActions, + }, }, }); }); @@ -51,6 +82,8 @@ describe('EksClusterConfigurationForm', () => { }); const findRegionDropdown = () => vm.find(RegionDropdown); + const findVpcDropdown = () => vm.find('[field-id="eks-vpc"]'); + const findSubnetDropdown = () => vm.find('[field-id="eks-subnet"]'); describe('when mounted', () => { it('fetches available regions', () => { @@ -62,16 +95,72 @@ describe('EksClusterConfigurationForm', () => { regionsState.isLoadingItems = true; return Vue.nextTick().then(() => { - expect(findRegionDropdown().props('loading')).toEqual(regionsState.isLoadingItems); + expect(findRegionDropdown().props('loading')).toBe(regionsState.isLoadingItems); }); }); it('sets regions to RegionDropdown regions property', () => { - expect(findRegionDropdown().props('regions')).toEqual(regionsState.items); + expect(findRegionDropdown().props('regions')).toBe(regionsState.items); }); it('sets loadingRegionsError to RegionDropdown error property', () => { - expect(findRegionDropdown().props('error')).toEqual(regionsState.loadingItemsError); + expect(findRegionDropdown().props('error')).toBe(regionsState.loadingItemsError); + }); + + it('disables VpcDropdown when no region is selected', () => { + expect(findVpcDropdown().props('disabled')).toBe(true); + }); + + it('enables VpcDropdown when no region is selected', () => { + state.selectedRegion = { name: 'west-1 ' }; + + return Vue.nextTick().then(() => { + expect(findVpcDropdown().props('disabled')).toBe(false); + }); + }); + + it('sets isLoadingVpcs to VpcDropdown loading property', () => { + vpcsState.isLoadingItems = true; + + return Vue.nextTick().then(() => { + expect(findVpcDropdown().props('loading')).toBe(vpcsState.isLoadingItems); + }); + }); + + it('sets vpcs to VpcDropdown items property', () => { + expect(findVpcDropdown().props('items')).toBe(vpcsState.items); + }); + + it('sets loadingVpcsError to VpcDropdown hasErrors property', () => { + expect(findVpcDropdown().props('hasErrors')).toBe(vpcsState.loadingItemsError); + }); + + it('disables SubnetDropdown when no vpc is selected', () => { + expect(findSubnetDropdown().props('disabled')).toBe(true); + }); + + it('enables SubnetDropdown when a vpc is selected', () => { + state.selectedVpc = { name: 'vpc-1 ' }; + + return Vue.nextTick().then(() => { + expect(findSubnetDropdown().props('disabled')).toBe(false); + }); + }); + + it('sets isLoadingSubnets to SubnetDropdown loading property', () => { + subnetsState.isLoadingItems = true; + + return Vue.nextTick().then(() => { + expect(findSubnetDropdown().props('loading')).toBe(subnetsState.isLoadingItems); + }); + }); + + it('sets subnets to SubnetDropdown items property', () => { + expect(findSubnetDropdown().props('items')).toBe(subnetsState.items); + }); + + it('sets loadingSubnetsError to SubnetDropdown hasErrors property', () => { + expect(findSubnetDropdown().props('hasErrors')).toBe(subnetsState.loadingItemsError); }); describe('when region is selected', () => { @@ -84,5 +173,37 @@ describe('EksClusterConfigurationForm', () => { it('dispatches setRegion action', () => { expect(actions.setRegion).toHaveBeenCalledWith(expect.anything(), { region }, undefined); }); + + it('fetches available vpcs', () => { + expect(vpcsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region }, undefined); + }); + }); + + describe('when vpc is selected', () => { + const vpc = { name: 'vpc-1' }; + + beforeEach(() => { + findVpcDropdown().vm.$emit('input', vpc); + }); + + it('dispatches setVpc action', () => { + expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc }, undefined); + }); + + it('dispatches fetchSubnets action', () => { + expect(subnetsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { vpc }, undefined); + }); + }); + + describe('when a subnet is selected', () => { + const subnet = { name: 'subnet-1' }; + + beforeEach(() => { + findSubnetDropdown().vm.$emit('input', subnet); + }); + + it('dispatches setSubnet action', () => { + expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet }, undefined); + }); }); }); diff --git a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js index 3744ddb3dc6915d20c0909bd58e80ae483c256ca..893c657e6995b04592f9e6b1fb742c509f550938 100644 --- a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js @@ -1,17 +1,28 @@ import testAction from 'helpers/vuex_action_helper'; import createState from '~/create_cluster/eks_cluster/store/state'; -import * as types from '~/create_cluster/eks_cluster/store/mutation_types'; import * as actions from '~/create_cluster/eks_cluster/store/actions'; +import { SET_REGION, SET_VPC, SET_SUBNET } from '~/create_cluster/eks_cluster/store/mutation_types'; describe('EKS Cluster Store Actions', () => { - describe('setRegion', () => { - it(`commits ${types.SET_REGION} mutation`, () => { - const region = { name: 'west-1' }; + let region; + let vpc; + let subnet; - testAction(actions.setRegion, { region }, createState(), [ - { type: types.SET_REGION, payload: { region } }, - ]); - }); + beforeEach(() => { + region = { name: 'regions-1' }; + vpc = { name: 'vpc-1' }; + subnet = { name: 'subnet-1' }; + }); + + it.each` + action | mutation | payload | payloadDescription + ${'setRegion'} | ${SET_REGION} | ${{ region }} | ${'region'} + ${'setVpc'} | ${SET_VPC} | ${{ vpc }} | ${'vpc'} + ${'setSubnet'} | ${SET_SUBNET} | ${{ subnet }} | ${'subnet'} + `(`$action commits $mutation with $payloadDescription payload`, data => { + const { action, mutation, payload } = data; + + testAction(actions[action], payload, createState(), [{ type: mutation, payload }]); }); }); diff --git a/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js b/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js index 99f05b0f449646a8eaf6a8cc4b38fbd5bca2cde4..38199471f7973d44ee121cfc97775560a122033c 100644 --- a/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js @@ -1,19 +1,26 @@ -import { SET_REGION } from '~/create_cluster/eks_cluster/store/mutation_types'; +import { SET_REGION, SET_VPC, SET_SUBNET } from '~/create_cluster/eks_cluster/store/mutation_types'; import createState from '~/create_cluster/eks_cluster/store/state'; import mutations from '~/create_cluster/eks_cluster/store/mutations'; describe('Create EKS cluster store mutations', () => { let state; let region; + let vpc; + let subnet; beforeEach(() => { region = { name: 'regions-1' }; + vpc = { name: 'vpc-1' }; + subnet = { name: 'subnet-1' }; + state = createState(); }); it.each` mutation | mutatedProperty | payload | expectedValue | expectedValueDescription ${SET_REGION} | ${'selectedRegion'} | ${{ region }} | ${region} | ${'selected region payload'} + ${SET_VPC} | ${'selectedVpc'} | ${{ vpc }} | ${vpc} | ${'selected vpc payload'} + ${SET_SUBNET} | ${'selectedSubnet'} | ${{ subnet }} | ${subnet} | ${'selected sybnet payload'} `(`$mutation sets $mutatedProperty to $expectedValueDescription`, data => { const { mutation, mutatedProperty, payload, expectedValue } = data; diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb index 7d67dc0251ddb1f1937f7fcbf6fbd7e73e683280..c1eaf1d34332ae738036be8294ebd9c8170677f8 100644 --- a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb +++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb @@ -32,7 +32,7 @@ describe Gitlab::BackgroundMigration::LegacyUploadMover do if with_file upload = create(:upload, :with_file, :attachment_upload, params) - model.update(attachment: upload.build_uploader) + model.update(attachment: upload.retrieve_uploader) model.attachment.upload else create(:upload, :attachment_upload, params) diff --git a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb index ed8cbfeb11f20e4af99080af00a1628c37fe020e..cabca3dbef908272ed1938ccff048d4cd08df763 100644 --- a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb +++ b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb @@ -24,7 +24,7 @@ describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do if with_file upload = create(:upload, :with_file, :attachment_upload, params) - model.update(attachment: upload.build_uploader) + model.update(attachment: upload.retrieve_uploader) model.attachment.upload else create(:upload, :attachment_upload, params) diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb index 792117e1df121cfa932ce678a9021dbee22a54fa..f13f639d6b7513175ed33578f8b1bbacdb73862a 100644 --- a/spec/lib/gitlab/import_export/uploads_manager_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb @@ -83,7 +83,7 @@ describe Gitlab::ImportExport::UploadsManager do it 'restores the file' do manager.restore - expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt') + expect(project.uploads.map { |u| u.retrieve_uploader.filename }).to include('dummy.txt') end end end diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb index 6072f18b8c7e89f0f5d044f62b6b8a2590a57faa..e2e8204b2fa1363217d0cbb44c0582364e599ffd 100644 --- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb @@ -27,7 +27,7 @@ describe Gitlab::ImportExport::UploadsRestorer do it 'copies the uploads to the project path' do subject.restore - expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt') + expect(project.uploads.map { |u| u.retrieve_uploader.filename }).to include('dummy.txt') end end @@ -43,7 +43,7 @@ describe Gitlab::ImportExport::UploadsRestorer do it 'copies the uploads to the project path' do subject.restore - expect(project.uploads.map { |u| u.build_uploader.filename }).to include('dummy.txt') + expect(project.uploads.map { |u| u.retrieve_uploader.filename }).to include('dummy.txt') end end end diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb index f882dbbdb5caf084dde7c6e35ad1d63960104802..11e430e0be43f58be0ccf8c1e28f5290d7628b0b 100644 --- a/spec/lib/gitlab/sanitizers/exif_spec.rb +++ b/spec/lib/gitlab/sanitizers/exif_spec.rb @@ -58,7 +58,7 @@ describe Gitlab::Sanitizers::Exif do end describe '#clean' do - let(:uploader) { create(:upload, :with_file, :issuable_upload).build_uploader } + let(:uploader) { create(:upload, :with_file, :issuable_upload).retrieve_uploader } context "no dry run" do it "removes exif from the image" do diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index ccb5cb3aa431b0b91c0893713349c8965ea27fdc..7e318017a0510ffe24648729744b685e638d8a0d 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -71,26 +71,30 @@ describe Gitlab do end describe '.com?' do + before do + Thread.current[:is_com] = nil + end + it 'is true when on GitLab.com' do - stub_config_setting(url: 'https://gitlab.com') + allow(LightSettings).to receive(:host).and_return('gitlab.com') expect(described_class.com?).to eq true end it 'is true when on staging' do - stub_config_setting(url: 'https://staging.gitlab.com') + allow(LightSettings).to receive(:host).and_return('staging.gitlab.com') expect(described_class.com?).to eq true end it 'is true when on other gitlab subdomain' do - stub_config_setting(url: 'https://example.gitlab.com') + allow(LightSettings).to receive(:host).and_return('example.gitlab.com') expect(described_class.com?).to eq true end it 'is false when not on GitLab.com' do - stub_config_setting(url: 'http://example.com') + allow(LightSettings).to receive(:host).and_return('example.com') expect(described_class.com?).to eq false end diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb index a8957bbfdd0b643d20e0ae5bf5d3084a70258d63..d49b71db5f840bc55aa05db98167908cb8eb5a9e 100644 --- a/spec/models/commit_collection_spec.rb +++ b/spec/models/commit_collection_spec.rb @@ -149,6 +149,17 @@ describe CommitCollection do collection.enrich! end + + it 'returns the original commit if the commit could not be lazy loaded' do + collection = described_class.new(project, [hash_commit]) + unexisting_lazy_commit = Commit.lazy(project, Gitlab::Git::BLANK_SHA) + + expect(Commit).to receive(:lazy).with(project, hash_commit.id).and_return(unexisting_lazy_commit) + + collection.enrich! + + expect(collection.commits).to contain_exactly(hash_commit) + end end end diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb index d97bb8cfb906a097771687d1127b81502edb00d3..03434c952180f8abb2c7dd8adfcaa276156dc976 100644 --- a/spec/models/upload_spec.rb +++ b/spec/models/upload_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Upload do - describe 'assocations' do + describe 'associations' do it { is_expected.to belong_to(:model) } end @@ -107,6 +107,52 @@ describe Upload do end end + describe '#build_uploader' do + it 'returns a uploader object with current upload associated with it' do + subject = build(:upload) + uploader = subject.build_uploader + + expect(uploader.upload).to eq(subject) + expect(uploader.mounted_as).to eq(subject.send(:mount_point)) + expect(uploader.file).to be_nil + end + end + + describe '#retrieve_uploader' do + it 'returns a uploader object with current uploader associated with and cache retrieved' do + subject = build(:upload) + uploader = subject.retrieve_uploader + + expect(uploader.upload).to eq(subject) + expect(uploader.mounted_as).to eq(subject.send(:mount_point)) + expect(uploader.file).not_to be_nil + end + end + + describe '#needs_checksum?' do + context 'with local storage' do + it 'returns true when no checksum exists' do + subject = create(:upload, :with_file, checksum: nil) + + expect(subject.needs_checksum?).to be_truthy + end + + it 'returns false when checksum is already present' do + subject = create(:upload, :with_file, checksum: 'something') + + expect(subject.needs_checksum?).to be_falsey + end + end + + context 'with remote storage' do + subject { build(:upload, :object_storage) } + + it 'returns false' do + expect(subject.needs_checksum?).to be_falsey + end + end + end + describe '#exist?' do it 'returns true when the file exists' do upload = described_class.new(path: __FILE__, store: ObjectStorage::Store::LOCAL) diff --git a/spec/models/uploads/fog_spec.rb b/spec/models/uploads/fog_spec.rb index 4a44cf5ab0fb32d8e869f274f4dc38bb88034596..b93d9449da9ae7644f30006c05718e9148f9d202 100644 --- a/spec/models/uploads/fog_spec.rb +++ b/spec/models/uploads/fog_spec.rb @@ -44,7 +44,7 @@ describe Uploads::Fog do subject { data_store.delete_keys(keys) } before do - uploads.each { |upload| upload.build_uploader.migrate!(2) } + uploads.each { |upload| upload.retrieve_uploader.migrate!(2) } end it 'deletes multiple data' do diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb index 4bc22861d583e56c594af87d28be25c654e9c419..0b4ab9941fc35d18a95e7deb06e6b7386a803903 100644 --- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb @@ -104,7 +104,7 @@ shared_examples 'handle uploads' do context "when neither the uploader nor the model exists" do before do - allow_any_instance_of(Upload).to receive(:build_uploader).and_return(nil) + allow_any_instance_of(Upload).to receive(:retrieve_uploader).and_return(nil) allow(controller).to receive(:find_model).and_return(nil) end diff --git a/spec/support/shared_examples/models/with_uploads_shared_examples.rb b/spec/support/shared_examples/models/with_uploads_shared_examples.rb index eb1ade030171692b2ebcdc1f29f8d087125122f5..822836c771e16d529e66035633e6171a69090f50 100644 --- a/spec/support/shared_examples/models/with_uploads_shared_examples.rb +++ b/spec/support/shared_examples/models/with_uploads_shared_examples.rb @@ -41,7 +41,8 @@ shared_examples_for 'model with uploads' do |supports_fileuploads| end it 'deletes remote files' do - expect_any_instance_of(Uploads::Fog).to receive(:delete_keys).with(uploads.map(&:path)) + expected_array = array_including(*uploads.map(&:path)) + expect_any_instance_of(Uploads::Fog).to receive(:delete_keys).with(expected_array) model_object.destroy end diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb index 04206de3dc64e2d2fa0dc8140c095910e2b46383..3c14edc7e0e25db44abe8eeabe58e3630ba6b5a1 100644 --- a/spec/uploaders/file_uploader_spec.rb +++ b/spec/uploaders/file_uploader_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe FileUploader do let(:group) { create(:group, name: 'awesome') } let(:project) { create(:project, :legacy_storage, namespace: group, name: 'project') } - let(:uploader) { described_class.new(project) } + let(:uploader) { described_class.new(project, :avatar) } let(:upload) { double(model: project, path: 'secret/foo.jpg') } subject { uploader } @@ -184,6 +184,14 @@ describe FileUploader do end end + describe '#replace_file_without_saving!' do + let(:replacement) { Tempfile.create('replacement.jpg') } + + it 'replaces an existing file without changing its metadata' do + expect { subject.replace_file_without_saving! CarrierWave::SanitizedFile.new(replacement) }.not_to change { subject.upload } + end + end + context 'when remote file is used' do let(:temp_file) { Tempfile.new("test") } diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb index 3bee4875348862de9e81241e7df26cbdebabee5e..4425dd947c03d888526a8bc28b0233c9a9602b63 100644 --- a/spec/uploaders/gitlab_uploader_spec.rb +++ b/spec/uploaders/gitlab_uploader_spec.rb @@ -69,6 +69,16 @@ describe GitlabUploader do end end + describe '#replace_file_without_saving!' do + it 'allows file to be replaced without triggering any callbacks' do + new_file = CarrierWave::SanitizedFile.new(Tempfile.new) + + expect(subject).not_to receive(:with_callbacks) + + subject.replace_file_without_saving!(new_file) + end + end + describe '#open' do context 'when trace is stored in File storage' do context 'when file exists' do diff --git a/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb index 6c2544d2efdf312cf7e334cf50ea779a2baaa8f0..97e8a43f7fd4fc064ca976e56cd23e46aa51a835 100644 --- a/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb +++ b/spec/uploaders/workers/object_storage/migrate_uploads_worker_spec.rb @@ -42,33 +42,23 @@ describe ObjectStorage::MigrateUploadsWorker, :sidekiq do end describe '.sanity_check!' do - shared_examples 'raises a SanityCheckError' do + shared_examples 'raises a SanityCheckError' do |expected_message| let(:mount_point) { nil } it do expect { described_class.sanity_check!(uploads, model_class, mount_point) } - .to raise_error(described_class::SanityCheckError) + .to raise_error(described_class::SanityCheckError).with_message(expected_message) end end - before do - stub_const("WrongModel", Class.new) - end - context 'uploader types mismatch' do let!(:outlier) { create(:upload, uploader: 'GitlabUploader') } - include_examples 'raises a SanityCheckError' - end - - context 'model types mismatch' do - let!(:outlier) { create(:upload, model_type: 'WrongModel') } - - include_examples 'raises a SanityCheckError' + include_examples 'raises a SanityCheckError', /Multiple uploaders found/ end context 'mount point not found' do - include_examples 'raises a SanityCheckError' do + include_examples 'raises a SanityCheckError', /Mount point [a-z:]+ not found in/ do let(:mount_point) { :potato } end end