Merge branch 'CID-449-fix_expiration' into bug_fix
This commit is contained in:
commit
2bc04cd377
@ -20,13 +20,16 @@ class Deploy < Handler
|
|||||||
end
|
end
|
||||||
|
|
||||||
def deploy_handler args
|
def deploy_handler args
|
||||||
tags = options[:tags]
|
|
||||||
names = args[1..-1]
|
names = args[1..-1]
|
||||||
if names.empty?
|
if names.empty?
|
||||||
@options_parser.invalid_deploy_command
|
@options_parser.invalid_deploy_command
|
||||||
abort()
|
abort()
|
||||||
end
|
end
|
||||||
job_ids = post("/deploy", :names => names, :tags => tags)
|
job_ids = post("/deploy",
|
||||||
|
names: names,
|
||||||
|
tags: options[:tags],
|
||||||
|
named_task: options[:named_task]
|
||||||
|
)
|
||||||
reports_urls(job_ids)
|
reports_urls(job_ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -13,11 +13,12 @@ class DeployOptions < CommonOptions
|
|||||||
def deploy_options
|
def deploy_options
|
||||||
options do |parser, options|
|
options do |parser, options|
|
||||||
parser.banner << self.banner
|
parser.banner << self.banner
|
||||||
|
parser.resource_name = :deploy
|
||||||
|
|
||||||
parser.recognize_option_value(:tag, resource_name: :deploy, variable_name: 'TAG1,TAG2...') do |tags|
|
parser.recognize_option_value(:tag, variable: 'TAG1,TAG2...') do |tags|
|
||||||
options[:tags] = tags.split(",")
|
options[:tags] = tags.split(",")
|
||||||
end
|
end
|
||||||
|
parser.recognize_option_value(:named_task)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -314,6 +314,7 @@ en:
|
|||||||
descriptions:
|
descriptions:
|
||||||
deploy:
|
deploy:
|
||||||
tag: 'Tag names, comma separated list'
|
tag: 'Tag names, comma separated list'
|
||||||
|
named_task: Name of task to run
|
||||||
image:
|
image:
|
||||||
provider: Image provider
|
provider: Image provider
|
||||||
image_id: Image identifier
|
image_id: Image identifier
|
||||||
|
|||||||
@ -33,6 +33,7 @@ module Devops
|
|||||||
begin
|
begin
|
||||||
deploy_info = create_deploy_info(s, project, body["build_number"])
|
deploy_info = create_deploy_info(s, project, body["build_number"])
|
||||||
deploy_info["run_list"] = run_list if run_list
|
deploy_info["run_list"] = run_list if run_list
|
||||||
|
deploy_info["named_task"] = body["named_task"]
|
||||||
|
|
||||||
jid = Worker.start_async(DeployWorker,
|
jid = Worker.start_async(DeployWorker,
|
||||||
server_attrs: s.to_hash,
|
server_attrs: s.to_hash,
|
||||||
@ -117,6 +118,7 @@ module Devops
|
|||||||
@deploy_info_buf[buf_key] = project.deploy_info(deploy_env_model, build_number)
|
@deploy_info_buf[buf_key] = project.deploy_info(deploy_env_model, build_number)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -5,7 +5,6 @@ require "app/api2/parsers/project"
|
|||||||
require "lib/project/type/types_factory"
|
require "lib/project/type/types_factory"
|
||||||
require "lib/executors/server_executor"
|
require "lib/executors/server_executor"
|
||||||
require "workers/delete_server_worker"
|
require "workers/delete_server_worker"
|
||||||
|
|
||||||
require_relative "../helpers/version_2.rb"
|
require_relative "../helpers/version_2.rb"
|
||||||
require_relative "request_handler"
|
require_relative "request_handler"
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ module Devops
|
|||||||
build_number = check_string(r["build_number"], "Parameter 'build_number' should be a not empty string", true)
|
build_number = check_string(r["build_number"], "Parameter 'build_number' should be a not empty string", true)
|
||||||
rl = check_array(r["run_list"], "Parameter 'run_list' should be an array of string", String, true)
|
rl = check_array(r["run_list"], "Parameter 'run_list' should be an array of string", String, true)
|
||||||
Validators::Helpers::RunList.new(rl).validate! unless rl.nil?
|
Validators::Helpers::RunList.new(rl).validate! unless rl.nil?
|
||||||
|
check_string(r["named_task"], "Parameter 'named_task' should be a not empty string", true)
|
||||||
r
|
r
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -87,28 +87,12 @@ module Devops
|
|||||||
# - Content-Type: application/json
|
# - Content-Type: application/json
|
||||||
# - body :
|
# - body :
|
||||||
# {
|
# {
|
||||||
# "deploy_envs": [
|
# "run_list": [],
|
||||||
# {
|
# "description": "Description",
|
||||||
# "identifier": "dev",
|
# "named_tasks": [{
|
||||||
# "provider": "openstack",
|
# "name": "restart",
|
||||||
# "flavor": "m1.small",
|
# "run_list": ["role[restart_service]"]
|
||||||
# "image": "image id",
|
# }]
|
||||||
# "subnets": [
|
|
||||||
# "private"
|
|
||||||
# ],
|
|
||||||
# "groups": [
|
|
||||||
# "default"
|
|
||||||
# ],
|
|
||||||
# "users": [
|
|
||||||
# "user"
|
|
||||||
# ],
|
|
||||||
# "run_list": [
|
|
||||||
#
|
|
||||||
# ],
|
|
||||||
# "expires": null
|
|
||||||
# }
|
|
||||||
# ],
|
|
||||||
# "name": "project_1"
|
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
# * *Returns* :
|
# * *Returns* :
|
||||||
|
|||||||
@ -135,13 +135,16 @@ module Connectors
|
|||||||
end
|
end
|
||||||
|
|
||||||
def project_update id, params
|
def project_update id, params
|
||||||
keys = %w(run_list description)
|
params.keep_if {|k,v| permitted_project_fields.include?(k)}
|
||||||
params.delete_if{|k,v| !keys.include?(k)}
|
|
||||||
@collection.update({"_id" => id}, {'$set' => params })
|
@collection.update({"_id" => id}, {'$set' => params })
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def permitted_project_fields
|
||||||
|
%w(run_list description named_tasks)
|
||||||
|
end
|
||||||
|
|
||||||
def list(query={}, query_options={})
|
def list(query={}, query_options={})
|
||||||
@collection.find(query, query_options).to_a.map {|bson| model_from_bson(bson)}
|
@collection.find(query, query_options).to_a.map {|bson| model_from_bson(bson)}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -30,9 +30,6 @@ module Devops
|
|||||||
::Validators::FieldValidator::FieldType::String,
|
::Validators::FieldValidator::FieldType::String,
|
||||||
::Validators::FieldValidator::Expires]
|
::Validators::FieldValidator::Expires]
|
||||||
|
|
||||||
set_field_validators :chef_client_options, [::Validators::FieldValidator::Nil,
|
|
||||||
::Validators::FieldValidator::FieldType::String]
|
|
||||||
|
|
||||||
def initialize d={}
|
def initialize d={}
|
||||||
self.identifier = d["identifier"]
|
self.identifier = d["identifier"]
|
||||||
set_provider(d)
|
set_provider(d)
|
||||||
@ -41,7 +38,6 @@ module Devops
|
|||||||
self.expires = d["expires"]
|
self.expires = d["expires"]
|
||||||
b = d["users"] || []
|
b = d["users"] || []
|
||||||
self.users = b.uniq
|
self.users = b.uniq
|
||||||
self.chef_client_options = d["chef_client_options"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_hash
|
def to_hash
|
||||||
@ -49,8 +45,7 @@ module Devops
|
|||||||
"identifier" => self.identifier,
|
"identifier" => self.identifier,
|
||||||
"run_list" => self.run_list,
|
"run_list" => self.run_list,
|
||||||
"expires" => self.expires,
|
"expires" => self.expires,
|
||||||
"users" => self.users,
|
"users" => self.users
|
||||||
"chef_client_options" => self.chef_client_options
|
|
||||||
}.merge(provider_hash)
|
}.merge(provider_hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -41,12 +41,23 @@ module Devops
|
|||||||
::Validators::FieldValidator::FieldType::Array,
|
::Validators::FieldValidator::FieldType::Array,
|
||||||
::Validators::FieldValidator::RunList]
|
::Validators::FieldValidator::RunList]
|
||||||
|
|
||||||
|
# should be an array of hashes like:
|
||||||
|
# {
|
||||||
|
# 'name' => 'restart',
|
||||||
|
# 'run_list' => [
|
||||||
|
# 'role[restart_service]'
|
||||||
|
# ]
|
||||||
|
# }
|
||||||
|
set_field_validators :named_tasks, [::Validators::FieldValidator::Nil,
|
||||||
|
::Validators::FieldValidator::FieldType::Array,
|
||||||
|
::Validators::FieldValidator::NamedTasks]
|
||||||
|
|
||||||
set_validators ::Validators::DeployEnv::RunList,
|
set_validators ::Validators::DeployEnv::RunList,
|
||||||
::Validators::DeployEnv::DeployEnvs
|
::Validators::DeployEnv::DeployEnvs
|
||||||
|
|
||||||
|
|
||||||
def self.fields
|
def self.fields
|
||||||
["deploy_envs", "type", "description"]
|
["deploy_envs", "type", "description", "named_tasks"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize p={}
|
def initialize p={}
|
||||||
@ -56,6 +67,7 @@ module Devops
|
|||||||
self.description = p["description"]
|
self.description = p["description"]
|
||||||
self.archived = p["archived"] || false
|
self.archived = p["archived"] || false
|
||||||
self.run_list = p["run_list"] || []
|
self.run_list = p["run_list"] || []
|
||||||
|
self.named_tasks = p["named_tasks"] || []
|
||||||
|
|
||||||
@handler = Devops::TypesFactory.type self.type
|
@handler = Devops::TypesFactory.type self.type
|
||||||
@handler.prepare(self)
|
@handler.prepare(self)
|
||||||
@ -174,6 +186,7 @@ module Devops
|
|||||||
h["archived"] = self.archived if self.archived
|
h["archived"] = self.archived if self.archived
|
||||||
h["description"] = self.description
|
h["description"] = self.description
|
||||||
h["run_list"] = self.run_list
|
h["run_list"] = self.run_list
|
||||||
|
h["named_tasks"] = self.named_tasks
|
||||||
if self.multi?
|
if self.multi?
|
||||||
h["type"] = MULTI_TYPE
|
h["type"] = MULTI_TYPE
|
||||||
end
|
end
|
||||||
|
|||||||
65
devops-service/db/validators/field_validators/named_tasks.rb
Normal file
65
devops-service/db/validators/field_validators/named_tasks.rb
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
require_relative "base"
|
||||||
|
|
||||||
|
module Validators
|
||||||
|
module FieldValidator
|
||||||
|
class NamedTasks < Base
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
@value.each do |named_task|
|
||||||
|
break unless check_name!(named_task)
|
||||||
|
break unless check_run_list!(named_task)
|
||||||
|
break unless check_additional_keys!(named_task)
|
||||||
|
end
|
||||||
|
@message.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def message
|
||||||
|
@message
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def check_name!(task)
|
||||||
|
if task.key?('name')
|
||||||
|
true
|
||||||
|
else
|
||||||
|
@message = "One of tasks doesn't have a name"
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_run_list!(task)
|
||||||
|
if !task.key?('run_list')
|
||||||
|
@message = "Task #{task['name']} doesn't have run_list"
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if !task['run_list'].is_a?(Array)
|
||||||
|
@message = "Run list of #{task['name']} isn't an array"
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
wrong_elements = task['run_list'].select do |element|
|
||||||
|
Validators::Helpers::RunList::RUN_LIST_REGEX.match(element).nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
if wrong_elements.empty?
|
||||||
|
true
|
||||||
|
else
|
||||||
|
@message = "Invalid run list elements: '#{wrong_elements.join(', ')}' for task #{task['name']}."
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_additional_keys!(task)
|
||||||
|
if task.keys.length > 2
|
||||||
|
@message = "Task hash should contain only name and run_list"
|
||||||
|
false
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -9,7 +9,7 @@ module Devops
|
|||||||
|
|
||||||
def schedule_expiration!
|
def schedule_expiration!
|
||||||
return unless @expires
|
return unless @expires
|
||||||
DeleteExpiredServerWorker.perform_in(interval_in_seconds, server_chef_node_name: @server.chef_node_name)
|
DeleteExpiredServerWorker.perform_in(interval_in_seconds, server_id: @server.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def interval_in_seconds
|
def interval_in_seconds
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
require "lib/knife/knife_factory"
|
require "lib/knife/knife_factory"
|
||||||
require "lib/executors/expiration_scheduler"
|
require "lib/executors/expiration_scheduler"
|
||||||
|
require "lib/puts_and_flush"
|
||||||
require "hooks"
|
require "hooks"
|
||||||
require 'net/ssh'
|
require 'net/ssh'
|
||||||
|
|
||||||
@ -7,6 +8,7 @@ module Devops
|
|||||||
module Executor
|
module Executor
|
||||||
class ServerExecutor
|
class ServerExecutor
|
||||||
include Hooks
|
include Hooks
|
||||||
|
include PutsAndFlush
|
||||||
|
|
||||||
ERROR_CODES = {
|
ERROR_CODES = {
|
||||||
server_bootstrap_fail: 2,
|
server_bootstrap_fail: 2,
|
||||||
@ -41,6 +43,7 @@ module Devops
|
|||||||
|
|
||||||
|
|
||||||
attr_accessor :server, :deploy_env, :report, :project
|
attr_accessor :server, :deploy_env, :report, :project
|
||||||
|
attr_reader :out
|
||||||
|
|
||||||
def initialize server, out, options={}
|
def initialize server, out, options={}
|
||||||
if server
|
if server
|
||||||
@ -394,8 +397,11 @@ module Devops
|
|||||||
@out.flush
|
@out.flush
|
||||||
cmd << " -j http://#{DevopsConfig.config[:address]}:#{DevopsConfig.config[:port]}/#{DevopsConfig.config[:url_prefix]}/v2.0/deploy/data/#{file}"
|
cmd << " -j http://#{DevopsConfig.config[:address]}:#{DevopsConfig.config[:port]}/#{DevopsConfig.config[:url_prefix]}/v2.0/deploy/data/#{file}"
|
||||||
else
|
else
|
||||||
if @deploy_env.chef_client_options && !@deploy_env.chef_client_options.empty?
|
if deploy_info['named_task'].present?
|
||||||
cmd << " #{@deploy_env.chef_client_options}"
|
named_task = @project.named_tasks.detect {|task| task['name'] == deploy_info['named_task']}
|
||||||
|
raise "Named task #{deploy_info['named_task']} doesn't exist." unless named_task
|
||||||
|
puts_and_flush "Using named task #{deploy_info['named_task']}."
|
||||||
|
cmd << " -o #{named_task['run_list'].join(',')}"
|
||||||
else
|
else
|
||||||
cmd << " -r #{deploy_info["run_list"].join(",")}" unless @server.stack.nil?
|
cmd << " -r #{deploy_info["run_list"].join(",")}" unless @server.stack.nil?
|
||||||
end
|
end
|
||||||
@ -503,15 +509,10 @@ module Devops
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def puts_and_flush(message)
|
|
||||||
@out.puts message
|
|
||||||
@out.flush
|
|
||||||
end
|
|
||||||
|
|
||||||
def schedule_expiration
|
def schedule_expiration
|
||||||
if @deploy_env.expires
|
if @deploy_env.expires
|
||||||
puts_and_flush "Planning expiration in #{@deploy_env.expires}"
|
job_id = ExpirationScheduler.new(@deploy_env.expires, @server).schedule_expiration!
|
||||||
ExpirationScheduler.new(@deploy_env.expires, @server).schedule_expiration!
|
puts_and_flush "Planning expiration in #{@deploy_env.expires}, job_id: #{job_id}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ RSpec.describe Devops::Executor::ExpirationScheduler do
|
|||||||
|
|
||||||
describe '#schedule_expiration!' do
|
describe '#schedule_expiration!' do
|
||||||
it 'schedules server deleting at given time' do
|
it 'schedules server deleting at given time' do
|
||||||
expect(DeleteExpiredServerWorker).to receive(:perform_in).with(120, server_chef_node_name: 'chef_node_name')
|
expect(DeleteExpiredServerWorker).to receive(:perform_in).with(120, server_id: server.id)
|
||||||
described_class.new('2m', server).schedule_expiration!
|
described_class.new('2m', server).schedule_expiration!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -140,7 +140,7 @@ RSpec.describe Devops::Executor::ServerExecutor, type: :executor, stubbed_connec
|
|||||||
it 'schedules expiration for server' do
|
it 'schedules expiration for server' do
|
||||||
deploy_env.expires = '2m'
|
deploy_env.expires = '2m'
|
||||||
allow(DeleteExpiredServerWorker).to receive(:perform_in)
|
allow(DeleteExpiredServerWorker).to receive(:perform_in)
|
||||||
expect(DeleteExpiredServerWorker).to receive(:perform_in).with(120, {server_chef_node_name: 'node_name'})
|
expect(DeleteExpiredServerWorker).to receive(:perform_in).with(120, hash_including(:server_id))
|
||||||
create_server
|
create_server
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -585,15 +585,10 @@ RSpec.describe Devops::Executor::ServerExecutor, type: :executor, stubbed_connec
|
|||||||
deploy_server
|
deploy_server
|
||||||
end
|
end
|
||||||
|
|
||||||
it "uses deploy_env's chef_client_options if they are set" do
|
it "uses run list from named_task if it's set" do
|
||||||
deploy_env.chef_client_options = '-r role'
|
project.named_tasks = [{'name' => 'foo', 'run_list' => ['role[backend]', 'role[frontend]']}]
|
||||||
expect(stubbed_knife).to receive(:ssh_stream).with(anything, 'chef-client --no-color -r role', any_args)
|
deploy_info['named_task'] = 'foo'
|
||||||
deploy_server
|
expect(stubbed_knife).to receive(:ssh_stream).with(anything, 'chef-client --no-color -o role[backend],role[frontend]', any_args)
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't use deploy_env's chef_client_options if it's blank string" do
|
|
||||||
deploy_env.chef_client_options = ''
|
|
||||||
expect(stubbed_knife).to receive(:ssh_stream).with(anything, 'chef-client --no-color', any_args)
|
|
||||||
deploy_server
|
deploy_server
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -10,7 +10,6 @@ RSpec.shared_examples 'deploy env' do
|
|||||||
include_examples 'field type validation', :run_list, :not_nil, :maybe_empty_array, :run_list, :field_validator
|
include_examples 'field type validation', :run_list, :not_nil, :maybe_empty_array, :run_list, :field_validator
|
||||||
include_examples 'field type validation', :users, :not_nil, :maybe_empty_array, :field_validator
|
include_examples 'field type validation', :users, :not_nil, :maybe_empty_array, :field_validator
|
||||||
include_examples 'field type validation', :expires, :maybe_nil, :non_empty_string, :field_validator
|
include_examples 'field type validation', :expires, :maybe_nil, :non_empty_string, :field_validator
|
||||||
include_examples 'field type validation', :chef_client_options, :maybe_nil, :maybe_empty_string, :field_validator
|
|
||||||
|
|
||||||
it 'should be valid only with all users available' do
|
it 'should be valid only with all users available' do
|
||||||
expect(build(validated_model_name, users: ['root'])).to be_valid
|
expect(build(validated_model_name, users: ['root'])).to be_valid
|
||||||
|
|||||||
@ -22,20 +22,33 @@ RSpec.describe Devops::Model::Project, type: :model do
|
|||||||
include_examples 'field type validation', :deploy_envs, :not_nil, :non_empty_array
|
include_examples 'field type validation', :deploy_envs, :not_nil, :non_empty_array
|
||||||
include_examples 'field type validation', :description, :maybe_nil, :maybe_empty_string
|
include_examples 'field type validation', :description, :maybe_nil, :maybe_empty_string
|
||||||
include_examples 'field type validation', :run_list, :not_nil, :maybe_empty_array, :run_list
|
include_examples 'field type validation', :run_list, :not_nil, :maybe_empty_array, :run_list
|
||||||
|
include_examples 'field type validation', :named_tasks, :maybe_nil, :maybe_empty_array
|
||||||
|
|
||||||
it "isn't valid when has envs with same identifier" do
|
it "isn't valid when has envs with same identifier" do
|
||||||
project = build(:project, with_deploy_env_identifiers: %w(foo foo))
|
expect(build(:project, with_deploy_env_identifiers: %w(foo foo))).not_to be_valid
|
||||||
expect(project).not_to be_valid
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is valid when all envs have uniq identifiers" do
|
it "is valid when all envs have uniq identifiers" do
|
||||||
project = build(:project, with_deploy_env_identifiers: %w(foo bar))
|
expect(build(:project, with_deploy_env_identifiers: %w(foo bar))).to be_valid
|
||||||
expect(project).to be_valid
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "isn't valid when at least one of envs isn't valid" do
|
it "isn't valid when at least one of envs isn't valid" do
|
||||||
project = build(:project, with_deploy_env_identifiers: ['foo', nil])
|
expect(build(:project, with_deploy_env_identifiers: ['foo', nil])).not_to be_valid
|
||||||
expect(project).not_to be_valid
|
end
|
||||||
|
|
||||||
|
describe 'named_tasks validation' do
|
||||||
|
it 'is valid with array of hashes like {"name" => "foo", "run_list" => ["role[bar]"]}' do
|
||||||
|
expect(build(:project, named_tasks: [{"name" => "foo", "run_list" => ["role[bar]"]}] )).to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
it "isn't valid with array of hashes with invalid run_list elements" do
|
||||||
|
expect(build(:project, named_tasks: [{"name" => "foo", "run_list" => ["bar"]}] )).not_to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
it "isn't valid with array of hashes without name or run_list" do
|
||||||
|
expect(build(:project, named_tasks: [{run_list: ["role[bar]"]}] )).not_to be_valid
|
||||||
|
expect(build(:project, named_tasks: [{"name" => "foo"}] )).not_to be_valid
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'components validation' do
|
describe 'components validation' do
|
||||||
@ -58,7 +71,7 @@ RSpec.describe Devops::Model::Project, type: :model do
|
|||||||
|
|
||||||
describe '.fields' do
|
describe '.fields' do
|
||||||
subject { described_class.fields }
|
subject { described_class.fields }
|
||||||
it { should eq %w(deploy_envs type description) }
|
it { should eq %w(deploy_envs type description named_tasks) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#initialize' do
|
describe '#initialize' do
|
||||||
@ -286,8 +299,8 @@ RSpec.describe Devops::Model::Project, type: :model do
|
|||||||
expect(subject['deploy_envs']).to be_an_array_of(Hash)
|
expect(subject['deploy_envs']).to be_an_array_of(Hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'also contains descriptions and run_list' do
|
it 'also contains descriptions, run_list and named_tasks' do
|
||||||
expect(subject).to include('description', 'run_list')
|
expect(subject).to include('description', 'run_list', 'named_tasks')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'contains archived key if project is archived' do
|
it 'contains archived key if project is archived' do
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
require 'workers/stack_bootstrap/chef_node_name_builder'
|
require 'workers/stack_bootstrap/chef_node_name_builder'
|
||||||
RSpec.describe ChefNodeNameBuilder do
|
RSpec.describe ChefNodeNameBuilder do
|
||||||
# real response
|
# test with real response to ensure it is processed correctly
|
||||||
let(:server_info) do
|
let(:server_info) do
|
||||||
{
|
{
|
||||||
"name"=>"stack-achuchkalov-aws-test-1455976199-master01",
|
"name"=>"stack-achuchkalov-aws-test-1455976199-master01",
|
||||||
@ -21,33 +21,26 @@ RSpec.describe ChefNodeNameBuilder do
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
let(:project) { build(:project, id: 'proj', with_deploy_env_identifiers: %w(dev)) }
|
let(:node_name_builder) {
|
||||||
let(:env) { project.deploy_env('dev') }
|
ChefNodeNameBuilder.new(
|
||||||
let(:build_node_name) {
|
provider_server_info: server_info,
|
||||||
described_class.new(server_info, project, env).build_node_name
|
project_id: 'proj',
|
||||||
|
env_id: 'dev'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
let(:build_node_name) { node_name_builder.build_node_name!({}) }
|
||||||
|
|
||||||
def set_mask(mask)
|
def set_mask(mask)
|
||||||
server_info['tags']['cid:node-name-mask'] = mask
|
server_info['tags']['cid:node-name-mask'] = mask
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#build_node_name' do
|
describe '#build_node_name' do
|
||||||
it 'uses default mask ("$project-$cfname-$env")' do
|
it 'uses default mask (":project-:instancename-:env")' do
|
||||||
expect(build_node_name).to eq 'proj-master01-dev'
|
expect(build_node_name).to eq 'proj-master01-dev'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'substitutes $project, $env, $instanceid and $cfname' do
|
it 'substitutes :project, :env, :instanceid and :instancename' do
|
||||||
set_mask('$project/$env/$instanceid/$cfname')
|
set_mask(':project/:env/:instanceid/:instancename')
|
||||||
expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'substitutes $time' do
|
|
||||||
set_mask('$project-$time')
|
|
||||||
expect(build_node_name).to match /proj-\d+/
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'substitutes :project, :env, :instanceid and :cfname' do
|
|
||||||
set_mask(':project/:env/:instanceid/:cfname')
|
|
||||||
expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01'
|
expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01'
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -56,14 +49,24 @@ RSpec.describe ChefNodeNameBuilder do
|
|||||||
expect(build_node_name).to match /proj-\d+/
|
expect(build_node_name).to match /proj-\d+/
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'works with both colon and dollar variables' do
|
describe 'substitutes incrementers variables :increment-groupname: with numbers depending on @incrementers_values param' do
|
||||||
set_mask('$project/$env/:instanceid/:cfname')
|
it 'starts with 01 for empty hash' do
|
||||||
expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01'
|
set_mask('node-:increment-slave:')
|
||||||
|
expect(node_name_builder.build_node_name!({})).to eq 'node-01'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "continues with next values if hash isn't empty" do
|
||||||
|
set_mask('node-:increment-slave:')
|
||||||
|
expect(node_name_builder.build_node_name!({'slave' => nil})).to eq 'node-01'
|
||||||
|
expect(node_name_builder.build_node_name!({'slave' => 1})).to eq 'node-02'
|
||||||
|
expect(node_name_builder.build_node_name!({'slave' => 50})).to eq 'node-51'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'could substitute different incrementers at once' do
|
||||||
|
set_mask('node-:increment-slave:-:increment-master:')
|
||||||
|
expect(node_name_builder.build_node_name!({'slave' => 1, 'master' => 3})).to eq 'node-02-04'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'substitutes underscores to dashes' do
|
|
||||||
server_info['tags']['Name'] = 'server_1'
|
|
||||||
expect(build_node_name).to match 'proj-server-1-dev'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -98,7 +98,7 @@ RSpec.describe StackServersPersister, stubbed_connector: true do
|
|||||||
persister.persist
|
persister.persist
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'build chef_node_name with default mask "$project-$cfname-$env"' do
|
it 'build chef_node_name with default mask ":project-:instancename-:env"' do
|
||||||
expect(stubbed_connector).to receive(:server_insert) do |server|
|
expect(stubbed_connector).to receive(:server_insert) do |server|
|
||||||
expect(server.chef_node_name).to eq 'name-server1-foo'
|
expect(server.chef_node_name).to eq 'name-server1-foo'
|
||||||
end
|
end
|
||||||
@ -106,12 +106,28 @@ RSpec.describe StackServersPersister, stubbed_connector: true do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "builds chef_node_name with custom mask if info['tags']['cid:node-name-mask'] exists" do
|
it "builds chef_node_name with custom mask if info['tags']['cid:node-name-mask'] exists" do
|
||||||
server_info_hash['tags']['cid:node-name-mask'] = '$project-$cfname-123'
|
server_info_hash['tags']['cid:node-name-mask'] = ':project-:instancename-123'
|
||||||
expect(stubbed_connector).to receive(:server_insert) do |server|
|
expect(stubbed_connector).to receive(:server_insert) do |server|
|
||||||
expect(server.chef_node_name).to eq 'name-server1-123'
|
expect(server.chef_node_name).to eq 'name-server1-123'
|
||||||
end
|
end
|
||||||
persister.persist
|
persister.persist
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'incremented variables' do
|
||||||
|
it 'substitutes :increment-groupid: with incrementing numbers' do
|
||||||
|
allow(provider).to receive(:stack_servers) {[
|
||||||
|
{'id' => 'server1', 'tags' => {'cid:node-name-mask' => 'node-:increment-group1:-dev'}, 'key_name' => 'key'},
|
||||||
|
{'id' => 'server1', 'tags' => {'cid:node-name-mask' => 'node-:increment-group1:-dev'}, 'key_name' => 'key'}
|
||||||
|
]}
|
||||||
|
expect(stubbed_connector).to receive(:server_insert) do |server|
|
||||||
|
expect(server.chef_node_name).to eq 'node-01-dev'
|
||||||
|
end.ordered
|
||||||
|
expect(stubbed_connector).to receive(:server_insert) do |server|
|
||||||
|
expect(server.chef_node_name).to eq 'node-02-dev'
|
||||||
|
end
|
||||||
|
persister.persist
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@ -6,12 +6,11 @@ require "workers/worker"
|
|||||||
class DeleteExpiredServerWorker < Worker
|
class DeleteExpiredServerWorker < Worker
|
||||||
|
|
||||||
def perform(options)
|
def perform(options)
|
||||||
chef_node_name = options.fetch('server_chef_node_name')
|
call do
|
||||||
|
server_id = options.fetch('server_id')
|
||||||
call() do |out, file|
|
puts_and_flush "Expire server '#{server_id}'."
|
||||||
out.puts "Expire server '#{chef_node_name}'."
|
server = mongo.server_by_instance_id(server_id)
|
||||||
server = mongo.server_by_chef_node_name(chef_node_name)
|
report = save_report(server)
|
||||||
report = save_report(file, server)
|
|
||||||
|
|
||||||
e = Devops::Executor::ServerExecutor.new(server, out)
|
e = Devops::Executor::ServerExecutor.new(server, out)
|
||||||
e.report = report
|
e.report = report
|
||||||
@ -21,17 +20,13 @@ class DeleteExpiredServerWorker < Worker
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def save_report(file, server)
|
def save_report(server)
|
||||||
report = Devops::Model::Report.new(
|
update_report(
|
||||||
"file" => file,
|
|
||||||
"_id" => jid,
|
|
||||||
"created_by" => 'SYSTEM',
|
"created_by" => 'SYSTEM',
|
||||||
"project" => server.project,
|
"project" => server.project,
|
||||||
"deploy_env" => server.deploy_env,
|
"deploy_env" => server.deploy_env,
|
||||||
"type" => Devops::Model::Report::EXPIRE_SERVER_TYPE
|
"type" => Devops::Model::Report::EXPIRE_SERVER_TYPE
|
||||||
)
|
)
|
||||||
mongo.save_report(report)
|
|
||||||
report
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,34 +1,64 @@
|
|||||||
class ChefNodeNameBuilder
|
# Builds node name from mask. Mask could be passed in server's +cid:node-name-mask+ tag,
|
||||||
DEFAULT_MASK = '$project-$cfname-$env'
|
# default mask is used otherwise. Mask is a string with possible several variables inserted, e.g.
|
||||||
|
# +':project/:env/:instanceid/:instancename/:increment-group1:'+
|
||||||
|
# Variables meanings:
|
||||||
|
# - +:project+ is replaced with project name (given in constructor)
|
||||||
|
# - +:env+ is replaced with env name (given in constructor)
|
||||||
|
# - +:time+ is replaced with current timestamp
|
||||||
|
# - +:instanceid+ is replaced with provider instance id (fetched from server info)
|
||||||
|
# - +:instancename+ is replaced with value of Name tag (fetched from server info)
|
||||||
|
# - +:increment-groupname:+ is replaced with incremented number tied to group name. There could be several groups in one stack.
|
||||||
|
# P.S. Colons are used instead of dollar signs, because stacks don't support dollar signs in tags (unlike EC2 instances),
|
||||||
|
# but it's convenient to set mask tag directly to a stack (not in template): you set tag once and it propagates to all instances.
|
||||||
|
|
||||||
def initialize(server_info, project, env)
|
class ChefNodeNameBuilder
|
||||||
@server_info, @project, @env = server_info, project, env
|
DEFAULT_MASK = ':project-:instancename-:env'
|
||||||
@mask = server_info['tags']['cid:node-name-mask'] || DEFAULT_MASK
|
|
||||||
|
# @param attrs [Hash] should contain
|
||||||
|
# +:provider_server_info+
|
||||||
|
# +:project_id+
|
||||||
|
# +:env_id+
|
||||||
|
def initialize(attrs)
|
||||||
|
@server_info = attrs[:provider_server_info]
|
||||||
|
@project, @env = attrs[:project_id], attrs[:env_id]
|
||||||
|
@mask = @server_info['tags']['cid:node-name-mask'] || DEFAULT_MASK
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_node_name
|
|
||||||
|
# @param incrementers_values [Hash] is a hash in which key is name of a variable and value is last substituted number for that var.
|
||||||
|
# This method modifies +incrementers_values+, updating values for substituted variables.
|
||||||
|
#
|
||||||
|
# Examples (assume mask is set to +':project-master-:increment-group1:'+):
|
||||||
|
# incremeters_values = {}
|
||||||
|
# builder.build_node_name!(incremeters_values) # returns 'mpda-master-01'
|
||||||
|
# puts incremeters_values # {'group1' => 1}
|
||||||
|
# builder.build_node_name!(incremeters_values) # returns 'mpda-master-02'
|
||||||
|
# puts incremeters_values # {'group1' => 2}
|
||||||
|
def build_node_name!(incrementers_values)
|
||||||
result = @mask.dup
|
result = @mask.dup
|
||||||
replace_dollar_variables!(result)
|
replace_variables!(result)
|
||||||
replace_colon_variables!(result)
|
replace_incrementers!(result, incrementers_values)
|
||||||
result.gsub!('_', '-')
|
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def replace_dollar_variables!(result)
|
def replace_variables!(result)
|
||||||
result.gsub!('$project', @project.id)
|
result.gsub!(':project', @project)
|
||||||
result.gsub!('$env', @env.identifier)
|
result.gsub!(':env', @env)
|
||||||
result.gsub!('$instanceid', @server_info['id'])
|
|
||||||
result.gsub!('$cfname', @server_info['tags']['Name'] || '')
|
|
||||||
result.gsub!('$time', Time.now.to_i.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def replace_colon_variables!(result)
|
|
||||||
result.gsub!(':project', @project.id)
|
|
||||||
result.gsub!(':env', @env.identifier)
|
|
||||||
result.gsub!(':instanceid', @server_info['id'])
|
result.gsub!(':instanceid', @server_info['id'])
|
||||||
result.gsub!(':cfname', @server_info['tags']['Name'] || '')
|
result.gsub!(':instancename', @server_info['tags']['Name'] || '')
|
||||||
result.gsub!(':time', Time.now.to_i.to_s)
|
result.gsub!(':time', Time.now.to_i.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def replace_incrementers!(result, incrementers_values)
|
||||||
|
groupname_regexp = /(?<=:increment-)\w+(?=:)/
|
||||||
|
result.gsub!(/:increment-\w+:/) do |incrementer|
|
||||||
|
group_name = groupname_regexp.match(incrementer)[0]
|
||||||
|
prev_value = incrementers_values[group_name] || 0
|
||||||
|
current_value = prev_value + 1
|
||||||
|
incrementers_values[group_name] = current_value
|
||||||
|
current_value.to_s.rjust(2, '0')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@ -42,18 +42,18 @@ class StackServersPersister
|
|||||||
end
|
end
|
||||||
|
|
||||||
# takes a hash, returns Server model
|
# takes a hash, returns Server model
|
||||||
def persist_stack_server(info_hash)
|
def persist_stack_server(server_info)
|
||||||
server_attrs = {
|
server_attrs = {
|
||||||
'_id' => info_hash['id'],
|
'_id' => server_info['id'],
|
||||||
'chef_node_name' => ChefNodeNameBuilder.new(info_hash, @project, @deploy_env).build_node_name,
|
'chef_node_name' => get_name_builder(server_info).build_node_name!(incrementers_values),
|
||||||
'created_by' => stack.owner,
|
'created_by' => stack.owner,
|
||||||
'deploy_env' => @deploy_env.identifier,
|
'deploy_env' => @deploy_env.identifier,
|
||||||
'key' => info_hash['key_name'] || @provider.ssh_key,
|
'key' => server_info['key_name'] || @provider.ssh_key,
|
||||||
'project' => @project.id,
|
'project' => @project.id,
|
||||||
'provider' => @provider.name,
|
'provider' => @provider.name,
|
||||||
'remote_user' => mongo.image(@deploy_env.image).remote_user,
|
'remote_user' => mongo.image(@deploy_env.image).remote_user,
|
||||||
'private_ip' => info_hash['private_ip'],
|
'private_ip' => server_info['private_ip'],
|
||||||
'public_ip' => info_hash['public_ip'],
|
'public_ip' => server_info['public_ip'],
|
||||||
'run_list' => stack.run_list || [],
|
'run_list' => stack.run_list || [],
|
||||||
'stack' => stack.name
|
'stack' => stack.name
|
||||||
}
|
}
|
||||||
@ -63,6 +63,19 @@ class StackServersPersister
|
|||||||
server
|
server
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_name_builder(server_info)
|
||||||
|
ChefNodeNameBuilder.new(
|
||||||
|
provider_server_info: server_info,
|
||||||
|
project_id: @project.id,
|
||||||
|
env_id: @deploy_env.identifier,
|
||||||
|
owner: stack.owner
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def incrementers_values
|
||||||
|
@incrementers_values ||= {}
|
||||||
|
end
|
||||||
|
|
||||||
def mongo
|
def mongo
|
||||||
Devops::Db.connector
|
Devops::Db.connector
|
||||||
end
|
end
|
||||||
|
|||||||
@ -18,7 +18,7 @@ class StackSynchronizer
|
|||||||
stack.sync!
|
stack.sync!
|
||||||
print_new_events
|
print_new_events
|
||||||
case stack.stack_status
|
case stack.stack_status
|
||||||
when 'CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS'
|
when 'CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'DELETE_IN_PROGRESS'
|
||||||
when 'CREATE_COMPLETE'
|
when 'CREATE_COMPLETE'
|
||||||
::Devops::Db.connector.stack_update(stack)
|
::Devops::Db.connector.stack_update(stack)
|
||||||
puts_and_flush "Stack '#{stack.id}' status is now #{stack.stack_status}"
|
puts_and_flush "Stack '#{stack.id}' status is now #{stack.stack_status}"
|
||||||
@ -26,6 +26,9 @@ class StackSynchronizer
|
|||||||
when 'ROLLBACK_COMPLETE'
|
when 'ROLLBACK_COMPLETE'
|
||||||
puts_and_flush "Stack '#{stack.id}' status is rolled back"
|
puts_and_flush "Stack '#{stack.id}' status is rolled back"
|
||||||
return error_code(:stack_rolled_back)
|
return error_code(:stack_rolled_back)
|
||||||
|
when 'DELETE_COMPLETE'
|
||||||
|
puts_and_flush "Stack '#{stack.id}' status is deleted"
|
||||||
|
return error_code(:stack_deleted)
|
||||||
else
|
else
|
||||||
puts_and_flush "Unknown stack status: '#{stack.stack_status}'"
|
puts_and_flush "Unknown stack status: '#{stack.stack_status}'"
|
||||||
return error_code(:unkown_status)
|
return error_code(:unkown_status)
|
||||||
@ -54,7 +57,8 @@ class StackSynchronizer
|
|||||||
stack_rolled_back: 1,
|
stack_rolled_back: 1,
|
||||||
unkown_status: 2,
|
unkown_status: 2,
|
||||||
timeout: 3,
|
timeout: 3,
|
||||||
error: 5
|
error: 5,
|
||||||
|
stack_deleted: 6
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user