lib/cloudstrap/bootstrap_agent.rb in cloudstrap-0.38.17.pre vs lib/cloudstrap/bootstrap_agent.rb in cloudstrap-0.38.18.pre

- old
+ new

@@ -1,498 +1,6 @@ -require 'contracts' -require 'moneta' -require 'securerandom' +require_relative 'agent' -require_relative 'amazon' -require_relative 'config' -require_relative 'errors' -require_relative 'hdp/bootstrap_properties' -require_relative 'ssh' - module Cloudstrap - class BootstrapAgent - include ::Contracts::Core - include ::Contracts::Builtin - - Contract None => BootstrapAgent - def initialize - validate_configuration! - self - end - - Contract None => Any - def validate_configuration! # TODO: Does this really belong in BootstrapAgent? - return if ec2.valid_region?(config.region) - raise ::Cloudstrap::ConfigurationError, "Region #{config.region} is not valid" - end - - Contract None => String - def create_vpc - cache.store(:vpc_id, ec2.create_vpc.vpc_id).tap do |vpc_id| - ec2.assign_name(bootstrap_tag, vpc_id) - end - end - - Contract None => Maybe[String] - def find_vpc - ENV.fetch('BOOTSTRAP_VPC_ID') do - cache.fetch(:vpc_id) do - cache.store :vpc_id, ec2 - .tagged(type: 'vpc', value: bootstrap_tag) - .map(&:resource_id) - .first - end - end - end - - Contract None => String - def internet_gateway - find_internet_gateway || create_internet_gateway - end - - Contract None => String - def create_internet_gateway - cache.store(:internet_gateway_id, - ec2.create_internet_gateway.internet_gateway_id - ).tap { |internet_gateway_id| ec2.assign_name bootstrap_tag, internet_gateway_id } - end - - Contract None => String - def nat_gateway_ip_allocation - ENV.fetch('BOOTSTRAP_NAT_GATEWAY_ALLOCATION_ID') do - cache.fetch(:nat_gateway_allocation_id) do # TODO: Simplify this. - id = ec2 - .nat_gateways - .select { |nat_gateway| nat_gateway.vpc_id == vpc } - .flat_map { |nat_gateway| nat_gateway.nat_gateway_addresses.map { |address| address.allocation_id } } - .first || ec2.unassociated_address || ec2.create_address - cache.store(:nat_gateway_allocation_id, id) - end - end - end - - Contract None => Maybe[String] - def find_nat_gateway - ec2 - .nat_gateways - .select { |nat_gateway| nat_gateway.vpc_id == vpc } - .reject { |nat_gateway| %w(failed deleted).include? nat_gateway.state } - .map { |nat_gateway| nat_gateway.nat_gateway_id } - .first - end - - Contract None => String - def create_nat_gateway - attach_gateway unless ec2.internet_gateway_attached?(internet_gateway, vpc) - ec2.create_nat_gateway(public_subnet, nat_gateway_ip_allocation).nat_gateway_id - end - - Contract None => String - def nat_gateway - ENV.fetch('BOOTSTRAP_NAT_GATEWAY_ID') do - cache.fetch(:nat_gateway_id) do - cache.store(:nat_gateway_id, (find_nat_gateway || create_nat_gateway)) - end - end - end - - Contract None => Maybe[String] - def find_internet_gateway - ENV.fetch('BOOTSTRAP_INTERNET_GATEWAY_ID') do - cache.fetch(:internet_gateway_id) do - find_tagged_internet_gateway || find_internet_gateway_for_vpc - end - end - end - - Contract None => Maybe[String] - def find_tagged_internet_gateway - ec2 - .tagged(type: 'internet-gateway', value: bootstrap_tag) - .map { |resource| resource.resource.id } - .first - end - - Contract None => Maybe[String] - def find_internet_gateway_for_vpc - ec2 - .internet_gateways - .select { |gateway| gateway.attachments.any? { |attachment| attachment.vpc_id == vpc } } - .map { |gateway| gateway.internet_gateway_id } - .first - end - - Contract None => String - def create_jumpbox_security_group - cache.store(:jumpbox_security_group, ec2.create_security_group(:jumpbox, vpc)).tap do |sg| - ec2.assign_name(bootstrap_tag, sg) - end - end - - Contract None => Maybe[String] - def find_jumpbox_security_group - @jumpbox_security_group ||= ENV.fetch('BOOTSTRAP_JUMPBOX_SECURITY_GROUP') do - cache.fetch(:jumpbox_security_group) do - cache.store :jumpbox_security_group, ec2 - .tagged(type: 'security-group', value: bootstrap_tag) - .map(&:resource_id) - .first - end - end - end - - Contract None => Bool - def allow_ssh - ec2.authorize_security_group_ingress :tcp, 22, '0.0.0.0/0', jumpbox_security_group - end - - Contract None => String - def jumpbox_security_group - find_jumpbox_security_group || create_jumpbox_security_group - end - - Contract None => String - def private_subnet - @private_subnet ||= ENV.fetch('BOOTSTRAP_PRIVATE_SUBNET_ID') do - cache.fetch(:private_subnet_id) do - properties = { vpc_id: vpc, cidr_block: config.private_cidr_block } - cache.store(:private_subnet_id, (ec2.subnet(properties) || ec2.create_subnet(properties)).tap do |subnet| - ec2.assign_name bootstrap_tag, subnet.subnet_id unless subnet.tags.any? do |tag| - tag.key == 'Name' && tag.value = bootstrap_tag - end - end.subnet_id) - end - end - end - - Contract None => String - def public_subnet - @public_subnet ||= ENV.fetch('BOOTSTRAP_PUBLIC_SUBNET_ID') do - cache.fetch(:public_subnet_id) do - properties = { vpc_id: vpc, cidr_block: config.public_cidr_block } - cache.store(:public_subnet_id, (ec2.subnet(properties) || ec2.create_subnet(properties)).tap do |subnet| - ec2.assign_name bootstrap_tag, subnet.subnet_id unless subnet.tags.any? do |tag| - tag.key == 'Name' && tag.value = bootstrap_tag - end - end.subnet_id) - end - end - end - - Contract None => String - def route_table - @route_table ||= ENV.fetch('BOOTSTRAP_ROUTE_TABLE_ID') do - cache.fetch(:route_table_id) do - cache.store(:route_table_id, ec2 - .route_tables - .select { |route_table| route_table.vpc_id == vpc } - .select { |route_table| route_table.associations.any? { |association| association.main } } - .map { |route_table| route_table.route_table_id } - .first).tap do |route_table_id| - ec2.assign_name bootstrap_tag, route_table_id - end - end - end - end - - Contract None => String - def private_route_table - @private_route_table ||= ENV.fetch('BOOTSTRAP_PRIVATE_ROUTE_TABLE_ID') do - cache.fetch(:private_route_table_id) do - id = ec2 - .route_tables - .select { |route_table| route_table.vpc_id == vpc } - .reject { |route_table| route_table.associations.any? { |association| association.main } } - .map { |route_table| route_table.route_table_id } - .first || ec2.create_route_table(vpc).route_table_id - cache.store(:private_route_table_id, id).tap do |private_route_table_id| - ec2.assign_name bootstrap_tag, private_route_table_id - end - end - end - end - - Contract None => Bool - def attach_gateway - ec2.attach_internet_gateway internet_gateway, vpc # TODO: Cache this - end - - Contract None => Bool - def default_route - ec2.create_route('0.0.0.0/0', internet_gateway, route_table) # TODO: Cache this - end - - Contract None => Bool - def nat_route - ec2.create_route('0.0.0.0/0', nat_gateway, private_route_table) # TODO: Cache this - end - - Contract None => String - def nat_route_association - @nat_route_association || ENV.fetch('BOOTSTRAP_NAT_ROUTE_ASSOCIATION_ID') do - cache.fetch(:nat_route_association_id) do - cache.store(:nat_route_association_id, ec2.associate_route_table(private_route_table, private_subnet)) - end - end - end - - Contract None => ArrayOf[String] - def subnets - [public_subnet, private_subnet] - end - - Contract None => Bool - def enable_public_ips - ec2.map_public_ip_on_launch?(public_subnet) || ec2.map_public_ip_on_launch(public_subnet, true) - end - - Contract None => String - def vpc - find_vpc || create_vpc - end - - Contract None => Bool - def enable_dns_support - ec2.vpc_supports_dns?(vpc) || ec2.enable_dns_support(vpc) - end - - Contract None => Bool - def enable_dns_hostnames - ec2.vpc_supports_dns_hostnames?(vpc) || ec2.enable_dns_hostnames(vpc) - end - - Contract None => String - def create_jumpbox - upload_ssh_key - - cache.store(:jumpbox_id, ec2.create_instance( - image_id: ami, - instance_type: config.instance_type, - key_name: bootstrap_tag, - client_token: Digest::SHA256.hexdigest(bootstrap_tag), - network_interfaces: [{ - device_index: 0, - subnet_id: public_subnet, - associate_public_ip_address: true, - groups: [jumpbox_security_group] - }] - ).instance_id).tap do |instance_id| - ec2.assign_name bootstrap_tag, instance_id - end - end - - Contract None => Maybe[String] - def find_jumpbox - ENV.fetch('BOOTSTRAP_JUMPBOX_ID') do - cache.fetch(:jumpbox_id) do - ec2 - .tagged(type: 'instance', value: bootstrap_tag) - .map(&:resource_id) - .first - end - end - end - - Contract None => String - def jumpbox - find_jumpbox || create_jumpbox - end - - Contract None => Bool - def tag_jumpbox - ec2.create_tags([jumpbox], [{ key: 'Cloudstrapped', value: 'true' }]) - end - - Contract None => String - def ami - @ami ||= ENV.fetch('BOOTSTRAP_AMI') do - cache.fetch(:ami_id) do - cache.store :ami_id, ec2.latest_ubuntu(config.ubuntu_release).image_id - end - end - end - - Contract None => String - def upload_ssh_key - ec2.import_key_pair bootstrap_tag, ssh_key.to_s # TODO: Cache this. - end - - Contract None => SSH::Key - def ssh_key - @ssh_key ||= SSH::Key.new bootstrap_tag - end - - Contract None => String - def bootstrap_tag - @bootstrap_tag ||= ENV.fetch('BOOTSTRAP_TAG') do - "lkg@#{username}/#{uuid}" - end - end - - Contract None => String - def username - @username ||= ENV.fetch('BOOTSTRAP_USERNAME') do - cache.fetch(:username) do - cache.store(:username, iam.user.user_name) - end - end - end - - Contract None => String - def uuid - @uuid ||= ENV.fetch('BOOTSTRAP_UUID') do - cache.fetch(:uuid) do - cache.store(:uuid, SecureRandom.uuid) - end - end - end - - Contract None => String - def public_availability_zone - @public_availability_zone ||= ENV.fetch('BOOTSTRAP_PUBLIC_AVAILABILITY_ZONE') do - cache.fetch(:public_availability_zone) do - cache.store(:public_availability_zone, ec2 - .subnets - .select { |subnet| subnet.subnet_id == public_subnet } - .map { |subnet| subnet.availability_zone } - .first) - end - end - end - - Contract None => String - def private_availability_zone - @private_availability_zone ||= ENV.fetch('BOOTSTRAP_PRIVATE_AVAILABILITY_ZONE') do - cache.fetch(:private_availability_zone) do - cache.store(:private_availability_zone, ec2 - .subnets - .select { |subnet| subnet.subnet_id == private_subnet } - .map { |subnet| subnet.availability_zone } - .first) - end - end - end - - Contract None => String - def jumpbox_ip - @jumpbox_ip ||= ENV.fetch('BOOTSTRAP_JUMPBOX_IP') do - cache.fetch(:jumpbox_ip) do - cache.store(:jumpbox_ip, ec2 - .instances - .select { |instance| instance.instance_id == jumpbox } - .flat_map(&:network_interfaces) - .map(&:association) - .map(&:public_ip) - .first) - end - end - end - - Contract None => Bool - def configure_hdp - bootstrap_properties - .define('Provider', 'AWS') - .define('KeepTerraform', 'true') - .define('AWS.NodeInstanceType', config.node_instance_type) - .define('AWS.MasterInstanceType', config.master_instance_type) - .define('AWS.GlusterFSInstanceType', config.gluster_instance_type) - .define('NodeCount', config.node_count) - .define('MasterCount', config.master_count) - .define('GlusterNodeCount', config.gluster_count) - .define('AWS.Region', config.region) - .define('AWS.AvailabilityZones', public_availability_zone) - .define('AWS.PublicSubnetIDsAndAZ', [public_subnet, public_availability_zone].join(':')) - .define('AWS.PrivateSubnetIDsAndAZ', [private_subnet, private_availability_zone].join(':')) - .define('AWS.Keypair', bootstrap_tag) - .define('AWS.KeypairFile', '/home/ubuntu/.ssh/id_rsa') - .define('AWS.JumpboxCIDR', '0.0.0.0/0') - .define('AWS.VPCID', vpc) - .define('AWS.LinuxAMI', ami) - .define('HCPDomainName', config.domain_name) - .save! - end - - Contract None => Bool - def jumpbox_running? - ec2 - .instances - .select { |instance| instance.instance_id == jumpbox } - .map { |instance| instance.state.name } - .first == 'running' - end - - Contract None => Any - def configure_jumpbox - private_key = ssh_key.private_file - properties = bootstrap_properties.file - package = config.hdp_package_url - - ssh.to(jumpbox_ip) do - '/home/ubuntu/.ssh/id_rsa'.tap do |target| - execute :rm, '-f', target - upload! private_key, target - execute :chmod, '-w', target - end - - upload! properties, '/home/ubuntu/bootstrap.properties' - - as :root do - execute :apt, *%w(install --assume-yes genisoimage aria2) - execute :rm, '-f', '/opt/bootstrap.deb' - execute :aria2c, '--continue=true', '--dir=/opt', '--out=bootstrap.deb', package - execute :dpkg, *%w(--install /opt/bootstrap.deb) - end - end - end - - Contract None => Bool - def requires_human_oversight? - ['false', 'nil', nil].include? ENV['BOOTSTRAP_WITHOUT_HUMAN_OVERSIGHT'] - end - - Contract None => Any - def launch - return false if requires_human_oversight? - - access_key_id = ec2.api.config.credentials.credentials.access_key_id - secret_access_key = ec2.api.config.credentials.credentials.secret_access_key - - ssh.to(jumpbox_ip) do - with(aws_access_key_id: access_key_id, aws_secret_access_key: secret_access_key) do - execute :bootstrap, *%w(install bootstrap.properties) - end - end - end - - private - - Contract None => SSH::Client - def ssh - @ssh ||= SSH::Client.new(ssh_key.private_file) - end - - Contract None => HDP::BootstrapProperties - def bootstrap_properties - @hdp ||= HDP::BootstrapProperties.new - end - - Contract None => Amazon::EC2 - def ec2 - @ec2 ||= Amazon::EC2.new - end - - Contract None => Amazon::IAM - def iam - @iam ||= Amazon::IAM.new - end - - Contract None => Config - def config - @config ||= Config.new - end - - Contract None => Moneta::Proxy - def cache - @cache ||= Moneta.new :File, dir: config.cache_path - end + class BootstrapAgent < Agent end end