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