# frozen_string_literal: true require 'aws-sdk-elasticloadbalancingv2' require 'hako' require 'hako/error' module Hako module Schedulers class EcsElbV2 # @param [String] app_id # @param [String] region # @param [Hash] elb_v2_config # @param [Boolean] dry_run def initialize(app_id, region, elb_v2_config, dry_run:) @app_id = app_id @region = region @elb_v2_config = elb_v2_config @dry_run = dry_run end # @param [Aws::ECS::Types::LoadBalancer] ecs_lb # @return [nil] def show_status(ecs_lb) lb = describe_load_balancer elb_client.describe_listeners(load_balancer_arn: lb.load_balancer_arn).each do |page| page.listeners.each do |listener| puts " #{lb.dns_name}:#{listener.port} -> #{ecs_lb.container_name}:#{ecs_lb.container_port}" end end end # @return [Aws::ElasticLoadBalancingV2::Types::LoadBalancer] def describe_load_balancer elb_client.describe_load_balancers(names: [elb_name]).load_balancers[0] rescue Aws::ElasticLoadBalancingV2::Errors::LoadBalancerNotFound nil end # @return [Aws::ElasticLoadBalancingV2::Types::TargetGroup] def describe_target_group elb_client.describe_target_groups(names: [target_group_name]).target_groups[0] rescue Aws::ElasticLoadBalancingV2::Errors::TargetGroupNotFound nil end # @param [Fixnum] front_port # @return [nil] def find_or_create_load_balancer(_front_port) unless @elb_v2_config return false end load_balancer = describe_load_balancer if !load_balancer_given? && !load_balancer tags = @elb_v2_config.fetch('tags', {}).map { |k, v| { key: k, value: v.to_s } } elb_type = @elb_v2_config.fetch('type', nil) if elb_type == 'network' load_balancer = elb_client.create_load_balancer( name: elb_name, subnets: @elb_v2_config.fetch('subnets'), scheme: @elb_v2_config.fetch('scheme', nil), type: 'network', tags: tags.empty? ? nil : tags, ).load_balancers[0] Hako.logger.info "Created ELBv2(NLB) #{load_balancer.dns_name}" else load_balancer = elb_client.create_load_balancer( name: elb_name, subnets: @elb_v2_config.fetch('subnets'), security_groups: @elb_v2_config.fetch('security_groups'), scheme: @elb_v2_config.fetch('scheme', nil), type: @elb_v2_config.fetch('type', nil), tags: tags.empty? ? nil : tags, ).load_balancers[0] Hako.logger.info "Created ELBv2 #{load_balancer.dns_name}" end end target_group = describe_target_group if !target_group_given? && !target_group elb_type = @elb_v2_config.fetch('type', nil) target_group = if elb_type == 'network' elb_client.create_target_group( name: target_group_name, port: 80, protocol: 'TCP', vpc_id: @elb_v2_config.fetch('vpc_id'), target_type: @elb_v2_config.fetch('target_type', nil), ).target_groups[0] else matcher = if @elb_v2_config.key?('matcher') { http_code: @elb_v2_config.fetch('matcher')['http_code'], grpc_code: @elb_v2_config.fetch('matcher')['grpc_code'], } end elb_client.create_target_group( name: target_group_name, port: 80, protocol: 'HTTP', protocol_version: @elb_v2_config.fetch('protocol_version', 'HTTP1'), vpc_id: @elb_v2_config.fetch('vpc_id'), health_check_path: @elb_v2_config.fetch('health_check_path', nil), target_type: @elb_v2_config.fetch('target_type', nil), matcher: matcher, ).target_groups[0] end Hako.logger.info "Created target group #{target_group.target_group_arn}" end unless load_balancer_given? listener_ports = elb_client.describe_listeners(load_balancer_arn: load_balancer.load_balancer_arn).flat_map { |page| page.listeners.map(&:port) } @elb_v2_config.fetch('listeners').each do |l| params = { load_balancer_arn: load_balancer.load_balancer_arn, protocol: l.fetch('protocol'), port: l.fetch('port'), ssl_policy: l['ssl_policy'], default_actions: [{ type: 'forward', target_group_arn: target_group.target_group_arn }], } certificate_arn = l.fetch('certificate_arn', nil) if certificate_arn params[:certificates] = [{ certificate_arn: certificate_arn }] end unless listener_ports.include?(params[:port]) listener = elb_client.create_listener(**params).listeners[0] Hako.logger.info("Created listener #{listener.listener_arn}") end end end true end # @return [nil] def modify_attributes unless @elb_v2_config return nil end unless load_balancer_given? load_balancer = describe_load_balancer subnets = @elb_v2_config.fetch('subnets').sort if load_balancer && subnets != load_balancer.availability_zones.map(&:subnet_id).sort if @dry_run Hako.logger.info("elb_client.set_subnets(load_balancer_arn: #{load_balancer.load_balancer_arn}, subnets: #{subnets}) (dry-run)") else Hako.logger.info("Updating ELBv2 subnets to #{subnets}") elb_client.set_subnets(load_balancer_arn: load_balancer.load_balancer_arn, subnets: subnets) end end new_listeners = @elb_v2_config.fetch('listeners') if load_balancer current_listeners = elb_client.describe_listeners(load_balancer_arn: load_balancer.load_balancer_arn).listeners new_listeners.each do |new_listener| current_listener = current_listeners.find { |l| l.port == new_listener['port'] } if current_listener && new_listener['ssl_policy'] && new_listener['ssl_policy'] != current_listener.ssl_policy if @dry_run Hako.logger.info("elb_client.modify_listener(listener_arn: #{current_listener.listener_arn}, ssl_policy: #{new_listener['ssl_policy']}) (dry-run)") else Hako.logger.info("Updating ELBv2 listener #{new_listener['port']} ssl_policy to #{new_listener['ssl_policy']}") elb_client.modify_listener(listener_arn: current_listener.listener_arn, ssl_policy: new_listener['ssl_policy']) end end end end if @elb_v2_config.key?('load_balancer_attributes') attributes = @elb_v2_config.fetch('load_balancer_attributes').map { |key, value| { key: key, value: value } } if @dry_run if load_balancer Hako.logger.info("elb_client.modify_load_balancer_attributes(load_balancer_arn: #{load_balancer.load_balancer_arn}, attributes: #{attributes.inspect}) (dry-run)") else Hako.logger.info("elb_client.modify_load_balancer_attributes(load_balancer_arn: unknown, attributes: #{attributes.inspect}) (dry-run)") end else Hako.logger.info("Updating ELBv2 attributes to #{attributes.inspect}") elb_client.modify_load_balancer_attributes(load_balancer_arn: load_balancer.load_balancer_arn, attributes: attributes) end end end unless target_group_given? if @elb_v2_config.key?('target_group_attributes') target_group = describe_target_group attributes = @elb_v2_config.fetch('target_group_attributes').map { |key, value| { key: key, value: value } } if @dry_run if target_group Hako.logger.info("elb_client.modify_target_group_attributes(target_group_arn: #{target_group.target_group_arn}, attributes: #{attributes.inspect}) (dry-run)") else Hako.logger.info("elb_client.modify_target_group_attributes(target_group_arn: unknown, attributes: #{attributes.inspect}) (dry-run)") end else Hako.logger.info("Updating target group attributes to #{attributes.inspect}") elb_client.modify_target_group_attributes(target_group_arn: target_group.target_group_arn, attributes: attributes) end end end nil end # @return [nil] def destroy unless @elb_v2_config return false end unless load_balancer_given? load_balancer = describe_load_balancer if load_balancer if @dry_run Hako.logger.info("elb_client.delete_load_balancer(load_balancer_arn: #{load_balancer.load_balancer_arn})") else elb_client.delete_load_balancer(load_balancer_arn: load_balancer.load_balancer_arn) Hako.logger.info "Deleted ELBv2 #{load_balancer.load_balancer_arn}" end else Hako.logger.info "ELBv2 #{elb_name} doesn't exist" end end unless target_group_given? target_group = describe_target_group if target_group if @dry_run Hako.logger.info("elb_client.delete_target_group(target_group_arn: #{target_group.target_group_arn})") else deleted = false 30.times do begin elb_client.delete_target_group(target_group_arn: target_group.target_group_arn) deleted = true break rescue Aws::ElasticLoadBalancingV2::Errors::ResourceInUse => e Hako.logger.warn("#{e.class}: #{e.message}") end sleep 1 end unless deleted raise Error.new("Cannot delete target group #{target_group.target_group_arn}") end Hako.logger.info "Deleted target group #{target_group.target_group_arn}" end end end end # @return [String] def elb_name @elb_v2_config.fetch('load_balancer_name', "hako-#{@app_id}") end # @return [Boolean] def load_balancer_given? @elb_v2_config.key?('load_balancer_name') end # @return [String] def target_group_name @elb_v2_config.fetch('target_group_name', "hako-#{@app_id}") end # @return [Boolean] def target_group_given? @elb_v2_config.key?('target_group_name') end # @return [Hash] def load_balancer_params_for_service { target_group_arn: describe_target_group.target_group_arn, container_name: @elb_v2_config.fetch('container_name', 'front'), container_port: @elb_v2_config.fetch('container_port', 80), } end private def elb_client @elb_v2 ||= Aws::ElasticLoadBalancingV2::Client.new(region: @region) end end end end