loe-icagent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +2 -0
- data/VERSION +1 -0
- data/bin/icagent +91 -0
- data/bin/icsearch +120 -0
- data/bin/icwatcher +95 -0
- data/lib/iclassify.rb +18 -0
- data/lib/iclassify/agent.rb +155 -0
- data/lib/iclassify/client.rb +108 -0
- data/lib/iclassify/node.rb +134 -0
- data/recipes/00_facter.rb +12 -0
- data/recipes/01_default_class.rb +5 -0
- data/recipes/02_apache_server.rb +6 -0
- data/recipes/02_ec2.rb +27 -0
- data/recipes/02_mongrel_server.rb +21 -0
- data/recipes/02_puppet_env.rb +17 -0
- data/recipes/03_mysql_server.rb +17 -0
- metadata +70 -0
data/README
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/icagent
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# iClassify - A node classification service.
|
4
|
+
# Copyright (C) 2007 HJK Solutions and Adam Jacob (<adam@hjksolutions.com>)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
18
|
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
19
|
+
#
|
20
|
+
# icagent registers a node with iclassify, and lets you use small DSL for
|
21
|
+
# classifiying them.
|
22
|
+
|
23
|
+
require 'rubygems'
|
24
|
+
require File.dirname(__FILE__) + '/../lib/iclassify'
|
25
|
+
require 'optparse'
|
26
|
+
|
27
|
+
config = {
|
28
|
+
:uuidfile => File.dirname(__FILE__) + '/../icagent.uuid',
|
29
|
+
:server => 'https://ops.onehub.com'
|
30
|
+
}
|
31
|
+
opts = OptionParser.new do |opts|
|
32
|
+
opts.banner = "Usage: #{$0} [-d DIR|-r FILE] (options)"
|
33
|
+
opts.on("-d DIRECTORY", "--directory DIRECTORY", "Path to icagent recipes") do |d|
|
34
|
+
config[:directory] = d
|
35
|
+
end
|
36
|
+
opts.on("-r RECIPE", "--recipe RECIPE", "Path to a single icagent recipe") do |r|
|
37
|
+
config[:recipe] = r
|
38
|
+
end
|
39
|
+
opts.on("-u UUIDFILE", "--uuidfile UUIDFILE", "Path to the uuid file") do |u|
|
40
|
+
config[:uuidfile] = u
|
41
|
+
end
|
42
|
+
opts.on("-s SERVER", "--server", "iClassify Server URL") do |s|
|
43
|
+
config[:server] = s
|
44
|
+
end
|
45
|
+
opts.on("-n", "--no-action", "Don't update anything, just print.") do |n|
|
46
|
+
config[:dryrun] = true
|
47
|
+
end
|
48
|
+
opts.on("-w WAIT", "--wait TIME", "Wait for up to TIME seconds.") do |w|
|
49
|
+
config[:wait] = w.to_i
|
50
|
+
end
|
51
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
52
|
+
puts opts
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
end
|
56
|
+
opts.parse!(ARGV)
|
57
|
+
|
58
|
+
unless config.has_key?(:recipe) || config.has_key?(:directory)
|
59
|
+
puts "You must specify either a recipe (-r) or a directory (-d)"
|
60
|
+
puts opts
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
|
64
|
+
if config.has_key?(:wait)
|
65
|
+
splay = rand(config[:wait])
|
66
|
+
sleep(splay)
|
67
|
+
end
|
68
|
+
|
69
|
+
agent = IClassify::Agent.new(config[:uuidfile], config[:server])
|
70
|
+
begin
|
71
|
+
agent.load
|
72
|
+
rescue SocketError
|
73
|
+
$stderr.puts("Error: Cannot connect to server.")
|
74
|
+
exit 1
|
75
|
+
end
|
76
|
+
|
77
|
+
if config.has_key?(:recipe)
|
78
|
+
agent.run_script(File.expand_path(config[:recipe]))
|
79
|
+
end
|
80
|
+
if config.has_key?(:directory)
|
81
|
+
Dir.glob(File.join(File.expand_path(config[:directory]), '*.rb')).sort.each do |file|
|
82
|
+
if File.file?(file)
|
83
|
+
agent.run_script(file)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
if config.has_key?(:dryrun) && config[:dryrun]
|
88
|
+
puts agent.to_s
|
89
|
+
else
|
90
|
+
agent.update
|
91
|
+
end
|
data/bin/icsearch
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# iClassify - A node classification service.
|
4
|
+
# Copyright (C) 2007 HJK Solutions and Adam Jacob (<adam@hjksolutions.com>)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
18
|
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
19
|
+
#
|
20
|
+
# A very simple search utility for iclassify.
|
21
|
+
#
|
22
|
+
|
23
|
+
require 'rubygems'
|
24
|
+
require File.dirname(__FILE__) + '/../lib/iclassify'
|
25
|
+
require 'optparse'
|
26
|
+
require 'highline/import'
|
27
|
+
|
28
|
+
config = {
|
29
|
+
:server => 'https://iclassify',
|
30
|
+
:user => ENV.has_key?('USER') ? ENV['USER'] : 'puppet',
|
31
|
+
:only => nil,
|
32
|
+
:quiet => nil
|
33
|
+
}
|
34
|
+
opts = OptionParser.new do |opts|
|
35
|
+
opts.banner = "Usage: #{$0} (options) query"
|
36
|
+
opts.on("-a one,two,three", "--attrib one,two,three", Array, "Attributes to print") do |a|
|
37
|
+
config[:attribs] = a
|
38
|
+
end
|
39
|
+
opts.on("-t", "--tags", "Print tags or not, if attribs specified") do |t|
|
40
|
+
config[:tags] = t
|
41
|
+
end
|
42
|
+
opts.on("-u user", "--user user", "User to authenticate with, defaults to USER env variable") do |u|
|
43
|
+
config[:user] = u
|
44
|
+
end
|
45
|
+
opts.on("-p passwd", "--passwd passwd", "Password to authenticate with") do |p|
|
46
|
+
config[:passwd] = p
|
47
|
+
end
|
48
|
+
opts.on("-s server", "--server server", "iClassify Server URL") do |s|
|
49
|
+
config[:server] = s
|
50
|
+
end
|
51
|
+
opts.on("-o", "--only", "Print only the attributes specified on the command line") do |o|
|
52
|
+
config[:only] = o
|
53
|
+
end
|
54
|
+
opts.on("-q", "--quiet", "Do not print the header") do |q|
|
55
|
+
config[:quiet] = q
|
56
|
+
end
|
57
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
58
|
+
puts opts
|
59
|
+
exit
|
60
|
+
end
|
61
|
+
end
|
62
|
+
args = ARGV
|
63
|
+
opts.parse!(args)
|
64
|
+
|
65
|
+
if args.length != 1
|
66
|
+
puts "You must specify a single query."
|
67
|
+
puts opts.help
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
unless config[:passwd]
|
72
|
+
config[:passwd] = HighLine.ask("Password: ") { |q| q.echo = "*" }
|
73
|
+
end
|
74
|
+
|
75
|
+
unless config[:user] && config[:passwd]
|
76
|
+
puts "You must provide a username and password."
|
77
|
+
puts opts.help
|
78
|
+
exit 1
|
79
|
+
end
|
80
|
+
|
81
|
+
query = args[0]
|
82
|
+
|
83
|
+
client = IClassify::Client.new(config[:server], config[:user], config[:passwd])
|
84
|
+
begin
|
85
|
+
results = client.search(query, config[:attribs] ? config[:attribs] : [])
|
86
|
+
rescue SocketError
|
87
|
+
$stderr.puts("Error: Could not connect to server.")
|
88
|
+
exit 1
|
89
|
+
end
|
90
|
+
|
91
|
+
if config.has_key?(:attribs)
|
92
|
+
header = "# "
|
93
|
+
if config[:only]
|
94
|
+
header += "#{config[:attribs].join(',')}"
|
95
|
+
else
|
96
|
+
header += "description,uuid,#{config[:attribs].join(',')}"
|
97
|
+
end
|
98
|
+
header << ",tags" if config.has_key?(:tags)
|
99
|
+
puts header unless config[:quiet]
|
100
|
+
results.each do |node|
|
101
|
+
line = Array.new
|
102
|
+
line << "#{node.description}" unless config[:only]
|
103
|
+
line << "#{node.uuid}" unless config[:only]
|
104
|
+
config[:attribs].each do |attrib|
|
105
|
+
na = node.attribs.detect { |a| a[:name] == attrib }
|
106
|
+
if na
|
107
|
+
line << "#{na[:values].join(':')}"
|
108
|
+
else
|
109
|
+
line << ""
|
110
|
+
end
|
111
|
+
end
|
112
|
+
line << "\"#{node.tags.join(' ')}\"" if config.has_key?(:tags)
|
113
|
+
puts line.join(",")
|
114
|
+
end
|
115
|
+
else
|
116
|
+
results.each do |node|
|
117
|
+
puts "#\n# Node #{node.uuid}\n#"
|
118
|
+
puts node.to_s
|
119
|
+
end
|
120
|
+
end
|
data/bin/icwatcher
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# iClassify - A node classification service.
|
4
|
+
# Copyright (C) 2007 HJK Solutions and Adam Jacob (<adam@hjksolutions.com>)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
18
|
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
19
|
+
#
|
20
|
+
# Runs a command if the node has changed in iClassify
|
21
|
+
#
|
22
|
+
|
23
|
+
require 'rubygems'
|
24
|
+
require File.dirname(__FILE__) + '/../lib/iclassify'
|
25
|
+
require 'optparse'
|
26
|
+
require 'tempfile'
|
27
|
+
require 'open3'
|
28
|
+
|
29
|
+
config = {
|
30
|
+
:server => 'https://ops.onehub.com',
|
31
|
+
:uuidfile => File.dirname(__FILE__) + '/../icagent.uuid',
|
32
|
+
:tmpfile => File.join(Dir::tmpdir, "icwatcher.digest"),
|
33
|
+
}
|
34
|
+
verbose = false
|
35
|
+
|
36
|
+
args = ARGV
|
37
|
+
opts = OptionParser.new do |opts|
|
38
|
+
opts.banner = "Usage: #{$0} [-s server] -c command"
|
39
|
+
opts.on("-s SERVER", "--server", "iClassify Server URL") do |s|
|
40
|
+
config[:server] = s
|
41
|
+
end
|
42
|
+
opts.on("-u UUIDFILE", "--uuidfile UUIDFILE", "Path to the uuid file") do |u|
|
43
|
+
config[:uuidfile] = u
|
44
|
+
end
|
45
|
+
opts.on("-c COMMAND", "--command", "Command to run on changes") do |c|
|
46
|
+
config[:command] = c
|
47
|
+
end
|
48
|
+
opts.on("-t TMPFILE", "--tmpfile", "Where to store the digest between runs") do |t|
|
49
|
+
config[:tmpfile] = t
|
50
|
+
end
|
51
|
+
opts.on("-v", "--verbose", "Print the output of command") do
|
52
|
+
verbose = true
|
53
|
+
end
|
54
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
55
|
+
puts opts
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
end
|
59
|
+
opts.parse!(args)
|
60
|
+
|
61
|
+
unless config.has_key?(:command)
|
62
|
+
puts "You must supply a command!"
|
63
|
+
puts opts.help
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
|
67
|
+
hostname = args[0]
|
68
|
+
|
69
|
+
agent = IClassify::Agent.new(config[:uuidfile], config[:server])
|
70
|
+
agent.load
|
71
|
+
digest = agent.node.digest
|
72
|
+
|
73
|
+
if FileTest.file?(config[:tmpfile])
|
74
|
+
last_digest = File.readlines(config[:tmpfile])
|
75
|
+
last_digest[0].chomp!
|
76
|
+
if digest == last_digest[0]
|
77
|
+
puts "Node has not changed." if verbose
|
78
|
+
exit 0
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
output = `#{config[:command]} 2>&1`
|
83
|
+
raise "#{config[:command]} failed: #{output}" unless $?.success?
|
84
|
+
|
85
|
+
if verbose
|
86
|
+
puts "Command: #{config[:command]}"
|
87
|
+
puts "---- OUTPUT ----"
|
88
|
+
puts output
|
89
|
+
end
|
90
|
+
|
91
|
+
File.open(config[:tmpfile], "w") do |tmp|
|
92
|
+
tmp.puts digest
|
93
|
+
end
|
94
|
+
|
95
|
+
exit 0
|
data/lib/iclassify.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Author:: Adam Jacob (<adam@hjksolutions.com>)
|
2
|
+
# Copyright:: Copyright (c) 2007 HJK Solutions, LLC
|
3
|
+
# License:: GNU General Public License version 2.1
|
4
|
+
#
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License version 2.1
|
7
|
+
# as published by the Free Software Foundation.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License along
|
15
|
+
# with this program; if not, write to the Free Software Foundation, Inc.,
|
16
|
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
17
|
+
|
18
|
+
Dir[File.join(File.dirname(__FILE__), 'iclassify/**/*.rb')].sort.each { |lib| require lib }
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'uuidtools'
|
3
|
+
|
4
|
+
module IClassify
|
5
|
+
class Agent
|
6
|
+
attr_accessor :node
|
7
|
+
attr_accessor :uuid
|
8
|
+
attr_accessor :password
|
9
|
+
|
10
|
+
#
|
11
|
+
# Create a new Agent. Takes a path to a file to either read or drop
|
12
|
+
# a UUID, and a server URL.
|
13
|
+
#
|
14
|
+
def initialize(uuidfile="/etc/icagent/icagent.uuid", server_url="http://localhost:3000")
|
15
|
+
@uuid = nil
|
16
|
+
@password = nil
|
17
|
+
if File.exists?(uuidfile)
|
18
|
+
IO.foreach(uuidfile) do |line|
|
19
|
+
@uuid, @password = line.chomp.split("!")
|
20
|
+
end
|
21
|
+
unless @password
|
22
|
+
@password = random_password(30)
|
23
|
+
write_uuidfile(uuidfile)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
@uuid = UUID.random_create
|
27
|
+
@password = random_password(30)
|
28
|
+
write_uuidfile(uuidfile)
|
29
|
+
end
|
30
|
+
@client = IClassify::Client.new(server_url, @uuid, @password)
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Loads data about this node from the iClassify service
|
35
|
+
#
|
36
|
+
def load
|
37
|
+
begin
|
38
|
+
@node = @client.get_node(@uuid)
|
39
|
+
rescue Net::HTTPServerException => e
|
40
|
+
if e.to_s =~ /^404/
|
41
|
+
@node = IClassify::Node.new()
|
42
|
+
@node.description = "New Node"
|
43
|
+
@node.tags << "unclassified"
|
44
|
+
@node.password = @password
|
45
|
+
@node.uuid = @uuid
|
46
|
+
else
|
47
|
+
throw(e)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Updates this node in the iClassify service.
|
54
|
+
#
|
55
|
+
def update
|
56
|
+
if @node.description == "New Node"
|
57
|
+
hostname = attrib?("hostname")
|
58
|
+
hostname ||= "New Node"
|
59
|
+
@node.description = hostname
|
60
|
+
end
|
61
|
+
@client.update_node(@node)
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Deletes this node from the iClassify service.
|
66
|
+
#
|
67
|
+
def delete
|
68
|
+
@client.delete_node(@node)
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Returns the tag name if this node has that tag.
|
73
|
+
#
|
74
|
+
def tag?(tag)
|
75
|
+
@node.tag?(tag)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the values for this attribute, if it exists for this node. If
|
79
|
+
# there is only one, it will return it, if it's an array, you get the
|
80
|
+
# array. You have to check!
|
81
|
+
def attrib?(attrib)
|
82
|
+
@node.attrib?(attrib)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the current node as a string.
|
86
|
+
def to_s
|
87
|
+
@node.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the value if the given attribute has a given attribute.
|
91
|
+
def attrib_has_value?(attrib, value)
|
92
|
+
na = @node.attribs.detect { |a| a[:name] == attrib }
|
93
|
+
if na
|
94
|
+
return na.values.detect { |v| v == value}
|
95
|
+
else
|
96
|
+
return nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Add a tag to this node.
|
101
|
+
def add_tag(tag)
|
102
|
+
load unless @node
|
103
|
+
@node.tags << tag
|
104
|
+
end
|
105
|
+
|
106
|
+
# Add an attribute to this node. Requires a name and either a string or
|
107
|
+
# array of values.
|
108
|
+
#
|
109
|
+
# Will be cumulative!
|
110
|
+
def add_attrib(name, values)
|
111
|
+
load unless @node
|
112
|
+
@node.attribs << { :name => name, :values => values.kind_of?(Array) ? values : [ values ] }
|
113
|
+
end
|
114
|
+
|
115
|
+
# Replace the attribute with the given name's values in place.
|
116
|
+
# Will add a new attribute if it needs to.
|
117
|
+
def replace_attrib(name, values)
|
118
|
+
exists = @node.attribs.detect { |a| a[:name] == name }
|
119
|
+
if exists
|
120
|
+
exists[:values] = values.kind_of?(Array) ? values : [ values ]
|
121
|
+
else
|
122
|
+
add_attrib(name, values)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Set the description for the node
|
127
|
+
def description(value)
|
128
|
+
@node.description = value
|
129
|
+
end
|
130
|
+
|
131
|
+
# return the value of @node.description
|
132
|
+
def description?()
|
133
|
+
@node.description
|
134
|
+
end
|
135
|
+
|
136
|
+
# Run an iclassify script.
|
137
|
+
def run_script(scriptfile)
|
138
|
+
eval(IO.read(scriptfile))
|
139
|
+
end
|
140
|
+
|
141
|
+
protected
|
142
|
+
def random_password(len)
|
143
|
+
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
|
144
|
+
newpass = ""
|
145
|
+
1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
|
146
|
+
newpass
|
147
|
+
end
|
148
|
+
|
149
|
+
def write_uuidfile(uuidfile)
|
150
|
+
File.open(uuidfile, "w") do |file|
|
151
|
+
file.puts "#{@uuid}!#{@password}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/https'
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'uri'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module IClassify
|
8
|
+
|
9
|
+
class Client
|
10
|
+
UUID_REGEX = /^[[:xdigit:]]{8}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{12}$/
|
11
|
+
|
12
|
+
def initialize(service_url, username, password)
|
13
|
+
service_url = "#{service_url}/rest" unless service_url =~ /rest$/
|
14
|
+
@url = URI.parse(service_url)
|
15
|
+
@username = username
|
16
|
+
@password = password
|
17
|
+
end
|
18
|
+
|
19
|
+
def make_url(method, params)
|
20
|
+
params[:appid] = @appid
|
21
|
+
super method, params
|
22
|
+
end
|
23
|
+
|
24
|
+
def search(query, attribs=[])
|
25
|
+
raise ArgumentError, "Attributes must be given as a list!" unless attribs.kind_of?(Array)
|
26
|
+
querystring = "search"
|
27
|
+
querystring << "?q=#{URI.escape(query)}"
|
28
|
+
querystring << "&a=#{URI.escape(attribs.join(','))}" if attribs.length > 0
|
29
|
+
results = get_rest(querystring, "text/yaml")
|
30
|
+
node_array = YAML.load(results).collect { |n| IClassify::Node.new(:yaml, n) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_node(node_id)
|
34
|
+
IClassify::Node.new(:xml, get_rest("nodes/#{node_id}"))
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_node(node)
|
38
|
+
if node.node_id
|
39
|
+
put_rest("nodes/#{node.node_id}", node.to_xml)
|
40
|
+
else
|
41
|
+
post_rest("nodes", node.to_xml)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete_node(node)
|
46
|
+
delete_rest("nodes/#{node.node_id}")
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def get_rest(path, accept="application/xml")
|
52
|
+
url = URI.parse("#{@url}/#{path}")
|
53
|
+
run_request(:GET, url, false, accept)
|
54
|
+
end
|
55
|
+
|
56
|
+
def delete_rest(path, accept="application/xml")
|
57
|
+
url = URI.parse("#{@url}/#{path}")
|
58
|
+
run_request(:DELETE, url, false, accept)
|
59
|
+
end
|
60
|
+
|
61
|
+
def post_rest(path, xml, accept="application/xml")
|
62
|
+
url = URI.parse("#{@url}/#{path}")
|
63
|
+
run_request(:POST, url, xml, accept)
|
64
|
+
end
|
65
|
+
|
66
|
+
def put_rest(path, xml, accept="application/xml")
|
67
|
+
url = URI.parse("#{@url}/#{path}")
|
68
|
+
run_request(:PUT, url, xml, accept)
|
69
|
+
end
|
70
|
+
|
71
|
+
def run_request(method, url, data=false, accept="application/xml")
|
72
|
+
http = Net::HTTP.new(url.host, url.port)
|
73
|
+
if url.scheme == "https"
|
74
|
+
http.use_ssl = true
|
75
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
76
|
+
end
|
77
|
+
http.read_timeout = 60
|
78
|
+
headers = {
|
79
|
+
'Accept' => accept,
|
80
|
+
'Content-Type' => accept
|
81
|
+
}
|
82
|
+
req = nil
|
83
|
+
case method
|
84
|
+
when :GET
|
85
|
+
req_path = "#{url.path}"
|
86
|
+
req_path << "?#{url.query}" if url.query
|
87
|
+
req = Net::HTTP::Get.new(req_path, headers)
|
88
|
+
when :POST
|
89
|
+
req = Net::HTTP::Post.new(url.path, headers)
|
90
|
+
req.body = data if data
|
91
|
+
when :PUT
|
92
|
+
req = Net::HTTP::Put.new(url.path, headers)
|
93
|
+
req.body = data if data
|
94
|
+
when :DELETE
|
95
|
+
req = Net::HTTP::Delete.new(url.path, headers)
|
96
|
+
end
|
97
|
+
req.basic_auth(@username, @password)
|
98
|
+
res = http.request(req)
|
99
|
+
case res
|
100
|
+
when Net::HTTPSuccess
|
101
|
+
res.body
|
102
|
+
else
|
103
|
+
res.error!
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rexml/document'
|
3
|
+
require 'builder'
|
4
|
+
require 'yaml'
|
5
|
+
require 'digest/sha1'
|
6
|
+
|
7
|
+
module IClassify
|
8
|
+
class Node
|
9
|
+
attr_accessor :tags, :uuid, :description, :notes, :attribs, :node_id, :password
|
10
|
+
|
11
|
+
def initialize(type=:xml, data=nil)
|
12
|
+
from_xml(data) if type == :xml && data
|
13
|
+
from_yaml(data) if type == :yaml && data
|
14
|
+
@tags ||= Array.new
|
15
|
+
@attribs ||= Array.new
|
16
|
+
@password = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_xml
|
20
|
+
xml = Builder::XmlMarkup.new
|
21
|
+
output = xml.node do
|
22
|
+
xml.id(@node_id) if @node_id
|
23
|
+
xml.uuid(@uuid)
|
24
|
+
xml.password(@password) if @password
|
25
|
+
xml.description(@description)
|
26
|
+
xml.notes(@notes)
|
27
|
+
xml.tags do
|
28
|
+
@tags.sort.each do |tag|
|
29
|
+
xml.tag(tag)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
xml.attribs do
|
33
|
+
@attribs.sort{ |a,b| a[:name] <=> b[:name] }.each do |attrib|
|
34
|
+
xml.attrib do
|
35
|
+
xml.name(attrib[:name])
|
36
|
+
xml.values do
|
37
|
+
attrib[:values].each do |v|
|
38
|
+
xml.value(v)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
output
|
46
|
+
end
|
47
|
+
|
48
|
+
def digest
|
49
|
+
Digest::SHA1.hexdigest(to_s())
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Returns the tag name if this node has that tag.
|
54
|
+
#
|
55
|
+
def tag?(tag)
|
56
|
+
@tags.detect { |t| t == tag }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the values for this attribute, if it exists for this node. If
|
60
|
+
# there is only one, it will return it, if it's an array, you get the
|
61
|
+
# array. You have to check!
|
62
|
+
def attrib?(attrib)
|
63
|
+
na = @attribs.detect { |a| a[:name] == attrib }
|
64
|
+
return nil unless na
|
65
|
+
if na[:values].length > 1
|
66
|
+
return na[:values]
|
67
|
+
else
|
68
|
+
return na[:values][0]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s(tags=nil,attribs=nil)
|
73
|
+
output = String.new
|
74
|
+
output << "uuid: #{@uuid}\n"
|
75
|
+
output << "node_id: #{@node_id}\n"
|
76
|
+
output << "notes: #{@notes}\n"
|
77
|
+
output << "description: #{@description}\n"
|
78
|
+
output << "tags: #{@tags.sort.join(' ')}\n"
|
79
|
+
output << "attribs:\n"
|
80
|
+
@attribs.sort{ |a,b| a[:name] <=> b[:name] }.each do |attrib|
|
81
|
+
output << " #{attrib[:name]}: #{attrib[:values].join(', ')}\n"
|
82
|
+
end
|
83
|
+
output
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_puppet
|
87
|
+
output = Hash.new
|
88
|
+
output["classes"] = @tags
|
89
|
+
output["parameters"] = Hash.new
|
90
|
+
@attribs.each do |attrib|
|
91
|
+
if attrib[:values].length > 1
|
92
|
+
output["parameters"][attrib[:name]] = attrib[:values]
|
93
|
+
else
|
94
|
+
output["parameters"][attrib[:name]] = attrib[:values][0]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
output.to_yaml
|
98
|
+
end
|
99
|
+
|
100
|
+
def from_xml(doc)
|
101
|
+
xml = nil
|
102
|
+
if doc.kind_of?(REXML::Element)
|
103
|
+
xml = doc
|
104
|
+
else
|
105
|
+
xml = REXML::Document.new(doc)
|
106
|
+
end
|
107
|
+
@tags = Array.new
|
108
|
+
xml.elements.each('//tag') { |t| @tags << t.text }
|
109
|
+
@uuid = xml.get_text('//uuid')
|
110
|
+
@node_id = xml.get_text('//id')
|
111
|
+
@description = xml.get_text('//description')
|
112
|
+
@notes = xml.get_text('//notes')
|
113
|
+
@attribs = Array.new
|
114
|
+
xml.elements.each('//attrib') do |attrib|
|
115
|
+
cattrib = Hash.new
|
116
|
+
cattrib[:name] = attrib.get_text('name').to_s
|
117
|
+
value_array = Array.new
|
118
|
+
attrib.elements.each('values/value') { |v| value_array << v.text }
|
119
|
+
cattrib[:values] = value_array
|
120
|
+
@attribs << cattrib
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def from_yaml(data)
|
125
|
+
@tags = data[:tags].collect { |t| t[:name] }
|
126
|
+
@uuid = data[:uuid]
|
127
|
+
@description = data[:description]
|
128
|
+
@notes = data[:notes]
|
129
|
+
@attribs = data[:attribs].delete_if { |x| x[:name] == "text" }
|
130
|
+
@node_id = data[:id]
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#
|
2
|
+
# A simple icagent recipe. Takes all the facter facts and submits them to
|
3
|
+
# iClassify.
|
4
|
+
#
|
5
|
+
|
6
|
+
ENV['FACTERLIB'] = '/var/lib/puppet/lib/facter'
|
7
|
+
require 'rubygems'
|
8
|
+
require 'facter'
|
9
|
+
|
10
|
+
Facter.each do |name, value|
|
11
|
+
replace_attrib(name, value)
|
12
|
+
end
|
data/recipes/02_ec2.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
ec2 = false
|
4
|
+
domain = attrib?('domain')
|
5
|
+
ec2 = true if domain =~ /(\.amazonaws.com|compute-1.internal)$/
|
6
|
+
|
7
|
+
if ec2
|
8
|
+
replace_attrib("ec2", "true")
|
9
|
+
else
|
10
|
+
replace_attrib("ec2", "false")
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_from_ec2(thing="/")
|
14
|
+
base_url = "http://169.254.169.254/latest/meta-data" + thing
|
15
|
+
url = URI.parse(base_url)
|
16
|
+
req = Net::HTTP::Get.new(url.path)
|
17
|
+
res = Net::HTTP.start(url.host, url.port) {|http|
|
18
|
+
http.request(req)
|
19
|
+
}
|
20
|
+
res.body
|
21
|
+
end
|
22
|
+
|
23
|
+
if ec2
|
24
|
+
get_from_ec2.split("\n").each do |key|
|
25
|
+
add_attrib("ec2-#{key}", get_from_ec2("/#{key}"))
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# Configure how many mongrels a server should have
|
3
|
+
#
|
4
|
+
|
5
|
+
mongrel_size = 60
|
6
|
+
head_room = 1024
|
7
|
+
|
8
|
+
total_memory = attrib?("memorysize")
|
9
|
+
if total_memory
|
10
|
+
if total_memory =~ /MB$/
|
11
|
+
total_memory.gsub!(/ MB/, '')
|
12
|
+
total_memory = total_memory.to_f
|
13
|
+
else
|
14
|
+
total_memory.gsub!(/ GB/, '')
|
15
|
+
total_memory = total_memory.to_f * 1024
|
16
|
+
end
|
17
|
+
available_memory = total_memory - head_room
|
18
|
+
total_mongrels = available_memory / mongrel_size
|
19
|
+
add_attrib("mongrel_servers", total_mongrels.to_i) unless attrib?("mongrel_servers")
|
20
|
+
add_attrib("mongrel_port_number", 5000) unless attrib?("mongrel_port_number")
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# Set up our puppet environment
|
3
|
+
#
|
4
|
+
|
5
|
+
unless attrib?("puppet_env")
|
6
|
+
hostname = attrib?("hostname")
|
7
|
+
fqdn = attrib?("fqdn")
|
8
|
+
if fqdn =~ /amazonaws.com$/
|
9
|
+
replace_attrib("puppet_env", "production")
|
10
|
+
else
|
11
|
+
if hostname =~ /^.+?\d+(.+)$/
|
12
|
+
replace_attrib("puppet_env", $1)
|
13
|
+
else
|
14
|
+
replace_attrib("puppet_env", "production")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# How big a buffer pool size?
|
3
|
+
#
|
4
|
+
|
5
|
+
total_memory = attrib?("memorysize")
|
6
|
+
if total_memory
|
7
|
+
if total_memory =~ /MB$/
|
8
|
+
total_memory.gsub!(/ MB/, '')
|
9
|
+
total_memory = total_memory.to_f
|
10
|
+
else
|
11
|
+
total_memory.gsub!(/ GB/, '')
|
12
|
+
total_memory = total_memory.to_f * 1024
|
13
|
+
end
|
14
|
+
buffer_pool_size = total_memory * 0.75
|
15
|
+
buffer_pool_size = buffer_pool_size.to_i
|
16
|
+
add_attrib("innodb_buffer_pool_size", buffer_pool_size.to_s + "M") unless attrib?("innodb_buffer_pool_size")
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: loe-icagent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- W. Andrew Loe III
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-07-22 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: andrew@andrewloe.com
|
18
|
+
executables:
|
19
|
+
- icagent
|
20
|
+
- icsearch
|
21
|
+
- icwatcher
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files:
|
25
|
+
- README
|
26
|
+
files:
|
27
|
+
- README
|
28
|
+
- VERSION
|
29
|
+
- bin/icagent
|
30
|
+
- bin/icsearch
|
31
|
+
- bin/icwatcher
|
32
|
+
- lib/iclassify.rb
|
33
|
+
- lib/iclassify/agent.rb
|
34
|
+
- lib/iclassify/client.rb
|
35
|
+
- lib/iclassify/node.rb
|
36
|
+
- recipes/00_facter.rb
|
37
|
+
- recipes/01_default_class.rb
|
38
|
+
- recipes/02_apache_server.rb
|
39
|
+
- recipes/02_ec2.rb
|
40
|
+
- recipes/02_mongrel_server.rb
|
41
|
+
- recipes/02_puppet_env.rb
|
42
|
+
- recipes/03_mysql_server.rb
|
43
|
+
has_rdoc: false
|
44
|
+
homepage: http://github.com/loe/icagent
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options:
|
47
|
+
- --charset=UTF-8
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.2.0
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: TODO
|
69
|
+
test_files: []
|
70
|
+
|