Coffee & Beer

Rantings and Ravings of the technical sort

Rc_whatis, Finger for Systems

| Comments

As I’ve mentioned, at work, we’ve got a lot of systems, physical and virtual. All sorts of different hardware, specs, ages, etc. Its hard to keep track of, and harder to quickly say “This is what that is” with confidence. Puppet and the ability to collect and query facts from systems has been a HUGE help with this, of course. We use Foreman to provide a nice shiny web interface to this, but, its not the fastest, and most of us live in the CLI dayin a day out. So, I wanted a way to quickly, from any system, find out about any other system in our infrastructure. So, rc_whatis was born.

Its really just a hacky bit of ruby. I’ve opened up the /facts REST endpoint on our puppet massters so that any of our systems can get the facts of any other system, ssl cert or no. We don’t have any secrets in this info.

[root@nichols2tst ~]# rc_whatis --help
Usage: whatis [options] <hostname>
-j, --json                       JSON output
-y, --yaml                       YAML output
-p, --pp                         Pretty Print output
-a, --all                        Use all facts

As you can see, its pretty staight forward to call. Even provides a few serialized forms of output so other scripts can call this (more on that in a few!). Output looks like:

[root@nichols2tst ~]# rc_whatis nichols2tst
Hostname: nichols2tst
Born_on: 2012-08-24
Manufacturer: Red Hat
Productname: KVM
Serialnumber: Not Specified
Operatingsystem: CentOS
Operatingsystemrelease: 6.3
Architecture: x86_64
Processor0: QEMU Virtual CPU version (cpu64-rhel6)
Processorcount: 1
Memorytotal: 996.77 MB
Kernelrelease: 2.6.32-279.5.2.el6.centos.plus.x86_64
Ipaddress: 10.X.X.X
Macaddress: 00:16:3E:XX:XX:XX
Vlan: 375
Location_row: virtual
Location_rack: virtual
Location_ru: virtual
Uptime: 10 days
Virtual: kvm
Hypervisor: kvm03a

Thats the default output, of a select # of facts. -a, --all would get everything, of course. I’ve already mentioned born_on in another post. location_* comesfrom a hacky little interfacts (yaml) to racktables we have (this systems is virtual, so, no physical location). And hypervisor is a conditional query based on a fact we populate on production hypervisors that marks them as such, as well as a list of vms running on a given hypervisor.

The coolest bit is it now exists in roots $PATH on ALL of our systems, so this info, for any host, is now a few keystrokes away all the time!

Even better, our nagios alerts now call this when crafting their emails to send us, so when a system drops, there is no question as to what it is/where/etc. Its all right in the email, along with a like to its full Foreman page and entry in Nagios, of course along witht he normal alert info!

Source? Heres your source!

(rc_whatis.rb) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/ruby

require 'optparse'
require 'yaml'
require 'puppet'
require 'puppet/node'
require 'puppet/node/facts'
require 'pp'
require 'json'
require 'yaml'


#Setup
#
#List of values we want, unelss we call w/ --all
values = ["hostname","born_on","manufacturer","productname","serialnumber","operatingsystem","operatingsystemrelease","architecture","processor0","processorcount","memorytotal","kernelrelease","ipaddress","macaddress","vlan","location_row","location_rack","location_ru","uptime","virtual"]

#Puppet Server Info:
puppet_server="puppet"
puppet_port="8140"
puppet_url="https://" + puppet_server + ":" + puppet_port

options={}
OptionParser.new do |opts|
        opts.banner = "Usage: whatis [options] <hostname>"

        opts.on("-j","--json","JSON output") do |j|
                options[:json] = j
        end
        opts.on("-y","--yaml","YAML output") do |y|
                options[:yaml] = y
        end
        opts.on("-p","--pp","Pretty Print output") do |p|
                options[:pp] = p
        end
        opts.on("-a","--all","Use all facts") do |a|
                options[:all] = a
        end
end.parse!

if ARGV.length != 1
        puts "Please pass a hostname, see --help"
        exit
else
        host = ARGV[0]
end

if host.match(".edu")
        fqdn = host.to_s
else
        fqdn = host.to_s + ".domain.edu"
end

fact_url = puppet_url + "/production/facts/" + fqdn
fact_cmd = "curl -s -k -H \"Accept: yaml\" " + fact_url
rawfacts = `#{fact_cmd}`
if rawfacts.match("Could")
        puts rawfacts
        exit
