Skip to content

Commit 7c6651d

Browse files
committed
feat: add ALB IP address lookup by availability zone
Add new ELB module with alb_addresses method to retrieve ALB IP addresses grouped by availability zone. Supports querying ALBs by name with optional region override.
1 parent cc676aa commit 7c6651d

File tree

5 files changed

+163
-1
lines changed

5 files changed

+163
-1
lines changed

Gemfile.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ PATH
99
aws-sdk-ec2 (~> 1)
1010
aws-sdk-eks (~> 1)
1111
aws-sdk-elasticache (~> 1)
12+
aws-sdk-elasticloadbalancingv2 (~> 1)
1213
aws-sdk-iam (~> 1)
1314
aws-sdk-lambda (~> 1)
1415
aws-sdk-rds (~> 1)
@@ -53,6 +54,9 @@ GEM
5354
aws-sdk-elasticache (1.128.0)
5455
aws-sdk-core (~> 3, >= 3.225.0)
5556
aws-sigv4 (~> 1.5)
57+
aws-sdk-elasticloadbalancingv2 (1.134.0)
58+
aws-sdk-core (~> 3, >= 3.225.0)
59+
aws-sigv4 (~> 1.5)
5660
aws-sdk-iam (1.124.0)
5761
aws-sdk-core (~> 3, >= 3.225.0)
5862
aws-sigv4 (~> 1.5)

MovableInkAWS.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
1818
s.add_runtime_dependency 'aws-sdk-ec2', '~> 1'
1919
s.add_runtime_dependency 'aws-sdk-eks', '~> 1'
2020
s.add_runtime_dependency 'aws-sdk-elasticache', '~> 1'
21+
s.add_runtime_dependency 'aws-sdk-elasticloadbalancingv2', '~> 1'
2122
s.add_runtime_dependency 'aws-sdk-iam', '~> 1'
2223
s.add_runtime_dependency 'aws-sdk-lambda', '~> 1'
2324
s.add_runtime_dependency 'aws-sdk-rds', '~> 1'

lib/movable_ink/aws.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
require_relative 'aws/s3'
1010
require_relative 'aws/iam'
1111
require_relative 'aws/eks'
12+
require_relative 'aws/elb'
1213
require_relative 'aws/elasticache'
1314
require_relative 'aws/api_gateway'
1415
require_relative 'aws/lambda'
@@ -26,6 +27,7 @@ class AWS
2627
include Athena
2728
include S3
2829
include ElastiCache
30+
include ELB
2931
include ApiGateway
3032
include EKS
3133
include IAM
@@ -96,7 +98,9 @@ def run_with_backoff(quiet: false, tries: 9, expected_errors: [])
9698
MovableInk::AWS::Errors::NoEnvironmentTagError,
9799
Aws::IAM::Errors::LimitExceededException,
98100
Aws::IAM::Errors::RequestLimitExceeded,
99-
Aws::IAM::Errors::Throttling
101+
Aws::IAM::Errors::Throttling,
102+
Aws::ElasticLoadBalancingV2::Errors::Throttling,
103+
Aws::ElasticLoadBalancingV2::Errors::ThrottlingException
100104
sleep_time = (num+1)**2 + rand(10)
101105
if quiet
102106
(num >= tries - 1) ? notify_and_sleep(sleep_time, $!.class) : sleep(sleep_time)

lib/movable_ink/aws/elb.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
require 'aws-sdk-elasticloadbalancingv2'
2+
require 'ostruct'
3+
4+
module MovableInk
5+
class AWS
6+
module ELB
7+
def elbv2(region: my_region, client: nil)
8+
@elbv2_client ||= {}
9+
@elbv2_client[region] ||= (client) ? client : Aws::ElasticLoadBalancingV2::Client.new(region: region)
10+
end
11+
12+
def elbv2_with_retries(region: my_region, client: nil)
13+
@elbv2_client_with_retries ||= {}
14+
if (client)
15+
@elbv2_client_with_retries[region] ||= client
16+
else
17+
instance_credentials = Aws::InstanceProfileCredentials.new(retries: 5, disable_imds_v1: true)
18+
@elbv2_client_with_retries[region] ||= Aws::ElasticLoadBalancingV2::Client.new(region: region, credentials: instance_credentials)
19+
end
20+
end
21+
22+
# Get load balancer addresses with their availability zones
23+
#
24+
# @param name [String] the name of the Application Load Balancer
25+
# @param region [String] the AWS region (defaults to my_region)
26+
# @return [Array<OpenStruct>] an array of address objects with ip_address and availability_zone properties
27+
def alb_addresses(name:, region: my_region)
28+
result = run_with_backoff do
29+
elbv2_with_retries(region: region).describe_load_balancers(names: [name])
30+
end
31+
32+
load_balancer = result.load_balancers.first
33+
raise "Load balancer '#{name}' not found" unless load_balancer
34+
35+
addresses = []
36+
37+
load_balancer.availability_zones.each do |az|
38+
az.load_balancer_addresses.each do |addr|
39+
ip = addr.ip_address || addr.private_ipv4_address
40+
next unless ip
41+
42+
addresses << OpenStruct.new(
43+
ip_address: ip,
44+
availability_zone: az.zone_name,
45+
subnet_id: az.subnet_id
46+
)
47+
end
48+
end
49+
50+
addresses
51+
end
52+
end
53+
end
54+
end

