How can I create a custom :host_role fact from the hostname? - ruby

I'm looking to create a role based on host name prefix and I'm running into some problems. Ruby is new to me and although I've done extensive searching for a solution, I'm still confused.
Host names look like this:
work-server-01
home-server-01
Here's what I've written:
require 'facter'
Facter.add('host_role') do
setcode do
hostname_array = Facter.value(:hostname).split('-')
first_in_array = hostname_array.first
first_in_array.each do |x|
if x =~ /^(home|work)/
role = '"#{x}" server'
end
role
end
end
I'd like to use variable interpolation within my role assignment, but I feel like using a case statement along with 'when' is incorrect. Please keep in mind that I'm new to Ruby.
Would anybody have any ideas on how I might achieve my goal?

Pattern-Matching the Hostname Fact
The following is a relatively DRY refactoring of your code:
require 'facter'
Facter.add :host_role do
setcode do
location = case Facter.value(:hostname)
when /home/ then $&
when /work/ then $&
else 'unknown'
end
'%s server' % location
end
end
Mostly, it just looks for a regex match, and assigns the value of the match to location which is then returned as part of a formatted string.
On my system the hostname doesn't match either "home" or "work", so I correctly get:
Facter.value :host_role
#=> "unknown server"

Related

apply recipe to all the servers whose hostnames matches regexp using CHEF

