diff --git a/devops-service/config.ru b/devops-service/config.ru index 661b207..f7aff87 100644 --- a/devops-service/config.ru +++ b/devops-service/config.ru @@ -20,11 +20,6 @@ else end config[:devops_dir] = File.join(ENV["HOME"], ".devops") if config[:devops_dir].nil? -puts "Devops home: #{config[:devops_dir]}" -unless File.exists?(config[:devops_dir]) - FileUtils.mkdir_p config[:devops_dir] - puts "Directory '#{config[:devops_dir]}' has been created" -end config[:report_dir_v2] = File.expand_path(File.join(config[:devops_dir], "report", "v2")) unless config[:report_dir_v2] [ diff --git a/devops-service/devops-service.rb b/devops-service/devops-service.rb index d1396f5..229fcf5 100644 --- a/devops-service/devops-service.rb +++ b/devops-service/devops-service.rb @@ -21,13 +21,32 @@ require "routes/v2.0/user" class DevopsService < Sinatra::Base helpers Sinatra::Streaming - helpers Sinatra::Version2_0::Helpers + helpers Devops::Version2_0::Helpers - register Sinatra::Version2_0::Core::ProviderRoutes - register Sinatra::Version2_0::Core::UserRoutes + register Devops::Version2_0::Core::ProviderRoutes + register Devops::Version2_0::Core::BootstrapTemplatesRoutes + register Devops::Version2_0::Core::UserRoutes + register Devops::Version2_0::Core::FilterRoutes + register Devops::Version2_0::Core::FlavorRoutes + register Devops::Version2_0::Core::GroupRoutes + register Devops::Version2_0::Core::ImageRoutes + register Devops::Version2_0::Core::KeyRoutes + register Devops::Version2_0::Core::NetworkRoutes + register Devops::Version2_0::Core::ProjectRoutes + register Devops::Version2_0::Core::ScriptRoutes + register Devops::Version2_0::Core::ServerRoutes + register Devops::Version2_0::Core::StatusRoutes + register Devops::Version2_0::Core::TagRoutes def initialize config super() + puts "Devops home: #{config[:devops_dir]}" + unless File.exists?(config[:devops_dir]) + FileUtils.mkdir_p config[:devops_dir] + puts "Directory '#{config[:devops_dir]}' has been created" + end + self.class.set :devops_home, config[:devops_dir] + self.class.set :config, config @@config = config root = File.dirname(__FILE__) @@ -57,7 +76,7 @@ class DevopsService < Sinatra::Base use Rack::Auth::Basic do |username, password| begin - mongo.user_auth(username, password) + settings.mongo.user_auth(username, password) true rescue RecordNotFound => e false @@ -70,6 +89,7 @@ class DevopsService < Sinatra::Base disable :dump_errors disable :show_exceptions set :logging, Logger::INFO + puts "TODO2" end configure :development do @@ -77,6 +97,7 @@ class DevopsService < Sinatra::Base disable :raise_errors # disable :dump_errors set :show_exceptions, :after_handler + puts "TODO1" end not_found do diff --git a/devops-service/helpers/version_2.rb b/devops-service/helpers/version_2.rb index 0559b13..6e0418c 100644 --- a/devops-service/helpers/version_2.rb +++ b/devops-service/helpers/version_2.rb @@ -2,10 +2,9 @@ require "json" require 'sinatra/base' require "sinatra/json" - require "providers/provider_factory" -module Sinatra +module Devops module Version2_0 module Helpers diff --git a/devops-service/routes/v2.0/bootstrap_templates.rb b/devops-service/routes/v2.0/bootstrap_templates.rb index e44bc22..7506c4a 100644 --- a/devops-service/routes/v2.0/bootstrap_templates.rb +++ b/devops-service/routes/v2.0/bootstrap_templates.rb @@ -1,35 +1,37 @@ require "json" -require "routes/v2.0/base_routes" require "providers/provider_factory" require "commands/bootstrap_templates" -module Version2_0 - class BootstrapTemplatesRoutes < BaseRoutes +module Devops + module Version2_0 + module Core + module BootstrapTemplatesRoutes - include BootstrapTemplatesCommands + extend BootstrapTemplatesCommands - def initialize wrapper - super wrapper - puts "Bootstrap templates routes initialized" + def self.registered(app) + + # Get list of available bootstrap templates + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : array of strings + # [ + # "omnibus" + # ] + app.get "/templates" do + check_headers :accept + check_privileges("templates", "r") + json BootstrapTemplatesRoutes.get_templates + end + + puts "Bootstrap templates routes initialized" + end + + end end - - # Get list of available bootstrap templates - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : array of strings - # [ - # "omnibus" - # ] - get "/templates" do - check_headers :accept - check_privileges("templates", "r") - json get_templates - end - end end - diff --git a/devops-service/routes/v2.0/filter.rb b/devops-service/routes/v2.0/filter.rb index 3be037e..e6bfd71 100644 --- a/devops-service/routes/v2.0/filter.rb +++ b/devops-service/routes/v2.0/filter.rb @@ -1,84 +1,87 @@ -require "routes/v2.0/base_routes" -module Version2_0 - class FilterRoutes < BaseRoutes +module Devops + module Version2_0 + module Core + module FilterRoutes - def initialize wrapper - super wrapper - puts "Filter routes initialized" + def self.registered(app) + + app.before "/filter/:provider/image" do + check_headers :accept, :content_type + check_privileges("filter", "w") + check_provider(params[:provider]) + @images = create_object_from_json_body(Array) + halt_response("Request body should not be an empty array") if @images.empty? + check_array(@images, "Request body should contains an array with strings") + end + + app.after "/filter/:provider/image" do + statistic + end + + # Get list of images filters for :provider + # + # Devops can works with images from filters list only + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : array of strings + # - ec2: + # [ + # "ami-83e4bcea" + # ] + # - openstack: + # [ + # "36dc7618-4178-4e29-be43-286fbfe90f50" + # ] + app.get "/filter/:provider/images" do + check_headers :accept + check_privileges("filter", "r") + check_provider(params[:provider]) + json settings.mongo.available_images(params[:provider]) + end + + # Add image ids to filter for :provider + # + # * *Request* + # - method : PUT + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # [ + # "image_id" + # ] -> array of image ids to add to filter + # + # * *Returns* : list of images filters for :provider + app.put "/filter/:provider/image" do + create_response("Updated", {:images => settings.mongo.add_available_images(@images, params[:provider])}) + end + + # Delete image ids from filter for :provider + # + # * *Request* + # - method : DELETE + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # [ + # "image_id" + # ] -> array of image ids to delete from filter + # + # * *Returns* : list of images filters for :provider + app.delete "/filter/:provider/image" do + create_response("Deleted", {:images => settings.mongo.delete_available_images(@images, params[:provider])}) + end + + puts "Filter routes initialized" + end + + end end - - before "/filter/:provider/image" do - check_headers :accept, :content_type - check_privileges("filter", "w") - check_provider(params[:provider]) - @images = create_object_from_json_body(Array) - halt_response("Request body should not be an empty array") if @images.empty? - check_array(@images, "Request body should contains an array with strings") - end - - after "/filter/:provider/image" do - statistic - end - - # Get list of images filters for :provider - # - # Devops can works with images from filters list only - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : array of strings - # - ec2: - # [ - # "ami-83e4bcea" - # ] - # - openstack: - # [ - # "36dc7618-4178-4e29-be43-286fbfe90f50" - # ] - get "/filter/:provider/images" do - check_headers :accept - check_privileges("filter", "r") - check_provider(params[:provider]) - json BaseRoutes.mongo.available_images(params[:provider]) - end - - # Add image ids to filter for :provider - # - # * *Request* - # - method : PUT - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # [ - # "image_id" - # ] -> array of image ids to add to filter - # - # * *Returns* : list of images filters for :provider - put "/filter/:provider/image" do - create_response("Updated", {:images => BaseRoutes.mongo.add_available_images(@images, params[:provider])}) - end - - # Delete image ids from filter for :provider - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # [ - # "image_id" - # ] -> array of image ids to delete from filter - # - # * *Returns* : list of images filters for :provider - delete "/filter/:provider/image" do - create_response("Deleted", {:images => BaseRoutes.mongo.delete_available_images(@images, params[:provider])}) - end - end end diff --git a/devops-service/routes/v2.0/flavor.rb b/devops-service/routes/v2.0/flavor.rb index 951dac4..f227d27 100644 --- a/devops-service/routes/v2.0/flavor.rb +++ b/devops-service/routes/v2.0/flavor.rb @@ -1,49 +1,52 @@ require "json" -require "routes/v2.0/base_routes" require "providers/provider_factory" -module Version2_0 - class FlavorRoutes < BaseRoutes +module Devops + module Version2_0 + module Core + module FlavorRoutes - def initialize wrapper - super wrapper - puts "Flavor routes initialized" + def self.registered(app) + + # Get list of flavors for :provider + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : array of objects + # - ec2: + # [ + # { + # "id": "t1.micro", + # "cores": 2, + # "disk": 0, + # "name": "Micro Instance", + # "ram": 613 + # } + # ] + # - openstack: + # [ + # { + # "id": "m1.small", + # "v_cpus": 1, + # "ram": 2048, + # "disk": 20 + # } + # ] + app.get "/flavors/:provider" do + check_headers :accept + check_privileges("flavor", "r") + check_provider(params[:provider]) + p = ::Provider::ProviderFactory.get params[:provider] + json p.flavors + end + + puts "Flavor routes initialized" + end + + end end - - # Get list of flavors for :provider - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : array of objects - # - ec2: - # [ - # { - # "id": "t1.micro", - # "cores": 2, - # "disk": 0, - # "name": "Micro Instance", - # "ram": 613 - # } - # ] - # - openstack: - # [ - # { - # "id": "m1.small", - # "v_cpus": 1, - # "ram": 2048, - # "disk": 20 - # } - # ] - get "/flavors/:provider" do - check_headers :accept - check_privileges("flavor", "r") - check_provider(params[:provider]) - p = ::Provider::ProviderFactory.get params[:provider] - json p.flavors - end - end end diff --git a/devops-service/routes/v2.0/group.rb b/devops-service/routes/v2.0/group.rb index 7d0b0d3..4165dd6 100644 --- a/devops-service/routes/v2.0/group.rb +++ b/devops-service/routes/v2.0/group.rb @@ -1,61 +1,63 @@ # encoding: UTF-8 require "json" -require "routes/v2.0/base_routes" require "providers/provider_factory" -module Version2_0 - class GroupRoutes < BaseRoutes +module Devops + module Version2_0 + module Core + module GroupRoutes - def initialize wrapper - super wrapper - puts "Group routes initialized" + def self.registered(app) + # Get security groups for :provider + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : + # - ec2: + # { + # "default": { + # "description": "default group", + # "id": "sg-565cf93f", + # "rules": [ + # { + # "protocol": "tcp", + # "from": 22, + # "to": 22, + # "cidr": "0.0.0.0/0" + # } + # ] + # } + # } + # - openstack: + # { + # "default": { + # "description": "default", + # "rules": [ + # { + # "protocol": null, + # "from": null, + # "to": null, + # "cidr": null + # } + # ] + # } + # } + # TODO: vpc support for ec2 + app.get "/groups/:provider" do + check_headers :accept + check_privileges("group", "r") + check_provider(params[:provider]) + p = ::Provider::ProviderFactory.get params[:provider] + json p.groups(params) + end + + puts "Group routes initialized" + end + + end end - - # Get security groups for :provider - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # - ec2: - # { - # "default": { - # "description": "default group", - # "id": "sg-565cf93f", - # "rules": [ - # { - # "protocol": "tcp", - # "from": 22, - # "to": 22, - # "cidr": "0.0.0.0/0" - # } - # ] - # } - # } - # - openstack: - # { - # "default": { - # "description": "default", - # "rules": [ - # { - # "protocol": null, - # "from": null, - # "to": null, - # "cidr": null - # } - # ] - # } - # } - # TODO: vpc support for ec2 - get "/groups/:provider" do - check_headers :accept - check_privileges("group", "r") - check_provider(params[:provider]) - p = ::Provider::ProviderFactory.get params[:provider] - json p.groups(params) - end - end end diff --git a/devops-service/routes/v2.0/image.rb b/devops-service/routes/v2.0/image.rb index c5bbe6e..f4ac183 100644 --- a/devops-service/routes/v2.0/image.rb +++ b/devops-service/routes/v2.0/image.rb @@ -1,178 +1,180 @@ require "providers/provider_factory" -require "routes/v2.0/base_routes" require "commands/image" -module Version2_0 - class ImageRoutes < BaseRoutes +module Devops + module Version2_0 + module Core + module ImageRoutes - include ImageCommands + extend ImageCommands - def initialize wrapper - super wrapper - puts "Image routes initialized" - end + def self.registered(app) + app.after %r{\A/image(/[\w]+)?\z} do + statistic + end - after %r{\A/image(/[\w]+)?\z} do - statistic - end + # Get devops images list + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # - parameters: + # - provider=ec2|openstack -> return images for provider + # + # * *Returns* : + # [ + # { + # "provider": "openstack", + # "name": "centos-6.4-x86_64", + # "remote_user": "root", + # "bootstrap_template": null, + # "id": "36dc7618-4178-4e29-be43-286fbfe90f50" + # } + # ] + app.get "/images" do + check_headers :accept + check_privileges("image", "r") + check_provider(params[:provider]) if params[:provider] + images = settings.mongo.images(params[:provider]) + json(images.map {|i| i.to_hash}) + end - # Get devops images list - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - parameters: - # - provider=ec2|openstack -> return images for provider - # - # * *Returns* : - # [ - # { - # "provider": "openstack", - # "name": "centos-6.4-x86_64", - # "remote_user": "root", - # "bootstrap_template": null, - # "id": "36dc7618-4178-4e29-be43-286fbfe90f50" - # } - # ] - get "/images" do - check_headers :accept - check_privileges("image", "r") - check_provider(params[:provider]) if params[:provider] - images = BaseRoutes.mongo.images(params[:provider]) - json(images.map {|i| i.to_hash}) - end + # Get raw images for :provider + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : + # - ec2 + # [ + # { + # "id": "ami-83e4bcea", + # "name": "amzn-ami-pv-2013.09.1.x86_64-ebs", + # "status": "available" + # } + # ] + # - openstack + # [ + # { + # "id": "36dc7618-4178-4e29-be43-286fbfe90f50", + # "name": "centos-6.4-x86_64", + # "status": "ACTIVE" + # } + # ] + app.get "/images/provider/:provider" do + check_headers :accept + check_privileges("image", "r") + check_provider(params[:provider]) + json get_images(settings.mongo, params[:provider]) + end - # Get raw images for :provider - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # - ec2 - # [ - # { - # "id": "ami-83e4bcea", - # "name": "amzn-ami-pv-2013.09.1.x86_64-ebs", - # "status": "available" - # } - # ] - # - openstack - # [ - # { - # "id": "36dc7618-4178-4e29-be43-286fbfe90f50", - # "name": "centos-6.4-x86_64", - # "status": "ACTIVE" - # } - # ] - get "/images/provider/:provider" do - check_headers :accept - check_privileges("image", "r") - check_provider(params[:provider]) - json get_images(BaseRoutes.mongo, params[:provider]) - end + # Get devops image by id + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : + # { + # "provider": "openstack", + # "name": "centos-6.4-x86_64", + # "remote_user": "root", + # "bootstrap_template": null, + # "id": "36dc7618-4178-4e29-be43-286fbfe90f50" + # } + app.get "/image/:image_id" do + check_headers :accept + check_privileges("image", "r") + json settings.mongo.image(params[:image_id]) + end - # Get devops image by id - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # { - # "provider": "openstack", - # "name": "centos-6.4-x86_64", - # "remote_user": "root", - # "bootstrap_template": null, - # "id": "36dc7618-4178-4e29-be43-286fbfe90f50" - # } - get "/image/:image_id" do - check_headers :accept - check_privileges("image", "r") - json BaseRoutes.mongo.image(params[:image_id]) - end + # Create devops image + # + # * *Request* + # - method : POST + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # { + # "id": "image id", + # "provider": "image provider", + # "remote_user": "user", -> the ssh username + # "bootstrap_template": null, -> specific bootstrap template name or nil + # "name": "image name" + # } + # + # * *Returns* : + # 201 - Created + app.post "/image" do + check_headers + check_privileges("image", "w") + image = create_object_from_json_body + settings.mongo.image_insert Image.new(image) + create_response "Created", nil, 201 + end - # Create devops image - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "id": "image id", - # "provider": "image provider", - # "remote_user": "user", -> the ssh username - # "bootstrap_template": null, -> specific bootstrap template name or nil - # "name": "image name" - # } - # - # * *Returns* : - # 201 - Created - post "/image" do - check_headers - check_privileges("image", "w") - image = create_object_from_json_body - BaseRoutes.mongo.image_insert Image.new(image) - create_response "Created", nil, 201 - end + # Update devops image + # + # * *Request* + # - method : PUT + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # { + # "id": "image id", + # "provider": "image provider", + # "remote_user": "user" -> the ssh username + # "bootstrap_template": null -> specific bootstrap template name or nil + # "name": "image name" + # } + # + # * *Returns* : + # 200 - Updated + app.put "/image/:image_id" do + check_headers + check_privileges("image", "w") + settings.mongo.image params[:image_id] + image = Image.new(create_object_from_json_body) + image.id = params[:image_id] + settings.mongo.image_update image + create_response("Image '#{params[:image_id]}' has been updated") + end - # Update devops image - # - # * *Request* - # - method : PUT - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "id": "image id", - # "provider": "image provider", - # "remote_user": "user" -> the ssh username - # "bootstrap_template": null -> specific bootstrap template name or nil - # "name": "image name" - # } - # - # * *Returns* : - # 200 - Updated - put "/image/:image_id" do - check_headers - check_privileges("image", "w") - BaseRoutes.mongo.image params[:image_id] - image = Image.new(create_object_from_json_body) - image.id = params[:image_id] - BaseRoutes.mongo.image_update image - create_response("Image '#{params[:image_id]}' has been updated") - end + # Delete devops image + # + # * *Request* + # - method : DELETE + # - headers : + # - Accept: application/json + # + # * *Returns* : + # 200 - Deleted + app.delete "/image/:image_id" do + check_headers + check_privileges("image", "w") + projects = settings.mongo.projects_by_image params[:image_id] + unless projects.empty? + ar = [] + projects.each do |p| + ar += p.deploy_envs.select{|e| e.image == params[:image_id]}.map{|e| "#{p.id}.#{e.identifier}"} + end + raise DependencyError.new "Deleting is forbidden: Image is used in #{ar.join(", ")}" + end - # Delete devops image - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - # * *Returns* : - # 200 - Deleted - delete "/image/:image_id" do - check_headers - check_privileges("image", "w") - projects = BaseRoutes.mongo.projects_by_image params[:image_id] - unless projects.empty? - ar = [] - projects.each do |p| - ar += p.deploy_envs.select{|e| e.image == params[:image_id]}.map{|e| "#{p.id}.#{e.identifier}"} + r = settings.mongo.image_delete params[:image_id] + create_response("Image '#{params[:image_id]}' has been removed") + end + + puts "Image routes initialized" end - raise DependencyError.new "Deleting is forbidden: Image is used in #{ar.join(", ")}" + end - - r = BaseRoutes.mongo.image_delete params[:image_id] - create_response("Image '#{params[:image_id]}' has been removed") end - end end diff --git a/devops-service/routes/v2.0/key.rb b/devops-service/routes/v2.0/key.rb index 2936c4e..63e67cb 100644 --- a/devops-service/routes/v2.0/key.rb +++ b/devops-service/routes/v2.0/key.rb @@ -3,108 +3,111 @@ require "db/exceptions/invalid_record" require "db/mongo/models/key" require "fileutils" -module Version2_0 - class KeyRoutes < BaseRoutes +module Devops + module Version2_0 + module Core + module KeyRoutes - def initialize wrapper - super wrapper - puts "Key routes initialized" - end + def self.registered(app) + app.before %r{\A/key(/[\w]+)?\z} do + if request.delete? + check_headers :accept + else + check_headers :accept, :content_type + end + check_privileges("key", "w") + end + + app.after %r{\A/key(/[\w]+)?\z} do + statistic + end + + # Get list of available ssh keys + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : array of strings + # [ + # { + # "scope": "system", -> 'system' - key was added by server, 'user' - key was added by user + # "id": "devops" + # } + # ] + app.get "/keys" do + check_headers :accept + check_privileges("key", "r") + keys = settings.mongo.keys.map {|i| i.to_hash} + keys.each {|k| k.delete("path")} # We should not return path to the key + json keys + end + + # Create ssh key on devops server + # + # * *Request* + # - method : POST + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # { + # "file_name": "key file name", + # "key_name": "key name", + # "content": "key content" + # } + # + # * *Returns* : + # 201 - Created + app.post "/key" do + key = create_object_from_json_body + fname = check_filename(key["file_name"], "Parameter 'file_name' must be a not empty string") + kname = check_string(key["key_name"], "Parameter 'key_name' should be a not empty string") + content = check_string(key["content"], "Parameter 'content' should be a not empty string") + file_name = File.join(DevopsService.config[:keys_dir], fname) + halt(400, "File '#{fname}' already exist") if File.exists?(file_name) + File.open(file_name, "w") do |f| + f.write(content) + f.chmod(0400) + end + + key = Key.new({"path" => file_name, "id" => kname}) + settings.mongo.key_insert key + create_response("Created", nil, 201) + end + + # Delete ssh key from devops server + # + # * *Request* + # - method : DELETE + # - headers : + # - Accept: application/json + # + # * *Returns* : + # 200 - Deleted + app.delete "/key/:key" do + servers = settings.mongo.servers_by_key params[:key] + unless servers.empty? + s_str = servers.map{|s| s.id}.join(", ") + raise DependencyError.new "Deleting is forbidden: Key is used in servers: #{s_str}" + end + + k = settings.mongo.key params[:key] + begin + FileUtils.rm(k.path) + rescue + logger.error "Missing key file for #{params[:key]} - #{k.filename}" + end + r = settings.mongo.key_delete params[:key] + return [500, r["err"].inspect] unless r["err"].nil? + create_response("Key '#{params[:key]}' removed") + end + + puts "Key routes initialized" + end - before %r{\A/key(/[\w]+)?\z} do - if request.delete? - check_headers :accept - else - check_headers :accept, :content_type end - check_privileges("key", "w") end - - after %r{\A/key(/[\w]+)?\z} do - statistic - end - - # Get list of available ssh keys - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : array of strings - # [ - # { - # "scope": "system", -> 'system' - key was added by server, 'user' - key was added by user - # "id": "devops" - # } - # ] - get "/keys" do - check_headers :accept - check_privileges("key", "r") - keys = BaseRoutes.mongo.keys.map {|i| i.to_hash} - keys.each {|k| k.delete("path")} # We should not return path to the key - json keys - end - - # Create ssh key on devops server - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "file_name": "key file name", - # "key_name": "key name", - # "content": "key content" - # } - # - # * *Returns* : - # 201 - Created - post "/key" do - key = create_object_from_json_body - fname = check_filename(key["file_name"], "Parameter 'file_name' must be a not empty string") - kname = check_string(key["key_name"], "Parameter 'key_name' should be a not empty string") - content = check_string(key["content"], "Parameter 'content' should be a not empty string") - file_name = File.join(DevopsService.config[:keys_dir], fname) - halt(400, "File '#{fname}' already exist") if File.exists?(file_name) - File.open(file_name, "w") do |f| - f.write(content) - f.chmod(0400) - end - - key = Key.new({"path" => file_name, "id" => kname}) - BaseRoutes.mongo.key_insert key - create_response("Created", nil, 201) - end - - # Delete ssh key from devops server - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - # * *Returns* : - # 200 - Deleted - delete "/key/:key" do - servers = BaseRoutes.mongo.servers_by_key params[:key] - unless servers.empty? - s_str = servers.map{|s| s.id}.join(", ") - raise DependencyError.new "Deleting is forbidden: Key is used in servers: #{s_str}" - end - - k = BaseRoutes.mongo.key params[:key] - begin - FileUtils.rm(k.path) - rescue - logger.error "Missing key file for #{params[:key]} - #{k.filename}" - end - r = BaseRoutes.mongo.key_delete params[:key] - return [500, r["err"].inspect] unless r["err"].nil? - create_response("Key '#{params[:key]}' removed") - end - end end diff --git a/devops-service/routes/v2.0/network.rb b/devops-service/routes/v2.0/network.rb index d209b25..f54c374 100644 --- a/devops-service/routes/v2.0/network.rb +++ b/devops-service/routes/v2.0/network.rb @@ -1,49 +1,51 @@ # encoding: UTF-8 require "json" -require "routes/v2.0/base_routes" require "providers/provider_factory" -module Version2_0 - class NetworkRoutes < BaseRoutes +module Devops + module Version2_0 + module Core + module NetworkRoutes - def initialize wrapper - super wrapper - puts "Network routes initialized" + def self.registered(app) + # Get list of networks for :provider + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : array of strings + # - ec2: + # [ + # { + # "cidr": "0.0.0.0/16", + # "vpcId": "vpc-1", + # "subnetId": "subnet-1", + # "name": "subnet-1", + # "zone": "us-east-1a" + # } + # ] + # - openstack: + # [ + # { + # "cidr": "0.0.0.0/16", + # "name": "private", + # "id": "b14f8df9-ac27-48e2-8d65-f7ef78dc2654" + # } + # ] + app.get "/networks/:provider" do + check_headers :accept + check_privileges("network", "r") + check_provider(params[:provider]) + p = ::Provider::ProviderFactory.get params[:provider] + json p.networks_detail + end + + puts "Network routes initialized" + end + + end end - - # Get list of networks for :provider - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : array of strings - # - ec2: - # [ - # { - # "cidr": "0.0.0.0/16", - # "vpcId": "vpc-1", - # "subnetId": "subnet-1", - # "name": "subnet-1", - # "zone": "us-east-1a" - # } - # ] - # - openstack: - # [ - # { - # "cidr": "0.0.0.0/16", - # "name": "private", - # "id": "b14f8df9-ac27-48e2-8d65-f7ef78dc2654" - # } - # ] - get "/networks/:provider" do - check_headers :accept - check_privileges("network", "r") - check_provider(params[:provider]) - p = ::Provider::ProviderFactory.get params[:provider] - json p.networks_detail - end - end end diff --git a/devops-service/routes/v2.0/project.rb b/devops-service/routes/v2.0/project.rb index 199bbcb..f3c4f60 100644 --- a/devops-service/routes/v2.0/project.rb +++ b/devops-service/routes/v2.0/project.rb @@ -7,550 +7,554 @@ require "commands/status" require "commands/server" require "workers/project_test_worker" -module Version2_0 - class ProjectRoutes < BaseRoutes +module Devops + module Version2_0 + module Core + module ProjectRoutes - include DeployCommands - include StatusCommands - include ServerCommands + extend DeployCommands + extend StatusCommands + extend ServerCommands - def initialize wrapper - super wrapper - puts "Project routes initialized" - end + def self.registered(app) + app.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 = settings.mongo.project(params[:id]) + 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 + app.after %r{\A/project(/[\w]+(/(user|deploy))?)?\z} do + statistic + end - after %r{\A/project(/[\w]+(/(user|deploy))?)?\z} do - statistic - end + app.after "/project/:id/:env/run_list" do + statistic + end - after "/project/:id/:env/run_list" do - statistic - end - - # Get projects list - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - params : - # - fields - show project fields, available values: deploy_envs, type - # - # * *Returns* : - # [ - # {"name" : "project_1"} - # ] - get "/projects" do - check_headers :accept - check_privileges("project", "r") - 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 - end - json BaseRoutes.mongo.projects(nil, nil, fields).map {|p| p.to_hash} - 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 - check_headers :accept - check_privileges("project", "r") - 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 - check_headers - check_privileges("project", "w") - 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 - check_headers :accept, :content_type - check_privileges("project", "w") - 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 - - # Run chef-client on reserved project servers - # - # * *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") - 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"], obj["servers"], true) - keys = {} - if obj.key?("trace") - stream() do |out| - begin - out << (servers.empty? ? "No reserved servers to deploy\n" : "Deploy servers: '#{servers.map{|s| s.chef_node_name}.join("', '")}'\n") - status = [] - servers.each do |s| - logger.debug "Deploy server: #{s.inspect}" - - 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 + # Get projects list + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # - params : + # - fields - show project fields, available values: deploy_envs, type + # + # * *Returns* : + # [ + # {"name" : "project_1"} + # ] + app.get "/projects" do + check_headers :accept + check_privileges("project", "r") + 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 - 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 + json settings.mongo.projects(nil, nil, fields).map {|p| p.to_hash} end - end - else - dir = DevopsService.config[:report_dir_v2] - files = [] - uri = URI.parse(request.url) - servers.each do |s| - project = begin - BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] - rescue InvalidPrivileges, RecordNotFound => e - next + + # 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" + # } + app.get "/project/:project" do + check_headers :accept + check_privileges("project", "r") + json settings.mongo.project(params[:project]) end - jid = DeployWorker.perform_async(dir, s.to_hash, [], DevopsService.config) - logger.info "Job '#{jid}' has been started" - uri.path = "#{DevopsService.config[:url_prefix]}/v2.0/report/" + jid - files.push uri.to_s + + # 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" + # } + # ] + app.get "/project/:project/servers" do + check_headers :accept + check_privileges("project", "r") + settings.mongo.project(params[:project]) + json settings.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 + app.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 settings.mongo.is_project_exists?(p) + p.add_authorized_user [request.env['REMOTE_USER']] + settings.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 + app.put "/project/:id" do + check_headers + check_privileges("project", "w") + project = Project.new(create_object_from_json_body) + project.id = params[:id] + old_project = settings.mongo.project params[:id] + settings.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 + app.put "/project/:id/user" do + users = settings.mongo.users(@users).map{|u| u.id} + buf = @users - users + @project.add_authorized_user users, @deploy_env + settings.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 + app.delete "/project/:id/user" do + @project.remove_authorized_user @users, @deploy_env + settings.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 + app.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 = settings.mongo.project(params[:id]) + env = project.deploy_env params[:env] + env.run_list = list + settings.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 + app.delete "/project/:id" do + check_headers :accept, :content_type + check_privileges("project", "w") + servers = settings.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? + settings.mongo.project_delete(params[:id]) + "Project '#{params[:id]}' is deleted" + else + project = settings.mongo.project(params[:id]) + project.remove_env params[:deploy_env] + settings.mongo.project_update project + "Project '#{params[:id]}'. Deploy environment '#{params[:deploy_env]}' has been deleted" + end + create_response(info) + end + + # Run chef-client on reserved project servers + # + # * *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 + app.post "/project/:id/deploy" do + check_headers :content_type + check_privileges("project", "x") + 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 = settings.mongo.project(params[:id]) + servers = settings.mongo.servers(params[:id], obj["deploy_env"], obj["servers"], true) + keys = {} + if obj.key?("trace") + stream() do |out| + begin + out << (servers.empty? ? "No reserved servers to deploy\n" : "Deploy servers: '#{servers.map{|s| s.chef_node_name}.join("', '")}'\n") + status = [] + servers.each do |s| + logger.debug "Deploy server: #{s.inspect}" + + begin + settings.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 = settings.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 + else + dir = DevopsService.config[:report_dir_v2] + files = [] + uri = URI.parse(request.url) + servers.each do |s| + project = begin + settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] + rescue InvalidPrivileges, RecordNotFound => e + next + end + jid = DeployWorker.perform_async(dir, s.to_hash, [], DevopsService.config) + logger.info "Job '#{jid}' has been started" + uri.path = "#{DevopsService.config[:url_prefix]}/v2.0/report/" + jid + files.push uri.to_s + end + json files + + 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'" + # } + app.post "/project/test/:id/:env" do + check_headers :accept, :content_type + check_privileges("project", "r") + project = settings.mongo.project(params[:id]) + env = project.deploy_env params[:env] + logger.info "Test project '#{project.id}' and environment '#{env.identifier}'" + + dir = DevopsService.config[:report_dir_v2] + uri = URI.parse(request.url) + p = { + :project => project.id, + :env => env.identifier, + :user => request.env['REMOTE_USER'] + } + jid = ProjectTestWorker.perform_async(dir, p, DevopsService.config) + logger.info "Job '#{jid}' has been created" + uri.path = "#{DevopsService.config[:url_prefix]}/v2.0/report/" + jid + files = [uri.to_s] + sleep 1 + json files + end + + + puts "Project routes initialized" end - json files - end - end + 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| + role_name = KnifeCommands.role_name(project_id, e.identifier) + begin + if all_roles.include? role_name + roles[:exist].push role_name + else + KnifeCommands.create_role role_name, project_id, e.identifier + 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 - # 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] - logger.info "Test project '#{project.id}' and environment '#{env.identifier}'" + 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 - dir = DevopsService.config[:report_dir_v2] - uri = URI.parse(request.url) - p = { - :project => project.id, - :env => env.identifier, - :user => request.env['REMOTE_USER'] - } - jid = ProjectTestWorker.perform_async(dir, p, DevopsService.config) - logger.info "Job '#{jid}' has been created" - uri.path = "#{DevopsService.config[:url_prefix]}/v2.0/report/" + jid - files = [uri.to_s] - sleep 1 - json files - 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| - role_name = KnifeCommands.role_name(project_id, e.identifier) - begin - if all_roles.include? role_name - roles[:exist].push role_name + def create_roles_response roles + if roles.is_a?(String) + roles else - KnifeCommands.create_role role_name, project_id, e.identifier - roles[:new].push role_name - logger.info "Role '#{role_name}' created" + 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 - 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 - if roles.is_a?(String) - roles - else - 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 diff --git a/devops-service/routes/v2.0/provider.rb b/devops-service/routes/v2.0/provider.rb index 344a195..1fc4d1b 100644 --- a/devops-service/routes/v2.0/provider.rb +++ b/devops-service/routes/v2.0/provider.rb @@ -1,18 +1,14 @@ # encoding: UTF-8 -require 'sinatra/base' - require "json" -require "routes/v2.0/base_routes" require "providers/provider_factory" -module Sinatra +module Devops module Version2_0 module Core module ProviderRoutes def self.registered(app) - puts "Provider routes initialized" # Get devops providers # @@ -31,6 +27,8 @@ module Sinatra check_privileges("provider", "r") json ::Provider::ProviderFactory.providers end + + puts "Provider routes initialized" end end end diff --git a/devops-service/routes/v2.0/script.rb b/devops-service/routes/v2.0/script.rb index abe3183..ae7aa7b 100644 --- a/devops-service/routes/v2.0/script.rb +++ b/devops-service/routes/v2.0/script.rb @@ -1,181 +1,184 @@ require "providers/provider_factory" -require "routes/v2.0/base_routes" require "fileutils" require "commands/status" -module Version2_0 - class ScriptRoutes < BaseRoutes +module Devops + module Version2_0 + module Core + module ScriptRoutes - include StatusCommands + extend StatusCommands - def initialize wrapper - super wrapper - puts "Script routes initialized" - end - - before "/script/:script_name" do - check_headers :accept - check_privileges("script", "w") - file_name = params[:script_name] - @file = File.join(DevopsService.config[:scripts_dir], check_filename(file_name, "Parameter 'script_name' must be a not empty string")) - if request.put? - halt_response("File '#{file_name}' already exist") if File.exists?(@file) - elsif request.delete? - halt_response("File '#{file_name}' does not exist", 404) unless File.exists?(@file) - end - end - - after %r{\A/script/((command|run)/)?[\w]+\z} do - statistic - end - - # Get scripts names - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # [ - # "script_1" - # ] - get "/scripts" do - check_headers :accept - check_privileges("script", "r") - res = [] - Dir.foreach(DevopsService.config[:scripts_dir]) {|f| res.push(f) unless f.start_with?(".")} - json res - end - - # Run command on node :node_name - # - # * *Request* - # - method : POST - # - body : - # command to run - # - # * *Returns* : text stream - post "/script/command/:node_name" do - check_privileges("script", "x") - user = request.env['REMOTE_USER'] - s = BaseRoutes.mongo.server_by_chef_node_name params[:node_name] - BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, user - cert = BaseRoutes.mongo.key s.key - cmd = request.body.read - addr = "#{s.remote_user}@#{s.public_ip || s.private_ip}" - ssh_cmd = "ssh -i %s #{addr} '#{cmd}'" - stream() do |out| - begin - out << ssh_cmd % File.basename(cert.path) - out << "\n" - IO.popen((ssh_cmd % cert.path) + " 2>&1") do |so| - while line = so.gets do - out << line + def self.registered(app) + app.before "/script/:script_name" do + check_headers :accept + check_privileges("script", "w") + file_name = params[:script_name] + @file = File.join(DevopsService.config[:scripts_dir], check_filename(file_name, "Parameter 'script_name' must be a not empty string")) + if request.put? + halt_response("File '#{file_name}' already exist") if File.exists?(@file) + elsif request.delete? + halt_response("File '#{file_name}' does not exist", 404) unless File.exists?(@file) end end - out << "\nDone" - rescue IOError => e - logger.error e.message - end - end - end - # Run script :script_name on nodes - # - # * *Request* - # - method : POST - # - headers : - # - Content-Type: application/json - # - body : - # { - # "nodes": [], -> array of nodes names - # "params": [] -> array of script arguments - # } - # - # * *Returns* : text stream - post "/script/run/:script_name" do - check_headers :content_type - check_privileges("script", "x") - file_name = params[:script_name] - @file = File.join(DevopsService.config[:scripts_dir], check_filename(file_name, "Parameter 'script_name' must be a not empty string", false)) - halt(404, "File '#{file_name}' does not exist") unless File.exists?(@file) - body = create_object_from_json_body - nodes = check_array(body["nodes"], "Parameter 'nodes' must be a not empty array of strings") - p = check_array(body["params"], "Parameter 'params' should be a not empty array of strings", String, true) - servers = BaseRoutes.mongo.servers_by_names(nodes) - return [404, "No servers found for names '#{nodes.join("', '")}'"] if servers.empty? - user = request.env['REMOTE_USER'] - servers.each do |s| - BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, user - end - stream() do |out| - begin - status = [] - servers.each do |s| - cert = begin - BaseRoutes.mongo.key s.key - rescue - out << "No key found for '#{s.chef_node_name}'" - status.push 2 - next - end - ssh_cmd = "ssh -i #{cert.path} #{s.remote_user}@#{s.public_ip || s.private_ip} 'bash -s' < %s" - out << "\nRun script on '#{s.chef_node_name}'\n" - unless p.nil? - ssh_cmd += " " + p.join(" ") - end - out << (ssh_cmd % [params[:script_name]]) - out << "\n" + app.after %r{\A/script/((command|run)/)?[\w]+\z} do + statistic + end - begin - IO.popen( (ssh_cmd % [@file]) + " 2>&1") do |so| - while line = so.gets do - out << line + # Get scripts names + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : + # [ + # "script_1" + # ] + app.get "/scripts" do + check_headers :accept + check_privileges("script", "r") + res = [] + Dir.foreach(DevopsService.config[:scripts_dir]) {|f| res.push(f) unless f.start_with?(".")} + json res + end + + # Run command on node :node_name + # + # * *Request* + # - method : POST + # - body : + # command to run + # + # * *Returns* : text stream + app.post "/script/command/:node_name" do + check_privileges("script", "x") + user = request.env['REMOTE_USER'] + s = BaseRoutes.mongo.server_by_chef_node_name params[:node_name] + BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, user + cert = BaseRoutes.mongo.key s.key + cmd = request.body.read + addr = "#{s.remote_user}@#{s.public_ip || s.private_ip}" + ssh_cmd = "ssh -i %s #{addr} '#{cmd}'" + stream() do |out| + begin + out << ssh_cmd % File.basename(cert.path) + out << "\n" + IO.popen((ssh_cmd % cert.path) + " 2>&1") do |so| + while line = so.gets do + out << line + end end - so.close - status.push $?.to_i + out << "\nDone" + rescue IOError => e + logger.error e.message end - rescue IOError => e - logger.error e.message - out << e.message - status.push 3 end end - out << create_status(status) - rescue IOError => e - logger.error e.message + + # Run script :script_name on nodes + # + # * *Request* + # - method : POST + # - headers : + # - Content-Type: application/json + # - body : + # { + # "nodes": [], -> array of nodes names + # "params": [] -> array of script arguments + # } + # + # * *Returns* : text stream + app.post "/script/run/:script_name" do + check_headers :content_type + check_privileges("script", "x") + file_name = params[:script_name] + @file = File.join(DevopsService.config[:scripts_dir], check_filename(file_name, "Parameter 'script_name' must be a not empty string", false)) + halt(404, "File '#{file_name}' does not exist") unless File.exists?(@file) + body = create_object_from_json_body + nodes = check_array(body["nodes"], "Parameter 'nodes' must be a not empty array of strings") + p = check_array(body["params"], "Parameter 'params' should be a not empty array of strings", String, true) + servers = BaseRoutes.mongo.servers_by_names(nodes) + return [404, "No servers found for names '#{nodes.join("', '")}'"] if servers.empty? + user = request.env['REMOTE_USER'] + servers.each do |s| + BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, user + end + stream() do |out| + begin + status = [] + servers.each do |s| + cert = begin + BaseRoutes.mongo.key s.key + rescue + out << "No key found for '#{s.chef_node_name}'" + status.push 2 + next + end + ssh_cmd = "ssh -i #{cert.path} #{s.remote_user}@#{s.public_ip || s.private_ip} 'bash -s' < %s" + out << "\nRun script on '#{s.chef_node_name}'\n" + unless p.nil? + ssh_cmd += " " + p.join(" ") + end + out << (ssh_cmd % [params[:script_name]]) + out << "\n" + + begin + IO.popen( (ssh_cmd % [@file]) + " 2>&1") do |so| + while line = so.gets do + out << line + end + so.close + status.push $?.to_i + end + rescue IOError => e + logger.error e.message + out << e.message + status.push 3 + end + end + out << create_status(status) + rescue IOError => e + logger.error e.message + end + end + end + + # Create script :script_name + # + # * *Request* + # - method : PUT + # - headers : + # - Accept: application/json + # - body : script content + # + # * *Returns* : + # 201 - Created + app.put "/script/:script_name" do + File.open(@file, "w") {|f| f.write(request.body.read)} + create_response("File '#{params[:script_name]}' created", nil, 201) + end + + # Delete script :script_name + # + # * *Request* + # - method : Delete + # - headers : + # - Accept: application/json + # + # * *Returns* : + # 200 - Deleted + app.delete "/script/:script_name" do + FileUtils.rm(@file) + create_response("File '#{params[:script_name]}' deleted") + end + + puts "Script routes initialized" end + end end - - # Create script :script_name - # - # * *Request* - # - method : PUT - # - headers : - # - Accept: application/json - # - body : script content - # - # * *Returns* : - # 201 - Created - put "/script/:script_name" do - File.open(@file, "w") {|f| f.write(request.body.read)} - create_response("File '#{params[:script_name]}' created", nil, 201) - end - - # Delete script :script_name - # - # * *Request* - # - method : Delete - # - headers : - # - Accept: application/json - # - # * *Returns* : - # 200 - Deleted - delete "/script/:script_name" do - FileUtils.rm(@file) - create_response("File '#{params[:script_name]}' deleted") - end end end diff --git a/devops-service/routes/v2.0/server.rb b/devops-service/routes/v2.0/server.rb index 49430b8..a583096 100644 --- a/devops-service/routes/v2.0/server.rb +++ b/devops-service/routes/v2.0/server.rb @@ -3,7 +3,6 @@ require "json" require "chef" require "commands/knife_commands" require 'rufus-scheduler' -require "routes/v2.0/base_routes" require "providers/provider_factory" require "db/mongo/models/deploy_env" require "commands/status" @@ -12,513 +11,513 @@ require "commands/bootstrap_templates" require "workers/create_server_worker" require "workers/bootstrap_worker" -module Version2_0 +module Devops + module Version2_0 + module Core + module ServerRoutes - class ExpireHandler - include ServerCommands + class ExpireHandler + include ServerCommands - def initialize server, logger - @server = server - @logger = logger - end + def initialize server, logger + @server = server + @logger = logger + end - def call(job) - @logger.info("Removing node '#{@server.chef_node_name}' form project '#{@server.project}' and env '#{@server.deploy_env}'") - begin - delete_server(@server, BaseRoutes.mongo, @logger) - rescue => e - logger.error "ExpiredHandler error: " + e.message - end - end - end - - class ServerRoutes < BaseRoutes - - include StatusCommands - include ServerCommands - include BootstrapTemplatesCommands - - def initialize wrapper - super wrapper - puts "Server routes initialized" - end - - before %r{\A/server/[\w]+/(pause|unpouse|reserve|unreserve)\z} do - check_headers :accept, :content_type - check_privileges("server", "w") - body = create_object_from_json_body(Hash, true) - @key = (body.nil? ? nil : body["key"]) - end - - after %r{\A/server(/[\w]+)?\z | \A/server/(add|bootstrap)\z | \A/server/[\w]+/(un)?pause\z} do - statistic - end - - scheduler = Rufus::Scheduler.new - - # Get devops servers list - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - params : - # - fields - show server fields, available values: project, deploy_env, provider, remote_user, private_ip, public_ip, created_at, created_by, static, key, reserved_by - # - # * *Returns* : - # [ - # { - # "id": "instance id", - # "chef_node_name": "chef name" - # } - # ] - get "/servers" do - check_headers :accept - check_privileges("server", "r") - fields = [] - if params.key?("fields") and params["fields"].is_a?(Array) - Server.fields.each do |k| - fields.push k if params["fields"].include?(k) - end - end - reserved = (params.key?("reserved") ? true : nil) - json BaseRoutes.mongo.servers(nil, nil, nil, reserved, fields).map {|s| s.to_hash} - end - - # Get chef nodes list - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # [ - # { - # "chef_node_name": "chef name" - # } - # ] - get "/servers/chef" do - check_headers :accept - check_privileges("server", "r") - json KnifeCommands.chef_node_list - end - - # Get provider servers list - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # -ec2 - # [ - # { - # "state": "running", - # "name": "name", - # "image": "ami-83e4bcea", - # "flavor": "m1.small", - # "keypair": "ssh key", - # "instance_id": "i-8441bfd4", - # "dns_name": "ec2-204-236-199-49.compute-1.amazonaws.com", - # "zone": "us-east-1d", - # "private_ip": "10.215.217.210", - # "public_ip": "204.236.199.49", - # "launched_at": "2014-04-25 07:56:33 UTC" - # } - # ] - # -openstack - # [ - # { - # "state": "ACTIVE", - # "name": "name", - # "image": "image id", - # "flavor": null, - # "keypair": "ssh key", - # "instance_id": "instance id", - # "private_ip": "172.17.0.1" - # } - # ] - get "/servers/:provider" do - check_headers :accept - check_privileges("server", "r") - json ::Provider::ProviderFactory.get(params[:provider]).servers - end - - # Get server info by :name - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - parameters: - # key=instance -> search server by instance_id rather then chef_node_name - # - # * *Returns* : - # [ - # { - # "chef_node_name": "chef name" - # } - # ] - get "/server/:name" do - check_headers :accept - check_privileges("server", "r") - json get_server(params[:name], params[:key]).to_hash - end - - # Delete devops server - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Deleted - delete "/server/:id" do - check_headers - check_privileges("server", "w") - body = create_object_from_json_body(Hash, true) - key = (body.nil? ? nil : body["key"]) - s = get_server(params[:id], key) - ### Authorization - BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] - info, r = delete_server(s, BaseRoutes.mongo, logger) - create_response(info, r) - end - - # Create devops server - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "project": "project name", -> mandatory parameter - # "deploy_env": "env", -> mandatory parameter - # "name": "server_name", -> if null, name will be generated - # "without_bootstrap": null, -> do not install chef on instance if true - # "force": null, -> do not delete server on error - # "groups": [], -> specify special security groups, overrides value from project env - # "key": "ssh key", -> specify ssh key for server, overrides value from project env - # "trace": true -> return output in stream - # } - # - # * *Returns* : text stream - post "/server" do - check_headers :content_type - check_privileges("server", "w") - body = create_object_from_json_body - user = request.env['REMOTE_USER'] - project_name = check_string(body["project"], "Parameter 'project' must be a not empty string") - env_name = check_string(body["deploy_env"], "Parameter 'deploy_env' must be a not empty string") - server_name = check_string(body["name"], "Parameter 'name' should be null or not empty string", true) - without_bootstrap = body["without_bootstrap"] - halt_response("Parameter 'without_bootstrap' should be a null or true") unless without_bootstrap.nil? or without_bootstrap == true - force = body["force"] - halt_response("Parameter 'force' should be a null or true") unless force.nil? or force == true - groups = check_array(body["groups"], "Parameter 'groups' should be null or not empty array of string", String, true) - key_name = check_string(body["key"], "Parameter 'key' should be null or not empty string", true) - new_key = BaseRoutes.mongo.key(key_name) unless key_name.nil? - - p = BaseRoutes.mongo.check_project_auth(project_name, env_name, user) - env = p.deploy_env(env_name) - - provider = ::Provider::ProviderFactory.get(env.provider) - check_chef_node_name(server_name, provider) unless server_name.nil? - unless groups.nil? - buf = groups - provider.groups.keys - halt_response("Invalid security groups '#{buf.join("', '")}' for provider '#{provider.name}'") if buf.empty? - end - - servers = extract_servers(provider, p, env, body, user, BaseRoutes.mongo) - if body.key?("trace") - stream() do |out| - begin - status = [] - servers.each do |s| - res = create_server_proc.call(out, s, provider, BaseRoutes.mongo) - status.push res + def call(job) + @logger.info("Removing node '#{@server.chef_node_name}' form project '#{@server.project}' and env '#{@server.deploy_env}'") + begin + delete_server(@server, settings.mongo, @logger) + rescue => e + logger.error "ExpiredHandler error: " + e.message end - out << create_status(status) - rescue IOError => e - logger.error e.message end end - else - dir = DevopsService.config[:report_dir_v2] - files = [] - uri = URI.parse(request.url) - servers.each do |s| - h = s.to_hash - h["options"] = s.options - jid = CreateServerWorker.perform_async(dir, env.provider, h, request.env['REMOTE_USER'], DevopsService.config) - logger.info "Job '#{jid}' has been started" - uri.path = "#{DevopsService.config[:url_prefix]}/v2.0/report/" + jid - files.push uri.to_s - end - sleep 1 - json files - end - end - # Pause devops server by name - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Paused - post "/server/:node_name/pause" do - s = get_server(params[:node_name], @key) - ## Authorization - BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] - provider = ::Provider::ProviderFactory.get(s.provider) - r = provider.pause_server s - if r.nil? - create_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' is paused") - else - halt_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' can not be paused, It in state '#{r}'", 409) - end - end + extend StatusCommands + extend ServerCommands + extend BootstrapTemplatesCommands - # Unpause devops server by name - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Unpaused - post "/server/:node_name/unpause" do - s = get_server(params[:node_name], @key) - ## Authorization - BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] - provider = ::Provider::ProviderFactory.get(s.provider) - r = provider.unpause_server s - if r.nil? - create_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' is unpaused") - else - halt_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' can not be unpaused, It in state '#{r}'", 409) - end - end + scheduler = Rufus::Scheduler.new - # Reserve devops server - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Reserved - post "/server/:node_name/reserve" do - s = get_server(params[:node_name], params[:key]) - user = request.env['REMOTE_USER'] - BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, user - halt_response(400, "Server '#{params[:node_name]}' already reserved") unless s.reserved_by.nil? - s.reserved_by = user - BaseRoutes.mongo.server_update(s) - create_response("Server '#{params[:node_name]}' has been reserved") - end + def self.registered(app) + app.before %r{\A/server/[\w]+/(pause|unpouse|reserve|unreserve)\z} do + check_headers :accept, :content_type + check_privileges("server", "w") + body = create_object_from_json_body(Hash, true) + @key = (body.nil? ? nil : body["key"]) + end - # Unreserve devops server - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "key": "instance", -> search server by instance_id rather then chef_node_name - # } - # - # * *Returns* : - # 200 - Unreserved - post "/server/:node_name/unreserve" do - s = get_server(params[:node_name], params[:key]) - BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] - halt_response(400, "Server '#{params[:node_name]}' is not reserved") if s.reserved_by.nil? - s.reserved_by = nil - BaseRoutes.mongo.server_update(s) - create_response("Server '#{params[:node_name]}' has been unreserved") - end + app.after %r{\A/server(/[\w]+)?\z | \A/server/(add|bootstrap)\z | \A/server/[\w]+/(un)?pause\z} do + statistic + end - # Bootstrap devops server - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "instance_id": "instance id", -> mandatory parameter - # "name": "server_name", -> if null, name will be generated - # "run_list": [], -> specify list of roles and recipes - # "bootstrap_template": "template" -> specify ssh key for server, overrides value from project env - # } - # - # * *Returns* : text stream - # TODO: check bootstrap template name - post "/server/bootstrap" do - check_headers - check_privileges("server", "w") - body = create_object_from_json_body(Hash, true) - id = check_string(body["instance_id"], "Parameter 'instance_id' must be a not empty string") - name = check_string(body["name"], "Parameter 'name' should be a not empty string", true) - rl = check_array(body["run_list"], "Parameter 'run_list' should be a not empty array of string", String, true) - unless rl.nil? - validator = Validators::Helpers::RunList.new(rl) - halt_response(validator.message) unless validator.valid? - end - t = check_string(body["bootstrap_template"], "Parameter 'bootstrap_template' should be a not empty string", true) - s = BaseRoutes.mongo.server_by_instance_id(id) + # Get devops servers list + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # - params : + # - fields - show server fields, available values: project, deploy_env, provider, remote_user, private_ip, public_ip, created_at, created_by, static, key, reserved_by + # + # * *Returns* : + # [ + # { + # "id": "instance id", + # "chef_node_name": "chef name" + # } + # ] + app.get "/servers" do + check_headers :accept + check_privileges("server", "r") + fields = [] + if params.key?("fields") and params["fields"].is_a?(Array) + Server.fields.each do |k| + fields.push k if params["fields"].include?(k) + end + end + reserved = (params.key?("reserved") ? true : nil) + json settings.mongo.servers(nil, nil, nil, reserved, fields).map {|s| s.to_hash} + end - p = BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] - d = p.deploy_env s.deploy_env + # Get chef nodes list + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : + # [ + # { + # "chef_node_name": "chef name" + # } + # ] + app.get "/servers/chef" do + check_headers :accept + check_privileges("server", "r") + json KnifeCommands.chef_node_list + end - provider = ::Provider::ProviderFactory.get(s.provider) + # Get provider servers list + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : + # -ec2 + # [ + # { + # "state": "running", + # "name": "name", + # "image": "ami-83e4bcea", + # "flavor": "m1.small", + # "keypair": "ssh key", + # "instance_id": "i-8441bfd4", + # "dns_name": "ec2-204-236-199-49.compute-1.amazonaws.com", + # "zone": "us-east-1d", + # "private_ip": "10.215.217.210", + # "public_ip": "204.236.199.49", + # "launched_at": "2014-04-25 07:56:33 UTC" + # } + # ] + # -openstack + # [ + # { + # "state": "ACTIVE", + # "name": "name", + # "image": "image id", + # "flavor": null, + # "keypair": "ssh key", + # "instance_id": "instance id", + # "private_ip": "172.17.0.1" + # } + # ] + app.get "/servers/:provider" do + check_headers :accept + check_privileges("server", "r") + json ::Provider::ProviderFactory.get(params[:provider]).servers + end - check_chef_node_name(name, provider) unless name.nil? - s.options = { - :run_list => rl || d.run_list, - } - unless t.nil? - templates = get_templates - halt_response("Invalid bootstrap template '#{t}', available values: #{templates.join(", ")}", 400) unless templates.include?(t) - s.options[:bootstrap_template] = t - end - s.chef_node_name = name || provider.create_default_chef_node_name(s) - logger.debug "Chef node name: '#{s.chef_node_name}'" - status = [] - if body.key?("trace") - stream() do |out| - begin - cert = BaseRoutes.mongo.key s.key - logger.debug "Bootstrap certificate path: #{cert.path}" - bootstrap s, out, cert.path, logger - str = nil - r = if check_server(s) - BaseRoutes.mongo.server_set_chef_node_name s - str = "Server with id '#{s.id}' is bootstraped" - logger.info str - 0 + # Get server info by :name + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # - parameters: + # key=instance -> search server by instance_id rather then chef_node_name + # + # * *Returns* : + # [ + # { + # "chef_node_name": "chef name" + # } + # ] + app.get "/server/:name" do + check_headers :accept + check_privileges("server", "r") + json get_server(params[:name], params[:key]).to_hash + end + + # Delete devops server + # + # * *Request* + # - method : DELETE + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # { + # "key": "instance", -> search server by instance_id rather then chef_node_name + # } + # + # * *Returns* : + # 200 - Deleted + app.delete "/server/:id" do + check_headers + check_privileges("server", "w") + body = create_object_from_json_body(Hash, true) + key = (body.nil? ? nil : body["key"]) + s = get_server(params[:id], key) + ### Authorization + settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] + info, r = delete_server(s, settings.mongo, logger) + create_response(info, r) + end + + # Create devops server + # + # * *Request* + # - method : POST + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # { + # "project": "project name", -> mandatory parameter + # "deploy_env": "env", -> mandatory parameter + # "name": "server_name", -> if null, name will be generated + # "without_bootstrap": null, -> do not install chef on instance if true + # "force": null, -> do not delete server on error + # "groups": [], -> specify special security groups, overrides value from project env + # "key": "ssh key", -> specify ssh key for server, overrides value from project env + # "trace": true -> return output in stream + # } + # + # * *Returns* : text stream + app.post "/server" do + check_headers :content_type + check_privileges("server", "w") + body = create_object_from_json_body + user = request.env['REMOTE_USER'] + project_name = check_string(body["project"], "Parameter 'project' must be a not empty string") + env_name = check_string(body["deploy_env"], "Parameter 'deploy_env' must be a not empty string") + server_name = check_string(body["name"], "Parameter 'name' should be null or not empty string", true) + without_bootstrap = body["without_bootstrap"] + halt_response("Parameter 'without_bootstrap' should be a null or true") unless without_bootstrap.nil? or without_bootstrap == true + force = body["force"] + halt_response("Parameter 'force' should be a null or true") unless force.nil? or force == true + groups = check_array(body["groups"], "Parameter 'groups' should be null or not empty array of string", String, true) + key_name = check_string(body["key"], "Parameter 'key' should be null or not empty string", true) + new_key = settings.mongo.key(key_name) unless key_name.nil? + + p = settings.mongo.check_project_auth(project_name, env_name, user) + env = p.deploy_env(env_name) + + provider = ::Provider::ProviderFactory.get(env.provider) + check_chef_node_name(server_name, provider) unless server_name.nil? + unless groups.nil? + buf = groups - provider.groups.keys + halt_response("Invalid security groups '#{buf.join("', '")}' for provider '#{provider.name}'") if buf.empty? + end + + servers = extract_servers(provider, p, env, body, user, settings.mongo) + if body.key?("trace") + stream() do |out| + begin + status = [] + servers.each do |s| + res = create_server_proc.call(out, s, provider, settings.mongo) + status.push res + end + out << create_status(status) + rescue IOError => e + logger.error e.message + end + end else - str = "Server with id '#{s.id}' is not bootstraped" - logger.warn str - 1 + dir = DevopsService.config[:report_dir_v2] + files = [] + uri = URI.parse(request.url) + servers.each do |s| + h = s.to_hash + h["options"] = s.options + jid = CreateServerWorker.perform_async(dir, env.provider, h, request.env['REMOTE_USER'], DevopsService.config) + logger.info "Job '#{jid}' has been started" + uri.path = "#{DevopsService.config[:url_prefix]}/v2.0/report/" + jid + files.push uri.to_s + end + sleep 1 + json files end - status.push r - out << str - out << "\n" - out << create_status(status) - rescue IOError => e - logger.error e.message end + + # Pause devops server by name + # + # * *Request* + # - method : POST + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # { + # "key": "instance", -> search server by instance_id rather then chef_node_name + # } + # + # * *Returns* : + # 200 - Paused + app.post "/server/:node_name/pause" do + s = get_server(params[:node_name], @key) + ## Authorization + settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] + provider = ::Provider::ProviderFactory.get(s.provider) + r = provider.pause_server s + if r.nil? + create_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' is paused") + else + halt_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' can not be paused, It in state '#{r}'", 409) + end + end + + # Unpause devops server by name + # + # * *Request* + # - method : POST + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # { + # "key": "instance", -> search server by instance_id rather then chef_node_name + # } + # + # * *Returns* : + # 200 - Unpaused + app.post "/server/:node_name/unpause" do + s = get_server(params[:node_name], @key) + ## Authorization + settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] + provider = ::Provider::ProviderFactory.get(s.provider) + r = provider.unpause_server s + if r.nil? + create_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' is unpaused") + else + halt_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' can not be unpaused, It in state '#{r}'", 409) + end + end + + # Reserve devops server + # + # * *Request* + # - method : POST + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # { + # "key": "instance", -> search server by instance_id rather then chef_node_name + # } + # + # * *Returns* : + # 200 - Reserved + app.post "/server/:node_name/reserve" do + s = get_server(params[:node_name], params[:key]) + user = request.env['REMOTE_USER'] + settings.mongo.check_project_auth s.project, s.deploy_env, user + halt_response(400, "Server '#{params[:node_name]}' already reserved") unless s.reserved_by.nil? + s.reserved_by = user + settings.mongo.server_update(s) + create_response("Server '#{params[:node_name]}' has been reserved") + end + + # Unreserve devops server + # + # * *Request* + # - method : POST + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # { + # "key": "instance", -> search server by instance_id rather then chef_node_name + # } + # + # * *Returns* : + # 200 - Unreserved + app.post "/server/:node_name/unreserve" do + s = get_server(params[:node_name], params[:key]) + settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] + halt_response(400, "Server '#{params[:node_name]}' is not reserved") if s.reserved_by.nil? + s.reserved_by = nil + settings.mongo.server_update(s) + create_response("Server '#{params[:node_name]}' has been unreserved") + end + + # Bootstrap devops server + # + # * *Request* + # - method : POST + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # { + # "instance_id": "instance id", -> mandatory parameter + # "name": "server_name", -> if null, name will be generated + # "run_list": [], -> specify list of roles and recipes + # "bootstrap_template": "template" -> specify ssh key for server, overrides value from project env + # } + # + # * *Returns* : text stream + # TODO: check bootstrap template name + app.post "/server/bootstrap" do + check_headers + check_privileges("server", "w") + body = create_object_from_json_body(Hash, true) + id = check_string(body["instance_id"], "Parameter 'instance_id' must be a not empty string") + name = check_string(body["name"], "Parameter 'name' should be a not empty string", true) + rl = check_array(body["run_list"], "Parameter 'run_list' should be a not empty array of string", String, true) + unless rl.nil? + validator = Validators::Helpers::RunList.new(rl) + halt_response(validator.message) unless validator.valid? + end + t = check_string(body["bootstrap_template"], "Parameter 'bootstrap_template' should be a not empty string", true) + s = settings.mongo.server_by_instance_id(id) + + p = settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] + d = p.deploy_env s.deploy_env + + provider = ::Provider::ProviderFactory.get(s.provider) + + check_chef_node_name(name, provider) unless name.nil? + s.options = { + :run_list => rl || d.run_list, + } + unless t.nil? + templates = get_templates + halt_response("Invalid bootstrap template '#{t}', available values: #{templates.join(", ")}", 400) unless templates.include?(t) + s.options[:bootstrap_template] = t + end + s.chef_node_name = name || provider.create_default_chef_node_name(s) + logger.debug "Chef node name: '#{s.chef_node_name}'" + status = [] + if body.key?("trace") + stream() do |out| + begin + cert = settings.mongo.key s.key + logger.debug "Bootstrap certificate path: #{cert.path}" + bootstrap s, out, cert.path, logger + str = nil + r = if check_server(s) + settings.mongo.server_set_chef_node_name s + str = "Server with id '#{s.id}' is bootstraped" + logger.info str + 0 + else + str = "Server with id '#{s.id}' is not bootstraped" + logger.warn str + 1 + end + status.push r + out << str + out << "\n" + out << create_status(status) + rescue IOError => e + logger.error e.message + end + end + else + dir = DevopsService.config[:report_dir_v2] + files = [] + uri = URI.parse(request.url) + h = s.to_hash + h["options"] = s.options + h["_id"] = s.id + jid = BootstrapWorker.perform_async(dir, d.provider, h, request.env['REMOTE_USER'], DevopsService.config) + logger.info "Job '#{jid}' has been started" + uri.path = "#{DevopsService.config[:url_prefix]}/v2.0/report/" + jid + uri.query = nil + uri.fragment = nil + files.push uri.to_s + sleep 1 + json files + end + end + + # Add external server to devops + # + # * *Request* + # - method : POST + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # { + # "project": "project name", -> mandatory parameter + # "deploy_env": "env", -> mandatory parameter + # "key": "ssh key", -> mandatory parameter + # "remote_user": "ssh user", -> mandatory parameter + # "private_ip": "ip", -> mandatory parameter + # "public_ip": "ip" + # } + # + # * *Returns* : + # 200 - Added + # TODO: should be refactored + app.post "/server/add" do + check_headers + check_privileges("server", "w") + body = create_object_from_json_body + project = check_string(body["project"], "Parameter 'project' must be a not empty string") + deploy_env = check_string(body["deploy_env"], "Parameter 'deploy_env' must be a not empty string") + key = check_string(body["key"], "Parameter 'key' must be a not empty string") + remote_user = check_string(body["remote_user"], "Parameter 'remote_user' must be a not empty string") + private_ip = check_string(body["private_ip"], "Parameter 'private_ip' must be a not empty string") + public_ip = check_string(body["public_ip"], "Parameter 'public_ip' should be a not empty string", true) + p = settings.mongo.check_project_auth project, deploy_env, request.env['REMOTE_USER'] + + d = p.deploy_env(deploy_env) + + cert = settings.mongo.key(key) + provider = ::Provider::ProviderFactory.get("static") + s = Server.new + s.provider = provider.name + s.project = project + s.deploy_env = deploy_env + s.remote_user = remote_user + s.private_ip = private_ip + s.public_ip = public_ip + s.static = true + s.id = "static_#{cert.id}-#{Time.now.to_i}" + s.key = cert.id + settings.mongo.server_insert s + create_response("Server '#{s.id}' has been added") + end + + puts "Server routes initialized" end - else - dir = DevopsService.config[:report_dir_v2] - files = [] - uri = URI.parse(request.url) - h = s.to_hash - h["options"] = s.options - h["_id"] = s.id - jid = BootstrapWorker.perform_async(dir, d.provider, h, request.env['REMOTE_USER'], DevopsService.config) - logger.info "Job '#{jid}' has been started" - uri.path = "#{DevopsService.config[:url_prefix]}/v2.0/report/" + jid - uri.query = nil - uri.fragment = nil - files.push uri.to_s - sleep 1 - json files - end - end - # Add external server to devops - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # { - # "project": "project name", -> mandatory parameter - # "deploy_env": "env", -> mandatory parameter - # "key": "ssh key", -> mandatory parameter - # "remote_user": "ssh user", -> mandatory parameter - # "private_ip": "ip", -> mandatory parameter - # "public_ip": "ip" - # } - # - # * *Returns* : - # 200 - Added - # TODO: should be refactored - post "/server/add" do - check_headers - check_privileges("server", "w") - body = create_object_from_json_body - project = check_string(body["project"], "Parameter 'project' must be a not empty string") - deploy_env = check_string(body["deploy_env"], "Parameter 'deploy_env' must be a not empty string") - key = check_string(body["key"], "Parameter 'key' must be a not empty string") - remote_user = check_string(body["remote_user"], "Parameter 'remote_user' must be a not empty string") - private_ip = check_string(body["private_ip"], "Parameter 'private_ip' must be a not empty string") - public_ip = check_string(body["public_ip"], "Parameter 'public_ip' should be a not empty string", true) - p = BaseRoutes.mongo.check_project_auth project, deploy_env, request.env['REMOTE_USER'] - d = p.deploy_env(deploy_env) - - cert = BaseRoutes.mongo.key(key) - provider = ::Provider::ProviderFactory.get("static") - s = Server.new - s.provider = provider.name - s.project = project - s.deploy_env = deploy_env - s.remote_user = remote_user - s.private_ip = private_ip - s.public_ip = public_ip - s.static = true - s.id = "static_#{cert.id}-#{Time.now.to_i}" - s.key = cert.id - BaseRoutes.mongo.server_insert s - create_response("Server '#{s.id}' has been added") - end - - private def get_server id, key - key == "instance" ? BaseRoutes.mongo.server_by_instance_id(id) : BaseRoutes.mongo.server_by_chef_node_name(id) + key == "instance" ? settings.mongo.server_by_instance_id(id) : settings.mongo.server_by_chef_node_name(id) end def check_chef_node_name name, provider - BaseRoutes.mongo.server_by_chef_node_name name + settings.mongo.server_by_chef_node_name name halt(400, "Server with name '#{name}' already exist") rescue RecordNotFound => e # server not found - OK @@ -530,5 +529,7 @@ module Version2_0 halt(400, "Chef client with name '#{name}' already exist") unless s.nil? end + end + end end end diff --git a/devops-service/routes/v2.0/status.rb b/devops-service/routes/v2.0/status.rb index aa2bbd4..10b3a90 100644 --- a/devops-service/routes/v2.0/status.rb +++ b/devops-service/routes/v2.0/status.rb @@ -1,21 +1,24 @@ require "json" -require "routes/v2.0/base_routes" require "sidekiq" -module Version2_0 - class StatusRoutes < BaseRoutes +module Devops + module Version2_0 + module Core + module StatusRoutes - def initialize wrapper - super wrapper - puts "Status routes initialized" - end + def self.registered(app) + app.get "/status/:id" do + r = Sidekiq.redis do |connection| + connection.hget("devops", params[:id]) + end + return [404, "Job with id '#{params[:id]}' not found"] if r.nil? + r + end + + puts "Status routes initialized" + end - get "/status/:id" do - r = Sidekiq.redis do |connection| - connection.hget("devops", params[:id]) end - return [404, "Job with id '#{params[:id]}' not found"] if r.nil? - r end end end diff --git a/devops-service/routes/v2.0/tag.rb b/devops-service/routes/v2.0/tag.rb index b5d13af..4ab735f 100644 --- a/devops-service/routes/v2.0/tag.rb +++ b/devops-service/routes/v2.0/tag.rb @@ -1,87 +1,91 @@ require "commands/knife_commands" -module Version2_0 - class TagRoutes < BaseRoutes +module Devops + module Version2_0 + module Core + module TagRoutes - def initialize wrapper - super wrapper - puts "Tag routes initialized" - end + def self.registered(app) + app.before "/tags/:node_name" do + if request.get? + check_headers :accept + check_privileges("server", "r") + else + check_headers :accept, :content_type + check_privileges("server", "w") + @tags = create_object_from_json_body(Array) + check_array(@tags, "Request body should be a not empty array of strings") + end + server = BaseRoutes.mongo.server_by_chef_node_name(params[:node_name]) + halt_response("No servers found for name '#{params[:node_name]}'", 404) if server.nil? + @chef_node_name = server.chef_node_name + end + + app.after "/tags/:node_name" do + statistic + end + + # Get tags list for :node_name + # + # * *Request* + # - method : GET + # - headers : + # - Accept: application/json + # + # * *Returns* : + # [ + # "tag_1" + # ] + app.get "/tags/:node_name" do + json(KnifeCommands.tags_list(@chef_node_name)) + end + + # Set tags list to :node_name + # + # * *Request* + # - method : POST + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # [ + # "tag_1" + # ] + # + # * *Returns* : + # 200 + app.post "/tags/:node_name" do + tagsStr = @tags.join(" ") + cmd = KnifeCommands.tags_create(@chef_node_name, tagsStr) + halt_response("Error: Cannot add tags #{tagsStr} to server #{@chef_node_name}", 500) unless cmd[1] + create_response("Set tags for #{@chef_node_name}: #{tagsStr}") + end + + # Delete tags from :node_name + # + # * *Request* + # - method : DELETE + # - headers : + # - Accept: application/json + # - Content-Type: application/json + # - body : + # [ + # "tag_1" + # ] + # + # * *Returns* : + # 200 + app.delete "/tags/:node_name" do + tagsStr = @tags.join(" ") + cmd = KnifeCommands.tags_delete(@chef_node_name, tagsStr) + halt_response("Cannot delete tags #{tagsStr} from server #{@chef_node_name}: #{cmd[0]}", 500) unless cmd[1] + create_response("Deleted tags for #{@chef_node_name}: #{tagsStr}") + end + + puts "Tag routes initialized" + end - before "/tags/:node_name" do - if request.get? - check_headers :accept - check_privileges("server", "r") - else - check_headers :accept, :content_type - check_privileges("server", "w") - @tags = create_object_from_json_body(Array) - check_array(@tags, "Request body should be a not empty array of strings") end - server = BaseRoutes.mongo.server_by_chef_node_name(params[:node_name]) - halt_response("No servers found for name '#{params[:node_name]}'", 404) if server.nil? - @chef_node_name = server.chef_node_name - end - - after "/tags/:node_name" do - statistic - end - - # Get tags list for :node_name - # - # * *Request* - # - method : GET - # - headers : - # - Accept: application/json - # - # * *Returns* : - # [ - # "tag_1" - # ] - get "/tags/:node_name" do - json(KnifeCommands.tags_list(@chef_node_name)) - end - - # Set tags list to :node_name - # - # * *Request* - # - method : POST - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # [ - # "tag_1" - # ] - # - # * *Returns* : - # 200 - post "/tags/:node_name" do - tagsStr = @tags.join(" ") - cmd = KnifeCommands.tags_create(@chef_node_name, tagsStr) - halt_response("Error: Cannot add tags #{tagsStr} to server #{@chef_node_name}", 500) unless cmd[1] - create_response("Set tags for #{@chef_node_name}: #{tagsStr}") - end - - # Delete tags from :node_name - # - # * *Request* - # - method : DELETE - # - headers : - # - Accept: application/json - # - Content-Type: application/json - # - body : - # [ - # "tag_1" - # ] - # - # * *Returns* : - # 200 - delete "/tags/:node_name" do - tagsStr = @tags.join(" ") - cmd = KnifeCommands.tags_delete(@chef_node_name, tagsStr) - halt_response("Cannot delete tags #{tagsStr} from server #{@chef_node_name}: #{cmd[0]}", 500) unless cmd[1] - create_response("Deleted tags for #{@chef_node_name}: #{tagsStr}") end end end diff --git a/devops-service/routes/v2.0/user.rb b/devops-service/routes/v2.0/user.rb index 1756743..e983995 100644 --- a/devops-service/routes/v2.0/user.rb +++ b/devops-service/routes/v2.0/user.rb @@ -1,7 +1,7 @@ require "db/exceptions/invalid_record" require "db/mongo/models/user" -module Sinatra +module Devops module Version2_0 module Core module UserRoutes