end
rawfacts = rawfacts.sub("!ruby/object:Puppet::Node::Facts","")
rawfacts = YAML::parse(rawfacts)
rawfacts = rawfacts.transform

#We can now access things like:
# rawfacts["values"]["virtual"]

facts = Hash.new
rawfacts["values"].each_pair do |a,b|
        facts[a] = b
end

#Okay, we have a hash or all facts.
#Make second hash of specific facts


facts2 = Hash.new
if options[:all] == true
  facts2 = facts
else
values.each do |val|
  facts2[val] = facts[val]
end
end

#Lets see if it is virtual so we can add a fact about where it is running...
if facts2["virtual"] == "kvm"
        hypervisor_url = puppet_url + "/production/facts_search/search?facts.kvm_production=true"
        hypervisor_cmd = "curl -s -k -H 'Accept: YAML' " +  hypervisor_url
        hypervisor_yaml = `#{hypervisor_cmd}`
        hypervisors = YAML::load(hypervisor_yaml)
        hypervisors.each do |hyp|
                hyp_facts_url = puppet_url + "/production/facts/" + hyp
                hyp_facts_cmd = "curl -s -k -H \"Accept: yaml\" " + hyp_facts_url
                hyp_facts = `#{hyp_facts_cmd}`
                hyp_facts = hyp_facts.sub("!ruby/object:Puppet::Node::Facts","")
                hyp_facts = YAML::parse(hyp_facts)
                hyp_facts = hyp_facts.transform
                vms = hyp_facts["values"]["kvm_vms"].to_a
                vms.each do |vm|
                        if vm.match(facts2["hostname"])
                                facts2["hypervisor"] = "#{hyp}"
                        end
                end
        end
        #Add "hypervisor" to the list of values we care about
        values.push("hypervisor")
end

#output time
if options[:json] == true
        puts facts2.to_json
elsif options[:yaml] == true
        puts facts2.to_yaml
elsif options[:pp] == true
        pp facts2
else
  if options[:all] == true
        pp facts2
  else
        values.each do |val|
                puts "#{val.capitalize}: #{facts2[val]}"
        end
  end
end

could it be written better? yep. But its quick and its a start!

Born on Dates for Systems

| Comments

Wrote this fact a while ago but though it was worth throwing up here.

We’ve got a lot of systems. Our inventory is slightly lacking, and many were build a long long time ago. Many time we’ve found ourselves asking “When the hell was X system built?” or maybe “rebuilt”. Thus, for RHEL/CentOS systems at least, we can get a fact for that:

(born_on.rb) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/ruby
require 'facter'

begin
          Facter.operatingsystem
rescue
          Facter.loadfacts()
end
os = Facter.value("operatingsystem")
if os.match(/CentOS|RedHat/) then
  unless  `rpm -q --last basesystem`.empty?
      Facter.add("born_on") do
          setcode do
              date = `rpm -q --qf '%{INSTALLTIME}' basesystem`
              born_on = `date --date=@#{date} +%F`.chomp
              born_on
          end
      end
  end
end

Giving us:

1
born_on => 2011-11-03

Updated: Puppet Facts for Puppet Classes

| Comments

Just the other day on Google+ I got a comment form someone who had found my old “Puppet facts about puppet classes” post and had used it. Sadly, I had gone through a few revisoins after that post and never followed up. There as a bit of a memory leak, and I decided I wanted things done a little different. Instead of creating a fact per-class (and having n fact if the clas wasn’t used), I’d rather have a list of the classes, as one fact, I can regex/etc on. Our group has recently started creating facts like this as json arrays so we can prase the data easy later, and its a bit more readable even if not.

(puppet_classes_2.rb) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/ruby
#Get puppet classes, from /var/lib/puppet/classes.txt
#
require 'facter'
require 'json'
begin
        Facter.hostname
rescue
        Facter.loadfacts()
end
hostname = Facter.value('hostname')

classes_txt = "/var/lib/puppet/classes.txt"

if File.exists?(classes_txt) then
        f = File.new(classes_txt)
        classes = Array.new()
        f.readlines.each do |line|
                line = line.chomp.to_s
                line = line.sub(" ","_")
                classes.push(line)
        end
        classes.delete("settings")
        classes.delete("#{hostname}")
        Facter.add("puppet_classes") do
                setcode do
                        classes.to_json
                end
        end
end

thus, a node would have a fact like:

1
puppet_classes => ["base","salt::minion","ssh::service"]