I'm creating a chef recipe to apply a configuration change on all servers whose hostname matches a specific pattern using regexp. However, I'm not sure how to do it.
Example: my hostname looks like this:
dvabwichf01
dvcdwichf01
my recipe in default.rb is :
case node['hostname']
when '*ab*'
template "/tmp/regextest" do
source "test_ab.erb"
mode "0644"
end
else
template "/tmp/regextest" do
source "test_cd.erb"
mode "0644"
end
end
But this is not working as expected, only the "else" template is updating on all servers. please assist.
You would need to use an actual regex, not a string like you have there (also you're using fnmatch glob matching, not a regex). That would only fix when the hostname is literally *ab*. A regexp literal in Ruby usually looks like /whatever/. so when /ab/ in this case.
I used switch for choosing values by adding a method in my helper file (in my case I put it into / app / helpers / application_helper.rb
Example below:
def name_of_your_method(hostname)
case hostname
when "Host1"
"template_1"
when "Host2"
"template_2"
when "Host2"
"template_3"
when "Host3"
"template_4"
else
"template_default"
end
end
Then in your code you would use the name in your method:
<%= user.hostname %>
And in your table(data) you would have a column for hostname(in this example)
Hope this helps

How do I share object from main file to supporting file in ruby?

I have something similar.
# MAIN.RB
require 'sockets'
require_relative 'replies.rb'
hostname = 'localhost'
port = 6500
s = TCPSocket.open(hostname, port)
$connected = 0
while line = s.gets # Read lines from the socket
#DO A BUNCH OF STUFF
if line == "Hi"
reply line
end
end
s.close
Then I have the reply function in a secondary file.
# REPLIES.RB
def reply(input)
if input == "Hi"
s.write("Hello my friend.\n"
end
end
However calling on the object s from the second file does not seem to work. How would I go about making this work. I'm new to Ruby. I've searched google for the answer, but the only results I have found is with sharing variables across files. I could always do a return "Hello my friend.\n", but I rather be able to write to the socket object directly from the function in REPLIES.rb
Remember that variables are strictly local unless you expressly pass them in. This means s only exists in the main context. You can fix this by passing it in:
reply(s, line)
And on the receiving side:
def reply(s, input)
# ...
end
I'd strongly encourage you to try and indent things consistently here, this code is really out of sorts, and avoid using global variables like $connected. Using a simple self-contained class you could clean up this code considerably.
Also, don't add .rb extensions when calling require. It's implied.

Fact file was parsed but returned an empty data set

For my current module, I need to check if php version 5 or 7 is installed and created a fact for this. The fact file is stored in the modules directory in facts.d/packageversion.rb and has the following content:
#!/usr/bin/ruby
require 'facter'
Facter.add(:php_version) do
setcode do
if File.directory? '/etc/php5'
5
else
if File.directory? '/etc/php7'
7
else
0
end
end
end
end
But I can't use it in my module. In Puppet agent log, i get this error:
Fact file /var/lib/puppet/facts.d/packageversion.rb was parsed but
returned an empty data set
How can I solve this?
facts.d is the module directory for external facts. You could place this file into the external facts directory, but the expected output would need to be key-value pairs. This is not happening, so Puppet is not finding a data set for the fact. https://docs.puppet.com/facter/3.6/custom_facts.html#executable-facts-----unix
You have written this fact as a custom fact and not an external fact. Therefore, it needs to be placed inside the lib/facter directory in your module instead. Then it will function correctly. I notice this important information seems to have been removed from the latest Facter documentation, which probably lends to your confusion.
Also, consider using an elsif in your code for clarity and optimization:
if File.directory? '/etc/php5'
5
elsif File.directory? '/etc/php7'
7
else
0
end
What Matt Schuchard said.
Also, you might consider that the Approved Vox Populi Puppet module uses this code for PHP version:
Facter.add(:phpversion) do
setcode do
output = Facter::Util::Resolution.exec('php -v')
unless output.nil?
output.split("\n").first.split(' ').
select { |x| x =~ %r{^(?:(\d+)\.)(?:(\d+)\.)?(\*|\d+)} }.first
end
end
end
Note that Facter::Util::Resolution.exec is deprecated in favour of Facter::Core::Execution.exec.
Aside from that, you might consider this a better way of getting the PHP version.

How to assign file content to chef node attribute

I have fingreprint.txt at the location "#{node['abc.d']}/fingreprint.txt"
The contents of the file are as below:
time="2015-03-25T17:53:12C" level=info msg="SHA1 Fingerprint=7F:D0:19:C5:80:42:66"
Now I want to retrieve the value of fingerprint and assign it to chef attribute
I am using the following ruby block
ruby_block "retrieve_fingerprint" do
block do
path="#{node['abc.d']}/fingreprint.txt"
Chef::Resource::RubyBlock.send(:include, Chef::Mixin::ShellOut)
command = 'grep -Po '(?<=Fingerprint=)[^"]*' path '
command_out = shell_out(command)
node.default['fingerprint'] = command_out.stdout
end
action :create
end
It seems not to be working because of missing escape chars in command = 'grep -Po '(?<=Fingerprint=)[^"]*' path '.
Please let me know if there is some other way of assigning file content to node attribute
Two ways to answer this: first I would do the read (IO.read) and parsing (RegExp.new and friends) in Ruby rather than shelling out to grep.
if IO.read("#{node['abc.d']}/fingreprint.txt") =~ /Fingerprint=([^"]+)/
node.default['fingerprint'] = $1
end
Second, don't do this at all because it probably won't behave how you expect. You would have to take in to account both the two-pass loading process and the fact that default attributes are reset on every run. If you're trying to make an Ohai plugin, do that instead. If you're trying to use this data in later resources, you'll probably want to store it in a global variable and make copious use of the lazy {} helper.

Nested directory searching

I'm trying to make a program that searches through hopefully every directory, sub directory, sub sub directory and so on in C:\. I feel like I can take care of that part, but there's also the issue of the folder names. There may be case issues like a folder named FOO not being dected when my program searches for Foo or a giant if/else or case statement for multiple search criteria.
My questions are: 1. is there a way to ignore letter case? and 2. is there a way to make a more efficient statement for searching?
My current code:
#foldersniffer by Touka, ©2015
base = Dir.entries("C:\\")
trees = Dir.entries("#{base}")
trees.each do |tree|
if Dir.exist?("Foo")
puts "Found Folder \"Foo\" in C:\\"
elsif Dir.exist?("Bar")
puts "Found Folder \"Bar\" in C:\\"
else
puts "No folders found"
end
end
sleep
any help is appreciated.
edit: it's trying to scan files like bootmgr and it's giving me errors... I'm not sure how to fix that.
Consider using Dir.glob(...) and regular expressions for case insensitive matching:
Dir.glob('c:\\**\*') do |filename|
if filename =~ /c:\\(foo|bar)($|\\)/i
puts "Found #{filename}"
end
end
Case sensitivity for the Dir.glob argument is likely not relevant on Windows systems:
Note that this pattern is not a regexp, it’s closer to a shell glob. See File.fnmatch for the meaning of the flags parameter. Note that case sensitivity depends on your system (so File::FNM_CASEFOLD is ignored), as does the order in which the results are returned.
I am not expert enough to say for sure but I would look into File::FNM_CASEFOLD
https://lostechies.com/derickbailey/2011/04/14/case-insensitive-dir-glob-in-ruby-really-it-has-to-be-that-cryptic/

Resources