spec/elb_spec.rb

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
require_relative '../lib/movable_ink/aws'
2+
3+
describe MovableInk::AWS::ELB do
4+
let(:aws) { MovableInk::AWS.new(availability_zone: 'us-east-1a') }
5+
6+
context 'alb_addresses' do
7+
let(:elbv2) { Aws::ElasticLoadBalancingV2::Client.new(stub_responses: true) }
8+
9+
it "should return ALB IP addresses with availability zones" do
10+
lb_data = elbv2.stub_data(:describe_load_balancers, load_balancers: [{
11+
load_balancer_name: 'test-alb',
12+
load_balancer_arn: 'arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/test-alb/50dc6c495c0c9188',
13+
availability_zones: [
14+
{
15+
zone_name: 'us-east-1a',
16+
subnet_id: 'subnet-12345',
17+
load_balancer_addresses: [
18+
{ ip_address: '10.0.1.5' }
19+
]
20+
},
21+
{
22+
zone_name: 'us-east-1b',
23+
subnet_id: 'subnet-67890',
24+
load_balancer_addresses: [
25+
{ ip_address: '10.0.2.7' }
26+
]
27+
}
28+
]
29+
}])
30+
31+
elbv2.stub_responses(:describe_load_balancers, lb_data)
32+
allow(aws).to receive(:elbv2_with_retries).and_return(elbv2)
33+
34+
addresses = aws.alb_addresses(name: 'test-alb')
35+
expect(addresses.count).to eq(2)
36+
expect(addresses[0].ip_address).to eq('10.0.1.5')
37+
expect(addresses[0].availability_zone).to eq('us-east-1a')
38+
expect(addresses[0].subnet_id).to eq('subnet-12345')
39+
expect(addresses[1].ip_address).to eq('10.0.2.7')
40+
expect(addresses[1].availability_zone).to eq('us-east-1b')
41+
expect(addresses[1].subnet_id).to eq('subnet-67890')
42+
end
43+
44+
it "should handle ALBs with no IP addresses" do
45+
lb_data = elbv2.stub_data(:describe_load_balancers, load_balancers: [{
46+
load_balancer_name: 'test-alb',
47+
load_balancer_arn: 'arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/test-alb/50dc6c495c0c9188',
48+
availability_zones: [
49+
{
50+
zone_name: 'us-east-1a',
51+
subnet_id: 'subnet-12345',
52+
load_balancer_addresses: []
53+
}
54+
]
55+
}])
56+
57+
elbv2.stub_responses(:describe_load_balancers, lb_data)
58+
allow(aws).to receive(:elbv2_with_retries).and_return(elbv2)
59+
60+
addresses = aws.alb_addresses(name: 'test-alb')
61+
expect(addresses.count).to eq(0)
62+
end
63+
64+
it "should raise error when load balancer not found" do
65+
lb_data = elbv2.stub_data(:describe_load_balancers, load_balancers: [])
66+
67+
elbv2.stub_responses(:describe_load_balancers, lb_data)
68+
allow(aws).to receive(:elbv2_with_retries).and_return(elbv2)
69+
70+
expect { aws.alb_addresses(name: 'nonexistent-alb') }.to raise_error("Load balancer 'nonexistent-alb' not found")
71+
end
72+
73+
it "should support querying ALBs in a different region" do
74+
elbv2_west = Aws::ElasticLoadBalancingV2::Client.new(stub_responses: true)
75+
lb_data = elbv2_west.stub_data(:describe_load_balancers, load_balancers: [{
76+
load_balancer_name: 'west-alb',
77+
load_balancer_arn: 'arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/west-alb/50dc6c495c0c9188',
78+
availability_zones: [
79+
{
80+
zone_name: 'us-west-2a',
81+
subnet_id: 'subnet-west1',
82+
load_balancer_addresses: [
83+
{ ip_address: '10.1.1.5' }
84+
]
85+
}
86+
]
87+
}])
88+
89+
elbv2_west.stub_responses(:describe_load_balancers, lb_data)
90+
allow(aws).to receive(:elbv2_with_retries).with(region: 'us-west-2').and_return(elbv2_west)
91+
92+
addresses = aws.alb_addresses(name: 'west-alb', region: 'us-west-2')
93+
expect(addresses.count).to eq(1)
94+
expect(addresses[0].ip_address).to eq('10.1.1.5')
95+
expect(addresses[0].availability_zone).to eq('us-west-2a')
96+
expect(addresses[0].subnet_id).to eq('subnet-west1')
97+
end
98+
end
99+
end

0 commit comments

Comments
 (0)