How do I extract these values from a Ruby array - ruby

I'm debugging some Ruby code that uses the AWS SDK to extract EC2 tags:
resource = Aws::EC2::Resource.new(client: client)
resource.instances({filters: fetch(:ec2_filters)}).each do |instance|
puts "DEBUG: #{instance.tags}"
app_tag = instance.tags.select { |t| t.key == 'application' }.pop.value
We recently updated the jmespath dependency to pick up this change.
BEFORE updating:
DEBUG: [#<struct Aws::EC2::Types::Tag key="application", value="api">, #<struct Aws::EC2::Types::Tag key="environment", value="production">]
AFTER updating jmespath
DEBUG: [{:key=>"application", :value=>"api"}, {:key=>"environment", :value=>"production"}]
The .select { |t| t.key == 'application' }. now throws an error:
ArgumentError: wrong number of arguments (given 0, expected 1)
Can anyone advise how to parse the new response format? (i.e. [{:key=>"application", :value=>"api"}...)
UPDATE 1
# This finds the correct item, but I'm not sure how to extract ':value'
puts "DEBUG: #{instance.tags.select { |t| t[:key] == 'application' }}"
DEBUG: [{:key=>"application", :value=>"api"}]
UPDATE 2
This works:
puts "DEBUG: #{instance.tags.select { |t| t[:key] == 'application' }[0][:value]}"
DEBUG: api

Before the change the value of instance.tags used to be a struct, where you can access the key attribute by using dot notation, now you have a hash so you can use :[] instead, e.g:
Struct.new(:key).new('hej').key # "hej"
({ key: 'hej' })[:key] # "hej"

Related

How to valid input to matches data in CSV file

I'm using Ruby 2.7 above. I've been working this task and still learning. I'm pretty sure that I am not using the right code. This task require me to do a mimic atm program. One of the requirements is where I need to check user valid inputs are matches the data in the CSV.file before user can access the program.
I'm using ruby (not allowed to use rails or any advance ruby code). I searched for similar program anywhere for reference but mostly does not involve with CSV file. How do I check that input from user is valid and matches in CSV file? I'm having trouble on how to do a validation and how to valid with two inputs (username and password). This program is run on command-line. Apologies if im not being clear enough. Can you tell me from my code where I'm going wrong please?
I have three .rb files and two csv files. I am not sure if I'm supposed to create two separate csv files.
function.rb (where all the functions)
login.rb (verify username and password from 'user.csv' file before proceed to system.rb file)
system.rb (the main where all data save or changes in 'account.csv' file)
below is function.rb file.
require 'csv'
class Function
def log_in(user)
CSV.foreach('user.csv', 'r', headers => true) do |row|
#check the user is valid, else error
if row[0] == uname && row[1] == pwd
puts "succesfully login"
ATMSystem.main_menu
end
end
if login == false
puts "invalid credentials."
Login.log_menu
end
end
login.rb file
require './function'
class Inn
def signin
function = AtmFunction.new
puts "Account login"
puts "Enter username"
uname = gets.chomp
puts "Enter password"
pwd = gets.chomp
user = [uname, pwd]
function.log_in(user)
end
end
Let's say this is your users.csv file:
name,password
bob,1234
alice,5678
This is one possible option.
Load the file into an array of hashes Enumerable#to_h and Hash#transform_keys:
require 'csv'
data_file = 'user.csv'
user_map = CSV.foreach(data_file, headers: true).map do |row|
row.to_h.transform_keys(&:to_sym)
end
user_map
#=> [{:name=>"bob", :password=>"1234"}, {:name=>"alice", :password=>"5678"}]
Then, given the input from the user:
input_username = 'bob'
input_password = '1234'
Check if user exists and in case compare the password:
user = user_map.find { |h| h[:name] == input_username }
#=> {:name=>"bob", :password=>"1234"}
user[:password] == input_password
#=> true
Check the password if Enumerable#find returns a non nil value: the user doesn't exist:
input_username = 'ron'
user = user_map.find { |h| h[:name] == input_username }
user
#=> nil
Following your implementation you can also write:
login_passed = false
CSV.foreach(data_file, headers: true) do |row|
login_passed = row['name'] == input_username && row['password'] == input_password
break if login_passed
end
login_passed
#=> true (or false)

append multiple structured custom facts

I'm trying to get multiple structured custom facts to append to my root key (called rag here), but instead they always replace the current value.
Q1: Is this the expected behavior of Facter.add ?
So to get it to work i created two external facts, and on the custom fact i just read their results and append to the root with type => :aggregate
I do get the expected result, which is:
:~# facter -p rag
{
role => "win-mbox",
ambiente => "producao",
enabled_services => [
"auditd",
"lvm2-monitor",
"mdmonitor",
"rhsmcertd",
"rsyslog",
"sshd",
"syslog",
"sysstat",
]
}
I'm breaking it in multiple files for better maintenance (each script outputs 1 key), but i feel like there should be a better way.
Q2: Is there a better way? I mean, not having to use a custom fact just to read the external facts values and append.
Below, details about the code:
/opt/puppetlabs/puppet/cache/facts.d/rag_system_services.rb
#!/usr/bin/env ruby
require 'facter'
require 'json'
retorno = Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) }
os_family = Facter.value(:osfamily)
if os_family == 'RedHat'
retorno[:rag_system_services] = `systemctl list-unit-files --no-legend --no-pager -t service --state=enabled`.scan(/(^.+?)\.service\s+enabled/i).flatten
else
retorno[:rag_system_services] = `rcconf --list | sort`.scan(/(^.+?)\s+on/i).flatten
end
puts JSON.pretty_generate(retorno)
/opt/puppetlabs/puppet/cache/facts.d/rag_role.rb
#!/usr/bin/env ruby
require 'facter'
require 'json'
retorno = Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) }
fqdn = Facter.value(:fqdn)
retorno[:rag_role] = if fqdn.start_with? 'win-'
case fqdn
when /-aio/ then 'win-aio'
when /-ldap/ then 'win-ldap'
when /-logger/ then 'win-logger'
when /-mbox/ then 'win-mbox'
when /-mta/ then 'win-mta'
when /-proxy/ then 'win-proxy'
end
elsif fqdn.include? 'lnx-'
case fqdn
when /balancer/ then 'lnx-balancer'
when /database/ then 'lnx-database'
when /nfs/ then 'lnx-nfs'
when /server/ then 'lnx-server'
end
else
case fqdn
when /^dns-/ then 'dns'
when /^elastic-/ then 'elastic'
when /^pre-auth/ then 'pre-auth'
when /^puppetserver/ then 'puppetserver'
end
end
puts JSON.pretty_generate(retorno)
/opt/puppetlabs/puppet/cache/lib/facter/rag.rb
Facter.add(:rag, :type => :aggregate) do
chunk(:ambiente) do
rag = {}
rag['ambiente'] = (Facter.value(:fqdn).include? 'hom-') ? 'homologacao' : 'producao'
rag
end
chunk(:enabled_services) do
rag = {}
rag['enabled_services'] = Facter.value(:rag_system_services)
rag
end
chunk(:role) do
rag = {}
rag['role'] = Facter.value(:rag_role)
rag
end
end

