require 'pp' require 'applb/client_wrapper' require 'applb/converter' require 'applb/dsl' require 'applb/dsl/load_balancer' require 'applb/error' require 'applb/filterable' require 'applb/utils' module Applb class Client include Filterable MAGIC_COMMENT = <<-EOS # -*- mode: ruby -*- # vi: set ft=ruby : EOS def initialize(filepath, options = {}) @filepath = filepath @options = options end def apply dsl = load_file(@filepath) dsl_ec2s = dsl.ec2s aws_ec2s = client.load_balancers.group_by(&:vpc_id) dsl.ec2s.each do |vpc_id, dsl_ec2| aws_ec2 = aws_ec2s[vpc_id] || [] traverse_ec2(vpc_id, dsl_ec2, aws_ec2) end end def export result = {} lbs = client.load_balancers tags_by_arn = describe_tags(lbs) lbs.each do |lb| attributes = client.load_balancer_attributes(load_balancer_arn: lb.load_balancer_arn) target_groups = describe_target_groups(lb) listeners = describe_listeners(lb) rules_by_listener_arn = listeners.each_with_object({}) do |listener, rules_by_listener_arn| rules_by_listener_arn[listener.listener_arn] = describe_rules(listener) end (result[lb.vpc_id] ||= {})[lb.load_balancer_name] = export_lb( lb, attributes, target_groups, listeners, rules_by_listener_arn, ) end path = Pathname.new(@filepath) base_dir = path.parent if @options[:split_more] result.each do |vpc_id, lbs_by_name| lbs_by_name.each do |name, lbs| Converter.new({vpc_id => {name => lbs}}, tags_by_arn).convert do |vpc_id, dsl| alb_base_dir = base_dir.join("#{vpc_id}") FileUtils.mkdir_p(alb_base_dir) alb_file = alb_base_dir.join("#{name}.alb") Applb.logger.info("export #{alb_file}") open(alb_file, 'wb') do |f| f.puts MAGIC_COMMENT f.puts dsl end end end end elsif @options[:split] Converter.new(result, tags_by_arn).convert do |vpc_id, dsl| FileUtils.mkdir_p(base_dir) alb_file = base_dir.join("#{vpc_id}.alb") Applb.logger.info("export #{alb_file}") open(alb_file, 'wb') do |f| f.puts MAGIC_COMMENT f.puts dsl end end else dsls = [] Converter.new(result, tags_by_arn).convert do |vpc_id, dsl| dsls << dsl end FileUtils.mkdir_p(base_dir) Applb.logger.info("export #{path}") open(path, 'wb') do |f| f.puts MAGIC_COMMENT f.puts dsls.join("\n") end end end private def load_file(file) open(file) do |f| DSL.define(f.read, file, @options).result end end def traverse_ec2(vpc_id, dsl_ec2, aws_ec2) dsl_lb_by_name = dsl_ec2.load_balancers.group_by(&:name).each_with_object({}) do |(k, v), h| h[k] = v.first if target?(k) end aws_lb_by_name = aws_ec2.group_by(&:load_balancer_name).each_with_object({}) do |(k, v), h| h[k] = v.first if target?(k) end # create dsl_lb_by_name.reject { |n| aws_lb_by_name[n] }.each do |name, dsl_lb| aws_lb_by_name[name] = dsl_lb.create end # modify dsl_lb_by_name.each do |name, dsl_lb| next unless aws_lb = aws_lb_by_name.delete(name) dsl_lb.aws(aws_lb) traverse_lb(dsl_lb, aws_lb) end # delete aws_lb_by_name.each do |name, aws_lb| Applb.logger.info "Delete ELB v2 #{name}" aws_tgs = client.describe_target_groups( load_balancer_arn: aws_lb.load_balancer_arn, ).target_groups unless @options[:dry_run] client.delete_load_balancer(aws_lb.load_balancer_arn) # wait until load_balancer is deleted sleep 3 end aws_tgs.each do |tg| Applb.logger.info "Delete target_group associated #{tg.target_group_name}" next if @options[:dry_run] client.delete_target_group( target_group_arn: tg.target_group_arn, ) end end end def traverse_lb(dsl_lb, aws_lb) dsl_lb.modify_subnets dsl_lb.modify_security_groups dsl_lb.modify_ip_address_type dsl_lb.modify_load_balancer_attributes traverse_target_groups(dsl_lb, aws_lb) traverse_listeners(dsl_lb, aws_lb) end def traverse_target_groups(dsl_lb, aws_lb) aws_tg_by_name = @client.target_groups(load_balancer_arn: aws_lb.load_balancer_arn).group_by(&:target_group_name).each_with_object({}) do |(k, v), h| h[k] = v.first end dsl_tg_by_name = dsl_lb.target_groups.group_by(&:name).each_with_object({}) do |(k, v), h| h[k] = v.first end # create dsl_tg_by_name.reject { |n, _| aws_tg_by_name[n] }.each do |name, dsl_tg| aws_tg_by_name[name] = dsl_tg.create end # modify dsl_tg_by_name.each do |name, dsl_tg| aws_tg = aws_tg_by_name.delete(name) next unless aws_tg dsl_tg.aws(aws_tg).modify end aws_tg_by_name.each do |name, aws_tg| Applb.logger.info("Delete target group #{name}") next if @options[:dry_run] # client.modify_listener({}) TODO remove from listener first client.delete_target_group(target_group_arn: aws_tg.target_group_arn) end end def traverse_listeners(dsl_lb, aws_lb) aws_listener_by_port = @client.listeners(load_balancer_arn: aws_lb.load_balancer_arn).group_by(&:port).each_with_object({}) do |(k, v), h| h[k] = v.first end aws_target_group_by_name = @client.target_groups.group_by(&:target_group_name).each_with_object({}) do |(k, v), h| h[k] = v.first end aws_target_group_by_arn = aws_target_group_by_name.each_with_object({}) do |(k, v), h| h[v.target_group_arn] = v end dsl_listener_by_port = dsl_lb.listeners.group_by(&:port).each_with_object({}) do |(k, v), h| dsl_listener = v.first dsl_listener.load_balancer_arn = aws_lb.load_balancer_arn h[k] = dsl_listener end # create dsl_listener_by_port.reject { |port, _| aws_listener_by_port[port] }.each do |port, dsl_listener| # resolve target_group_arn by target_group_name target_group_name = dsl_listener.default_actions.first[:target_group_name] if target_group_name dsl_listener.default_actions.first[:target_group_arn] = aws_target_group_by_name[target_group_name].target_group_arn end aws_listener_by_port[port] = dsl_listener.create end # modify dsl_listener_by_port.each do |port, dsl_listener| aws_listener = aws_listener_by_port.delete(port) next unless aws_listener target_group_name = dsl_listener.default_actions.first[:target_group_name] if target_group_name dsl_listener.default_actions.first[:target_group_arn] = aws_target_group_by_name[target_group_name].target_group_arn end dsl_listener.aws(aws_listener).modify traverse_rule(dsl_listener, aws_listener, aws_target_group_by_name) end # delete aws_listener_by_port.each do |port, aws_listener| aws_actions = client.describe_rules(listener_arn: aws_listener.listener_arn).rules.map(&:actions).flatten Applb.logger.info("#{aws_lb.load_balancer_name} Delete listener for port #{port}") unless @options[:dry_run] client.delete_listener(listener_arn: aws_listener.listener_arn) end (aws_actions + aws_listener.default_actions).each do |action| aws_tg = aws_target_group_by_arn.delete(action.target_group_arn) next unless aws_tg Applb.logger.info("#{aws_lb.load_balancer_name} Delete target_group associated #{aws_tg.target_group_name}") next if @options[:dry_run] client.delete_target_group(target_group_arn: action.target_group_arn) end end end class TargetGroupResolveError < Error end def traverse_rule(dsl_listener, aws_listener, aws_target_group_by_name) dummy_idx = 0 dsl_rule_by_arn = (dsl_listener.rules || []).each_with_object({}) do |dsl_rule, h| # give dummy arn for grouping arn = dsl_rule.rule_arn ? dsl_rule.rule_arn : "dummy-#{(dummy_idx += 1)}" # set listener_arn here dsl_rule.listener_arn = aws_listener.listener_arn h[arn] = dsl_rule end aws_rules = client.describe_rules(listener_arn: aws_listener.listener_arn).rules aws_rule_by_arn = aws_rules.reject(&:is_default).group_by(&:rule_arn).each_with_object({}) do |(k, v), h| h[k] = v.first end # create dsl_rule_by_arn.reject { |arn, _| aws_rule_by_arn[arn] }.each do |arn, dsl_rule| # resolve target_group_arn by target_group_name target_group_name = dsl_rule.actions.first[:target_group_name] if target_group_name Applb.logger.debug("Resolve target_group_arn by target_group_name. -> #{target_group_name}") aws_tg = aws_target_group_by_name[target_group_name] unless aws_tg Applb.logger.error("AWS target groups by name:\n#{aws_target_group_by_name.pretty_inspect}") raise TargetGroupResolveError.new("target_group_name #{target_group_name}") end dsl_rule.actions.first[:target_group_arn] = aws_tg.target_group_arn end rule = dsl_rule.create next if @options[:dry_run] dsl_rule_by_arn[rule.rule_arn] = dsl_rule_by_arn.delete(arn) end # modify dsl_rule_by_arn.each do |arn, dsl_rule| aws_rule = aws_rule_by_arn.delete(arn) next unless aws_rule # resolve target_group_arn by target_group_name target_group_name = dsl_rule.actions.first[:target_group_name] if target_group_name Applb.logger.debug("Resolve target_group_arn by target_group_name. -> #{target_group_name}") aws_tg = aws_target_group_by_name[target_group_name] unless aws_tg Applb.logger.error("AWS target groups by name:\n#{aws_target_group_by_name.pretty_inspect}") raise TargetGroupResolveError.new("target_group_name #{target_group_name}") end dsl_rule.actions.first[:target_group_arn] = aws_tg.target_group_arn end dsl_rule.aws(aws_rule).modify end # delete aws_rule_by_arn.values.each do |aws_rule| Applb.logger.info("Delete rule #{aws_rule.conditions.first[:values].first}") next if @options[:dry_run] Applb.logger.debug("deleting rule_arn #{aws_rule.rule_arn}") client.delete_rule(rule_arn: aws_rule.rule_arn) end end # @param [Aws::ElasticLodaBalancingV2::Types::LoadBalancer] lb def describe_tags(lbs) result = {} arns = lbs.map(&:load_balancer_arn) unless arns.empty? resp = client.describe_tags(resource_arns: lbs.map(&:load_balancer_arn)) resp.tag_descriptions.each do |tag_desc| result[tag_desc.resource_arn] = Hash[tag_desc.tags.map { |tag| [tag.key, tag.value] }] end end result end def describe_target_groups(lb) client.target_groups(load_balancer_arn: lb.load_balancer_arn) end def describe_listeners(lb) client.listeners(load_balancer_arn: lb.load_balancer_arn) end def describe_rules(listener) client.rules(listener_arn: listener.listener_arn) end # @param [Aws::ElasticLodaBalancingV2::Types::LoadBalancer] lb # @param [Array] attrs # @param [Array] target_groups # @param [Array] listeners # @param [Hash] rules_by_listener_arn def export_lb(lb, attrs, target_groups, listeners, rules_by_listener_arn) { availability_zones: lb.availability_zones.map { |az| Hash[az.each_pair.to_a] }, canonical_hosted_zone_id: lb.canonical_hosted_zone_id, created_time: lb.created_time, dns_name: lb.dns_name, ip_address_type: lb.ip_address_type, load_balancer_arn: lb.load_balancer_arn, load_balancer_name: lb.load_balancer_name, scheme: lb.scheme, security_groups: lb.security_groups, state: lb.state, type: lb.type, vpc_id: lb.vpc_id, attributes: export_attributes(attrs), target_groups: target_groups, listeners: listeners, rules_by_listener_arn: rules_by_listener_arn, } end # @param [Array] attrs def export_attributes(attrs) result = {} attrs.each do |attr| case attr.key when 'access_logs.s3.enabled' then (result['access_logs'] ||= {s3: {}})[:s3][:enabled] = attr.value when 'access_logs.s3.prefix' then result['access_logs'] ||= {s3: {}}[:s3][:prefix] = attr.value when 'access_logs.s3.bucket' then result['access_logs'] ||= {s3: {}}[:s3][:bucket] = attr.value else result[attr.key] = attr.value end end result end def client @client ||= ClientWrapper.new(@options) end end end