440 lines
17 KiB
Ruby
440 lines
17 KiB
Ruby
|
|
require "commands/status"
|
||
|
|
require "db/mongo/models/project"
|
||
|
|
require "db/mongo/models/user"
|
||
|
|
require "workers/project_test_worker"
|
||
|
|
require "app/api3/parsers/project"
|
||
|
|
require "lib/project/type/types_factory"
|
||
|
|
require "lib/executors/server_executor"
|
||
|
|
|
||
|
|
require_relative "request_handler"
|
||
|
|
|
||
|
|
module Devops
|
||
|
|
module API3
|
||
|
|
module Handler
|
||
|
|
class Project < RequestHandler
|
||
|
|
|
||
|
|
set_parser Devops::API3::Parser::ProjectParser
|
||
|
|
|
||
|
|
include Devops::API3::Helpers
|
||
|
|
|
||
|
|
extend StatusCommands
|
||
|
|
|
||
|
|
def project_types
|
||
|
|
Devops::TypesFactory.types_names
|
||
|
|
end
|
||
|
|
|
||
|
|
def projects
|
||
|
|
user = parser.current_user
|
||
|
|
query = {}
|
||
|
|
if user != Devops::Model::User::ROOT_USER_NAME
|
||
|
|
query['$or'] = [{owner: user}, {project_users: user}]
|
||
|
|
end
|
||
|
|
parser.archived_projects ? query["archived"] = true : query["archived"] = {"$exists" => false}
|
||
|
|
Devops::Model::Project.where(query)
|
||
|
|
#Devops::Db.connector.projects(nil, nil, parser.projects, parser.archived_projects)
|
||
|
|
end
|
||
|
|
|
||
|
|
def project id
|
||
|
|
Devops::Model::Project.find(id)
|
||
|
|
rescue Mongoid::Errors::DocumentNotFound
|
||
|
|
raise Devops::Exception::RecordNotFound.new("Project '#{id}' not found")
|
||
|
|
end
|
||
|
|
|
||
|
|
def project_environments(id)
|
||
|
|
project = project(id)
|
||
|
|
project.environments
|
||
|
|
end
|
||
|
|
|
||
|
|
def get_project_with_environment id, env
|
||
|
|
#Devops::Model::Project.where({'environments.id' => env}).only('environments.$.id').find(id) ??? TODO: projection with array index
|
||
|
|
project = Devops::Model::Project.where({'environments.id' => env}).find(id)
|
||
|
|
project.environments = [ project.environments.detect{|de| de.id == env} ]
|
||
|
|
project
|
||
|
|
rescue Mongoid::Errors::DocumentNotFound
|
||
|
|
raise Devops::Exception::RecordNotFound.new("Project '#{id}' with deploy environment '#{env}' not found")
|
||
|
|
end
|
||
|
|
|
||
|
|
def project_environment(id, env)
|
||
|
|
get_project_with_environment(id, env).environments[0]
|
||
|
|
end
|
||
|
|
|
||
|
|
def project_env_servers id, env
|
||
|
|
project = get_project_with_environment(id, env)
|
||
|
|
user = parser.current_user
|
||
|
|
users = [ project.owner ] + project.project_users
|
||
|
|
Devop::Exception::AccessError.new("User can not read this project") unless users.include?(user)
|
||
|
|
Devops::Model::Server.where({'project' => id, 'environment' => env}).all
|
||
|
|
end
|
||
|
|
|
||
|
|
def pvroject_servers id
|
||
|
|
project = project(id)
|
||
|
|
user = parser.current_user
|
||
|
|
users = [ project.owner ] + project.project_users
|
||
|
|
Devop::Exception::AccessError.new("User can not read this project") unless users.include?(user)
|
||
|
|
Devops::Model::Server.where({'project' => id}).all
|
||
|
|
end
|
||
|
|
|
||
|
|
def add_project_users id
|
||
|
|
users = parser.users
|
||
|
|
#TODO: projection
|
||
|
|
dbusers = Devops::Model::User.where('_id.in' => users).map{|u| u.id}
|
||
|
|
invalid_users = users - dbusers
|
||
|
|
raise Devops::Exception::ValidationError.new("Invalid users: '#{invalid_users.join("', '")}'") unless invalid_users.empty?
|
||
|
|
add_to_project_array id, :project_users, users
|
||
|
|
end
|
||
|
|
|
||
|
|
def delete_project_users id
|
||
|
|
pull_from_project_array id, :project_users, parser.users
|
||
|
|
end
|
||
|
|
|
||
|
|
def set_project_description id
|
||
|
|
db_project = project(id)
|
||
|
|
db_project.description = parser.set_description
|
||
|
|
db_project.save!
|
||
|
|
end
|
||
|
|
|
||
|
|
def add_to_project_array id, key, list_to_add
|
||
|
|
db_project = project(id)
|
||
|
|
set = Set.new(db_project.send(key)).merge(list_to_add)
|
||
|
|
db_project.add_to_set({key => list_to_add})
|
||
|
|
set.to_a
|
||
|
|
end
|
||
|
|
|
||
|
|
def pull_from_project_array id, key, list_to_pull
|
||
|
|
db_project = project(id)
|
||
|
|
set = Set.new(db_project.send(key)).subtract(list_to_pull)
|
||
|
|
db_project.pull_all({key => list_to_pull})
|
||
|
|
set.to_a
|
||
|
|
end
|
||
|
|
|
||
|
|
def set_project_run_list id
|
||
|
|
db_project = project(id)
|
||
|
|
db_project.run_list = parser.run_list
|
||
|
|
db_project.save!
|
||
|
|
db_project.run_list
|
||
|
|
end
|
||
|
|
|
||
|
|
def add_project_run_list id
|
||
|
|
add_to_project_array id, :run_list, parser.run_list
|
||
|
|
end
|
||
|
|
|
||
|
|
def delete_project_run_list id
|
||
|
|
pull_from_project_array id, :run_list, parser.run_list
|
||
|
|
end
|
||
|
|
|
||
|
|
def add_project_env_users id, env
|
||
|
|
project = get_project_with_environment(id, env)
|
||
|
|
users = parser.users
|
||
|
|
dbusers = project.project_users + [project.owner]
|
||
|
|
invalid_users = users - dbusers
|
||
|
|
raise Devops::Exception::ValidationError.new("User(s) '#{invalid_users.join("', '")}' is/are not a project user(s)") unless invalid_users.empty?
|
||
|
|
Devops::Model::Project.where({'_id' => id, 'environments.id' => env}).add_to_set('environments.$.users' => users)
|
||
|
|
Set.new(project.environments[0].users + users).to_a
|
||
|
|
end
|
||
|
|
|
||
|
|
def delete_project_env_users id, env
|
||
|
|
project = get_project_with_environment(id, env)
|
||
|
|
users = parser.users
|
||
|
|
Devops::Model::Project.where({'_id' => id, 'environments.id' => env}).pull_all('environments.$.users' => users)
|
||
|
|
project.environments[0].users - users
|
||
|
|
end
|
||
|
|
|
||
|
|
def project_env_stacks id, env
|
||
|
|
# check if project exists
|
||
|
|
get_project_with_environment(id, env)
|
||
|
|
Devops::Model::StackBase.where({project: id, environment: env}).all
|
||
|
|
end
|
||
|
|
|
||
|
|
def create_project
|
||
|
|
p = parser.create_project
|
||
|
|
|
||
|
|
p.owner = parser.current_user
|
||
|
|
p.environments.each do |env|
|
||
|
|
env.add_users [parser.current_user]
|
||
|
|
end
|
||
|
|
p.save!
|
||
|
|
info = "Project '#{p.id}' has been created."
|
||
|
|
DevopsLogger.logger.info info
|
||
|
|
info
|
||
|
|
end
|
||
|
|
|
||
|
|
=begin
|
||
|
|
def set_project_components id
|
||
|
|
body = parser.set_project_components
|
||
|
|
project = Devops::Db.connector.project(id)
|
||
|
|
project.components = body["components"]
|
||
|
|
project.validate_components
|
||
|
|
Devops::Db.connector.project_update_field id, "components", body["components"]
|
||
|
|
"Updated project '#{project.id}' with components '#{body["components"].inspect}'"
|
||
|
|
end
|
||
|
|
=end
|
||
|
|
|
||
|
|
def add_environment id
|
||
|
|
db_project = project(id)
|
||
|
|
env = parser.add_environment
|
||
|
|
env.validate!
|
||
|
|
env.add_users [parser.current_user]
|
||
|
|
begin
|
||
|
|
db_env = db_project.environment(env.id)
|
||
|
|
raise Devops::Exception::ValidationError.new("Can not add new environment for project '#{id}'. Environment '#{env.id}' already exist")
|
||
|
|
rescue Devops::Exception::RecordNotFound => e
|
||
|
|
db_project.add_environment(env)
|
||
|
|
info = "Deploy environment '#{env.id}' has been added to project '#{id}'."
|
||
|
|
DevopsLogger.logger.info info
|
||
|
|
[info, env]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def project_environment_categories project, env
|
||
|
|
project = get_project_with_environment(project, env)
|
||
|
|
penv = project.environments[0]
|
||
|
|
penv.categories
|
||
|
|
end
|
||
|
|
|
||
|
|
def add_category id, env
|
||
|
|
project = get_project_with_environment(id, env)
|
||
|
|
penv = project.environments[0]
|
||
|
|
cat = parser.add_category
|
||
|
|
cat.validate!
|
||
|
|
db_cat = penv.get_category(cat.id)
|
||
|
|
if db_cat.nil?
|
||
|
|
category = project.add_category(penv, cat)
|
||
|
|
roles = category.create_role(id, env)
|
||
|
|
roles_response = Model::Category.present_created_roles(roles)
|
||
|
|
info = "New category '#{cat.id}' has been added to environment '#{env}' of project '#{id}'. "
|
||
|
|
info += roles_response
|
||
|
|
DevopsLogger.logger.info info
|
||
|
|
[info, cat]
|
||
|
|
else
|
||
|
|
raise Devops::Exception::ValidationError.new("Category '#{cat.id}' for project '#{id}' and deploy environment '#{env}' already exist")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def show_category id, env, category
|
||
|
|
project = get_project_with_environment(id, env)
|
||
|
|
penv = project.environments[0]
|
||
|
|
cat = penv.categories.detect{|c| c.id == category}
|
||
|
|
raise Devops::Exception::RecordNotFound.new("Category '#{category}' for project '#{id}' and environment '#{env}' not found") if cat.nil?
|
||
|
|
cat
|
||
|
|
end
|
||
|
|
|
||
|
|
def delete_category id, env, category
|
||
|
|
project = get_project_with_environment(id, env)
|
||
|
|
penv = project.environments[0]
|
||
|
|
project.delete_category(penv, category)
|
||
|
|
info = "Category '#{category}' has been removed from environment '#{env}' of project '#{id}'"
|
||
|
|
DevopsLogger.logger.info info
|
||
|
|
return info
|
||
|
|
end
|
||
|
|
|
||
|
|
def update_environment_field id, environment, field
|
||
|
|
project = Devops::Db.connector.project(id)
|
||
|
|
db_env = project.environment(environment)
|
||
|
|
value = parser.update_environment_field
|
||
|
|
if db_env.respond_to?(field + "=")
|
||
|
|
if field == "id"
|
||
|
|
db_env.rename id, value
|
||
|
|
"Environment '#{environment}' has been renamed to '#{value}'"
|
||
|
|
else
|
||
|
|
db_env.update_field(id, field, value)
|
||
|
|
"Environment's field '#{field}' has been updated"
|
||
|
|
end
|
||
|
|
else
|
||
|
|
raise Devops::Exception::RecordNotFound.new("Field '#{field}' does not exist")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def update_environment id, environment
|
||
|
|
project = Devops::Db.connector.project(id)
|
||
|
|
db_env = project.environment(environment)
|
||
|
|
env = parser.update_environment
|
||
|
|
env.id = environment if env.id.nil?
|
||
|
|
begin
|
||
|
|
unless env.id == environment
|
||
|
|
servers = Devops::Db.connector.servers_by_project_and_environment(id, environment)
|
||
|
|
raise InvalidRecord.new("Environment '#{environment}' can't be updated: it has #{servers.size} running servers.") unless servers.empty?
|
||
|
|
end
|
||
|
|
begin
|
||
|
|
project.environment(env.id)
|
||
|
|
raise InvalidRecord.new("Environment '#{environment}' can't be renamed to '#{env.id}', environment '#{env.id}' already exists") unless environment == env.id
|
||
|
|
rescue Devops::Exception::RecordNotFound => e
|
||
|
|
end
|
||
|
|
env.validate!
|
||
|
|
project.delete_environment(environment)
|
||
|
|
project.add_environment(env)
|
||
|
|
"Deploy environment '#{environment}' has been updated in project '#{project.id}'"
|
||
|
|
rescue Devops::Exception::RecordNotFound => e
|
||
|
|
env.id = environment
|
||
|
|
res = project.add_environment env
|
||
|
|
"Deploy environment '#{env.id}' has been added to project '#{project.id}'." + res
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def delete_environment id, environment
|
||
|
|
db_project = get_project_with_environment(id, environment)
|
||
|
|
servers = Devops::Model::Server.find({'project' => id, 'environment' => environment})
|
||
|
|
raise Devops::Exception::DependencyError.new("Can not delete environment '#{environment}', there are #{servers.size} servers on it")
|
||
|
|
rescue Mongoid::Errors::DocumentNotFound
|
||
|
|
db_project.delete_environment(environment)
|
||
|
|
DevopsLogger.logger.info "Deploy environment '#{environment}' for project '#{id}' has been deleted"
|
||
|
|
end
|
||
|
|
|
||
|
|
def update_project id
|
||
|
|
body = parser.update
|
||
|
|
db_project = project(id)
|
||
|
|
%w(description run_list project_users).each do |key|
|
||
|
|
db_project.send(key + "=", body[key]) if body.key? key
|
||
|
|
end
|
||
|
|
db_project.save!
|
||
|
|
DevopsLogger.logger.info "Project '#{id}' has been updated"
|
||
|
|
end
|
||
|
|
|
||
|
|
def set_project_env_run_list id, environment
|
||
|
|
list = parser.run_list
|
||
|
|
project = Devops::Db.connector.project(id)
|
||
|
|
env = project.environment environment
|
||
|
|
Devops::Db.connector.set_project_env_run_list id, environment, list
|
||
|
|
"Updated environment '#{env.id}' with run_list '#{list.inspect}' in project '#{project.id}'"
|
||
|
|
end
|
||
|
|
|
||
|
|
def delete_project id
|
||
|
|
db_project = project(id)
|
||
|
|
servers_cnt = Devops::Model::Server.where({'project' => id}).count
|
||
|
|
if servers_cnt != 0
|
||
|
|
raise Devops::Exception::DependencyError.new "Deleting project #{id} is forbidden: Project has #{servers_cnt} server(s)"
|
||
|
|
else
|
||
|
|
db_project.delete
|
||
|
|
"Project '#{id}' has been deleted"
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
def deploy_project_stream out, id
|
||
|
|
# check if project exist
|
||
|
|
project = Devops::Db.connector.project(id)
|
||
|
|
environment, servers = parser.deploy
|
||
|
|
keys = {}
|
||
|
|
dbserver = Devops::Db.connector.servers(id, environment, servers, true)
|
||
|
|
out << (dbservers.empty? ? "No reserved servers to deploy\n" : "Deploy servers: '#{dbservers.map{|s| s.name}.join("', '")}'\n")
|
||
|
|
status = []
|
||
|
|
deploy_info_buf = {}
|
||
|
|
dbservers.each do |s|
|
||
|
|
begin
|
||
|
|
Devops::Db.connector.check_project_auth s.project, s.environment, parser.current_user
|
||
|
|
rescue InvalidPrivileges, Devops::Exception::RecordNotFound => e
|
||
|
|
out << e.message + "\n"
|
||
|
|
status.push 2
|
||
|
|
next
|
||
|
|
end
|
||
|
|
environment_model = project.environment(s.environment)
|
||
|
|
deploy_info = if deploy_info_buf[s.environment]
|
||
|
|
deploy_info_buf[s.environment]
|
||
|
|
else
|
||
|
|
# мы не можем указать один build_number для всех окружений, поэтому nil
|
||
|
|
deploy_info_buf[s.environment] = project.deploy_info(environment_model, nil)
|
||
|
|
end
|
||
|
|
status.push(Devops::Executor::ServerExecutor.new(s, out, current_user: parser.current_user).deploy_server(deploy_info))
|
||
|
|
end
|
||
|
|
status
|
||
|
|
end
|
||
|
|
|
||
|
|
def deploy_project id
|
||
|
|
# check if project exist
|
||
|
|
project_model = Devops::Db.connector.project(id)
|
||
|
|
environment, servers = parser.deploy
|
||
|
|
files = []
|
||
|
|
dbservers = Devops::Db.connector.servers(id, environment, servers, true)
|
||
|
|
#out << (dbservers.empty? ? "No reserved servers to deploy\n" : "Deploy servers: '#{dbservers.map{|s| s.name}.join("', '")}'\n")
|
||
|
|
deploy_info_buf = {}
|
||
|
|
dbservers.each do |s|
|
||
|
|
begin
|
||
|
|
Devops::Db.connector.check_project_auth s.project, s.environment, parser.current_user
|
||
|
|
rescue InvalidPrivileges, Devops::Exception::RecordNotFound => e
|
||
|
|
next
|
||
|
|
end
|
||
|
|
|
||
|
|
environment_model = project_model.environment(s.environment)
|
||
|
|
deploy_info = if deploy_info_buf[s.environment]
|
||
|
|
deploy_info_buf[s.environment]
|
||
|
|
else
|
||
|
|
# мы не можем указать один build_number для всех окружений, поэтому nil
|
||
|
|
deploy_info_buf[s.environment] = project_model.deploy_info(environment_model, nil)
|
||
|
|
end
|
||
|
|
|
||
|
|
jid = Worker.start_async(DeployWorker,
|
||
|
|
server_attrs: s.to_hash,
|
||
|
|
owner: parser.current_user,
|
||
|
|
tags: [],
|
||
|
|
deploy_info: deploy_info
|
||
|
|
)
|
||
|
|
files.push jid
|
||
|
|
end
|
||
|
|
files
|
||
|
|
end
|
||
|
|
|
||
|
|
def archive_project id
|
||
|
|
db_project = project(id)
|
||
|
|
Devops::Model::Project.where('_id' => id).set('archived' => true)
|
||
|
|
msg = "Project '#{id}' has been archived"
|
||
|
|
DevopsLogger.logger.info msg
|
||
|
|
msg
|
||
|
|
end
|
||
|
|
|
||
|
|
def unarchive_project id
|
||
|
|
db_project = project(id)
|
||
|
|
Devops::Model::Project.where('_id' => id).unset('archived')
|
||
|
|
msg = "Project '#{id}' has been unarchived"
|
||
|
|
DevopsLogger.logger.info msg
|
||
|
|
msg
|
||
|
|
end
|
||
|
|
|
||
|
|
def test_project id, environment
|
||
|
|
project = Devops::Db.connector.project(id)
|
||
|
|
env = project.environment environment
|
||
|
|
DevopsLogger.logger.info "Test project '#{project.id}' and environment '#{env.id}'"
|
||
|
|
if env.provider == ::Provider::Static::PROVIDER
|
||
|
|
msg = "Can not test environment with provider '#{::Provider::Static::PROVIDER}'"
|
||
|
|
Logger.warn msg
|
||
|
|
raise InvalidRecord.new(msg)
|
||
|
|
end
|
||
|
|
|
||
|
|
jid = Worker.start_async(ProjectTestWorker,
|
||
|
|
project: project.id,
|
||
|
|
environment: env.id,
|
||
|
|
user: @request.env['REMOTE_USER']
|
||
|
|
)
|
||
|
|
|
||
|
|
sleep 1
|
||
|
|
return [jid]
|
||
|
|
end
|
||
|
|
|
||
|
|
def delete_project_env_servers(project_id, env)
|
||
|
|
dry_run = parser.delete_project_env_servers
|
||
|
|
servers = project_env_servers project_id, env
|
||
|
|
info = {to_delete: servers.map(&:id)}
|
||
|
|
if !dry_run
|
||
|
|
info.merge!(delete_chosen_servers!(servers))
|
||
|
|
end
|
||
|
|
info
|
||
|
|
end
|
||
|
|
|
||
|
|
private
|
||
|
|
|
||
|
|
def delete_chosen_servers!(servers)
|
||
|
|
deleted, failed = [], []
|
||
|
|
servers.each do |server|
|
||
|
|
begin
|
||
|
|
Devops::Executor::ServerExecutor.new(server, '').delete_server
|
||
|
|
deleted << server.id
|
||
|
|
rescue
|
||
|
|
failed << server.id
|
||
|
|
end
|
||
|
|
end
|
||
|
|
{deleted: deleted, failed: failed}
|
||
|
|
end
|
||
|
|
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|