fluke/devops-service/routes/v2.0/project.rb

580 lines
18 KiB
Ruby
Raw Normal View History

2014-05-08 15:34:26 +04:00
require "json"
require "db/mongo/models/project"
require "db/mongo/models/deploy_env"
require "db/exceptions/invalid_record"
require "commands/deploy"
require "commands/status"
require "commands/server"
module Version2_0
class ProjectRoutes < BaseRoutes
include DeployCommands
include StatusCommands
include ServerCommands
def initialize wrapper
super wrapper
puts "Project routes initialized"
end
before "/project/:id/user" do
check_headers :accept, :content_type
check_privileges("project", "w")
body = create_object_from_json_body
@users = check_array(body["users"], "Parameter 'users' must be a not empty array of strings")
@deploy_env = check_string(body["deploy_env"], "Parameter 'deploy_env' must be a not empty string", true)
@project = BaseRoutes.mongo.project(params[:id])
end
after %r{\A/project(/[\w]+(/(user|deploy))?)?\z} do
statistic
end
after "/project/:id/:env/run_list" do
statistic
end
# Get projects list
#
# * *Request*
# - method : GET
# - headers :
# - Accept: application/json
2014-08-26 10:45:47 +04:00
# - params :
2014-08-26 12:04:51 +04:00
# - fields - show project fields, available values: deploy_envs, type
2014-05-08 15:34:26 +04:00
#
# * *Returns* :
# [
2014-08-26 12:04:51 +04:00
# {"name" : "project_1"}
2014-05-08 15:34:26 +04:00
# ]
get "/projects" do
check_headers :accept
check_privileges("project", "r")
2014-08-26 10:45:47 +04:00
fields = []
if params.key?("fields") and params["fields"].is_a?(Array)
Project.fields.each do |k|
fields.push k if params["fields"].include?(k)
end
2014-08-26 10:45:47 +04:00
end
json BaseRoutes.mongo.projects(nil, nil, fields).map {|p| p.to_hash}
2014-05-08 15:34:26 +04:00
end
# Get project by id
#
# * *Request*
# - method : GET
# - headers :
# - Accept: application/json
#
# * *Returns* :
# {
# "deploy_envs": [
# {
# "flavor": "flavor",
# "identifier": "prod",
# "image": "image id",
# "run_list": [
# "role[project_1-prod]"
# ],
# "subnets": [
# "private"
# ],
# "expires": null,
# "provider": "openstack",
# "groups": [
# "default"
# ],
# "users": [
# "user"
# ]
# }
# ],
# "name": "project_1"
# }
get "/project/:project" do
2014-07-14 11:31:21 +04:00
check_headers :accept
check_privileges("project", "r")
2014-05-08 15:34:26 +04:00
json BaseRoutes.mongo.project(params[:project])
end
# Get project servers
#
# * *Request*
# - method : GET
# - headers :
# - Accept: application/json
# - parameters :
# - deploy_env=:env -> show servers with environment :env
#
# * *Returns* :
# [
# {
# "provider": "openstack",
# "chef_node_name": "project_1_server",
# "remote_user": "root",
# "project": "project_1",
# "deploy_env": "prod",
# "private_ip": "10.8.8.8",
# "public_ip": null,
# "created_at": "2014-04-23 13:35:18 UTC",
# "created_by": "user",
# "static": false,
# "key": "ssh key",
# "id": "nstance id"
# }
# ]
get "/project/:project/servers" do
check_headers :accept
check_privileges("project", "r")
BaseRoutes.mongo.project(params[:project])
json BaseRoutes.mongo.servers(params[:project], params[:deploy_env]).map{|s| s.to_hash}
end
# Create project and chef roles
#
# * *Request*
# - method : POST
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# {
# "deploy_envs": [
# {
# "identifier": "prod",
# "provider": "openstack",
# "flavor": "m1.small",
# "image": "image id",
# "subnets": [
# "private"
# ],
# "groups": [
# "default"
# ],
# "users": [
# "user"
# ],
# "run_list": [
#
# ],
# "expires": null
# }
# ],
# "name": "project_1"
# }
#
# * *Returns* :
# 201 - Created
# TODO: multi project
post "/project" do
check_headers :accept, :content_type
check_privileges("project", "w")
body = create_object_from_json_body
check_string(body["name"], "Parameter 'name' must be a not empty string")
check_array(body["deploy_envs"], "Parameter 'deploy_envs' must be a not empty array of objects", Hash)
p = Project.new(body)
halt_response("Project '#{p.id}' already exist") if BaseRoutes.mongo.is_project_exists?(p)
p.add_authorized_user [request.env['REMOTE_USER']]
BaseRoutes.mongo.project_insert p
roles_res = ""
if p.multi?
logger.info "Project '#{p.id}' with type 'multi' created"
else
logger.info "Project '#{p.id}' created"
roles = create_roles p.id, p.deploy_envs, logger
roles_res = ". " + create_roles_response(roles)
end
res = "Created" + roles_res
create_response(res, nil, 201)
end
# Update project and create chef roles
#
# * *Request*
# - method : PUT
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# {
# "deploy_envs": [
# {
# "identifier": "dev",
# "provider": "openstack",
# "flavor": "m1.small",
# "image": "image id",
# "subnets": [
# "private"
# ],
# "groups": [
# "default"
# ],
# "users": [
# "user"
# ],
# "run_list": [
#
# ],
# "expires": null
# }
# ],
# "name": "project_1"
# }
#
# * *Returns* :
# 200 - Updated
# TODO: multi project
put "/project/:id" do
2014-07-14 11:31:21 +04:00
check_headers
check_privileges("project", "w")
2014-05-08 15:34:26 +04:00
project = Project.new(create_object_from_json_body)
project.id = params[:id]
old_project = BaseRoutes.mongo.project params[:id]
BaseRoutes.mongo.project_update project
roles = create_new_roles(old_project, project, logger)
info = "Project '#{project.id}' has been updated." + create_roles_response(roles)
create_response(info)
end
# Add users to project environment
#
# * *Request*
# - method : PUT
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# {
# "users": [
# "user1"
# ],
# "deploy_env": "env" -> if null, users will be added to all environments
# }
#
# * *Returns* :
# 200 - Updated
# TODO: multi project
put "/project/:id/user" do
users = BaseRoutes.mongo.users(@users).map{|u| u.id}
buf = @users - users
@project.add_authorized_user users, @deploy_env
BaseRoutes.mongo.project_update(@project)
info = "Users '#{users.join("', '")}' have been added to '#{params[:id]}' project's authorized users"
info << ", invalid users: '#{buf.join("', '")}'" unless buf.empty?
create_response(info)
end
# Delete users from project environment
#
# * *Request*
# - method : DELETE
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# {
# "users": [
# "user1"
# ],
# "deploy_env": "env" -> if null, users will be deleted from all environments
# }
#
# * *Returns* :
# 200 - Updated
# TODO: multi project
delete "/project/:id/user" do
@project.remove_authorized_user @users, @deploy_env
BaseRoutes.mongo.project_update @project
info = "Users '#{@users.join("', '")}' have been removed from '#{params[:id]}' project's authorized users"
create_response(info)
end
# Set run_list to project environment
#
# * *Request*
# - method : PUT
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# [
# "role[role_1]",
# "recipe[recipe_1]"
# ]
#
# * *Returns* :
# 200 - Updated
# TODO: multi project
put "/project/:id/:env/run_list" do
check_headers :accept, :content_type
check_privileges("project", "w")
list = create_object_from_json_body(Array)
check_array(list, "Body must contains not empty array of strings")
project = BaseRoutes.mongo.project(params[:id])
env = project.deploy_env params[:env]
env.run_list = list
BaseRoutes.mongo.project_update project
create_response("Updated environment '#{env.identifier}' with run_list '#{env.run_list.inspect}' in project '#{project.id}'")
end
# Delete project
#
# * *Request*
# - method : DELETE
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# {
# "deploy_env": "env" -> if not null, will be deleted environment only
# }
#
# * *Returns* :
# 200 - Deleted
delete "/project/:id" do
2014-07-14 11:31:21 +04:00
check_headers :accept, :content_type
check_privileges("project", "w")
2014-05-08 15:34:26 +04:00
servers = BaseRoutes.mongo.servers params[:id]
raise DependencyError.new "Deleting #{params[:id]} is forbidden: Project has servers" if !servers.empty?
body = create_object_from_json_body(Hash, true)
deploy_env = unless body.nil?
check_string(body["deploy_env"], "Parameter 'deploy_env' should be a not empty string", true)
end
info = if deploy_env.nil?
BaseRoutes.mongo.project_delete(params[:id])
"Project '#{params[:id]}' is deleted"
else
project = BaseRoutes.mongo.project(params[:id])
project.remove_env params[:deploy_env]
BaseRoutes.mongo.project_update project
"Project '#{params[:id]}'. Deploy environment '#{params[:deploy_env]}' has been deleted"
end
create_response(info)
end
2014-05-23 17:36:16 +04:00
# Run chef-client on reserved project servers
2014-05-08 15:34:26 +04:00
#
# * *Request*
# - method : POST
# - headers :
# - Content-Type: application/json
# - body :
# {
# "servers": [
# "server_1"
# ], -> deploy servers from list, all servers if null
# "deploy_env": "env" -> deploy servers with environment 'env' or all project servers if null
# }
#
# * *Returns* : text stream
post "/project/:id/deploy" do
check_headers :content_type
check_privileges("project", "x")
2014-05-08 15:34:26 +04:00
obj = create_object_from_json_body
check_string(obj["deploy_env"], "Parameter 'deploy_env' should be a not empty string", true)
check_array(obj["servers"], "Parameter 'servers' should be a not empty array of strings", String, true)
project = BaseRoutes.mongo.project(params[:id])
servers = BaseRoutes.mongo.servers(params[:id], obj["deploy_env"])
2014-05-23 17:36:16 +04:00
servers.delete_if{|s| s.reserved_by.nil?}
2014-05-08 15:34:26 +04:00
unless obj["servers"].nil?
logger.debug "Servers in params: #{obj["servers"].inspect}\nServers: #{servers.map{|s| s.chef_node_name}.inspect}"
servers.select!{|ps| obj["servers"].include?(ps.chef_node_name)}
end
keys = {}
stream() do |out|
begin
2014-05-23 17:36:16 +04:00
out << (servers.empty? ? "No reserved servers to deploy\n" : "Deploy servers: '#{servers.map{|s| s.chef_node_name}.join("', '")}'\n")
2014-05-08 15:34:26 +04:00
status = []
servers.each do |s|
begin
BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER']
rescue InvalidPrivileges, RecordNotFound => e
out << e.message + "\n"
status.push 2
next
end
unless keys.key? s.key
k = BaseRoutes.mongo.key s.key
keys[s.key] = k.path
end
status.push(deploy_server out, s, keys[s.key])
end
out << create_status(status)
rescue IOError => e
logger.error e.message
end
end
end
# Test project environment
#
# Run tests:
# - run server
# - bootstrap server
# - delete server
#
# * *Request*
# - method : DELETE
# - headers :
# - Accept: application/json
# - Content-Type: application/json
#
# * *Returns* :
# 200 -
# {
# "servers": [
# {
# "id": "132958f0-61c5-4665-8cc3-66e1bacd285b",
# "create": {
# "status": true,
# "time": "155s"
# },
# "chef_node_name": "chef name",
# "bootstrap": {
# "status": true,
# "log": "\nWaiting for SSH...\n"
# "return_code": 0
# },
# "delete": {
# "status": true,
# "time": "2s"
# "log": {
# "chef_node": "Deleted node[chef name]",
# "chef_client": "Deleted client[chef name]",
# "server": "Server with id '132958f0-61c5-4665-8cc3-66e1bacd285b' terminated"
# }
# },
# }
# ],
# "project": {
# "deploy_envs": [
# {
# "flavor": "flavor",
# "identifier": "prod",
# "image": "image id",
# "run_list": [
# "role[prod]"
# ],
# "subnets": [
# "private"
# ],
# "expires": null,
# "provider": "openstack",
# "groups": [
# "default"
# ],
# "users": [
# "root"
# ]
# }
# ],
# "name": "prject_1"
# },
# "message": "Test project 'project_1' and environment 'prod'"
# }
post "/project/test/:id/:env" do
check_headers :accept, :content_type
check_privileges("project", "r")
project = BaseRoutes.mongo.project(params[:id])
env = project.deploy_env params[:env]
user = request.env['REMOTE_USER']
provider = ::Provider::ProviderFactory.get(env.provider)
2014-05-08 15:34:26 +04:00
header = "Test project '#{project.id}' and environment '#{env.identifier}'"
logger.info header
servers = extract_servers(provider, project, env, {}, user, BaseRoutes.mongo)
result = {:servers => []}
project.deploy_envs = [ env ]
result[:project] = project.to_hash
servers.each do |s|
sr = {}
t1 = Time.now
out = ""
if provider.create_server(s, out)
t2 = Time.now
sr[:id] = s.id
sr[:create] = {:status => true}
sr[:create][:time] = time_diff_s(t1, t2)
logger.info "Server with parameters: #{s.to_hash.inspect} is running"
key = BaseRoutes.mongo.key(s.key)
b_out = ""
r = bootstrap(s, b_out, key.path, logger)
t1 = Time.now
sr[:chef_node_name] = s.chef_node_name
if r == 0
sr[:bootstrap] = {:status => true}
sr[:bootstrap][:time] = time_diff_s(t2, t1)
logger.info "Server with id '#{s.id}' is bootstraped"
if check_server(s)
BaseRoutes.mongo.server_insert s
end
else
sr[:bootstrap] = {:status => false}
sr[:bootstrap][:log] = b_out
sr[:bootstrap][:return_code] = r
end
t1 = Time.now
r = delete_from_chef_server(s.chef_node_name)
begin
r[:server] = provider.delete_server s.id
rescue Fog::Compute::OpenStack::NotFound, Fog::Compute::AWS::Error
r[:server] = "Server with id '#{s.id}' not found in '#{provider.name}' servers"
logger.warn r[:server]
end
BaseRoutes.mongo.server_delete s.id
t2 = Time.now
sr[:delete] = {:status => true}
sr[:delete][:time] = time_diff_s(t1, t2)
sr[:delete][:log] = r
else
sr[:create] = {:status => false}
sr[:create][:log] = out
end
result[:servers].push sr
end
create_response(header, result)
end
private
def create_roles project_id, envs, logger
all_roles = KnifeCommands.roles
return "Can't get roles list" if all_roles.nil?
roles = {:new => [], :error => [], :exist => []}
envs.each do |e|
2014-05-26 13:40:25 +04:00
role_name = KnifeCommands.role_name(project_id, e.identifier)
2014-05-08 15:34:26 +04:00
begin
if all_roles.include? role_name
roles[:exist].push role_name
else
2014-05-26 13:40:25 +04:00
KnifeCommands.create_role role_name, project_id, e.identifier
2014-05-08 15:34:26 +04:00
roles[:new].push role_name
logger.info "Role '#{role_name}' created"
end
rescue => er
roles[:error].push role_name
logger.error "Role '#{role_name}' can not be created: #{er.message}"
end
end
roles
end
def create_new_roles old_project, new_project, logger
old_project.deploy_envs.each do |e|
new_project.remove_env(e.identifier)
end
create_roles new_project.id, new_project.deploy_envs, logger
end
def create_roles_response roles
info = ""
info += " Project roles '#{roles[:new].join("', '")}' have been automaticaly created" unless roles[:new].empty?
info += " Project roles '#{roles[:exist].join("', '")}' weren't created because they exist" unless roles[:exist].empty?
info += " Project roles '#{roles[:error].join("', '")}' weren't created because of internal error" unless roles[:error].empty?
info
end
end
end