fluke/devops-service/providers/ec2.rb
2015-09-21 20:27:43 +03:00

396 lines
12 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

require "exceptions/conflict_exception"
require "providers/base_provider"
module Provider
# Provider for Amazon EC2
class Ec2 < BaseProvider
PROVIDER = "ec2"
attr_accessor :availability_zone
def initialize config
self.certificate_path = config[:aws_certificate]
self.ssh_key = config[:aws_ssh_key]
options = {
:provider => "aws",
:aws_access_key_id => config[:aws_access_key_id],
:aws_secret_access_key => config[:aws_secret_access_key]
}
if config[:aws_proxy] and config[:aws_no_proxy]
options[:connection_options] = {
:proxy => config[:aws_proxy],
:no_proxy => config[:no_proxy]
}
end
self.connection_options = options
self.availability_zone = config[:aws_availability_zone] || "us-east-1a"
self.run_list = config[:aws_integration_run_list] || []
end
def configured?
o = self.connection_options
super and !(empty_param?(o[:aws_access_key_id]) or empty_param?(o[:aws_secret_access_key]))
end
def name
PROVIDER
end
def flavors
self.compute.flavors.all.map do |f|
{
"id" => f.id,
"cores" => f.cores,
"disk" => f.disk,
"name" => f.name,
"ram" => f.ram
}
end
end
def groups filters=nil
buf = {}
buf = filters.select{|k,v| ["vpc-id"].include?(k)} unless filters.nil?
g = if buf.empty?
self.compute.describe_security_groups
else
self.compute.describe_security_groups(buf)
end
convert_groups(g.body["securityGroupInfo"])
end
def images filters
self.compute.describe_images({"image-id" => filters}).body["imagesSet"].map do |i|
{
"id" => i["imageId"],
"name" => i["name"],
"status" => i["imageState"]
}
end
end
def networks_detail
self.networks
end
def networks
self.compute.describe_subnets.body["subnetSet"].select{|n| n["state"] == "available"}.map do |n|
{
"cidr" => n["cidrBlock"],
"vpcId" => n["vpcId"],
"subnetId" => n["subnetId"],
"name" => n["subnetId"],
"zone" => n["availabilityZone"]
}
end
end
def servers
list = self.compute.describe_instances.body["reservationSet"]
list.select{|l| l["instancesSet"][0]["instanceState"]["name"].to_s != "terminated"}.map do |server|
convert_server server["instancesSet"][0]
end
end
def server id
list = self.compute.describe_instances('instance-id' => [id]).body["reservationSet"]
convert_server list[0]["instancesSet"][0]
end
def create_server s, image, flavor, subnets, groups, out, options={}
out << "Creating server for project '#{s.project} - #{s.deploy_env}'\n"
options = {
"InstanceType" => flavor,
# "Placement.AvailabilityZone" => s.options[:availability_zone],
"KeyName" => self.ssh_key,
"Tags" => {
"Name" => s.chef_node_name,
"cid:project" => s.project,
"cid:deployEnv" => s.deploy_env,
"cid:user" => s.created_by,
"cid:remoteUser" => s.remote_user
}
}
vpcId = nil
unless subnets.empty?
options["SubnetId"] = subnets[0]
vpcId = self.networks.detect{|n| n["name"] == options["SubnetId"]}["vpcId"]
if vpcId.nil?
out << "Can not get 'vpcId' by subnet name '#{options["SubnetId"]}'\n"
return false
end
end
options["SecurityGroupId"] = extract_group_ids(groups, vpcId).join(",")
aws_server = nil
compute = self.compute
begin
aws_server = compute.run_instances(image, 1, 1, options)
rescue Excon::Errors::Unauthorized => ue
#root = XML::Parser.string(ue.response.body).parse.root
#msg = root.children.find { |node| node.name == "Message" }
#code = root.children.find { |node| node.name == "Code" }
code = "TODO"
msg = ue.response.body
out << "\nERROR: Unauthorized (#{code}: #{msg})"
return false
rescue Fog::Compute::AWS::Error => e
out << e.message
return false
end
abody = aws_server.body
instance = abody["instancesSet"][0]
s.id = instance["instanceId"]
out << "\nWaiting for server..."
details, state = nil, instance["instanceState"]["name"]
until state == "running"
sleep(2)
details = compute.describe_instances("instance-id" => [s.id]).body["reservationSet"][0]["instancesSet"][0]
state = details["instanceState"]["name"].to_s
next if state == "pending" or state == "running"
out << "Server returns state '#{state}'"
return false
end
s.public_ip = details["ipAddress"]
s.private_ip = details["privateIpAddress"]
# set_server_tags(s)
out << "\nDone\n\n"
out << s.info
true
end
def set_server_tags s
tags = {
}
compute.create_tags(s.id, tags)
end
def delete_server s
r = self.compute.terminate_instances(s.id)
i = r.body["instancesSet"][0]
old_state = i["previousState"]["name"]
state = i["currentState"]["name"]
return r.status == 200 ? "Server with id '#{s.id}' changed state '#{old_state}' to '#{state}'" : r.body
end
def pause_server s
es = self.server s.id
if es["state"] == "running"
self.compute.stop_instances [ s.id ]
return nil
else
return es["state"]
end
end
def unpause_server s
es = self.server s.id
if es["state"] == "stopped"
self.compute.start_instances [ s.id ]
return nil
else
return es["state"]
end
end
def set_tags instance_id, tags
raise ConflictException.new("You can not change 'Name' tag") if tags.key?("Name")
compute.create_tags(instance_id, tags)
end
def unset_tags instance_id, tags
raise ConflictException.new("You can not change 'Name' tag") if tags.key?("Name")
compute.delete_tags(instance_id, tags)
end
def compute
connection_compute(connection_options)
end
def cloud_formation
@cloud_formation ||= Fog::AWS::CloudFormation.new(connection_options)
end
def create_stack(stack, out)
begin
out << "Creating stack for project '#{stack.project}' and environment '#{stack.deploy_env}'...\n"
stack.name = create_default_stack_name(stack) unless stack.name
out << "Stack name: #{stack.name}\n"
out << "Stack template: #{stack.stack_template}\n"
out << "Stack parameters: #{stack.parameters}\n"
out.flush
response = cloud_formation.create_stack(stack.name,
{
'TemplateBody' => stack.template_body,
'Parameters' => stack.parameters || {},
'Capabilities' => ['CAPABILITY_IAM'],
'Tags' => {
"StackName" => stack.name,
"cid:project" => stack.project,
"cid:deployEnv" => stack.deploy_env,
"cid:user" => stack.owner
}
}
)
stack.id = response.body['StackId']
out << "Stack id: #{stack.id}\n"
#set_stack_tags(stack, out)
out.flush
rescue Excon::Errors::Conflict => e
raise ProviderErrors::NameConflict
rescue Excon::Errors::BadRequest => br
response = ::Chef::JSONCompat.from_json(br.response.body)
if response['code'] == 400
out << "\nERROR: Bad request (400): #{response['explanation']}"
out << "\n"
raise InvalidRecord.new(response['explanation'])
else
out << "\nERROR: Unknown server error (#{response['code']}): #{response['explanation']}"
out << "\n"
raise InvalidRecord.new(response['explanation'])
end
end
end
def set_stack_tags stack, out=""
tags = {
# "cid:remoteUser" => s.remote_user
}
#ids = stack_resources(stack).map {|resource| resource['PhysicalResourceId']}
#ids << stack.id
#compute.create_tags(ids, tags)
stack_resources(stack).each do |resource|
begin
compute.create_tags(resource['PhysicalResourceId'], tags)
rescue Fog::Compute::AWS::Error => e
out << "Error: " + e.message
end
end
end
def validate_stack_template template
r = cloud_formation.validate_template({'TemplateBody' => template})
pp r.body
end
def delete_stack(stack)
cloud_formation.delete_stack(stack.name)
end
def stack_details(stack)
response = cloud_formation.describe_stacks({'StackName' => stack.name}).body['Stacks']
# somewhy it sometimes returns blank results
response.detect do |t|
t.has_key?('StackName')
end
end
def stack_resources(stack)
cloud_formation.describe_stack_resources({'StackName' => stack.name}).body['StackResources']
end
# не работает, не используется
# def stack_resource(stack, resource_id)
# physical_id = fog_stack(stack).resources.get(resource_id).physical_resource_id
# compute.servers.get(physical_id)
# end
def stack_servers(stack)
# orchestration.describe_stack_resources возвращает мало информации
resources = compute.describe_instances(
'tag-key' => 'aws:cloudformation:stack-id',
'tag-value' => stack.id
).body["reservationSet"]
# В ресурсах могут лежать не только конкретные инстансы, но и MasterNodesGroup, которые управляют
# несколькими инстансами. Обрабатываем эту ситуацию.
instances = resources.map { |resource| resource["instancesSet"] }.flatten
instances.map do |instance|
{
# 'name' => instance["tagSet"]["Name"],
'name' => [stack.name, instance_name(instance)].join('-'),
'id' => instance["instanceId"],
'key_name' => instance["keyName"],
'private_ip' => instance["privateIpAddress"],
'public_ip' => instance["ipAddress"],
}
end
end
def create_default_stack_name s
"stack-#{self.ssh_key}-#{s.project}-#{s.deploy_env}-#{Time.now.to_i}".gsub('_', '-')
end
private
def convert_groups list
res = {}
list.each do |g|
next if g["groupName"].nil?
res[g["groupName"]] = {
"description" => g["groupDescription"],
"id" => g["groupId"]
}
rules = []
g["ipPermissions"].each do |r|
cidr = r["ipRanges"][0] || {}
rules.push({
"protocol" => r["ipProtocol"],
"from" => r["fromPort"],
"to" => r["toPort"],
"cidr" => cidr["cidrIp"]
})
end
res[g["groupName"]]["rules"] = rules
end
res
end
def convert_server s
{
"state" => s["instanceState"]["name"],
"name" => s["tagSet"]["Name"],
"image" => s["imageId"],
"flavor" => s["instanceType"],
"keypair" => s["keyName"],
"instance_id" => s["instanceId"],
"dns_name" => s["dnsName"],
"zone" => s["placement"]["availabilityZone"],
"private_ip" => s["privateIpAddress"],
"public_ip" => s["ipAddress"],
"launched_at" => s["launchTime"]
}
end
def extract_group_ids names, vpcId
return [] if names.nil?
p = nil
p = {"vpc-id" => vpcId} unless vpcId.nil?
groups = self.groups(p)
r = names.map do |name|
groups[name]["id"]
end
r
end
def orchestration
@orchestration ||= Fog::AWS::CloudFormation.new(connection_options)
end
def instance_name(instance)
return instance["tagSet"]["Name"] if instance["tagSet"]["Name"]
if instance['tagSet']['aws:autoscaling:groupName']
instance["instanceId"]
else
instance['tagSet']['aws:cloudformation:logical-id']
end
end
end
end