383 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			383 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| require "uri"
 | |
| 
 | |
| require "commands/status"
 | |
| require "commands/bootstrap_templates"
 | |
| 
 | |
| require "lib/executors/server_executor"
 | |
| require "providers/provider_factory"
 | |
| 
 | |
| require "db/mongo/models/server"
 | |
| 
 | |
| require "workers/create_server_worker"
 | |
| require "workers/bootstrap_worker"
 | |
| require "workers/delete_server_worker"
 | |
| require "app/api2/parsers/server"
 | |
| require_relative "request_handler"
 | |
| 
 | |
| module Devops
 | |
|   module API2_0
 | |
|     module Handler
 | |
|       class Server < RequestHandler
 | |
| 
 | |
|         set_parser Devops::API2_0::Parser::ServerParser
 | |
| 
 | |
|         extend StatusCommands
 | |
|         extend BootstrapTemplatesCommands
 | |
| 
 | |
|         #scheduler = Rufus::Scheduler.new
 | |
| 
 | |
|         def servers
 | |
|           fields, reserved = parser.servers
 | |
|           Devops::Db.connector.servers(nil, nil, nil, reserved, fields).map {|s| s.to_hash}
 | |
|         end
 | |
| 
 | |
|         def chef_servers
 | |
|           KnifeFactory.instance.chef_node_list
 | |
|         end
 | |
| 
 | |
|         def provider_servers provider
 | |
|           provider_servers_with_account provider, nil
 | |
|         end
 | |
| 
 | |
|         def provider_servers_with_account provider, account
 | |
|           ::Provider::ProviderFactory.get(provider, account).servers
 | |
|         end
 | |
| 
 | |
|         def server id
 | |
|           get_server_by_key(id, parser.server)
 | |
|         end
 | |
| 
 | |
|         def delete id
 | |
|           server = get_server_by_key(id, parser.instance_key)
 | |
|           current_user = parser.current_user
 | |
|           Devops::Db.connector.check_project_auth server.project, server.deploy_env, current_user
 | |
|           jid = Worker.start_async(DeleteServerWorker, 'server_id' => server.id, 'current_user' => current_user)
 | |
|           [jid]
 | |
|         end
 | |
| 
 | |
|         def delete_list
 | |
|           server_ids = parser.delete_list.uniq
 | |
|           servers = server_ids.map { |id| Devops::Db.connector.server_by_instance_id(id) }
 | |
|           current_user = parser.current_user
 | |
|           check_servers_list_auth(servers, current_user)
 | |
|           server_ids.inject({}) do |hash, server_id|
 | |
|             hash[server_id] = Worker.start_async(DeleteServerWorker, 'server_id' => server_id, 'current_user' => current_user)
 | |
|             hash
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         def create_server_stream out
 | |
|           status = []
 | |
|           prepare_create_server do |project, env, user, body|
 | |
|             e = Devops::Executor::ServerExecutor.new(nil, out)
 | |
|             e.project = project
 | |
|             e.deploy_env = env
 | |
|             body["created_by"] = user
 | |
|             res = e.create_server(body)
 | |
|             status.push res
 | |
|           end
 | |
|           status
 | |
|         end
 | |
| 
 | |
|         def create_server
 | |
|           body = parser.create
 | |
|           user = parser.current_user
 | |
| 
 | |
|           check_if_server_attrs_are_valid(body, user)
 | |
|           jid = Worker.start_async(CreateServerWorker,
 | |
|             server_attrs: body,
 | |
|             owner: user
 | |
|           )
 | |
|           [jid]
 | |
|         end
 | |
| 
 | |
|         def deploy node_name
 | |
|           call_deploy(node_name) do |options|
 | |
|             [ Worker.start_async(DeployWorker, @request, options) ]
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         def deploy_stream node_name, out
 | |
|           call_deploy(node_name) do |options|
 | |
|             Worker.start_sync(DeployWorker, @request, options, out)
 | |
|           end
 | |
|         rescue RecordNotFound  => e
 | |
|           out << e.message
 | |
|           -10
 | |
|         end
 | |
| 
 | |