Update hash values with hash key

I'm facing a problem that I couldn't find a working solution yet.
I have my YAML config file for the environment, let's call it development.yml.
This file is used to create the hash that should be updated:
data = YAML.load_file(File.join(Rails.root, 'config', 'environments', 'development.yml'))
What I'm trying to accomplish is something along these lines. Let's suppose we have an element of the sort
data['server']['test']['user']
data['server']['test']['password']
What I want to have is:
data['server']['test']['user'] = #{Server.Test.User}
data['server']['test']['password'] = #{Server.Test.Password}
The idea is to create a placeholder for each value that is the key mapping for that value dynamically, going until the last level of the hash and replacing the value with the mapping to this value, concatenating the keys.
Sorry, it doesn't solve my problem. The location data['server']['test']['user'] will be built dynamically, via a loop that will go through a nested Hash. The only way I found to do it was to append to the string the key for the current iteration of the Hash. At the end, I have a string like "data['server']['test']['name']", which I was thinking on converting to a variable data['server']['test']['name'] and then assigning to this variable the value #{Server.Test.Name}. Reading my question I'm not sure if this is clear, I hope this helps to clarify it.
Input sample:
api: 'active'
server:
test:
user: 'test'
password: 'passwordfortest'
prod:
user: 'nottest'
password: 'morecomplicatedthantest'
In this case, the final result should be to update this yml file in this way:
api: #{Api}
server:
test:
user: #{Server.Test.User}
password: #{Server.Test.Password}
prod:
user: #{Server.Prod.User}
password: #{Server.Prod.Password}
It sounds silly, but I couldn't figure out a way to do it.
I am posting another answer now since I realize what the question is all about.
Use Iteraptor gem:
require 'iteraptor'
require 'yaml'
# or load from file
yaml = <<-YAML.squish
api: 'active'
server:
test:
user: 'test'
password: 'passwordfortest'
prod:
user: 'nottest'
password: 'morecomplicatedthantest'
YAML
mapped =
yaml.iteraptor.map(full_parent: true) do |parent, (k, _)|
v = parent.map(&:capitalize).join('.')
[k, "\#{#{v}}"]
end
puts YAML.dump(mapped)
#⇒ ---
# api: "#{Api}"
# server:
# test:
# user: "#{Server.Test.User}"
# password: "#{Server.Test.Password}"
# prod:
# user: "#{Server.Prod.User}"
# password: "#{Server.Prod.Password}"
puts YAML.dump(mapped).delete('"')
#⇒ ---
# api: #{Api}
# server:
# test:
# user: #{Server.Test.User}
# password: #{Server.Test.Password}
# prod:
# user: #{Server.Prod.User}
# password: #{Server.Prod.Password}
Use String#%:
input = %|
data['server']['host']['name'] = %{server_host}
data['server']['host']['user'] = %{server_host_user}
data['server']['host']['password'] = %{server_host_password}
|
puts (
input % {server_host: "Foo",
server_host_user: "Bar",
server_host_password: "Baz"})
#⇒ data['server']['host']['name'] = Foo
# data['server']['host']['user'] = Bar
# data['server']['host']['password'] = Baz
You can not add key-value pair to a string.
data['server']['host'] # => which results in a string
Option 1:
You can either save Server.Host as host name in the hash
data['server']['host']['name'] = "#{Server.Host}"
data['server']['host']['user'] = "#{Server.Host.User}"
data['server']['host']['password'] = "#{Server.Host.Password}"
Option 2:
You can construct the hash in a single step with Host as key.
data['server']['host'] = { "#{Server.Host}" => {
'user' => "#{Server.Host.User}",
'password' => "#{Server.Host.Password}"
}
}

