Ruby calling AWS ELB functions - ruby

I'm writing some Ruby scripts to wrap AWS ELB command line calls, mostly so that I can act on several ELB instances simultaneously. One task is to use the elb-describe-instance-health call to see what instance IDs are attached to this ELB.
I want to match the Instance ID to a nickname we have set up for those instances, so that I can see at a glance what machines area connected to the ELB, without having to look up the instance names.
So I am issuing:
cmd = "elb-describe-instance-health #{elbName}"
value = `#{cmd}`
Passing the elb name into the call. This returns output such as:
INSTANCE_ID i-jfjtktykg InService N/A N/A
INSTANCE_ID i-ujelforos InService N/A N/A
One line appear for each instance in the ELB. There are two spaces between each field.
What I need to get is the second field, which is the actual instance ID. Basically I'm trying to get each line returned, turn it into an array, get the 2nd field, which I can then use to lookup our server nickname.
Not sure if this is the right approach, but any suggestions on how to get this done are very welcome.

The newly released aws-sdk gem supports Elastic Load Balancing (AWS::ELB). If you want to get a list of instance ids attached to your load balancer you can do the following:
AWS.config(:access_key_id => '...', :secret_access_key => '...')
elb = AWS::ELB.new
intsance_ids = elb.load_balancers['LOAD_BALANCER_NAME'].instances.collect(&:id)
You could also use EC2 to store your instance nicknames.
ec2 = AWS::EC2.new
ec2.instances['INSTANCE_ID'].tags['nickname'] = 'NICKNAME'
Assuming your instances are tagged with their nicknames, you could collect them like so:
elb = AWS::ELB.new
elb.load_balancers['LOAD_BALANCER_NAME'].instances.collect{|i| i.tags['nickname'] }

A simple way to extract the second column would be something like this:
ids = value.split("\n").collect { |line| line.split(/\s+/)[1] }
This will leave the second column values in the Array ids. All this does is breaks the value into lines, breaks each line into whitespace delimited columns, and then extracts the second column.
There's probably no need to try to be too clever for something like this, a simple and straight forward solution should be sufficient.
References:
collect
split

Related

how can I get ALL records from route53?

