Merge branch 'CID-443-rollback_servers_with_failed_bootstrap' into bug_fix
This commit is contained in:
commit
cd62d0f874
@ -50,8 +50,19 @@ class Stack < Handler
|
|||||||
env = fetcher.fetch_project(q[:project])['deploy_envs'].detect {|env| env['identifier'] == q[:deploy_env]}
|
env = fetcher.fetch_project(q[:project])['deploy_envs'].detect {|env| env['identifier'] == q[:deploy_env]}
|
||||||
q[:provider] = env['provider']
|
q[:provider] = env['provider']
|
||||||
|
|
||||||
filepath = options[:parameters_file] || enter_parameter(I18n.t('handler.stack.create.parameters_file'))
|
params_filepath = options[:parameters_file] || enter_parameter_or_empty(I18n.t('handler.stack.create.parameters_file'))
|
||||||
q[:parameters] = JSON.parse(File.read(filepath))
|
if params_filepath.empty?
|
||||||
|
q[:parameters] = {}
|
||||||
|
else
|
||||||
|
q[:parameters] = JSON.parse(File.read(params_filepath))
|
||||||
|
end
|
||||||
|
|
||||||
|
tags_filepath = options[:tags_file] || enter_parameter_or_empty(I18n.t('handler.stack.create.tags_file'))
|
||||||
|
if tags_filepath.empty?
|
||||||
|
q[:tags] = {}
|
||||||
|
else
|
||||||
|
q[:tags] = JSON.parse(File.read(tags_filepath))
|
||||||
|
end
|
||||||
|
|
||||||
json = JSON.pretty_generate(q)
|
json = JSON.pretty_generate(q)
|
||||||
if question(I18n.t("handler.stack.question.create")) {puts json}
|
if question(I18n.t("handler.stack.question.create")) {puts json}
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class StackOptions < CommonOptions
|
|||||||
parser.recognize_option_value(:deploy_env)
|
parser.recognize_option_value(:deploy_env)
|
||||||
parser.recognize_option_value(:stack_template)
|
parser.recognize_option_value(:stack_template)
|
||||||
parser.recognize_option_value(:parameters_file)
|
parser.recognize_option_value(:parameters_file)
|
||||||
|
parser.recognize_option_value(:tags_file)
|
||||||
parser.recognize_option_value(:run_list)
|
parser.recognize_option_value(:run_list)
|
||||||
parser.recognize_option_value(:without_bootstrap, type: :switch, switch_value: true)
|
parser.recognize_option_value(:without_bootstrap, type: :switch, switch_value: true)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -115,8 +115,9 @@ en:
|
|||||||
create:
|
create:
|
||||||
id: "Id: "
|
id: "Id: "
|
||||||
deploy_env: "Deploy env: "
|
deploy_env: "Deploy env: "
|
||||||
parameters_file: "Path to file with JSON parameters: "
|
parameters_file: "Path to file with parameters in JSON (or Enter): "
|
||||||
run_list: "Run list: "
|
run_list: "Run list: "
|
||||||
|
tags_file: "Path to file with tags in JSON (or Enter): "
|
||||||
question:
|
question:
|
||||||
create: "Are you sure to create stack?"
|
create: "Are you sure to create stack?"
|
||||||
delete: "Are you sure to delete stack '%{name}'?"
|
delete: "Are you sure to delete stack '%{name}'?"
|
||||||
@ -374,6 +375,7 @@ en:
|
|||||||
project: Stack project
|
project: Stack project
|
||||||
stack_template: Stack template
|
stack_template: Stack template
|
||||||
parameters_file: File with parameters JSON
|
parameters_file: File with parameters JSON
|
||||||
|
tags_file: File with tags JSON
|
||||||
run_list: Stack run list
|
run_list: Stack run list
|
||||||
without_bootstrap: Skip bootsraping phase on created instances
|
without_bootstrap: Skip bootsraping phase on created instances
|
||||||
stack_template:
|
stack_template:
|
||||||
|
|||||||
@ -21,6 +21,29 @@ module Devops
|
|||||||
json Devops::API2_0::Handler::Stack.new(request).stack_servers(name).map(&:to_hash)
|
json Devops::API2_0::Handler::Stack.new(request).stack_servers(name).map(&:to_hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Create stack
|
||||||
|
#
|
||||||
|
# * *Request*
|
||||||
|
# - method : PATCH
|
||||||
|
# - headers :
|
||||||
|
# - Accept: application/json
|
||||||
|
# - Content-Type: application/json
|
||||||
|
# - body :
|
||||||
|
# {
|
||||||
|
# "without_bootstrap": null,
|
||||||
|
# "project": "project_name",
|
||||||
|
# "deploy_env": "test",
|
||||||
|
# "provider": "ec2",
|
||||||
|
# "tags": {
|
||||||
|
# "tagName": "tagValue"
|
||||||
|
# },
|
||||||
|
# "parameters": {
|
||||||
|
# "KeyName": "Value"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# * *Returns* :
|
||||||
|
# [report_id]
|
||||||
app.post_with_headers "/stack", :headers => [:accept, :content_type] do
|
app.post_with_headers "/stack", :headers => [:accept, :content_type] do
|
||||||
check_privileges("stack", "w")
|
check_privileges("stack", "w")
|
||||||
json Devops::API2_0::Handler::Stack.new(request).create_stack
|
json Devops::API2_0::Handler::Stack.new(request).create_stack
|
||||||
|
|||||||
@ -41,6 +41,9 @@ module Devops
|
|||||||
::Validators::FieldValidator::FieldType::Array,
|
::Validators::FieldValidator::FieldType::Array,
|
||||||
::Validators::FieldValidator::RunList]
|
::Validators::FieldValidator::RunList]
|
||||||
|
|
||||||
|
set_field_validators :tags, [::Validators::FieldValidator::Nil,
|
||||||
|
::Validators::FieldValidator::FieldType::Hash]
|
||||||
|
|
||||||
def initialize attrs={}
|
def initialize attrs={}
|
||||||
self.set_provider(attrs)
|
self.set_provider(attrs)
|
||||||
self.id = attrs['id']
|
self.id = attrs['id']
|
||||||
@ -51,6 +54,7 @@ module Devops
|
|||||||
self.parameters = attrs['parameters']
|
self.parameters = attrs['parameters']
|
||||||
self.owner = attrs['owner']
|
self.owner = attrs['owner']
|
||||||
self.run_list = attrs['run_list'] || []
|
self.run_list = attrs['run_list'] || []
|
||||||
|
self.tags = attrs['tags'] || {}
|
||||||
self.stack_status = attrs['stack_status']
|
self.stack_status = attrs['stack_status']
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
@ -65,7 +69,8 @@ module Devops
|
|||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
stack_status: stack_status,
|
stack_status: stack_status,
|
||||||
owner: owner,
|
owner: owner,
|
||||||
run_list: run_list
|
run_list: run_list,
|
||||||
|
tags: tags
|
||||||
}.merge(provider_hash)
|
}.merge(provider_hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -262,8 +262,8 @@ module Devops
|
|||||||
return error_code(:server_not_in_chef_nodes)
|
return error_code(:server_not_in_chef_nodes)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# @out << roll_back
|
roll_back
|
||||||
# mongo.server_delete @server.id
|
mongo.server_delete @server.id
|
||||||
msg = "Failed while bootstraping server with id '#{@server.id}'\n"
|
msg = "Failed while bootstraping server with id '#{@server.id}'\n"
|
||||||
msg << "Bootstraping operation result was #{bootstrap_status}"
|
msg << "Bootstraping operation result was #{bootstrap_status}"
|
||||||
DevopsLogger.logger.error msg
|
DevopsLogger.logger.error msg
|
||||||
@ -510,7 +510,7 @@ module Devops
|
|||||||
|
|
||||||
def schedule_expiration
|
def schedule_expiration
|
||||||
if @deploy_env.expires
|
if @deploy_env.expires
|
||||||
@out << "Planning expiration in #{@deploy_env.expires}"
|
puts_and_flush "Planning expiration in #{@deploy_env.expires}"
|
||||||
ExpirationScheduler.new(@deploy_env.expires, @server).schedule_expiration!
|
ExpirationScheduler.new(@deploy_env.expires, @server).schedule_expiration!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -220,6 +220,7 @@ module Provider
|
|||||||
out << "Stack name: #{stack.name}\n"
|
out << "Stack name: #{stack.name}\n"
|
||||||
out << "Stack template: #{stack.stack_template}\n"
|
out << "Stack template: #{stack.stack_template}\n"
|
||||||
out << "Stack parameters: #{stack.parameters}\n"
|
out << "Stack parameters: #{stack.parameters}\n"
|
||||||
|
out << "Stack tags: #{JSON.pretty_generate(stack_tags(stack))}\n"
|
||||||
out.flush
|
out.flush
|
||||||
response = cloud_formation.create_stack(stack.name,
|
response = cloud_formation.create_stack(stack.name,
|
||||||
{
|
{
|
||||||
@ -254,7 +255,7 @@ module Provider
|
|||||||
"cid:project" => stack.project,
|
"cid:project" => stack.project,
|
||||||
"cid:deployEnv" => stack.deploy_env,
|
"cid:deployEnv" => stack.deploy_env,
|
||||||
"cid:user" => stack.owner
|
"cid:user" => stack.owner
|
||||||
}
|
}.merge(stack.tags)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_stack(stack, params)
|
def update_stack(stack, params)
|
||||||
|
|||||||
@ -366,10 +366,19 @@ RSpec.describe Devops::Executor::ServerExecutor, type: :executor, stubbed_connec
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "when bootstrap wasn't successful" do
|
context "when bootstrap wasn't successful" do
|
||||||
it 'returns :server_bootstrap_fail error code' do
|
before do
|
||||||
allow(executor).to receive(:bootstrap) { 1 }
|
allow(executor).to receive(:bootstrap) { 1 }
|
||||||
|
allow(executor).to receive(:roll_back)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns :server_bootstrap_fail error code' do
|
||||||
expect(two_phase_bootstrap).to eq 2
|
expect(two_phase_bootstrap).to eq 2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'rollbacks server' do
|
||||||
|
allow(executor).to receive(:roll_back)
|
||||||
|
two_phase_bootstrap
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when an error occured during bootstrap' do
|
context 'when an error occured during bootstrap' do
|
||||||
|
|||||||
@ -19,6 +19,7 @@ RSpec.describe Devops::Model::StackEc2, type: :model do
|
|||||||
include_examples 'field type validation', :name, :maybe_nil, :non_empty_string, :field_validator
|
include_examples 'field type validation', :name, :maybe_nil, :non_empty_string, :field_validator
|
||||||
include_examples 'field type validation', :owner, :not_nil, :non_empty_string, :field_validator
|
include_examples 'field type validation', :owner, :not_nil, :non_empty_string, :field_validator
|
||||||
include_examples 'field type validation', :run_list, :maybe_nil, :maybe_empty_array, :run_list, :field_validator
|
include_examples 'field type validation', :run_list, :maybe_nil, :maybe_empty_array, :run_list, :field_validator
|
||||||
|
include_examples 'field type validation', :tags, :maybe_nil, :field_validator
|
||||||
|
|
||||||
it 'validates provider' do
|
it 'validates provider' do
|
||||||
stack.provider = nil
|
stack.provider = nil
|
||||||
@ -27,11 +28,13 @@ RSpec.describe Devops::Model::StackEc2, type: :model do
|
|||||||
stack.provider = ''
|
stack.provider = ''
|
||||||
expect(stack).not_to be_valid
|
expect(stack).not_to be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
pending 'stack tags should be a hash'
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#to_hash_without_id' do
|
describe '#to_hash_without_id' do
|
||||||
it 'returns hash with several fields' do
|
it 'returns hash with several fields' do
|
||||||
expect(stack.to_hash_without_id.keys).to include('provider', :project, :deploy_env, :stack_template, :name, :owner, :run_list)
|
expect(stack.to_hash_without_id.keys).to include('provider', :project, :deploy_env, :stack_template, :name, :owner, :run_list, :tags)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -43,6 +46,10 @@ RSpec.describe Devops::Model::StackEc2, type: :model do
|
|||||||
it 'sets run_list to empty array if it\'s blank' do
|
it 'sets run_list to empty array if it\'s blank' do
|
||||||
expect(described_class.new.run_list).to eq []
|
expect(described_class.new.run_list).to eq []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'sets tags to empty hash if it\'s blank' do
|
||||||
|
expect(described_class.new.tags).to eq({})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,45 +1,69 @@
|
|||||||
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
|
||||||
let(:server_info) do
|
let(:server_info) do
|
||||||
{
|
{
|
||||||
'id' => 'server1',
|
"name"=>"stack-achuchkalov-aws-test-1455976199-master01",
|
||||||
'name' => 'server_name',
|
"id"=>"i-fac32c7e",
|
||||||
'key_name' => 'key',
|
"key_name"=>"achuchkalov",
|
||||||
'private_ip' => '127.0.0.1',
|
"private_ip"=>"172.31.11.30",
|
||||||
'public_ip' => '127.0.0.2',
|
"public_ip"=>"52.90.250.51",
|
||||||
'tags' => {
|
"tags" => {
|
||||||
'cid:priority' => '3'
|
"Name"=>"master01",
|
||||||
|
"aws:cloudformation:logical-id"=>"EC2Instance1",
|
||||||
|
"aws:cloudformation:stack-name"=>"stack-achuchkalov-aws-test-1455976199",
|
||||||
|
"StackTemplate"=>"1inst",
|
||||||
|
"aws:cloudformation:stack-id" => "arn:aws:cloudformation:us-east-1:736558555923:stack/stack-achuchkalov-aws-test-1455976199/d5f3ca60-d7d8-11e5-9ba1-50d5cd24fac6",
|
||||||
|
"cid:deployEnv"=>"test",
|
||||||
|
"cid:project"=>"aws",
|
||||||
|
"cid:user"=>"root",
|
||||||
|
"cid:priority"=>0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
let(:project) { build(:project, id: 'proj', with_deploy_env_identifiers: %w(dev)) }
|
let(:project) { build(:project, id: 'proj', with_deploy_env_identifiers: %w(dev)) }
|
||||||
let(:env) { project.deploy_env('dev') }
|
let(:env) { project.deploy_env('dev') }
|
||||||
let(:name_builder) { described_class.new(server_info, project, env) }
|
let(:build_node_name) {
|
||||||
let(:build_name) { name_builder.build_node_name }
|
described_class.new(server_info, project, env).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-$nodename-$env")' do
|
it 'uses default mask ("$project-$cfname-$env")' do
|
||||||
expect(build_name).to eq 'proj-server1-dev'
|
expect(build_node_name).to eq 'proj-master01-dev'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'substitutes project, env and nodename' do
|
it 'substitutes $project, $env, $instanceid and $cfname' do
|
||||||
set_mask('$project/$env/$nodename')
|
set_mask('$project/$env/$instanceid/$cfname')
|
||||||
expect(build_name).to eq 'proj/dev/server1'
|
expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'substitutes $time' do
|
it 'substitutes $time' do
|
||||||
set_mask('$nodename-$time')
|
set_mask('$project-$time')
|
||||||
expect(build_name).to match /server1-\d+/
|
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'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'substitutes :time' do
|
||||||
|
set_mask(':project-:time')
|
||||||
|
expect(build_node_name).to match /proj-\d+/
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works with both colon and dollar variables' do
|
||||||
|
set_mask('$project/$env/:instanceid/:cfname')
|
||||||
|
expect(build_node_name).to eq 'proj/dev/i-fac32c7e/master01'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'substitutes underscores to dashes' do
|
it 'substitutes underscores to dashes' do
|
||||||
server_info['id'] = 'server_1'
|
server_info['tags']['Name'] = 'server_1'
|
||||||
expect(build_name).to match 'proj-server-1-dev'
|
expect(build_node_name).to match 'proj-server-1-dev'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@ -15,7 +15,8 @@ RSpec.describe StackServersPersister, stubbed_connector: true do
|
|||||||
'private_ip' => '127.0.0.1',
|
'private_ip' => '127.0.0.1',
|
||||||
'public_ip' => '127.0.0.2',
|
'public_ip' => '127.0.0.2',
|
||||||
'tags' => {
|
'tags' => {
|
||||||
'cid:priority' => '3'
|
'cid:priority' => '3',
|
||||||
|
'Name' => 'server1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@ -97,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-$nodename-$env"' do
|
it 'build chef_node_name with default mask "$project-$cfname-$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
|
||||||
@ -105,7 +106,7 @@ 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-$nodename-123'
|
server_info_hash['tags']['cid:node-name-mask'] = '$project-$cfname-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
|
||||||
|
|||||||
@ -1,15 +1,34 @@
|
|||||||
class ChefNodeNameBuilder
|
class ChefNodeNameBuilder
|
||||||
|
DEFAULT_MASK = '$project-$cfname-$env'
|
||||||
|
|
||||||
def initialize(server_info, project, env)
|
def initialize(server_info, project, env)
|
||||||
@server_info, @project, @env = server_info, project, env
|
@server_info, @project, @env = server_info, project, env
|
||||||
@mask = server_info['tags']['cid:node-name-mask'] || '$project-$nodename-$env'
|
@mask = server_info['tags']['cid:node-name-mask'] || DEFAULT_MASK
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_node_name
|
def build_node_name
|
||||||
@mask.gsub!('$project', @project.id)
|
result = @mask.dup
|
||||||
@mask.gsub!('$env', @env.identifier)
|
replace_dollar_variables!(result)
|
||||||
@mask.gsub!('$nodename', @server_info['id'])
|
replace_colon_variables!(result)
|
||||||
@mask.gsub!('$time', Time.now.to_i.to_s)
|
result.gsub!('_', '-')
|
||||||
@mask.gsub!('_', '-')
|
result
|
||||||
@mask
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def replace_dollar_variables!(result)
|
||||||
|
result.gsub!('$project', @project.id)
|
||||||
|
result.gsub!('$env', @env.identifier)
|
||||||
|
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!(':cfname', @server_info['tags']['Name'] || '')
|
||||||
|
result.gsub!(':time', Time.now.to_i.to_s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
Loading…
Reference in New Issue
Block a user