require 'fileutils' require 'erb' require 'open3' require 'shell-spinner' module Kontena module Machine module Aws class MasterProvisioner include RandomName attr_reader :client, :http_client, :region # @param [String] access_key_id aws_access_key_id # @param [String] secret_key aws_secret_access_key # @param [String] region def initialize(access_key_id, secret_key, region) @client = Fog::Compute.new(:provider => 'AWS', :aws_access_key_id => access_key_id, :aws_secret_access_key => secret_key, :region => region) end # @param [Hash] opts def run!(opts) if opts[:ssl_cert] abort('Invalid ssl cert') unless File.exists?(File.expand_path(opts[:ssl_cert])) ssl_cert = File.read(File.expand_path(opts[:ssl_cert])) end ami = resolve_ami(client.region) abort('No valid AMI found for region') unless ami opts[:vpc] = default_vpc.id unless opts[:vpc] if opts[:subnet].nil? subnet = default_subnet(opts[:vpc], client.region+opts[:zone]) opts[:subnet] = subnet.subnet_id else subnet = client.subnets.get(opts[:subnet]) end userdata_vars = { ssl_cert: ssl_cert, auth_server: opts[:auth_server], version: opts[:version] } security_group = ensure_security_group(opts[:vpc]) name = generate_name response = client.run_instances( ami, 1, 1, 'InstanceType' => opts[:type], 'SecurityGroupId' => security_group.group_id, 'KeyName' => opts[:key_pair], 'SubnetId' => opts[:subnet], 'UserData' => user_data(userdata_vars), 'BlockDeviceMapping' => [ { 'DeviceName' => '/dev/xvda', 'VirtualName' => 'Root', 'Ebs.VolumeSize' => opts[:storage], 'Ebs.VolumeType' => 'gp2' } ] ) instance_id = response.body['instancesSet'].first['instanceId'] instance = client.servers.get(instance_id) ShellSpinner "Creating AWS instance #{name.colorize(:cyan)} " do instance.wait_for { ready? } end if opts[:ssl_cert] master_url = "https://#{instance.public_ip_address}" else master_url = "http://#{instance.public_ip_address}" end Excon.defaults[:ssl_verify_peer] = false @http_client = Excon.new("#{master_url}", :connect_timeout => 10) ShellSpinner "Waiting for #{name.colorize(:cyan)} to start" do sleep 5 until master_running? end puts "Kontena Master is now running at #{master_url}" puts "Use #{"kontena login #{master_url}".colorize(:light_black)} to complete Kontena Master setup" end ## # @param [String] grid # @return Fog::Compute::AWS::SecurityGroup def ensure_security_group(vpc_id) group_name = "kontena_master" if vpc_id client.security_groups.all({'group-name' => group_name, 'vpc-id' => vpc_id}).first || create_security_group(group_name, vpc_id) else client.security_groups.get(group_name) || create_security_group(group_name) end end ## # creates security_group and authorizes default port ranges # # @param [String] name # @return Fog::Compute::AWS::SecurityGroup def create_security_group(name, vpc_id = nil) security_group = client.security_groups.new(:name => name, :description => "Kontena Master", :vpc_id => vpc_id) security_group.save security_group.authorize_port_range(80..80) security_group.authorize_port_range(443..443) security_group.authorize_port_range(22..22) security_group end # @param [String] region # @return String def resolve_ami(region) images = { 'eu-central-1' => 'ami-74bbba69', 'ap-northeast-1' => 'ami-1e77ff1e', 'us-gov-west-1' => 'ami-f1d1b2d2', 'sa-east-1' => 'ami-632ba17e', 'ap-southeast-2' => 'ami-83f8b4b9', 'ap-southeast-1' => 'ami-12060c40', 'us-east-1' => 'ami-f396fa96', 'us-west-2' => 'ami-99bfada9', 'us-west-1' => 'ami-dbe71d9f', 'eu-west-1' => 'ami-83e9c8f4' } images[region] end def default_subnet(vpc, zone) client.subnets.all('vpc-id' => vpc, 'availabilityZone' => zone).first end def default_vpc client.vpcs.all('isDefault' => true).first end def user_data(vars) cloudinit_template = File.join(__dir__ , '/cloudinit_master.yml') erb(File.read(cloudinit_template), vars) end def generate_name "kontena-master-#{super}-#{rand(1..99)}" end def master_running? http_client.get(path: '/').status == 200 rescue false end def erb(template, vars) ERB.new(template).result(OpenStruct.new(vars).instance_eval { binding }) end end end end end