Custom fact with home directorys as domains for puppet - bash

I'm trying to generate a custom fact called domains.
the idea is to list all the directories within /home but remove some default directory's such as centos, ec2-user, myadmin.
I'm using bash as I don't know ruby. so far my script outputs the list into a txt file which it then cats the answer for factors. but it is treated as one long answer and not multiple like an array?
My script is as follows:
#!/bin/bash
ls -m /home/ | sed -e 's/, /,/g' | tr -d '\n' > /tmp/domains.txt
cat /tmp/domains.txt | awk '{gsub("it_support,", "");print}'| awk '{gsub("ec2-user,", "");print}'| awk '{gsub("myadmin,", "");print}'| awk '{gsub("nginx", "");print}'| awk '{gsub("lost+found,", "");print}' > /tmp/domains1.txt
echo "domains={$(cat /tmp/domains1.txt)}"
exit
Foremans sees my domains as
facts.domains = "{domain1,domain2,domain3,domain4,lost+found,}"
I also need to remove lost+found, some how.
Any help or advice would be appreciated
Kevin

I'm also not familiar with ruby, but I have an idea for some workaround:
Please look at the following example about returning an array of network interfaces. Now to create domain_array fact use the following code:
Facter.add(:domain_array) do
setcode do
domains = Facter.value(:domains)
domain_array = domains.split(',')
domain_array
end
end

You can put a parser function to do this. Parser functions go inside:
modules/<modulename>/lib/puppet/parser/functions/getdomain.rb
Note: Parser function will compile only in the puppet master. See below for a custom fact that will run on the agent.
getdomain.rb can contain something like the following for your purpose:
module Puppet::Parser::Functions
newfunction(:getdomain, :type => :rvalue) do |args|
dnames=Array.new
Dir.foreach("/home/") do |d|
# Avoid listing directories starts with . or ..
if !d.start_with?('.') then
# You can put more names inside the [...] that you want to avoid
dnames.push(d) unless ['lost+found','centos'].include?(d)
end
end
domainlist=dnames.join(',')
return domainlist
end
end
You can call it from a manifest and assign to a variable:
$myhomedomains=getdomain()
$myhomedomains should return something similar to this : user1,user2,user3
.......
For a custom fact with similar code. You can put it in :
modules/<modulename>/lib/facter/getdomain.rb
Content of getdomain.rb :
Facter.add(:getdomain) do
setcode do
dnames=Array.new
Dir.foreach("/home/") do |d|
# Avoid listing directories starts with . or ..
if !d.start_with?('.') then
# You can put more names inside the [...] that you want to avoid
dnames.push(d) unless ['lost+found','centos'].include?(d)
end
end
getdomain=dnames.join(',')
getdomain
end
end
You can call the getdomain fact in any manifest, for example, calling it from the same module's init.pp :
notify { "$::getdomain" : }
will result in something similar :
Notice: /Stage[main]/Testmodule/Notify[user1,user2,user3]

Related

Make ARGV[0] optional in command line Ruby

I have the following code:
#!/usr/bin/env ruby
require 'yaml'
require 'json'
require 'getoptlong'
DEFAULT_CONF_FILE = 'conf.yaml'
opts = GetoptLong.new(
[ '--asn', '-a', GetoptLong::OPTIONAL_ARGUMENT ],
[ '--modify', '-m', GetoptLong::OPTIONAL_ARGUMENT ]
)
config_file = ARGV[0]
if config_file.to_s.empty?
config_file = DEFAULT_CONF_FILE
end
opts.each do |opt, arg|
case opt
when '--asn'
write_asn_database(arg,config_file)
when '--modify'
generate_modify_conf_file(arg,config_file)
end
end
This code generates some json files according to the given YAML configuration file. And what I am trying to do is:
If in the command line is given a conf file, the program will use that given file;
If not, it will use the default file.
Something like:
Example 1 (with a conf file):
$ ./my_script.rb new_conf_file.yaml -a
Example 2 (without conf file):
$ ./my_script.rb -a
The first example works, the second example gives me the following error:
No such file or directory # rb_sysopen - -a (Errno::ENOENT)
And it makes sense because the program assumes that the option -a is a file.
I'm new to Ruby and I never used the command line to run scripts.
OK, I would post it here. Please do not do this, since it’s just ugly:
config_file = ARGV[0]
unless File.exist?(config_file)
config_file = DEFAULT_CONF_FILE
end
From the very first paragraph of GetoptLong documentation:
The empty option -- (two minus symbols) is used to end option processing. This can be particularly important if options have optional arguments.
Use your code by calling a script as:
# ⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓
./my_script.rb -a -- new_conf_file.yaml
Also, one should put the opts reading part before dealing with ARGV explicitly.
And no, with GetoptLong it is impossible to parse the command line you provided properly.

