passing variables to cloud-config chef? - amazon-ec2

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.

Related

Automating MSK details retrival with Makefile and JSON

I'm currently working on a cloudformation script that requires a lot of MSK configuration details to run. I am working on a makefile that runs the command
#aws kafka list-clusters
This returns a json-like structure that can be found here . Most of the details I require are in that structure, is there a way to retrieve each of them without having to save the output and then parse through the structure..? All of this would be done within the makefile, so that it can be plugged directly into the cloudformation and wouldn't require manual input/hardcoded values.
I hope I'm not missing something simple, Thanks!
Right, seems it was as simple as using a --query.
So let's say I wanted to get the ARN of the cluster (if there is only one on the region which there is in my case)
I'd call
CLUSTER_ARN = #aws kafka list-clusters --query 'ClusterInfoList[0].ClusterArn'
This will call the Shell command to list only the first kafka cluster, and return the ClusterArn value and store it in CLUSTER_ARN to be used throughout the Makefile.
Please read here to see more about filtering :)

Use Chef to Set Environment Variables for different Windows Users

How can I use Use Chef to set Environment Variables for different Windows Users?
I've tried using the Execute block like so:
execute 'setx DUMMYENV' do
command 'setx DUMMYENV "THISISDUMMY"'
user "UserA"
end
However this requires a password and I don't want the password in plaintext in any of the recipes so this solution doesn't seem viable.
I've also thought about doing something with registry_key like so:
registry_key 'HKEY_USERS\S-1-5-21-0123456789-012345678-...\Environment' do
values [{name: 'DUMMYENV', type: :string, data: '1'},
{name: 'ENVTEST', type: :string, data: 'TESTING'}
]
action :create
end
The only problem I have with this is I'm not sure if the SID for each of the User accounts I'm trying to modify will change between machines. I can probably find a solution to loop through the available HKEY_Users and parse out the ones I'm looking for however this seems tedious.
Another option that I've started to look into is windows_env however I haven't gotten to test it much and I don't see a robust option to specify the user.
End goal is to set different environment variables for different users with Chef running as admin. Is there any easy way to do this with Chef?
Thanks in advance!
You can indeed do this with windows_env, though it isn't reflected in the documentation yet: https://github.com/chef/chef/blob/master/lib/chef/resource/windows_env.rb#L39
windows_env 'DUMMYENV' do
value 'THISISDUMMY'
user 'UserA'
end
Remember that if you're talking about scheduled tasks using env vars, you have to reboot before it will be visible.

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 #.

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)

Ruby calling AWS ELB functions

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

Resources