|         def call_deploy node_name
 | |
|           body = parser.deploy
 | |
|           names = body["names"]
 | |
|           tags = body["tags"] || []
 | |
|           run_list = body["run_list"]
 | |
|           dir = DevopsConfig.config[:report_dir_v2]
 | |
|           owner = parser.current_user
 | |
|           s = Server.get_server_by_key(node_name, parser.instance_key)
 | |
|           project = Devops::Db.connector.check_project_auth s.project, s.deploy_env, owner
 | |
|           deploy_info = create_deploy_info(s, project, body["build_number"])
 | |
|           deploy_info["run_list"] = run_list if run_list
 | |
| 
 | |
|           yield({
 | |
|             server_attrs: s.to_hash,
 | |
|             owner: owner,
 | |
|             tags: tags,
 | |
|             deploy_info: deploy_info
 | |
|           })
 | |
|         end
 | |
| 
 | |
|         def pause_server node_name
 | |
|           s = get_server_by_key(node_name, parser.instance_key)
 | |
|           ## Authorization
 | |
|           Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user
 | |
|           provider = s.provider_instance
 | |
|           r = provider.pause_server s
 | |
|           if r.nil?
 | |
|             set_last_operation_and_save(s, Devops::Model::Server::OperationType::PAUSE)
 | |
| 
 | |
|             "Server with instance ID '#{s.id}' and node name '#{node_name}' is paused"
 | |
|           else
 | |
|             raise ConflictException.new("Server with instance ID '#{s.id}' and node name '#{node_name}' can not be paused, It in state '#{r}'", 409)
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         def unpause_server node_name
 | |
|           s = get_server_by_key(node_name, parser.instance_key)
 | |
|            ## Authorization
 | |
|           Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user
 | |
|           provider = s.provider_instance
 | |
|           r = provider.unpause_server s
 | |
|           if r.nil?
 | |
|             set_last_operation_and_save(s, Devops::Model::Server::OperationType::UNPAUSE)
 | |
| 
 | |
|             "Server with instance ID '#{s.id}' and node name '#{node_name}' is unpaused"
 | |
|           else
 | |
|             raise ConflictException.new("Server with instance ID '#{s.id}' and node name '#{node_name}' can not be unpaused, It in state '#{r}'")
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         def reserve_server node_name
 | |
|           s = get_server_by_key(node_name, parser.instance_key)
 | |
|           user = parser.current_user
 | |
|           Devops::Db.connector.check_project_auth s.project, s.deploy_env, user
 | |
|           raise ConflictException.new("Server '#{node_name}' already reserved") unless s.reserved_by.nil?
 | |
|           s.reserved_by = user
 | |
|           set_last_operation_and_save(s, Devops::Model::Server::OperationType::RESERVE)
 | |
|         end
 | |
| 
 | |
|         def unreserve_server node_name
 | |
|           s = get_server_by_key(node_name, parser.instance_key)
 | |
|           Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user
 | |
|           raise ConflictException.new("Server '#{node_name}' is not reserved") if s.reserved_by.nil?
 | |
|           s.reserved_by = nil
 | |
|           set_last_operation_and_save(s, Devops::Model::Server::OperationType::UNRESERVE)
 | |
|         end
 | |
| 
 | |
|         # TODO: check bootstrap template name
 | |
|         def bootstrap_server_stream out
 | |
|           options = prepare_bootstrap_server
 | |
|           s = options.delete(:server)
 | |
|           status = []
 | |
|           cert = Devops::Db.connector.key s.key
 | |
|           DevopsLogger.logger.debug "Bootstrap certificate path: #{cert.path}"
 | |
|           #bootstrap s, out, cert.path, logger
 | |
|           options[:cert_path] = cert.path
 | |
|           executor = Devops::Executor::ServerExecutor.new(s, out, current_user: parser.current_user)
 | |
|           r = executor.two_phase_bootstrap(options)
 | |
|           str = nil
 | |
|           r = if check_server(s)
 | |
|             Devops::Db.connector.server_set_chef_node_name s
 | |
|             str = "Server with id '#{s.id}' is bootstraped"
 | |
