merged with achuchkalov

This commit is contained in:
amartynov 2015-02-12 13:01:05 +03:00
parent a0dd907afc
commit fa3450e03e
49 changed files with 1172 additions and 578 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"])

View File

@ -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

View File

@ -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"]

View File

@ -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

View File

@ -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

View File

@ -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|

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -16,4 +16,25 @@ class Validators::Base
def message
raise 'override me'
end
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
module Validators
class Key::FileExistence < Base
delegate_to_helper_validator { Helpers::FileExistence.new(@model.path) }
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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