Ruby key / value for each loop debug - ruby

For various reasons I have a chef recipe that I want to use add multiple groups to a system with. I've defined a data bag with a key / value structure that provides the group name as the key and the guid as the value eg:
{
"id": "default_groups",
"group": {
"sales": "200",
"marketing": "800",
"csr": "1000",
"devel": "9000",
"scientists": "500"
}
}
I was wanting to use the group resource in a for each loop but I don't seem to be referencing the data in the data bag correctly:
user_groups = data_bag_item('groups', 'default_groups')
%w{"#{user_groups['group']}"}.each do |usr|
group "#{usr}.key" do
action :create
gid "#{usr}.value"
end
end
Can anyone explain what I'm doing wrong with my syntax?
The error output from the chef run is as follows:
ERROR: group["#{user_groups['group']}".key] (cookbook_groups::recipe_groups line 10) had an error: Mixlib::ShellOut::ShellCommandFailed: Expected process to exit with [0], but received '3'
---- Begin output of ["groupadd", "-g", "\"\#{user_groups['group']}\".value", "\"\#{user_groups['group']}\".key"] ----
STDOUT:
STDERR: groupadd: invalid group ID '"#{user_groups['group']}".value'
---- End output of ["groupadd", "-g", "\"\#{user_groups['group']}\".value", "\"\#{user_groups['group']}\".key"] ----
Ran ["groupadd", "-g", "\"\#{user_groups['group']}\".value", "\"\#{user_groups['group']}\".key"] returned 3

You cannot combine string interpolation, word arrays and array/hash access like that.
This might work:
user_groups = data_bag_item('groups', 'default_groups')
user_groups['group'].each do |key, value|
group key do
action :create
gid value
end
end

Apparently, %w does not do what you think:
▶ %w{"#{user_groups['group']}"}
#⇒ ["\"\#{user_groups['group']}\""]
You probably need just:
user_groups['group'].each
or like.

Related

Refactor CHEF cookbooks

