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