From e23f78caebec15965a16330e0a000622ab9fd4aa Mon Sep 17 00:00:00 2001 From: Anton Chuchkalov Date: Wed, 25 Nov 2015 18:37:47 +0300 Subject: [PATCH 1/7] update field validators in stacks, stack_templates and cloud deploy envs --- .../models/deploy_env/cloud_deploy_env.rb | 3 +-- .../stack_template/stack_template_base.rb | 18 +++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/devops-service/db/mongo/models/deploy_env/cloud_deploy_env.rb b/devops-service/db/mongo/models/deploy_env/cloud_deploy_env.rb index a19abcc..c8dbc39 100644 --- a/devops-service/db/mongo/models/deploy_env/cloud_deploy_env.rb +++ b/devops-service/db/mongo/models/deploy_env/cloud_deploy_env.rb @@ -11,13 +11,12 @@ module Devops module Model class CloudDeployEnv < DeployEnvBase -# attr_accessor :flavor, :image, :subnets, :groups, :stack_template + attr_accessor :flavor, :image, :subnets, :groups, :stack_template set_validators ::Validators::DeployEnv::Flavor, ::Validators::DeployEnv::Image, ::Validators::DeployEnv::Groups, ::Validators::DeployEnv::StackTemplate -# set_validators ::Validators::DeployEnv::CloudParameters set_field_validators :flavor, [::Validators::FieldValidator::Nil, ::Validators::FieldValidator::FieldType::String, ::Validators::FieldValidator::Flavor], order: 2 diff --git a/devops-service/db/mongo/models/stack_template/stack_template_base.rb b/devops-service/db/mongo/models/stack_template/stack_template_base.rb index 2bdfb1e..58ebc2c 100644 --- a/devops-service/db/mongo/models/stack_template/stack_template_base.rb +++ b/devops-service/db/mongo/models/stack_template/stack_template_base.rb @@ -17,21 +17,21 @@ module Devops owner: {type: String, empty: false} set_field_validators :id, [::Validators::FieldValidator::NotNil, - ::Validators::FieldValidator::FieldType::String, - ::Validators::FieldValidator::NotEmpty,] - ::Validators::FieldValidator::Name + ::Validators::FieldValidator::FieldType::String, + ::Validators::FieldValidator::NotEmpty, + ::Validators::FieldValidator::Name] set_field_validators :provider, [::Validators::FieldValidator::NotNil, - ::Validators::FieldValidator::FieldType::String, - ::Validators::FieldValidator::NotEmpty] + ::Validators::FieldValidator::FieldType::String, + ::Validators::FieldValidator::NotEmpty] set_field_validators :template_body, [::Validators::FieldValidator::NotNil, - ::Validators::FieldValidator::FieldType::String, - ::Validators::FieldValidator::NotEmpty] + ::Validators::FieldValidator::FieldType::String, + ::Validators::FieldValidator::NotEmpty] set_field_validators :owner, [::Validators::FieldValidator::NotNil, - ::Validators::FieldValidator::FieldType::String, - ::Validators::FieldValidator::NotEmpty] + ::Validators::FieldValidator::FieldType::String, + ::Validators::FieldValidator::NotEmpty] set_validators ::Validators::StackTemplate::TemplateContent From b65dca60a9e0d37ebc142c037218afcc24360e1a Mon Sep 17 00:00:00 2001 From: Anton Chuchkalov Date: Fri, 20 Nov 2015 18:31:54 +0300 Subject: [PATCH 2/7] specs for several connectors --- devops-service/Gemfile | 2 +- devops-service/Gemfile.lock | 2 +- devops-service/db/mongo/connectors/filter.rb | 17 +- .../connectors/helpers/update_command.rb | 14 +- devops-service/db/mongo/connectors/project.rb | 20 +- devops-service/db/mongo/models/project.rb | 21 +- .../stack_template/stack_template_factory.rb | 2 + .../spec/connectors/filter_connector_spec.rb | 90 ++++++ .../spec/connectors/image_connector_spec.rb | 37 +++ .../spec/connectors/key_connector_spec.rb | 62 ++++ .../spec/connectors/project_connector_spec.rb | 295 ++++++++++++++++++ .../connectors/shared_connectors_context.rb | 22 ++ .../stack_template_connector_spec.rb | 42 +++ .../spec/connectors/tester_connector/base.rb | 53 ++++ .../connectors/tester_connector/filter.rb | 6 + .../spec/connectors/tester_connector/image.rb | 6 + .../spec/connectors/tester_connector/key.rb | 6 + .../connectors/tester_connector/project.rb | 6 + .../tester_connector/stack_template.rb | 6 + devops-service/spec/factories/key.rb | 1 + devops-service/spec/factories/project.rb | 12 + devops-service/spec/factories/report.rb | 4 +- .../models/deploy_env/deploy_env_ec2_spec.rb | 14 +- .../deploy_env/deploy_env_openstack_spec.rb | 14 +- devops-service/spec/models/project_spec.rb | 49 +++ devops-service/spec/models/server_spec.rb | 3 +- .../spec/models/shared_models_context.rb | 13 + devops-service/spec/spec_helper.rb | 8 + devops-service/spec/support/array_matcher.rb | 13 + .../spec/support/shared_connectors_specs.rb | 127 ++++++++ devops-service/spec/support/spec_support.rb | 8 + 31 files changed, 909 insertions(+), 66 deletions(-) create mode 100644 devops-service/spec/connectors/filter_connector_spec.rb create mode 100644 devops-service/spec/connectors/image_connector_spec.rb create mode 100644 devops-service/spec/connectors/key_connector_spec.rb create mode 100644 devops-service/spec/connectors/project_connector_spec.rb create mode 100644 devops-service/spec/connectors/shared_connectors_context.rb create mode 100644 devops-service/spec/connectors/stack_template_connector_spec.rb create mode 100644 devops-service/spec/connectors/tester_connector/base.rb create mode 100644 devops-service/spec/connectors/tester_connector/filter.rb create mode 100644 devops-service/spec/connectors/tester_connector/image.rb create mode 100644 devops-service/spec/connectors/tester_connector/key.rb create mode 100644 devops-service/spec/connectors/tester_connector/project.rb create mode 100644 devops-service/spec/connectors/tester_connector/stack_template.rb create mode 100644 devops-service/spec/factories/project.rb create mode 100644 devops-service/spec/models/project_spec.rb create mode 100644 devops-service/spec/models/shared_models_context.rb create mode 100644 devops-service/spec/support/array_matcher.rb create mode 100644 devops-service/spec/support/shared_connectors_specs.rb diff --git a/devops-service/Gemfile b/devops-service/Gemfile index 96840ba..13b88c3 100644 --- a/devops-service/Gemfile +++ b/devops-service/Gemfile @@ -10,7 +10,7 @@ gem "sinatra-websocket"#, "~>0.3.0" gem "fog", "~>1.20" gem "mixlib-shellout" gem "chef", ">=12" -gem "mongo" +gem "mongo", '1.12.3' gem "bson_ext" gem "multi_json", "1.7.8" gem "sidekiq", "3.2.6" diff --git a/devops-service/Gemfile.lock b/devops-service/Gemfile.lock index b5bf929..31da4a0 100644 --- a/devops-service/Gemfile.lock +++ b/devops-service/Gemfile.lock @@ -344,7 +344,7 @@ DEPENDENCIES httpclient mime-types (~> 1.25.1) mixlib-shellout - mongo + mongo (= 1.12.3) multi_json (= 1.7.8) rack (= 1.5.2) rack-accept-media-types diff --git a/devops-service/db/mongo/connectors/filter.rb b/devops-service/db/mongo/connectors/filter.rb index 8fde9c1..3bea129 100644 --- a/devops-service/db/mongo/connectors/filter.rb +++ b/devops-service/db/mongo/connectors/filter.rb @@ -20,23 +20,24 @@ module Connectors f = collection.find('type' => 'image', 'provider' => provider).to_a.first if f.nil? collection.insert('type' => 'image', 'provider' => provider, 'images' => images) - return images + images else f['images'] |= images collection.update({'_id' => f['_id']}, f) - return f['images'] + f['images'] end end def delete_available_images images, provider return unless images.is_a?(Array) - f = collection.find('type' => 'image', 'provider' => provider).to_a.first - unless f.nil? - f['images'] -= images - collection.update({'_id' => f['_id']}, f) - return f['images'] + filter = collection.find('type' => 'image', 'provider' => provider).to_a.first + if filter + filter['images'] -= images + collection.update({'_id' => filter['_id']}, filter) + filter['images'] + else + [] end - [] end end end diff --git a/devops-service/db/mongo/connectors/helpers/update_command.rb b/devops-service/db/mongo/connectors/helpers/update_command.rb index e1ebec7..674eb60 100644 --- a/devops-service/db/mongo/connectors/helpers/update_command.rb +++ b/devops-service/db/mongo/connectors/helpers/update_command.rb @@ -14,16 +14,10 @@ module Connectors end def update(record) - begin - record.validate! - collection.update({"_id" => record.id}, record.to_mongo_hash) - record - rescue Mongo::OperationFailure => e - if e.message =~ /^11000/ - resource_name = StringHelper.underscore_class(record.class) - raise InvalidRecord.new("Duplicate key error: #{resource_name} with id '#{record.id}'") - end - end + record.validate! + r = collection.update({"_id" => record.id}, record.to_mongo_hash) + raise RecordNotFound.new("'#{record.id}' not found") if r['n'] == 0 + record end end diff --git a/devops-service/db/mongo/connectors/project.rb b/devops-service/db/mongo/connectors/project.rb index 4f10fe2..abe74f9 100644 --- a/devops-service/db/mongo/connectors/project.rb +++ b/devops-service/db/mongo/connectors/project.rb @@ -2,9 +2,7 @@ module Connectors class Project < Base include Helpers::InsertCommand, Helpers::ShowCommand, -# Helpers::ListCommand, Helpers::DeleteCommand -# Helpers::UpdateCommand def initialize(db) @@ -73,7 +71,7 @@ module Connectors res.each do |ar| r[ar['_id']] = ar['envs'] end - return r + r end def projects_by_image(image) @@ -98,10 +96,9 @@ module Connectors project end + # {find all projects with deploy_envs with field=value}, {return deploy_envs with field=value} def projects_and_deploy_envs_by_field field, value - q = {} - q[field] = value - # {find all projects with deploy_envs with field=value}, {return deploy_envs with field=value} + q = {field => value} list( {'deploy_envs' => {'$elemMatch' => q}}, {:fields => {'deploy_envs' => {'$elemMatch' => q}}} ) end @@ -114,12 +111,12 @@ module Connectors end def set_project_env_run_list(project_id, env, run_list) - Helpers::RunList.new(run_list).validate! + ::Validators::Helpers::RunList.new(run_list).validate! set_project_deploy_env_field(project_id, env, {"run_list" => run_list}) end - def set_project_run_list(project_id, env, run_list) - Helpers::RunList.new(run_list).validate! + def set_project_run_list(project_id, run_list) + ::Validators::Helpers::RunList.new(run_list).validate! @collection.update({"_id" => project_id}, {"$set" => {run_list: run_list}}) end @@ -128,17 +125,16 @@ module Connectors end def remove_deploy_env_from_project id, env + raise ArgumentError unless env.is_a?(String) @collection.update({"_id" => id}, {'$pull' => {deploy_envs: {identifier: env}} }) end def project_update_field id, field, value - obj = {} - obj[field] = value + obj = {field => value} @collection.update({"_id" => id}, {'$set' => obj }) end def project_update id, params - #raise InvalidRecord.new("You can not change project name for '#{id}'.") if params["name"] keys = %w(run_list description) params.delete_if{|k,v| !keys.include?(k)} @collection.update({"_id" => id}, {'$set' => params }) diff --git a/devops-service/db/mongo/models/project.rb b/devops-service/db/mongo/models/project.rb index e34a779..8b14847 100644 --- a/devops-service/db/mongo/models/project.rb +++ b/devops-service/db/mongo/models/project.rb @@ -23,16 +23,27 @@ module Devops attr_accessor :id, :deploy_envs, :type, :archived, :description, :run_list - types :id => {:type => String, :empty => false}, - :deploy_envs => {:type => Array, :value_type => false, :empty => false}, - :description => {:type => String, :empty => true, :nil => true}, - :run_list => {:type => Array, :value_type => String, :empty => true, :nil => false} - MULTI_TYPE = "multi" + set_field_validators :id, [::Validators::FieldValidator::NotNil, + ::Validators::FieldValidator::FieldType::String, + ::Validators::FieldValidator::NotEmpty] + + set_field_validators :deploy_envs, [::Validators::FieldValidator::NotNil, + ::Validators::FieldValidator::FieldType::Array, + ::Validators::FieldValidator::NotEmpty] + + set_field_validators :description, [::Validators::FieldValidator::Nil, + ::Validators::FieldValidator::FieldType::String] + + set_field_validators :run_list, [::Validators::FieldValidator::NotNil, + ::Validators::FieldValidator::FieldType::Array, + ::Validators::FieldValidator::RunList] + set_validators ::Validators::DeployEnv::RunList, ::Validators::DeployEnv::DeployEnvs + def self.fields ["deploy_envs", "type", "description"] end diff --git a/devops-service/db/mongo/models/stack_template/stack_template_factory.rb b/devops-service/db/mongo/models/stack_template/stack_template_factory.rb index c085196..2094da3 100644 --- a/devops-service/db/mongo/models/stack_template/stack_template_factory.rb +++ b/devops-service/db/mongo/models/stack_template/stack_template_factory.rb @@ -1,6 +1,8 @@ require_relative "stack_template_base" require_relative "stack_template_openstack" require_relative "stack_template_ec2" +require 'providers/openstack' +require 'providers/ec2' module Devops module Model diff --git a/devops-service/spec/connectors/filter_connector_spec.rb b/devops-service/spec/connectors/filter_connector_spec.rb new file mode 100644 index 0000000..2850835 --- /dev/null +++ b/devops-service/spec/connectors/filter_connector_spec.rb @@ -0,0 +1,90 @@ +require 'db/mongo/connectors/filter' +require 'spec/connectors/tester_connector/filter' +require_relative 'shared_connectors_context' + +RSpec.describe Connectors::Filter, type: :connector do + set_tester_connector TesterConnector::Filter + include_context 'connectors' + let(:provider) {'ec2'} + + describe '#available_images' do + subject { @connector.available_images(provider) } + + context 'when there is no filter for given provider' do + it 'returns empty array' do + expect(subject).to eq [] + end + + context 'when there is filter for given provider but for another type' do + it 'returns empty array' do + test_filter = {provider: provider, type: 'foo', images: ['foo', 'bar']} + @tester_connector.create(test_filter) do + expect(subject).to eq [] + end + end + end + end + + context 'when there is filter for given provider' do + it "returns array with images' names" do + test_filter = {provider: provider, type: 'image', images: ['foo', 'bar']} + @tester_connector.create(test_filter) do + expect(subject).to match_array ['foo', 'bar'] + end + end + end + end + + describe '#add_available_images', cleanup_after: :each do + subject { @connector.add_available_images(['foo'], provider) } + + it "do nothing if images is not Array" do + expect { + result = @connector.add_available_images('foo', provider) + expect(result).to be_nil + }.not_to change {@tester_connector.list} + end + + it 'creates filter if there is no one' do + expect(@tester_connector.list.empty?).to be true + expect(subject).to match_array ['foo'] + expect(@tester_connector.list.empty?).to be false + end + + it 'updates filter if one already exists' do + @tester_connector.create(provider: provider, type: 'image', images: ['bar']) + expect(subject).to match_array ['bar', 'foo'] + expect(@tester_connector.list.first['images']).to match_array ['bar', 'foo'] + end + + it "doesn't duplicate images in filter" do + @tester_connector.create(provider: provider, type: 'image', images: ['foo']) + expect(subject).to match_array ['foo'] + expect(@tester_connector.list.first['images']).to match_array ['foo'] + end + end + + describe '#delete_available_images' do + subject { @connector.delete_available_images(['foo'], provider) } + after { @tester_connector.cleanup } + let(:create_filter) { @tester_connector.create(provider: provider, type: 'image', images: ['foo', 'bar']) } + + it 'does nothing if images is not Array' do + create_filter + expect { + result = @connector.delete_available_images('foo', provider) + expect(result).to be_nil + }.not_to change{ @tester_connector.list.first['images'] } + end + + it 'removes given images from filter' do + create_filter + expect(subject).to match_array ['bar'] + expect(@tester_connector.list.first['images']).to match_array ['bar'] + end + + it 'returns empty array if there is no filter' do + expect(subject).to eq [] + end + end +end diff --git a/devops-service/spec/connectors/image_connector_spec.rb b/devops-service/spec/connectors/image_connector_spec.rb new file mode 100644 index 0000000..585d728 --- /dev/null +++ b/devops-service/spec/connectors/image_connector_spec.rb @@ -0,0 +1,37 @@ +require 'db/mongo/connectors/image' +require 'spec/connectors/tester_connector/image' +require_relative 'shared_connectors_context' + +RSpec.describe Connectors::Image, type: :connector do + set_tester_connector TesterConnector::Image + include_context 'connectors' + let(:model_class) { Devops::Model::Image } + + include_examples 'mongo connector', { + model_name: :image, + only: [:insert, :show, :update, :delete], + field_to_update: :name + } + + describe '#images', cleanup_after: :each do + subject { @connector.images('ec2') } + + it 'should be empty if collection is empty' do + expect(subject).to eq [] + end + + it "returns array of Model::Key's of given provider" do + @tester_connector.create(provider: 'ec2') + @tester_connector.create(provider: 'openstack') + expect(subject).to be_an_array_of(model_class).and have_size(1) + expect(subject.first.provider).to eq 'ec2' + end + + it 'returns images for both providers if is is unset' do + @tester_connector.create(provider: 'ec2') + @tester_connector.create(provider: 'openstack') + expect(@connector.images.length).to eq 2 + end + end + +end diff --git a/devops-service/spec/connectors/key_connector_spec.rb b/devops-service/spec/connectors/key_connector_spec.rb new file mode 100644 index 0000000..9be46ee --- /dev/null +++ b/devops-service/spec/connectors/key_connector_spec.rb @@ -0,0 +1,62 @@ +require 'db/mongo/connectors/key' +require 'db/mongo/models/key' +require 'spec/connectors/tester_connector/key' +require_relative 'shared_connectors_context' + +RSpec.describe Connectors::Key, type: :connector do + set_tester_connector TesterConnector::Key + include_context 'connectors' + let(:model_class) { Devops::Model::Key } + + include_examples 'mongo connector', model_name: :key, only: [:insert, :list] + + describe '#key' do + context 'when scope was passed' do + subject { @connector.key('foo') } + + it 'raises RecordNotFound when there is no such record' do + expect { subject }.to raise_error(RecordNotFound) + end + + it 'returns record if was found' do + @tester_connector.create(id: 'foo') do + expect(subject).to be_an_instance_of(model_class) + end + end + end + + context 'when scope was passed' do + subject { @connector.key('foo', 'user') } + + it 'raises RecordNotFound when there is no such record' do + expect { subject }.to raise_error(RecordNotFound) + end + + it 'returns record if was found' do + @tester_connector.create(id: 'foo', scope: 'user') do + expect(subject).to be_an_instance_of(model_class) + end + end + end + end + + describe '#key_delete' do + subject { @connector.key_delete('foo') } + + it 'raises RecordNotFound if there is no such record' do + expect{ subject }.to raise_error(RecordNotFound) + end + + it 'remove key in "user" scope' do + @tester_connector.create(id: 'foo', scope: 'user') + subject + expect(@tester_connector.list).to eq [] + end + + it "doesn't remove key in 'system' scope" do + @tester_connector.create(id: 'foo', scope: 'system') do + expect {subject}.to raise_error RecordNotFound + end + end + end +end diff --git a/devops-service/spec/connectors/project_connector_spec.rb b/devops-service/spec/connectors/project_connector_spec.rb new file mode 100644 index 0000000..2b86bd8 --- /dev/null +++ b/devops-service/spec/connectors/project_connector_spec.rb @@ -0,0 +1,295 @@ +require 'db/mongo/connectors/project' +require 'spec/connectors/tester_connector/project' +require_relative 'shared_connectors_context' + +RSpec.describe Connectors::Project, type: :connector do + set_tester_connector TesterConnector::Project + include_context 'connectors' + let(:model_class) { Devops::Model::Project } + + include_examples 'mongo connector', { + model_name: :project, + factory_name: :project, + only: [:insert, :show, :delete], + field_to_update: :deploy_envs + } + + describe '#is_project_exists?' do + subject { @connector.is_project_exists?(build(:project, id: 'foo')) } + it 'returns true if project exists' do + @tester_connector.create(id: 'foo') do + expect(subject).to be true + end + end + it 'returns false if project doesn\'t exists' do + expect(subject).to be false + end + end + + describe '#projects_all' do + it 'returns array of projects' do + @tester_connector.create_list(2) do + result = @connector.projects_all + expect(result).to be_an_array_of(model_class).and have_size(2) + end + end + end + + describe '#projects', cleanup_after: :all do + before(:all) do + @tester_connector.create build(:project, id: 'foo', type: 'multi').to_mongo_hash + @tester_connector.create build(:project, id: 'bar', archived: true).to_mongo_hash + @tester_connector.create build(:project, id: 'baz').to_mongo_hash + end + + it 'returns non archived projects with all params unset' do + expect(@connector.projects).to have_size(2) + end + + it 'returns projects with given ids' do + result = @connector.projects(%w(foo baz)) + expect(result).to be_an_array_of(model_class) + expect(result.map(&:id)).to match_array(%w(foo baz)) + end + + it 'returns multi projects if @type == :multi' do + expect( + @connector.projects(nil, :multi).map(&:id) + ).to match_array ['foo'] + end + + it 'returns only given fields and id if @fields is set' do + result = @connector.projects(nil, nil, [:deploy_envs]) + expect(result.map(&:id).compact).not_to eq [] + expect(result.map(&:deploy_envs).compact).not_to eq [] + expect(result.map(&:description).compact).to eq [] + end + + it 'returns only archived projects if @archived=true' do + result = @connector.projects(nil, nil, [], true) + expect(result.map(&:id)).to match_array(%w(bar)) + end + end + + describe '#project_names_with_envs', cleanup_after: :all do + before(:all) do + @tester_connector.create( + id: 'foo', + deploy_envs: [{identifier: 'env1'}, {identifier: 'env2'}] + ) + @tester_connector.create( + id: 'bar', + deploy_envs: [{identifier: 'env3'}, {identifier: 'env4'}] + ) + end + + it 'returns hash like {"project_name" => ["env1", "env2]}' do + result = @connector.project_names_with_envs + expect(result.keys.sort).to match_array %w(foo bar) + expect(result['foo'].sort).to match_array %w(env1 env2) + expect(result['bar'].sort).to match_array %w(env3 env4) + end + + it 'returns only projects with given names' do + expect(@connector.project_names_with_envs(['bar']).keys).to match_array %w(bar) + end + end + + describe '#projects_by_image', cleanup_after: :each do + def env(image) + {image: image, provider: 'ec2'} + end + + it 'returns projects deploy_envs of which have given image' do + @tester_connector.create(id: 'foo', deploy_envs: [env('a'), env('b')]) + @tester_connector.create(id: 'bar', deploy_envs: [env('a'), env('a')]) + @tester_connector.create(id: 'baz', deploy_envs: [env('b'), env('b')]) + result = @connector.projects_by_image('a') + expect(result).to be_an_array_of(model_class) + expect(result.map(&:id)).to match_array %w(foo bar) + end + end + + describe '#projects_by_user', cleanup_after: :each do + def env(users) + {users: users, provider: 'ec2'} + end + + it 'returns projects deploy_envs of which have given user' do + @tester_connector.create(id: 'foo', deploy_envs: [ env(%w(user1 user2)) ]) + @tester_connector.create(id: 'bar', deploy_envs: [ env(%w(user1 user1)) ]) + @tester_connector.create(id: 'baz', deploy_envs: [ env(%w(user2 user2)) ]) + @tester_connector.create(id: 'baf', deploy_envs: [ env(%w(user1)), env(%w(user2)) ]) + result = @connector.projects_by_user('user1') + expect(result).to be_an_array_of(model_class) + expect(result.map(&:id)).to match_array %w(foo bar baf) + end + end + + describe '#archive_project' do + it 'sets archived to true' do + @tester_connector.create(id: 'foo') do + @connector.archive_project('foo') + expect(@tester_connector.show('foo')).to include('archived' => true) + end + end + end + + describe '#unarchive_project' do + it 'unsets archived' do + @tester_connector.create(id: 'foo', archived: true) do + @connector.unarchive_project('foo') + expect(@tester_connector.show('foo')).not_to include('archived') + end + end + end + + describe '#check_project_auth', cleanup_after: :all do + before(:all) do + @tester_connector.create(id: 'foo', deploy_envs: [ + {identifier: 'bar', provider: 'ec2', users: %w(user1)} + ]) + end + + it "raises InvalidPrivileges if given env users don't include given user" do + expect { + @connector.check_project_auth('foo', 'bar', 'user2') + }.to raise_error(InvalidPrivileges) + end + + it "returns project if env's users include given user" do + result = @connector.check_project_auth('foo', 'bar', 'user1') + expect(result).to be_an_instance_of(model_class) + expect(result.id).to eq 'foo' + end + end + + describe '#projects_and_deploy_envs_by_field', cleanup_after: :all do + def env(field, value) + {provider: 'ec2', field => value} + end + before(:all) do + @tester_connector.create(id: 'foo', deploy_envs: [ + env(:image, 'image1'), env(:stack_template, 'template1') + ]) + end + + it 'returns projects with deploy envs containing given field with given value' do + expect( + @connector.projects_and_deploy_envs_by_field(:image, 'image1').map(&:id) + ).to match_array %w(foo) + + expect( + @connector.projects_and_deploy_envs_by_field(:stack_template, 'template1').map(&:id) + ).to match_array %w(foo) + end + end + + describe '#set_project_deploy_env_field', cleanup_after: :each do + it 'updates given env from given field_value_hash' do + @tester_connector.create(id: 'foo', deploy_envs: [ + {identifier: 'bar', provider: 'ec2'} + ]) + @connector.set_project_deploy_env_field('foo', 'bar', image: 'a', stack_template: 'b') + updated_project = @tester_connector.show('foo') + expect(updated_project['deploy_envs'].first['image']).to eq 'a' + expect(updated_project['deploy_envs'].first['stack_template']).to eq 'b' + end + end + + describe '#set_project_env_run_list', cleanup_after: :all do + before(:all) do + @tester_connector.create(id: 'foo', deploy_envs: [{identifier: 'bar', provider: 'ec2'}]) + end + before { allow_any_instance_of(Validators::Helpers::RunList).to receive(:validate!) } + + it 'validates run list' do + run_list = [] + expect_any_instance_of(Validators::Helpers::RunList).to receive(:validate!) + @connector.set_project_env_run_list('foo', 'bar', run_list) + end + + it "updates env's RunList" do + run_list = ['role[foo]'] + @connector.set_project_env_run_list('foo', 'bar', run_list) + expect(@tester_connector.show('foo')['deploy_envs'].first['run_list']).to eq run_list + end + end + + describe '#set_project_env_run_list', cleanup_after: :all do + before(:all) do + @tester_connector.create(id: 'foo') + end + before { allow_any_instance_of(Validators::Helpers::RunList).to receive(:validate!) } + + it 'validates run list' do + run_list = [] + expect_any_instance_of(Validators::Helpers::RunList).to receive(:validate!) + @connector.set_project_run_list('foo', run_list) + end + + it "updates project's RunList" do + run_list = ['role[foo]'] + @connector.set_project_run_list('foo', run_list) + expect(@tester_connector.show('foo')['run_list']).to eq run_list + end + end + + describe '#add_deploy_env_to_project' do + it 'adds env to project' do + @tester_connector.create(id: 'foo', deploy_envs: []) do + @connector.add_deploy_env_to_project('foo', build(:deploy_env)) + expect(@tester_connector.show('foo')['deploy_envs']).not_to be_empty + end + end + end + + describe '#remove_deploy_env_from_project', cleanup_after: :each do + before { @tester_connector.create(id: 'foo', deploy_envs: [{identifier: 'bar'}]) } + + it "removes env from project" do + @connector.remove_deploy_env_from_project('foo', 'bar') + expect(@tester_connector.show('foo')['deploy_envs']).to be_empty + end + + it 'raises ArgumentError if given env is not a String' do + env = build(:deploy_env_ec2, identifier: 'bar') + expect { @connector.remove_deploy_env_from_project('foo', env) }.to raise_error(ArgumentError) + end + end + + describe '#project_update_field', cleanup_after: :each do + before { @tester_connector.create(id: 'foo', description: 'desc', run_list: []) } + subject { @connector.project_update_field('foo', 'run_list', ['role[a]']) } + + it 'updates given field of given project' do + expect {subject}.to change { @tester_connector.show('foo')['run_list'] }.to ['role[a]'] + end + + it "doesn't affect other fields" do + expect {subject}.not_to change { @tester_connector.show('foo')['desc'] } + end + end + + describe '#project_update', cleanup_after: :each do + before { @tester_connector.create(id: 'foo', deploy_envs: [], description: 'desc', run_list: []) } + subject { + attrs = {'id' => 'foo2', 'deploy_envs' => [build(:deploy_env)], 'description' => 'desc2', 'run_list' => ['role[asd]']} + @connector.project_update('foo', attrs) + } + let(:updated_project) { @tester_connector.show('foo') } + + it 'can update run_list and description' do + subject + expect(updated_project['run_list']).to eq ['role[asd]'] + expect(updated_project['description']).to eq 'desc2' + end + + it 'can not update other fields' do + subject + expect(updated_project['_id']).to eq 'foo' + expect(updated_project['deploy_envs']).to eq [] + end + end + +end diff --git a/devops-service/spec/connectors/shared_connectors_context.rb b/devops-service/spec/connectors/shared_connectors_context.rb new file mode 100644 index 0000000..3c0fedb --- /dev/null +++ b/devops-service/spec/connectors/shared_connectors_context.rb @@ -0,0 +1,22 @@ +def set_tester_connector(klass) + define_method :tester_connector_class do + klass + end +end + +RSpec.shared_context 'connectors' do + before(:all) do + @connector = described_class.new(SpecSupport.db) + @tester_connector = tester_connector_class.new + @tester_connector.cleanup + end + after(:all){ @tester_connector.cleanup } +end + +RSpec.shared_context 'cleanup after all', cleanup_after: :all do + after(:all){ @tester_connector.cleanup } +end + +RSpec.shared_context 'cleanup after each', cleanup_after: :each do + after { @tester_connector.cleanup } +end \ No newline at end of file diff --git a/devops-service/spec/connectors/stack_template_connector_spec.rb b/devops-service/spec/connectors/stack_template_connector_spec.rb new file mode 100644 index 0000000..edb9fe7 --- /dev/null +++ b/devops-service/spec/connectors/stack_template_connector_spec.rb @@ -0,0 +1,42 @@ +require 'db/mongo/connectors/stack_template' +require 'spec/connectors/tester_connector/stack_template' +require_relative 'shared_connectors_context' + +RSpec.describe Connectors::StackTemplate, type: :connector do + set_tester_connector TesterConnector::StackTemplate + include_context 'connectors' + let(:model_class) { Devops::Model::StackTemplateEc2 } + + include_examples 'mongo connector', { + model_name: :stack_template, + factory_name: :stack_template_ec2, + only: [:insert, :show, :update, :delete], + field_to_update: :owner + } + + describe '#stack_templates' do + subject { @connector.stack_templates('ec2') } + + it 'should be empty if collection is empty' do + expect(subject).to eq [] + end + + it "returns array of stack_templates of given provider" do + @tester_connector.create(provider: 'ec2') + @tester_connector.create(provider: 'openstack') + expect(subject).to be_a(Array) + expect(subject.length).to eq 1 + expect(subject.first).to be_an_instance_of(model_class) + expect(subject.first.provider).to eq 'ec2' + @tester_connector.cleanup + end + + it 'returns stack_templates for both providers if is is unset' do + @tester_connector.create(provider: 'ec2') + @tester_connector.create(provider: 'openstack') + expect(@connector.stack_templates.length).to eq 2 + @tester_connector.cleanup + end + end + +end diff --git a/devops-service/spec/connectors/tester_connector/base.rb b/devops-service/spec/connectors/tester_connector/base.rb new file mode 100644 index 0000000..4f595ab --- /dev/null +++ b/devops-service/spec/connectors/tester_connector/base.rb @@ -0,0 +1,53 @@ +module TesterConnector + class Base + attr_reader :collection + + def initialize + collection_name = self.class.name.demodulize.underscore.pluralize + @collection = SpecSupport.db.collection(collection_name) + @next_id = 1 + end + + def create(hash={}) + collection.insert(create_params(hash)) + if block_given? + yield + cleanup + end + end + + def create_list(size=2, hash={}) + size.times { create(hash) } + if block_given? + yield + cleanup + end + end + + def list + collection.find().to_a + end + + def show(id) + collection.find({'_id' => id}).to_a.first + end + + def cleanup + collection.remove + end + + private + + def create_params(hash) + params = hash.dup + if params[:id] + params['_id'] = params.delete(:id) + end + unless params['_id'] + params['_id'] = @next_id + @next_id += 1 + end + params + end + end +end \ No newline at end of file diff --git a/devops-service/spec/connectors/tester_connector/filter.rb b/devops-service/spec/connectors/tester_connector/filter.rb new file mode 100644 index 0000000..c8cd04f --- /dev/null +++ b/devops-service/spec/connectors/tester_connector/filter.rb @@ -0,0 +1,6 @@ +require_relative 'base' + +module TesterConnector + class Filter < Base + end +end \ No newline at end of file diff --git a/devops-service/spec/connectors/tester_connector/image.rb b/devops-service/spec/connectors/tester_connector/image.rb new file mode 100644 index 0000000..5f7ac86 --- /dev/null +++ b/devops-service/spec/connectors/tester_connector/image.rb @@ -0,0 +1,6 @@ +require_relative 'base' + +module TesterConnector + class Image < Base + end +end \ No newline at end of file diff --git a/devops-service/spec/connectors/tester_connector/key.rb b/devops-service/spec/connectors/tester_connector/key.rb new file mode 100644 index 0000000..f9dd014 --- /dev/null +++ b/devops-service/spec/connectors/tester_connector/key.rb @@ -0,0 +1,6 @@ +require_relative 'base' + +module TesterConnector + class Key < Base + end +end \ No newline at end of file diff --git a/devops-service/spec/connectors/tester_connector/project.rb b/devops-service/spec/connectors/tester_connector/project.rb new file mode 100644 index 0000000..36f45e2 --- /dev/null +++ b/devops-service/spec/connectors/tester_connector/project.rb @@ -0,0 +1,6 @@ +require_relative 'base' + +module TesterConnector + class Project < Base + end +end \ No newline at end of file diff --git a/devops-service/spec/connectors/tester_connector/stack_template.rb b/devops-service/spec/connectors/tester_connector/stack_template.rb new file mode 100644 index 0000000..38383aa --- /dev/null +++ b/devops-service/spec/connectors/tester_connector/stack_template.rb @@ -0,0 +1,6 @@ +require_relative 'base' + +module TesterConnector + class StackTemplate < Base + end +end \ No newline at end of file diff --git a/devops-service/spec/factories/key.rb b/devops-service/spec/factories/key.rb index 48b0262..06fb96e 100644 --- a/devops-service/spec/factories/key.rb +++ b/devops-service/spec/factories/key.rb @@ -2,6 +2,7 @@ require 'db/mongo/models/key' FactoryGirl.define do factory :key, class: Devops::Model::Key do + id 'user_key' path SpecSupport::BLANK_FILE scope 'user' end diff --git a/devops-service/spec/factories/project.rb b/devops-service/spec/factories/project.rb new file mode 100644 index 0000000..561085f --- /dev/null +++ b/devops-service/spec/factories/project.rb @@ -0,0 +1,12 @@ +require 'db/mongo/models/project' + +FactoryGirl.define do + factory :project, class: Devops::Model::Project do + id 'my_project' + deploy_envs { + [build(:deploy_env_ec2)] + } + run_list [] + description 'desc' + end +end \ No newline at end of file diff --git a/devops-service/spec/factories/report.rb b/devops-service/spec/factories/report.rb index 70395a5..07711f0 100644 --- a/devops-service/spec/factories/report.rb +++ b/devops-service/spec/factories/report.rb @@ -13,8 +13,6 @@ FactoryGirl.define do updated_at Time.now status 'completed' - initialize_with { - new(attributes.stringify_keys) - } + initialize_with { new(attributes.stringify_keys) } end end \ No newline at end of file diff --git a/devops-service/spec/models/deploy_env/deploy_env_ec2_spec.rb b/devops-service/spec/models/deploy_env/deploy_env_ec2_spec.rb index 5b622b9..2d3b2d8 100644 --- a/devops-service/spec/models/deploy_env/deploy_env_ec2_spec.rb +++ b/devops-service/spec/models/deploy_env/deploy_env_ec2_spec.rb @@ -1,19 +1,9 @@ require 'db/mongo/models/deploy_env/deploy_env_ec2' +require_relative '../shared_models_context' RSpec.describe Devops::Model::DeployEnvEc2, type: :model do let(:env) { build(:deploy_env_ec2) } - - before do - allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(ec2)) - allow_any_instance_of(Validators::Helpers::Users).to receive(:available_users).and_return(['root']) - allow_any_instance_of(Validators::DeployEnv::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}]) - allow_any_instance_of(Validators::FieldValidator::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}]) - allow_any_instance_of(Validators::DeployEnv::Groups).to receive(:available_groups).and_return(['default']) - allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}]) - allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}]) - allow_any_instance_of(Validators::DeployEnv::StackTemplate).to receive(:available_stack_templates).and_return([{'id' => 'template'}]) - allow_any_instance_of(Validators::FieldValidator::Image).to receive(:available_images).and_return([{'id' => 'image'}]) - end + include_context 'stubbed calls to connector in env validators' it_behaves_like 'deploy env' it_behaves_like 'cloud deploy env' diff --git a/devops-service/spec/models/deploy_env/deploy_env_openstack_spec.rb b/devops-service/spec/models/deploy_env/deploy_env_openstack_spec.rb index 428241f..08783b6 100644 --- a/devops-service/spec/models/deploy_env/deploy_env_openstack_spec.rb +++ b/devops-service/spec/models/deploy_env/deploy_env_openstack_spec.rb @@ -1,19 +1,9 @@ require 'db/mongo/models/deploy_env/deploy_env_openstack' +require_relative '../shared_models_context' RSpec.describe Devops::Model::DeployEnvOpenstack, type: :model do let(:env) { build(:deploy_env_openstack) } - - before do - allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(openstack)) - allow_any_instance_of(Validators::Helpers::Users).to receive(:available_users).and_return(['root']) - allow_any_instance_of(Validators::DeployEnv::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}]) - allow_any_instance_of(Validators::FieldValidator::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}]) - allow_any_instance_of(Validators::DeployEnv::Groups).to receive(:available_groups).and_return(['default']) - allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}]) - allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}]) - allow_any_instance_of(Validators::DeployEnv::StackTemplate).to receive(:available_stack_templates).and_return([{'id' => 'template'}]) - allow_any_instance_of(Validators::FieldValidator::Image).to receive(:available_images).and_return([{'id' => 'image'}]) - end + include_context 'stubbed calls to connector in env validators' it_behaves_like 'deploy env' it_behaves_like 'cloud deploy env' diff --git a/devops-service/spec/models/project_spec.rb b/devops-service/spec/models/project_spec.rb new file mode 100644 index 0000000..9b19eb2 --- /dev/null +++ b/devops-service/spec/models/project_spec.rb @@ -0,0 +1,49 @@ +require 'db/mongo/models/project' +require_relative 'shared_models_context' + +RSpec.describe Devops::Model::Project, type: :model do + let(:project) { build(:project) } + include_context 'stubbed calls to connector in env validators' + + describe 'validation rules:' do + include_examples 'field type validation', :id, :not_nil, :non_empty_string + include_examples 'field type validation', :deploy_envs, :not_nil, :non_empty_array + include_examples 'field type validation', :description, :maybe_nil, :maybe_empty_string + include_examples 'field type validation', :run_list, :not_nil, :maybe_empty_array, :run_list + + it "isn't valid when has envs with same identifier" do + project.deploy_envs << build(:deploy_env_ec2) + expect(project).not_to be_valid + end + + it "is valid when all envs have uniq identifiers" do + project.deploy_envs << build(:deploy_env_ec2, identifier: 'new') + expect(project).to be_valid + end + + it "isn't valid when at least one of envs isn't valid" do + project.deploy_envs << build(:deploy_env_ec2, identifier: nil) + expect(project).not_to be_valid + end + end + + describe '.fields' do + subject { described_class.fields } + it { should eq %w(deploy_envs type description) } + end + + describe '#initialize' do + it 'sets @type to generic by default' do + expect(described_class.new.type).to eq 'generic' + end + + it 'sets @archived to false by default' do + expect(described_class.new.archived).to eq false + end + + it 'sets run_list to empty_array by default' do + expect(described_class.new.run_list).to eq [] + end + end + +end \ No newline at end of file diff --git a/devops-service/spec/models/server_spec.rb b/devops-service/spec/models/server_spec.rb index f970df8..d49f328 100644 --- a/devops-service/spec/models/server_spec.rb +++ b/devops-service/spec/models/server_spec.rb @@ -24,8 +24,7 @@ RSpec.describe Devops::Model::Server, type: :model do include_examples 'field type validation', :chef_node_name, :not_nil, :maybe_empty_string include_examples 'field type validation', :reserved_by, :not_nil, :maybe_empty_string include_examples 'field type validation', :stack, :maybe_nil, :non_empty_string - include_examples 'field type validation', :run_list, :not_nil, :maybe_empty_array - include_examples 'field type validation', :run_list, :run_list + include_examples 'field type validation', :run_list, :not_nil, :maybe_empty_array, :run_list end describe '#initialize' do diff --git a/devops-service/spec/models/shared_models_context.rb b/devops-service/spec/models/shared_models_context.rb new file mode 100644 index 0000000..51804e0 --- /dev/null +++ b/devops-service/spec/models/shared_models_context.rb @@ -0,0 +1,13 @@ +RSpec.shared_context 'stubbed calls to connector in env validators' do + before do + allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(ec2 openstack)) + allow_any_instance_of(Validators::Helpers::Users).to receive(:available_users).and_return(['root']) + allow_any_instance_of(Validators::DeployEnv::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}]) + allow_any_instance_of(Validators::FieldValidator::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}]) + allow_any_instance_of(Validators::DeployEnv::Groups).to receive(:available_groups).and_return(['default']) + allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}]) + allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}]) + allow_any_instance_of(Validators::DeployEnv::StackTemplate).to receive(:available_stack_templates).and_return([{'id' => 'template'}]) + allow_any_instance_of(Validators::FieldValidator::Image).to receive(:available_images).and_return([{'id' => 'image'}]) + end +end diff --git a/devops-service/spec/spec_helper.rb b/devops-service/spec/spec_helper.rb index 7552bf9..ac74d92 100644 --- a/devops-service/spec/spec_helper.rb +++ b/devops-service/spec/spec_helper.rb @@ -8,6 +8,10 @@ require 'active_support/inflector' root = File.join(File.dirname(__FILE__), "..") $LOAD_PATH.push root unless $LOAD_PATH.include? root +# suppress output +original_stdout = $stdout +$stdout = File.open(File::NULL, "w") + Dir[("./spec/support/**/*.rb")].each { |f| require f } # Factory girl configuration @@ -21,6 +25,10 @@ FactoryGirl.find_definitions RSpec.configure do |config| config.include FactoryGirl::Syntax::Methods + config.after(:all) do + $stdout = original_stdout + end + # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. diff --git a/devops-service/spec/support/array_matcher.rb b/devops-service/spec/support/array_matcher.rb new file mode 100644 index 0000000..34f7376 --- /dev/null +++ b/devops-service/spec/support/array_matcher.rb @@ -0,0 +1,13 @@ +RSpec::Matchers.define :be_an_array_of do |klass| + match do |actual| + actual.class == Array && + actual.all? {|item| item.class == klass} + end +end + +RSpec::Matchers.define :have_size do |length| + match { |actual| actual.length == length } + failure_message do |actual| + "expected #{actual} to have size #{expected} but have #{actual.length}" + end +end \ No newline at end of file diff --git a/devops-service/spec/support/shared_connectors_specs.rb b/devops-service/spec/support/shared_connectors_specs.rb new file mode 100644 index 0000000..2bfe99d --- /dev/null +++ b/devops-service/spec/support/shared_connectors_specs.rb @@ -0,0 +1,127 @@ +RSpec.shared_examples 'mongo connector' do |options| + model_name = options.fetch(:model_name) + factory_name = options[:factory_name] || model_name + commands = options.fetch(:only) + + if commands.include?(:insert) + insert_method_name = "#{model_name}_insert" + + describe "##{insert_method_name}" do + let(:model) { build(factory_name) } + subject { @connector.send(insert_method_name, model) } + + before { allow(model).to receive(:validate!).and_return(true) } + after { @tester_connector.cleanup } + + it 'inserts new record' do + expect {subject}.to change {@tester_connector.list.size}.from(0).to(1) + end + + it 'validates inserted record' do + expect(model).to receive(:validate!) + subject + end + + it "doesn't insert nonvalid records" do + allow(model).to receive(:validate!) { raise InvalidRecord.new('') } + expect {subject}.to raise_error(InvalidRecord) + end + + it 'sets created_at of record' do + expect(model.created_at).to be nil + subject + expect(@tester_connector.list.first['created_at']).not_to be nil + end + + it 'raises error if record with such id already exists' do + @tester_connector.create(id: model.id) + expect {subject}.to raise_error(InvalidRecord) + end + end + end + + if commands.include?(:list) + list_method_name = "#{model_name.to_s.pluralize}" + + describe "##{list_method_name}" do + subject { @connector.send(list_method_name) } + + it 'should be empty if collection is empty' do + expect(subject).to eq [] + end + + it "returns array of #{model_name.to_s.pluralize} if collection isn't empty" do + @tester_connector.create_list(2) do + expect(subject).to be_an_array_of(model_class).and have_size(2) + end + end + end + end + + if commands.include?(:show) + show_method_name = model_name + + describe "##{show_method_name}" do + subject { @connector.send(show_method_name, 'foo') } + + it 'raises RecordNotFound when there is no such record' do + expect { subject }.to raise_error(RecordNotFound) + end + + it 'returns record if was found' do + @tester_connector.create(build(factory_name, id: 'foo').to_mongo_hash) do + expect(subject).to be_an_instance_of(model_class) + end + end + end + end + + if commands.include?(:update) + update_method_name = "#{model_name}_update" + field_to_update = options.fetch(:field_to_update).to_s + + describe "##{update_method_name}" do + let(:model) { build(model_name, field_to_update => 'new_value') } + let(:insert_model_to_update) { @tester_connector.create(attributes_for(factory_name)) } + subject { @connector.send(update_method_name, model) } + before { allow(model).to receive(:validate!).and_return(true) } + after { @tester_connector.cleanup } + + it 'validates updated record' do + insert_model_to_update + expect(model).to receive(:validate!) + subject + end + + it 'updates record' do + insert_model_to_update + subject + updated_value = @tester_connector.list.first[field_to_update] + expect(updated_value).to eq 'new_value' + end + + it 'raises RecordNotFound if there is no such record' do + expect{ subject }.to raise_error(RecordNotFound) + end + end + end + + if commands.include?(:delete) + delete_method_name = "#{model_name}_delete" + describe "##{delete_method_name}" do + let(:model) { build(factory_name) } + let(:insert_model_to_delete) { @tester_connector.create(model.to_mongo_hash) } + subject { @connector.send(delete_method_name, model.id) } + + it 'deletes record' do + insert_model_to_delete + expect {subject}.to change {@tester_connector.list.empty?}.to(true) + @tester_connector.cleanup + end + + it 'raises RecordNotFound if there is no such record' do + expect { subject }.to raise_error(RecordNotFound) + end + end + end +end diff --git a/devops-service/spec/support/spec_support.rb b/devops-service/spec/support/spec_support.rb index 159e1cf..2c5a04e 100644 --- a/devops-service/spec/support/spec_support.rb +++ b/devops-service/spec/support/spec_support.rb @@ -2,10 +2,18 @@ require 'core/devops-application' module SpecSupport BLANK_FILE = File.join(Devops::Application.root, 'spec/support/blank_file') + TEST_DB = 'devops_test' def stub_loggers allow(DevopsLogger).to receive_message_chain('logger.debug') allow(DevopsLogger).to receive_message_chain('logger.info') allow(DevopsLogger).to receive_message_chain('logger.error') end + + def self.db + @db ||= begin + require 'mongo' + Mongo::MongoClient.new.db(TEST_DB) + end + end end \ No newline at end of file From 82ad9cac0dedeb3fe9223736c8357357c04d9fb7 Mon Sep 17 00:00:00 2001 From: Anton Chuchkalov Date: Tue, 1 Dec 2015 17:40:37 +0300 Subject: [PATCH 3/7] add specs config --- devops-service/spec/support/config.yml | 7 +++++ devops-service/spec/support/spec_support.rb | 33 +++++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 devops-service/spec/support/config.yml diff --git a/devops-service/spec/support/config.yml b/devops-service/spec/support/config.yml new file mode 100644 index 0000000..fe15e15 --- /dev/null +++ b/devops-service/spec/support/config.yml @@ -0,0 +1,7 @@ +# only :db key is required +mongo: + :db: devops_test + :host: + :port: + :user: + :password: diff --git a/devops-service/spec/support/spec_support.rb b/devops-service/spec/support/spec_support.rb index 2c5a04e..664927f 100644 --- a/devops-service/spec/support/spec_support.rb +++ b/devops-service/spec/support/spec_support.rb @@ -1,8 +1,9 @@ -require 'core/devops-application' +require 'yaml' module SpecSupport - BLANK_FILE = File.join(Devops::Application.root, 'spec/support/blank_file') - TEST_DB = 'devops_test' + ROOT = File.join(__dir__, '../../') + BLANK_FILE = File.join(ROOT, 'spec/support/blank_file') + def stub_loggers allow(DevopsLogger).to receive_message_chain('logger.debug') @@ -10,10 +11,32 @@ module SpecSupport allow(DevopsLogger).to receive_message_chain('logger.error') end - def self.db + def self.db_params @db ||= begin + conf = config['mongo'] + db_name = conf.fetch(:db) + [db_name, conf[:host], conf[:port], conf[:user], conf[:password]] + end + end + + def self.db + unless @db require 'mongo' - Mongo::MongoClient.new.db(TEST_DB) + db_name, host, port, user, password = db_params + @db = MongoClient.new(host, port).db(db_name) + @db.authenticate(user, password) unless user.nil? or password.nil? + end + @db + end + + def self.config + @config ||= begin + config_file = ENV['RSPEC_CONFIG_PATH'] || File.join(ROOT, 'spec/support/config.yml') + if File.exists?(config_file) + YAML.load_file(config_file) + else + raise "There is no config file: '#{config_file}'" + end end end end \ No newline at end of file From 1b66ca88adeab7e10dca29da2ccb9dfa478efafa Mon Sep 17 00:00:00 2001 From: Anton Chuchkalov Date: Tue, 1 Dec 2015 19:35:45 +0300 Subject: [PATCH 4/7] update specs for new name validation rules --- devops-service/spec/support/shared_validation_specs.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/devops-service/spec/support/shared_validation_specs.rb b/devops-service/spec/support/shared_validation_specs.rb index 487e8d7..4d86657 100644 --- a/devops-service/spec/support/shared_validation_specs.rb +++ b/devops-service/spec/support/shared_validation_specs.rb @@ -37,7 +37,8 @@ RSpec.shared_examples 'field type validation' do |field, *properties| end if properties.include?(:maybe_empty_array) it 'should contain only word symbols' do - expect(build(validated_model_name, field => 'asd-asd')).not_to be_valid + expect(build(validated_model_name, field => '!')).not_to be_valid + expect(build(validated_model_name, field => '/')).not_to be_valid end if properties.include?(:only_word_symbols) it 'should contain elements like role[asd] or recipe[asd]' do @@ -99,9 +100,12 @@ RSpec.shared_examples 'field type validation' do |field, *properties| }.not_to raise_error end if properties.include?(:maybe_empty_array) - it 'should contain only word symbols' do + it 'should contain only symbols in [a-zA-Z0-9_-]' do expect{ - build(validated_model_name, field => 'asd-asd').send(field_validation_method) + build(validated_model_name, field => '!').send(field_validation_method) + }.to raise_error InvalidRecord + expect{ + build(validated_model_name, field => '/').send(field_validation_method) }.to raise_error InvalidRecord end if properties.include?(:only_word_symbols) From c320edaa7175566cea547f1b489cfc9979bd7eb3 Mon Sep 17 00:00:00 2001 From: Anton Chuchkalov Date: Wed, 2 Dec 2015 12:51:23 +0300 Subject: [PATCH 5/7] add few project specs --- devops-service/db/mongo/models/project.rb | 3 +- devops-service/spec/factories/project.rb | 17 ++++- devops-service/spec/models/project_spec.rb | 74 ++++++++++++++++++- .../spec/models/shared_models_context.rb | 8 ++ .../spec/support/shared_deploy_env_specs.rb | 6 +- .../spec/support/shared_validation_specs.rb | 4 +- devops-service/spec/support/spec_support.rb | 7 -- 7 files changed, 100 insertions(+), 19 deletions(-) diff --git a/devops-service/db/mongo/models/project.rb b/devops-service/db/mongo/models/project.rb index 8b14847..da52ce5 100644 --- a/devops-service/db/mongo/models/project.rb +++ b/devops-service/db/mongo/models/project.rb @@ -213,7 +213,8 @@ module Devops end def self.create_roles_response roles - if !roles || roles.is_a?(String) + return "error in creating roles" unless roles + if roles.is_a?(String) roles else info = "" diff --git a/devops-service/spec/factories/project.rb b/devops-service/spec/factories/project.rb index 561085f..4569e4f 100644 --- a/devops-service/spec/factories/project.rb +++ b/devops-service/spec/factories/project.rb @@ -2,11 +2,22 @@ require 'db/mongo/models/project' FactoryGirl.define do factory :project, class: Devops::Model::Project do + transient do + with_deploy_env_identifier false + with_deploy_env_identifiers ['foo'] + end + id 'my_project' - deploy_envs { - [build(:deploy_env_ec2)] - } run_list [] description 'desc' + + after(:build) do |project, evaluator| + unless project.deploy_envs + project.deploy_envs = [] + evaluator.with_deploy_env_identifiers.each do |env_id| + project.deploy_envs << build(:deploy_env_ec2, identifier: env_id) + end + end + end end end \ No newline at end of file diff --git a/devops-service/spec/models/project_spec.rb b/devops-service/spec/models/project_spec.rb index 9b19eb2..c721912 100644 --- a/devops-service/spec/models/project_spec.rb +++ b/devops-service/spec/models/project_spec.rb @@ -12,17 +12,17 @@ RSpec.describe Devops::Model::Project, type: :model do include_examples 'field type validation', :run_list, :not_nil, :maybe_empty_array, :run_list it "isn't valid when has envs with same identifier" do - project.deploy_envs << build(:deploy_env_ec2) + project = build(:project, with_deploy_env_identifiers: %w(foo foo)) expect(project).not_to be_valid end it "is valid when all envs have uniq identifiers" do - project.deploy_envs << build(:deploy_env_ec2, identifier: 'new') + project = build(:project, with_deploy_env_identifiers: %w(foo bar)) expect(project).to be_valid end it "isn't valid when at least one of envs isn't valid" do - project.deploy_envs << build(:deploy_env_ec2, identifier: nil) + project = build(:project, with_deploy_env_identifiers: ['foo', nil]) expect(project).not_to be_valid end end @@ -44,6 +44,74 @@ RSpec.describe Devops::Model::Project, type: :model do it 'sets run_list to empty_array by default' do expect(described_class.new.run_list).to eq [] end + + describe 'deploy envs building' do + let(:params) { {'deploy_envs' => [ {'provider' => @env_provider, 'identifier' => 'foo'} ]} } + + it 'builds envs array from given params' do + @env_provider = 'ec2' + expect( + described_class.new(params).deploy_envs + ).to be_an_array_of(Devops::Model::DeployEnvEc2).and have_size(1) + end + + it 'builds ec2 deploy envs from given params' do + @env_provider = 'ec2' + builded_env = described_class.new(params).deploy_envs.first + expect(builded_env).to be_a(Devops::Model::DeployEnvEc2) + expect(builded_env.identifier).to eq 'foo' + end + + it 'build openstack deploy envs from given params' do + @env_provider = 'openstack' + builded_env = described_class.new(params).deploy_envs.first + expect(builded_env).to be_a(Devops::Model::DeployEnvOpenstack) + end + + it 'build static deploy envs from given params' do + @env_provider = 'static' + builded_env = described_class.new(params).deploy_envs.first + expect(builded_env).to be_a(Devops::Model::DeployEnvStatic) + end + end + end + + describe '#deploy_env' do + let(:project) { project = build(:project, with_deploy_env_identifiers: %w(foo bar)) } + + it 'returns found env' do + expect(project.deploy_env('bar')).to be_an(Devops::Model::DeployEnvEc2) + end + + it 'raises RecordNotFound if there is no such env' do + expect { + project.deploy_env('missing') + }.to raise_error RecordNotFound + end + end + + describe '#add_deploy_env', stub_logger: true do + let(:env) {build(:deploy_env_ec2)} + subject { project.add_deploy_env(env) } + before do + allow(Devops::Db).to receive_message_chain('connector.add_deploy_env_to_project') + end + + it 'inserts deploy env into mongo via connector' do + expect( + Devops::Db + ).to receive_message_chain('connector.add_deploy_env_to_project').with(project.id, env) + subject + end + + it 'creates chef role' do + expect(env).to receive(:create_role).with(project.id) + subject + end + + it 'returns string' do + expect(subject).to be_a(String) + end end end \ No newline at end of file diff --git a/devops-service/spec/models/shared_models_context.rb b/devops-service/spec/models/shared_models_context.rb index 51804e0..fe5e754 100644 --- a/devops-service/spec/models/shared_models_context.rb +++ b/devops-service/spec/models/shared_models_context.rb @@ -11,3 +11,11 @@ RSpec.shared_context 'stubbed calls to connector in env validators' do allow_any_instance_of(Validators::FieldValidator::Image).to receive(:available_images).and_return([{'id' => 'image'}]) end end + +RSpec.shared_context 'stubbed calls to logger', stub_logger: true do + before do + allow(DevopsLogger).to receive_message_chain('logger.debug') + allow(DevopsLogger).to receive_message_chain('logger.info') + allow(DevopsLogger).to receive_message_chain('logger.error') + end +end diff --git a/devops-service/spec/support/shared_deploy_env_specs.rb b/devops-service/spec/support/shared_deploy_env_specs.rb index de65ed3..a23b1b6 100644 --- a/devops-service/spec/support/shared_deploy_env_specs.rb +++ b/devops-service/spec/support/shared_deploy_env_specs.rb @@ -103,10 +103,9 @@ RSpec.shared_examples 'deploy env' do end end - describe '#create_role' do + describe '#create_role', stub_logger: true do subject { env.create_role('project_name') } before do - stub_loggers allow(env).to receive_message_chain('knife_instance.role_name') { 'role_name' } end @@ -153,7 +152,7 @@ RSpec.shared_examples 'deploy env' do end end - describe '#rename' do + describe '#rename', stub_logger: true do subject { env.rename('project_id', 'new_name') } let(:old_role_name) {'project_id_name'} let(:new_role_name) {'project_id_new_name'} @@ -161,7 +160,6 @@ RSpec.shared_examples 'deploy env' do let(:suggested_new_roles) {["role[#{new_role_name}]"]} before do - stub_loggers # simulate correct start conditions env.run_list = suggested_old_roles diff --git a/devops-service/spec/support/shared_validation_specs.rb b/devops-service/spec/support/shared_validation_specs.rb index 4d86657..67bc6a4 100644 --- a/devops-service/spec/support/shared_validation_specs.rb +++ b/devops-service/spec/support/shared_validation_specs.rb @@ -9,7 +9,9 @@ RSpec.shared_examples 'field type validation' do |field, *properties| describe field do it 'should not be nil' do - expect(build(validated_model_name, field => nil)).not_to be_valid + model = build(validated_model_name) + model.send("#{field}=", nil) + expect(model).not_to be_valid end if properties.include?(:not_nil) it 'may be nil' do diff --git a/devops-service/spec/support/spec_support.rb b/devops-service/spec/support/spec_support.rb index 664927f..14a684a 100644 --- a/devops-service/spec/support/spec_support.rb +++ b/devops-service/spec/support/spec_support.rb @@ -4,13 +4,6 @@ module SpecSupport ROOT = File.join(__dir__, '../../') BLANK_FILE = File.join(ROOT, 'spec/support/blank_file') - - def stub_loggers - allow(DevopsLogger).to receive_message_chain('logger.debug') - allow(DevopsLogger).to receive_message_chain('logger.info') - allow(DevopsLogger).to receive_message_chain('logger.error') - end - def self.db_params @db ||= begin conf = config['mongo'] From 1933e25925bbc5eaa111802cfd7685a7a9030420 Mon Sep 17 00:00:00 2001 From: Anton Chuchkalov Date: Wed, 2 Dec 2015 20:29:09 +0300 Subject: [PATCH 6/7] Add specs for several project model methods: add_authorized_user remove_authorized_user check_authorization delete_deploy_env to_hash --- devops-service/db/mongo/models/project.rb | 5 +- devops-service/spec/models/project_spec.rb | 157 +++++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) diff --git a/devops-service/db/mongo/models/project.rb b/devops-service/db/mongo/models/project.rb index da52ce5..a4a4a23 100644 --- a/devops-service/db/mongo/models/project.rb +++ b/devops-service/db/mongo/models/project.rb @@ -81,11 +81,14 @@ module Devops Project.create_roles_response(res) end + # user could be a String or an Array of strings. + # if env is nil, add given user(s) to all project's envs, + # otherwise only to specified one. def add_authorized_user user, env=nil return if user.nil? new_users = ( user.is_a?(Array) ? user : [ user ] ) environments = env.nil? ? self.deploy_envs : [ self.deploy_env(env) ] - environments .each do |e| + environments.each do |e| e.add_users new_users Devops::Db.connector.set_project_deploy_env_field(self.id, e.identifier, {users: e.users}) end diff --git a/devops-service/spec/models/project_spec.rb b/devops-service/spec/models/project_spec.rb index c721912..bed0128 100644 --- a/devops-service/spec/models/project_spec.rb +++ b/devops-service/spec/models/project_spec.rb @@ -114,4 +114,161 @@ RSpec.describe Devops::Model::Project, type: :model do end end + describe '#add_authorized_user' do + before do + allow(Devops::Db).to receive_message_chain('connector.set_project_deploy_env_field') + end + let(:env1) { build(:deploy_env_ec2, identifier: 'foo') } + let(:env2) { build(:deploy_env_ec2, identifier: 'bar') } + let(:project) { build(:project, deploy_envs: [env1, env2]) } + + it 'returns nil if user is nil' do + expect(project.add_authorized_user(nil)).to be_nil + end + + context "when env isn't given" do + context 'when user is a String' do + it 'adds given user to all envs' do + project.add_authorized_user('John') + expect(env1.users).to match_array(%w(root John)) + expect(env2.users).to match_array(%w(root John)) + end + end + + context 'when user is an Array of strings' do + it 'adds given users to all envs' do + project.add_authorized_user(['John', 'Matz']) + expect(env1.users).to match_array(%w(root John Matz)) + expect(env2.users).to match_array(%w(root John Matz)) + end + end + end + + context 'when env is given' do + context 'when user is a String' do + it 'adds given user to given env' do + project.add_authorized_user('John', 'bar') + expect(env1.users).to match_array(%w(root)) + expect(env2.users).to match_array(%w(root John)) + end + end + + context 'when user is an Array of strings' do + it 'adds given users to all envs' do + project.add_authorized_user(['John', 'Matz'], 'bar') + expect(env1.users).to match_array(%w(root)) + expect(env2.users).to match_array(%w(root John Matz)) + end + end + end + end + + describe '#remove_authorized_user' do + before do + allow(Devops::Db).to receive_message_chain('connector.set_project_deploy_env_field') + end + let(:env1) { build(:deploy_env_ec2, identifier: 'foo', users: %w(root John Matz)) } + let(:env2) { build(:deploy_env_ec2, identifier: 'bar', users: %w(root John Matz)) } + let(:project) { build(:project, deploy_envs: [env1, env2]) } + + it 'returns nil if user is nil' do + expect(project.remove_authorized_user(nil)).to be_nil + end + + context "when env isn't given" do + context 'when user is a String' do + it 'adds given user to all envs' do + project.remove_authorized_user('John') + expect(env1.users).to match_array(%w(root Matz)) + expect(env2.users).to match_array(%w(root Matz)) + end + end + + context 'when user is an Array of strings' do + it 'adds given users to all envs' do + project.remove_authorized_user(['John', 'Matz']) + expect(env1.users).to match_array(%w(root)) + expect(env2.users).to match_array(%w(root)) + end + end + end + + context 'when env is given' do + context 'when user is a String' do + it 'adds given user to given env' do + project.remove_authorized_user('John', 'bar') + expect(env1.users).to match_array(%w(root John Matz)) + expect(env2.users).to match_array(%w(root Matz)) + end + end + + context 'when user is an Array of strings' do + it 'adds given users to all envs' do + project.remove_authorized_user(['John', 'Matz'], 'bar') + expect(env1.users).to match_array(%w(root John Matz)) + expect(env2.users).to match_array(%w(root)) + end + end + end + end + + describe '#check_authorization' do + subject { project.check_authorization(@user || 'Matz', 'foo') } + + it 'returns true for root user' do + @user = 'root' + expect(subject).to be true + end + + it "returns false if env's users don't include given user" do + expect(subject).to be false + end + + it "returns true if env's users include given user" do + project.deploy_env('foo').users = %w(root Matz) + expect(subject).to be true + end + + it 'returns false if there is no such env' do + expect(project.check_authorization('root', 'wrong')).to be false + end + end + + describe '#delete_deploy_env' do + it 'removes env' do + allow(Devops::Db).to receive_message_chain('connector.remove_deploy_env_from_project') + expect(Devops::Db).to receive_message_chain('connector.remove_deploy_env_from_project').with(project.id, 'foo') + project.delete_deploy_env('foo') + expect(project.deploy_envs).to match_array [] + end + end + + describe '#to_hash' do + subject { project.to_hash } + it 'returns hash' do + expect(subject).to be_a(Hash) + end + + it 'contains project id under name key' do + expect(subject['name']).to eq project.id + end + + it 'contains deploy_envs converted to hashes' do + expect(subject['deploy_envs']).to be_an_array_of(Hash) + end + + it 'also contains descriptions and run_list' do + expect(subject).to include('description', 'run_list') + end + + it 'contains archived key if project is archived' do + project.archived = true + expect(subject).to include('archived') + end + + it "doesn't contain archived if project isn't archived" do + expect(subject).not_to include('archived') + end + end + end \ No newline at end of file From 47d521870e714b5c75fe99ddbd7d5fae7b915177 Mon Sep 17 00:00:00 2001 From: amartynov Date: Mon, 7 Dec 2015 14:14:10 +0300 Subject: [PATCH 7/7] cid-36 --- devops-service/config.rb | 1 + .../models/provider_accounts/ec2_provider_account.rb | 6 +++++- .../db/validators/field_validators/field_type.rb | 11 +++++++++++ devops-service/providers/ec2.rb | 10 +++++++--- devops-service/providers/ec2_accounts_factory.rb | 1 + 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/devops-service/config.rb b/devops-service/config.rb index 60aaf1a..5bc27b3 100644 --- a/devops-service/config.rb +++ b/devops-service/config.rb @@ -29,6 +29,7 @@ config[:openstack_ssh_key] = "ssh_key" config[:openstack_certificate] = "/path/to/.ssh/openstack.pem" # aws settings +config[:aws_use_iam_profile] = false config[:aws_access_key_id] = "access_key_id" config[:aws_secret_access_key] = "secret_access_key" config[:aws_ssh_key] = "ssh_key" diff --git a/devops-service/db/mongo/models/provider_accounts/ec2_provider_account.rb b/devops-service/db/mongo/models/provider_accounts/ec2_provider_account.rb index 3f2fb41..7453835 100644 --- a/devops-service/db/mongo/models/provider_accounts/ec2_provider_account.rb +++ b/devops-service/db/mongo/models/provider_accounts/ec2_provider_account.rb @@ -4,7 +4,7 @@ module Devops module Model class Ec2ProviderAccount < ProviderAccount - attr_accessor :access_key_id, :availability_zone, :secret_access_key + attr_accessor :availability_zone set_field_validators :access_key_id, ::Validators::FieldValidator::NotNil, ::Validators::FieldValidator::FieldType::String, @@ -13,6 +13,9 @@ module Devops set_field_validators :secret_access_key, ::Validators::FieldValidator::NotNil, ::Validators::FieldValidator::FieldType::String, ::Validators::FieldValidator::NotEmpty + + set_field_validators :use_iam_profile, ::Validators::FieldValidator::Nil, + ::Validators::FieldValidator::FieldType::Boolean def initialize a={} super(a) self.provider = Provider::Ec2::PROVIDER @@ -45,6 +48,7 @@ module Devops { access_key_id: "AWS account access key", secret_access_key: "AWS account secret key", + use_iam_profile: "Should use iam profile?", availability_zone: "Availability zone, todo: remove field?" }.merge(ProviderAccount::ACCOUNT_FIELDS) end diff --git a/devops-service/db/validators/field_validators/field_type.rb b/devops-service/db/validators/field_validators/field_type.rb index f90d191..2ad69fd 100644 --- a/devops-service/db/validators/field_validators/field_type.rb +++ b/devops-service/db/validators/field_validators/field_type.rb @@ -25,6 +25,17 @@ module Validators end end + class Boolean < FieldType + + def valid? + @value == true or @value == false + end + + def type_name + "boolean" + end + end + class Array < FieldType def type diff --git a/devops-service/providers/ec2.rb b/devops-service/providers/ec2.rb index e6d88fb..b001baa 100644 --- a/devops-service/providers/ec2.rb +++ b/devops-service/providers/ec2.rb @@ -15,10 +15,14 @@ module Provider self.certificate_path = config[:aws_certificate] self.ssh_key = config[:aws_ssh_key] options = { - :provider => "aws", - :aws_access_key_id => config[:aws_access_key_id], - :aws_secret_access_key => config[:aws_secret_access_key] + :provider => "aws" } + if config[:aws_use_iam_profile] + options[:use_iam_profile] = true + else + options[:aws_access_key_id] = config[:aws_access_key_id] + options[:aws_secret_access_key] = config[:aws_secret_access_key] + end if config[:aws_proxy] and config[:aws_no_proxy] options[:connection_options] = { :proxy => config[:aws_proxy], diff --git a/devops-service/providers/ec2_accounts_factory.rb b/devops-service/providers/ec2_accounts_factory.rb index 58eb3a8..7a875f3 100644 --- a/devops-service/providers/ec2_accounts_factory.rb +++ b/devops-service/providers/ec2_accounts_factory.rb @@ -20,6 +20,7 @@ module Provider aws_ssh_key: account.ssh_key, aws_access_key_id: account.access_key_id, aws_secret_access_key: account.secret_access_key, + aws_use_iam_profile: account.use_iam_profile, aws_availability_zone: account.availability_zone, aws_proxy: config[:aws_proxy],