how can I get ALL records from route53?
referring code snippet here, which seemed to work for someone, however not clear to me: https://github.com/aws/aws-sdk-ruby/issues/620
Trying to get all (I have about ~7000 records) via resource record sets but can't seem to get the pagination to work with list_resource_record_sets. Here's what I have:
route53 = Aws::Route53::Client.new
response = route53.list_resource_record_sets({
start_record_name: fqdn(name),
start_record_type: type,
max_items: 100, # fyi - aws api maximum is 100 so we'll need to page
})
response.last_page?
response = response.next_page until response.last_page?
I verified I'm hooked into right region, I see the record I'm trying to get (so I can delete later) in aws console, but can't seem to get it through the api. I used this: https://github.com/aws/aws-sdk-ruby/issues/620 as a starting point.
Any ideas on what I'm doing wrong? Or is there an easier way, perhaps another method in the api I'm not finding, for me to get just the record I need given the hosted_zone_id, type and name?
The issue you linked is for the Ruby AWS SDK v2, but the latest is v3. It also looks like things may have changed around a bit since 2014, as I'm not seeing the #next_page or #last_page? methods in the v2 API or the v3 API.
Consider using the #next_record_name and #next_record_type from the response when #is_truncated is true. That's more consistent with how other paginations work in the Ruby AWS SDK, such as with DynamoDB scans for example.
Something like the following should work (though I don't have an AWS account with records to test it out):
route53 = Aws::Route53::Client.new
hosted_zone = ? # Required field according to the API docs
next_name = fqdn(name)
next_type = type
loop do
response = route53.list_resource_record_sets(
hosted_zone_id: hosted_zone,
start_record_name: next_name,
start_record_type: next_type,
max_items: 100, # fyi - aws api maximum is 100 so we'll need to page
)
records = response.resource_record_sets
# Break here if you find the record you want
# Also break if we've run out of pages
break unless response.is_truncated
next_name = response.next_record_name
next_type = response.next_record_type
end

List Tag Value - EC2 Boto3

My understanding is I'm supposed to use resource when using Boto3 :)
The following returns all the key/value pairs; how would I get a specific key value? I'm looking print out the name given to the instance.
ec2 = boto3.resource('ec2')
for instance in ec2.instances.all():
print (instance.tags)
You can use either the boto3 resource or client interfaces. The resource interface is a higher level which is easier (simpler) to work with. The client interface is lower layer and you have more fine grained control. Start off with using resource and later switch to client as you better understand Python / boto3 / AWS SDKs.
Here is an example that will print the Value value.
The key parts to understand is that instance.tags is an array of Python dict (dictionary). You need to loop thru this array to get to each "Value". When accessing a dict you use this syntax ['name_of_item'].
AWS stores tags as Key and Value. These are the names to use when processing the dict.
import boto3
ec2 = boto3.resource('ec2')
for instance in ec2.instances.all():
print (instance.tags)
for tag in instance.tags:
print(tag['Value'])

How do I insert an encrypted data bag item value into a Chef recipe?

I've created an encrypted data bag value that I'm trying to load into a chef recipe.
knife data bag show foo bar --secret_file secret.key
Encrypted data bag detected, decrypting with provided secret.
id: bar
pass: p4ssw0rd
I'm trying to get the pass value to load up as a variable in a bash resource, and have the encrypted_data_bag_secret in /etc/chef on the client (hence no secret key show, reverting to default /etc/chef location):
dbag = Chef::EncryptedDataBagItem.load("foo", "bar")
foo_pass = dbag["pass"]
I've also tried using the recipe DSL instead of Chef::EncryptedDataBadItem method:
dbag = data_bag_item('foo', 'bar')
foo_pass = dbag["pass"]
And then loading it into a bash resource:
bash 'install_mysql' do
code <<-EOC
...
sudo mysqladmin -u root password {foo_pass}
...
EOC
end
I had a few questions regarding this process.
i) Will Chef::EncryptedDataBagItem.load be deprecated and replaced with data_bag_item; should I use one over the other?
ii) Am I pulling the dbag["pass"] using the correct methods; how would I grab the 'pass' value from inside foo (data bag) => bar (item?)
iii) To call the foo_pass variable inside the bash resource, do I just encapsulate the variable in curly braces {}, or am I missing something here?
iv) Is there a better method than what I am trying out?
I've tried adding the following to see if I can see the variable value printed to screen when running the chef-client, but it's not showing me any of the text or values:
puts "foo_pass equals 1:{foo_pass} 2:#{foo_pass}'
I've been hammering away at this for half the day, and was hoping to get some more experienced responses as how to handle this.
Yes prefer data_bag_item in most cases, it is more correct.
Yes, that is correct.
You need #{foo_pass}, with the leading #.

passing variables to cloud-config chef?

I'm trying to create a user-data script with cloud-init using the chef features. I've run into a limitation and I'm wondering if there's a way around it. I need my node names to be unique since the chef server will only accept a client with a unique name. I've tried several things to pass either a datetime variable or the instance ID, but I can't seem to pass variables to the node_name section.
node_name: "server-app-$INSTANCE_ID"
or
node_name: "server-app-$(date +%s)"
Is there a way to escape this so that it doesn't get interpreted literally?
Chef encountered an error attempting to create the client "server-app-$INSTANCE_ID"
You'll have to do the expansion before you write out the client.rb:
echo "node_name '$(date +%s)' >> /etc/chef/client.rb"
Not that this could still collide if two machines boot in the same second. I would highly recommend using the EC2 instance ID or IP address instead. You can fetch these from the metadata server. See https://github.com/coderanger/brix/blob/master/packer/client-bootstrap.sh#L26-L37 for an example.

how to get an aws instance given I have the instance's ip

Given I have an aws instance IP, how can I get the EC2 instance collection object via the ruby aws-sdk's filter option. For example
#ec2.instances.filter(valid_filter_name, ec2_instance_ip)
I've tried 'public_ip_address' and 'public_ip' as the filter name but those didn't work. I'm using this API doc http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/FilteredCollection.html#filter-instance_method, but it's does not mentioned what the valid parameters are.
It turns out the correct parameter to use (by trial & error) is 'ip-address'. Here's an example:
#ec2.instances.filter('ip-address', ec2_instance_ip)

Resources