diff --git a/devops-service/db/mongo/connectors/base.rb b/devops-service/db/mongo/connectors/base.rb new file mode 100644 index 0000000..36f9047 --- /dev/null +++ b/devops-service/db/mongo/connectors/base.rb @@ -0,0 +1,19 @@ +require "db/exceptions/record_not_found" +require "db/exceptions/invalid_record" +require "exceptions/invalid_command" +require "exceptions/invalid_privileges" + +module Connectors + class Base + # Yes, we can implement connectors without attr_accessor, storing collection directly + # in instance variable like + # @collection = db.collection('users') + # + # But with latter approach included modules should know about instance variables of + # base classes. + # Also, debugging "No method error" is simplier than seeking missing instance var. + private + + attr_accessor :collection + end +end diff --git a/devops-service/db/mongo/connectors/filter.rb b/devops-service/db/mongo/connectors/filter.rb new file mode 100644 index 0000000..9cd4ba5 --- /dev/null +++ b/devops-service/db/mongo/connectors/filter.rb @@ -0,0 +1,38 @@ +module Connectors + class Filter < Base + + def initialize(db) + self.collection = db.collection('filters') + end + + def available_images provider + f = collection.find('type' => 'image', 'provider' => provider).to_a.first + return [] if f.nil? + f['images'] + end + + def add_available_images images, provider + return unless images.is_a?(Array) + f = collection.find('type' => 'image', 'provider' => provider).to_a.first + if f.nil? + collection.insert('type' => 'image', 'provider' => provider, 'images' => images) + return images + else + f['images'] |= images + collection.update({'_id' => f['_id']}, f) + return 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'] + end + [] + end + end +end diff --git a/devops-service/db/mongo/connectors/helpers/delete_command.rb b/devops-service/db/mongo/connectors/helpers/delete_command.rb new file mode 100644 index 0000000..b66be9c --- /dev/null +++ b/devops-service/db/mongo/connectors/helpers/delete_command.rb @@ -0,0 +1,26 @@ +require 'lib/string_helper' + +module Connectors + module Helpers + module DeleteCommand + + # when included, this module adds method #delete and alias for it. + # Alias name depends on base class name. + # We need this alias to forward methods from MongoConnector to resources connectors. + + def self.included(base) + resource_name = StringHelper.underscore_class(base) + method_name = "#{resource_name}_delete".to_sym + alias_method method_name, :delete + end + + def delete(id, options={}) + delete_query = {'_id' => id}.merge(options) + r = collection.remove(delete_query) + raise RecordNotFound.new("'#{id}' not found") if r['n'] == 0 + r + end + + end + end +end diff --git a/devops-service/db/mongo/connectors/helpers/insert_command.rb b/devops-service/db/mongo/connectors/helpers/insert_command.rb new file mode 100644 index 0000000..c5566b4 --- /dev/null +++ b/devops-service/db/mongo/connectors/helpers/insert_command.rb @@ -0,0 +1,31 @@ +require 'lib/string_helper' + +module Connectors + module Helpers + module InsertCommand + + # when included, this module adds method #insert and alias for it. + # Alias name depends on base class name. + # We need this alias to forward methods from MongoConnector to resources connectors. + + def self.included(base) + resource_name = StringHelper.underscore_class(base) + method_name = "#{resource_name}_insert".to_sym + alias_method method_name, :insert + end + + def insert(record) + begin + record.validate! + collection.insert(record.to_mongo_hash) + 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 + end + + end + end +end diff --git a/devops-service/db/mongo/connectors/helpers/list_command.rb b/devops-service/db/mongo/connectors/helpers/list_command.rb new file mode 100644 index 0000000..aaab3b8 --- /dev/null +++ b/devops-service/db/mongo/connectors/helpers/list_command.rb @@ -0,0 +1,24 @@ +require 'lib/string_helper' + +module Connectors + module Helpers + module ListCommand + + # when included, this module adds method #list and alias for it. + # Alias name depends on base class name. + # We need this alias to forward methods from MongoConnector to resources connectors. + + def self.included(base) + resource_name = StringHelper.underscore_class(base).to_sym + method_name = StringHelper.pluralize(resource_name) + alias_method method_name, :list + end + + # query options is needed, for example, for fields limiting + def list(query={}, query_options={}) + collection.find(query, query_options).to_a.map {|bson| model_from_bson(bson)} + end + + end + end +end diff --git a/devops-service/db/mongo/connectors/helpers/show_command.rb b/devops-service/db/mongo/connectors/helpers/show_command.rb new file mode 100644 index 0000000..cb06d74 --- /dev/null +++ b/devops-service/db/mongo/connectors/helpers/show_command.rb @@ -0,0 +1,25 @@ +require 'lib/string_helper' + +module Connectors + module Helpers + module ShowCommand + + # when included, this module adds method #show and alias for it. + # Alias name depends on base class name. + # We need this alias to forward methods from MongoConnector to resources connectors. + + def self.included(base) + method_name = StringHelper.underscore_class(base).to_sym + alias_method method_name, :show + end + + def show(id, options={}) + query = {'_id' => id}.merge(options) + bson = collection.find(query).to_a.first + raise RecordNotFound.new("'#{id}' not found") unless bson + model_from_bson(bson) + 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 new file mode 100644 index 0000000..ac5d3bb --- /dev/null +++ b/devops-service/db/mongo/connectors/helpers/update_command.rb @@ -0,0 +1,29 @@ +require 'lib/string_helper' + +module Connectors + module Helpers + module UpdateCommand + + # when included, this module adds method #update and alias for it. + # Alias name depends on base class name. + # We need this alias to forward methods from MongoConnector to resources connectors. + + def self.included(base) + resource_name = StringHelper.underscore_class(base) + method_name = "#{resource_name}_update".to_sym + alias_method method_name, :update + end + + def update(record) + record.validate! + collection.update({"_id" => record.id}, record.to_mongo_hash) + 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 + + end + end +end diff --git a/devops-service/db/mongo/connectors/image.rb b/devops-service/db/mongo/connectors/image.rb new file mode 100644 index 0000000..e023be8 --- /dev/null +++ b/devops-service/db/mongo/connectors/image.rb @@ -0,0 +1,25 @@ +module Connectors + class Image < Base + include Helpers::InsertCommand, + Helpers::ShowCommand, + Helpers::ListCommand, + Helpers::DeleteCommand, + Helpers::UpdateCommand + + def initialize(db) + self.collection = db.collection('images') + end + + def images(provider=nil) + query = (provider.nil? ? {} : {'provider' => provider}) + list(query) + end + + private + + def model_from_bson(bson) + ::Image.create_from_bson(bson) + end + end + +end diff --git a/devops-service/db/mongo/connectors/key.rb b/devops-service/db/mongo/connectors/key.rb new file mode 100644 index 0000000..ea725a6 --- /dev/null +++ b/devops-service/db/mongo/connectors/key.rb @@ -0,0 +1,28 @@ +module Connectors + class Key < Base + include Helpers::InsertCommand, + Helpers::ShowCommand, + Helpers::ListCommand, + Helpers::DeleteCommand + + def initialize(db) + self.collection = db.collection('keys') + end + + def key(id, scope=nil) + options = scope ? {'scope' => scope} : {} + show(id, options) + end + + def key_delete id + delete(id, 'scope' => ::Key::USER) + end + + private + + def model_from_bson(bson) + ::Key.create_from_bson(bson) + end + end + +end diff --git a/devops-service/db/mongo/connectors/project.rb b/devops-service/db/mongo/connectors/project.rb new file mode 100644 index 0000000..f7c07e8 --- /dev/null +++ b/devops-service/db/mongo/connectors/project.rb @@ -0,0 +1,90 @@ +module Connectors + class Project < Base + include Helpers::InsertCommand, + Helpers::ShowCommand, + Helpers::ListCommand, + Helpers::DeleteCommand, + Helpers::UpdateCommand + + + def initialize(db) + @collection = db.collection('projects') + end + + def is_project_exists?(project) + self.project(project.id) + return true + rescue RecordNotFound => e + return false + end + + def projects_all + list + end + + def projects ids=nil, type=nil, fields=[] + query = {} + query['_id'] = {'$in' => ids} if ids + query['type'] = 'multi' if type == :multi + list(query, fields: fields) + end + + # names - array of project names + def project_names_with_envs(names=nil) + # db.projects.aggregate({$unwind:"$deploy_envs"}, {$project:{"deploy_envs.identifier":1}}, {$group:{_id:"$_id", envs: {$addToSet: "$deploy_envs.identifier"}}}) + q = [] + unless names.nil? + q.push({ + '$match' => { + '_id' => { + '$in' => names + } + } + }) + end + q.push({ + '$unwind' => '$deploy_envs' + }) + q.push({ + '$project' => { + 'deploy_envs.identifier' => 1 + } + }) + q.push({ + '$group' => { + '_id' => '$_id', + 'envs' => { + '$addToSet' => '$deploy_envs.identifier' + } + } + }) + res = @collection.aggregate(q) + r = {} + res.each do |ar| + r[ar['_id']] = ar['envs'] + end + return r + end + + def projects_by_image(image) + list('deploy_envs.image' => image) + end + + def projects_by_user(user) + list('deploy_envs.users' => user) + end + + def check_project_auth(project_id, env, user_id) + project = show(project_id) + raise InvalidPrivileges.new("User '#{user_id}' unauthorized to work with project '#{project_id}'") unless project.check_authorization(user_id, env) + project + end + + private + + def model_from_bson(bson) + ::Project.create_from_bson(bson) + end + end + +end diff --git a/devops-service/db/mongo/connectors/report.rb b/devops-service/db/mongo/connectors/report.rb new file mode 100644 index 0000000..3509bb1 --- /dev/null +++ b/devops-service/db/mongo/connectors/report.rb @@ -0,0 +1,48 @@ +require "date" + +module Connectors + class Report < Base + include Helpers::ShowCommand, + Helpers::ListCommand + + def initialize(db) + self.collection = db.collection('reports') + end + + def save_report r + r.created_at = Time.new + collection.insert(r.to_mongo_hash) + end + + def reports options={} + date = {} + if options.has_key?("date_from") or options.has_key?("date_to") + if options.has_key?("date_from") + begin + d = Date.parse(options["date_from"]) + date["$gte"] = d.to_time + rescue ArgumentError + end + end + if options.has_key?("date_to") + begin + d = Date.parse(options["date_to"]) + date["$lt"] = d.to_time + rescue ArgumentError + end + end + options.delete("date_from") + options.delete("date_to") + options["created_at"] = date unless date.empty? + end + list(options) + end + + private + + def model_from_bson(bson) + ::Report.new(bson) + end + end + +end diff --git a/devops-service/db/mongo/connectors/server.rb b/devops-service/db/mongo/connectors/server.rb new file mode 100644 index 0000000..7a59b1b --- /dev/null +++ b/devops-service/db/mongo/connectors/server.rb @@ -0,0 +1,88 @@ +module Connectors + class Server < Base + include Helpers::DeleteCommand, + Helpers::ListCommand + + + def initialize(db) + self.collection = db.collection('servers') + end + + def servers_find(query, fields) + query_options = fields.nil? ? {} : {fields: fields} + list(query, query_options) + end + + def servers(p=nil, env=nil, names=nil, reserved=nil, fields=:all) + q = {} + q['project'] = p unless p.nil? or p.empty? + q['deploy_env'] = env unless env.nil? or env.empty? + q['chef_node_name'] = {'$in' => names} unless names.nil? or names.class != Array + q['reserved_by'] = {'$ne' => nil} unless reserved.nil? + f = nil + unless fields == :all + f = fields + ['_id', 'chef_node_name'].each do |k| + f.push(k) unless f.include?(k) + end + end + servers_find(q, f) + end + + def servers_by_names(names) + query = {} + query['chef_node_name'] = {'$in' => names} unless names.nil? or names.class != Array + list(query) + end + + def server_by_instance_id(id) + find_server('_id' => id) + end + + def server_by_chef_node_name(name) + find_server('chef_node_name' => name) + end + + def servers_by_key(key_name) + collection.find('key' => key_name).to_a.map { |bson| model_from_bson(bson) } + end + + def server_insert(server) + #server.validate! + server.created_at = Time.now + collection.insert(server.to_mongo_hash) + end + + # somewhy servers are not validated in previous version of code. I leave this until I know, why. + def server_update(server) + collection.update({'_id' => server.id}, server.to_hash_without_id) + end + + # somewhy servers are not validated in previous version of code. I leave this until I know, why. + def server_set_chef_node_name(server) + collection.update({'_id' => server.id}, {'$set' => {'chef_node_name' => server.chef_node_name}}) + end + + private + + def model_from_bson(bson) + ::Server.create_from_bson(bson) + end + + # couldn't be replaced with ShowCommand (_id doesn't neccesary appear in params) + def find_server(params) + bson = collection.find(params).to_a.first + if bson.nil? + if params.has_key? "_id" + raise RecordNotFound.new("No server by instance id '#{params["_id"]}' found") + elsif params.has_key? "chef_node_name" + raise RecordNotFound.new("No server by node name '#{params["chef_node_name"]}' found") + else + raise RecordNotFound.new('Invalid params') + end + end + model_from_bson(bson) + end + end + +end diff --git a/devops-service/db/mongo/connectors/stack.rb b/devops-service/db/mongo/connectors/stack.rb new file mode 100644 index 0000000..7d63db5 --- /dev/null +++ b/devops-service/db/mongo/connectors/stack.rb @@ -0,0 +1,24 @@ +module Connectors + class Stack < Base + include Helpers::InsertCommand, + Helpers::ShowCommand, + Helpers::ListCommand, + Helpers::DeleteCommand + + def initialize(db) + self.collection = db.collection('stacks') + end + + def stacks(provider=nil) + query = (provider.nil? ? {} : {'provider' => provider}) + list(query) + end + + private + + def model_from_bson(bson) + provider = bson['provider'] + ::StackFactory.get_class(provider).create_from_bson(bson) + end + end +end diff --git a/devops-service/db/mongo/connectors/stack_template.rb b/devops-service/db/mongo/connectors/stack_template.rb new file mode 100644 index 0000000..a51764f --- /dev/null +++ b/devops-service/db/mongo/connectors/stack_template.rb @@ -0,0 +1,25 @@ +module Connectors + class StackTemplate < Base + include Helpers::InsertCommand, + Helpers::ShowCommand, + Helpers::ListCommand, + Helpers::DeleteCommand + + def initialize(db) + self.collection = db.collection('stack_templates') + end + + def stack_templates(provider=nil) + query = (provider.nil? ? {} : {'provider' => provider}) + list(query) + end + + private + + def model_from_bson(bson) + provider = bson['provider'] + ::StackTemplateFactory.get_class(provider).create_from_bson(bson) + end + end + +end diff --git a/devops-service/db/mongo/connectors/statictic.rb b/devops-service/db/mongo/connectors/statictic.rb new file mode 100644 index 0000000..b2d9b34 --- /dev/null +++ b/devops-service/db/mongo/connectors/statictic.rb @@ -0,0 +1,14 @@ +module Connectors + class Statistic < Base + + def initialize(db) + self.collection = db.collection('statistic') + end + + def statistic(user, path, method, body, response_code) + collection.insert(user: user, path: path, method: method, body: body, response_code: response_code, date: Time.now) + end + + end + +end diff --git a/devops-service/db/mongo/connectors/user.rb b/devops-service/db/mongo/connectors/user.rb new file mode 100644 index 0000000..9155d5e --- /dev/null +++ b/devops-service/db/mongo/connectors/user.rb @@ -0,0 +1,57 @@ +module Connectors + class User < Base + include Helpers::InsertCommand, + Helpers::ShowCommand, + Helpers::ListCommand, + Helpers::DeleteCommand, + Helpers::UpdateCommand + + + def initialize(db) + self.collection = db.collection('users') + end + + def user_auth user, password + u = collection.find('_id' => user, 'password' => password).to_a.first + raise RecordNotFound.new('Invalid username or password') if u.nil? + end + + def users(ids=nil) + query = {} + query['_id'] = {'$in' => ids} if ids.is_a?(Array) + list(query) + end + + def users_names(ids=nil) + users = self.users(ids) + users.map(&:id) + end + + def create_root_user + u = user('root') + rescue RecordNotFound => e + root = ::User.create_root + collection.insert(root.to_mongo_hash) + end + + def check_user_privileges(id, cmd, required_privelege) + user = show(id) + + unless %w(r w x).include?(required_privelege) + raise InvalidPrivileges.new("Access internal problem with privilege '#{required_privelege}'") + end + + unless user.can?(cmd, required_privelege) + raise InvalidPrivileges.new("Access denied for '#{user.id}'") + end + true + end + + private + + def model_from_bson(bson) + ::User.create_from_bson(bson) + end + end + +end diff --git a/devops-service/db/mongo/models/deploy_env_base.rb b/devops-service/db/mongo/models/deploy_env/deploy_env_base.rb similarity index 78% rename from devops-service/db/mongo/models/deploy_env_base.rb rename to devops-service/db/mongo/models/deploy_env/deploy_env_base.rb index 9f68c8c..021a37f 100644 --- a/devops-service/db/mongo/models/deploy_env_base.rb +++ b/devops-service/db/mongo/models/deploy_env/deploy_env_base.rb @@ -1,5 +1,6 @@ require "db/mongo/models/mongo_model" require "db/exceptions/invalid_record" +require "providers/provider_factory" require "commands/deploy_env" class DeployEnvBase < MongoModel @@ -18,18 +19,6 @@ class DeployEnvBase < MongoModel self.users = (b.is_a?(Array) ? b.uniq : b) end - def validate! - super - begin - self.class.validators.each do |validator| - validator.new(self).validate! - end - true - rescue InvalidRecord => e - raise InvalidRecord.new "Deploy environment '#{self.identifier}'. " + e.message - end - end - def to_hash { "identifier" => self.identifier, @@ -44,6 +33,10 @@ class DeployEnvBase < MongoModel @provider_instance ||= ::Provider::ProviderFactory.get(self.provider) end + def build_error_message(message) + "Deploy environment '#{self.identifier}'. " + message + end + # class methods class << self @@ -52,7 +45,7 @@ class DeployEnvBase < MongoModel @validators end - private + private def set_validators(*validators) @validators = validators diff --git a/devops-service/db/mongo/models/deploy_env/deploy_env_ec2.rb b/devops-service/db/mongo/models/deploy_env/deploy_env_ec2.rb new file mode 100644 index 0000000..0045939 --- /dev/null +++ b/devops-service/db/mongo/models/deploy_env/deploy_env_ec2.rb @@ -0,0 +1,63 @@ +require "db/mongo/models/deploy_env/deploy_env_base" + +class DeployEnvEc2 < DeployEnvBase + + attr_accessor :flavor, :image, :subnets, :groups + + types :identifier => {:type => String, :empty => false}, + :image => {:type => String, :empty => false}, + :flavor => {:type => String, :empty => false}, + :provider => {:type => String, :empty => false}, + :expires => {:type => String, :empty => false, :nil => true}, + :run_list => {:type => Array, :empty => true}, + :users => {:type => Array, :empty => true}, + :subnets => {:type => Array, :empty => true}, + :groups => {:type => Array, :empty => false} + + set_validators ::Validators::DeployEnv::RunList, + ::Validators::DeployEnv::Expiration, + ::Validators::DeployEnv::Users, + ::Validators::DeployEnv::Flavor, + ::Validators::DeployEnv::Image, + ::Validators::DeployEnv::SubnetBelongsToProvider, + ::Validators::DeployEnv::Groups + + def initialize d={} + super(d) + self.flavor = d["flavor"] + self.image = d["image"] + b = d["subnets"] || [] + self.subnets = if b.is_a?(Array) + (b.size > 1 ? [ b[0] ] : b) + else + b + end + b = d["groups"] || ["default"] + self.groups = (b.is_a?(Array) ? b.uniq : b) + end + + def to_hash + h = super + h.merge!({ + "flavor" => self.flavor, + "image" => self.image, + "subnets" => self.subnets, + "groups" => self.groups + }) + end + + def self.create hash + DeployEnvEc2.new(hash) + end + + private + + def subnets_filter + networks = provider_instance.networks + + unless self.subnets.empty? + {"vpc-id" => networks.detect{|n| n["name"] == self.subnets[0]}["vpcId"] } + end + end + +end diff --git a/devops-service/db/mongo/models/deploy_env.rb b/devops-service/db/mongo/models/deploy_env/deploy_env_factory.rb similarity index 59% rename from devops-service/db/mongo/models/deploy_env.rb rename to devops-service/db/mongo/models/deploy_env/deploy_env_factory.rb index bffd553..a85964c 100644 --- a/devops-service/db/mongo/models/deploy_env.rb +++ b/devops-service/db/mongo/models/deploy_env/deploy_env_factory.rb @@ -1,12 +1,8 @@ -require "db/exceptions/invalid_record" -require "db/mongo/models/deploy_env_static" -require "db/mongo/models/deploy_env_openstack" -require "db/mongo/models/deploy_env_ec2" -require "providers/static" -require "providers/openstack" -require "providers/ec2" +require_relative "deploy_env_static" +require_relative "deploy_env_openstack" +require_relative "deploy_env_ec2" -class DeployEnv +class DeployEnvFactory def self.create hash c = case(hash["provider"]) diff --git a/devops-service/db/mongo/models/deploy_env_multi.rb b/devops-service/db/mongo/models/deploy_env/deploy_env_multi.rb similarity index 100% rename from devops-service/db/mongo/models/deploy_env_multi.rb rename to devops-service/db/mongo/models/deploy_env/deploy_env_multi.rb diff --git a/devops-service/db/mongo/models/deploy_env_openstack.rb b/devops-service/db/mongo/models/deploy_env/deploy_env_openstack.rb similarity index 100% rename from devops-service/db/mongo/models/deploy_env_openstack.rb rename to devops-service/db/mongo/models/deploy_env/deploy_env_openstack.rb diff --git a/devops-service/db/mongo/models/deploy_env_static.rb b/devops-service/db/mongo/models/deploy_env/deploy_env_static.rb similarity index 100% rename from devops-service/db/mongo/models/deploy_env_static.rb rename to devops-service/db/mongo/models/deploy_env/deploy_env_static.rb diff --git a/devops-service/db/mongo/models/deploy_env_ec2.rb b/devops-service/db/mongo/models/deploy_env_ec2.rb deleted file mode 100644 index 5370b70..0000000 --- a/devops-service/db/mongo/models/deploy_env_ec2.rb +++ /dev/null @@ -1,64 +0,0 @@ -require "db/mongo/models/deploy_env_base" -require "providers/provider_factory" - -class DeployEnvEc2 < DeployEnvBase - - attr_accessor :flavor, :image, :subnets, :groups - - types :identifier => {:type => String, :empty => false}, - :image => {:type => String, :empty => false}, - :flavor => {:type => String, :empty => false}, - :provider => {:type => String, :empty => false}, - :expires => {:type => String, :empty => false, :nil => true}, - :run_list => {:type => Array, :empty => true}, - :users => {:type => Array, :empty => true}, - :subnets => {:type => Array, :empty => true}, - :groups => {:type => Array, :empty => false} - - set_validators ::Validators::DeployEnv::RunList, - ::Validators::DeployEnv::Expiration, - ::Validators::DeployEnv::Users, - ::Validators::DeployEnv::Flavor, - ::Validators::DeployEnv::Image, - ::Validators::DeployEnv::SubnetBelongsToProvider, - ::Validators::DeployEnv::Groups - - def initialize d={} - super(d) - self.flavor = d["flavor"] - self.image = d["image"] - b = d["subnets"] || [] - self.subnets = if b.is_a?(Array) - (b.size > 1 ? [ b[0] ] : b) - else - b - end - b = d["groups"] || ["default"] - self.groups = (b.is_a?(Array) ? b.uniq : b) - end - - def to_hash - h = super - h.merge!({ - "flavor" => self.flavor, - "image" => self.image, - "subnets" => self.subnets, - "groups" => self.groups - }) - end - - def self.create hash - DeployEnvEc2.new(hash) - end - - private - - def subnets_filter - networks = provider_instance.networks - - unless self.subnets.empty? - {"vpc-id" => networks.detect{|n| n["name"] == self.subnets[0]}["vpcId"] } - end - end - -end diff --git a/devops-service/db/mongo/models/image.rb b/devops-service/db/mongo/models/image.rb index 0bdf761..212b22d 100644 --- a/devops-service/db/mongo/models/image.rb +++ b/devops-service/db/mongo/models/image.rb @@ -1,30 +1,16 @@ require "db/exceptions/invalid_record" require "db/mongo/models/mongo_model" -require "commands/image" -require "commands/bootstrap_templates" class Image < MongoModel - include ImageCommands - include BootstrapTemplatesCommands - attr_accessor :id, :provider, :remote_user, :name, :bootstrap_template types :id => {:type => String, :empty => false}, :provider => {:type => String, :empty => false}, :remote_user => {:type => String, :empty => false}, :name => {:type => String, :empty => true}, :bootstrap_template => {:type => String, :empty => false, :nil => true} - - def validate! - super - images = get_images(DevopsService.mongo, self.provider) - raise InvalidRecord.new "Invalid image id '#{self.id}' for provider '#{self.provider}', please check image filters" unless images.map{|i| i["id"]}.include?(self.id) - - if self.bootstrap_template - templates = get_templates - raise InvalidRecord.new "Invalid bootstrap template '#{self.bootstrap_template}' for image '#{self.id}'" unless templates.include?(self.bootstrap_template) - end - end + set_validators ::Validators::Image::ImageInFilter, + ::Validators::Image::BootstrapTemplate def initialize p={} self.id = p["id"] diff --git a/devops-service/db/mongo/models/key.rb b/devops-service/db/mongo/models/key.rb index 507e858..296d6fc 100644 --- a/devops-service/db/mongo/models/key.rb +++ b/devops-service/db/mongo/models/key.rb @@ -12,6 +12,9 @@ class Key < MongoModel :path => {:type => String, :empty => false}, :scope => {:type => String, :empty => false} + set_validators ::Validators::Key::FileExistence, + ::Validators::Key::Scope + def initialize p={} self.id = p["id"] self.path = p["path"] @@ -40,11 +43,4 @@ class Key < MongoModel o end - def validate! - super - raise InvalidRecord.new "File does not exist" unless File.exist?(self.path) - raise InvalidRecord.new "Key parameter 'scope' is invalid" unless [SYSTEM, USER].include?(self.scope) - true - end - end diff --git a/devops-service/db/mongo/models/mongo_model.rb b/devops-service/db/mongo/models/mongo_model.rb index d8137f1..91c2bb7 100644 --- a/devops-service/db/mongo/models/mongo_model.rb +++ b/devops-service/db/mongo/models/mongo_model.rb @@ -48,6 +48,22 @@ class MongoModel end end + def validate! + begin + self.validate_fields_types + self.class.validate_model(self) + true + rescue InvalidRecord => e + error_message = self.build_error_message(e.message) + raise InvalidRecord.new(error_message) + end + end + + def build_error_message(message) + # overrided in descendants + message + end + # types - Hash # key - param name # value - Hash @@ -56,7 +72,7 @@ class MongoModel # :nil - can param be nil? (false) # :value_type - type of array element (String) def self.types types - define_method :validate do + define_method :validate_fields_types do t = types.keys e = types.keys n = types.keys @@ -98,8 +114,32 @@ class MongoModel end end - def validate! - self.validate + def self.validators + @validators || [] + end + + # all exceptions are handled in @validate! method + def self.validate_model(model) + validators.each do |validator| + validator.new(model).validate! + end + end + + # private class methods + class << self + + private + + def set_validators(*validators_to_add) + @validators ||= [] + @validators += validators_to_add + end + + def unset_validators(*validators_to_remove) + @validators ||= [] + self.validators -= validators_to_remove + end + end end diff --git a/devops-service/db/mongo/models/project.rb b/devops-service/db/mongo/models/project.rb index 416a267..46d2a74 100644 --- a/devops-service/db/mongo/models/project.rb +++ b/devops-service/db/mongo/models/project.rb @@ -1,8 +1,8 @@ require "db/exceptions/invalid_record" require "db/exceptions/record_not_found" -require "db/mongo/models/deploy_env" +require "db/mongo/models/deploy_env/deploy_env_factory" require "db/mongo/models/user" -require "db/mongo/models/deploy_env_multi" +require "db/mongo/models/deploy_env/deploy_env_multi" require "db/mongo/models/mongo_model" require "json" @@ -23,7 +23,7 @@ class Project < MongoModel self.id = p["name"] #raise InvalidRecord.new "No deploy envirenments for project #{self.id}" if p["deploy_envs"].nil? or p["deploy_envs"].empty? self.type = p["type"] - env_class = ( self.multi? ? DeployEnvMulti : DeployEnv ) + env_class = ( self.multi? ? DeployEnvMulti : DeployEnvFactory ) unless p["deploy_envs"].nil? self.deploy_envs = [] p["deploy_envs"].each do |e| diff --git a/devops-service/db/mongo/models/stack/stack_base.rb b/devops-service/db/mongo/models/stack/stack_base.rb new file mode 100644 index 0000000..470b0ca --- /dev/null +++ b/devops-service/db/mongo/models/stack/stack_base.rb @@ -0,0 +1,52 @@ +class StackBase < MongoModel + + attr_accessor :id, :project, :deploy_env, :stack_template, :cloud_stack_id, :provider + + types id: {type: String, empty: false}, + provider: {type: String, empty: false}, + project: {type: String, empty: false}, + deploy_env: {type: String, empty: false}, + stack_template: {type: String, empty: false}, + cloud_stack_id: {type: String, empty: false} + + def initialize attrs={} + self.id = attrs['id'] + self.provider = attrs['provider'] + self.project = attrs['project'] + self.deploy_env = attrs['deploy_env'] + self.stack_template = attrs['stack_template'] + self.cloud_stack_id = attrs['cloud_stack_id'] + self + end + + def to_hash_without_id + { + provider: provider, + project: self.project, + deploy_env: self.deploy_env, + stack_template: self.stack_template, + cloud_stack_id: self.cloud_stack_id + } + end + + # attrs should include: + # - id (String) + # - provider (String) + # - deploy_env (String) + # - stack_template (String) + def self.create(attrs) + model = new(attrs) + model.create_stack_in_cloud! + model + end + + def self.create_from_bson(attrs) + attrs['id'] = attrs["_id"] + self.new(attrs) + end + + def create_stack_in_cloud! + raise 'override me' + end + +end diff --git a/devops-service/db/mongo/models/stack/stack_ec2.rb b/devops-service/db/mongo/models/stack/stack_ec2.rb new file mode 100644 index 0000000..41b2a3f --- /dev/null +++ b/devops-service/db/mongo/models/stack/stack_ec2.rb @@ -0,0 +1,8 @@ +class StackEc2 < StackBase + + def create_stack_in_cloud! + # create stack in AWS + self.cloud_stack_id = 'arn:aws:cloudformation:us-east-1:123456789:stack/MyStack/aaf549a0-a413-11df-adb3-5081b3858e83' + end + +end diff --git a/devops-service/db/mongo/models/stack/stack_factory.rb b/devops-service/db/mongo/models/stack/stack_factory.rb new file mode 100644 index 0000000..3576d33 --- /dev/null +++ b/devops-service/db/mongo/models/stack/stack_factory.rb @@ -0,0 +1,26 @@ +require_relative "stack_base" +require_relative "stack_openstack" +require_relative "stack_ec2" + +class StackFactory + + def self.create(provider, attrs) + get_class(provider).create(attrs) + end + + def self.create_from_bson(provider, attrs) + get_class(provider).create_from_bson(attrs) + end + + def self.get_class(provider) + case provider + when ::Provider::Openstack::PROVIDER + StackOpenstack + when ::Provider::Ec2::PROVIDER + StackEc2 + else + raise InvalidRecord.new "Invalid provider: '#{provider}'" + end + end + +end diff --git a/devops-service/db/mongo/models/stack/stack_openstack.rb b/devops-service/db/mongo/models/stack/stack_openstack.rb new file mode 100644 index 0000000..3022b21 --- /dev/null +++ b/devops-service/db/mongo/models/stack/stack_openstack.rb @@ -0,0 +1,10 @@ +class StackOpenstack < StackBase + + def create_stack_in_cloud! + provider = ::Provider::ProviderFactory.get('openstack') + provider.create_stack(self) + # # create stack in Openstack + # self.cloud_stack_id = '4c712026-dcd5-4664-90b8-0915494c1332' + end + +end 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 new file mode 100644 index 0000000..6453a93 --- /dev/null +++ b/devops-service/db/mongo/models/stack_template/stack_template_base.rb @@ -0,0 +1,73 @@ +require 'tempfile' +require 'securerandom' + +class StackTemplateBase < MongoModel + + attr_accessor :id, :template_url, :template_json, :provider + + # Few words about template_url: + # In Amazon Cloudformation the template file must be stored on an Amazon S3 bucket, + # but for Openstack stacks it isn't neccessary (your may use local file). + # I decided to enforce template_url strategy using in openstack to reach more common + # interface between different providers' stack templates. + + types id: {type: String, empty: false}, + provider: {type: String, empty: false}, + template_json: {type: String, empty: false}, + template_url: {type: String, empty: false} + + def initialize(attrs) + self.id = attrs['id'] + self.template_json = attrs['template_json'].to_s + self.template_url = attrs['template_url'] + self.provider = attrs['provider'] + self + end + + def to_hash_without_id + { + provider: provider, + template_json: template_json, + template_url: template_url + } + end + + # do not forget to destroy template files on template destroying + def delete_template_file_from_storage + raise 'Override me' + end + + # attrs should include: + # - id (String) + # - provider (String) + # - template_json (String) + def self.create(attrs) + json = attrs['template_json'] + attrs['template_url'] = generate_template_file_and_upload_to_storage(attrs['id'], json) + new(attrs) + end + + def self.create_from_bson(attrs) + attrs['id'] = attrs["_id"] + self.new(attrs) + end + + class << self + private + + def generate_template_file_and_upload_to_storage(id, json) + tempfile = Tempfile.new('foo') + tempfile.write(json) + secure_filename = "#{id}-#{SecureRandom.hex}.template" + upload_file_to_storage(secure_filename, tempfile.path) + ensure + tempfile.close + tempfile.unlink + end + + def upload_file_to_storage(filename, file_path) + raise 'Override me' + end + + end +end diff --git a/devops-service/db/mongo/models/stack_template/stack_template_ec2.rb b/devops-service/db/mongo/models/stack_template/stack_template_ec2.rb new file mode 100644 index 0000000..ed6b0aa --- /dev/null +++ b/devops-service/db/mongo/models/stack_template/stack_template_ec2.rb @@ -0,0 +1,15 @@ +class StackTemplateEc2 < StackTemplateBase + + def delete_template_file_from_storage + raise 'Implement me' + end + + class << self + private + + def upload_file_to_storage(filename, path) + "https://s3.amazonaws.com/#{filename}" + end + end + +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 new file mode 100644 index 0000000..95b4a27 --- /dev/null +++ b/devops-service/db/mongo/models/stack_template/stack_template_factory.rb @@ -0,0 +1,26 @@ +require_relative "stack_template_base" +require_relative "stack_template_openstack" +require_relative "stack_template_ec2" + +class StackTemplateFactory + + def self.create(provider, attrs) + get_class(provider).create(attrs) + end + + def self.create_from_bson(provider, attrs) + get_class(provider).create_from_bson(attrs) + end + + def self.get_class(provider) + case provider + when ::Provider::Openstack::PROVIDER + StackTemplateOpenstack + when ::Provider::Ec2::PROVIDER + StackTemplateEc2 + else + raise InvalidRecord.new "Invalid provider: '#{provider}'" + end + end + +end diff --git a/devops-service/db/mongo/models/stack_template/stack_template_openstack.rb b/devops-service/db/mongo/models/stack_template/stack_template_openstack.rb new file mode 100644 index 0000000..d7885d8 --- /dev/null +++ b/devops-service/db/mongo/models/stack_template/stack_template_openstack.rb @@ -0,0 +1,15 @@ +class StackTemplateOpenstack < StackTemplateBase + + def delete_template_file_from_storage + raise 'Implement me' + end + + class << self + private + + def upload_file_to_storage(filename, path) + "https://openstack_host/v1/my_account/#{filename}" + end + end + +end diff --git a/devops-service/db/mongo/models/user.rb b/devops-service/db/mongo/models/user.rb index fdb04e3..abb5fa4 100644 --- a/devops-service/db/mongo/models/user.rb +++ b/devops-service/db/mongo/models/user.rb @@ -2,8 +2,6 @@ require "db/exceptions/invalid_record" require "exceptions/invalid_command" require "db/mongo/models/mongo_model" -#require "common/fog" - class User < MongoModel ROOT_USER_NAME = 'root' @@ -70,28 +68,17 @@ class User < MongoModel o end + def can?(command, privilege) + p = self.privileges[command] || [] + p.include?(privilege) + end + def check_privilege cmd, priv p = self.privileges[cmd] return false if p.nil? return p.include?(priv) end -=begin - def check_privilege_read cmd - check_privilege_r_w cmd, "r" - end - - def check_privilege_write cmd - check_privilege_r_w cmd, "w" - end - - def check_privilege_r_w cmd, flag - p = self.privileges[cmd] - return false if p.nil? - return p == flag || p == 'rw' - end -=end - def self.create_root root = User.new({'username' => ROOT_USER_NAME, 'password' => ROOT_PASSWORD}) root.privileges = root.all_privileges @@ -101,20 +88,25 @@ class User < MongoModel private def privileges_with_value v, options={} - { - "flavor" => v, - "group" => v, - "image" => v, - "project" => v, - "server" => v, - "key" => v, - "user" => v, - "filter" => v, - "network" => v, - "provider" => v, - "script" => v, - "templates" => v - }.merge(options) + privileges = {} + [ + 'flavor', + 'group', + 'image', + 'project', + 'server', + 'key', + 'user', + 'filter', + 'network', + 'provider', + 'script', + 'templates', + 'stack_template', + 'stack' + ].each { |t| privileges.store(t, value) } + + privileges.merge(options) end end diff --git a/devops-service/db/mongo/mongo_connector.rb b/devops-service/db/mongo/mongo_connector.rb index 15ad0b4..781223b 100644 --- a/devops-service/db/mongo/mongo_connector.rb +++ b/devops-service/db/mongo/mongo_connector.rb @@ -1,438 +1,77 @@ require "mongo" -require "date" +require "forwardable" -require "db/exceptions/record_not_found" -require "db/exceptions/invalid_record" -require "exceptions/invalid_privileges" - -require "db/mongo/models/project" -require "db/mongo/models/image" -require "db/mongo/models/report" -require "db/mongo/models/key" -require "db/mongo/models/project" -require "db/mongo/models/server" -require "db/mongo/models/user" +require "db/mongo/connectors/base" +Dir["db/mongo/connectors/helpers/*.rb"].each {|file| require file } +Dir["db/mongo/connectors/*.rb"].each {|file| require file } include Mongo class MongoConnector + extend Forwardable + + delegate( + [:images, :image, :image_insert, :image_delete, :image_update] => :images_connector, + [:stack_templates, :stack_template, :stack_template_insert, :stack_template_delete] => :stack_templates_connector, + [:stacks, :stack, :stack_insert, :stack_delete] => :stacks_connector, + [:available_images, :add_available_images, :delete_available_images] => :filters_connector, + [:project, :projects_all, :projects, :project_names_with_envs, + :projects_by_image, :projects_by_user, :project_insert, :project_update, + :project_delete, :is_project_exists?, :check_project_auth] => :projects_connector, + [:servers_find, :servers, :servers_by_names, :server_by_instance_id, + :server_by_chef_node_name, :servers_by_key, :server_insert, + :server_delete, :server_update, :server_set_chef_node_name] => :servers_connector, + [:user_auth, :user, :users, :users_names, :user_insert, :user_delete, + :user_update, :create_root_user, :check_user_privileges] => :users_connector, + [:keys, :key, :key_insert, :key_delete] => :keys_connector, + [:save_report, :report, :reports] => :reports_connector, + [:statistic] => :statistics_connector + ) def initialize(db, host, port=27017, user=nil, password=nil) - @mongo_client = MongoClient.new(host, port) - @db = @mongo_client.db(db) + @db = MongoClient.new(host, port).db(db) @db.authenticate(user, password) unless user.nil? or password.nil? - @projects = @db.collection("projects") - @images = @db.collection("images") - @servers = @db.collection("servers") - @filters = @db.collection("filters") - @keys = @db.collection("keys") - @users = @db.collection("users") - @statistic = @db.collection("statistic") - @reports = @db.collection("reports") end - def images provider=nil - q = (provider.nil? ? {} : {"provider" => provider}) - @images.find(q).to_a.map {|bi| Image.create_from_bson bi} + private + + def images_connector + @image_connector ||= Connectors::Image.new(@db) end - def image id - i = @images.find(create_query("_id" => id)).to_a[0] - raise RecordNotFound.new("Image '#{id}' does not exist") if i.nil? - Image.create_from_bson i + def stack_templates_connector + @stack_templates_connector ||= Connectors::StackTemplate.new(@db) end - def image_insert image - image.validate! - @images.insert(image.to_mongo_hash) - rescue Mongo::OperationFailure => e - if e.message =~ /^11000/ - raise InvalidRecord.new("Duplicate key error: image with id '#{image.id}'") - end + def stacks_connector + @stack_connector ||= Connectors::Stack.new(@db) end - def image_update image - image.validate! - @images.update(create_query({"_id" => image.id}), create_query(image.to_mongo_hash)) - rescue Mongo::OperationFailure => e - if e.message =~ /^11000/ - raise InvalidRecord.new("Duplicate key error: image with id '#{image.id}'") - end + def filters_connector + @filter_connector ||= Connectors::Filter.new(@db) end - def image_delete id - r = @images.remove(create_query("_id" => id)) - raise RecordNotFound.new("Image '#{id}' not found") if r["n"] == 0 - r + def projects_connector + @projects_connector ||= Connectors::Project.new(@db) end - def available_images provider - f = @filters.find(create_query("type" => "image", "provider" => provider)).to_a[0] - return [] if f.nil? - f["images"] + def servers_connector + @servers_connector ||= Connectors::Server.new(@db) end - def add_available_images images, provider - return unless images.is_a?(Array) - f = @filters.find(create_query("type" => "image", "provider" => provider)).to_a[0] - if f.nil? - @filters.insert(create_query({"type" => "image", "provider" => provider, "images" => images})) - return images - else - f["images"] |= images - @filters.update({"_id" => f["_id"]}, f) - return f["images"] - end + def users_connector + @users_connector ||= Connectors::User.new(@db) end - def delete_available_images images, provider - return unless images.is_a?(Array) - f = @filters.find(create_query("type" => "image", "provider" => provider)).to_a[0] - unless f.nil? - f["images"] -= images - @filters.update({"_id" => f["_id"]}, f) - return f["images"] - end - [] + def keys_connector + @keys_connector ||= Connectors::Key.new(@db) end - def is_project_exists? project - self.project project.id - return true - rescue RecordNotFound => e - return false + def reports_connector + @reports_connector ||= Connectors::Report.new(@db) end - def project_insert project - project.validate! - @projects.insert(create_query(project.to_mongo_hash)) - rescue Mongo::OperationFailure => e - if e.message =~ /^11000/ - raise InvalidRecord.new("Duplicate key error: project with id '#{project.id}'") - end - end - - def project name - p = @projects.find(create_query("_id" => name)).to_a[0] - raise RecordNotFound.new("Project '#{name}' does not exist") if p.nil? - Project.create_from_bson p - end - - def projects_all - p = @projects.find() - p.to_a.map {|bp| Project.create_from_bson bp} - end - - def projects list=nil, type=nil, fields=[] - q = (list.nil? ? {} : {"_id" => {"$in" => list}}) - case type - when :multi - q["type"] = "multi" - #else - # q["type"] = {"$exists" => false} - end - res = @projects.find(create_query(q), :fields => fields) - a = res.to_a - a.map {|bp| Project.create_from_bson bp} - end - - # names - array of project names - def project_names_with_envs names=nil - # db.projects.aggregate({$unwind:"$deploy_envs"}, {$project:{"deploy_envs.identifier":1}}, {$group:{_id:"$_id", envs: {$addToSet: "$deploy_envs.identifier"}}}) - q = [] - unless names.nil? - q.push({ - "$match" => { - "_id" => { - "$in" => names - } - } - }) - end - q.push({ - "$unwind" => "$deploy_envs" - }) - q.push({ - "$project" => { - "deploy_envs.identifier" => 1 - } - }) - q.push({ - "$group" => { - "_id" => "$_id", - "envs" => { - "$addToSet" => "$deploy_envs.identifier" - } - } - }) - res = @projects.aggregate(q) - r = {} - res.each do |ar| - r[ar["_id"]] = ar["envs"] - end - return r - end - - def projects_by_image image - @projects.find(create_query("deploy_envs.image" => image)).to_a.map {|bp| Project.create_from_bson bp} - end - - def projects_by_user user - @projects.find(create_query("deploy_envs.users" => user)).to_a.map {|bp| Project.create_from_bson bp} - end - - def project_delete name - r = @projects.remove(create_query("_id" => name)) - raise RecordNotFound.new("Project '#{name}' not found") if r["n"] == 0 - end - - def project_update project - project.validate! - @projects.update(create_query({"_id" => project.id}), project.to_mongo_hash) - rescue Mongo::OperationFailure => e - if e.message =~ /^11000/ - raise InvalidRecord.new("Duplicate key error: project with id '#{project.id}'") - end - end - - def servers_find q, fields - s = if fields.nil? - @servers.find(create_query(q)) - else - @servers.find(create_query(q), :fields => fields) - end - s.to_a.map{|bs| Server.create_from_bson bs} - end - - def servers p=nil, env=nil, names=nil, reserved=nil, fields=:all - q = {} - q["project"] = p unless p.nil? or p.empty? - q["deploy_env"] = env unless env.nil? or env.empty? - q["chef_node_name"] = {"$in" => names} unless names.nil? or names.class != Array - q["reserved_by"] = {"$ne" => nil} unless reserved.nil? - f = nil - unless fields == :all - f = fields - ["_id", "chef_node_name"].each do |k| - f.push(k) unless f.include?(k) - end - end - servers_find(q, f) - end - - def servers_by_names names - q = {} - q["chef_node_name"] = {"$in" => names} unless names.nil? or names.class != Array - @servers.find(create_query(q)).to_a.map{|bs| Server.create_from_bson bs} - end - - def server_by_instance_id id - find_server "_id" => id - end - - def server_by_chef_node_name name - find_server "chef_node_name" => name - end - - def servers_by_key key_name - @servers.find(create_query("key" => key_name)).to_a.map {|bs| Server.create_from_bson bs} - end - - def server_insert s - #s.validate! - s.created_at = Time.now - @servers.insert(create_query(s.to_mongo_hash)) - end - - def server_delete id - @servers.remove(create_query("_id" => id)) - end - - def server_update server - @servers.update({"_id" => server.id}, server.to_hash_without_id) - end - - def server_set_chef_node_name server - @servers.update({"_id" => server.id}, {"$set" => {"chef_node_name" => server.chef_node_name}}) - end - - def keys - @keys.find(create_query).to_a.map {|bi| Key.create_from_bson bi} - end - - def key id, scope=nil - q = { - "_id" => id - } - q["scope"] = scope unless scope.nil? - k = @keys.find(create_query(q)).to_a[0] - raise RecordNotFound.new("Key '#{id}' does not exist") if k.nil? - Key.create_from_bson k - end - - def key_insert key - key.validate! - @keys.insert(create_query(key.to_mongo_hash)) - rescue Mongo::OperationFailure => e - if e.message =~ /^11000/ - raise InvalidRecord.new("Duplicate key error: key with id '#{key.id}'") - end - end - - def key_delete id - r = @keys.remove(create_query("_id" => id, "scope" => Key::USER)) - raise RecordNotFound.new("Key '#{id}' not found") if r["n"] == 0 - r - end - - def user_auth user, password - u = @users.find("_id" => user, "password" => password).to_a[0] - raise RecordNotFound.new("Invalid username or password") if u.nil? - end - - def user id - u = @users.find("_id" => id).to_a[0] - raise RecordNotFound.new("User '#{id}' does not exist") if u.nil? - User.create_from_bson u - end - - def users array=nil - q = {} - q["_id"] = {"$in" => array} if array.is_a?(Array) - @users.find(q).to_a.map {|bi| User.create_from_bson bi} - end - - def users_names array=nil - q = {} - q["_id"] = {"$in" => array} if array.is_a?(Array) - @users.find({}, :fields => ["_id"]).to_a.map{|u| u["_id"]} - end - - def user_insert user - user.validate! - @users.insert(user.to_mongo_hash) - rescue Mongo::OperationFailure => e - if e.message =~ /^11000/ - raise InvalidRecord.new("Duplicate key error: user with id '#{user.id}'") - end - end - - def user_delete id - r = @users.remove("_id" => id) - raise RecordNotFound.new("User '#{id}' not found") if r["n"] == 0 - r - end - - def user_update user - user.validate! - @users.update({"_id" => user.id}, user.to_mongo_hash) - rescue Mongo::OperationFailure => e - if e.message =~ /^11000/ - raise InvalidRecord.new("Duplicate key error: user with id '#{user.id}'") - end - end - - def create_root_user - begin - u = user("root") - rescue RecordNotFound => e - root = User.create_root - @users.insert(root.to_mongo_hash) - end - end - - def check_user_privileges id, cmd, priv - user = self.user(id) - case priv - when "r", "w", "x" - raise InvalidPrivileges.new("Access denied for '#{user.id}'") unless user.check_privilege cmd, priv - else - raise InvalidPrivileges.new("Access internal problem with privilege '#{priv}'") - end - end - - def check_project_auth project_id, env, user_id - p = @projects.find(create_query("_id" => project_id)).to_a[0] - raise RecordNotFound.new("Project '#{project_id}' does not exist") if p.nil? - project = Project.create_from_bson p - raise InvalidPrivileges.new("User '#{user_id}' unauthorized to work with project '#{project_id}'") unless project.check_authorization(user_id, env) - project - end - - def statistic user, path, method, body, response_code - @statistic.insert({:user => user, :path => path, :method => method, :body => body, :response_code => response_code, :date => Time.now}) - end - - def save_report r - r.created_at = Time.new - @reports.insert(r.to_mongo_hash) - end - - def reports options={} - date = {} - if options.has_key?("date_from") or options.has_key?("date_to") - if options.has_key?("date_from") - begin - d = Date.parse(options["date_from"]) - date["$gte"] = d.to_time - rescue ArgumentError - end - end - if options.has_key?("date_to") - begin - d = Date.parse(options["date_to"]) - date["$lt"] = d.to_time - rescue ArgumentError - end - end - options.delete("date_from") - options.delete("date_to") - options["created_at"] = date unless date.empty? - end - if options.has_key?("type") - begin - options["type"] = Integer(options["type"]) - rescue ArgumentError - options.delete("type") - end - end - sort = -1 - if options.has_key?("sort") - sort = 1 if options["sort"] == "asc" - options.delete("sort") - end - @reports.find(options).to_a.map{|e| Report.new(e)} - end - - def report id - r = @reports.find({"_id" => id}).to_a[0] - raise RecordNotFound.new("Report '#{id}' does not exist") if r.nil? - Report.new(r) - end - - def set_report_status id, status - @reports.update({"_id" => id}, {"$set" => {"status" => status, "updated_at" => Time.new}}) - end - -private - def find_server params - s = @servers.find(create_query(params)).to_a[0] - if s.nil? - if params.has_key? "_id" - raise RecordNotFound.new("No server by instance id '#{params["_id"]}' found") - elsif params.has_key? "chef_node_name" - raise RecordNotFound.new("No server by node name '#{params["chef_node_name"]}' found") - end - end - Server.create_from_bson s - end - - def create_query q={} - q - end - - def create_query_with_provider provider, q={} - q["provider"] = provider - q + def statistics_connector + @statistics_connector ||= Connectors::Statistic.new(@db) end end diff --git a/devops-service/db/validators/all.rb b/devops-service/db/validators/all.rb index 060e313..f11a5d2 100644 --- a/devops-service/db/validators/all.rb +++ b/devops-service/db/validators/all.rb @@ -1,8 +1,16 @@ module Validators class Helpers; end class DeployEnv; end + class Key; end + class Image; end end require "db/validators/base" -Dir["db/validators/helpers/*.rb"].each {|file| require file } -Dir["db/validators/deploy_env/*.rb"].each {|file| require file } +[ + 'db/validators/helpers/*.rb', + 'db/validators/deploy_env/*.rb', + 'db/validators/key/*.rb', + 'db/validators/image/*.rb' +].each do |files_regexp| + Dir[files_regexp].each {|file| require file } +end diff --git a/devops-service/db/validators/base.rb b/devops-service/db/validators/base.rb index 51c91c9..a195053 100644 --- a/devops-service/db/validators/base.rb +++ b/devops-service/db/validators/base.rb @@ -16,4 +16,25 @@ class Validators::Base def message raise 'override me' end -end \ No newline at end of file + + class << self + private + + # this method delegates @valid? and @message methods to helper validator, passed as block + def delegate_to_helper_validator(&block) + + define_method :helper_validator do + @helper_validator ||= self.instance_eval(&block) + end + + define_method :valid? do + self.helper_validator.valid? + end + + define_method :message do + self.helper_validator.message + end + end + end + +end diff --git a/devops-service/db/validators/deploy_env/run_list.rb b/devops-service/db/validators/deploy_env/run_list.rb index dba7c18..c37eec1 100644 --- a/devops-service/db/validators/deploy_env/run_list.rb +++ b/devops-service/db/validators/deploy_env/run_list.rb @@ -4,17 +4,6 @@ module Validators class DeployEnv::RunList < Base - def initialize(model) - super(model) - @helper_validator = Helpers::RunList.new(@model.run_list) - end - - def valid? - @helper_validator.valid? - end - - def message - @helper_validator.message - end + delegate_to_helper_validator { Helpers::RunList.new(@model.run_list) } end -end \ No newline at end of file +end diff --git a/devops-service/db/validators/helpers/file_existence.rb b/devops-service/db/validators/helpers/file_existence.rb new file mode 100644 index 0000000..16c6196 --- /dev/null +++ b/devops-service/db/validators/helpers/file_existence.rb @@ -0,0 +1,12 @@ +module Validators + class Helpers::FileExistence < Base + + def valid? + File.exist?(@model) + end + + def message + "File does not exist: '#{@model}'" + end + end +end diff --git a/devops-service/db/validators/image/bootstrap_template.rb b/devops-service/db/validators/image/bootstrap_template.rb new file mode 100644 index 0000000..5bf37e4 --- /dev/null +++ b/devops-service/db/validators/image/bootstrap_template.rb @@ -0,0 +1,20 @@ +require "commands/bootstrap_templates" +module Validators + class Image::BootstrapTemplate < Base + + include BootstrapTemplatesCommands + + def valid? + if @model.bootstrap_template + templates = get_templates + templates.include?(@model.bootstrap_template) + else + true + end + end + + def message + "Invalid bootstrap template '#{@model.bootstrap_template}' for image '#{@model.id}'" + end + end +end diff --git a/devops-service/db/validators/image/image_in_filter.rb b/devops-service/db/validators/image/image_in_filter.rb new file mode 100644 index 0000000..19936bb --- /dev/null +++ b/devops-service/db/validators/image/image_in_filter.rb @@ -0,0 +1,16 @@ +require "commands/image" +module Validators + class Image::ImageInFilter < Base + + include ImageCommands + + def valid? + images = get_images(DevopsService.mongo, @model.provider) + images.map{|i| i["id"]}.include?(@model.id) + end + + def message + "Invalid image id '#{@model.id}' for provider '#{@model.provider}', please check image filters" + end + end +end diff --git a/devops-service/db/validators/key/file_existence.rb b/devops-service/db/validators/key/file_existence.rb new file mode 100644 index 0000000..a14a6b5 --- /dev/null +++ b/devops-service/db/validators/key/file_existence.rb @@ -0,0 +1,7 @@ +module Validators + class Key::FileExistence < Base + + delegate_to_helper_validator { Helpers::FileExistence.new(@model.path) } + + end +end diff --git a/devops-service/db/validators/key/scope.rb b/devops-service/db/validators/key/scope.rb new file mode 100644 index 0000000..0e89a03 --- /dev/null +++ b/devops-service/db/validators/key/scope.rb @@ -0,0 +1,12 @@ +module Validators + class Key::Scope < Base + + def valid? + [::Key::SYSTEM, ::Key::USER].include?(@model.scope) + end + + def message + "Key parameter 'scope' is invalid" + end + end +end diff --git a/devops-service/lib/string_helper.rb b/devops-service/lib/string_helper.rb new file mode 100644 index 0000000..74a6727 --- /dev/null +++ b/devops-service/lib/string_helper.rb @@ -0,0 +1,35 @@ +module StringHelper + extend self + + # from Rails' ActiveSupport + def underscore(string) + string.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + tr("-", "_"). + downcase + end + + def underscore_class(klass, without_ancestors=true) + class_name = if without_ancestors + klass.to_s.split('::').last + else + klass.to_s + end + StringHelper.underscore(class_name) + end + + # from Rails' ActiveSupport + def camelize(term) + string = term.to_s + string = string.sub(/^[a-z\d]*/) { $&.capitalize } + string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" } + string.gsub!(/\//, '::') + string + end + + # rough simplification + def pluralize(string) + "#{string}s" + end +end diff --git a/devops-service/providers/all.rb b/devops-service/providers/all.rb new file mode 100644 index 0000000..80e40a7 --- /dev/null +++ b/devops-service/providers/all.rb @@ -0,0 +1,13 @@ +["ec2", "openstack", "static"].each do |provider| + begin + require_relative provider + + provider_stub_path = File.expand_path("../#{provider}_stub.rb", __FILE__) + + if DevopsService.debug? && File.exists?(provider_stub_path) + require provider_stub_path + end + rescue LoadError => e + puts "Can not load provider '#{provider}': " + e.message + end +end diff --git a/devops-service/providers/openstack.rb b/devops-service/providers/openstack.rb index 32b58b0..101c559 100644 --- a/devops-service/providers/openstack.rb +++ b/devops-service/providers/openstack.rb @@ -190,6 +190,11 @@ module Provider def network connection_network(self.connection_options) end + + def create_stack(stack) + byebug + result = orchestration.create_stack(stack.id, {template_url: stack.template_url}) + end private def convert_groups list res = {} @@ -211,5 +216,9 @@ module Provider res end + def orchestration + @connection ||= Fog::Orchestration::OpenStack.new(connection_options) + end + end end diff --git a/devops-service/providers/provider_factory.rb b/devops-service/providers/provider_factory.rb index af5afe5..05a1711 100644 --- a/devops-service/providers/provider_factory.rb +++ b/devops-service/providers/provider_factory.rb @@ -20,14 +20,12 @@ module Provider end def self.init conf + + # require providers here to get access to debug properties + require 'providers/all' + ["ec2", "openstack", "static"].each do |p| begin - require "providers/#{p}" - - if File.exist?("providers/#{p}_stub.rb") - require "providers/#{p}_stub" - end - o = Provider.const_get(p.capitalize).new(conf) if o.configured? @@providers[p] = o @@ -36,9 +34,6 @@ module Provider rescue => e puts "Error while loading provider '#{p}': " + e.message next - rescue LoadError => e - puts "Can not load provider '#{p}': " + e.message - next end end end