Merge branch 'cid-394-deploy_delete_list_of_servers' into cid-395-bootstrap_list_of_ips
This commit is contained in:
commit
2987e3d82e
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,6 @@
|
||||
devops-service/tests/features/support/config.yml
|
||||
.devops_files/
|
||||
devops-service/plugins
|
||||
devops-service/spec/examples.txt
|
||||
devops-service/spec/examples.txt
|
||||
devops-service/coverage
|
||||
devops-service/tmp
|
||||
@ -49,7 +49,7 @@ module DevopsClient
|
||||
if result.is_a?(Hash)
|
||||
puts result["message"]
|
||||
else
|
||||
puts result
|
||||
puts result if result
|
||||
end
|
||||
rescue OptionParser::InvalidOption => e
|
||||
puts e.message
|
||||
|
||||
@ -26,7 +26,8 @@ class Deploy < Handler
|
||||
@options_parser.invalid_deploy_command
|
||||
abort()
|
||||
end
|
||||
post_chunk("/deploy", :names => names, :tags => tags)
|
||||
job_ids = post("/deploy", :names => names, :tags => tags)
|
||||
reports_urls(job_ids)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -87,7 +87,7 @@ class DeployEnv
|
||||
end
|
||||
|
||||
def fetcher
|
||||
@fetcher ||= Helpers::ResourcesFetcher.new(host: @host, handler_object_options: @options, auth: @auth)
|
||||
@fetcher ||= Helpers::ResourcesFetcher.new(host: @host, handler_options: @options, auth: @auth)
|
||||
end
|
||||
|
||||
|
||||
|
||||
@ -26,7 +26,11 @@ class Handler
|
||||
attr_accessor :auth
|
||||
|
||||
def host
|
||||
"http://#{@host}"
|
||||
if @host.start_with?('http')
|
||||
@host
|
||||
else
|
||||
"http://#{@host}"
|
||||
end
|
||||
end
|
||||
|
||||
#TODO: only basic auth now
|
||||
@ -50,30 +54,9 @@ protected
|
||||
end
|
||||
|
||||
def fetcher
|
||||
@fetcher ||= Helpers::ResourcesFetcher.new(host: @host, handler_object_options: @options, auth: @auth)
|
||||
@fetcher ||= Helpers::ResourcesFetcher.new(host: @host, handler_options: @options, auth: @auth)
|
||||
end
|
||||
|
||||
def params_filter params
|
||||
r = []
|
||||
return params if params.kind_of?(String)
|
||||
params.each do |k,v|
|
||||
key = k.to_s
|
||||
if v.kind_of?(Array)
|
||||
v.each do |val|
|
||||
r.push "#{key}[]=#{val}"
|
||||
end
|
||||
elsif v.kind_of?(Hash)
|
||||
buf = {}
|
||||
v.each do |k1,v1|
|
||||
buf["#{key}[#{k1}]"] = v1
|
||||
end
|
||||
r = r + params_filter(buf)
|
||||
else
|
||||
r.push "#{key}=#{v}"
|
||||
end
|
||||
end
|
||||
r
|
||||
end
|
||||
|
||||
def inspect_parameters names, *args
|
||||
names.each_with_index do |name, i|
|
||||
|
||||
@ -143,4 +143,26 @@ module HttpUtils
|
||||
params_filter(params.select{|k,v| k != :cmd and !v.nil?}).join("&")
|
||||
end
|
||||
|
||||
def params_filter params
|
||||
r = []
|
||||
return params if params.kind_of?(String)
|
||||
params.each do |k,v|
|
||||
key = k.to_s
|
||||
if v.kind_of?(Array)
|
||||
v.each do |val|
|
||||
r.push "#{key}[]=#{val}"
|
||||
end
|
||||
elsif v.kind_of?(Hash)
|
||||
buf = {}
|
||||
v.each do |k1,v1|
|
||||
buf["#{key}[#{k1}]"] = v1
|
||||
end
|
||||
r = r + params_filter(buf)
|
||||
else
|
||||
r.push "#{key}=#{v}"
|
||||
end
|
||||
end
|
||||
r
|
||||
end
|
||||
|
||||
end
|
||||
@ -13,6 +13,17 @@ module Outputtable
|
||||
outputter.output(options)
|
||||
end
|
||||
|
||||
def report_url(job_id)
|
||||
create_url "report/#{job_id}"
|
||||
end
|
||||
|
||||
def reports_urls(job_ids)
|
||||
raise "Parameter should be an array of strings" unless job_ids.is_a?(Array)
|
||||
job_ids.map do |job_id|
|
||||
report_url(job_id)
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
require "devops-client/handler/helpers/http_utils"
|
||||
require 'devops-client/helpers/string_helper'
|
||||
|
||||
# fetches resources list along with table
|
||||
# Rewrite this to avoid dependency on handlers
|
||||
module Helpers
|
||||
class ResourcesFetcher
|
||||
include HttpUtils
|
||||
|
||||
# have the same meaning as in handlers
|
||||
attr_reader :username, :password, :options, :host
|
||||
|
||||
def initialize(options)
|
||||
@host = options.fetch(:host)
|
||||
@handler_object_options = options.fetch(:handler_object_options)
|
||||
@host = "http://#{options.fetch(:host)}"
|
||||
@options = options.fetch(:handler_options)
|
||||
@auth = options.fetch(:auth)
|
||||
|
||||
# username, password and options are used to perform http queries with module HttpUtils
|
||||
@username, @password = @auth[:username], @auth[:password]
|
||||
end
|
||||
|
||||
def fetch(collection_name, *args)
|
||||
@ -19,12 +28,21 @@ module Helpers
|
||||
[handler.list_handler(*args), handler.outputter.table]
|
||||
end
|
||||
|
||||
def fetch_project(project_id)
|
||||
@fetched_projects = {}
|
||||
if cached = @fetched_projects[project_id]
|
||||
cached
|
||||
else
|
||||
@fetched_projects[project_id] = get("/project/#{project_id}")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_handler(collection_name)
|
||||
require_handler_file(collection_name)
|
||||
|
||||
handler = resource_handler_klass(collection_name).new(@host, @handler_object_options)
|
||||
handler = resource_handler_klass(collection_name).new(host, options)
|
||||
handler.auth = @auth
|
||||
handler
|
||||
end
|
||||
|
||||
@ -1,38 +1,36 @@
|
||||
require 'devops-client/handler/helpers/resources_fetcher'
|
||||
require 'devops-client/handler/helpers/input_utils'
|
||||
require 'devops-client/output/project'
|
||||
|
||||
module Helpers
|
||||
class ResourcesSelector
|
||||
|
||||
include InputUtils
|
||||
|
||||
# fetcher_instance_or_attrs should be:
|
||||
# instance of ResourcesFetcher
|
||||
# OR
|
||||
# hash with these keys:
|
||||
# :host
|
||||
# :handler_object_options
|
||||
# :auth
|
||||
def initialize(fetcher_instance_or_attrs)
|
||||
if fetcher_instance_or_attrs.is_a?(ResourcesFetcher)
|
||||
@fetcher = fetcher_instance_or_attrs
|
||||
else
|
||||
@fetcher = ResourcesFetcher.new(fetcher_instance_or_attrs)
|
||||
end
|
||||
def initialize(fetcher)
|
||||
raise "fetcher should be instance of ResourcesFetcher" unless fetcher.is_a?(ResourcesFetcher)
|
||||
@fetcher = fetcher
|
||||
end
|
||||
|
||||
def select_available_provider(options={})
|
||||
def select_available_provider
|
||||
providers, table = @fetcher.fetch_with_table('provider')
|
||||
# somewhy returns provider name as String.
|
||||
select_item_from_table(I18n.t("headers.provider"), providers, table)
|
||||
end
|
||||
|
||||
def select_available_project(options={})
|
||||
def select_available_project
|
||||
projects, table = @fetcher.fetch_with_table('project')
|
||||
project = select_item_from_table(I18n.t("headers.project"), projects, table)
|
||||
project['name']
|
||||
end
|
||||
|
||||
def select_available_env(project_id)
|
||||
project = @fetcher.fetch_project(project_id)
|
||||
outputter = Output::Project.new(project, {output_type: :show, with_num: true})
|
||||
env = select_item_from_table("Select deploy env", project['deploy_envs'], outputter.table)
|
||||
env['identifier']
|
||||
end
|
||||
|
||||
def select_available_stack_template(options={})
|
||||
stack_templates, table = @fetcher.fetch_with_table('stack_template', options[:provider])
|
||||
stack_template = select_item_from_table(I18n.t("headers.stack_template"), stack_templates, table)
|
||||
|
||||
@ -87,7 +87,6 @@ class Project < Handler
|
||||
when "delete_servers"
|
||||
self.options = @options_parser.delete_servers_options
|
||||
delete_servers_handler @options_parser.args
|
||||
output(output_type: :delete_servers)
|
||||
else
|
||||
@options_parser.invalid_command
|
||||
end
|
||||
@ -279,7 +278,8 @@ class Project < Handler
|
||||
q = {}
|
||||
q[:servers] = options[:servers] unless options[:servers].nil?
|
||||
q[:deploy_env] = args[3] unless args[3].nil?
|
||||
post_chunk "/project/#{args[2]}/deploy", q
|
||||
job_ids = post "/project/#{args[2]}/deploy", q
|
||||
reports_urls(job_ids)
|
||||
end
|
||||
|
||||
def test_handler args
|
||||
@ -288,8 +288,8 @@ class Project < Handler
|
||||
@options_parser.invalid_test_command
|
||||
abort(r)
|
||||
end
|
||||
response = post "/project/test/#{args[2]}/#{args[3]}"
|
||||
puts response.inspect
|
||||
job_ids = post "/project/test/#{args[2]}/#{args[3]}"
|
||||
reports_urls(job_ids)
|
||||
end
|
||||
|
||||
protected
|
||||
@ -456,7 +456,8 @@ protected
|
||||
deploy_env: env,
|
||||
dry_run: false
|
||||
}
|
||||
@list = delete("/project/#{project}/servers", body)
|
||||
response = delete("/project/#{project}/servers", body)
|
||||
reports_urls(response['reports'])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@ -25,6 +25,8 @@ class Server < Handler
|
||||
create_handler
|
||||
when :delete
|
||||
delete_handler
|
||||
when :delete_list
|
||||
delete_list_handler
|
||||
when :bootstrap
|
||||
bootstrap_handler
|
||||
when :sync
|
||||
@ -77,7 +79,8 @@ class Server < Handler
|
||||
q[k] = self.options[k] unless self.options[k].nil?
|
||||
end
|
||||
|
||||
post_chunk "/server", q
|
||||
job_ids = post "/server", q
|
||||
reports_urls(job_ids)
|
||||
end
|
||||
|
||||
def delete_handler
|
||||
@ -88,11 +91,24 @@ class Server < Handler
|
||||
abort(r)
|
||||
end
|
||||
if question(I18n.t("handler.server.question.delete", :name => name))
|
||||
puts "Server '#{name}', deleting..."
|
||||
o = delete("/server/#{name}", options)
|
||||
["server", "chef_node", "chef_client", "message"].each do |k|
|
||||
puts o[k] unless o[k].nil?
|
||||
end
|
||||
jobs_ids = delete("/server/#{name}", options) # returns array with one job id, actually
|
||||
puts reports_urls(jobs_ids)
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# this method differs from #delete_handler in this:
|
||||
# it deletes multiple servers at once and takes servers ids instead of node names.
|
||||
# Timur said we planned to transfer all server requests to using ids by default, that's why
|
||||
# later we could get rid of #delete_list method.
|
||||
def delete_list_handler
|
||||
abort "Please specify at least one server id" if @args.length < 3
|
||||
server_ids = @args[2..-1]
|
||||
if question(I18n.t("handler.server.question.delete_list", ids: server_ids.join(', ')))
|
||||
servers_jobs = post("/server/delete_list", {servers_ids: server_ids})
|
||||
servers_jobs.each do |server_id, job_id|
|
||||
puts "Report for deleting #{server_id}: #{report_url(job_id)}"
|
||||
end
|
||||
end
|
||||
nil
|
||||
@ -122,7 +138,8 @@ class Server < Handler
|
||||
if q.has_key?(:run_list)
|
||||
abort unless Project.validate_run_list(q[:run_list])
|
||||
end
|
||||
post_chunk "/server/bootstrap", q
|
||||
job_ids = post "/server/bootstrap", q
|
||||
reports_urls(job_ids)
|
||||
end
|
||||
|
||||
def add_static_handler # add <project> <env> <private_ip> <ssh_username> <keyname> --public-ip <public_ip>
|
||||
|
||||
@ -51,19 +51,19 @@ class Stack < Handler
|
||||
q = {}
|
||||
|
||||
q[:without_bootstrap] = options[:without_bootstrap]
|
||||
q[:provider] = options[:provider] || resources_selector.select_available_provider
|
||||
# q[:id] = options[:id] || enter_parameter(I18n.t('handler.stack.create.id'))
|
||||
# q[:provider] = options[:provider] || resources_selector.select_available_provider
|
||||
q[:project] = options[:project] || resources_selector.select_available_project
|
||||
q[:deploy_env] = options[:deploy_env] || enter_parameter(I18n.t('handler.stack.create.deploy_env'))
|
||||
# q[:run_list] = options[:run_list] || enter_parameter_or_empty(I18n.t('handler.stack.create.run_list'))
|
||||
# q[:run_list] = q[:run_list].split(',')
|
||||
q[:deploy_env] = options[:deploy_env] || resources_selector.select_available_env(q[:project])
|
||||
env = fetcher.fetch_project(q[:project])['deploy_envs'].detect {|env| env['identifier'] == q[:deploy_env]}
|
||||
q[:provider] = env['provider']
|
||||
|
||||
filepath = options[:parameters_file] || enter_parameter(I18n.t('handler.stack.create.parameters_file'))
|
||||
q[:parameters] = JSON.parse(File.read(filepath))
|
||||
|
||||
json = JSON.pretty_generate(q)
|
||||
if question(I18n.t("handler.stack.question.create")) {puts json}
|
||||
post_body "/stack", json
|
||||
job_ids = post_body "/stack", json
|
||||
reports_urls(job_ids)
|
||||
end
|
||||
end
|
||||
|
||||
@ -123,8 +123,8 @@ class Stack < Handler
|
||||
|
||||
def deploy_handler
|
||||
stack_id = @args[2]
|
||||
response = post "/stack/#{stack_id}/deploy"
|
||||
puts response.inspect
|
||||
job_ids = post "/stack/#{stack_id}/deploy"
|
||||
reports_urls(job_ids)
|
||||
end
|
||||
|
||||
def reserve_handler
|
||||
|
||||
@ -2,7 +2,7 @@ require "devops-client/options/common_options"
|
||||
|
||||
class ServerOptions < CommonOptions
|
||||
|
||||
commands :add, :bootstrap, :create, :delete, :list, :pause, :reserve, :show, :unpause, :unreserve, :add_and_bootstrap_list
|
||||
commands :add, :bootstrap, :create, :delete, :list, :pause, :reserve, :show, :unpause, :unreserve, :delete_list, :add_and_bootstrap_list
|
||||
|
||||
def initialize args, def_options
|
||||
super(args, def_options)
|
||||
@ -127,7 +127,7 @@ class ServerOptions < CommonOptions
|
||||
options[:groups] = groups.split(",")
|
||||
end
|
||||
|
||||
parser.recognize_option_value(:private_ip, 'server', short: '-N', i18n_scope: 'create')
|
||||
parser.recognize_option_value(:private_ip, short: '-N', i18n_scope: 'create')
|
||||
|
||||
# it was disabled somewhy
|
||||
# parser.on('--public-ip', "Associate public IP with server") do
|
||||
@ -172,4 +172,8 @@ class ServerOptions < CommonOptions
|
||||
self.banner_header + " delete NODE_NAME [NODE_NAME ...]\n"
|
||||
end
|
||||
|
||||
def delete_list_banner
|
||||
self.banner_header + " delete_list INSTANCE_ID [INSTANCE_ID ...]\n"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -38,7 +38,7 @@ module Output
|
||||
end
|
||||
|
||||
def with_num?
|
||||
outputting_list?
|
||||
@options[:with_num] || outputting_list?
|
||||
end
|
||||
|
||||
def create_table headers, rows, title=nil, with_num=true, separator=false
|
||||
|
||||
@ -27,8 +27,6 @@ module Output
|
||||
when :test
|
||||
title = I18n.t("output.title.project.test", :project => ARGV[2], :env => ARGV[3])
|
||||
create_test(@data)
|
||||
when :delete_servers
|
||||
return delete_servers_output
|
||||
else
|
||||
title = I18n.t("output.title.project.list")
|
||||
create_list(@data)
|
||||
@ -62,7 +60,7 @@ module Output
|
||||
|
||||
def create_show show
|
||||
rows = []
|
||||
headers = if show["type"] == "multi"
|
||||
if show["type"] == "multi"
|
||||
show["deploy_envs"].each do |de|
|
||||
subprojects = []
|
||||
nodes = []
|
||||
@ -74,14 +72,14 @@ module Output
|
||||
end
|
||||
rows.push [ de["identifier"], subprojects.join("\n"), nodes.join("\n"), de["users"].join("\n") ]
|
||||
end
|
||||
[
|
||||
headers = [
|
||||
I18n.t("output.table_header.deploy_env"),
|
||||
I18n.t("output.table_header.subproject") + " - " + I18n.t("output.table_header.deploy_env"),
|
||||
I18n.t("output.table_header.node_number"),
|
||||
I18n.t("output.table_header.users")
|
||||
]
|
||||
else
|
||||
show["deploy_envs"].each do |de|
|
||||
show["deploy_envs"].each_with_index do |de, i|
|
||||
rows.push [
|
||||
show["name"],
|
||||
de["identifier"],
|
||||
@ -93,7 +91,7 @@ module Output
|
||||
(de["users"] || []).join("\n")
|
||||
]
|
||||
end
|
||||
[
|
||||
headers = [
|
||||
I18n.t("output.table_header.id"),
|
||||
I18n.t("output.table_header.deploy_env"),
|
||||
I18n.t("output.table_header.image_id"),
|
||||
@ -151,22 +149,5 @@ module Output
|
||||
headers_and_rows(stacks, fields_to_output)
|
||||
end
|
||||
|
||||
def delete_servers_output
|
||||
output = ''
|
||||
|
||||
if @data['deleted'].empty?
|
||||
output << 'There are no deleted servers.'
|
||||
else
|
||||
output << "Deleted servers:\n----\n"
|
||||
output << @data['deleted'].join("\n")
|
||||
end
|
||||
|
||||
if !@data['failed'].empty?
|
||||
output << "\nThere were errors during deleting these servers:\n----\n"
|
||||
output << @data['failed'].join("\n")
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@ -43,6 +43,7 @@ en:
|
||||
user: "User"
|
||||
stack: "Stack"
|
||||
stack_template: "Stack template"
|
||||
env: "Deploy environment"
|
||||
handler:
|
||||
flavor:
|
||||
list:
|
||||
@ -102,6 +103,7 @@ en:
|
||||
server:
|
||||
question:
|
||||
delete: "Are you sure to delete server '%{name}'?"
|
||||
delete_list: "Are you sure to delete these servers: %{ids}?"
|
||||
stack_template:
|
||||
create:
|
||||
id: "Id: "
|
||||
@ -214,6 +216,7 @@ en:
|
||||
show: "Project '%{name}' information"
|
||||
servers: "Project '%{title}' servers"
|
||||
test: "Project test: %{project} - %{env}"
|
||||
envs: "Project '%{name}' deploy envs"
|
||||
provider:
|
||||
list: "Providers"
|
||||
script:
|
||||
|
||||
@ -36,4 +36,6 @@ end
|
||||
group :devepoment do
|
||||
gem 'byebug'
|
||||
gem 'guard-rspec', require: false
|
||||
gem 'simplecov', require: false
|
||||
gem 'simplecov-rcov', require: false
|
||||
end
|
||||
|
||||
@ -60,6 +60,7 @@ GEM
|
||||
gherkin (~> 2.12.0)
|
||||
daemons (1.2.3)
|
||||
diff-lcs (1.2.5)
|
||||
docile (1.1.5)
|
||||
em-websocket (0.3.8)
|
||||
addressable (>= 2.1.1)
|
||||
eventmachine (>= 0.12.9)
|
||||
@ -291,6 +292,13 @@ GEM
|
||||
json
|
||||
redis (>= 3.0.6)
|
||||
redis-namespace (>= 1.3.1)
|
||||
simplecov (0.11.1)
|
||||
docile (~> 1.1.0)
|
||||
json (~> 1.8)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
simplecov-rcov (0.2.3)
|
||||
simplecov (>= 0.4.1)
|
||||
sinatra (1.4.5)
|
||||
rack (~> 1.4)
|
||||
rack-protection (~> 1.4)
|
||||
@ -352,9 +360,14 @@ DEPENDENCIES
|
||||
rspec (~> 3.3)
|
||||
rspec_junit_formatter
|
||||
sidekiq (= 3.2.6)
|
||||
simplecov
|
||||
simplecov-rcov
|
||||
sinatra (= 1.4.5)
|
||||
sinatra-contrib
|
||||
sinatra-websocket
|
||||
test-unit
|
||||
thin (~> 1.5.1)
|
||||
wisper
|
||||
|
||||
BUNDLED WITH
|
||||
1.11.2
|
||||
|
||||
@ -42,4 +42,5 @@ guard :rspec, cmd: "rspec" do
|
||||
|
||||
# Devops files
|
||||
watch(%r{db/.+\.rb}) { rspec.spec_dir }
|
||||
watch(%r{lib/executors/.+\.rb}) { "#{rspec.spec_dir}/executors" }
|
||||
end
|
||||
|
||||
@ -4,6 +4,7 @@ require "workers/project_test_worker"
|
||||
require "app/api2/parsers/project"
|
||||
require "lib/project/type/types_factory"
|
||||
require "lib/executors/server_executor"
|
||||
require "workers/delete_server_worker"
|
||||
|
||||
require_relative "../helpers/version_2.rb"
|
||||
require_relative "request_handler"
|
||||
@ -314,16 +315,11 @@ module Devops
|
||||
private
|
||||
|
||||
def delete_chosen_servers!(servers)
|
||||
deleted, failed = [], []
|
||||
servers.each do |server|
|
||||
begin
|
||||
Devops::Executor::ServerExecutor.new(server, '').delete_server
|
||||
deleted << server.id
|
||||
rescue
|
||||
failed << server.id
|
||||
end
|
||||
current_user = parser.current_user
|
||||
reports = servers.map do |server|
|
||||
Worker.start_async(DeleteServerWorker, 'server_id' => server.id, 'current_user' => current_user)
|
||||
end
|
||||
{deleted: deleted, failed: failed}
|
||||
{reports: reports}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -27,14 +27,14 @@ module Devops
|
||||
def add_account provider
|
||||
account = ::Provider::ProviderFactory.get_accounts_factory(provider).create_account(parser.account)
|
||||
account.validate_fields!
|
||||
Devops::Db.connector.provider_accounts_insert(account)
|
||||
Devops::Db.connector.provider_account_insert(account)
|
||||
::Provider::ProviderFactory.add_account(provider, account)
|
||||
account.to_hash
|
||||
end
|
||||
|
||||
def delete_account name, provider
|
||||
account = Devops::Db.connector.provider_account(provider, name)
|
||||
Devops::Db.connector.provider_accounts_delete(name)
|
||||
Devops::Db.connector.provider_account_delete(name)
|
||||
::Provider::ProviderFactory.delete_account(provider, account)
|
||||
account.to_hash
|
||||
end
|
||||
|
||||
@ -10,6 +10,7 @@ require "db/mongo/models/server"
|
||||
|
||||
require "workers/create_server_worker"
|
||||
require "workers/bootstrap_worker"
|
||||
require "workers/delete_server_worker"
|
||||
require "app/api2/parsers/server"
|
||||
require_relative "request_handler"
|
||||
|
||||
@ -47,10 +48,22 @@ module Devops
|
||||
end
|
||||
|
||||
def delete id
|
||||
s = get_server_by_key(id, parser.instance_key)
|
||||
### Authorization
|
||||
Devops::Db.connector.check_project_auth s.project, s.deploy_env, parser.current_user
|
||||
Devops::Executor::ServerExecutor.new(s, "").delete_server
|
||||
server = get_server_by_key(id, parser.instance_key)
|
||||
current_user = parser.current_user
|
||||
Devops::Db.connector.check_project_auth server.project, server.deploy_env, current_user
|
||||
jid = Worker.start_async(DeleteServerWorker, 'server_id' => server.id, 'current_user' => current_user)
|
||||
[jid]
|
||||
end
|
||||
|
||||
def delete_list
|
||||
server_ids = parser.delete_list.uniq
|
||||
servers = server_ids.map { |id| Devops::Db.connector.server_by_instance_id(id) }
|
||||
current_user = parser.current_user
|
||||
check_servers_list_auth(servers, current_user)
|
||||
server_ids.inject({}) do |hash, server_id|
|
||||
hash[server_id] = Worker.start_async(DeleteServerWorker, 'server_id' => server_id, 'current_user' => current_user)
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
def create_server_stream out
|
||||
@ -238,8 +251,8 @@ module Devops
|
||||
project, deploy_env, server_attrs = parser.add_server
|
||||
Devops::Db.connector.check_project_auth project, deploy_env, parser.current_user
|
||||
|
||||
add_static_server(server_attrs)
|
||||
"Server '#{s.id}' has been added"
|
||||
server = add_static_server(server_attrs)
|
||||
"Server '#{server.id}' has been added"
|
||||
end
|
||||
|
||||
# returns jobs ids
|
||||
@ -352,6 +365,16 @@ module Devops
|
||||
Devops::Db.connector.server_update(server)
|
||||
end
|
||||
|
||||
def check_servers_list_auth(servers, current_user)
|
||||
project_with_env_pairs = servers.map do |server|
|
||||
[server.project, server.deploy_env]
|
||||
end
|
||||
project_with_env_pairs.uniq.each do |pair|
|
||||
project, env = *pair
|
||||
Devops::Db.connector.check_project_auth project, env, current_user
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -109,6 +109,11 @@ module Devops
|
||||
rl
|
||||
end
|
||||
|
||||
def delete_list
|
||||
@body ||= create_object_from_json_body
|
||||
check_array(@body["servers_ids"], "Parameter 'servers_ids' should be a not empty array of string", String, false)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_list_of_ips_with_names(text)
|
||||
|
||||
@ -14,7 +14,7 @@ module Devops
|
||||
# - Content-Type: application/json
|
||||
# - body :
|
||||
# {
|
||||
# "names": [], -> array of servers names to run chef-client
|
||||
# "names": [], -> array of servers chef node names to run chef-client
|
||||
# "tags": [], -> array of tags to apply on each server before running chef-client
|
||||
# "build_number": "", -> string, build number to deploy
|
||||
# "run_list": [], -> array of strings to set run_list for chef-client
|
||||
|
||||
@ -127,8 +127,7 @@ module Devops
|
||||
# 200 - Deleted
|
||||
hash["DELETE"] = lambda {|id|
|
||||
check_privileges("server", "w")
|
||||
info, r = Devops::API2_0::Handler::Server.new(request).delete(id)
|
||||
create_response(info, r)
|
||||
json Devops::API2_0::Handler::Server.new(request).delete(id)
|
||||
}
|
||||
app.multi_routes "/server/:id", {:headers => [:accept, :content_type]}, hash
|
||||
|
||||
@ -470,6 +469,29 @@ module Devops
|
||||
create_response("Run list has been changed")
|
||||
end
|
||||
|
||||
|
||||
# Delete list of servers
|
||||
#
|
||||
# * *Request*
|
||||
# - method : POST
|
||||
# - headers :
|
||||
# - Accept: application/json
|
||||
# - Content-Type: application/json
|
||||
# - body :
|
||||
# {
|
||||
# "servers_ids": [ "server1", "server2"]
|
||||
# }
|
||||
#
|
||||
# * *Returns* :
|
||||
# {
|
||||
# "server1": "report_1",
|
||||
# "server2": "report_2"
|
||||
# }
|
||||
app.post_with_headers "/server/delete_list", :headers => [:accept, :content_type] do
|
||||
check_privileges("server", "w")
|
||||
json Devops::API2_0::Handler::Server.new(request).delete_list
|
||||
end
|
||||
|
||||
puts "Server routes initialized"
|
||||
end
|
||||
|
||||
|
||||
@ -38,6 +38,13 @@ class KnifeCommands
|
||||
knife("tag delete #{name} #{tagsStr}")
|
||||
end
|
||||
|
||||
# extracted from server_executor.rb
|
||||
def swap_tags(name, from, to)
|
||||
tags_delete(name, from)
|
||||
result = tags_create(name, to)
|
||||
!!result[1]
|
||||
end
|
||||
|
||||
def create_role role_name, project, env
|
||||
file = "/tmp/new_role.json"
|
||||
File.open(file, "w") do |f|
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
require "wisper"
|
||||
require "lib/hash_ext"
|
||||
require "lib/nil_class_ext"
|
||||
require "lib/string_ext"
|
||||
require "lib/core_ext/hash"
|
||||
require "lib/core_ext/nil_class"
|
||||
require "lib/core_ext/string"
|
||||
|
||||
require_relative "devops-loader"
|
||||
require_relative "devops-application"
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
require 'lib/string_helper'
|
||||
|
||||
module Connectors
|
||||
module Helpers
|
||||
module DeleteCommand
|
||||
@ -9,7 +7,7 @@ module Connectors
|
||||
# We need this alias to forward methods from MongoConnector to resources connectors.
|
||||
|
||||
def self.included(base)
|
||||
resource_name = StringHelper.underscore_class(base)
|
||||
resource_name = base.to_s.underscore_class
|
||||
method_name = "#{resource_name}_delete".to_sym
|
||||
alias_method method_name, :delete
|
||||
end
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
require 'lib/string_helper'
|
||||
|
||||
module Connectors
|
||||
module Helpers
|
||||
module InsertCommand
|
||||
@ -9,7 +7,7 @@ module Connectors
|
||||
# We need this alias to forward methods from MongoConnector to resources connectors.
|
||||
|
||||
def self.included(base)
|
||||
resource_name = StringHelper.underscore_class(base)
|
||||
resource_name = base.to_s.underscore_class
|
||||
method_name = "#{resource_name}_insert".to_sym
|
||||
alias_method method_name, :insert
|
||||
end
|
||||
@ -24,7 +22,7 @@ module Connectors
|
||||
rescue Mongo::OperationFailure => e
|
||||
# exception's message doesn't always start from error code
|
||||
if e.message =~ /11000/
|
||||
resource_name = StringHelper.underscore_class(record.class)
|
||||
resource_name = record.class.to_s.underscore_class
|
||||
raise InvalidRecord.new("Duplicate key error: #{resource_name} with id '#{record.id}'")
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
require 'lib/string_helper'
|
||||
|
||||
module Connectors
|
||||
module Helpers
|
||||
module ListCommand
|
||||
@ -9,9 +7,7 @@ module Connectors
|
||||
# We need this alias to forward methods from MongoConnector to resources connectors.
|
||||
|
||||
def self.included(base)
|
||||
resource_name = StringHelper.underscore_class(base).to_sym
|
||||
method_name = StringHelper.pluralize(resource_name)
|
||||
alias_method method_name, :list
|
||||
alias_method base.to_s.underscore_class.pluralize, :list
|
||||
end
|
||||
|
||||
# query options is needed, for example, for fields limiting
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
require 'lib/string_helper'
|
||||
|
||||
module Connectors
|
||||
module Helpers
|
||||
module ShowCommand
|
||||
@ -9,7 +7,7 @@ module Connectors
|
||||
# We need this alias to forward methods from MongoConnector to resources connectors.
|
||||
|
||||
def self.included(base)
|
||||
method_name = StringHelper.underscore_class(base).to_sym
|
||||
method_name = base.to_s.underscore_class.to_sym
|
||||
alias_method method_name, :show
|
||||
end
|
||||
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
require 'lib/string_helper'
|
||||
|
||||
module Connectors
|
||||
module Helpers
|
||||
module UpdateCommand
|
||||
@ -8,8 +6,7 @@ module Connectors
|
||||
# We need second method name to forward methods from MongoConnector to resources connectors.
|
||||
|
||||
def self.included(base)
|
||||
resource_name = StringHelper.underscore_class(base)
|
||||
method_name = "#{resource_name}_update".to_sym
|
||||
method_name = "#{base.to_s.underscore_class}_update".to_sym
|
||||
alias_method method_name, :update
|
||||
end
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
module Connectors
|
||||
class ProviderAccounts < Base
|
||||
class ProviderAccount < Base
|
||||
|
||||
include Helpers::InsertCommand,
|
||||
Helpers::DeleteCommand
|
||||
@ -16,7 +16,7 @@ module Connectors
|
||||
def provider_account provider, account
|
||||
c = Provider::ProviderFactory.get_account_class(provider)
|
||||
bson = collection.find({provider: provider, _id: account}).to_a.first
|
||||
raise RecordNotFound.new("'Account #{account}' for provider '#{provider}' not found") unless bson
|
||||
raise RecordNotFound.new("Account '#{account}' for provider '#{provider}' not found") unless bson
|
||||
c.build_from_bson(bson)
|
||||
end
|
||||
|
||||
@ -53,7 +53,7 @@ module Devops
|
||||
def subnets_filter
|
||||
networks = provider_instance.networks
|
||||
|
||||
unless self.subnets.empty?
|
||||
if subnets && !subnets.empty?
|
||||
network = networks.detect {|n| n["name"] == self.subnets[0]}
|
||||
if network
|
||||
{"vpc-id" => network["vpcId"] }
|
||||
|
||||
@ -73,10 +73,6 @@ module Devops
|
||||
}
|
||||
end
|
||||
|
||||
def self.create_from_json! json
|
||||
Image.new( JSON.parse(json) )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -30,10 +30,6 @@ module Devops
|
||||
key
|
||||
end
|
||||
|
||||
def self.create_from_json json
|
||||
Key.new( JSON.parse(json) )
|
||||
end
|
||||
|
||||
def filename
|
||||
File.basename(self.path)
|
||||
end
|
||||
|
||||
@ -22,6 +22,7 @@ module Devops
|
||||
#define_hook :after_add_deploy_env
|
||||
|
||||
attr_accessor :id, :deploy_envs, :type, :archived, :description, :run_list
|
||||
attr_accessor :components
|
||||
|
||||
MULTI_TYPE = "multi"
|
||||
|
||||
|
||||
@ -61,6 +61,12 @@ module Devops
|
||||
}
|
||||
end
|
||||
|
||||
# absent of "id" attribute can cause some inconviniences.
|
||||
# for example, we have "record.id" call in InsertCommand
|
||||
def id
|
||||
account_name
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -11,6 +11,7 @@ module Devops
|
||||
STACK_TYPE = 5
|
||||
DEPLOY_STACK_TYPE = 6
|
||||
DELETE_SERVER_TYPE = 7
|
||||
EXPIRE_SERVER_TYPE = 8
|
||||
|
||||
attr_accessor :id, :file, :updated_at, :created_by, :project, :deploy_env, :type, :chef_node_name, :host, :status, :stack, :subreports, :job_result_code
|
||||
|
||||
|
||||
@ -16,10 +16,6 @@ module Devops
|
||||
super.merge(template_url: template_url)
|
||||
end
|
||||
|
||||
def delete_template_file_from_storage
|
||||
raise 'Implement me'
|
||||
end
|
||||
|
||||
def update_template_url
|
||||
self.template_url = generate_template_file_and_upload_to_storage(id, template_body)
|
||||
end
|
||||
|
||||
@ -75,17 +75,12 @@ module Devops
|
||||
user
|
||||
end
|
||||
|
||||
def self.create_from_json json
|
||||
User.new( JSON.parse(json) )
|
||||
end
|
||||
|
||||
def to_hash_without_id
|
||||
o = {
|
||||
{
|
||||
"email" => self.email,
|
||||
"password" => self.password,
|
||||
"privileges" => self.privileges
|
||||
}
|
||||
o
|
||||
end
|
||||
|
||||
def check_privileges cmd, required_privelege
|
||||
|
||||
@ -33,7 +33,7 @@ class MongoConnector
|
||||
[:keys, :key, :key_insert, :key_delete] => :keys_connector,
|
||||
[:save_report, :report, :reports, :set_report_status, :set_report_server_data, :add_report_subreports] => :reports_connector,
|
||||
[:insert_statistic, :search_statistic] => :statistics_connector,
|
||||
[:provider_accounts, :provider_accounts_insert, :provider_accounts_delete, :provider_account] => :provider_accounts_connector
|
||||
[:provider_accounts, :provider_account_insert, :provider_account_delete, :provider_account] => :provider_accounts_connector
|
||||
)
|
||||
|
||||
def initialize(db, host, port=27017, user=nil, password=nil)
|
||||
@ -48,7 +48,7 @@ class MongoConnector
|
||||
private
|
||||
|
||||
def provider_accounts_connector
|
||||
@provider_accounts_connector ||= Connectors::ProviderAccounts.new(@db)
|
||||
@provider_accounts_connector ||= Connectors::ProviderAccount.new(@db)
|
||||
end
|
||||
|
||||
def images_connector
|
||||
|
||||
@ -13,6 +13,7 @@ module Validators
|
||||
raise InvalidRecord.new("An error raised during validation with #{self.class}: #{e.class}: #{e.message}")
|
||||
end
|
||||
|
||||
# :nocov:
|
||||
def valid?
|
||||
raise 'override me'
|
||||
end
|
||||
@ -20,6 +21,7 @@ module Validators
|
||||
def message
|
||||
raise 'override me'
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
class << self
|
||||
private
|
||||
|
||||
@ -3,7 +3,7 @@ module Validators
|
||||
|
||||
def valid?
|
||||
return true unless @model.flavor
|
||||
available_flavors.detect do |flavor|
|
||||
@model.provider_instance.flavors.detect do |flavor|
|
||||
flavor['id'] == @model.flavor
|
||||
end
|
||||
end
|
||||
@ -11,11 +11,5 @@ module Validators
|
||||
def message
|
||||
"Invalid flavor '#{@model.flavor}'."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_flavors
|
||||
@model.provider_instance.flavors
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -3,6 +3,9 @@ module Validators
|
||||
|
||||
def valid?
|
||||
return true if @model.groups.nil?
|
||||
subnets_filter = @model.subnets_filter
|
||||
available_groups = @model.provider_instance.groups(subnets_filter).keys
|
||||
|
||||
@invalid_groups = @model.groups - available_groups
|
||||
@invalid_groups.empty?
|
||||
end
|
||||
@ -10,12 +13,5 @@ module Validators
|
||||
def message
|
||||
"Invalid groups '#{@invalid_groups.join("', '")}'."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_groups
|
||||
subnets_filter = @model.subnets_filter
|
||||
@model.provider_instance.groups(subnets_filter).keys
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -6,7 +6,7 @@ module Validators
|
||||
|
||||
def valid?
|
||||
return true unless @model.image
|
||||
available_images.detect do |image|
|
||||
get_available_provider_images(::Devops::Db.connector, @model.provider).detect do |image|
|
||||
image["id"] == @model.image
|
||||
end
|
||||
end
|
||||
@ -14,11 +14,5 @@ module Validators
|
||||
def message
|
||||
"Invalid image '#{@model.image}'."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_images
|
||||
get_available_provider_images(::Devops::Db.connector, @model.provider)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -4,21 +4,13 @@ module Validators
|
||||
def valid?
|
||||
return true unless @model.stack_template
|
||||
|
||||
available_stack_templates.detect do |template|
|
||||
template['id'] == @model.stack_template
|
||||
Devops::Db.connector.stack_templates.detect do |template|
|
||||
template.id == @model.stack_template
|
||||
end
|
||||
end
|
||||
|
||||
def message
|
||||
"Invalid stack template '#{@model.stack_template}'."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_stack_templates
|
||||
# map to hash to simplify mocks. Later replace this method with something more suitable
|
||||
Devops::Db.connector.stack_templates.map(&:to_hash)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@ -4,7 +4,7 @@ module Validators
|
||||
class Flavor < Base
|
||||
|
||||
def valid?
|
||||
available_flavors.detect do |flavor|
|
||||
@model.provider_instance.flavors.detect do |flavor|
|
||||
flavor['id'] == @value
|
||||
end
|
||||
end
|
||||
@ -12,12 +12,6 @@ module Validators
|
||||
def message
|
||||
"Invalid flavor '#{@value}'."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_flavors
|
||||
@model.provider_instance.flavors
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -7,7 +7,7 @@ module Validators
|
||||
include ::ImageCommands
|
||||
|
||||
def valid?
|
||||
available_images.detect do |image|
|
||||
get_available_provider_images(::Devops::Db.connector, @model.provider).detect do |image|
|
||||
image["id"] == @value
|
||||
end
|
||||
end
|
||||
@ -15,12 +15,6 @@ module Validators
|
||||
def message
|
||||
"Invalid image '#{@value}'."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_images
|
||||
get_available_provider_images(::Devops::Db.connector, @model.provider)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -2,6 +2,7 @@ module Validators
|
||||
class Helpers::Users < Base
|
||||
|
||||
def valid?
|
||||
available_users = ::Devops::Db.connector.users_names(@model)
|
||||
@nonexistent_users = (@model || []) - available_users
|
||||
@nonexistent_users.empty?
|
||||
end
|
||||
@ -9,11 +10,5 @@ module Validators
|
||||
def message
|
||||
Devops::Messages.t("project.deploy_env.validation.users.not_exist", users: @nonexistent_users.join("', '"))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_users
|
||||
::Devops::Db.connector.users_names(@model)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -8,22 +8,12 @@ module Validators
|
||||
include BootstrapTemplatesCommands
|
||||
|
||||
def valid?
|
||||
if @model.bootstrap_template
|
||||
available_templates.include?(@model.bootstrap_template)
|
||||
else
|
||||
true
|
||||
end
|
||||
get_templates.include?(@model.bootstrap_template)
|
||||
end
|
||||
|
||||
def message
|
||||
"Invalid bootstrap template '#{@model.bootstrap_template}' for image '#{@model.id}'"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_templates
|
||||
get_templates
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
27
devops-service/lib/core_ext/string.rb
Normal file
27
devops-service/lib/core_ext/string.rb
Normal file
@ -0,0 +1,27 @@
|
||||
class String
|
||||
def present?
|
||||
!empty?
|
||||
end
|
||||
|
||||
def blank?
|
||||
empty?
|
||||
end
|
||||
|
||||
# from ActiveSupport
|
||||
def underscore
|
||||
gsub(/::/, '/').
|
||||
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
||||
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
||||
tr("-", "_").
|
||||
downcase
|
||||
end
|
||||
|
||||
def underscore_class
|
||||
split('::').last.underscore
|
||||
end
|
||||
|
||||
# rough simplification
|
||||
def pluralize
|
||||
"#{self}s"
|
||||
end
|
||||
end
|
||||
35
devops-service/lib/executors/expiration_scheduler.rb
Normal file
35
devops-service/lib/executors/expiration_scheduler.rb
Normal file
@ -0,0 +1,35 @@
|
||||
require "workers/delete_expired_server_worker"
|
||||
|
||||
module Devops
|
||||
module Executor
|
||||
class ExpirationScheduler
|
||||
def initialize(expires, server)
|
||||
@expires, @server = expires, server
|
||||
end
|
||||
|
||||
def schedule_expiration!
|
||||
return unless @expires
|
||||
DeleteExpiredServerWorker.perform_in(interval_in_seconds, server_chef_node_name: @server.chef_node_name)
|
||||
end
|
||||
|
||||
def interval_in_seconds
|
||||
interval = @expires.to_i
|
||||
measure_unit = @expires.chars.last
|
||||
case measure_unit
|
||||
when 's'
|
||||
interval
|
||||
when 'm'
|
||||
interval * 60
|
||||
when 'h'
|
||||
interval * 60 * 60
|
||||
when 'd'
|
||||
interval * 60 * 60 * 24
|
||||
when 'w'
|
||||
interval * 60 * 60 * 24 * 7
|
||||
else
|
||||
raise 'Wrong interval format'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,6 +1,5 @@
|
||||
require "lib/knife/knife_factory"
|
||||
require "workers/worker"
|
||||
require "workers/delete_server_worker"
|
||||
require "lib/executors/expiration_scheduler"
|
||||
require "hooks"
|
||||
require 'net/ssh'
|
||||
|
||||
@ -9,12 +8,16 @@ module Devops
|
||||
class ServerExecutor
|
||||
include Hooks
|
||||
|
||||
RESULT_CODES = {
|
||||
ERROR_CODES = {
|
||||
server_bootstrap_fail: 2,
|
||||
server_cannot_update_tags: 3,
|
||||
server_bootstrap_private_ip_unset: 4,
|
||||
server_not_in_chef_nodes: 5,
|
||||
server_bootstrap_unknown_error: 7,
|
||||
deploy_unknown_error: 6,
|
||||
deploy_failed: 8
|
||||
deploy_failed: 8,
|
||||
creating_server_unknown_error: 9,
|
||||
creating_server_in_cloud_failed: 10
|
||||
}
|
||||
|
||||
# waiting for 5*60 seconds (5 min)
|
||||
@ -34,46 +37,32 @@ module Devops
|
||||
define_hook :before_bootstrap
|
||||
define_hook :after_bootstrap
|
||||
|
||||
before_deploy :create_run_list
|
||||
before_deploy :add_run_list_to_deploy_info
|
||||
|
||||
|
||||
attr_accessor :server, :deploy_env, :report, :project
|
||||
|
||||
def initialize server, out, options={}
|
||||
if server
|
||||
@project = Devops::Db.connector.project(server.project)
|
||||
@deploy_env = @project.deploy_env(server.deploy_env)
|
||||
end
|
||||
@knife_instance = KnifeFactory.instance
|
||||
@server = server
|
||||
@out = out
|
||||
@out.class.send(:define_method, :flush) { } unless @out.respond_to?(:flush)
|
||||
@current_user = options[:current_user]
|
||||
end
|
||||
|
||||
def self.result_code(symbolic_code)
|
||||
RESULT_CODES.fetch(symbolic_code)
|
||||
def self.error_code(symbolic_code)
|
||||
ERROR_CODES.fetch(symbolic_code)
|
||||
end
|
||||
|
||||
def self.symbolic_result_code(integer_code)
|
||||
RESULT_CODES.key(integer_code) || :unknown_error
|
||||
def self.symbolic_error_code(integer_code)
|
||||
ERROR_CODES.key(integer_code) || :unknown_error
|
||||
end
|
||||
|
||||
def result_code(symbolic_code)
|
||||
self.class.result_code(symbolic_code)
|
||||
end
|
||||
|
||||
def report= r
|
||||
@report = r
|
||||
end
|
||||
|
||||
def project= p
|
||||
@project = p
|
||||
end
|
||||
|
||||
def deploy_env= e
|
||||
@deploy_env = e
|
||||
end
|
||||
|
||||
def server
|
||||
@server
|
||||
def error_code(symbolic_code)
|
||||
self.class.error_code(symbolic_code)
|
||||
end
|
||||
|
||||
def create_server_object options
|
||||
@ -109,7 +98,9 @@ module Devops
|
||||
res[:before] = self.run_hook :before_create
|
||||
@out << "Done\n"
|
||||
|
||||
return false unless provider.create_server(@server, @deploy_env.image, @deploy_env.flavor, @deploy_env.subnets, @deploy_env.groups, @out)
|
||||
unless provider.create_server(@server, @deploy_env.image, @deploy_env.flavor, @deploy_env.subnets, @deploy_env.groups, @out)
|
||||
return error_code(:creating_server_in_cloud_failed)
|
||||
end
|
||||
mongo.server_insert @server
|
||||
|
||||
@out << "\nAfter create hooks...\n"
|
||||
@ -118,7 +109,7 @@ module Devops
|
||||
@out.flush
|
||||
DevopsLogger.logger.info "Server with parameters: #{@server.to_hash.inspect} is running"
|
||||
|
||||
schedule_expiration(@server)
|
||||
schedule_expiration()
|
||||
|
||||
unless options["without_bootstrap"]
|
||||
bootstrap_options = {
|
||||
@ -135,11 +126,15 @@ module Devops
|
||||
DevopsLogger.logger.error e.message
|
||||
roll_back
|
||||
mongo.server_delete @server.id
|
||||
# return 5
|
||||
return result_code(:server_not_in_chef_nodes)
|
||||
error_code(:creating_server_unknown_error)
|
||||
end
|
||||
end
|
||||
|
||||
# options:
|
||||
# :run_list (optional)
|
||||
# :bootstrap_template (optional)
|
||||
# :chef_environment (optional)
|
||||
# :config (optional)
|
||||
def bootstrap options
|
||||
@out << "\n\nBootstrap...\n"
|
||||
@out.flush
|
||||
@ -151,7 +146,7 @@ module Devops
|
||||
@out << "Done\n"
|
||||
if @server.private_ip.nil?
|
||||
@out << "Error: Private IP is null"
|
||||
return false
|
||||
return error_code(:server_bootstrap_private_ip_unset)
|
||||
end
|
||||
ja = {
|
||||
:provider => @server.provider,
|
||||
@ -166,13 +161,7 @@ module Devops
|
||||
|
||||
address = "#{@server.remote_user}@#{ip}"
|
||||
|
||||
cmd = 'ssh '
|
||||
cmd << "-i #{cert_path} "
|
||||
cmd << '-q '
|
||||
cmd << '-o StrictHostKeyChecking=no '
|
||||
cmd << '-o ConnectTimeout=2 -o ConnectionAttempts=1 '
|
||||
cmd << "#{address} 'exit'"
|
||||
cmd << " 2>&1"
|
||||
cmd = check_ssh_command(cert_path, address)
|
||||
|
||||
@out << "\nWaiting for SSH..."
|
||||
@out << "\nTest command: '#{cmd}'\n"
|
||||
@ -181,16 +170,16 @@ module Devops
|
||||
retries_amount = 0
|
||||
begin
|
||||
sleep(5)
|
||||
res = `#{cmd}`
|
||||
res = execute_system_command(cmd)
|
||||
retries_amount += 1
|
||||
if retries_amount > MAX_SSH_RETRIES_AMOUNT
|
||||
if retries_amount >= MAX_SSH_RETRIES_AMOUNT
|
||||
@out.puts "Can not connect to #{address}"
|
||||
@out.puts res
|
||||
@out.flush
|
||||
DevopsLogger.logger.error "Can not connect with command '#{cmd}':\n#{res}"
|
||||
return result_code(:server_bootstrap_fail)
|
||||
return error_code(:server_bootstrap_fail)
|
||||
end
|
||||
raise ArgumentError.new("Can not connect with command '#{cmd}' ") unless $?.success?
|
||||
raise ArgumentError.new("Can not connect with command '#{cmd}' ") unless last_command_successful?
|
||||
rescue ArgumentError => e
|
||||
@out.puts "SSH command failed, retry (#{retries_amount}/#{MAX_SSH_RETRIES_AMOUNT})"
|
||||
@out.flush
|
||||
@ -200,7 +189,7 @@ module Devops
|
||||
provider = @server.provider_instance
|
||||
@server.chef_node_name = provider.create_default_chef_node_name(@server) if @server.chef_node_name.nil?
|
||||
|
||||
r = @knife_instance.knife_bootstrap(@out, ip, self.bootstrap_options(ja, options))
|
||||
r = knife_instance.knife_bootstrap(@out, ip, self.bootstrap_options(ja, options))
|
||||
|
||||
if r == 0
|
||||
@out << "Chef node name: #{@server.chef_node_name}\n"
|
||||
@ -217,10 +206,16 @@ module Devops
|
||||
else
|
||||
@out << "Can not bootstrap node '#{@server.id}', error code: #{r}"
|
||||
@out.flush
|
||||
result_code(:server_bootstrap_fail)
|
||||
error_code(:server_bootstrap_fail)
|
||||
end
|
||||
end
|
||||
|
||||
# options:
|
||||
# :cert_path (required)
|
||||
# :run_list (optional)
|
||||
# :bootstrap_template (optional)
|
||||
# :chef_environment (optional)
|
||||
# :config (optional)
|
||||
def bootstrap_options attributes, options
|
||||
bootstrap_options = [
|
||||
"-x #{@server.remote_user}",
|
||||
@ -242,6 +237,7 @@ module Devops
|
||||
@out << "Done\n"
|
||||
end
|
||||
|
||||
# essentially, it just bootstrap and then deploy
|
||||
def two_phase_bootstrap options
|
||||
prepare_two_phase_bootstrap(options)
|
||||
# bootstrap phase
|
||||
@ -252,14 +248,14 @@ module Devops
|
||||
bootstrap_status = bootstrap(options)
|
||||
|
||||
if bootstrap_status == 0
|
||||
if check_server
|
||||
if check_server_on_chef_server
|
||||
@out << "Server #{@server.chef_node_name} is created"
|
||||
else
|
||||
@out.puts "Can not find client or node on chef-server"
|
||||
roll_back
|
||||
@out.flush
|
||||
mongo.server_delete @server.id
|
||||
return result_code(:server_not_in_chef_nodes)
|
||||
return error_code(:server_not_in_chef_nodes)
|
||||
end
|
||||
else
|
||||
# @out << roll_back
|
||||
@ -269,22 +265,20 @@ module Devops
|
||||
DevopsLogger.logger.error msg
|
||||
@out.puts msg
|
||||
@out.flush
|
||||
return result_code(:server_bootstrap_fail)
|
||||
return error_code(:server_bootstrap_fail)
|
||||
end
|
||||
rescue => e
|
||||
@out << "\nError: #{e.message}\n"
|
||||
@out.flush
|
||||
return result_code(:server_bootstrap_unknown_error)
|
||||
return error_code(:server_bootstrap_unknown_error)
|
||||
end
|
||||
|
||||
# deploy phase. Assume that all servers are bootstraped successfully here.
|
||||
begin
|
||||
#raise "hello"
|
||||
@out << "\n"
|
||||
run_list = compute_run_list
|
||||
@out << "\nComputed run list: #{run_list.join(", ")}"
|
||||
@out << "\n\nComputed run list: #{run_list.join(", ")}"
|
||||
@out.flush
|
||||
@knife_instance.set_run_list(@server.chef_node_name, run_list)
|
||||
knife_instance.set_run_list(@server.chef_node_name, run_list)
|
||||
deploy_info = options[:deploy_info] || @project.deploy_info(@deploy_env)
|
||||
deploy_status = deploy_server(deploy_info)
|
||||
if deploy_status == 0
|
||||
@ -294,19 +288,20 @@ module Devops
|
||||
msg << "\nDeploing server operation status was #{deploy_status}"
|
||||
DevopsLogger.logger.error msg
|
||||
@out << "\n" + msg + "\n"
|
||||
result_code(:deploy_failed)
|
||||
error_code(:deploy_failed)
|
||||
end
|
||||
rescue => e
|
||||
@out << "\nError: #{e.message}\n"
|
||||
DevopsLogger.logger.error(e.message + "\n" + e.backtrace.join("\n"))
|
||||
result_code(:deploy_unknown_error)
|
||||
error_code(:deploy_unknown_error)
|
||||
end
|
||||
end
|
||||
|
||||
def check_server
|
||||
@knife_instance.chef_node_list.include?(@server.chef_node_name) and @knife_instance.chef_client_list.include?(@server.chef_node_name)
|
||||
def check_server_on_chef_server
|
||||
knife_instance.chef_node_list.include?(@server.chef_node_name) and knife_instance.chef_client_list.include?(@server.chef_node_name)
|
||||
end
|
||||
|
||||
# returns a hash with :chef_node, :chef_client and :server keys
|
||||
def unbootstrap
|
||||
k = Devops::Db.connector.key(@server.key)
|
||||
cert_path = k.path
|
||||
@ -346,35 +341,28 @@ module Devops
|
||||
end
|
||||
|
||||
def deploy_server_with_tags tags, deploy_info
|
||||
old_tags_str = nil
|
||||
new_tags_str = nil
|
||||
unless tags.empty?
|
||||
old_tags_str = @knife_instance.tags_list(@server.chef_node_name).join(" ")
|
||||
@out << "Server tags: #{old_tags_str}\n"
|
||||
@knife_instance.tags_delete(@server.chef_node_name, old_tags_str)
|
||||
return deploy_server(deploy_info) if tags.empty?
|
||||
|
||||
new_tags_str = tags.join(" ")
|
||||
@out << "Server new tags: #{new_tags_str}\n"
|
||||
cmd = @knife_instance.tags_create(@server.chef_node_name, new_tags_str)
|
||||
unless cmd[1]
|
||||
m = "Error: Cannot add tags '#{new_tags_str}' to server '#{@server.chef_node_name}'"
|
||||
DevopsLogger.logger.error(m)
|
||||
@out << m + "\n"
|
||||
return 3
|
||||
end
|
||||
DevopsLogger.logger.info("Set tags for '#{@server.chef_node_name}': #{new_tags_str}")
|
||||
old_tags_str = knife_instance.tags_list(@server.chef_node_name).join(" ")
|
||||
new_tags_str = tags.join(" ")
|
||||
|
||||
@out.puts "Temporarily changing tags (#{old_tags_str}) to (#{new_tags_str})"
|
||||
unless knife_instance.swap_tags(@server.chef_node_name, old_tags_str, new_tags_str)
|
||||
m = "Error: Cannot add tags '#{new_tags_str}' to server '#{@server.chef_node_name}'"
|
||||
DevopsLogger.logger.error(m)
|
||||
@out.puts m
|
||||
return error_code(:server_cannot_update_tags)
|
||||
end
|
||||
DevopsLogger.logger.info("Set tags for '#{@server.chef_node_name}': #{new_tags_str}")
|
||||
|
||||
r = deploy_server deploy_info
|
||||
|
||||
unless tags.empty?
|
||||
@out << "Restore tags\n"
|
||||
cmd = @knife_instance.tags_delete(@server.chef_node_name, new_tags_str)
|
||||
DevopsLogger.logger.info("Deleted tags for #{@server.chef_node_name}: #{new_tags_str}")
|
||||
cmd = @knife_instance.tags_create(@server.chef_node_name, old_tags_str)
|
||||
DevopsLogger.logger.info("Set tags for #{@server.chef_node_name}: #{old_tags_str}")
|
||||
begin
|
||||
deploy_result = deploy_server deploy_info
|
||||
ensure
|
||||
@out.puts "Restoring tags"
|
||||
knife_instance.swap_tags(@server.chef_node_name, new_tags_str, old_tags_str)
|
||||
DevopsLogger.logger.info("Restoring tags for #{@server.chef_node_name}: from #{new_tags_str} back to (#{old_tags_str})")
|
||||
end
|
||||
return r
|
||||
deploy_result
|
||||
end
|
||||
|
||||
def deploy_server deploy_info
|
||||
@ -397,7 +385,7 @@ module Devops
|
||||
f.write json
|
||||
end
|
||||
end
|
||||
@out << "Deploy Input Parameters:\n"
|
||||
@out.puts "Deploy Input Parameters:"
|
||||
@out.puts json
|
||||
@out.flush
|
||||
cmd << " -j http://#{DevopsConfig.config[:address]}:#{DevopsConfig.config[:port]}/#{DevopsConfig.config[:url_prefix]}/v2.0/deploy/data/#{file}"
|
||||
@ -412,7 +400,7 @@ module Devops
|
||||
end
|
||||
@out.flush
|
||||
k = Devops::Db.connector.key(@server.key)
|
||||
lline = @knife_instance.ssh_stream(@out, cmd, ip, @server.remote_user, k.path)
|
||||
lline = knife_instance.ssh_stream(@out, cmd, ip, @server.remote_user, k.path)
|
||||
r = /Chef\sClient\sfinished/i
|
||||
|
||||
if lline && lline[r]
|
||||
@ -432,21 +420,11 @@ module Devops
|
||||
|
||||
def delete_from_chef_server node_name
|
||||
{
|
||||
:chef_node => @knife_instance.chef_node_delete(node_name),
|
||||
:chef_client => @knife_instance.chef_client_delete(node_name)
|
||||
:chef_node => knife_instance.chef_node_delete(node_name),
|
||||
:chef_client => knife_instance.chef_client_delete(node_name)
|
||||
}
|
||||
end
|
||||
|
||||
=begin
|
||||
def delete_etc_chef s, cert_path
|
||||
cmd = "ssh -i #{cert_path} -t -q #{s.remote_user}@#{s.private_ip}"
|
||||
cmd += " sudo " unless s.remote_user == "root"
|
||||
cmd += "rm -Rf /etc/chef"
|
||||
r = `#{cmd}`
|
||||
raise(r) unless $?.success?
|
||||
end
|
||||
=end
|
||||
|
||||
def delete_server
|
||||
mongo = ::Devops::Db.connector
|
||||
if @server.static?
|
||||
@ -454,23 +432,25 @@ module Devops
|
||||
unbootstrap
|
||||
end
|
||||
mongo.server_delete @server.id
|
||||
msg = "Static server '#{@server.id}' is removed"
|
||||
DevopsLogger.logger.info msg
|
||||
return msg, nil
|
||||
puts_and_flush "Static server '#{@server.id}' is removed"
|
||||
return 0
|
||||
end
|
||||
r = delete_from_chef_server(@server.chef_node_name)
|
||||
|
||||
puts_and_flush "Deleting from chef server:"
|
||||
delete_from_chef_server(@server.chef_node_name).each do |key, result|
|
||||
@out.puts "#{key} - #{result}"
|
||||
end
|
||||
|
||||
puts_and_flush "Deleting from cloud:"
|
||||
provider = @server.provider_instance
|
||||
begin
|
||||
r[:server] = provider.delete_server @server
|
||||
puts_and_flush provider.delete_server @server
|
||||
rescue Fog::Compute::OpenStack::NotFound, Fog::Compute::AWS::NotFound
|
||||
r[:server] = "Server with id '#{@server.id}' not found in '#{provider.name}' servers"
|
||||
DevopsLogger.logger.warn r[:server]
|
||||
puts_and_flush "Server with id '#{@server.id}' not found among '#{provider.name}' servers"
|
||||
end
|
||||
mongo.server_delete @server.id
|
||||
info = "Server '#{@server.id}' with name '#{@server.chef_node_name}' for project '#{@server.project}-#{@server.deploy_env}' is removed"
|
||||
DevopsLogger.logger.info info
|
||||
r.each{|key, log| DevopsLogger.logger.info("#{key} - #{log}")}
|
||||
return info, r
|
||||
puts_and_flush "Server '#{@server.id}' with name '#{@server.chef_node_name}' for project '#{@server.project}-#{@server.deploy_env}' is removed."
|
||||
0
|
||||
end
|
||||
|
||||
def roll_back
|
||||
@ -487,7 +467,7 @@ module Devops
|
||||
end
|
||||
end
|
||||
|
||||
def create_run_list out, deploy_info
|
||||
def add_run_list_to_deploy_info out, deploy_info
|
||||
out << "\nGenerate run list hook...\n"
|
||||
if deploy_info["run_list"]
|
||||
out << "Deploy info already contains 'run_list': #{deploy_info["run_list"].join(", ")}\n"
|
||||
@ -496,14 +476,6 @@ module Devops
|
||||
out << "Project run list: #{@project.run_list.join(", ")}\n"
|
||||
out << "Deploy environment run list: #{@deploy_env.run_list.join(", ")}\n"
|
||||
out << "Server run list: #{@server.run_list.join(", ")}\n"
|
||||
=begin
|
||||
rlist = Set.new.merge(@deploy_env.provider_instance.run_list).merge(@project.run_list).merge(@deploy_env.run_list).merge(@server.run_list)
|
||||
if @server.stack
|
||||
stack = Devops::Db.connector.stack(@server.stack)
|
||||
out << "Stack run list: #{stack.run_list.join(", ")}\n"
|
||||
rlist.merge(stack.run_list)
|
||||
end
|
||||
=end
|
||||
deploy_info["run_list"] = compute_run_list
|
||||
out << "New deploy run list: #{deploy_info["run_list"].join(", ")}\nRun list has been generated\n\n"
|
||||
end
|
||||
@ -511,47 +483,56 @@ module Devops
|
||||
def compute_run_list
|
||||
rlist = []
|
||||
[@deploy_env.provider_instance.run_list, @project.run_list, @deploy_env.run_list, @server.run_list].each do |sub_run_list|
|
||||
rlist += sub_run_list if sub_run_list.is_a?(Array)
|
||||
rlist += sub_run_list if sub_run_list
|
||||
end
|
||||
rlist = Set.new(rlist)
|
||||
if @server.stack
|
||||
stack = Devops::Db.connector.stack(@server.stack)
|
||||
# out << "Stack run list: #{stack.run_list.join(", ")}\n"
|
||||
srl = stack.run_list
|
||||
rlist.merge(srl) if srl.is_a?(Array)
|
||||
rlist += srl if srl
|
||||
end
|
||||
rlist.to_a
|
||||
rlist.uniq
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def schedule_expiration(server)
|
||||
expires = @deploy_env.expires
|
||||
return unless expires
|
||||
interval = interval_in_seconds(expires)
|
||||
@out << "Planning expiration in #{expires}"
|
||||
DeleteServerWorker.perform_in(interval, server_chef_node_name: server.chef_node_name)
|
||||
def puts_and_flush(message)
|
||||
@out.puts message
|
||||
@out.flush
|
||||
end
|
||||
|
||||
def interval_in_seconds(interval_as_string)
|
||||
interval = interval_as_string.to_i
|
||||
measure_unit = interval_as_string.chars.last
|
||||
case measure_unit
|
||||
when 's'
|
||||
interval
|
||||
when 'm'
|
||||
interval * 60
|
||||
when 'h'
|
||||
interval * 60 * 60
|
||||
when 'd'
|
||||
interval * 60 * 60 * 24
|
||||
when 'w'
|
||||
interval * 60 * 60 * 24 * 7
|
||||
else
|
||||
raise 'Wrong interval format'
|
||||
def schedule_expiration
|
||||
if @deploy_env.expires
|
||||
@out << "Planning expiration in #{@deploy_env.expires}"
|
||||
ExpirationScheduler.new(@deploy_env.expires, @server).schedule_expiration!
|
||||
end
|
||||
end
|
||||
|
||||
def check_ssh_command(cert_path, address)
|
||||
cmd = 'ssh '
|
||||
cmd << "-i #{cert_path} "
|
||||
cmd << '-q '
|
||||
cmd << '-o StrictHostKeyChecking=no '
|
||||
cmd << '-o ConnectTimeout=2 -o ConnectionAttempts=1 '
|
||||
cmd << "#{address} 'exit'"
|
||||
cmd << " 2>&1"
|
||||
cmd
|
||||
end
|
||||
|
||||
# to simplify testing
|
||||
# :nocov:
|
||||
def execute_system_command(cmd)
|
||||
`#{cmd}`
|
||||
end
|
||||
|
||||
def last_command_successful?
|
||||
$?.success?
|
||||
end
|
||||
|
||||
def knife_instance
|
||||
@knife_instance ||= KnifeFactory.instance
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
class String
|
||||
def present?
|
||||
!empty?
|
||||
end
|
||||
|
||||
def blank?
|
||||
empty?
|
||||
end
|
||||
end
|
||||
@ -1,35 +0,0 @@
|
||||
module StringHelper
|
||||
extend self
|
||||
|
||||
# from Rails' ActiveSupport
|
||||
def underscore(string)
|
||||
string.gsub(/::/, '/').
|
||||
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
||||
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
||||
tr("-", "_").
|
||||
downcase
|
||||
end
|
||||
|
||||
def underscore_class(klass, without_ancestors=true)
|
||||
class_name = if without_ancestors
|
||||
klass.to_s.split('::').last
|
||||
else
|
||||
klass.to_s
|
||||
end
|
||||
StringHelper.underscore(class_name)
|
||||
end
|
||||
|
||||
# from Rails' ActiveSupport
|
||||
def camelize(term)
|
||||
string = term.to_s
|
||||
string = string.sub(/^[a-z\d]*/) { $&.capitalize }
|
||||
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
|
||||
string.gsub!(/\//, '::')
|
||||
string
|
||||
end
|
||||
|
||||
# rough simplification
|
||||
def pluralize(string)
|
||||
"#{string}s"
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,58 @@
|
||||
require 'db/mongo/models/provider_accounts/ec2_provider_account'
|
||||
require 'db/mongo/models/provider_accounts/openstack_provider_account'
|
||||
require 'db/mongo/connectors/provider_account'
|
||||
require 'spec/connectors/tester_connector/provider_account'
|
||||
|
||||
RSpec.describe Connectors::ProviderAccount, type: :connector do
|
||||
set_tester_connector TesterConnector::ProviderAccount
|
||||
|
||||
include_examples 'mongo connector', {
|
||||
model_name: :provider_account,
|
||||
factory_name: :ec2_provider_account,
|
||||
only: [:insert, :delete],
|
||||
field_to_update: :description
|
||||
}
|
||||
|
||||
describe '#provider_accounts', cleanup_after: :all do
|
||||
before(:all) do
|
||||
@tester_connector.create(id: 'foo', provider: 'ec2')
|
||||
@tester_connector.create(id: 'bar', provider: 'openstack')
|
||||
end
|
||||
|
||||
it 'returns array of Ec2ProviderAccount if @provider is ec2' do
|
||||
expect(
|
||||
@connector.provider_accounts('ec2')
|
||||
).to be_an_array_of(Devops::Model::Ec2ProviderAccount).and have_size(1)
|
||||
end
|
||||
|
||||
it 'returns array of Ec2ProviderAccount if @provider is openstack' do
|
||||
expect(
|
||||
@connector.provider_accounts('openstack')
|
||||
).to be_an_array_of(Devops::Model::OpenstackProviderAccount).and have_size(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#provider_account', cleanup_after: :all do
|
||||
before(:all) do
|
||||
@tester_connector.create(id: 'foo', provider: 'ec2')
|
||||
@tester_connector.create(id: 'bar', provider: 'openstack')
|
||||
end
|
||||
|
||||
it 'returns ec2 provider account' do
|
||||
acc = @connector.provider_account('ec2', 'foo')
|
||||
expect(acc).to be_a(Devops::Model::Ec2ProviderAccount)
|
||||
expect(acc.account_name).to eq 'foo'
|
||||
end
|
||||
|
||||
it 'returns openstack provider account' do
|
||||
acc = @connector.provider_account('openstack', 'bar')
|
||||
expect(acc).to be_a(Devops::Model::OpenstackProviderAccount)
|
||||
expect(acc.account_name).to eq 'bar'
|
||||
end
|
||||
|
||||
it 'raises error if account is missing' do
|
||||
expect{@connector.provider_account('ec2', 'missing')}.to raise_error(RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@ -0,0 +1,6 @@
|
||||
require_relative 'base'
|
||||
|
||||
module TesterConnector
|
||||
class ProviderAccount < Base
|
||||
end
|
||||
end
|
||||
47
devops-service/spec/executors/expiration_scheduler_spec.rb
Normal file
47
devops-service/spec/executors/expiration_scheduler_spec.rb
Normal file
@ -0,0 +1,47 @@
|
||||
require 'lib/executors/expiration_scheduler'
|
||||
|
||||
RSpec.describe Devops::Executor::ExpirationScheduler do
|
||||
let(:server) { build(:server) }
|
||||
|
||||
describe '#schedule_expiration!' do
|
||||
it 'schedules server deleting at given time' do
|
||||
expect(DeleteExpiredServerWorker).to receive(:perform_in).with(120, server_chef_node_name: 'chef_node_name')
|
||||
described_class.new('2m', server).schedule_expiration!
|
||||
end
|
||||
|
||||
it "doesn't schedule job if expires is nil" do
|
||||
expect(DeleteExpiredServerWorker).not_to receive(:perform_in)
|
||||
described_class.new(nil, server).schedule_expiration!
|
||||
end
|
||||
end
|
||||
|
||||
describe '#interval_in_seconds' do
|
||||
def interval_in_seconds(expires)
|
||||
described_class.new(expires, server).interval_in_seconds
|
||||
end
|
||||
|
||||
it 'recognizes seconds' do
|
||||
expect(interval_in_seconds('2s')).to eq 2
|
||||
end
|
||||
|
||||
it 'recognizes minutes' do
|
||||
expect(interval_in_seconds('3m')).to eq 180
|
||||
end
|
||||
|
||||
it 'recognizes hours' do
|
||||
expect(interval_in_seconds('1h')).to eq 3600
|
||||
end
|
||||
|
||||
it 'recognizes days' do
|
||||
expect(interval_in_seconds('1d')).to eq 86400
|
||||
end
|
||||
|
||||
it 'recognizes weeks' do
|
||||
expect(interval_in_seconds('1w')).to eq 604800
|
||||
end
|
||||
|
||||
it 'raises on wrong format' do
|
||||
expect { interval_in_seconds('wrong') }.to raise_error(StandardError)
|
||||
end
|
||||
end
|
||||
end
|
||||
790
devops-service/spec/executors/server_executor_spec.rb
Normal file
790
devops-service/spec/executors/server_executor_spec.rb
Normal file
@ -0,0 +1,790 @@
|
||||
require 'lib/executors/server_executor'
|
||||
|
||||
RSpec.describe Devops::Executor::ServerExecutor, type: :executor, stubbed_connector: true, stubbed_logger: true do
|
||||
let(:project) { build(:project) }
|
||||
let(:deploy_env) { project.deploy_env('foo') }
|
||||
let(:server) { build(:server, project: project.id, deploy_env: 'foo') }
|
||||
let(:output) { File.open(File::NULL, "w") }
|
||||
let(:provider) { double('Provider instance') }
|
||||
let(:executor) { described_class.new(server, output) }
|
||||
|
||||
|
||||
before do
|
||||
allow(stubbed_connector).to receive(:project) { project }
|
||||
allow(executor.deploy_env).to receive(:provider_instance) { provider }
|
||||
allow(server).to receive(:provider_instance) { provider }
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'sets server, project, deploy_env, out instance variables' do
|
||||
expect(executor.server).to eq server
|
||||
expect(executor.deploy_env).to eq deploy_env
|
||||
expect(executor).to have_instance_variable_value(:project, project)
|
||||
expect(executor).to have_instance_variable_value(:out, output)
|
||||
end
|
||||
|
||||
it 'defines :flush method on @out if it is absent' do
|
||||
out = Class.new.new
|
||||
expect(out).not_to respond_to(:flush)
|
||||
described_class.new(server, out)
|
||||
expect(out).to respond_to(:flush)
|
||||
end
|
||||
|
||||
it 'sets current_user from options' do
|
||||
user = double
|
||||
executor = described_class.new(server, '', {current_user: user})
|
||||
expect(executor).to have_instance_variable_value(:current_user, user)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe '#report=' do
|
||||
it 'sets report instance variable' do
|
||||
executor.report= 'foo'
|
||||
expect(executor).to have_instance_variable_value(:report, 'foo')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#project=' do
|
||||
it 'sets project instance variable' do
|
||||
executor.project= 'foo'
|
||||
expect(executor).to have_instance_variable_value(:project, 'foo')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe '.symbolic_error_code' do
|
||||
it 'returns symbol given an integer' do
|
||||
expect(described_class.symbolic_error_code(2)).to eq :server_bootstrap_fail
|
||||
end
|
||||
|
||||
it "returns :unknown_error if can't recognize error code" do
|
||||
expect(described_class.symbolic_error_code(123)).to eq :unknown_error
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
describe '#create_server_object' do
|
||||
it 'builds Server object' do
|
||||
server = executor.create_server_object('created_by' => 'me')
|
||||
expect(server).to be_a(Devops::Model::Server)
|
||||
expect(server.project).to eq 'my_project'
|
||||
expect(server.deploy_env).to eq 'foo'
|
||||
expect(server.created_by).to eq 'me'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
describe '#create_server' do
|
||||
let(:image) { double('Image instance', remote_user: 'remote_user') }
|
||||
let(:create_server) {
|
||||
executor.create_server(
|
||||
'created_by' => 'user',
|
||||
'run_list' => @run_list,
|
||||
'name' => 'node_name',
|
||||
'key' => @key,
|
||||
'without_bootstrap' => @without_bootstrap
|
||||
)
|
||||
}
|
||||
|
||||
before do
|
||||
allow(provider).to receive(:create_server) { true }
|
||||
allow(stubbed_connector).to receive(:image) { image }
|
||||
allow(stubbed_connector).to receive(:server_insert)
|
||||
@without_bootstrap = true
|
||||
@run_list = %w(role[asd])
|
||||
@key = 'key'
|
||||
end
|
||||
|
||||
it 'builds server model from given options' do
|
||||
create_server
|
||||
expect(executor.server.created_by).to eq 'user'
|
||||
expect(executor.server.chef_node_name).to eq 'node_name'
|
||||
expect(executor.server.key).to eq @key
|
||||
expect(executor.server.run_list).to eq @run_list
|
||||
end
|
||||
|
||||
it 'sets run list to an empty array by default' do
|
||||
@run_list = nil
|
||||
create_server
|
||||
expect(executor.server.run_list).to eq []
|
||||
end
|
||||
|
||||
it 'sets key to default provider ssh key by default' do
|
||||
@key = nil
|
||||
allow(provider).to receive(:ssh_key) { 'default_key' }
|
||||
create_server
|
||||
expect(executor.server.key).to eq 'default_key'
|
||||
end
|
||||
|
||||
it 'runs hooks' do
|
||||
expect(executor).to receive(:run_hook).with(:before_create).ordered
|
||||
expect(executor).to receive(:run_hook).with(:after_create).ordered
|
||||
create_server
|
||||
end
|
||||
|
||||
it 'creates server in cloud' do
|
||||
expect(provider).to receive(:create_server).with(
|
||||
an_instance_of(Devops::Model::Server), deploy_env.image, deploy_env.flavor, deploy_env.subnets, deploy_env.groups, output
|
||||
)
|
||||
create_server
|
||||
end
|
||||
|
||||
it 'inserts built server into mongo' do
|
||||
expect(stubbed_connector).to receive(:server_insert)
|
||||
create_server
|
||||
end
|
||||
|
||||
it 'schedules expiration for server' do
|
||||
deploy_env.expires = '2m'
|
||||
allow(DeleteExpiredServerWorker).to receive(:perform_in)
|
||||
expect(DeleteExpiredServerWorker).to receive(:perform_in).with(120, {server_chef_node_name: 'node_name'})
|
||||
create_server
|
||||
end
|
||||
|
||||
it "doesn't schedule expiration if deploy_env.expires is nil" do
|
||||
deploy_env.expires = nil
|
||||
expect(DeleteExpiredServerWorker).not_to receive(:perform_in)
|
||||
create_server
|
||||
end
|
||||
|
||||
context 'without_bootstrap option is false' do
|
||||
it 'launches bootstrap' do
|
||||
@without_bootstrap = false
|
||||
allow(image).to receive(:bootstrap_template) { 'template' }
|
||||
allow(executor).to receive(:two_phase_bootstrap)
|
||||
expect(executor).to receive(:two_phase_bootstrap)
|
||||
create_server
|
||||
end
|
||||
end
|
||||
|
||||
context 'without_bootstrap option is nil' do
|
||||
it 'launches bootstrap' do
|
||||
@without_bootstrap = nil
|
||||
allow(image).to receive(:bootstrap_template) { 'template' }
|
||||
allow(executor).to receive(:two_phase_bootstrap)
|
||||
expect(executor).to receive(:two_phase_bootstrap)
|
||||
create_server
|
||||
end
|
||||
end
|
||||
|
||||
context 'without_bootstrap option is true' do
|
||||
it "doesn't launch bootstrap" do
|
||||
@without_bootstrap = true
|
||||
expect(executor).not_to receive(:two_phase_bootstrap)
|
||||
create_server
|
||||
end
|
||||
end
|
||||
|
||||
context 'if error has been raised during execution' do
|
||||
before do
|
||||
allow(stubbed_connector).to receive(:server_delete)
|
||||
allow(provider).to receive(:create_server) { raise }
|
||||
end
|
||||
|
||||
it 'rollbacks server creating' do
|
||||
expect(executor).to receive(:roll_back)
|
||||
create_server
|
||||
end
|
||||
|
||||
it 'deletes server from mongo' do
|
||||
expect(stubbed_connector).to receive(:server_delete)
|
||||
create_server
|
||||
end
|
||||
end
|
||||
|
||||
context "if creating server in cloud wasn't successful" do
|
||||
it 'returns creating_server_in_cloud_failed error code' do
|
||||
allow(provider).to receive(:create_server) { false }
|
||||
expect(create_server).to eq 10
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
describe '#bootstrap', stubbed_knife: true do
|
||||
let(:image) { double('Key instance', path: 'path') }
|
||||
let(:bootstrap) { executor.bootstrap({}) }
|
||||
|
||||
before do
|
||||
allow(executor).to receive(:sleep)
|
||||
allow(executor).to receive(:last_command_successful?).and_return(true)
|
||||
allow(executor).to receive(:execute_system_command)
|
||||
allow(provider).to receive(:create_default_chef_node_name).and_return('chef_node')
|
||||
allow(stubbed_connector).to receive(:key).and_return(image)
|
||||
allow(stubbed_connector).to receive(:server_set_chef_node_name)
|
||||
allow(stubbed_knife).to receive(:knife_bootstrap).and_return(0)
|
||||
end
|
||||
|
||||
it 'run before hook' do
|
||||
expect(executor).to receive(:run_hook).with(:before_bootstrap, output).ordered
|
||||
expect(executor).to receive(:run_hook).with(:after_bootstrap, output).ordered
|
||||
bootstrap
|
||||
end
|
||||
|
||||
context "when server's private ip is unset" do
|
||||
it 'returns server_bootstrap_private_ip_unset error code' do
|
||||
server.private_ip = nil
|
||||
expect(bootstrap).to eq 4
|
||||
end
|
||||
end
|
||||
|
||||
it 'tries to ssh to server' do
|
||||
expect(executor).to receive(:execute_system_command).with(/ssh/)
|
||||
bootstrap
|
||||
end
|
||||
|
||||
context "couldn't ssh to server" do
|
||||
before { allow(executor).to receive(:last_command_successful?) { false } }
|
||||
|
||||
it 'tries to ssh to server maximum MAX_SSH_RETRIES_AMOUNT times' do
|
||||
max_retries = Devops::Executor::ServerExecutor::MAX_SSH_RETRIES_AMOUNT
|
||||
expect(executor).to receive(:execute_system_command).exactly(max_retries).times
|
||||
bootstrap
|
||||
end
|
||||
|
||||
it 'returns server_bootstrap_fail error code' do
|
||||
expect(bootstrap).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context 'after successful ssh check' do
|
||||
before { allow(executor).to receive(:last_command_successful?).and_return(false, true) }
|
||||
|
||||
it "sets default chef node name if it's nil" do
|
||||
executor.server.chef_node_name = nil
|
||||
expect {bootstrap}.to change {executor.server.chef_node_name}.to 'chef_node'
|
||||
end
|
||||
|
||||
it 'executes knife bootstrap' do
|
||||
expect(stubbed_knife).to receive(:knife_bootstrap).with(output, server.private_ip, instance_of(Array))
|
||||
bootstrap
|
||||
end
|
||||
|
||||
it "bootstraps to public ip if it's set" do
|
||||
server.public_ip = '8.8.8.8'
|
||||
expect(stubbed_knife).to receive(:knife_bootstrap).with(output, '8.8.8.8', instance_of(Array))
|
||||
bootstrap
|
||||
end
|
||||
|
||||
context 'after successful bootstrap' do
|
||||
it "updates server's chef node name in db" do
|
||||
expect(stubbed_connector).to receive(:server_set_chef_node_name).with(instance_of(Devops::Model::Server))
|
||||
bootstrap
|
||||
end
|
||||
end
|
||||
|
||||
context "if bootstraping wasn't successful" do
|
||||
before { allow(stubbed_knife).to receive(:knife_bootstrap).and_return(123) }
|
||||
|
||||
it 'returns :server_bootstrap_fail code' do
|
||||
expect(bootstrap).to eq 2
|
||||
end
|
||||
|
||||
it "doesn't run after hook" do
|
||||
expect(executor).to receive(:run_hook).with(:before_bootstrap, output)
|
||||
bootstrap
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
describe '#two_phase_bootstrap', stubbed_knife: true do
|
||||
let(:two_phase_bootstrap) { executor.two_phase_bootstrap({}) }
|
||||
|
||||
before do
|
||||
allow(provider).to receive(:run_list) {[]}
|
||||
allow(stubbed_connector).to receive(:server_delete)
|
||||
end
|
||||
|
||||
context 'when bootstrap was successful' do
|
||||
before do
|
||||
allow(executor).to receive(:bootstrap) { 0 }
|
||||
allow(executor).to receive(:check_server_on_chef_server) { false }
|
||||
end
|
||||
|
||||
context 'if node presents on chef server' do
|
||||
before do
|
||||
allow(executor).to receive(:check_server_on_chef_server) { true }
|
||||
allow(executor).to receive(:deploy_server)
|
||||
allow(stubbed_knife).to receive(:set_run_list)
|
||||
end
|
||||
|
||||
it 'builds run list' do
|
||||
expect(executor).to receive(:compute_run_list)
|
||||
two_phase_bootstrap
|
||||
end
|
||||
|
||||
it 'sets run list to chef node' do
|
||||
expect(stubbed_knife).to receive(:set_run_list)
|
||||
two_phase_bootstrap
|
||||
end
|
||||
|
||||
it 'deploys server' do
|
||||
expect(executor).to receive(:deploy_server)
|
||||
two_phase_bootstrap
|
||||
end
|
||||
|
||||
context 'if deploy was successful' do
|
||||
it 'returns 0' do
|
||||
allow(executor).to receive(:deploy_server) { 0 }
|
||||
expect(two_phase_bootstrap).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context "if deploy wasn't successful" do
|
||||
it 'returns :deploy_failed code' do
|
||||
allow(executor).to receive(:deploy_server) { 1 }
|
||||
expect(two_phase_bootstrap).to eq 8
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an error occured during deploy' do
|
||||
it 'returns :deploy_unknown_error code' do
|
||||
allow(executor).to receive(:deploy_server) { raise }
|
||||
expect(two_phase_bootstrap).to eq 6
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "if node doesn't present on chef server" do
|
||||
it 'roll backs and then deletes server from mongo' do
|
||||
allow(executor).to receive(:check_server_on_chef_server) { false }
|
||||
allow(executor).to receive(:roll_back)
|
||||
allow(stubbed_connector).to receive(:server_delete)
|
||||
expect(executor).to receive(:roll_back).ordered
|
||||
expect(stubbed_connector).to receive(:server_delete).ordered
|
||||
two_phase_bootstrap
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when bootstrap wasn't successful" do
|
||||
it 'returns :server_bootstrap_fail error code' do
|
||||
allow(executor).to receive(:bootstrap) { 1 }
|
||||
expect(two_phase_bootstrap).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an error occured during bootstrap' do
|
||||
it 'returns :server_bootstrap_unknown_error error code' do
|
||||
allow(executor).to receive(:bootstrap) { raise }
|
||||
expect(two_phase_bootstrap).to eq 7
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#check_server_on_chef_server', stubbed_knife: true do
|
||||
before do
|
||||
server.chef_node_name = 'a'
|
||||
allow(stubbed_knife).to receive(:chef_node_list) { @node_list }
|
||||
allow(stubbed_knife).to receive(:chef_client_list) { @client_list }
|
||||
end
|
||||
|
||||
it 'returns true when node_name in node list and in client list' do
|
||||
@node_list = %w(a); @client_list = %w(a)
|
||||
expect(executor.check_server_on_chef_server).to be true
|
||||
end
|
||||
|
||||
it "returns false if node name isn't in node list" do
|
||||
@node_list = []; @client_list = %w(a)
|
||||
expect(executor.check_server_on_chef_server).to be false
|
||||
end
|
||||
|
||||
it "returns false if node name isn't in node list" do
|
||||
@node_list = %w(a); @client_list = []
|
||||
expect(executor.check_server_on_chef_server).to be false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe '#unbootstrap', stubbed_knife: true do
|
||||
before do
|
||||
allow(stubbed_connector).to receive_message_chain('key.path') { 'path_to_key' }
|
||||
allow(stubbed_knife).to receive(:chef_node_delete)
|
||||
allow(stubbed_knife).to receive(:chef_client_delete)
|
||||
allow(executor).to receive(:execute_system_command) { '' }
|
||||
allow(executor).to receive(:last_command_successful?) { true }
|
||||
allow(executor).to receive(:sleep)
|
||||
allow(Net::SSH).to receive(:start)
|
||||
end
|
||||
|
||||
it 'connects by ssh' do
|
||||
expect(Net::SSH).to receive(:start)
|
||||
executor.unbootstrap
|
||||
end
|
||||
|
||||
it 'returns hash with error after 5 unsuccessful retries' do
|
||||
allow(Net::SSH).to receive(:start) { raise }
|
||||
expect(Net::SSH).to receive(:start).exactly(5).times
|
||||
expect(executor.unbootstrap).to be_a(Hash).and include(:error)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe '#deploy_server_with_tags', stubbed_knife: true do
|
||||
let(:current_tags) { @current_tags }
|
||||
let(:initial_tags) { %w(a b) }
|
||||
let(:joined_initial_tags) { initial_tags.join(' ') }
|
||||
let(:given_tags) { %w(c d) }
|
||||
let(:joined_given_tags) { given_tags.join(' ') }
|
||||
let(:deploy_server_with_tags) { executor.deploy_server_with_tags(given_tags, {}) }
|
||||
|
||||
before do
|
||||
@current_tags = initial_tags.dup
|
||||
allow(stubbed_knife).to receive(:tags_list) { @current_tags }
|
||||
allow(stubbed_knife).to receive(:swap_tags) do |_, tags_to_delete, tags_to_add|
|
||||
@current_tags -= tags_to_delete.split
|
||||
@current_tags += tags_to_add.split
|
||||
end
|
||||
allow(executor).to receive(:deploy_server)
|
||||
end
|
||||
|
||||
context 'when tags are empty' do
|
||||
it 'just deploys server' do
|
||||
expect(executor).to receive(:deploy_server)
|
||||
expect(stubbed_knife).not_to receive(:swap_tags)
|
||||
executor.deploy_server_with_tags([], {})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tags are not empty' do
|
||||
it 'temporarily swaps current_tags with given ones, deploys server and then restores tags' do
|
||||
expect(stubbed_knife).to receive(:tags_list).ordered
|
||||
expect(stubbed_knife).to receive(:swap_tags).with(instance_of(String), joined_initial_tags, joined_given_tags).ordered
|
||||
expect(executor).to receive(:deploy_server).ordered
|
||||
expect(stubbed_knife).to receive(:swap_tags).with(instance_of(String), joined_given_tags, joined_initial_tags).ordered
|
||||
deploy_server_with_tags
|
||||
end
|
||||
end
|
||||
|
||||
context 'if error occures during deploy' do
|
||||
it 'restores tags anyway' do
|
||||
allow(executor).to receive(:deploy_server) { raise }
|
||||
expect {
|
||||
deploy_server_with_tags
|
||||
}.to raise_error StandardError
|
||||
expect(current_tags).to eq initial_tags
|
||||
end
|
||||
end
|
||||
|
||||
context 'if cannot add tags to server' do
|
||||
it 'returns :server_cannot_update_tags code' do
|
||||
allow(stubbed_knife).to receive(:swap_tags) { false }
|
||||
expect(deploy_server_with_tags).to eq 3
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
describe '#deploy_server', stubbed_knife: true do
|
||||
let(:deploy_info) { @deploy_info }
|
||||
let(:json_file_name) { 'json.json' }
|
||||
let(:json_file_path) { File.join(SpecSupport.tmp_dir, json_file_name) }
|
||||
|
||||
let(:deploy_server) { executor.deploy_server(deploy_info) }
|
||||
|
||||
before do
|
||||
allow(executor).to receive(:run_hook).with(:before_deploy, any_args)
|
||||
allow(executor).to receive(:run_hook).with(:after_deploy, any_args)
|
||||
allow(stubbed_knife).to receive(:ssh_stream) { 'Chef Client finished'}
|
||||
allow(stubbed_connector).to receive(:key) { double('Key', path: 'path_to_key') }
|
||||
allow(stubbed_connector).to receive(:server_update)
|
||||
@deploy_info = {}
|
||||
end
|
||||
|
||||
|
||||
it 'runs before_deploy and after_deploy hooks' do
|
||||
expect(executor).to receive(:run_hook).with(:before_deploy, any_args).ordered
|
||||
expect(executor).to receive(:run_hook).with(:after_deploy, any_args).ordered
|
||||
deploy_server
|
||||
end
|
||||
|
||||
context 'when uses json file' do
|
||||
before(:all) do
|
||||
@tmp_files_at_start = Dir.entries(SpecSupport.tmp_dir)
|
||||
end
|
||||
before do
|
||||
allow(DevopsConfig).to receive(:config).and_return({
|
||||
project_info_dir: SpecSupport.tmp_dir,
|
||||
address: 'host.com',
|
||||
port: '8080',
|
||||
url_prefix: 'api'
|
||||
})
|
||||
deploy_info['use_json_file'] = true
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
diff = Dir.entries(SpecSupport.tmp_dir) - @tmp_files_at_start
|
||||
diff.each do |file|
|
||||
FileUtils.rm(File.join(SpecSupport.tmp_dir, file))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
it 'writes deploy_info to json file if it not exists' do
|
||||
expect { deploy_server }.to change { Dir.entries(SpecSupport.tmp_dir)}
|
||||
end
|
||||
|
||||
it "writes deploy_info to given json file name if it doesn't exist" do
|
||||
FileUtils.rm(json_file_path) if File.exists?(json_file_path)
|
||||
deploy_info['json_file'] = json_file_name
|
||||
expect { deploy_server }.to change {
|
||||
Dir.entries(SpecSupport.tmp_dir)
|
||||
}
|
||||
FileUtils.rm(json_file_path)
|
||||
end
|
||||
|
||||
it 'reads json from file if it exists' do
|
||||
deploy_info['json_file'] = json_file_name
|
||||
File.open(json_file_path, 'w') { |file| file.puts '{"foo": "bar"'}
|
||||
expect { deploy_server }.not_to change {
|
||||
Dir.entries(SpecSupport.tmp_dir)
|
||||
}
|
||||
FileUtils.rm(json_file_path)
|
||||
end
|
||||
|
||||
it 'adds link to json to deploy command' do
|
||||
deploy_info['json_file'] = json_file_name
|
||||
regexp = %r(-j http://host.com:8080/api/v2.0/deploy/data/#{json_file_name})
|
||||
expect(stubbed_knife).to receive(:ssh_stream).with(anything, regexp, any_args)
|
||||
deploy_server
|
||||
end
|
||||
end
|
||||
|
||||
context "doesn't use json file" do
|
||||
before do
|
||||
deploy_info['use_json_file'] = false
|
||||
deploy_info['run_list'] = %w(foo bar)
|
||||
end
|
||||
|
||||
it "adds run list to command if server's stack is set" do
|
||||
server.stack = 'stack'
|
||||
expect(stubbed_knife).to receive(:ssh_stream).with(anything, %r(-r foo,bar), any_args)
|
||||
deploy_server
|
||||
end
|
||||
|
||||
it "doesn't add run list to command if server's stack is unset" do
|
||||
expect(stubbed_knife).to receive(:ssh_stream).with(anything, 'chef-client --no-color', any_args)
|
||||
deploy_server
|
||||
end
|
||||
end
|
||||
|
||||
it "uses server's key" do
|
||||
expect(stubbed_connector).to receive(:key).with('key_id')
|
||||
expect(stubbed_knife).to receive(:ssh_stream).with(any_args, 'path_to_key')
|
||||
deploy_server
|
||||
end
|
||||
|
||||
it "uses public ip if it's set" do
|
||||
server.public_ip = '127.0.0.1'
|
||||
expect(stubbed_knife).to receive(:ssh_stream).with(anything, anything, '127.0.0.1', any_args)
|
||||
deploy_server
|
||||
end
|
||||
|
||||
it "uses private_ip if public_ip isn't set" do
|
||||
expect(stubbed_knife).to receive(:ssh_stream).with(anything, anything, server.private_ip, any_args)
|
||||
deploy_server
|
||||
end
|
||||
|
||||
context 'if deploy was successful' do
|
||||
it "updates server's last operation" do
|
||||
expect(server).to receive(:set_last_operation).with('deploy', anything)
|
||||
expect(stubbed_connector).to receive(:server_update).with(server)
|
||||
deploy_server
|
||||
end
|
||||
|
||||
it 'returns 0' do
|
||||
expect(deploy_server).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context "when deploy wasn't successful" do
|
||||
before { allow(stubbed_knife).to receive(:ssh_stream) { 'fail'} }
|
||||
it "doesn't run after_deploy hook" do
|
||||
expect(executor).to receive(:run_hook).with(:before_deploy, any_args)
|
||||
expect(executor).not_to receive(:run_hook).with(:after_deploy, any_args)
|
||||
deploy_server
|
||||
end
|
||||
|
||||
it 'returns 1' do
|
||||
expect(deploy_server).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe '#delete_from_chef_server', stubbed_knife: true do
|
||||
let(:delete_from_chef_server) { executor.delete_from_chef_server('foo') }
|
||||
before do
|
||||
allow(stubbed_knife).to receive(:chef_client_delete)
|
||||
allow(stubbed_knife).to receive(:chef_node_delete)
|
||||
delete_from_chef_server
|
||||
end
|
||||
|
||||
it 'returns hash with :chef_node and :chef_client keys' do
|
||||
expect(delete_from_chef_server).to be_a(Hash).and include(:chef_node, :chef_client)
|
||||
end
|
||||
|
||||
it 'calls to :chef_node_delete and :chef_client_delete' do
|
||||
expect(stubbed_knife).to have_received(:chef_client_delete)
|
||||
expect(stubbed_knife).to have_received(:chef_node_delete)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe '#delete_server' do
|
||||
let(:delete_server) { executor.delete_server }
|
||||
|
||||
context 'when server is static' do
|
||||
before do
|
||||
server.provider = 'static'
|
||||
allow(stubbed_connector).to receive(:server_delete).with(server.id)
|
||||
allow(executor).to receive(:unbootstrap)
|
||||
end
|
||||
|
||||
it 'performs unbootstrap' do
|
||||
expect(executor).to receive(:unbootstrap)
|
||||
delete_server
|
||||
end
|
||||
|
||||
it 'deletes server from mongo' do
|
||||
expect(stubbed_connector).to receive(:server_delete).with(server.id)
|
||||
delete_server
|
||||
end
|
||||
|
||||
it 'returns 0' do
|
||||
expect(delete_server).to eq 0
|
||||
end
|
||||
|
||||
it "doesn't try to remove it from cloud" do
|
||||
expect{delete_server}.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context "when server isn't static", stubbed_knife: true do
|
||||
before do
|
||||
allow(server).to receive_message_chain('provider_instance.delete_server')
|
||||
allow(stubbed_connector).to receive(:server_delete).with(server.id)
|
||||
allow(stubbed_knife).to receive(:chef_node_delete)
|
||||
allow(stubbed_knife).to receive(:chef_client_delete)
|
||||
end
|
||||
|
||||
it 'deletes from info about note chef server' do
|
||||
allow(executor).to receive(:delete_from_chef_server).and_call_original
|
||||
expect(executor).to receive(:delete_from_chef_server)
|
||||
delete_server
|
||||
end
|
||||
|
||||
it "doesn't unbootstrap server" do
|
||||
expect(executor).not_to receive(:unbootstrap)
|
||||
delete_server
|
||||
end
|
||||
|
||||
it 'deletes server from cloud' do
|
||||
expect(server).to receive_message_chain('provider_instance.delete_server').with(server)
|
||||
delete_server
|
||||
end
|
||||
|
||||
it "doesn't raise error if server wasn't found in cloud" do
|
||||
allow(server).to receive_message_chain('provider_instance.name')
|
||||
allow(server).to receive_message_chain('provider_instance.delete_server') {
|
||||
raise Fog::Compute::OpenStack::NotFound
|
||||
}
|
||||
expect { delete_server }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'deletes server from mongo' do
|
||||
expect(stubbed_connector).to receive(:server_delete).with(server.id)
|
||||
delete_server
|
||||
end
|
||||
|
||||
it 'returns 0' do
|
||||
expect(delete_server).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe '#rollback' do
|
||||
before do
|
||||
allow(executor).to receive(:delete_from_chef_server) { {} }
|
||||
allow(server).to receive_message_chain('provider_instance.delete_server')
|
||||
end
|
||||
|
||||
it "does nothing if server.id is nil" do
|
||||
server.id = nil
|
||||
expect(executor).not_to receive(:delete_from_chef_server)
|
||||
expect(server).not_to receive(:provider_instance)
|
||||
executor.roll_back
|
||||
end
|
||||
|
||||
it 'deletes node from chef server and instance from cloud' do
|
||||
expect(executor).to receive(:delete_from_chef_server)
|
||||
expect(server).to receive_message_chain('provider_instance.delete_server')
|
||||
executor.roll_back
|
||||
end
|
||||
|
||||
it "doesn't raise if deleting server in cloud raises an error" do
|
||||
allow(server).to receive_message_chain('provider_instance.delete_server') { raise }
|
||||
expect { executor.roll_back }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe '#add_run_list_to_deploy_info' do
|
||||
it "doesn't change deploy info if it already includes run list" do
|
||||
deploy_info = {'run_list' => %w(foo)}
|
||||
expect {
|
||||
executor.add_run_list_to_deploy_info(output, deploy_info)
|
||||
}.not_to change { deploy_info }
|
||||
end
|
||||
|
||||
it 'computes and adds run_list to deploy_info' do
|
||||
deploy_info = {}
|
||||
allow(executor).to receive(:compute_run_list) { %w(foo) }
|
||||
expect(executor).to receive(:compute_run_list)
|
||||
executor.add_run_list_to_deploy_info(output, deploy_info)
|
||||
expect(deploy_info['run_list']).to eq %w(foo)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#compute_run_list' do
|
||||
before do
|
||||
allow(deploy_env).to receive_message_chain('provider_instance.run_list') { %w(a) }
|
||||
project.run_list = %w(b)
|
||||
deploy_env.run_list = %w(c)
|
||||
server.run_list = %w(d)
|
||||
end
|
||||
|
||||
it "returns array with run list merged from provider's, project's, env's and server's run lists" do
|
||||
expect(executor.compute_run_list).to be_an(Array).and contain_exactly(*%w(a b c d))
|
||||
end
|
||||
|
||||
it "includes stack's run list if stack is set", stubbed_connector: true do
|
||||
server.stack = 'stack'
|
||||
allow(stubbed_connector).to receive(:stack) { instance_double(Devops::Model::StackEc2, run_list: %w(e)) }
|
||||
expect(executor.compute_run_list).to be_an(Array).and contain_exactly(*%w(a b c d e))
|
||||
end
|
||||
|
||||
it "doesn't contain nils" do
|
||||
server.run_list = nil
|
||||
server.stack = 'stack'
|
||||
allow(stubbed_connector).to receive(:stack) { instance_double(Devops::Model::StackEc2, run_list: nil) }
|
||||
expect(executor.compute_run_list).to be_an(Array).and contain_exactly(*%w(a b c))
|
||||
end
|
||||
|
||||
it 'returns uniq elements' do
|
||||
project.run_list = %w(a)
|
||||
deploy_env.run_list = %w(a)
|
||||
expect(executor.compute_run_list).to be_an(Array).and contain_exactly(*%w(a d))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -3,7 +3,7 @@ require 'db/mongo/models/key'
|
||||
FactoryGirl.define do
|
||||
factory :key, class: Devops::Model::Key do
|
||||
id 'user_key'
|
||||
path SpecSupport::BLANK_FILE
|
||||
path SpecSupport.blank_file
|
||||
scope 'user'
|
||||
end
|
||||
end
|
||||
@ -5,7 +5,21 @@ require_relative 'shared_cloud_deploy_env_specs'
|
||||
RSpec.describe Devops::Model::DeployEnvEc2, type: :model do
|
||||
let(:env) { build(:deploy_env_ec2) }
|
||||
|
||||
describe 'it inherits from cloud deploy_env', stubbed_env_validators: true, stubbed_logger: true do
|
||||
describe 'it inherits from cloud deploy_env', stubbed_connector: true, stubbed_logger: true do
|
||||
before do
|
||||
provider_double = instance_double('Provider::Ec2',
|
||||
flavors: [{'id' => 'flavor'}],
|
||||
networks: [{'default' => {'vpcId' => 'foo'}}],
|
||||
groups: {'default' => nil},
|
||||
images: [{'id' => 'image'}]
|
||||
)
|
||||
allow(Provider::ProviderFactory).to receive(:providers) { %w(ec2) }
|
||||
allow(Provider::ProviderFactory).to receive(:get) { provider_double }
|
||||
allow(stubbed_connector).to receive(:users_names) { %w(root) }
|
||||
allow(stubbed_connector).to receive(:available_images) { %w(image) }
|
||||
allow(stubbed_connector).to receive(:stack_templates) { [build(:stack_template_ec2, id: 'template')] }
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy env'
|
||||
it_behaves_like 'cloud deploy env'
|
||||
end
|
||||
|
||||
@ -5,7 +5,21 @@ require_relative 'shared_cloud_deploy_env_specs'
|
||||
RSpec.describe Devops::Model::DeployEnvOpenstack, type: :model do
|
||||
let(:env) { build(:deploy_env_openstack) }
|
||||
|
||||
describe 'it inherits from cloud deploy_env', stubbed_env_validators: true, stubbed_logger: true do
|
||||
describe 'it inherits from cloud deploy_env', stubbed_connector: true, stubbed_logger: true do
|
||||
before do
|
||||
provider_double = instance_double('Provider::Openstack',
|
||||
flavors: [{'id' => 'flavor'}],
|
||||
networks: [{'default' => {'vpcId' => 'foo'}}],
|
||||
groups: {'default' => nil},
|
||||
images: [{'id' => 'image'}]
|
||||
)
|
||||
allow(Provider::ProviderFactory).to receive(:providers) { %w(openstack) }
|
||||
allow(Provider::ProviderFactory).to receive(:get) { provider_double }
|
||||
allow(stubbed_connector).to receive(:users_names) { %w(root) }
|
||||
allow(stubbed_connector).to receive(:available_images) { %w(image) }
|
||||
allow(stubbed_connector).to receive(:stack_templates) { [build(:stack_template_openstack, id: 'template')] }
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy env'
|
||||
it_behaves_like 'cloud deploy env'
|
||||
end
|
||||
|
||||
@ -5,10 +5,10 @@ RSpec.describe Devops::Model::DeployEnvStatic, type: :model do
|
||||
let(:env) { build(:deploy_env_static) }
|
||||
|
||||
|
||||
describe 'it inherits from deploy env', stubbed_logger: true do
|
||||
describe 'it inherits from deploy env', stubbed_logger: true, stubbed_connector: true do
|
||||
before do
|
||||
allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(static))
|
||||
allow_any_instance_of(Validators::Helpers::Users).to receive(:available_users).and_return(['root'])
|
||||
allow(stubbed_connector).to receive(:users_names) { %w(root) }
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy env'
|
||||
|
||||
@ -3,7 +3,21 @@ require 'db/mongo/models/project'
|
||||
RSpec.describe Devops::Model::Project, type: :model do
|
||||
let(:project) { build(:project) }
|
||||
|
||||
describe 'validation rules:', stubbed_env_validators: true, stubbed_logger: true do
|
||||
describe 'validation rules:', stubbed_connector: true, stubbed_logger: true do
|
||||
before do
|
||||
provider_double = instance_double('Provider::Ec2',
|
||||
flavors: [{'id' => 'flavor'}],
|
||||
networks: [{'default' => {'vpcId' => 'foo'}}],
|
||||
groups: {'default' => nil},
|
||||
images: [{'id' => 'image'}]
|
||||
)
|
||||
allow(Provider::ProviderFactory).to receive(:providers) { %w(ec2) }
|
||||
allow(Provider::ProviderFactory).to receive(:get) { provider_double }
|
||||
allow(stubbed_connector).to receive(:users_names) { %w(root) }
|
||||
allow(stubbed_connector).to receive(:available_images) { %w(image) }
|
||||
allow(stubbed_connector).to receive(:stack_templates) { [build(:stack_template_ec2, id: 'template')] }
|
||||
end
|
||||
|
||||
include_examples 'field type validation', :id, :not_nil, :non_empty_string
|
||||
include_examples 'field type validation', :deploy_envs, :not_nil, :non_empty_array
|
||||
include_examples 'field type validation', :description, :maybe_nil, :maybe_empty_string
|
||||
@ -23,6 +37,23 @@ RSpec.describe Devops::Model::Project, type: :model do
|
||||
project = build(:project, with_deploy_env_identifiers: ['foo', nil])
|
||||
expect(project).not_to be_valid
|
||||
end
|
||||
|
||||
describe 'components validation' do
|
||||
it 'is valid with components with filenames' do
|
||||
project.components = {'foo' => {'filename' => 'bar'}}
|
||||
expect{project.validate_components}.not_to raise_error
|
||||
end
|
||||
|
||||
it "isn't valid if components isn't a hash" do
|
||||
project.components = []
|
||||
expect{project.validate_components}.to raise_error InvalidRecord
|
||||
end
|
||||
|
||||
it "raises InvalidRecord if one of componentsц hasn't filename" do
|
||||
project.components = {'foo' => {}}
|
||||
expect{project.validate_components}.to raise_error InvalidRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.fields' do
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
require 'spec_helper'
|
||||
|
||||
# не пытайся выделить в shared_specs, фигня выйдет
|
||||
RSpec.describe Devops::Model::OpenstackProviderAccount, type: :model do
|
||||
let(:provider_account) { build(:openstack_provider_account) }
|
||||
|
||||
|
||||
@ -44,6 +44,14 @@ RSpec.describe Devops::Model::Server, type: :model do
|
||||
end
|
||||
end
|
||||
|
||||
describe '.build_from_bson' do
|
||||
it 'takes a hash and returns instance of Server model' do
|
||||
model = described_class.build_from_bson('id' => 'foo')
|
||||
expect(model).to be_an_instance_of(described_class)
|
||||
expect(model.id).to eq 'foo'
|
||||
end
|
||||
end
|
||||
|
||||
it '#to_hash_without_id returns not nil fields' do
|
||||
server = described_class.new('run_list' => [], 'project' => 'asd')
|
||||
expect(server.to_hash_without_id.keys).to match_array(%w(run_list project))
|
||||
|
||||
@ -6,21 +6,23 @@ RSpec.describe Devops::Model::StackTemplateEc2, type: :model do
|
||||
|
||||
before do
|
||||
allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(ec2))
|
||||
allow_any_instance_of(Devops::Model::StackTemplateEc2).to receive_message_chain('provider_instance.validate_stack_template') { true }
|
||||
allow_any_instance_of(Devops::Model::StackTemplateEc2).to receive_message_chain('provider_instance.store_stack_template') { {'url' => nil} }
|
||||
provider_double = instance_double('Provider::Ec2',
|
||||
validate_stack_template: true,
|
||||
store_stack_template: {'url' => 'template_url'}
|
||||
)
|
||||
allow(Provider::ProviderFactory).to receive(:get) { provider_double }
|
||||
end
|
||||
|
||||
it_behaves_like 'stack template'
|
||||
|
||||
it 'uploads file to S3' do
|
||||
expect_any_instance_of(Devops::Model::StackTemplateEc2).to receive_message_chain('provider_instance.store_stack_template')
|
||||
params = {
|
||||
'id' => 'foo',
|
||||
result = described_class.create('id' => 'foo',
|
||||
'template_body' => '{}',
|
||||
'owner' => 'root',
|
||||
'provider' => 'ec2'
|
||||
}
|
||||
expect(described_class.create(params)).to be_an_instance_of(described_class)
|
||||
)
|
||||
expect(result).to be_an_instance_of(described_class)
|
||||
expect(result.template_url).to eq 'template_url'
|
||||
end
|
||||
|
||||
end
|
||||
@ -79,13 +79,25 @@ RSpec.describe Devops::Model::User, type: :model do
|
||||
end
|
||||
end
|
||||
|
||||
describe '.build_from_bson' do
|
||||
it 'builds User model from given hash and assigns id' do
|
||||
model = described_class.build_from_bson('_id' => 'foo', 'username' => 'not shown', 'email' => 'baz')
|
||||
expect(model.id).to eq 'foo'
|
||||
expect(model.email).to eq 'baz'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#check_privileges' do
|
||||
it "raises InvalidPrivileges if user hasn't specified privilege" do
|
||||
expect { user.check_privileges('key', 'w') }.to raise_error(InvalidPrivileges)
|
||||
end
|
||||
|
||||
it 'does nothing is user has specified privilege' do
|
||||
user.check_privileges('key', 'r')
|
||||
expect{user.check_privileges('key', 'r')}.not_to raise_error
|
||||
end
|
||||
|
||||
it 'raises InvalidPrivileges if given privelege is wrong' do
|
||||
expect{user.check_privileges('key', 't')}.to raise_error InvalidPrivileges
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
RSpec.shared_context 'stubbed calls to connector', stubbed_connector: true do
|
||||
let(:stubbed_connector) { double() }
|
||||
let(:stubbed_connector) { instance_double(MongoConnector) }
|
||||
before do
|
||||
allow(Devops::Db).to receive(:connector) { stubbed_connector }
|
||||
end
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
RSpec.shared_context 'stubbed calls to connector in env validators', stubbed_env_validators: true do
|
||||
before do
|
||||
allow(Provider::ProviderFactory).to receive(:providers).and_return(%w(ec2 openstack))
|
||||
allow_any_instance_of(env_class).to receive_message_chain('provider_instance.flavors').and_return [{'id' => 'flavor'}]
|
||||
allow_any_instance_of(Validators::Helpers::Users).to receive(:available_users).and_return(['root'])
|
||||
allow_any_instance_of(Validators::DeployEnv::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}])
|
||||
allow_any_instance_of(Validators::FieldValidator::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}])
|
||||
# allow_any_instance_of(Validators::DeployEnv::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}])
|
||||
# allow_any_instance_of(Validators::FieldValidator::Flavor).to receive(:available_flavors).and_return([{'id' => 'flavor'}])
|
||||
allow_any_instance_of(Validators::DeployEnv::Groups).to receive(:available_groups).and_return(['default'])
|
||||
allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}])
|
||||
allow_any_instance_of(Validators::DeployEnv::Image).to receive(:available_images).and_return([{'id' => 'image'}])
|
||||
|
||||
6
devops-service/spec/shared_contexts/stubbed_knife.rb
Normal file
6
devops-service/spec/shared_contexts/stubbed_knife.rb
Normal file
@ -0,0 +1,6 @@
|
||||
RSpec.shared_context 'stubbed calls to KnifeFactory.instance', stubbed_knife: true do
|
||||
let(:stubbed_knife) { instance_double(KnifeCommands) }
|
||||
before do
|
||||
allow(KnifeFactory).to receive(:instance) { stubbed_knife }
|
||||
end
|
||||
end
|
||||
@ -3,5 +3,6 @@ RSpec.shared_context 'stubbed calls to logger', stubbed_logger: true do
|
||||
allow(DevopsLogger).to receive_message_chain('logger.debug')
|
||||
allow(DevopsLogger).to receive_message_chain('logger.info')
|
||||
allow(DevopsLogger).to receive_message_chain('logger.error')
|
||||
allow(DevopsLogger).to receive_message_chain('logger.warn')
|
||||
end
|
||||
end
|
||||
@ -4,32 +4,53 @@ require 'factory_girl'
|
||||
require 'active_support/core_ext/hash/indifferent_access'
|
||||
require 'active_support/inflector'
|
||||
|
||||
# setup load_path and require support files
|
||||
root = File.join(File.dirname(__FILE__), "..")
|
||||
$LOAD_PATH.push root unless $LOAD_PATH.include? root
|
||||
|
||||
# suppress output
|
||||
original_stdout = $stdout
|
||||
$stdout = File.open(File::NULL, "w")
|
||||
|
||||
Dir[("./spec/support/**/*.rb")].each { |f| require f }
|
||||
Dir[("./spec/shared_contexts/**/*.rb")].each { |f| require f }
|
||||
|
||||
# Factory girl configuration
|
||||
FactoryGirl.define do
|
||||
# do not try to persist, but raise validation errors
|
||||
to_create { |model| model.validate! }
|
||||
def suppress_output!
|
||||
original_stdout = $stdout
|
||||
$stdout = File.open(File::NULL, "w")
|
||||
RSpec.configure do |config|
|
||||
config.after(:all) do
|
||||
$stdout = original_stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
FactoryGirl.find_definitions
|
||||
|
||||
def check_coverage
|
||||
require 'simplecov'
|
||||
if ENV['JENKINS']
|
||||
require 'simplecov-rcov'
|
||||
SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
|
||||
end
|
||||
SimpleCov.start do
|
||||
add_filter { |src| src.filename =~ /spec\// }
|
||||
end
|
||||
end
|
||||
|
||||
def require_support_files
|
||||
root = File.join(File.dirname(__FILE__), "..")
|
||||
$LOAD_PATH.push root unless $LOAD_PATH.include?(root)
|
||||
Dir[("#{root}/spec/support/**/*.rb")].each { |f| require f }
|
||||
Dir[("#{root}/spec/shared_contexts/**/*.rb")].each { |f| require f }
|
||||
end
|
||||
|
||||
def setup_factory_girl
|
||||
FactoryGirl.define do
|
||||
# do not persist, but raise validation errors
|
||||
to_create { |model| model.validate! }
|
||||
end
|
||||
FactoryGirl.find_definitions
|
||||
RSpec.configure { |config| config.include FactoryGirl::Syntax::Methods }
|
||||
end
|
||||
|
||||
|
||||
# extra configuration
|
||||
suppress_output!
|
||||
check_coverage if ENV['COVERAGE']
|
||||
require_support_files
|
||||
setup_factory_girl
|
||||
|
||||
|
||||
# RSpec configuration
|
||||
RSpec.configure do |config|
|
||||
config.include FactoryGirl::Syntax::Methods
|
||||
|
||||
config.after(:all) do
|
||||
$stdout = original_stdout
|
||||
end
|
||||
|
||||
# rspec-expectations config goes here. You can use an alternate
|
||||
# assertion/expectation library such as wrong or the stdlib/minitest
|
||||
# assertions if you prefer.
|
||||
|
||||
6
devops-service/spec/support/instance_variable_matcher.rb
Normal file
6
devops-service/spec/support/instance_variable_matcher.rb
Normal file
@ -0,0 +1,6 @@
|
||||
RSpec::Matchers.define :have_instance_variable_value do |name, value|
|
||||
match do |actual|
|
||||
actual.instance_variable_defined?("@#{name}") &&
|
||||
actual.instance_variable_get("@#{name}") == value
|
||||
end
|
||||
end
|
||||
@ -2,7 +2,15 @@ require 'yaml'
|
||||
|
||||
module SpecSupport
|
||||
ROOT = File.join(__dir__, '../../')
|
||||
BLANK_FILE = File.join(ROOT, 'spec/support/blank_file')
|
||||
|
||||
def self.blank_file
|
||||
File.join(ROOT, 'spec/support/templates/blank_file')
|
||||
end
|
||||
|
||||
# for specs which write files
|
||||
def self.tmp_dir
|
||||
File.join(ROOT, 'spec/support/tmp/')
|
||||
end
|
||||
|
||||
def self.db_params
|
||||
@db_params ||= begin
|
||||
@ -32,4 +40,8 @@ module SpecSupport
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.root
|
||||
File.join(__dir__, '../../')
|
||||
end
|
||||
end
|
||||
0
devops-service/spec/support/tmp/.gitkeep
Normal file
0
devops-service/spec/support/tmp/.gitkeep
Normal file
37
devops-service/workers/delete_expired_server_worker.rb
Normal file
37
devops-service/workers/delete_expired_server_worker.rb
Normal file
@ -0,0 +1,37 @@
|
||||
require "db/mongo/models/server"
|
||||
require "db/mongo/models/report"
|
||||
require "lib/executors/server_executor"
|
||||
require "workers/worker"
|
||||
|
||||
class DeleteExpiredServerWorker < Worker
|
||||
|
||||
def perform(options)
|
||||
chef_node_name = options.fetch('server_chef_node_name')
|
||||
|
||||
call() do |out, file|
|
||||
out.puts "Expire server '#{chef_node_name}'."
|
||||
server = mongo.server_by_chef_node_name(chef_node_name)
|
||||
report = save_report(file, server)
|
||||
|
||||
e = Devops::Executor::ServerExecutor.new(server, out)
|
||||
e.report = report
|
||||
e.delete_server
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def save_report(file, server)
|
||||
report = Devops::Model::Report.new(
|
||||
"file" => file,
|
||||
"_id" => jid,
|
||||
"created_by" => 'SYSTEM',
|
||||
"project" => server.project,
|
||||
"deploy_env" => server.deploy_env,
|
||||
"type" => Devops::Model::Report::EXPIRE_SERVER_TYPE
|
||||
)
|
||||
mongo.save_report(report)
|
||||
report
|
||||
end
|
||||
|
||||
end
|
||||
@ -1,18 +1,21 @@
|
||||
require "db/mongo/models/server"
|
||||
require "db/mongo/models/report"
|
||||
require "lib/executors/server_executor"
|
||||
require "workers/worker"
|
||||
|
||||
class DeleteServerWorker < Worker
|
||||
|
||||
# options should contain 'server_id'
|
||||
def perform(options)
|
||||
chef_node_name = options.fetch('server_chef_node_name')
|
||||
puts "Expire server '#{chef_node_name}'."
|
||||
server_id = options.fetch('server_id')
|
||||
current_user = options.fetch('current_user')
|
||||
|
||||
call() do |out, file|
|
||||
server = mongo.server_by_chef_node_name(chef_node_name)
|
||||
report = save_report(file, server)
|
||||
out.puts "Deleting server with id #{server_id}" and out.flush
|
||||
@server = mongo.server_by_instance_id(server_id)
|
||||
report = save_report(file, current_user)
|
||||
|
||||
e = Devops::Executor::ServerExecutor.new(server, out)
|
||||
e = Devops::Executor::ServerExecutor.new(@server, out)
|
||||
e.report = report
|
||||
e.delete_server
|
||||
end
|
||||
@ -20,13 +23,13 @@ class DeleteServerWorker < Worker
|
||||
|
||||
private
|
||||
|
||||
def save_report(file, server)
|
||||
def save_report(file, current_user)
|
||||
report = Devops::Model::Report.new(
|
||||
"file" => file,
|
||||
"_id" => jid,
|
||||
"created_by" => 'SYSTEM',
|
||||
"project" => server.project,
|
||||
"deploy_env" => server.deploy_env,
|
||||
"created_by" => current_user,
|
||||
"project" => @server.project,
|
||||
"deploy_env" => @server.deploy_env,
|
||||
"type" => Devops::Model::Report::DELETE_SERVER_TYPE
|
||||
)
|
||||
mongo.save_report(report)
|
||||
|
||||
@ -5,6 +5,8 @@ require File.join(root, "deploy_worker")
|
||||
require File.join(root, "bootstrap_worker")
|
||||
require File.join(root, "project_test_worker")
|
||||
require File.join(root, "stack_bootstrap_worker")
|
||||
require File.join(root, "delete_server_worker")
|
||||
require File.join(root, "delete_expired_server_worker")
|
||||
|
||||
config = {}
|
||||
#require File.join(root, "../proxy")
|
||||
|
||||
@ -130,7 +130,7 @@ class StackBootstrapWorker < Worker
|
||||
|
||||
@out.puts
|
||||
results.each do |chef_node_name, code|
|
||||
human_readable_code = Devops::Executor::ServerExecutor.symbolic_result_code(code)
|
||||
human_readable_code = Devops::Executor::ServerExecutor.symbolic_error_code(code)
|
||||
@out.puts "Operation result for #{chef_node_name}: #{human_readable_code}"
|
||||
end
|
||||
|
||||
@ -144,7 +144,7 @@ class StackBootstrapWorker < Worker
|
||||
def errors_in_bootstrapping_present?(result_codes)
|
||||
bootstrap_error_codes = []
|
||||
[:server_bootstrap_fail, :server_not_in_chef_nodes, :server_bootstrap_unknown_error].each do |symbolic_code|
|
||||
bootstrap_error_codes << Devops::Executor::ServerExecutor.result_code(symbolic_code)
|
||||
bootstrap_error_codes << Devops::Executor::ServerExecutor.error_code(symbolic_code)
|
||||
end
|
||||
|
||||
(bootstrap_error_codes & result_codes).size > 0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user