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