Are there any existing methods for importing functions from other scripts without sourcing the entire script?

I am working on a large shell program and need a way to import functions from other scripts as required without polluting the global scope with all the internal functions from that script.
UPDATE: However, those imported functions have internal dependancies. So the imported function must be executed in the context of its script.
I came up with this solution and wonder if there is any existing strategy out there and if not, perhaps this is a really bad idea?
PLEASE TAKE A LOOK AT THE POSTED SOLUTION BEFORE RESPONDING
example usage of my solution:
main.sh
import user get_name
import user set_name
echo "hello $(get_name)"
echo "Enter a new user name :"
while true; do
read user_input < /dev/tty
done
set_name $user_input
user.sh
import state
set_name () {
state save "user_name" "$1"
}
get_name () {
state get_value "user_name"
}
As one approach, you could put a comment in the script to indicate where you want to stop sourcing:
$ cat script
fn() { echo "You are running fn"; }
#STOP HERE
export var="Unwanted name space pollution"
And then, if you are using bash, source it like this:
source <(sed '/#STOP HERE/q' script)
<(...) is process substitution and our process, sed '/#STOP HERE/q' script just extracts the lines from script until the stop line is reached.
Adding more precise control
We can select particular sections from a file if we add both start and stop flags:
$ cat script
export var1="Unwanted name space pollution"
#START
fn1() { echo "You are running fn1"; }
#STOP
export var2="More unwanted name space pollution"
#START
fn2() { echo "You are running fn2"; }
#STOP
export var3="More unwanted name space pollution"
And then source the file like this:
source <(sed -n '/#START/,/#STOP/p' script)
create standalone shel script that do this
will have 2 argument the file name and the function name
it will source the input file first
it will then use declare -f function name
in your code you can include functions like this
eval "./importfunctions.sh filename functionaname"
what is happening here :
step 1 basically read the file and source it in new shell environment . then it will echo the function declaration
step 2 will eval that function into our main code
So final result is as if we wrote just that function in our main script
When the functions in the script indent untill the closing } and all start with the keyword function, you can include specific functions without changing the original files:
largeshell.sh
#!/bin/bash
function demo1 {
echo "d1"
}
function demo2 {
echo "d2"
}
function demo3 {
echo "d3"
}
function demo4 {
echo "d4"
}
echo "Main code of largeshell... "
demo2
Now show how to source demo1() and forget demo4():
source <(sed -n '/^function demo1 /,/^}/p' largeshell.sh)
source <(sed -n '/^function demo3 /,/^}/p' largeshell.sh)
demo1
demo4
Or source all functions in a loop:
for f in demo1 demo3; do
echo sourcing $f
source <(sed -n '/^function '$f' /,/^}/p' largeshell.sh)
done
demo1
demo4
You can make it more fancy when you source a special script that will:
grep all strings starting with largeshell., like largefile.demo1
generate functions like largefile.demo1 that will call demo1
and source all functions that are called.
Your new script will look like
source function_includer.sh
largeshell.demo1
largeshell.demo4
EDIT:
You might want to reconsider your requirements.
Above solution is not only slow, but it will also make it hard for the
guys and ladies who made tha largeshell.sh. As soon as they are going to refactor their code or replace it with something in another language,
they have to refactor, test and deploy your code as well.
A better path is extracting the functions from largeshell.sh into some smaller files ("modules"), and put them in a shared directory (shlib?).
With names as sqlutil.sh, datetime.sh, formatting.sh, mailstuff.sh and comm.sh you can pick the includes file you need (and largefile.sh will include them all).
It's been a while and it would appear that my original solution is the best one out there. Thanks for the feedback.

What's the Ruby equivalent of Perl's s{}{}e?

