diff --git a/.gitignore b/.gitignore index 98dda23..862df22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ devops-service/tests/features/support/config.yml .devops_files/ +devops-service/plugins diff --git a/devops-service/Gemfile b/devops-service/Gemfile index 4a77b4f..ff2ad69 100644 --- a/devops-service/Gemfile +++ b/devops-service/Gemfile @@ -15,7 +15,9 @@ gem "bson_ext" gem "multi_json", "1.7.8" gem "rufus-scheduler", "2.0.24" gem "sidekiq", "3.2.6" +gem 'wisper' +gem "devops-nibr", :path => "/home/amartynov/workspace/github/devops-service/devops-service/plugins/devops-nibr" group :test do gem 'cucumber' diff --git a/devops-service/Gemfile.lock b/devops-service/Gemfile.lock index e27a649..22580b3 100644 --- a/devops-service/Gemfile.lock +++ b/devops-service/Gemfile.lock @@ -1,40 +1,42 @@ +PATH + remote: plugins/devops-nibr + specs: + devops-nibr (0.0.1) + GEM remote: https://rubygems.org/ specs: - addressable (2.3.5) - atomic (1.1.14) + CFPropertyList (2.2.8) + addressable (2.3.6) backports (3.6.4) - bson (1.9.2) - bson_ext (1.9.2) - bson (~> 1.9.2) + bson (1.11.1) + bson_ext (1.11.1) + bson (~> 1.11.1) builder (3.2.2) celluloid (0.15.2) timers (~> 1.1.0) - chef (11.10.4) - chef-zero (~> 1.7, >= 1.7.2) + chef (12.0.3) + chef-zero (~> 3.2) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) + ffi-yajl (~> 1.2) highline (~> 1.6, >= 1.6.9) - json (>= 1.4.4, <= 1.8.1) - mime-types (~> 1.16) mixlib-authentication (~> 1.3) mixlib-cli (~> 1.4) mixlib-config (~> 2.0) mixlib-log (~> 1.3) - mixlib-shellout (~> 1.3) + mixlib-shellout (>= 2.0.0.rc.0, < 3.0) net-ssh (~> 2.6) net-ssh-multi (~> 1.1) - ohai (~> 6.0) + ohai (~> 8.0) + plist (~> 3.1.0) pry (~> 0.9) - puma (~> 1.6) - rest-client (>= 1.0.4, < 1.7.0) - yajl-ruby (~> 1.1) - chef-zero (1.7.3) + chef-zero (3.2.1) + ffi-yajl (~> 1.1) hashie (~> 2.0) - json mixlib-log (~> 1.3) - moneta (< 0.7.0) rack + uuidtools (~> 2.1) coderay (1.1.0) connection_pool (2.1.0) cucumber (1.3.17) @@ -50,73 +52,140 @@ GEM eventmachine (>= 0.12.9) erubis (2.7.0) eventmachine (1.0.3) - excon (0.31.0) - fog (1.20.0) + excon (0.42.1) + ffi (1.9.6) + ffi-yajl (1.3.1) + ffi (~> 1.5) + libyajl2 (~> 1.2) + fission (0.5.0) + CFPropertyList (~> 2.2) + fog (1.26.0) + fog-atmos + fog-brightbox (~> 0.4) + fog-core (~> 1.27, >= 1.27.1) + fog-ecloud + fog-json + fog-profitbricks + fog-radosgw (>= 0.0.2) + fog-sakuracloud (>= 0.0.4) + fog-softlayer + fog-storm_on_demand + fog-terremark + fog-vmfusion + fog-voxel + fog-xml (~> 0.1.1) + ipaddress (~> 0.5) + nokogiri (~> 1.5, >= 1.5.11) + fog-atmos (0.1.0) + fog-core + fog-xml + fog-brightbox (0.7.1) + fog-core (~> 1.22) + fog-json + inflecto (~> 0.0.2) + fog-core (1.27.2) builder - excon (~> 0.31.0) - formatador (~> 0.2.0) + excon (~> 0.38) + formatador (~> 0.2) mime-types - multi_json (~> 1.0) net-scp (~> 1.1) net-ssh (>= 2.1.3) - nokogiri (>= 1.5.11) - formatador (0.2.4) + fog-ecloud (0.0.2) + fog-core + fog-xml + fog-json (1.0.0) + multi_json (~> 1.0) + fog-profitbricks (0.0.1) + fog-core + fog-xml + nokogiri + fog-radosgw (0.0.3) + fog-core (>= 1.21.0) + fog-json + fog-xml (>= 0.0.1) + fog-sakuracloud (0.1.1) + fog-core + fog-json + fog-softlayer (0.3.25) + fog-core + fog-json + fog-storm_on_demand (0.1.0) + fog-core + fog-json + fog-terremark (0.0.3) + fog-core + fog-xml + fog-vmfusion (0.0.1) + fission + fog-core + fog-voxel (0.0.2) + fog-core + fog-xml + fog-xml (0.1.1) + fog-core + nokogiri (~> 1.5, >= 1.5.11) + formatador (0.2.5) gherkin (2.12.2) multi_json (~> 1.3) - hashie (2.0.5) - highline (1.6.20) - httpclient (2.4.0) + hashie (2.1.2) + highline (1.6.21) + httpclient (2.5.3.3) + inflecto (0.0.2) ipaddress (0.8.0) json (1.8.1) + libyajl2 (1.2.0) method_source (0.8.2) mime-types (1.25.1) - mini_portile (0.5.2) + mini_portile (0.6.1) mixlib-authentication (1.3.0) mixlib-log - mixlib-cli (1.4.0) + mixlib-cli (1.5.0) mixlib-config (2.1.0) mixlib-log (1.6.0) - mixlib-shellout (1.3.0) - moneta (0.6.0) - mongo (1.9.2) - bson (~> 1.9.2) + mixlib-shellout (2.0.0) + mongo (1.11.1) + bson (= 1.11.1) multi_json (1.7.8) multi_test (0.1.1) - net-scp (1.1.2) + net-dhcp (1.3.2) + net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (2.8.0) + net-ssh (2.9.1) net-ssh-gateway (1.2.0) net-ssh (>= 2.6.5) net-ssh-multi (1.2.0) net-ssh (>= 2.6.5) net-ssh-gateway (>= 1.2.0) - nokogiri (1.6.1) - mini_portile (~> 0.5.0) - ohai (6.20.0) + nokogiri (1.6.5) + mini_portile (~> 0.6.0) + ohai (8.0.1) + ffi (~> 1.9) + ffi-yajl (~> 1.1) ipaddress + mime-types (~> 1.16) mixlib-cli - mixlib-config + mixlib-config (~> 2.0) mixlib-log - mixlib-shellout - systemu (~> 2.5.2) - yajl-ruby - power_assert (0.2.1) - pry (0.9.12.6) - coderay (~> 1.0) - method_source (~> 0.8) + mixlib-shellout (~> 2.0) + net-dhcp + rake (~> 10.1) + systemu (~> 2.6.4) + wmi-lite (~> 1.0) + plist (3.1.0) + power_assert (0.2.2) + pry (0.10.1) + coderay (~> 1.1.0) + method_source (~> 0.8.1) slop (~> 3.4) - puma (1.6.3) - rack (~> 1.2) - rack (1.5.2) - rack-protection (1.5.2) + rack (1.6.0) + rack-protection (1.5.3) rack rack-test (0.6.2) rack (>= 1.0) - redis (3.1.0) + rake (10.4.2) + redis (3.2.0) redis-namespace (1.5.1) redis (~> 3.0, >= 3.0.4) - rest-client (1.6.7) - mime-types (>= 1.16) rufus-scheduler (2.0.24) tzinfo (>= 0.3.22) sidekiq (3.2.6) @@ -136,25 +205,26 @@ GEM rack-test sinatra (~> 1.4.0) tilt (~> 1.3) - sinatra-websocket (0.3.0) + sinatra-websocket (0.3.1) em-websocket (~> 0.3.6) eventmachine - thin (>= 1.3.1) - slop (3.4.7) - systemu (2.5.2) - test-unit (3.0.7) + thin (>= 1.3.1, < 2.0.0) + slop (3.6.0) + systemu (2.6.4) + test-unit (3.0.8) power_assert thin (1.5.1) daemons (>= 1.0.9) eventmachine (>= 0.12.6) rack (>= 1.0.0) - thread_safe (0.1.3) - atomic + thread_safe (0.3.4) tilt (1.4.1) timers (1.1.0) - tzinfo (1.1.0) + tzinfo (1.2.2) thread_safe (~> 0.1) - yajl-ruby (1.2.0) + uuidtools (2.1.5) + wisper (1.6.0) + wmi-lite (1.0.0) PLATFORMS ruby @@ -163,6 +233,7 @@ DEPENDENCIES bson_ext chef (>= 11) cucumber + devops-nibr! fog (~> 1.20) httpclient mime-types (~> 1.25.1) @@ -176,3 +247,4 @@ DEPENDENCIES sinatra-websocket (~> 0.3.0) test-unit thin (~> 1.5.1) + wisper diff --git a/devops-service/devops-service.rb b/devops-service/devops-service.rb index 229fcf5..512cef7 100644 --- a/devops-service/devops-service.rb +++ b/devops-service/devops-service.rb @@ -4,39 +4,33 @@ require "rubygems" require "sinatra/base" require "sinatra/streaming" require "fileutils" +require "wisper" $:.push File.dirname(__FILE__) require "db/exceptions/invalid_record" require "db/exceptions/record_not_found" +require "exceptions/dependency_error" require "db/validators/all" require "db/mongo/mongo_connector" require "providers/provider_factory" +require "loader" + require "routes/v2.0" require "helpers/version_2" -require "routes/v2.0/provider" -require "routes/v2.0/user" +require "test_subscriber" +require "test_subscriber_2" class DevopsService < Sinatra::Base helpers Sinatra::Streaming - helpers Devops::Version2_0::Helpers - 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 + include Wisper::Publisher + + helpers Devops::Version2_0::Helpers + include Devops::Loader + register Devops::Version2_0::Routes def initialize config super() @@ -61,6 +55,8 @@ class DevopsService < Sinatra::Base mongo.create_root_user ::Provider::ProviderFactory.init(config) #set_up_providers_keys!(::Provider::ProviderFactory.all, mongo) + Wisper::GlobalListeners.subscribe(TestSubscriber2.new) + Wisper.subscribe(TestSubscriber.new) end @@mongo @@ -83,6 +79,10 @@ class DevopsService < Sinatra::Base end end + def self.create_method name, &block + send(:generate_method, name, &block) + end + include Sinatra::JSON configure :production do @@ -187,3 +187,5 @@ class DevopsService < Sinatra::Base end end + +require "wisper_fix" diff --git a/devops-service/loader.rb b/devops-service/loader.rb new file mode 100644 index 0000000..cbe1fc2 --- /dev/null +++ b/devops-service/loader.rb @@ -0,0 +1,50 @@ +require "json" + +require "routes/v2.0/flavor" +require "routes/v2.0/image" +require "routes/v2.0/filter" +require "routes/v2.0/network" +require "routes/v2.0/group" +require "routes/v2.0/deploy" +require "routes/v2.0/project" +require "routes/v2.0/key" +require "routes/v2.0/user" +require "routes/v2.0/provider" +require "routes/v2.0/tag" +require "routes/v2.0/server" +require "routes/v2.0/script" +require "routes/v2.0/status" +require "routes/v2.0/bootstrap_templates" + +require "routes/v2.0/handlers/provider" +require "routes/v2.0/handlers/bootstrap_templates" +require "routes/v2.0/handlers/deploy" +require "routes/v2.0/handlers/filter" +require "routes/v2.0/handlers/flavor" +require "routes/v2.0/handlers/group" +require "routes/v2.0/handlers/image" +require "routes/v2.0/handlers/network" +require "routes/v2.0/handlers/key" +require "routes/v2.0/handlers/project" +require "routes/v2.0/handlers/script" +require "routes/v2.0/handlers/status" +require "routes/v2.0/handlers/tag" +require "routes/v2.0/handlers/user" +require "routes/v2.0/handlers/server" + +module Devops + module Loader + def Loader.included(mod) + puts "#{self} included in #{mod}" + + if defined?(Devops::Plugin) + plugins = Devops::Plugin.constants.collect{|s| Devops::Plugin.const_get(s)}.select {|const| const.class == Module} + puts plugins.inspect + plugins.each do |p| + p.init(mod) + end + end + end + end + +end diff --git a/devops-service/routes/v2.0.rb b/devops-service/routes/v2.0.rb index 6613f12..f02b095 100644 --- a/devops-service/routes/v2.0.rb +++ b/devops-service/routes/v2.0.rb @@ -1,35 +1,25 @@ -require "routes/v2.0/flavor" -require "routes/v2.0/image" -require "routes/v2.0/filter" -require "routes/v2.0/network" -require "routes/v2.0/group" -require "routes/v2.0/deploy" -require "routes/v2.0/project" -require "routes/v2.0/key" -require "routes/v2.0/user" -require "routes/v2.0/provider" -require "routes/v2.0/tag" -require "routes/v2.0/server" -require "routes/v2.0/script" -require "routes/v2.0/status" -require "routes/v2.0/bootstrap_templates" +module Devops + module Version2_0 + module Routes -module Version2_0 - class V2_0 - - # Initialize modules of devops API v2.0 - def initialize app - stack = Rack::Builder.new - [FlavorRoutes, ImageRoutes, FilterRoutes, NetworkRoutes, GroupRoutes, DeployRoutes, - ProjectRoutes, KeyRoutes, UserRoutes, ProviderRoutes, TagRoutes, ServerRoutes, ScriptRoutes, BootstrapTemplatesRoutes, StatusRoutes].each do |m| - stack.use m + def self.registered(app) + app.register Devops::Version2_0::Routes::ProviderRoutes + app.register Devops::Version2_0::Routes::BootstrapTemplatesRoutes + app.register Devops::Version2_0::Routes::UserRoutes + app.register Devops::Version2_0::Routes::FilterRoutes + app.register Devops::Version2_0::Routes::FlavorRoutes + app.register Devops::Version2_0::Routes::GroupRoutes + app.register Devops::Version2_0::Routes::ImageRoutes + app.register Devops::Version2_0::Routes::KeyRoutes + app.register Devops::Version2_0::Routes::NetworkRoutes + app.register Devops::Version2_0::Routes::ProjectRoutes + app.register Devops::Version2_0::Routes::ScriptRoutes + app.register Devops::Version2_0::Routes::ServerRoutes + app.register Devops::Version2_0::Routes::StatusRoutes + app.register Devops::Version2_0::Routes::TagRoutes + app.register Devops::Version2_0::Routes::DeployRoutes end - stack.run app - @app = stack.to_app - end - def call(env) - @app.call env end end end diff --git a/devops-service/routes/v2.0/bootstrap_templates.rb b/devops-service/routes/v2.0/bootstrap_templates.rb index 7506c4a..a7c16ee 100644 --- a/devops-service/routes/v2.0/bootstrap_templates.rb +++ b/devops-service/routes/v2.0/bootstrap_templates.rb @@ -1,16 +1,18 @@ -require "json" -require "providers/provider_factory" -require "commands/bootstrap_templates" - module Devops module Version2_0 - module Core + module Routes module BootstrapTemplatesRoutes extend BootstrapTemplatesCommands def self.registered(app) + app.before "/templates" do + check_headers :accept + check_privileges("templates", "r") + broadcast(:cancel_order_failed, "hello") + end + # Get list of available bootstrap templates # # * *Request* @@ -22,11 +24,7 @@ module Devops # [ # "omnibus" # ] - app.get "/templates" do - check_headers :accept - check_privileges("templates", "r") - json BootstrapTemplatesRoutes.get_templates - end + app.get "/templates", &Devops::Version2_0::Handler::BootstrapTemplates.get_bootstrap_templates puts "Bootstrap templates routes initialized" end diff --git a/devops-service/routes/v2.0/deploy.rb b/devops-service/routes/v2.0/deploy.rb index 9bec4c6..1a98002 100644 --- a/devops-service/routes/v2.0/deploy.rb +++ b/devops-service/routes/v2.0/deploy.rb @@ -1,90 +1,40 @@ -require "commands/knife_commands" -require "routes/v2.0/base_routes" -require "providers/provider_factory" -require "commands/deploy" -require "commands/status" -require "workers/deploy_worker" -module Version2_0 - class DeployRoutes < BaseRoutes +module Devops + module Version2_0 + module Routes + module DeployRoutes - include DeployCommands - include StatusCommands - - def initialize wrapper - super wrapper - puts "Deploy routes initialized" - end - - after "/deploy" do - statistic - end - - # Run chef-client on reserved server - # - # * *Request* - # - method : POST - # - headers : - # - Content-Type: application/json - # - body : - # { - # "names": [], -> array of servers names to run chef-client - # "tags": [], -> array of tags to apply on each server before running chef-client - # "trace": true -> return output in stream - # } - # - # * *Returns* : text stream - post "/deploy" do - check_headers :content_type - check_privileges("server", "x") - r = create_object_from_json_body - names = check_array(r["names"], "Parameter 'names' should be a not empty array of strings") - tags = check_array(r["tags"], "Parameter 'tags' should be an array of strings", String, true) || [] - - servers = BaseRoutes.mongo.servers(nil, nil, names, true) - halt(404, "No reserved servers found for names '#{names.join("', '")}'") if servers.empty? - keys = {} - servers.sort_by!{|s| names.index(s.chef_node_name)} - if r.key?("trace") - stream() do |out| - status = [] - begin - servers.each do |s| - project = begin - BaseRoutes.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] - rescue InvalidPrivileges, RecordNotFound => e - out << e.message + "\n" - status.push 2 - next - end - res = deploy_server_proc.call(out, s, BaseRoutes.mongo, tags) - status.push(res) - end - out << create_status(status) - rescue IOError => e - logger.error e.message - break + def self.registered(app) + app.after "/deploy" do + statistic end - end # stream - 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 + + app.before "/deploy" do + check_headers :content_type + check_privileges("server", "x") + broadcast(:devops_deploy, "deploy") end - jid = DeployWorker.perform_async(dir, s.to_hash, tags, 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 + + # Run chef-client on reserved server + # + # * *Request* + # - method : POST + # - headers : + # - Content-Type: application/json + # - body : + # { + # "names": [], -> array of servers names to run chef-client + # "tags": [], -> array of tags to apply on each server before running chef-client + # "trace": true -> return output in stream + # } + # + # * *Returns* : text stream + app.post "/deploy", &Devops::Version2_0::Handler::Deploy.deploy + + puts "Deploy routes initialized" end - sleep 1 - json files + end end - end end diff --git a/devops-service/routes/v2.0/filter.rb b/devops-service/routes/v2.0/filter.rb index e6bfd71..08e38c8 100644 --- a/devops-service/routes/v2.0/filter.rb +++ b/devops-service/routes/v2.0/filter.rb @@ -1,7 +1,6 @@ - module Devops module Version2_0 - module Core + module Routes module FilterRoutes def self.registered(app) @@ -19,6 +18,11 @@ module Devops statistic end + app.before "/filter/:provider/images" do + check_headers :accept + check_privileges("filter", "r") + check_provider(params[:provider]) + end # Get list of images filters for :provider # # Devops can works with images from filters list only @@ -37,12 +41,7 @@ module Devops # [ # "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 + app.get "/filter/:provider/images", &Devops::Version2_0::Handler::Filter.get_filters # Add image ids to filter for :provider # @@ -57,9 +56,7 @@ module Devops # ] -> 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 + app.put "/filter/:provider/image", &Devops::Version2_0::Handler::Filter.add_filter # Delete image ids from filter for :provider # @@ -74,9 +71,7 @@ module Devops # ] -> 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 + app.delete "/filter/:provider/image", &Devops::Version2_0::Handler::Filter.delete_filter puts "Filter routes initialized" end diff --git a/devops-service/routes/v2.0/flavor.rb b/devops-service/routes/v2.0/flavor.rb index f227d27..92655c5 100644 --- a/devops-service/routes/v2.0/flavor.rb +++ b/devops-service/routes/v2.0/flavor.rb @@ -1,13 +1,15 @@ -require "json" -require "providers/provider_factory" - module Devops module Version2_0 - module Core + module Routes module FlavorRoutes def self.registered(app) + app.before "/flavors/:provider" do + check_headers :accept + check_privileges("flavor", "r") + check_provider(params[:provider]) + end # Get list of flavors for :provider # # * *Request* @@ -35,13 +37,7 @@ module Devops # "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 + app.get "/flavors/:provider", &Devops::Version2_0::Handler::Flavor.get_flavors puts "Flavor routes initialized" end diff --git a/devops-service/routes/v2.0/group.rb b/devops-service/routes/v2.0/group.rb index 4165dd6..125a28f 100644 --- a/devops-service/routes/v2.0/group.rb +++ b/devops-service/routes/v2.0/group.rb @@ -1,13 +1,16 @@ # encoding: UTF-8 -require "json" -require "providers/provider_factory" - module Devops module Version2_0 - module Core + module Routes module GroupRoutes def self.registered(app) + + app.before "/groups/:provider" do + check_headers :accept + check_privileges("group", "r") + check_provider(params[:provider]) + end # Get security groups for :provider # # * *Request* @@ -46,13 +49,7 @@ module Devops # } # } # 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 + app.get "/groups/:provider", &Devops::Version2_0::Handler::Group.get_groups puts "Group routes initialized" end diff --git a/devops-service/routes/v2.0/handlers/bootstrap_templates.rb b/devops-service/routes/v2.0/handlers/bootstrap_templates.rb new file mode 100644 index 0000000..06973b4 --- /dev/null +++ b/devops-service/routes/v2.0/handlers/bootstrap_templates.rb @@ -0,0 +1,18 @@ +require "commands/bootstrap_templates" + +module Devops + module Version2_0 + module Handler + class BootstrapTemplates + extend BootstrapTemplatesCommands + + def self.get_bootstrap_templates + lambda { + json BootstrapTemplates.get_templates + } + end + end + end + end +end + diff --git a/devops-service/routes/v2.0/handlers/deploy.rb b/devops-service/routes/v2.0/handlers/deploy.rb new file mode 100644 index 0000000..60f836b --- /dev/null +++ b/devops-service/routes/v2.0/handlers/deploy.rb @@ -0,0 +1,67 @@ +require "commands/deploy" +require "commands/status" +require "workers/deploy_worker" + +module Devops + module Version2_0 + module Handler + class Deploy + extend DeployCommands + extend StatusCommands + + def self.deploy + lambda { + r = create_object_from_json_body + names = check_array(r["names"], "Parameter 'names' should be a not empty array of strings") + tags = check_array(r["tags"], "Parameter 'tags' should be an array of strings", String, true) || [] + + servers = settings.mongo.servers(nil, nil, names, true) + halt(404, "No reserved servers found for names '#{names.join("', '")}'") if servers.empty? + keys = {} + servers.sort_by!{|s| names.index(s.chef_node_name)} + if r.key?("trace") + stream() do |out| + status = [] + begin + servers.each do |s| + project = 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 + res = deploy_server_proc.call(out, s, settings.mongo, tags) + status.push(res) + end + out << create_status(status) + rescue IOError => e + logger.error e.message + break + end + end # stream + 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, tags, 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 + end + end + end +end + diff --git a/devops-service/routes/v2.0/handlers/filter.rb b/devops-service/routes/v2.0/handlers/filter.rb new file mode 100644 index 0000000..4723cd4 --- /dev/null +++ b/devops-service/routes/v2.0/handlers/filter.rb @@ -0,0 +1,28 @@ +module Devops + module Version2_0 + module Handler + class Filter + + def self.get_filters + lambda { + json settings.mongo.available_images(params[:provider]) + } + end + + def self.add_filter + lambda { + create_response("Updated", {:images => settings.mongo.add_available_images(@images, params[:provider])}) + } + end + + def self.delete_filter + lambda { + create_response("Deleted", {:images => settings.mongo.delete_available_images(@images, params[:provider])}) + } + end + + end + end + end +end + diff --git a/devops-service/routes/v2.0/handlers/flavor.rb b/devops-service/routes/v2.0/handlers/flavor.rb new file mode 100644 index 0000000..0e30ab2 --- /dev/null +++ b/devops-service/routes/v2.0/handlers/flavor.rb @@ -0,0 +1,17 @@ +require "providers/provider_factory" + +module Devops + module Version2_0 + module Handler + class Flavor + def self.get_flavors + lambda { + p = ::Provider::ProviderFactory.get params[:provider] + json p.flavors + } + end + end + end + end +end + diff --git a/devops-service/routes/v2.0/handlers/group.rb b/devops-service/routes/v2.0/handlers/group.rb new file mode 100644 index 0000000..22570d2 --- /dev/null +++ b/devops-service/routes/v2.0/handlers/group.rb @@ -0,0 +1,17 @@ +require "providers/provider_factory" + +module Devops + module Version2_0 + module Handler + class Group + def self.get_groups + lambda { + p = ::Provider::ProviderFactory.get params[:provider] + json p.groups(params) + } + end + end + end + end +end + diff --git a/devops-service/routes/v2.0/handlers/image.rb b/devops-service/routes/v2.0/handlers/image.rb new file mode 100644 index 0000000..256d4ca --- /dev/null +++ b/devops-service/routes/v2.0/handlers/image.rb @@ -0,0 +1,67 @@ +require "providers/provider_factory" +require "commands/image" + +module Devops + module Version2_0 + module Handler + class Image + + extend ImageCommands + + def self.get_images + lambda { + images = settings.mongo.images(params[:provider]) + json(images.map {|i| i.to_hash}) + } + end + + def self.get_provider_images + lambda { + json get_images(settings.mongo, params[:provider]) + } + end + + def self.get_image + lambda { + json settings.mongo.image(params[:image_id]) + } + end + + def self.create_image + lambda { + image = create_object_from_json_body + settings.mongo.image_insert Image.new(image) + create_response "Created", nil, 201 + } + end + + def self.update_image + lambda { + 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 + + def self.delete_image + lambda { + 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 + + r = settings.mongo.image_delete params[:image_id] + create_response("Image '#{params[:image_id]}' has been removed") + } + end + end + end + end +end + diff --git a/devops-service/routes/v2.0/handlers/key.rb b/devops-service/routes/v2.0/handlers/key.rb new file mode 100644 index 0000000..251b8ef --- /dev/null +++ b/devops-service/routes/v2.0/handlers/key.rb @@ -0,0 +1,57 @@ +module Devops + module Version2_0 + module Handler + class Key + + def self.get_keys + lambda { + 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 + + def self.create_key + lambda { + 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 + + def self.delete_key + lambda { + 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 + + end + end + end +end + diff --git a/devops-service/routes/v2.0/handlers/network.rb b/devops-service/routes/v2.0/handlers/network.rb new file mode 100644 index 0000000..5c3948a --- /dev/null +++ b/devops-service/routes/v2.0/handlers/network.rb @@ -0,0 +1,18 @@ +require "providers/provider_factory" + +module Devops + module Version2_0 + module Handler + class Network + + def self.get_networks + lambda { + p = ::Provider::ProviderFactory.get params[:provider] + json p.networks_detail + } + end + end + end + end +end + diff --git a/devops-service/routes/v2.0/handlers/project.rb b/devops-service/routes/v2.0/handlers/project.rb new file mode 100644 index 0000000..b9e6a82 --- /dev/null +++ b/devops-service/routes/v2.0/handlers/project.rb @@ -0,0 +1,256 @@ +require "commands/deploy" +require "commands/status" +require "commands/server" +require "db/mongo/models/project" +require "db/mongo/models/deploy_env" +require "workers/project_test_worker" + +module Devops + module Version2_0 + module Handler + class Project + + extend DeployCommands + extend StatusCommands + extend ServerCommands + + def self.get_projects + lambda { + 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 settings.mongo.projects(nil, nil, fields).map {|p| p.to_hash} + } + end + + def self.get_project + lambda { + json settings.mongo.project(params[:project]) + } + end + + def self.get_project_servers + lambda { + settings.mongo.project(params[:project]) + json settings.mongo.servers(params[:project], params[:deploy_env]).map{|s| s.to_hash} + } + end + + # TODO: multi project + def self.create_project + lambda { + 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 = Project.create_roles p.id, p.deploy_envs, logger + roles_res = ". " + Project.create_roles_response(roles) + end + res = "Created" + roles_res + create_response(res, nil, 201) + } + end + + # TODO: multi project + def self.update_project + lambda { + 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 = Project.create_new_roles(old_project, project, logger) + info = "Project '#{project.id}' has been updated." + Project.create_roles_response(roles) + create_response(info) + } + end + + # TODO: multi project + def self.update_project_users + lambda { + 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 + + # TODO: multi project + def self.delete_project_users + lambda { + @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 + + # TODO: multi project + def self.set_project_env_run_list + lambda { + 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 + + def self.delete_project + lambda { + 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 + + def self.deploy_project + lambda { + 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 + + def self.test_project + lambda { + 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 + + def self.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 + + def self.create_new_roles old_project, new_project, logger + old_project.deploy_envs.each do |e| + new_project.remove_env(e.identifier) + end + Project.create_roles new_project.id, new_project.deploy_envs, logger + end + + def self.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 + end + end +end + diff --git a/devops-service/routes/v2.0/handlers/provider.rb b/devops-service/routes/v2.0/handlers/provider.rb new file mode 100644 index 0000000..bf36a29 --- /dev/null +++ b/devops-service/routes/v2.0/handlers/provider.rb @@ -0,0 +1,15 @@ +require "providers/provider_factory" + +module Devops + module Version2_0 + module Handler + class Provider + def self.get_providers + lambda { + json ::Provider::ProviderFactory.providers + } + end + end + end + end +end diff --git a/devops-service/routes/v2.0/handlers/script.rb b/devops-service/routes/v2.0/handlers/script.rb new file mode 100644 index 0000000..2d7fd78 --- /dev/null +++ b/devops-service/routes/v2.0/handlers/script.rb @@ -0,0 +1,116 @@ +require "providers/provider_factory" +require "fileutils" +require "commands/status" + +module Devops + module Version2_0 + module Handler + class Script + + def self.get_scripts + lambda { + res = [] + Dir.foreach(DevopsService.config[:scripts_dir]) {|f| res.push(f) unless f.start_with?(".")} + json res + } + end + + def self.execute_command + lambda { + 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 + out << "\nDone" + rescue IOError => e + logger.error e.message + end + end + } + end + + def self.run_script + lambda { + 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 + + def self.create_script + lambda { + File.open(@file, "w") {|f| f.write(request.body.read)} + create_response("File '#{params[:script_name]}' created", nil, 201) + } + end + + def self.delete_script + lambda { + FileUtils.rm(@file) + create_response("File '#{params[:script_name]}' deleted") + } + end + end + end + end +end + diff --git a/devops-service/routes/v2.0/handlers/server.rb b/devops-service/routes/v2.0/handlers/server.rb new file mode 100644 index 0000000..70abc26 --- /dev/null +++ b/devops-service/routes/v2.0/handlers/server.rb @@ -0,0 +1,328 @@ +require 'rufus-scheduler' +require "uri" + +require "commands/status" +require "commands/server" +require "commands/bootstrap_templates" +require "commands/knife_commands" + +require "providers/provider_factory" + +require "db/mongo/models/deploy_env" +require "db/mongo/models/server" + +require "workers/create_server_worker" +require "workers/bootstrap_worker" + +module Devops + module Version2_0 + module Handler + class Server + + extend StatusCommands + extend ServerCommands + extend BootstrapTemplatesCommands + + scheduler = Rufus::Scheduler.new + + def self.get_servers + lambda { + 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 + + def self.get_chef_servers + lambda { + json KnifeCommands.chef_node_list + } + end + + def self.get_provider_servers + lambda { + json ::Provider::ProviderFactory.get(params[:provider]).servers + } + end + + def self.get_server + lambda { + json Server.get_server_by_key(params[:name], params[:key]).to_hash + } + end + + def self.delete_server + lambda { + body = create_object_from_json_body(Hash, true) + key = (body.nil? ? nil : body["key"]) + s = Server.get_server_by_key(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 + + def self.create_server + lambda { + 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) + Server.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 + 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 + + def self.pause_server + lambda { + s = Server.get_server_by_key(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 + + def self.unpause_server + lambda { + s = Server.get_server_by_key(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 + + def self.reserve_server + lambda { + s = Server.get_server_by_key(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 + + def self.reserve_server + lambda { + s = Server.get_server_by_key(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 + + # TODO: check bootstrap template name + def self.bootstrap_server + lambda { + 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) + + Server.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 + + def self.add_server + lambda { + 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 + + def self.get_server_by_key id, key + key == "instance" ? settings.mongo.server_by_instance_id(id) : settings.mongo.server_by_chef_node_name(id) + end + + def self.check_chef_node_name name, provider + settings.mongo.server_by_chef_node_name name + halt(400, "Server with name '#{name}' already exist") + rescue RecordNotFound => e + # server not found - OK + s = provider.servers.detect {|s| s["name"] == name} + halt(400, "#{provider.name} node with name '#{name}' already exist") unless s.nil? + s = KnifeCommands.chef_node_list.detect {|n| n == name} + halt(400, "Chef node with name '#{name}' already exist") unless s.nil? + s = KnifeCommands.chef_client_list.detect {|c| c == name} + halt(400, "Chef client with name '#{name}' already exist") unless s.nil? + end + + end + + class ExpireHandler + include ServerCommands + + 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, settings.mongo, @logger) + rescue => e + logger.error "ExpiredHandler error: " + e.message + end + end + end + + end + end +end + diff --git a/devops-service/routes/v2.0/handlers/status.rb b/devops-service/routes/v2.0/handlers/status.rb new file mode 100644 index 0000000..0f2eab2 --- /dev/null +++ b/devops-service/routes/v2.0/handlers/status.rb @@ -0,0 +1,22 @@ +require "sidekiq" + +module Devops + module Version2_0 + module Handler + class Status + + def self.get_status + lambda { + 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 + end +end + diff --git a/devops-service/routes/v2.0/handlers/tag.rb b/devops-service/routes/v2.0/handlers/tag.rb new file mode 100644 index 0000000..6b0c647 --- /dev/null +++ b/devops-service/routes/v2.0/handlers/tag.rb @@ -0,0 +1,35 @@ +require "commands/knife_commands" + +module Devops + module Version2_0 + module Handler + class Tag + + def self.get_tags + lambda { + json(KnifeCommands.tags_list(@chef_node_name)) + } + end + + def self.set_tags + lambda { + 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 + + def self.unset_tags + lambda { + 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 + end +end + diff --git a/devops-service/routes/v2.0/handlers/user.rb b/devops-service/routes/v2.0/handlers/user.rb new file mode 100644 index 0000000..769170b --- /dev/null +++ b/devops-service/routes/v2.0/handlers/user.rb @@ -0,0 +1,80 @@ +require "db/exceptions/invalid_record" +require "db/mongo/models/user" + +module Devops + module Version2_0 + module Handler + class User + + def self.get_users + lambda { + users = settings.mongo.users.map {|i| i.to_hash} + users.each {|u| u.delete("password")} + json users + } + end + + def self.create_user + lambda { + user = create_object_from_json_body + ["username", "password", "email"].each do |p| + check_string(user[p], "Parameter '#{p}' must be a not empty string") + end + settings.mongo.user_insert User.new(user) + create_response("Created", nil, 201) + } + end + + def self.delete_user + lambda { + projects = settings.mongo.projects_by_user params[:user] + if !projects.empty? + str = "" + projects.each do |p| + p.deploy_envs.each do |e| + str+="#{p.id}.#{e.identifier} " if e.users.include? params[:user] + end + end + logger.info projects + raise DependencyError.new "Deleting is forbidden: User is included in #{str}" + #return [400, "Deleting is forbidden: User is included in #{str}"] + end + + r = settings.mongo.user_delete params[:user] + create_response("User '#{params[:user]}' removed") + } + end + + def self.change_user_privileges + lambda { + data = create_object_from_json_body + user = settings.mongo.user params[:user] + cmd = check_string(data["cmd"], "Parameter 'cmd' should be a not empty string", true) || "" + privileges = check_string(data["privileges"], "Parameter 'privileges' should be a not empty string", true) || "" + user.grant(cmd, privileges) + settings.mongo.user_update user + create_response("Updated") + } + end + + def self.change_user_email_or_password + lambda { + action = File.basename(request.path) + u = File.basename(File.dirname(request.path)) + raise InvalidPrivileges.new("Access denied for '#{request.env['REMOTE_USER']}'") if u == User::ROOT_USER_NAME and request.env['REMOTE_USER'] != User::ROOT_USER_NAME + + check_privileges("user", "w") unless request.env['REMOTE_USER'] == u + + body = create_object_from_json_body + p = check_string(body[action], "Parameter '#{action}' must be a not empty string") + user = settings.mongo.user u + user.send("#{action}=", p) + settings.mongo.user_update user + create_response("Updated") + } + end + end + end + end +end + diff --git a/devops-service/routes/v2.0/image.rb b/devops-service/routes/v2.0/image.rb index f4ac183..028d0d8 100644 --- a/devops-service/routes/v2.0/image.rb +++ b/devops-service/routes/v2.0/image.rb @@ -1,18 +1,18 @@ -require "providers/provider_factory" -require "commands/image" - module Devops module Version2_0 - module Core + module Routes module ImageRoutes - extend ImageCommands - def self.registered(app) app.after %r{\A/image(/[\w]+)?\z} do statistic end + app.before "/images" do + check_headers :accept + check_privileges("image", "r") + check_provider(params[:provider]) if params[:provider] + end # Get devops images list # # * *Request* @@ -32,14 +32,13 @@ module Devops # "id": "36dc7618-4178-4e29-be43-286fbfe90f50" # } # ] - app.get "/images" do + app.get "/images", &Devops::Version2_0::Handler::Image.get_images + + app.before "/images/provider/:provider" 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}) + check_provider(params[:provider]) end - # Get raw images for :provider # # * *Request* @@ -64,13 +63,18 @@ module Devops # "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 + app.get "/images/provider/:provider", &Devops::Version2_0::Handler::Image.get_provider_images + app.before "/image/:image_id" do + case request.method + when "get" + check_headers :accept + check_privileges("image", "r") + when "delete", "put" + check_headers + check_privileges("image", "w") + end + end # Get devops image by id # # * *Request* @@ -86,12 +90,12 @@ module Devops # "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 + app.get "/image/:image_id", &Devops::Version2_0::Handler::Image.get_image + app.before "/image" do + check_headers + check_privileges("image", "w") + end # Create devops image # # * *Request* @@ -110,13 +114,7 @@ module Devops # # * *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 + app.post "/image", &Devops::Version2_0::Handler::Image.create_image # Update devops image # @@ -136,15 +134,7 @@ module Devops # # * *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 + app.put "/image/:image_id", &Devops::Version2_0::Handler::Image.update_image # Delete devops image # @@ -155,21 +145,7 @@ module Devops # # * *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 - - r = settings.mongo.image_delete params[:image_id] - create_response("Image '#{params[:image_id]}' has been removed") - end + app.delete "/image/:image_id", &Devops::Version2_0::Handler::Image.delete_image puts "Image routes initialized" end diff --git a/devops-service/routes/v2.0/key.rb b/devops-service/routes/v2.0/key.rb index 63e67cb..c5acecd 100644 --- a/devops-service/routes/v2.0/key.rb +++ b/devops-service/routes/v2.0/key.rb @@ -5,23 +5,29 @@ require "fileutils" module Devops module Version2_0 - module Core + module Routes module KeyRoutes 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 + app.before "/keys" do + check_headers :accept + check_privileges("key", "r") + end + + app.before "/key" do + check_headers :accept, :content_type + check_privileges("key", "w") + end + + app.before "/key/:key" do + check_headers :accept + check_privileges("key", "w") + end # Get list of available ssh keys # # * *Request* @@ -36,13 +42,7 @@ module Devops # "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 + app.get "/keys", &Devops::Version2_0::Handler::Key.get_keys # Create ssh key on devops server # @@ -60,22 +60,7 @@ module Devops # # * *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 + app.post "/key", &Devops::Version2_0::Handler::Key.create_key # Delete ssh key from devops server # @@ -86,23 +71,7 @@ module Devops # # * *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 + app.delete "/key/:key", &Devops::Version2_0::Handler::Key.delete_key puts "Key routes initialized" end diff --git a/devops-service/routes/v2.0/network.rb b/devops-service/routes/v2.0/network.rb index f54c374..27dafbe 100644 --- a/devops-service/routes/v2.0/network.rb +++ b/devops-service/routes/v2.0/network.rb @@ -1,13 +1,16 @@ # encoding: UTF-8 -require "json" -require "providers/provider_factory" - module Devops module Version2_0 - module Core + module Routes module NetworkRoutes def self.registered(app) + + app.before "/networks/:provider" do + check_headers :accept + check_privileges("network", "r") + check_provider(params[:provider]) + end # Get list of networks for :provider # # * *Request* @@ -34,13 +37,7 @@ module Devops # "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 + app.get "/networks/:provider", &Devops::Version2_0::Handler::Network.get_networks puts "Network routes initialized" end diff --git a/devops-service/routes/v2.0/project.rb b/devops-service/routes/v2.0/project.rb index f3c4f60..7dd7fd1 100644 --- a/devops-service/routes/v2.0/project.rb +++ b/devops-service/routes/v2.0/project.rb @@ -1,22 +1,10 @@ -require "json" -require "db/mongo/models/project" -require "db/mongo/models/deploy_env" -require "db/exceptions/invalid_record" -require "commands/deploy" -require "commands/status" -require "commands/server" -require "workers/project_test_worker" - module Devops module Version2_0 - module Core + module Routes module ProjectRoutes - extend DeployCommands - extend StatusCommands - extend ServerCommands - def self.registered(app) + app.before "/project/:id/user" do check_headers :accept, :content_type check_privileges("project", "w") @@ -47,17 +35,11 @@ module Devops # [ # {"name" : "project_1"} # ] - app.get "/projects" do + app.before "/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 settings.mongo.projects(nil, nil, fields).map {|p| p.to_hash} end + app.get "/projects", &Devops::Version2_0::Handler::Project.get_projects # Get project by id # @@ -91,11 +73,18 @@ module Devops # ], # "name": "project_1" # } - app.get "/project/:project" do - check_headers :accept - check_privileges("project", "r") - json settings.mongo.project(params[:project]) + app.before "/project/:project" do + if request.get? + check_headers :accept + check_privileges("project", "r") + elsif request.put? or request.delete? + check_headers + check_privileges("project", "w") + else + return [404, "Route not found"] + end end + app.get "/project/:project", &Devops::Version2_0::Handler::Project.get_project # Get project servers # @@ -123,12 +112,11 @@ module Devops # "id": "nstance id" # } # ] - app.get "/project/:project/servers" do + app.before "/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 + app.get "/project/:project/servers", &Devops::Version2_0::Handler::Project.get_project_servers # Create project and chef roles # @@ -165,28 +153,11 @@ module Devops # # * *Returns* : # 201 - Created - # TODO: multi project - app.post "/project" do + app.before "/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 + app.post "/project", &Devops::Version2_0::Handler::Project.create_project # Update project and create chef roles # @@ -223,18 +194,7 @@ module Devops # # * *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 + app.put "/project/:id", &Devops::Version2_0::Handler::Project.update_project # Add users to project environment # @@ -253,16 +213,7 @@ module Devops # # * *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 + app.put "/project/:id/user", &Devops::Version2_0::Handler::Project.update_project_users # Delete users from project environment # @@ -281,13 +232,7 @@ module Devops # # * *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 + app.delete "/project/:id/user", &Devops::Version2_0::Handler::Project.delete_project_users # Set run_list to project environment # @@ -304,18 +249,11 @@ module Devops # # * *Returns* : # 200 - Updated - # TODO: multi project - app.put "/project/:id/:env/run_list" do + app.before "/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 + app.put "/project/:id/:env/run_list", &Devops::Version2_0::Handler::Project.set_project_env_run_list # Delete project # @@ -331,26 +269,7 @@ module Devops # # * *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 + app.delete "/project/:id", &Devops::Version2_0::Handler::Project.delete_project # Run chef-client on reserved project servers # @@ -367,60 +286,11 @@ module Devops # } # # * *Returns* : text stream - app.post "/project/:id/deploy" do + app.before "/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 + app.post "/project/:id/deploy", &Devops::Version2_0::Handler::Project.deploy_project # Test project environment # @@ -488,73 +358,15 @@ module Devops # }, # "message": "Test project 'project_1' and environment 'prod'" # } - app.post "/project/test/:id/:env" do + app.before "/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 - + app.post "/project/test/:id/:env", &Devops::Version2_0::Handler::Project.test_project puts "Project routes initialized" 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 - - 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 end end diff --git a/devops-service/routes/v2.0/provider.rb b/devops-service/routes/v2.0/provider.rb index 1fc4d1b..d99c99a 100644 --- a/devops-service/routes/v2.0/provider.rb +++ b/devops-service/routes/v2.0/provider.rb @@ -5,11 +5,16 @@ require "providers/provider_factory" module Devops module Version2_0 - module Core + module Routes module ProviderRoutes def self.registered(app) + app.before "/providers" do + check_headers :accept + check_privileges("provider", "r") + end + # Get devops providers # # * *Request* @@ -22,16 +27,11 @@ module Devops # "ec2", # "openstack" # ] - app.get "/providers" do - check_headers :accept - check_privileges("provider", "r") - json ::Provider::ProviderFactory.providers - end + app.get "/providers", &Devops::Version2_0::Handler::Provider.get_providers puts "Provider routes initialized" end end end end - #register Version2_0::Core::ProviderRoutes end diff --git a/devops-service/routes/v2.0/script.rb b/devops-service/routes/v2.0/script.rb index ae7aa7b..7e2c590 100644 --- a/devops-service/routes/v2.0/script.rb +++ b/devops-service/routes/v2.0/script.rb @@ -1,10 +1,7 @@ -require "providers/provider_factory" -require "fileutils" -require "commands/status" module Devops module Version2_0 - module Core + module Routes module ScriptRoutes extend StatusCommands @@ -37,13 +34,11 @@ module Devops # [ # "script_1" # ] - app.get "/scripts" do + app.before "/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 + app.get "/scripts", &Devops::Version2_0::Handler::Script.get_scripts # Run command on node :node_name # @@ -53,30 +48,10 @@ module Devops # command to run # # * *Returns* : text stream - app.post "/script/command/:node_name" do + app.before "/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 - out << "\nDone" - rescue IOError => e - logger.error e.message - end - end end + app.post "/script/command/:node_name", &Devops::Version2_0::Handler::Script.execute_command # Run script :script_name on nodes # @@ -91,60 +66,11 @@ module Devops # } # # * *Returns* : text stream - app.post "/script/run/:script_name" do + app.before "/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 + app.post "/script/run/:script_name", &Devops::Version2_0::Handler::Script.run_script # Create script :script_name # @@ -156,10 +82,7 @@ module Devops # # * *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 + app.put "/script/:script_name", &Devops::Version2_0::Handler::Script.create_script # Delete script :script_name # @@ -170,10 +93,7 @@ module Devops # # * *Returns* : # 200 - Deleted - app.delete "/script/:script_name" do - FileUtils.rm(@file) - create_response("File '#{params[:script_name]}' deleted") - end + app.delete "/script/:script_name", &Devops::Version2_0::Handler::Script.delete_script puts "Script routes initialized" end diff --git a/devops-service/routes/v2.0/server.rb b/devops-service/routes/v2.0/server.rb index a583096..20723e4 100644 --- a/devops-service/routes/v2.0/server.rb +++ b/devops-service/routes/v2.0/server.rb @@ -1,45 +1,10 @@ -require "uri" require "json" -require "chef" -require "commands/knife_commands" -require 'rufus-scheduler' -require "providers/provider_factory" -require "db/mongo/models/deploy_env" -require "commands/status" -require "commands/server" -require "commands/bootstrap_templates" -require "workers/create_server_worker" -require "workers/bootstrap_worker" module Devops module Version2_0 - module Core + module Routes module ServerRoutes - class ExpireHandler - include ServerCommands - - 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, settings.mongo, @logger) - rescue => e - logger.error "ExpiredHandler error: " + e.message - end - end - end - - extend StatusCommands - extend ServerCommands - extend BootstrapTemplatesCommands - - scheduler = Rufus::Scheduler.new - def self.registered(app) app.before %r{\A/server/[\w]+/(pause|unpouse|reserve|unreserve)\z} do check_headers :accept, :content_type @@ -68,18 +33,11 @@ module Devops # "chef_node_name": "chef name" # } # ] - app.get "/servers" do + app.before "/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 + app.get "/servers", &Devops::Version2_0::Handler::Server.get_servers # Get chef nodes list # @@ -94,11 +52,11 @@ module Devops # "chef_node_name": "chef name" # } # ] - app.get "/servers/chef" do + app.before "/servers/chef" do check_headers :accept check_privileges("server", "r") - json KnifeCommands.chef_node_list end + app.get "/servers/chef", &Devops::Version2_0::Handler::Server.get_chef_servers # Get provider servers list # @@ -136,11 +94,11 @@ module Devops # "private_ip": "172.17.0.1" # } # ] - app.get "/servers/:provider" do + app.before "/servers/:provider" do check_headers :accept check_privileges("server", "r") - json ::Provider::ProviderFactory.get(params[:provider]).servers end + app.get "/servers/:provider", &Devops::Version2_0::Handler::Server.get_provider_servers # Get server info by :name # @@ -157,11 +115,16 @@ module Devops # "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 + app.before "/server/:name" do + if request.get? + check_headers :accept + check_privileges("server", "r") + elsif request.delete? + check_headers + check_privileges("server", "w") + end end + app.get "/server/:name", &Devops::Version2_0::Handler::Server.get_server # Delete devops server # @@ -177,17 +140,7 @@ module Devops # # * *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 + app.delete "/server/:id", &Devops::Version2_0::Handler::Server.delete_server # Create devops server # @@ -209,62 +162,11 @@ module Devops # } # # * *Returns* : text stream - app.post "/server" do + app.before "/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 - 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 + app.post "/server", &Devops::Version2_0::Handler::Server.create_server # Pause devops server by name # @@ -280,18 +182,7 @@ module Devops # # * *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 + app.post "/server/:node_name/pause", &Devops::Version2_0::Handler::Server.pause_server # Unpause devops server by name # @@ -307,18 +198,7 @@ module Devops # # * *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 + app.post "/server/:node_name/unpause", &Devops::Version2_0::Handler::Server.unpause_server # Reserve devops server # @@ -334,15 +214,7 @@ module Devops # # * *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 + app.post "/server/:node_name/reserve", &Devops::Version2_0::Handler::Server.reserve_server # Unreserve devops server # @@ -358,14 +230,7 @@ module Devops # # * *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 + app.post "/server/:node_name/unreserve", &Devops::Version2_0::Handler::Server.unreserve_server # Bootstrap devops server # @@ -383,80 +248,11 @@ module Devops # } # # * *Returns* : text stream - # TODO: check bootstrap template name - app.post "/server/bootstrap" do + app.before "/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 + app.post "/server/bootstrap", &Devops::Version2_0::Handler::Server.bootstrap_server # Add external server to devops # @@ -477,58 +273,15 @@ module Devops # # * *Returns* : # 200 - Added - # TODO: should be refactored - app.post "/server/add" do + app.before "/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 + app.post "/server/add", &Devops::Version2_0::Handler::Server.add_server puts "Server routes initialized" end - - def get_server id, key - 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 - settings.mongo.server_by_chef_node_name name - halt(400, "Server with name '#{name}' already exist") - rescue RecordNotFound => e - # server not found - OK - s = provider.servers.detect {|s| s["name"] == name} - halt(400, "#{provider.name} node with name '#{name}' already exist") unless s.nil? - s = KnifeCommands.chef_node_list.detect {|n| n == name} - halt(400, "Chef node with name '#{name}' already exist") unless s.nil? - s = KnifeCommands.chef_client_list.detect {|c| c == name} - halt(400, "Chef client with name '#{name}' already exist") unless s.nil? - end - end end end diff --git a/devops-service/routes/v2.0/status.rb b/devops-service/routes/v2.0/status.rb index 10b3a90..69afa83 100644 --- a/devops-service/routes/v2.0/status.rb +++ b/devops-service/routes/v2.0/status.rb @@ -1,19 +1,10 @@ -require "json" -require "sidekiq" - module Devops module Version2_0 - module Core + module Routes module StatusRoutes 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 + app.get "/status/:id", &Devops::Version2_0::Handler::Status.get_status puts "Status routes initialized" end diff --git a/devops-service/routes/v2.0/tag.rb b/devops-service/routes/v2.0/tag.rb index 4ab735f..83fb1d7 100644 --- a/devops-service/routes/v2.0/tag.rb +++ b/devops-service/routes/v2.0/tag.rb @@ -1,8 +1,6 @@ -require "commands/knife_commands" - module Devops module Version2_0 - module Core + module Routes module TagRoutes def self.registered(app) @@ -16,7 +14,7 @@ module Devops @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]) + server = settings.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 @@ -36,9 +34,7 @@ module Devops # [ # "tag_1" # ] - app.get "/tags/:node_name" do - json(KnifeCommands.tags_list(@chef_node_name)) - end + app.get "/tags/:node_name", &Devops::Version2_0::Handler::Tag.get_tags # Set tags list to :node_name # @@ -54,12 +50,7 @@ module Devops # # * *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 + app.post "/tags/:node_name", &Devops::Version2_0::Handler::Tag.set_tags # Delete tags from :node_name # @@ -75,12 +66,7 @@ module Devops # # * *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 + app.delete "/tags/:node_name", &Devops::Version2_0::Handler::Tag.unset_tags puts "Tag routes initialized" end diff --git a/devops-service/routes/v2.0/user.rb b/devops-service/routes/v2.0/user.rb index e983995..f8534fb 100644 --- a/devops-service/routes/v2.0/user.rb +++ b/devops-service/routes/v2.0/user.rb @@ -1,13 +1,9 @@ -require "db/exceptions/invalid_record" -require "db/mongo/models/user" - module Devops module Version2_0 - module Core + module Routes module UserRoutes def self.registered(app) - puts "User routes initialized" app.after %r{\A/user(/[\w]+(/password)?)?\z} do statistic @@ -41,13 +37,11 @@ module Devops # "id": "test" # } # ] - app.get "/users" do + app.before "/users" do check_headers :accept check_privileges("user", "r") - users = settings.mongo.users.map {|i| i.to_hash} - users.each {|u| u.delete("password")} - json users end + app.get "/users", &Devops::Version2_0::Handler::User.get_users # Create user # @@ -65,17 +59,11 @@ module Devops # # * *Returns* : # 201 - Created - app.post "/user" do + app.before "/user" do check_headers :accept, :content_type check_privileges("user", "w") - user = create_object_from_json_body - ["username", "password", "email"].each do |p| - check_string(user[p], "Parameter '#{p}' must be a not empty string") - end - #BaseRoutes.mongo.user_insert User.new(user) - settings.mongo.user_insert User.new(user) - create_response("Created", nil, 201) end + app.post "/user", &Devops::Version2_0::Handler::User.create_user # Delete user # @@ -86,25 +74,15 @@ module Devops # # * *Returns* : # 200 - Deleted - app.delete "/user/:user" do - check_headers :accept - check_privileges("user", "w") - projects = settings.mongo.projects_by_user params[:user] - if !projects.empty? - str = "" - projects.each do |p| - p.deploy_envs.each do |e| - str+="#{p.id}.#{e.identifier} " if e.users.include? params[:user] - end - end - logger.info projects - raise DependencyError.new "Deleting is forbidden: User is included in #{str}" - #return [400, "Deleting is forbidden: User is included in #{str}"] + app.before "/user/:user" do + if request.delete? + check_headers :accept + elsif request.put? + check_headers :accept, :content_type end - - r = settings.mongo.user_delete params[:user] - create_response("User '#{params[:user]}' removed") + check_privileges("user", "w") end + app.delete "/user/:user", &Devops::Version2_0::Handler::User.delete_user # Change user privileges # @@ -121,17 +99,7 @@ module Devops # # * *Returns* : # 200 - Updated - app.put "/user/:user" do - check_headers :accept, :content_type - check_privileges("user", "w") - data = create_object_from_json_body - user = settings.mongo.user params[:user] - cmd = check_string(data["cmd"], "Parameter 'cmd' should be a not empty string", true) || "" - privileges = check_string(data["privileges"], "Parameter 'privileges' should be a not empty string", true) || "" - user.grant(cmd, privileges) - settings.mongo.user_update user - create_response("Updated") - end + app.put "/user/:user", &Devops::Version2_0::Handler::User.change_user_privileges # Change user email/password # @@ -147,21 +115,12 @@ module Devops # # * *Returns* : # 200 - Updated - app.put %r{\A/user/[\w]+/(email|password)\z} do + app.before %r{\A/user/[\w]+/(email|password)\z} do check_headers :accept, :content_type - action = File.basename(request.path) - u = File.basename(File.dirname(request.path)) - raise InvalidPrivileges.new("Access denied for '#{request.env['REMOTE_USER']}'") if u == User::ROOT_USER_NAME and request.env['REMOTE_USER'] != User::ROOT_USER_NAME - - check_privileges("user", "w") unless request.env['REMOTE_USER'] == u - - body = create_object_from_json_body - p = check_string(body[action], "Parameter '#{action}' must be a not empty string") - user = settings.mongo.user u - user.send("#{action}=", p) - settings.mongo.user_update user - create_response("Updated") end + app.put %r{\A/user/[\w]+/(email|password)\z}, &Devops::Version2_0::Handler::User.change_user_email_or_password + + puts "User routes initialized" end end diff --git a/devops-service/some_class.rb b/devops-service/some_class.rb new file mode 100644 index 0000000..8a30bc7 --- /dev/null +++ b/devops-service/some_class.rb @@ -0,0 +1,10 @@ +require "test_subscriber" +class SomeClass + + include Wisper::Publisher + + def sbc + self.subscribe TestSubscriber.new +# self.call("hello 2") + end +end diff --git a/devops-service/test_subscriber.rb b/devops-service/test_subscriber.rb new file mode 100644 index 0000000..e1fe1db --- /dev/null +++ b/devops-service/test_subscriber.rb @@ -0,0 +1,6 @@ +class TestSubscriber + def cancel_order_failed data + puts data + raise data + end +end diff --git a/devops-service/test_subscriber_2.rb b/devops-service/test_subscriber_2.rb new file mode 100644 index 0000000..a37b7ce --- /dev/null +++ b/devops-service/test_subscriber_2.rb @@ -0,0 +1,5 @@ +class TestSubscriber2 + def cancel_order_failed data + puts data + end +end diff --git a/devops-service/wisper_fix.rb b/devops-service/wisper_fix.rb new file mode 100644 index 0000000..2f5bd91 --- /dev/null +++ b/devops-service/wisper_fix.rb @@ -0,0 +1,15 @@ +module Wisper + module Publisher + def broadcast(event, *args) + registrations.each do | registration | + begin + registration.broadcast(clean_event(event), self, *args) + rescue => e + puts "Error: #{registration.class} #{event} #{e.message}" + end + end + self + end + end +end +