routes handlers

This commit is contained in:
amartynov 2014-12-22 14:22:04 +03:00
parent 79e2811e0c
commit de2842f2a4
40 changed files with 1616 additions and 1023 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
devops-service/tests/features/support/config.yml
.devops_files/
devops-service/plugins

View File

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

View File

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

View File

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

50
devops-service/loader.rb Normal file
View File

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

View File

@ -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
end
stack.run app
@app = stack.to_app
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
def call(env)
@app.call env
end
end
end

View File

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

View File

@ -1,23 +1,18 @@
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"
def self.registered(app)
app.after "/deploy" do
statistic
end
after "/deploy" do
statistic
app.before "/deploy" do
check_headers :content_type
check_privileges("server", "x")
broadcast(:devops_deploy, "deploy")
end
# Run chef-client on reserved server
@ -34,57 +29,12 @@ module Version2_0
# }
#
# * *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) || []
app.post "/deploy", &Devops::Version2_0::Handler::Deploy.deploy
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
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
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
puts "Deploy routes initialized"
end
end
end
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
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")
check_provider(params[:provider])
json get_images(settings.mongo, params[:provider])
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

View File

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

View File

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

View File

@ -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
app.before "/project/:project" do
if request.get?
check_headers :accept
check_privileges("project", "r")
json settings.mongo.project(params[:project])
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

View File

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

View File

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

View File

@ -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
app.before "/server/:name" do
if request.get?
check_headers :accept
check_privileges("server", "r")
json get_server(params[:name], params[:key]).to_hash
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

View File

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

View File

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

View File

@ -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
app.before "/user/:user" do
if request.delete?
check_headers :accept
elsif request.put?
check_headers :accept, :content_type
end
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}"]
end
r = settings.mongo.user_delete params[:user]
create_response("User '#{params[:user]}' removed")
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

View File

@ -0,0 +1,10 @@
require "test_subscriber"
class SomeClass
include Wisper::Publisher
def sbc
self.subscribe TestSubscriber.new
# self.call("hello 2")
end
end

View File

@ -0,0 +1,6 @@
class TestSubscriber
def cancel_order_failed data
puts data
raise data
end
end

View File

@ -0,0 +1,5 @@
class TestSubscriber2
def cancel_order_failed data
puts data
end
end

View File

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