Ruby. is there a way to construct a command and send it to execution?

The following code is giving me AWS API timeout error from time to time.
It transfers aws volume tags to snapshot tags one by one. I think this + other api requests overload api queue.
I wonder is there any way to construct ec2Client.create_tags command with ALL tags in it and execute it instead of looping it for each tag like below?
inst.tags.each do |tag|
puts "Tag Key : #{tag.key} , Tag Value : #{tag.value}\n"
if tag.key.downcase != "backup"
ec2Client.create_tags({
dry_run: $dry_run,
resources: [newImage.image_id],
tags: [{
key: "#{tag.key}",
value: "#{tag.value}",
},],
})
end
sleep (1.0/2.0)
end
I haven't tested this, but something like this should return an array of all the tags and pass it to the create_tags method.
def create_tags(tags)
tag_list = []
tags.each do |tag|
puts "Tag Key : #{tag.key} , Tag Value : #{tag.value}\n"
unless tag.key.casecmp('backup').zero?
tag_list << { key: tag.key.to_s, value: tag.value.to_s }
end
end
end
ec2Client.create_tags(dry_run: $dry_run,
resources: [newImage.image_id],
tags: create_tags(inst.tags))

ruby aws sdk v2 how to get the value of snapshot tag

The result of p snapshot.tags is:
[#<struct Aws::EC2::Types::Tag key="snapshot_expire", value="2016-09-23 08:30:12 +0000">]
How do I get only the value of Tag Key snapshot_expire without the hour:sec:min (2016-09-23) ?
This is my code:
resp = ec2.describe_snapshots(owner_ids:['1234']) #.each do |snapshot|
resp.snapshots.each do |snapshot|
p snapshot.snapshot_id
p snapshot.tags
end
I know that the regular aws cli tool has this option.
Thanks!
Reference: http://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Client.html#describe_snapshots-instance_method
The 'snapshots' are returned as objects. You can get the specific snapshot data from the object using methods; see the documentation above. The snapshot ID is available by calling snapshot.snapshot_id. Tags are snapshot.tags.
require 'aws-sdk'
require 'date'
ec2 = Aws::EC2::Client.new(region: 'us-east-1')
snapshot_response = ec2.describe_snapshots(owner_ids:['xxx'])
snapshot_response.snapshots.each do |snapshot|
tagkeyexpire = snapshot.tags.select{|tag| tag.key == 'snapshot_expire'}
snapid = snapshot.snapshot_id
expiredate = Date.parse(tagkeyexpire[0].value) unless tagkeyexpire == []
puts "#{snapid} : #{expiredate}"
end
Output looks like
snap-01234567 : 2016-09-30

Resources