I have a CHEF recipe for package install on windows hosts. We used to use chef13 client; however we have upgraded to chef16, and we are getting some issues with the cookbook migration.
When I run the existing role as-it-is on a win2k16 chef16 kitchen platform, I get this error,
Chef::Exceptions::ImmutableAttributeModification
------------------------------------------------
Node attributes are read-only when you do not specify which precedence level to set. To set an attribute use code like `node.default["key"] = "value"'
and it points out to this section of the recipe
67: guid = guid.gsub(/{/, '').gsub(/}/, '') # remove {}
68: log "[#{role}][#{recipe_name}][#{app_name}]: GUID for installer #{app_name} is #{guid}"
69>> app_params.store('product_guid', guid) # add GUID to the list of parameters
70: end
Hence, what I understand that chef16 dosen't like the hash.store method.
I have tried some other alternate solutions to update the hash, however they do not work.
Using merge method:-
app_params.merge!({ :product_guid => 'guid' })
Using precedence level explicitly:- ( I have used .default, .override as well, however its the same symptom)
node.force_override!['app_params']['product_guid'] = guid
(reff from : https://github.com/chef/chef/issues/6563)
Note that when using the above methods, i do not get any failures, however the hash dosent get updated at all.
I have added few log statements to show this:-
log "[#{role}][#{recipe_name}][#{app_name}]: app_params.keys is #{app_params.keys}"
log "[#{role}][#{recipe_name}][#{app_name}]: app_params is #{JSON.pretty_generate(app_params)}"
* log[[my-role][my-recipe][my-pkgName]: app_params.keys is ["app_name", "full_path", "action"]] action write
* log[[my-role][my-recipe][my-pkgName]: app_params is {
"app_name": "my-pkgName",
"full_path": "https://myArtifactRepo/.../.../../my-pkgName.msi",
"action": "install"
}] action write
## ^^ hash keys and hash values (app_params.keys and app_params) BEFORE the force_override method is used
log "[#{role}][#{recipe_name}][#{app_name}]: app_params.keys is #{app_params.keys}"
log "[#{role}][#{recipe_name}][#{app_name}]: app_params is #{JSON.pretty_generate(app_params)}"
* log[[my-role][my-recipe][my-pkgName]: app_params.keys is ["app_name", "full_path", "action"]] action write
* log[[my-role][my-recipe][my-pkgName]: app_params is {
"app_name": "my-pkgName",
"full_path": "https://myArtifactRepo/.../.../../my-pkgName.msi",
"action": "install"
}] action write
## ^^ hash keys and hash values (app_params.keys and app_params) AFTER the force_override method is used
Note that the hash and hash key values has not been updated it is still,
["app_name", "full_path", "action"];
however we would expect
["app_name", "full_path", "action", "product_guid"]
and the hash itself like,
"app_name": "my-pkgName",
"full_path": "https://myArtifactRepo/.../.../../my-pkgName.msi",
"action": "install",
"product_guid" : XXXXYYY-0WRR-1234-ABCD-3ERDFR234GRT

How to use local variable of whitespace array in chef template resource

I am trying to use whitespace arrays in chef template, like below and when I run the chef-client to execute the recipe getting an error saying: option variables must be a kind of [Hash]! below is recipe file
abc = node['abc']
def1 = node['def']
abc_sit = abc['sit']
def_sit = def1['sit']
%w{abc_sit def_sit}.each do | client |
template "/etc/#{client}.sh" do
source 'tunnel.erb'
owner 'root'
group 'root'
variables ("#{client}") --> At this line I am getting error
end
end
The error I am getting when I run the chef-client:
option variables must be a kind of [Hash]! You passed "abc_sit"
As it says, you have to pass in a Hash. Perhaps something like variables myclient: client and then <%= #myclient %> in the template.

Aws lambda code explanation

Can anybody please explain the working of the below code.
"def lambda_handlerOut(event, context):
if len(event) > 0:
success=1
print("length of event outside for--"+str(len(event)))
for record in event['Records']:
print("length of event--"+str(len(event)))
bucket=record['s3']['bucket']['name']
key=record['s3']['object']['key']
print("Bucket--"+bucket)
print("File that triggered this event--"+key)
Thanks in advance.
Regards,
Eleena Jose
This is a Lambda that receives S3 events - for example a PutObject request that creates a new file.
The method is the standard Python function - take a look at the Lambda Function Handler Docs for more details.
The structure of the event is defined here but basically there are some number of Records that are being iterated through with and, for each record, the bucket and key are being extracted and printed.
So, in more detail (comments above the line they reference):
# standard lambda event handler definition
def lambda_handlerOut(event, context):
# make sure that something was given - likely unneeded
if len(event) > 0:
success=1
print("length of event outside for--"+str(len(event)))
# loop through each record in Records
for record in event['Records']:
print("length of event--"+str(len(event)))
# take a look at the event structure - just extracting parts
bucket=record['s3']['bucket']['name']
# key is the object name - that is, the file
key=record['s3']['object']['key']
print("Bucket--"+bucket)
print("File that triggered this event--"+key)
EDIT
As I linked to above, the data in the event object looks something like:
{
"Records":[
{
"eventVersion":"2.0",
"eventSource":"aws:s3",
"awsRegion":"us-east-1",
"eventTime":"1970-01-01T00:00:00.000Z",
"eventName":"ObjectCreated:Put",
"userIdentity":{
"principalId":"AIDAJDPLRKLG7UEXAMPLE"
},
"requestParameters":{
"sourceIPAddress":"127.0.0.1"
},
"responseElements":{
"x-amz-request-id":"C3D13FE58DE4C810",
"x-amz-id-2":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"
},
"s3":{
"s3SchemaVersion":"1.0",
"configurationId":"testConfigRule",
"bucket":{
"name":"mybucket",
"ownerIdentity":{
"principalId":"A3NL1KOZZKExample"
},
"arn":"arn:aws:s3:::mybucket"
},
"object":{
"key":"HappyFace.jpg",
"size":1024,
"eTag":"d41d8cd98f00b204e9800998ecf8427e",
"versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko",
"sequencer":"0055AED6DCD90281E5"
}
}
}
]
}
So, as an example, bucket=record['s3']['bucket']['name'] starts by getting the s3 record from the data which leaves:
"s3":{
"s3SchemaVersion":"1.0",
"configurationId":"testConfigRule",
"bucket":{
"name":"mybucket",
"ownerIdentity":{
"principalId":"A3NL1KOZZKExample"
},
"arn":"arn:aws:s3:::mybucket"
},
"object":{
"key":"HappyFace.jpg",
"size":1024,
"eTag":"d41d8cd98f00b204e9800998ecf8427e",
"versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko",
"sequencer":"0055AED6DCD90281E5"
}
}
From there, it gets the bucket stanza:
"bucket":{
"name":"mybucket",
"ownerIdentity":{
"principalId":"A3NL1KOZZKExample"
},
"arn":"arn:aws:s3:::mybucket"
}
and lastly, the name:
"name":"mybucket"
This is assigned to the variable bucket which is printed out later. The key (which is the file name in this example) works the same way but gets different parts of the event.
Does that make sense now?

Why does puppet think my custom fact is a string?

I am trying to create a custom fact I can use as the value for a class parameter in a hiera yaml file.
I am using the openstack/puppet-keystone module and I want to use fernet-keys.
According to the comments in the module I can use this parameter.
# [*fernet_keys*]
# (Optional) Hash of Keystone fernet keys
# If you enable this parameter, make sure enable_fernet_setup is set to True.
# Example of valid value:
# fernet_keys:
# /etc/keystone/fernet-keys/0:
# content: c_aJfy6At9y-toNS9SF1NQMTSkSzQ-OBYeYulTqKsWU=
# /etc/keystone/fernet-keys/1:
# content: zx0hNG7CStxFz5KXZRsf7sE4lju0dLYvXdGDIKGcd7k=
# Puppet will create a file per key in $fernet_key_repository.
# Note: defaults to false so keystone-manage fernet_setup will be executed.
# Otherwise Puppet will manage keys with File resource.
# Defaults to false
So wrote this custom fact ...
[root#puppetmaster modules]# cat keystone_fernet/lib/facter/fernet_keys.rb
Facter.add(:fernet_keys) do
setcode do
fernet_keys = {}
puts ( 'Debug keyrepo is /etc/keystone/fernet-keys' )
Dir.glob('/etc/keystone/fernet-keys/*').each do |fernet_file|
data = File.read(fernet_file)
if data
content = {}
puts ( "Debug Key file #{fernet_file} contains #{data}" )
fernet_keys[fernet_file] = { 'content' => data }
end
end
fernet_keys
end
end
Then in my keystone.yaml file I have this line:
keystone::fernet_keys: '%{::fernet_keys}'
But when I run puppet agent -t on my node I get this error:
Error: Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Evaluation Error: Error while evaluating a Function Call, "{\"/etc/keystone/fernet-keys/1\"=>{\"content\"=>\"xxxxxxxxxxxxxxxxxxxx=\"}, \"/etc/keystone/fernet-keys/0\"=>{\"content\"=>\"xxxxxxxxxxxxxxxxxxxx=\"}}" is not a Hash. It looks to be a String at /etc/puppetlabs/code/environments/production/modules/keystone/manifests/init.pp:1144:7 on node mgmt-01
I had assumed that I had formatted the hash correctly because facter -p fernet_keys output this on the agent:
{
/etc/keystone/fernet-keys/1 => {
content => "xxxxxxxxxxxxxxxxxxxx="
},
/etc/keystone/fernet-keys/0 => {
content => "xxxxxxxxxxxxxxxxxxxx="
}
}
The code in the keystone module looks like this (with line numbers)
1142
1143 if $fernet_keys {
1144 validate_hash($fernet_keys)
1145 create_resources('file', $fernet_keys, {
1146 'owner' => $keystone_user,
1147 'group' => $keystone_group,
1148 'subscribe' => 'Anchor[keystone::install::end]',
1149 }
1150 )
1151 } else {
Puppet does not necessarily think your fact value is a string -- it might do, if the client is set to stringify facts, but that's actually beside the point. The bottom line is that Hiera interpolation tokens don't work the way you think. Specifically:
Hiera can interpolate values of any of Puppet’s data types, but the
value will be converted to a string.
(Emphasis added.)

Chef Attribute file

I'm trying to pass attributes to my recipe at run-time of chef client.
following is my default.rb attribute file:
if node['os'] == 'windows'
if node.attribute?('temp')
default['param']['name'] = node['temp']['tempname']
else node.attribute?('base')
default['param']['name'] = node['base']['nodename']
end
end
I first ran the chef-client with only the base attribute defined in the node.json file. My node.json looked like :
{
"base": {
"nodename": "John"
}
}
Chef-client run was successfull and the attribute was set accordingly. i.e.
default['param']['name'] = "John"
Than I ran chef-client with both base and temp attribute defined in the node.json.
And here is my node.json file :
{
"base": {
"nodename": "John"
},
"temp": {
"tempname": "Mike"
}
}
Chef-client again ran successfully and the attribute was set accordingly.
default['param']['name'] = "Mike"
The problem is when I run the chef-client again and pass only base attribute from the node.json file(see below for the file), the code doesn't seem to enter the else loop. It just runs the `if' loop with old value for temp attribute(Mike).
{
"base": {
"nodename": "John-2"
}
}
Any ideas to where I'm going wrong.
The second time you ran chef-client, it saved the node[:temp][:nodename] attribute to the node object on the Chef server.
When you ran chef-client again, it loaded the node's attributes back from the server before reaching the attributes/default.rb file, so node[:temp][:nodename] was set when it reached the conditions.
I'm not sure what you're trying to achieve, but it would probably be best to explicitly tell which attribute you want to use through another attribute (and maybe assume a default for it), such as:
default[:name_attribute] = 'temp'
then,
if node['os'] == 'windows'
if node['name_attribute'] == 'temp'
default['param']['name'] = node['temp']['tempname']
else
default['param']['name'] = node['base']['nodename']
end
end

Resources