|             DevopsLogger.logger.info str
 | |
|             0
 | |
|           else
 | |
|             str = "Server with id '#{s.id}' is not bootstraped"
 | |
|             DevopsLogger.logger.warn str
 | |
|             1
 | |
|           end
 | |
|           status.push r
 | |
|           out << str
 | |
|           out << "\n"
 | |
|           status
 | |
|         end
 | |
| 
 | |
|         def bootstrap_server
 | |
|           attrs = prepare_bootstrap_server
 | |
|           server = attrs[:server]
 | |
|           bootstrap_template = attrs[:bootstrap_template]
 | |
| 
 | |
|           jid = Worker.start_async(BootstrapWorker,
 | |
|             server_attrs: server.to_mongo_hash,
 | |
|             bootstrap_template: bootstrap_template,
 | |
|             owner: parser.current_user
 | |
|           )
 | |
| 
 | |
|           [jid]
 | |
|         end
 | |
| 
 | |
|         def prepare_bootstrap_server
 | |
|           body = parser.bootstrap
 | |
|           id = body["instance_id"]
 | |
|           name = body["name"]
 | |
|           rl = body["run_list"]
 | |
|           t = body["bootstrap_template"]
 | |
|           s = Devops::Db.connector.server_by_instance_id(id)
 | |
|           res = {server: s}
 | |
| 
 | |
|           p = Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user
 | |
|           d = p.deploy_env(s.deploy_env)
 | |
| 
 | |
|           provider = s.provider_instance
 | |
| 
 | |
|           check_chef_node_name(name, provider, KnifeFactory.instance) unless name.nil?
 | |
|           unless t.nil?
 | |
|             templates = get_templates
 | |
|             halt_response("Invalid bootstrap template '#{t}', available values: #{templates.join(", ")}", 400) unless templates.include?(t)
 | |
|             res[:bootstrap_template] = t
 | |
|           end
 | |
|           s.chef_node_name = name || provider.create_default_chef_node_name(s)
 | |
|           res[:run_list] = rl || d.run_list
 | |
|           return res
 | |
|         end
 | |
| 
 | |
|         def unbootstrap_server id
 | |
|           s = get_server_by_key(id, parser.instance_key)
 | |
|           ### Authorization
 | |
|           Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user
 | |
|           Devops::Executor::ServerExecutor.new(s, "").unbootstrap
 | |
|         end
 | |
| 
 | |
|         def add_server
 | |
|           project, deploy_env, server_attrs = parser.add_server
 | |
|           Devops::Db.connector.check_project_auth project, deploy_env, parser.current_user
 | |
| 
 | |
|           server = add_static_server(server_attrs)
 | |
|           "Server '#{server.id}' has been added"
 | |
|         end
 | |
| 
 | |
|         # returns jobs ids
 | |
|         def add_and_bootstrap_servers
 | |
|           body, servers_attrs = parser.add_and_bootstrap_servers
 | |
|           Devops::Db.connector.check_project_auth body['project'], body['deploy_env'], parser.current_user
 | |
|           servers_attrs.map do |attrs|
 | |
|             server = add_static_server(attrs)
 | |
|             Worker.start_async(BootstrapWorker,
 | |
|               server_attrs: server.to_mongo_hash,
 | |
|               bootstrap_template: body['bootstrap_template'],
 | |
|               owner: parser.current_user
 | |
|             )
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         def set_tags node_name
 | |
|           tags = parser.tags
 | |
|           prepare_tags do |id, provider|
 | |
|             provider.set_tags id, tags
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         def unset_tags node_name
 | |
|           tags = parser.tags
 | |
|           prepare_tags do |id, provider|
 | |
|             provider.unset_tags id, tags
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         def prepare_tags node_name
 | |
|           s = get_server_by_key(node_name, parser.instance_key)
 | |
|           Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user
 | |
|           provider = ::Provider::ProviderFactory.get(s.provider)
 | |
|           yield s.id, provider
 | |
|         end
 | |
| 
 | |
|         def set_run_list node_name
 | |
|           s = get_server_by_key(node_name, parser.instance_key)
 | |
|           Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user
 | |