I'm trying to rewrite a command to replace output files with the same files in a different directory, building up a translation map. In Perl I could do something like this:
s{(-o|--out)\s((?:\S+\/)?(\S+))}{ "$1 " . ($m{$2}="foo/$3") }eg
I'm not clear how to do the same under Ruby, because:
"-out AAA -out yeah/BBB".
gsub(/((?:\s|^)-out)\s+((?:\S+\/)?(\S+))/) { |f|
"#{f[1]} #{m[f[2]] = "temp/#{f[3]}"}"
}
results in
"o temp/t- temp/u"
because 'm' is the matched string, not a match object, so #{m[1]} is just the second character of the matched string.
I don't want to use $1, $2 because Rubocop says they're evil, and I'd prefer not to use 'Regexp.last_match' because it's quite remarkably verbose and turns this one-liner into a do-block.
Is there no built-in that gives me the match object as the parameter?
Once you do not want to use $1 as you did in perl, you might use named matches:
"-out AAA -out BBB".gsub(/(?<leading>\s|^)(?<out>-out)\s+(?<rest>\S+)/) do
"#{$~[:leading]}#{$~[:out]} BLOCK"
end
#⇒ "-out BLOCK -out BLOCK"
Another option would be to shut rubocop up, since you know what you are doing:
#rubocop:disable Style/PerlBackrefs
"-out AAA -out BBB".gsub(/(\s|^)(-out)\s+(\S+)/) do
"#{$1}#{$2} BLOCK"
end
#rubocop:enable Style/PerlBackrefs

Can't get bash to loop in chef

I am trying to run a loop over array in chef (The loop contains a bash command). Anyone has any idea on this ?
symlink_db = data_bag_item(“my”_db,”my”_db)
source = symlink_db[“sourceFile”]
instances = symlink_db["Instances"].split(',') <---Instances is a comma separated string in Databag
bash "create_link" do
puts "1: #{instances}" <-------Puts all instances correctly
instances.each do |instance|
puts "2: #{instance}" <------ This prints each instance in loop correctly
code <<-EOH
echo "ln -fs #{source} #{instance}"; <----- This is printed only for last instance in the loop
EOH
end
end
Appreciate if anyone can help soon.....Thanks
You need the bash resource inside your loop:
instances.each do |instance|
bash "create_link-#{instance}" do
code <<-EOH
echo "ln -fs #{source} #{instance}"
EOH
end
end
BTW, this is not idiomatic chef. You should simply use the link resource like this:
instances.each do |instance|
link instance do
to source
end
end
An advantage of this approach is that it makes your recipe cross-platform. It's also a lot more readable.

Recursively convert all folder and file names to lower-case or upper-case

I have a folder structure like follows.
-FOO
-BAG
Rose.TXT
-BAR
JaCk.txt
I need the following output.
-foo
-bag
rose.txt
-bar
jack.txt
I realize you want ruby code, but I present to you a one liner to run in your shell:
for i in `find * -depth`; do (mv $i `echo $i|tr [:upper:] [:lower:]`); done
as found here: http://ubuntuforums.org/showthread.php?t=244738
Run it once, and it should do the trick.
Update
Ruby Code:
Dir.glob("./**/*").each do |file|
File.rename(file, file.downcase) #or upcase if you want to convert to uppercase
end
Dir["**/*"].each {|f| File.rename(f, f.downcase)}
The accepted answer does not work: when it tries to convert a directory first and then a file in that directory.
Here is the code that does work:
Dir.glob("./**/*").sort{|x| x.size}.each do |name|
x = name.split('/')
newname = (x[0..-2] + [x[-1].downcase]).join('/')
File.rename(name, newname)
end
(it sorts the list by length, so the direcotry will be converted after the file in it)
Recursive directory listing :
http://www.mustap.com/rubyzone_post_162_recursive-directory-listing
Uppercase/lowercase conversion :
http://www.programmingforums.org/thread5455.html
Enjoy :)
If you want to rename your files recursively, you can use **/*
folder = "/home/prince"
Dir["#{folder}/**/*"].each {|file| File.rename(file, file.downcase)}
If you just want the file array output in lowercase
Dir["#{folder}/**/*"].map(&:downcase)
Just a bit of Find.find is all you need:
require 'find'
Find.find(directory) do |path|
if(path != '.' && path != '..')
File.rename(path, path.downcase)
end
end

Resources