hosttag 0.12
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/.gitignore +2 -0
- data/ChangeLog +44 -0
- data/EXCLUDE +3 -0
- data/LICENCE +674 -0
- data/README +92 -0
- data/Rakefile +30 -0
- data/TODO +4 -0
- data/bin/hosttag +168 -0
- data/bin/ht +168 -0
- data/bin/htdel +164 -0
- data/bin/htdump +70 -0
- data/bin/htexport +99 -0
- data/bin/htimport +81 -0
- data/bin/htremap +93 -0
- data/bin/htset +164 -0
- data/etc/Makefile +16 -0
- data/etc/README +10 -0
- data/hosttag.gemspec +23 -0
- data/hosttag.spec +179 -0
- data/lib/hosttag.rb +402 -0
- data/lib/hosttag/server.rb +35 -0
- data/test/data_hosttag/a/centos +0 -0
- data/test/data_hosttag/a/centos5 +0 -0
- data/test/data_hosttag/a/centos5-x86_64 +0 -0
- data/test/data_hosttag/a/public +0 -0
- data/test/data_hosttag/g/SKIP +0 -0
- data/test/data_hosttag/g/centos +0 -0
- data/test/data_hosttag/g/centos4 +0 -0
- data/test/data_hosttag/g/centos4-i386 +0 -0
- data/test/data_hosttag/h/SKIP +0 -0
- data/test/data_hosttag/h/centos +0 -0
- data/test/data_hosttag/h/centos4 +0 -0
- data/test/data_hosttag/h/centos4-x86_64 +0 -0
- data/test/data_hosttag/h/public +0 -0
- data/test/data_hosttag/m/centos +0 -0
- data/test/data_hosttag/m/centos4 +0 -0
- data/test/data_hosttag/m/centos4-x86_64 +0 -0
- data/test/data_hosttag/m/public +0 -0
- data/test/data_hosttag/m/vps +0 -0
- data/test/data_hosttag/n/centos +0 -0
- data/test/data_hosttag/n/centos5 +0 -0
- data/test/data_hosttag/n/centos5-i386 +0 -0
- data/test/data_hosttag/n/laptop +0 -0
- data/test/test_hosttag_bin.rb +70 -0
- data/test/test_hosttag_lib.rb +119 -0
- data/test/test_htset_bin.rb +174 -0
- data/test/test_htset_lib.rb +183 -0
- data/test/ts_all.rb +4 -0
- metadata +132 -0
data/bin/htset
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Hosttag update client, redis version
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
# htset <host1> [<host2> ...] <tag> [<tag2> ...]
|
7
|
+
# htdel <host1> [<host2> ...] <tag> [<tag2> ...]
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'optparse'
|
11
|
+
require 'fileutils'
|
12
|
+
require 'pp'
|
13
|
+
|
14
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
15
|
+
require 'hosttag'
|
16
|
+
include Hosttag
|
17
|
+
|
18
|
+
# ------------------------------------------------------------------------------
|
19
|
+
# Subroutines
|
20
|
+
|
21
|
+
def die(error)
|
22
|
+
puts error
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_options(me)
|
27
|
+
options = { :all => false, :autoconfirm => false }
|
28
|
+
opts = OptionParser.new
|
29
|
+
opts.banner = "Usage: #{me} [options] <host> [<host2> ...] <tag> [<tag2>...]"
|
30
|
+
opts.on('-?', '-h', '--help') do
|
31
|
+
puts opts
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
opts.on('-h', '--help', '-?', 'Show this usage information') do
|
35
|
+
die(opts)
|
36
|
+
end
|
37
|
+
opts.on('-A', '--all', '(htdel) Delete all tags from hosts') do
|
38
|
+
options[:all] = true
|
39
|
+
end
|
40
|
+
opts.on('-y', '--yes', "(htdel) Don't ask for confirmation on delete all operations") do
|
41
|
+
options[:autoconfirm] = true
|
42
|
+
end
|
43
|
+
opts.on('-H', '--host', '--hosts', 'Treat unrecognised elements as hosts') do
|
44
|
+
options[:host_mode] = true
|
45
|
+
end
|
46
|
+
opts.on('-T', '--tag', '--tags', 'Treat unrecognised elements as tags') do
|
47
|
+
options[:tag_mode] = true
|
48
|
+
end
|
49
|
+
opts.on('--ns=STR', '--namespace=STR', String, 'Namespace into which we load hosttag data. Default: hosttag') do |val|
|
50
|
+
options[:namespace] = val
|
51
|
+
end
|
52
|
+
opts.on('-s=ARG', '--server=ARG', String, 'Server hostname to connect to') do |val|
|
53
|
+
options[:server] = val
|
54
|
+
end
|
55
|
+
opts.on('-p=ARG', '--port=ARG', Integer, 'Server port to connect to') do |val|
|
56
|
+
options[:port] = val
|
57
|
+
end
|
58
|
+
opts.on('-v', '--verbose', 'Verbose output') do
|
59
|
+
options[:verbose] = true
|
60
|
+
end
|
61
|
+
|
62
|
+
# Parse options
|
63
|
+
begin
|
64
|
+
args = opts.parse(ARGV)
|
65
|
+
rescue => e
|
66
|
+
die(opts)
|
67
|
+
end
|
68
|
+
|
69
|
+
if args.length < 2 and not options[:all]
|
70
|
+
die(opts)
|
71
|
+
end
|
72
|
+
if options[:all] and me =~ /set$/
|
73
|
+
warn "Error: --all not available with #{me}"
|
74
|
+
die(opts)
|
75
|
+
end
|
76
|
+
if options[:host_mode] and options[:tag_mode]
|
77
|
+
warn "Error: --host and --tag options are mutually exclusive"
|
78
|
+
die(opts)
|
79
|
+
end
|
80
|
+
|
81
|
+
return options, args
|
82
|
+
end
|
83
|
+
|
84
|
+
# Classify args into hosts, tags, and uncertain buckets
|
85
|
+
def classify_args(args, options)
|
86
|
+
results = { :host => [], :tag => [], :uncertain => [] }
|
87
|
+
verbose = options[:verbose]
|
88
|
+
|
89
|
+
# First arg must be host, and last tag, by definition
|
90
|
+
results[:host].push(args.shift)
|
91
|
+
last_tag = args.pop
|
92
|
+
|
93
|
+
# Classify remainder by doing lookups
|
94
|
+
while a = args.shift do
|
95
|
+
begin
|
96
|
+
tags = hosttag_lookup_hosts(a, options)
|
97
|
+
if tags.length > 0
|
98
|
+
# if 'a' is a valid host, then everything already in uncertain must be too
|
99
|
+
if results[:uncertain].length > 0
|
100
|
+
results[:host].push(*results[:uncertain])
|
101
|
+
results[:uncertain] = []
|
102
|
+
end
|
103
|
+
results[:host].push(a)
|
104
|
+
end
|
105
|
+
rescue
|
106
|
+
# 'a' is not a known host, check if a tag
|
107
|
+
begin
|
108
|
+
hosts = hosttag_lookup_tags(a, options)
|
109
|
+
if hosts.length > 0
|
110
|
+
# If 'a' is a valid tag, then everything else in args must be too
|
111
|
+
results[:tag].push(a, *args)
|
112
|
+
args = []
|
113
|
+
end
|
114
|
+
rescue
|
115
|
+
# 'a' is not a known host or tag, add to uncertain list
|
116
|
+
results[:uncertain].push(a)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
results[:tag].push(last_tag)
|
122
|
+
|
123
|
+
return results
|
124
|
+
end
|
125
|
+
|
126
|
+
# ------------------------------------------------------------------------------
|
127
|
+
# Main
|
128
|
+
|
129
|
+
mode = $0.sub(/^.*\//, '')
|
130
|
+
|
131
|
+
options, args = parse_options(mode)
|
132
|
+
|
133
|
+
# Normal mode
|
134
|
+
if (not options[:all])
|
135
|
+
results = classify_args(args, options)
|
136
|
+
if options[:verbose]
|
137
|
+
print "+ results: "
|
138
|
+
pp results
|
139
|
+
end
|
140
|
+
|
141
|
+
if results[:uncertain].length > 0
|
142
|
+
# --hosts: treat unknown elements as hosts
|
143
|
+
if options[:host_mode]
|
144
|
+
results[:host].push(*results[:uncertain])
|
145
|
+
elsif options[:tag_mode]
|
146
|
+
results[:tag].push(*results[:uncertain])
|
147
|
+
else
|
148
|
+
# TODO: do something useful here - ask the user?
|
149
|
+
die("Error: can't auto-classify '#{results[:uncertain].join(',')}' - aborting")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if (mode =~ /del$/)
|
154
|
+
hosttag_delete_tags(results[:host], results[:tag], options)
|
155
|
+
else
|
156
|
+
hosttag_add_tags(results[:host], results[:tag], options)
|
157
|
+
end
|
158
|
+
|
159
|
+
# htdel --all mode
|
160
|
+
elsif (mode =~ /del$/)
|
161
|
+
hosttag_delete_all_tags(args, options)
|
162
|
+
|
163
|
+
end
|
164
|
+
|
data/etc/Makefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
all: import export
|
2
|
+
|
3
|
+
import:
|
4
|
+
# redis has essentially open write permissions, so htimport is limited to root
|
5
|
+
sudo /usr/sbin/htimport
|
6
|
+
|
7
|
+
export:
|
8
|
+
# htexport is already limited by filesystem permissions, so doesn't need to be root
|
9
|
+
/usr/bin/htexport
|
10
|
+
|
11
|
+
import-delete:
|
12
|
+
sudo /usr/sbin/htimport --delete
|
13
|
+
|
14
|
+
export-delete:
|
15
|
+
/usr/bin/htexport --delete
|
16
|
+
|
data/etc/README
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
To add hosttags, create host directories in the /etc/hosttag directory,
|
2
|
+
and touch tag files within the host directories e.g.
|
3
|
+
|
4
|
+
cd /etc/hosttag
|
5
|
+
mkdir host1 host2 host3
|
6
|
+
touch host1/{tag1,tag2,tag3}
|
7
|
+
touch host2/{tag1,tag2,tag3}
|
8
|
+
|
9
|
+
and then run 'make' to update the hosttag database.
|
10
|
+
|
data/hosttag.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "hosttag"
|
3
|
+
s.version = "0.12"
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.authors = ["Gavin Carr"]
|
6
|
+
s.email = ["gavin@openfusion.net"]
|
7
|
+
s.homepage = "http://github.com/gavincarr/hosttag"
|
8
|
+
s.summary = "Hosttag is a client for tagging hostnames into classes using a redis datastore"
|
9
|
+
s.description = "Hosttag is a client for tagging hostnames into groups or classes,
|
10
|
+
storing them in a redis datastore"
|
11
|
+
s.rubyforge_project = s.name
|
12
|
+
|
13
|
+
s.required_rubygems_version = ">= 1.3.6"
|
14
|
+
|
15
|
+
# If you have runtime dependencies, add them here
|
16
|
+
s.add_runtime_dependency "redis", "~> 2.0"
|
17
|
+
|
18
|
+
# The list of files to be contained in the gem
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
|
21
|
+
s.require_path = 'lib'
|
22
|
+
end
|
23
|
+
|
data/hosttag.spec
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
%define ruby_sitelib %(ruby -rrbconfig -e "puts Config::CONFIG['sitelibdir']")
|
2
|
+
|
3
|
+
Summary: Hosttag client
|
4
|
+
Name: hosttag
|
5
|
+
Version: 0.12
|
6
|
+
Release: 1%{org_tag}%{dist}
|
7
|
+
URL: http://www.openfusion.com.au/labs/
|
8
|
+
Source0: http://www.openfusion.com.au/labs/dist/%{name}-%{version}.tar.gz
|
9
|
+
License: GPL
|
10
|
+
Group: Applications/System
|
11
|
+
BuildRoot: %{_tmppath}/%{name}-%{version}
|
12
|
+
BuildArch: noarch
|
13
|
+
Requires: rubygems, rubygem-redis >= 2.0.0
|
14
|
+
|
15
|
+
%description
|
16
|
+
Hosttag is a client for tagging hostnames into groups or classes, storing
|
17
|
+
the mappings in a redis datastore.
|
18
|
+
|
19
|
+
This package contains the hosttag client utilities.
|
20
|
+
|
21
|
+
%package server-utils
|
22
|
+
Summary: Hosttag server utilities
|
23
|
+
Group: Applications/System
|
24
|
+
Requires: hosttag = %version
|
25
|
+
Requires: redis, rubygems, rubygem-redis >= 2.0
|
26
|
+
Obsoletes: hosttag-server
|
27
|
+
Conflicts: hosttag-server
|
28
|
+
|
29
|
+
%description server-utils
|
30
|
+
Hosttag is a client for tagging hostnames into groups or classes, storing
|
31
|
+
the mappings in a redis datastore.
|
32
|
+
|
33
|
+
This package contains optional hosttag server utilities, allowing you to
|
34
|
+
export and import hosttag mappings to disk. It's not required for normal
|
35
|
+
use, however.
|
36
|
+
|
37
|
+
%prep
|
38
|
+
%setup
|
39
|
+
|
40
|
+
%build
|
41
|
+
|
42
|
+
%install
|
43
|
+
test "%{buildroot}" != "/" && rm -rf %{buildroot}
|
44
|
+
|
45
|
+
mkdir -p %{buildroot}%{ruby_sitelib}/hosttag
|
46
|
+
install -m0644 lib/hosttag.rb %{buildroot}%{ruby_sitelib}/hosttag.rb
|
47
|
+
install -m0644 lib/hosttag/server.rb %{buildroot}%{ruby_sitelib}/hosttag
|
48
|
+
|
49
|
+
mkdir -p %{buildroot}%{_bindir}
|
50
|
+
install -m0755 bin/hosttag %{buildroot}%{_bindir}/hosttag
|
51
|
+
install -m0755 bin/htexport %{buildroot}%{_bindir}/htexport
|
52
|
+
|
53
|
+
# htset and htimport are executable by root only, to restrict tagging to root
|
54
|
+
install -m0700 bin/htset %{buildroot}%{_bindir}/htset
|
55
|
+
install -m0700 bin/htimport %{buildroot}%{_bindir}/htimport
|
56
|
+
#install -m0700 bin/htdump %{buildroot}%{_bindir}/htdump
|
57
|
+
|
58
|
+
mkdir -p %{buildroot}%{_sysconfdir}/%{name}
|
59
|
+
install -m0644 etc/Makefile %{buildroot}%{_sysconfdir}/%{name}
|
60
|
+
install -m0644 etc/README %{buildroot}%{_sysconfdir}/%{name}
|
61
|
+
|
62
|
+
cd %{buildroot}%{_bindir}
|
63
|
+
ln -s hosttag ht
|
64
|
+
cd %{buildroot}%{_bindir}
|
65
|
+
ln -s htset htdel
|
66
|
+
|
67
|
+
%clean
|
68
|
+
test "%{buildroot}" != "/" && rm -rf %{buildroot}
|
69
|
+
|
70
|
+
%files
|
71
|
+
%defattr(-,root,root)
|
72
|
+
%{ruby_sitelib}/hosttag.rb
|
73
|
+
%{ruby_sitelib}/hosttag/*
|
74
|
+
%{_bindir}/hosttag
|
75
|
+
%{_bindir}/ht
|
76
|
+
%attr(0700,root,root) %{_bindir}/htset
|
77
|
+
%{_bindir}/htdel
|
78
|
+
#%attr(0700,root,root) %{_bindir}/htdump
|
79
|
+
%doc README LICENCE
|
80
|
+
|
81
|
+
%files server-utils
|
82
|
+
%defattr(-,root,root)
|
83
|
+
%config(noreplace) %{_sysconfdir}/%{name}/Makefile
|
84
|
+
%{_sysconfdir}/%{name}/README
|
85
|
+
%attr(0755,root,root) %{_bindir}/htexport
|
86
|
+
%attr(0700,root,root) %{_bindir}/htimport
|
87
|
+
|
88
|
+
%changelog
|
89
|
+
* Mon Nov 21 2011 Gavin Carr <gavin@openfusion.com.au> 0.12
|
90
|
+
- Cleanups and tweaks for better compatibility with ruby 1.9.x.
|
91
|
+
|
92
|
+
* Wed Feb 02 2011 Gavin Carr <gavin@openfusion.com.au> 0.11
|
93
|
+
* Add Hosttag::Server support for HOSTTAG_{SERVER,PORT,NAMESPACE} env variables.
|
94
|
+
- Rename hosttag-server subpackage to hosttag-server-utils.
|
95
|
+
|
96
|
+
* Thu Jan 13 2011 Gavin Carr <gavin@openfusion.com.au> 0.10.5
|
97
|
+
- Remove htdump from spec file, since it clashes with htdig utility.
|
98
|
+
|
99
|
+
* Wed Jan 12 2011 Gavin Carr <gavin@openfusion.com.au> 0.10.4
|
100
|
+
- Add support for hosttag -A -l and -T -l.
|
101
|
+
|
102
|
+
* Wed Jan 12 2011 Gavin Carr <gavin@openfusion.com.au> 0.10.3
|
103
|
+
- Fix hosttag.render to handle nil results better.
|
104
|
+
|
105
|
+
* Tue Jan 11 2011 Gavin Carr <gavin@openfusion.com.au> 0.10.2
|
106
|
+
- Fix some buglets in htset.
|
107
|
+
- Move root utils from %{_sbindir} to %{_bindir}.
|
108
|
+
|
109
|
+
* Fri Jan 07 2011 Gavin Carr <gavin@openfusion.com.au> 0.10.1
|
110
|
+
- Fix a couple of small bugs in 0.10.
|
111
|
+
|
112
|
+
* Thu Jan 06 2011 Gavin Carr <gavin@openfusion.com.au> 0.10
|
113
|
+
- Librification release, creating new Hosttag module with core functionality.
|
114
|
+
- Rewrite hosttag, htset, and htimport to use new Hosttag module.
|
115
|
+
- Rewrite hosttag_{add,delete}_tags routines to handle tricksy SKIP corner cases.
|
116
|
+
- Create lib versions of unit tests alongside existing bin ones.
|
117
|
+
- Expand unit test coverage for htset/htdel.
|
118
|
+
- Update unit tests to use library calls instead of calling out to utils.
|
119
|
+
- Change all_{hosts,tags}_* key names for greater clarity.
|
120
|
+
|
121
|
+
* Mon May 24 2010 Gavin Carr <gavin@openfusion.com.au> 0.9
|
122
|
+
- Migrate redis api calls over to redis 2.0.0 gem.
|
123
|
+
- Add --all option to htdel, for deleting all tags from a host.
|
124
|
+
- Add --host and --tag option to htset for ambiguous elements.
|
125
|
+
|
126
|
+
* Fri Feb 12 2010 Gavin Carr <gavin@openfusion.com.au> 0.8.1
|
127
|
+
- Add a -1 argument to hosttag to list results one per line.
|
128
|
+
|
129
|
+
* Mon Feb 08 2010 Gavin Carr <gavin@openfusion.com.au> 0.8
|
130
|
+
- Refactor, pulling server bits into Hosttag::Server.
|
131
|
+
- Add htset unit test, and fix bugs arising.
|
132
|
+
|
133
|
+
* Mon Feb 08 2010 Gavin Carr <gavin@openfusion.com.au> 0.7.1
|
134
|
+
- Fix typo in htset.
|
135
|
+
|
136
|
+
* Fri Feb 05 2010 Gavin Carr <gavin@openfusion.com.au> 0.7
|
137
|
+
- Add namespace option to all binaries.
|
138
|
+
- Add options to htdump.
|
139
|
+
- Add missing htdump to spec file.
|
140
|
+
|
141
|
+
* Wed Feb 03 2010 Gavin Carr <gavin@openfusion.com.au> 0.6.9
|
142
|
+
- Add missing htdel symlink to hosttag package.
|
143
|
+
|
144
|
+
* Tue Feb 02 2010 Gavin Carr <gavin@openfusion.com.au> 0.6.8
|
145
|
+
- Fix bug with htset not deleting host from noskip list if SKIP tag set.
|
146
|
+
|
147
|
+
* Wed Jan 13 2010 Gavin Carr <gavin@openfusion.com.au> 0.6.7
|
148
|
+
- Add --list mode to hosttag.
|
149
|
+
|
150
|
+
* Thu Dec 31 2009 Gavin Carr <gavin@openfusion.com.au> 0.6.6
|
151
|
+
- Rename hosttag_export to htexport, and hosttag_load_data to htimport.
|
152
|
+
- Change old --import parameter to htimport to --delete, like htexport.
|
153
|
+
- Make htimport more verbose, like htexport.
|
154
|
+
|
155
|
+
* Tue Dec 29 2009 Gavin Carr <gavin@openfusion.com.au> 0.6.5
|
156
|
+
- Add hosttag_export utility to export redis db back to directory tree.
|
157
|
+
|
158
|
+
* Tue Dec 08 2009 Gavin Carr <gavin@openfusion.com.au> 0.6
|
159
|
+
- Move from tokyo cabinet/tyrant server to redis-based one.
|
160
|
+
- Rewrite client in ruby.
|
161
|
+
|
162
|
+
* Wed Nov 04 2009 Gavin Carr <gavin@openfusion.com.au> 0.5
|
163
|
+
- Fixes to hosttag_load_data.
|
164
|
+
- Add SKIP tag support to hosttag_load_data.
|
165
|
+
- Mode fixes to hosttag.
|
166
|
+
- Add htserver init scripts to hosttag-server package.
|
167
|
+
|
168
|
+
* Thu Oct 01 2009 Gavin Carr <gavin@openfusion.com.au> 0.4
|
169
|
+
- Rename data to etc, and load_data to hosttag_load_data.
|
170
|
+
- Add -server subpackage to hosttag.spec.
|
171
|
+
|
172
|
+
* Thu Oct 01 2009 Gavin Carr <gavin@openfusion.com.au> 0.3
|
173
|
+
- Change -h|--hosts parameters to -t|--tags (and deprecate -h).
|
174
|
+
- Allow bare 'ht -t' for listing all tags.
|
175
|
+
- Add default rel for multitag and multihost queries.
|
176
|
+
|
177
|
+
* Thu Feb 19 2009 Gavin Carr <gavin@openfusion.com.au> 0.1
|
178
|
+
- Initial package, version 0.1.
|
179
|
+
|
data/lib/hosttag.rb
ADDED
@@ -0,0 +1,402 @@
|
|
1
|
+
|
2
|
+
require 'hosttag/server'
|
3
|
+
|
4
|
+
module Hosttag
|
5
|
+
|
6
|
+
# Lookup the given tag(s), returning an array of hosts to which they apply.
|
7
|
+
# If multiple tags are given, by default the list of hosts is those to
|
8
|
+
# which ALL of the tags apply i.e. results are ANDed or intersected. To
|
9
|
+
# change this, pass :rel => :or in the options hash.
|
10
|
+
# The final argument may be an options hash, which accepts the following
|
11
|
+
# keys:
|
12
|
+
# - :rel - either :and or :or, specifying the relationship to use when
|
13
|
+
# interpreting the set of tags. :rel => :and returns the set of hosts to
|
14
|
+
# which ALL the given tags apply; :rel => :or returns the set of hosts
|
15
|
+
# to which ANY of the tags apply. Default: :rel => :and.
|
16
|
+
# - :include_skip? - flag indicating whether to include hosts that have
|
17
|
+
# the SKIP tag set. Default: false i.e. omit hosts tagged with SKIP.
|
18
|
+
def hosttag_lookup_tags(*args)
|
19
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
20
|
+
return lookup_keys(args, options.merge({ :type => :tag }))
|
21
|
+
end
|
22
|
+
|
23
|
+
# Lookup the given host(s), returning an array of tags that apply to
|
24
|
+
# them. If multiple hosts are given, by default the list of tags is
|
25
|
+
# those applying to ANY of the given hosts i.e. the results are ORed or
|
26
|
+
# unioned. To change this pass an explicit :rel => :and in the options
|
27
|
+
# hash.
|
28
|
+
# The final argument may be an options hash, which accepts the following
|
29
|
+
# keys:
|
30
|
+
# - :rel - either :and or :or, specifying the relationship to use when
|
31
|
+
# interpreting the set of hosts. :rel => :and returns the set of tags
|
32
|
+
# that apply to ALL the given hosts; :rel => :or returns the set of tags
|
33
|
+
# that apply to ANY of the given hosts. Default: :rel => :or.
|
34
|
+
# - :include_skip? - flag indicating whether to include hosts that have
|
35
|
+
# the SKIP tag set. Default: false i.e. omit hosts tagged with SKIP.
|
36
|
+
def hosttag_lookup_hosts(*args)
|
37
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
38
|
+
return lookup_keys(args, options.merge({ :type => :host }))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Lookup the given host(s) or tag(s), returning an array of tags or hosts,
|
42
|
+
# as appropriate. If a :type option is not explicitly given, first tries
|
43
|
+
# the lookup using hosttag_lookup_tags, and if that fails retries using
|
44
|
+
# hosttag_lookup_hosts.
|
45
|
+
# The final argument may be an options hash, which accepts the following
|
46
|
+
# keys:
|
47
|
+
# - :type - either :host or :tag, specifying how to interpret the given
|
48
|
+
# arguments: :type => :host specifies that the arguments are hosts, and
|
49
|
+
# that the resultset should be a list of tags; :type => :tag specifies
|
50
|
+
# that the arguments are tags, and the resultset should be a list of
|
51
|
+
# hosts. Required, no default.
|
52
|
+
# - :rel - either :and or :or, specifying the relationship to use when
|
53
|
+
# interpreting the set of results. :rel => :and returns only results
|
54
|
+
# that have ALL of the given attributes i.e. the AND result set;
|
55
|
+
# :rel => :or returns results that have ANY of the given attributes
|
56
|
+
# i.e. the OR result set. Default: depends on :type - :and for :type
|
57
|
+
# => :host, and :or for :type => :tag.
|
58
|
+
# - :include_skip? - flag indicating whether to include hosts that have
|
59
|
+
# the SKIP tag set. Default: false i.e. omit hosts tagged with SKIP.
|
60
|
+
def hosttag_lookup(*args)
|
61
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
62
|
+
return lookup_keys(args, options) if options[:type]
|
63
|
+
|
64
|
+
begin
|
65
|
+
return hosttag_lookup_tags(args, options)
|
66
|
+
rescue => e
|
67
|
+
begin
|
68
|
+
return hosttag_lookup_hosts(args, options)
|
69
|
+
rescue
|
70
|
+
# If both lookups failed, re-raise original error
|
71
|
+
raise e
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return an array of all hosts
|
77
|
+
# The final argument may be an options hash, which accepts the following
|
78
|
+
# keys:
|
79
|
+
# - :include_skip? - flag indicating whether to include hosts that have
|
80
|
+
# the SKIP tag set. Default: false i.e. omit hosts tagged with SKIP.
|
81
|
+
def hosttag_all_hosts(options)
|
82
|
+
r = hosttag_server(options)
|
83
|
+
key = r.get_key(options[:include_skip?] ? 'all_hosts_full' : 'all_hosts')
|
84
|
+
$stderr.puts "+ key: #{key}" if options[:debug]
|
85
|
+
return r.smembers(key).sort
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return an array of all tags
|
89
|
+
# The final argument may be an options hash, which accepts the following
|
90
|
+
# keys:
|
91
|
+
# - :include_skip? - flag indicating whether to include the SKIP tag.
|
92
|
+
# Default: false. Included for completeness.
|
93
|
+
def hosttag_all_tags(options)
|
94
|
+
r = hosttag_server(options)
|
95
|
+
key = r.get_key(options[:include_skip?] ? 'all_tags_full' : 'all_tags')
|
96
|
+
$stderr.puts "+ key: #{key}" if options[:debug]
|
97
|
+
return r.smembers(key).sort
|
98
|
+
end
|
99
|
+
|
100
|
+
# Add the given tags to all the given hosts
|
101
|
+
def hosttag_add_tags(hosts, tags, options)
|
102
|
+
r = hosttag_server(options)
|
103
|
+
|
104
|
+
# Add tags to each host
|
105
|
+
skip_host = {}
|
106
|
+
all_hosts_skip_hosts = true
|
107
|
+
hosts.each do |host|
|
108
|
+
key = r.get_key('host', host)
|
109
|
+
tags.each { |tag| r.sadd(key, tag) }
|
110
|
+
|
111
|
+
if r.sismember(key, 'SKIP')
|
112
|
+
skip_host[host] = true
|
113
|
+
else
|
114
|
+
all_hosts_skip_hosts = false
|
115
|
+
end
|
116
|
+
|
117
|
+
# Add to all_hosts sets
|
118
|
+
all_hosts = r.get_key('all_hosts')
|
119
|
+
all_hosts_full = r.get_key('all_hosts_full')
|
120
|
+
# all_hosts shouldn't include SKIP hosts, so those we remove
|
121
|
+
if skip_host[host]
|
122
|
+
r.srem(all_hosts, host)
|
123
|
+
else
|
124
|
+
r.sadd(all_hosts, host)
|
125
|
+
end
|
126
|
+
r.sadd(all_hosts_full, host)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Add hosts to each tag
|
130
|
+
recheck_for_skip = false
|
131
|
+
tags.each do |tag|
|
132
|
+
# If we've added a SKIP tag to these hosts, flag to do some extra work
|
133
|
+
recheck_for_skip = true if tag == 'SKIP'
|
134
|
+
|
135
|
+
key = r.get_key('tag', tag)
|
136
|
+
hosts.each do |host|
|
137
|
+
# The standard case is to add the host to the list for this tag.
|
138
|
+
# But we don't want SKIP hosts being included in these lists, so
|
139
|
+
# for them we actually do a remove to make sure they're omitted.
|
140
|
+
if skip_host[host] and tag != 'SKIP'
|
141
|
+
r.srem(key, host)
|
142
|
+
else
|
143
|
+
r.sadd(key, host)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Add to all_tags sets
|
148
|
+
all_tags = r.get_key('all_tags')
|
149
|
+
all_tags_full = r.get_key('all_tags_full')
|
150
|
+
r.sadd(all_tags, tag) unless all_hosts_skip_hosts
|
151
|
+
r.sadd(all_tags_full, tag)
|
152
|
+
end
|
153
|
+
|
154
|
+
# If we've added a SKIP tag here, we need to recheck all tags for all skip hosts
|
155
|
+
recheck_skip_change_for_all_tags(skip_host.keys, :add, r) if recheck_for_skip
|
156
|
+
end
|
157
|
+
|
158
|
+
# Delete the given tags from all the given hosts
|
159
|
+
def hosttag_delete_tags(hosts, tags, options)
|
160
|
+
r = hosttag_server(options)
|
161
|
+
|
162
|
+
# Delete tags from each host
|
163
|
+
non_skip_host = {}
|
164
|
+
hosts.each do |host|
|
165
|
+
key = r.get_key('host', host)
|
166
|
+
tags.each { |tag| r.srem(key, tag) }
|
167
|
+
|
168
|
+
if r.sismember(key, 'SKIP')
|
169
|
+
skip_host = true
|
170
|
+
else
|
171
|
+
non_skip_host[host] = true
|
172
|
+
end
|
173
|
+
|
174
|
+
# Delete from all_hosts sets
|
175
|
+
all_hosts = r.get_key('all_hosts')
|
176
|
+
all_hosts_full = r.get_key('all_hosts_full')
|
177
|
+
# If all tags have been deleted, or this is a SKIP host, remove from all_hosts
|
178
|
+
if r.scard(key) == 0 or skip_host
|
179
|
+
r.srem(all_hosts, host)
|
180
|
+
else
|
181
|
+
# NB: we explicitly add here in case we've deleted a SKIP tag
|
182
|
+
r.sadd(all_hosts, host)
|
183
|
+
end
|
184
|
+
if r.scard(key) == 0
|
185
|
+
r.srem(all_hosts_full, host)
|
186
|
+
r.del(key)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Delete hosts from each tag
|
191
|
+
recheck_for_skip = false
|
192
|
+
all_tags = r.get_key('all_tags')
|
193
|
+
all_tags_full = r.get_key('all_tags_full')
|
194
|
+
tags.each do |tag|
|
195
|
+
# If we've deleted a SKIP tag from these hosts, flag to do some extra work
|
196
|
+
recheck_for_skip = true if tag == 'SKIP'
|
197
|
+
|
198
|
+
tag_key = r.get_key('tag', tag)
|
199
|
+
hosts.each { |host| r.srem(tag_key, host) }
|
200
|
+
|
201
|
+
# Delete from all_tags sets
|
202
|
+
# If all hosts have been deleted (or this is the SKIP tag), remove from all_tags
|
203
|
+
if r.scard(tag_key) == 0 or tag == 'SKIP'
|
204
|
+
r.srem(all_tags, tag)
|
205
|
+
else
|
206
|
+
# NB: we explicitly add here in case we've deleted a SKIP tag
|
207
|
+
r.sadd(all_tags, tag)
|
208
|
+
end
|
209
|
+
if r.scard(tag_key) == 0
|
210
|
+
r.srem(all_tags_full, tag)
|
211
|
+
r.del(tag_key)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
r.del(all_tags) if r.scard(all_tags) == 0
|
215
|
+
r.del(all_tags_full) if r.scard(all_tags_full) == 0
|
216
|
+
|
217
|
+
# If we've deleted a SKIP tag here, we need to recheck all tags for all non-skip hosts
|
218
|
+
recheck_skip_change_for_all_tags(non_skip_host.keys, :delete, r) if recheck_for_skip
|
219
|
+
end
|
220
|
+
|
221
|
+
# Delete all hosts and tags in the hosttag datastore. This is the nuclear option,
|
222
|
+
# used in hosttag_import_from_directory if :delete => true. Interactively confirms
|
223
|
+
# unless the :autoconfirm option is set.
|
224
|
+
# The final argument may be an options hash, which accepts the following
|
225
|
+
# keys:
|
226
|
+
# - :autoconfirm - if true, truncate without asking for any confirmation
|
227
|
+
def hosttag_truncate(options)
|
228
|
+
if not options[:autoconfirm]
|
229
|
+
print "Do you really want to delete EVERYTHING from your datastore? [yN] "
|
230
|
+
$stdout.flush
|
231
|
+
confirm = $stdin.gets.chomp
|
232
|
+
return unless confirm =~ %r{^y}i
|
233
|
+
end
|
234
|
+
|
235
|
+
r = hosttag_server(options)
|
236
|
+
r.keys(r.get_key("*")).each { |k| r.del(k) }
|
237
|
+
end
|
238
|
+
|
239
|
+
# Delete all tags from the given hosts. Interactively confirms the deletions
|
240
|
+
# unless the :autoconfirm option is set.
|
241
|
+
# The final argument may be an options hash, which accepts the following
|
242
|
+
# keys:
|
243
|
+
# - :autoconfirm - if true, do deletes without asking for any confirmation
|
244
|
+
def hosttag_delete_all_tags(hosts, options)
|
245
|
+
if not options[:autoconfirm]
|
246
|
+
host_str = hosts.join(' ')
|
247
|
+
print "Do you want to delete all tags on the following host(s):\n #{host_str}\nConfirm? [yN] "
|
248
|
+
$stdout.flush
|
249
|
+
confirm = $stdin.gets.chomp
|
250
|
+
return unless confirm =~ %r{^y}i
|
251
|
+
end
|
252
|
+
|
253
|
+
hosts.each do |host|
|
254
|
+
begin
|
255
|
+
tags = hosttag_lookup_hosts(host, options)
|
256
|
+
hosttag_delete_tags([ host ], tags, options)
|
257
|
+
rescue
|
258
|
+
warn "Warning: invalid host '#{host}' - cannot delete"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Import hosts and tags from the given directory. The directory is
|
264
|
+
# expected to contain a set of directories, representing hosts; each
|
265
|
+
# file within those directories is treated as a tag that applies to
|
266
|
+
# that host.
|
267
|
+
# Options is a hash which accepts the following keys:
|
268
|
+
# - :delete - if true, delete ALL hosts and tags from the datastore
|
269
|
+
# before doing the import.
|
270
|
+
# - :autoconfirm - if true, don't interactively confirm deletions
|
271
|
+
def hosttag_import_from_directory(datadir, options)
|
272
|
+
# Delete ALL hosts and tags from the datastore if options[:delete] set
|
273
|
+
hosttag_truncate(options) if options[:delete]
|
274
|
+
|
275
|
+
# Load directory into a { host => [ taglist ] } hash
|
276
|
+
host_tag_hash = load_directory(datadir, options)
|
277
|
+
|
278
|
+
# Add all hosts and tags
|
279
|
+
host_tag_hash.each do |host, tags|
|
280
|
+
hosttag_add_tags([ host ], tags, options)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
private
|
285
|
+
|
286
|
+
# Lookup the given keys in the redis datastore, returning an array of
|
287
|
+
# results. If more than one key is specified, resultsets are merged
|
288
|
+
# (either ANDed or ORed) depending on the value of the :rel option.
|
289
|
+
# The final argument must be an options hash, which accepts the
|
290
|
+
# following options:
|
291
|
+
# - :type - specifies the type of keys to lookup, either :host or :tag.
|
292
|
+
# Required.
|
293
|
+
# - :rel - specifies how to merge multiple resultsets, either :and (set
|
294
|
+
# intersection) or :or (set union).
|
295
|
+
def lookup_keys(*args)
|
296
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
297
|
+
args.flatten!
|
298
|
+
|
299
|
+
type = options[:type]
|
300
|
+
throw "Required option 'type' missing" if not type
|
301
|
+
rel = options[:rel]
|
302
|
+
|
303
|
+
r = hosttag_server(options)
|
304
|
+
|
305
|
+
# Default a rel if we have multiple args
|
306
|
+
if args.length > 1 and not rel
|
307
|
+
rel = (type == :tag ? :and : :or)
|
308
|
+
end
|
309
|
+
$stderr.puts "+ rel (#{type}): #{rel}" if rel and options[:debug]
|
310
|
+
|
311
|
+
# Map keys to fetch
|
312
|
+
keys = args.collect {|v| r.get_key(type, v) }
|
313
|
+
$stderr.puts "+ keys: #{keys.join(' ')}" if options[:debug]
|
314
|
+
|
315
|
+
# Check all keys exist
|
316
|
+
keys.each do |k|
|
317
|
+
if not r.exists(k)
|
318
|
+
item = k.sub(%r{^[^:]+::[^:]+:}, '')
|
319
|
+
raise "Error: #{type} '#{item}' not found."
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Lookup and return
|
324
|
+
if keys.length == 1
|
325
|
+
r.smembers(keys[0]).sort
|
326
|
+
elsif rel == :and
|
327
|
+
r.sinter(*keys).sort
|
328
|
+
else
|
329
|
+
r.sunion(*keys).sort
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# If we've added or removed a SKIP tag, we now have to recheck all tags for
|
334
|
+
# the given hosts, removing or re-adding them from/to those tag sets, and
|
335
|
+
# then recalculate the all_tags set for each of those tags
|
336
|
+
def recheck_skip_change_for_all_tags(hosts, change, r)
|
337
|
+
recheck_tags = {}
|
338
|
+
hosts.each do |host|
|
339
|
+
host_key = r.get_key('host', host)
|
340
|
+
r.smembers(host_key).each do |tag|
|
341
|
+
next if tag == 'SKIP'
|
342
|
+
|
343
|
+
tag_key = r.get_key('tag', tag)
|
344
|
+
# If we've added SKIP tags, then we remove the host from tagsets
|
345
|
+
# (or vice-versa)
|
346
|
+
if change == :add
|
347
|
+
r.srem(tag_key, host)
|
348
|
+
else
|
349
|
+
r.sadd(tag_key, host)
|
350
|
+
end
|
351
|
+
|
352
|
+
recheck_tags[tag] = tag_key
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# Now recheck the all_tags set, adding tags that have hosts, and
|
357
|
+
# removing any that don't
|
358
|
+
all_tags = r.get_key('all_tags')
|
359
|
+
recheck_tags.each do |tag, tag_key|
|
360
|
+
tag_host_count = r.scard(tag_key)
|
361
|
+
if tag_host_count == 0
|
362
|
+
r.srem(all_tags, tag)
|
363
|
+
else
|
364
|
+
r.sadd(all_tags, tag)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
r.del(all_tags) if r.scard(all_tags) == 0
|
368
|
+
end
|
369
|
+
|
370
|
+
# Load all host/tag files in datadir, returning a { host => [ taglist ] } hash
|
371
|
+
def load_directory(datadir, options)
|
372
|
+
host_tag_hash = {}
|
373
|
+
|
374
|
+
Dir.glob("#{datadir}/*").each do |host_path|
|
375
|
+
next if not File.directory?(host_path)
|
376
|
+
host = host_path.sub(/^#{datadir}\//, '')
|
377
|
+
|
378
|
+
host_tag_hash[host] = []
|
379
|
+
Dir.glob("#{host_path}/*").each do |tag_path|
|
380
|
+
next if not File.file?(tag_path)
|
381
|
+
tag = File.basename(tag_path)
|
382
|
+
host_tag_hash[host].push(tag)
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
return host_tag_hash
|
387
|
+
end
|
388
|
+
|
389
|
+
def die(error)
|
390
|
+
warn error
|
391
|
+
exit 1
|
392
|
+
end
|
393
|
+
|
394
|
+
def hosttag_server(options)
|
395
|
+
begin
|
396
|
+
r = Hosttag::Server.new(options)
|
397
|
+
rescue Resolv::ResolvError => e
|
398
|
+
die e
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
end
|