|           Devops::Db.connector.set_server_run_list(s.id, parser.run_list)
 | |
|         end
 | |
| 
 | |
|         def get_server_by_key id, key
 | |
|           mongo = Devops::Db.connector
 | |
|           key == "instance" ? mongo.server_by_instance_id(id) : mongo.server_by_chef_node_name(id)
 | |
|         end
 | |
| 
 | |
|         def check_chef_node_name name, provider, knife
 | |
|           Devops::Db.connector.server_by_chef_node_name(name)
 | |
|           raise InvalidRecord.new("Server with name '#{name}' already exist")
 | |
|         rescue RecordNotFound => e
 | |
|           # server not found - OK
 | |
|           s = provider.servers.detect {|s| s["name"] == name}
 | |
|           raise InvalidRecord.new("#{provider.name} node with name '#{name}' already exist") unless s.nil?
 | |
|           s = knife.chef_node_list.detect {|n| n == name}
 | |
|           raise InvalidRecord.new("Chef node with name '#{name}' already exist") unless s.nil?
 | |
|           s = knife.chef_client_list.detect {|c| c == name}
 | |
|           raise InvalidRecord.new("Chef client with name '#{name}' already exist") unless s.nil?
 | |
|         end
 | |
| 
 | |
|         private
 | |
| 
 | |
|         # Security checks should be already done here.
 | |
|         # @attrs should be a hash with:
 | |
|         #   :project
 | |
|         #   :deploy_env
 | |
|         #   :key
 | |
|         #   :remote_user
 | |
|         #   :private_ip
 | |
|         #   :public_ip
 | |
|         #   :chef_node_name
 | |
|         #   :run_list
 | |
|         def add_static_server(attrs)
 | |
|           server = Devops::Model::Server.new(
 | |
|             '_id' => "static_#{attrs[:key]}-#{attrs[:chef_node_name]}-#{Time.now.to_i}",
 | |
|             'provider' => ::Provider::ProviderFactory.get('static').name,
 | |
|             'project' => attrs[:project],
 | |
|             'deploy_env' => attrs[:deploy_env],
 | |
|             'remote_user' => attrs[:remote_user],
 | |
|             'private_ip' => attrs[:private_ip],
 | |
|             'public_ip' => attrs[:public_ip],
 | |
|             'key' => attrs[:key],
 | |
|             'chef_node_name' => attrs[:chef_node_name],
 | |
|             'run_list' => attrs[:run_list]
 | |
|           )
 | |
|           Devops::Db.connector.server_insert(server)
 | |
|           server
 | |
|         end
 | |
| 
 | |
|         def check_if_server_attrs_are_valid(body, user)
 | |
|           key_name = body["key"]
 | |
|           Devops::Db.connector.key(key_name) unless key_name.nil?
 | |
| 
 | |
|           project = Devops::Db.connector.check_project_auth(body["project"], body["deploy_env"], user)
 | |
|           env = project.deploy_env(body["deploy_env"])
 | |
| 
 | |
|           provider = env.provider_instance
 | |
|           server_name = body["name"]
 | |
|           check_chef_node_name(server_name, provider, KnifeFactory.instance) unless server_name.nil?
 | |
|           groups = body["groups"]
 | |
|           unless groups.nil?
 | |
|             buf = groups - provider.groups.keys
 | |
|             halt_response("Invalid security groups '#{buf.join("', '")}' for provider '#{provider.name}'") if buf.empty?
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         def set_last_operation_and_save(server, operation_type)
 | |
|           server.set_last_operation(operation_type, parser.current_user)
 | |
|           Devops::Db.connector.server_update(server)
 | |
|         end
 | |
| 
 | |
|         def check_servers_list_auth(servers, current_user)
 | |
|           project_with_env_pairs = servers.map do |server|
 | |
|             [server.project, server.deploy_env]
 | |
|           end
 | |
|           project_with_env_pairs.uniq.each do |pair|
 | |
|             project, env = *pair
 | |
|             Devops::Db.connector.check_project_auth project, env, current_user
 | |
|           end
 | |
|         end
 | |
| 
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | 
