choria-mcorpc-support 0.0.1
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.
- checksums.yaml +7 -0
- data/bin/mco +64 -0
- data/lib/mcollective.rb +63 -0
- data/lib/mcollective/agent.rb +5 -0
- data/lib/mcollective/agents.rb +149 -0
- data/lib/mcollective/aggregate.rb +85 -0
- data/lib/mcollective/aggregate/average.ddl +33 -0
- data/lib/mcollective/aggregate/average.rb +29 -0
- data/lib/mcollective/aggregate/base.rb +40 -0
- data/lib/mcollective/aggregate/result.rb +9 -0
- data/lib/mcollective/aggregate/result/base.rb +25 -0
- data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
- data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
- data/lib/mcollective/aggregate/sum.ddl +33 -0
- data/lib/mcollective/aggregate/sum.rb +18 -0
- data/lib/mcollective/aggregate/summary.ddl +33 -0
- data/lib/mcollective/aggregate/summary.rb +53 -0
- data/lib/mcollective/application.rb +365 -0
- data/lib/mcollective/application/completion.rb +104 -0
- data/lib/mcollective/application/describe_filter.rb +87 -0
- data/lib/mcollective/application/facts.rb +62 -0
- data/lib/mcollective/application/find.rb +23 -0
- data/lib/mcollective/application/help.rb +28 -0
- data/lib/mcollective/application/inventory.rb +344 -0
- data/lib/mcollective/application/ping.rb +82 -0
- data/lib/mcollective/application/plugin.rb +369 -0
- data/lib/mcollective/application/rpc.rb +111 -0
- data/lib/mcollective/applications.rb +134 -0
- data/lib/mcollective/cache.rb +145 -0
- data/lib/mcollective/client.rb +353 -0
- data/lib/mcollective/config.rb +245 -0
- data/lib/mcollective/connector.rb +18 -0
- data/lib/mcollective/connector/base.rb +26 -0
- data/lib/mcollective/data.rb +91 -0
- data/lib/mcollective/data/agent_data.ddl +22 -0
- data/lib/mcollective/data/agent_data.rb +17 -0
- data/lib/mcollective/data/base.rb +67 -0
- data/lib/mcollective/data/collective_data.ddl +20 -0
- data/lib/mcollective/data/collective_data.rb +9 -0
- data/lib/mcollective/data/fact_data.ddl +28 -0
- data/lib/mcollective/data/fact_data.rb +55 -0
- data/lib/mcollective/data/fstat_data.ddl +89 -0
- data/lib/mcollective/data/fstat_data.rb +56 -0
- data/lib/mcollective/data/result.rb +45 -0
- data/lib/mcollective/ddl.rb +113 -0
- data/lib/mcollective/ddl/agentddl.rb +253 -0
- data/lib/mcollective/ddl/base.rb +217 -0
- data/lib/mcollective/ddl/dataddl.rb +56 -0
- data/lib/mcollective/ddl/discoveryddl.rb +52 -0
- data/lib/mcollective/ddl/validatorddl.rb +6 -0
- data/lib/mcollective/discovery.rb +143 -0
- data/lib/mcollective/discovery/flatfile.ddl +11 -0
- data/lib/mcollective/discovery/flatfile.rb +48 -0
- data/lib/mcollective/discovery/mc.ddl +11 -0
- data/lib/mcollective/discovery/mc.rb +30 -0
- data/lib/mcollective/discovery/stdin.ddl +11 -0
- data/lib/mcollective/discovery/stdin.rb +68 -0
- data/lib/mcollective/exceptions.rb +28 -0
- data/lib/mcollective/facts.rb +39 -0
- data/lib/mcollective/facts/base.rb +100 -0
- data/lib/mcollective/facts/yaml_facts.rb +65 -0
- data/lib/mcollective/generators.rb +7 -0
- data/lib/mcollective/generators/agent_generator.rb +51 -0
- data/lib/mcollective/generators/base.rb +46 -0
- data/lib/mcollective/generators/data_generator.rb +51 -0
- data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
- data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
- data/lib/mcollective/generators/templates/ddl.erb +8 -0
- data/lib/mcollective/generators/templates/plugin.erb +7 -0
- data/lib/mcollective/log.rb +118 -0
- data/lib/mcollective/logger.rb +5 -0
- data/lib/mcollective/logger/base.rb +77 -0
- data/lib/mcollective/logger/console_logger.rb +61 -0
- data/lib/mcollective/logger/file_logger.rb +53 -0
- data/lib/mcollective/logger/syslog_logger.rb +53 -0
- data/lib/mcollective/matcher.rb +224 -0
- data/lib/mcollective/matcher/parser.rb +128 -0
- data/lib/mcollective/matcher/scanner.rb +241 -0
- data/lib/mcollective/message.rb +248 -0
- data/lib/mcollective/monkey_patches.rb +152 -0
- data/lib/mcollective/optionparser.rb +197 -0
- data/lib/mcollective/pluginmanager.rb +180 -0
- data/lib/mcollective/pluginpackager.rb +98 -0
- data/lib/mcollective/pluginpackager/agent_definition.rb +94 -0
- data/lib/mcollective/pluginpackager/debpackage_packager.rb +237 -0
- data/lib/mcollective/pluginpackager/modulepackage_packager.rb +127 -0
- data/lib/mcollective/pluginpackager/ospackage_packager.rb +59 -0
- data/lib/mcollective/pluginpackager/rpmpackage_packager.rb +180 -0
- data/lib/mcollective/pluginpackager/standard_definition.rb +69 -0
- data/lib/mcollective/pluginpackager/templates/debian/Makefile.erb +7 -0
- data/lib/mcollective/pluginpackager/templates/debian/changelog.erb +5 -0
- data/lib/mcollective/pluginpackager/templates/debian/compat.erb +1 -0
- data/lib/mcollective/pluginpackager/templates/debian/control.erb +15 -0
- data/lib/mcollective/pluginpackager/templates/debian/copyright.erb +8 -0
- data/lib/mcollective/pluginpackager/templates/debian/rules.erb +6 -0
- data/lib/mcollective/pluginpackager/templates/module/Modulefile.erb +5 -0
- data/lib/mcollective/pluginpackager/templates/module/README.md.erb +37 -0
- data/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb +9 -0
- data/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb +63 -0
- data/lib/mcollective/registration/base.rb +91 -0
- data/lib/mcollective/rpc.rb +182 -0
- data/lib/mcollective/rpc/actionrunner.rb +158 -0
- data/lib/mcollective/rpc/agent.rb +374 -0
- data/lib/mcollective/rpc/audit.rb +38 -0
- data/lib/mcollective/rpc/client.rb +1066 -0
- data/lib/mcollective/rpc/helpers.rb +321 -0
- data/lib/mcollective/rpc/progress.rb +63 -0
- data/lib/mcollective/rpc/reply.rb +87 -0
- data/lib/mcollective/rpc/request.rb +86 -0
- data/lib/mcollective/rpc/result.rb +90 -0
- data/lib/mcollective/rpc/stats.rb +294 -0
- data/lib/mcollective/runnerstats.rb +90 -0
- data/lib/mcollective/security.rb +26 -0
- data/lib/mcollective/security/base.rb +244 -0
- data/lib/mcollective/shell.rb +126 -0
- data/lib/mcollective/ssl.rb +285 -0
- data/lib/mcollective/util.rb +579 -0
- data/lib/mcollective/validator.rb +85 -0
- data/lib/mcollective/validator/array_validator.ddl +7 -0
- data/lib/mcollective/validator/array_validator.rb +9 -0
- data/lib/mcollective/validator/ipv4address_validator.ddl +7 -0
- data/lib/mcollective/validator/ipv4address_validator.rb +16 -0
- data/lib/mcollective/validator/ipv6address_validator.ddl +7 -0
- data/lib/mcollective/validator/ipv6address_validator.rb +16 -0
- data/lib/mcollective/validator/length_validator.ddl +7 -0
- data/lib/mcollective/validator/length_validator.rb +11 -0
- data/lib/mcollective/validator/regex_validator.ddl +7 -0
- data/lib/mcollective/validator/regex_validator.rb +9 -0
- data/lib/mcollective/validator/shellsafe_validator.ddl +7 -0
- data/lib/mcollective/validator/shellsafe_validator.rb +13 -0
- data/lib/mcollective/validator/typecheck_validator.ddl +7 -0
- data/lib/mcollective/validator/typecheck_validator.rb +28 -0
- metadata +215 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
metadata :name => "Collective",
|
|
2
|
+
:description => "Collective membership",
|
|
3
|
+
:author => "Puppet Labs",
|
|
4
|
+
:license => "ASL 2.0",
|
|
5
|
+
:version => "1.0",
|
|
6
|
+
:url => "https://docs.puppetlabs.com/mcollective/",
|
|
7
|
+
:timeout => 1
|
|
8
|
+
|
|
9
|
+
dataquery :description => "Collective" do
|
|
10
|
+
input :query,
|
|
11
|
+
:prompt => 'Collective',
|
|
12
|
+
:description => 'Collective name to ask about, eg mcollective',
|
|
13
|
+
:type => :string,
|
|
14
|
+
:validation => /./,
|
|
15
|
+
:maxlength => 256
|
|
16
|
+
|
|
17
|
+
output :member,
|
|
18
|
+
:description => 'Node is a member of the named collective',
|
|
19
|
+
:display_as => 'member'
|
|
20
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
metadata :name => "Fact",
|
|
2
|
+
:description => "Structured fact query",
|
|
3
|
+
:author => "Puppet Labs",
|
|
4
|
+
:license => "ASL 2.0",
|
|
5
|
+
:version => "1.0",
|
|
6
|
+
:url => "https://docs.puppetlabs.com/mcollective/",
|
|
7
|
+
:timeout => 1
|
|
8
|
+
|
|
9
|
+
dataquery :description => "Fact" do
|
|
10
|
+
input :query,
|
|
11
|
+
:prompt => 'Fact Path',
|
|
12
|
+
:description => 'Path to a fact, eg network.eth0.address',
|
|
13
|
+
:type => :string,
|
|
14
|
+
:validation => /./,
|
|
15
|
+
:maxlength => 256
|
|
16
|
+
|
|
17
|
+
output :exists,
|
|
18
|
+
:description => 'Fact is present',
|
|
19
|
+
:display_as => 'exists'
|
|
20
|
+
|
|
21
|
+
output :value,
|
|
22
|
+
:description => 'Fact value',
|
|
23
|
+
:display_as => 'value'
|
|
24
|
+
|
|
25
|
+
output :value_encoding,
|
|
26
|
+
:description => 'Encoding of the fact value (text/plain or application/json)',
|
|
27
|
+
:display_as => 'value_encoding'
|
|
28
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module MCollective
|
|
2
|
+
module Data
|
|
3
|
+
class Fact_data<Base
|
|
4
|
+
query do |path|
|
|
5
|
+
parts = path.split /\./
|
|
6
|
+
walk_path(parts)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def walk_path(path)
|
|
12
|
+
# Set up results as though we didn't find the value
|
|
13
|
+
result[:exists] = false
|
|
14
|
+
result[:value] = false
|
|
15
|
+
result[:value_encoding] = false
|
|
16
|
+
|
|
17
|
+
facts = PluginManager['facts_plugin'].get_facts
|
|
18
|
+
|
|
19
|
+
path.each do |level|
|
|
20
|
+
case facts
|
|
21
|
+
when Array
|
|
22
|
+
level = Integer(level)
|
|
23
|
+
if level >= facts.size
|
|
24
|
+
# array index out would be out of bounds, so we don't have the value
|
|
25
|
+
return
|
|
26
|
+
end
|
|
27
|
+
when Hash
|
|
28
|
+
if !facts.include?(level)
|
|
29
|
+
# we don't have the key for the next level, so give up
|
|
30
|
+
return
|
|
31
|
+
end
|
|
32
|
+
else
|
|
33
|
+
# this isn't a container data type, so we can't walk into it
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
facts = facts[level]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
result[:exists] = true
|
|
41
|
+
case facts
|
|
42
|
+
when Array, Hash
|
|
43
|
+
# Currently data plugins cannot return structured data, so until
|
|
44
|
+
# this is fixed flatten the data with json and flag that we have
|
|
45
|
+
# munged the data
|
|
46
|
+
result[:value] = facts.to_json
|
|
47
|
+
result[:value_encoding] = 'application/json'
|
|
48
|
+
else
|
|
49
|
+
result[:value] = facts
|
|
50
|
+
result[:value_encoding] = 'text/plain'
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
metadata :name => "File Stat",
|
|
2
|
+
:description => "Retrieve file stat data for a given file",
|
|
3
|
+
:author => "R.I.Pienaar <rip@devco.net>",
|
|
4
|
+
:license => "ASL 2.0",
|
|
5
|
+
:version => "1.0",
|
|
6
|
+
:url => "https://docs.puppetlabs.com/mcollective/",
|
|
7
|
+
:timeout => 1
|
|
8
|
+
|
|
9
|
+
dataquery :description => "File stat information" do
|
|
10
|
+
input :query,
|
|
11
|
+
:prompt => "File Name",
|
|
12
|
+
:description => "Valid File Name",
|
|
13
|
+
:type => :string,
|
|
14
|
+
:validation => /.+/,
|
|
15
|
+
:maxlength => 120
|
|
16
|
+
|
|
17
|
+
output :name,
|
|
18
|
+
:description => "File name",
|
|
19
|
+
:display_as => "Name"
|
|
20
|
+
|
|
21
|
+
output :output,
|
|
22
|
+
:description => "Human readable information about the file",
|
|
23
|
+
:display_as => "Status"
|
|
24
|
+
|
|
25
|
+
output :present,
|
|
26
|
+
:description => "Indicates if the file exist using 0 or 1",
|
|
27
|
+
:display_as => "Present"
|
|
28
|
+
|
|
29
|
+
output :size,
|
|
30
|
+
:description => "File size",
|
|
31
|
+
:display_as => "Size"
|
|
32
|
+
|
|
33
|
+
output :mode,
|
|
34
|
+
:description => "File mode",
|
|
35
|
+
:display_as => "Mode"
|
|
36
|
+
|
|
37
|
+
output :md5,
|
|
38
|
+
:description => "File MD5 digest",
|
|
39
|
+
:display_as => "MD5"
|
|
40
|
+
|
|
41
|
+
output :mtime,
|
|
42
|
+
:description => "File modification time",
|
|
43
|
+
:display_as => "Modification time"
|
|
44
|
+
|
|
45
|
+
output :ctime,
|
|
46
|
+
:description => "File change time",
|
|
47
|
+
:display_as => "Change time"
|
|
48
|
+
|
|
49
|
+
output :atime,
|
|
50
|
+
:description => "File access time",
|
|
51
|
+
:display_as => "Access time"
|
|
52
|
+
|
|
53
|
+
output :mtime_seconds,
|
|
54
|
+
:description => "File modification time in seconds",
|
|
55
|
+
:display_as => "Modification time"
|
|
56
|
+
|
|
57
|
+
output :ctime_seconds,
|
|
58
|
+
:description => "File change time in seconds",
|
|
59
|
+
:display_as => "Change time"
|
|
60
|
+
|
|
61
|
+
output :atime_seconds,
|
|
62
|
+
:description => "File access time in seconds",
|
|
63
|
+
:display_as => "Access time"
|
|
64
|
+
|
|
65
|
+
output :mtime_age,
|
|
66
|
+
:description => "File modification age in seconds",
|
|
67
|
+
:display_as => "Modification age"
|
|
68
|
+
|
|
69
|
+
output :ctime_age,
|
|
70
|
+
:description => "File change age in seconds",
|
|
71
|
+
:display_as => "Change age"
|
|
72
|
+
|
|
73
|
+
output :atime_age,
|
|
74
|
+
:description => "File access age in seconds",
|
|
75
|
+
:display_as => "Access age"
|
|
76
|
+
|
|
77
|
+
output :uid,
|
|
78
|
+
:description => "File owner",
|
|
79
|
+
:display_as => "Owner"
|
|
80
|
+
|
|
81
|
+
output :gid,
|
|
82
|
+
:description => "File group",
|
|
83
|
+
:display_as => "Group"
|
|
84
|
+
|
|
85
|
+
output :type,
|
|
86
|
+
:description => "File type",
|
|
87
|
+
:display_as => "Type"
|
|
88
|
+
end
|
|
89
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module MCollective
|
|
2
|
+
module Data
|
|
3
|
+
class Fstat_data<Base
|
|
4
|
+
query do |file|
|
|
5
|
+
result[:name] = file
|
|
6
|
+
result[:output] = "not present"
|
|
7
|
+
result[:type] = "unknown"
|
|
8
|
+
result[:mode] = "0000"
|
|
9
|
+
result[:present] = 0
|
|
10
|
+
result[:size] = 0
|
|
11
|
+
result[:mtime] = 0
|
|
12
|
+
result[:ctime] = 0
|
|
13
|
+
result[:atime] = 0
|
|
14
|
+
result[:mtime_seconds] = 0
|
|
15
|
+
result[:ctime_seconds] = 0
|
|
16
|
+
result[:atime_seconds] = 0
|
|
17
|
+
result[:md5] = 0
|
|
18
|
+
result[:uid] = 0
|
|
19
|
+
result[:gid] = 0
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if File.exists?(file)
|
|
23
|
+
result[:output] = "present"
|
|
24
|
+
result[:present] = 1
|
|
25
|
+
|
|
26
|
+
if File.symlink?(file)
|
|
27
|
+
stat = File.lstat(file)
|
|
28
|
+
else
|
|
29
|
+
stat = File.stat(file)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
[:size, :uid, :gid].each do |item|
|
|
33
|
+
result[item] = stat.send(item)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
[:mtime, :ctime, :atime].each do |item|
|
|
37
|
+
result[item] = stat.send(item).strftime("%F %T")
|
|
38
|
+
result["#{item}_seconds".to_sym] = stat.send(item).to_i
|
|
39
|
+
result["#{item}_age".to_sym] = Time.now.to_i - stat.send(item).to_i
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
result[:mode] = "%o" % [stat.mode]
|
|
43
|
+
result[:md5] = Digest::MD5.hexdigest(File.read(file)) if stat.file?
|
|
44
|
+
|
|
45
|
+
result[:type] = "directory" if stat.directory?
|
|
46
|
+
result[:type] = "file" if stat.file?
|
|
47
|
+
result[:type] = "symlink" if stat.symlink?
|
|
48
|
+
result[:type] = "socket" if stat.socket?
|
|
49
|
+
result[:type] = "chardev" if stat.chardev?
|
|
50
|
+
result[:type] = "blockdev" if stat.blockdev?
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module MCollective
|
|
2
|
+
module Data
|
|
3
|
+
class Result
|
|
4
|
+
# remove some methods that might clash with commonly
|
|
5
|
+
# used return data to improve the effectiveness of the
|
|
6
|
+
# method_missing lookup strategy
|
|
7
|
+
undef :type if method_defined?(:type)
|
|
8
|
+
|
|
9
|
+
def initialize(outputs)
|
|
10
|
+
@data = {}
|
|
11
|
+
|
|
12
|
+
outputs.keys.each do |output|
|
|
13
|
+
@data[output] = Marshal.load(Marshal.dump(outputs[output].fetch(:default, nil)))
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def include?(key)
|
|
18
|
+
@data.include?(key.to_sym)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def [](key)
|
|
22
|
+
@data[key.to_sym]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def []=(key, val)
|
|
26
|
+
# checks using the string representation of the class name to avoid deprecations on Bignum and Fixnum
|
|
27
|
+
raise "Can only store String, Integer, Float or Boolean data but got #{val.class} for key #{key}" unless ["String", "Integer", "Bignum", "Fixnum", "Float", "TrueClass", "FalseClass"].include?(val.class.to_s)
|
|
28
|
+
|
|
29
|
+
@data[key.to_sym] = val
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def keys
|
|
33
|
+
@data.keys
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def method_missing(method, *args)
|
|
37
|
+
key = method.to_sym
|
|
38
|
+
|
|
39
|
+
raise NoMethodError, "undefined local variable or method `%s'" % key unless include?(key)
|
|
40
|
+
|
|
41
|
+
@data[key]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
module MCollective
|
|
2
|
+
# A set of classes that helps create data description language files
|
|
3
|
+
# for plugins. You can define meta data, actions, input and output
|
|
4
|
+
# describing the behavior of your agent or other plugins
|
|
5
|
+
#
|
|
6
|
+
# DDL files are used for input validation, constructing outputs,
|
|
7
|
+
# producing online help, informing the various display routines and
|
|
8
|
+
# so forth.
|
|
9
|
+
#
|
|
10
|
+
# A sample DDL for an agent be seen below, you'd put this in your agent
|
|
11
|
+
# dir as <agent name>.ddl
|
|
12
|
+
#
|
|
13
|
+
# metadata :name => "SimpleRPC Service Agent",
|
|
14
|
+
# :description => "Agent to manage services using the Puppet service provider",
|
|
15
|
+
# :author => "R.I.Pienaar",
|
|
16
|
+
# :license => "GPLv2",
|
|
17
|
+
# :version => "1.1",
|
|
18
|
+
# :url => "http://mcollective-plugins.googlecode.com/",
|
|
19
|
+
# :timeout => 60
|
|
20
|
+
#
|
|
21
|
+
# action "status", :description => "Gets the status of a service" do
|
|
22
|
+
# display :always
|
|
23
|
+
#
|
|
24
|
+
# input :service,
|
|
25
|
+
# :prompt => "Service Name",
|
|
26
|
+
# :description => "The service to get the status for",
|
|
27
|
+
# :type => :string,
|
|
28
|
+
# :validation => '^[a-zA-Z\-_\d]+$',
|
|
29
|
+
# :optional => true,
|
|
30
|
+
# :maxlength => 30
|
|
31
|
+
#
|
|
32
|
+
# output :status,
|
|
33
|
+
# :description => "The status of service",
|
|
34
|
+
# :display_as => "Service Status"
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# There are now many types of DDL and ultimately all pugins should have
|
|
38
|
+
# DDL files. The code is organized so that any plugin type will magically
|
|
39
|
+
# just work - they will be an instane of Base which has #metadata and a few
|
|
40
|
+
# common cases.
|
|
41
|
+
#
|
|
42
|
+
# For plugin types that require more specific behaviors they can just add a
|
|
43
|
+
# class here that inherits from Base and add their specific behavior.
|
|
44
|
+
#
|
|
45
|
+
# Base defines a specific behavior for input, output and metadata which we'd
|
|
46
|
+
# like to keep standard across plugin types so do not completely override the
|
|
47
|
+
# behavior of input. The methods are written that they will gladly store extra
|
|
48
|
+
# content though so you add, do not remove. See the AgentDDL class for an example
|
|
49
|
+
# where agents want a :required argument to be always set.
|
|
50
|
+
module DDL
|
|
51
|
+
require "mcollective/ddl/base"
|
|
52
|
+
require "mcollective/ddl/agentddl"
|
|
53
|
+
require "mcollective/ddl/dataddl"
|
|
54
|
+
require "mcollective/ddl/discoveryddl"
|
|
55
|
+
|
|
56
|
+
# There used to be only one big nasty DDL class with a bunch of mashed
|
|
57
|
+
# together behaviors. It's been around for ages and we would rather not
|
|
58
|
+
# ask all the users to change their DDL.new calls to some other factory
|
|
59
|
+
# method that would have this exact same behavior.
|
|
60
|
+
#
|
|
61
|
+
# So we override the behavior of #new which is a hugely sucky thing to do
|
|
62
|
+
# but ultimately it's what would be least disrupting to code out there
|
|
63
|
+
# today. We did though change DDL to a module to make it possibly a
|
|
64
|
+
# little less suprising, possibly.
|
|
65
|
+
def self.new(*args, &blk)
|
|
66
|
+
load_and_cache(*args)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.load_and_cache(*args)
|
|
70
|
+
Cache.setup(:ddl, 300)
|
|
71
|
+
|
|
72
|
+
plugin = args.first
|
|
73
|
+
args.size > 1 ? type = args[1].to_s : type = "agent"
|
|
74
|
+
path = "%s/%s" % [type, plugin]
|
|
75
|
+
|
|
76
|
+
begin
|
|
77
|
+
ddl = Cache.read(:ddl, path)
|
|
78
|
+
rescue
|
|
79
|
+
begin
|
|
80
|
+
klass = DDL.const_get("%sDDL" % type.capitalize)
|
|
81
|
+
rescue NameError
|
|
82
|
+
klass = Base
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
ddl = Cache.write(:ddl, path, klass.new(*args))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
return ddl
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# As we're taking arguments on the command line we need a
|
|
92
|
+
# way to input booleans, true on the cli is a string so this
|
|
93
|
+
# method will take the ddl, find all arguments that are supposed
|
|
94
|
+
# to be boolean and if they are the strings "true"/"yes" or "false"/"no"
|
|
95
|
+
# turn them into the matching boolean
|
|
96
|
+
def self.string_to_boolean(val)
|
|
97
|
+
return true if ["true", "t", "yes", "y", "1"].include?(val.downcase)
|
|
98
|
+
return false if ["false", "f", "no", "n", "0"].include?(val.downcase)
|
|
99
|
+
|
|
100
|
+
raise "#{val} does not look like a boolean argument"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# a generic string to number function, if a number looks like a float
|
|
104
|
+
# it turns it into a float else an int. This is naive but should be sufficient
|
|
105
|
+
# for numbers typed on the cli in most cases
|
|
106
|
+
def self.string_to_number(val)
|
|
107
|
+
return val.to_f if val =~ /^\d+\.\d+$/
|
|
108
|
+
return val.to_i if val =~ /^\d+$/
|
|
109
|
+
|
|
110
|
+
raise "#{val} does not look like a number"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
module MCollective
|
|
2
|
+
module DDL
|
|
3
|
+
# A DDL class specific to agent plugins.
|
|
4
|
+
#
|
|
5
|
+
# A full DDL can be seen below with all the possible bells and whistles present.
|
|
6
|
+
#
|
|
7
|
+
# metadata :name => "Utilities and Helpers for SimpleRPC Agents",
|
|
8
|
+
# :description => "General helpful actions that expose stats and internals to SimpleRPC clients",
|
|
9
|
+
# :author => "R.I.Pienaar <rip@devco.net>",
|
|
10
|
+
# :license => "Apache License, Version 2.0",
|
|
11
|
+
# :version => "1.0",
|
|
12
|
+
# :url => "https://docs.puppetlabs.com/mcollective/",
|
|
13
|
+
# :timeout => 10
|
|
14
|
+
#
|
|
15
|
+
# action "get_fact", :description => "Retrieve a single fact from the fact store" do
|
|
16
|
+
# display :always
|
|
17
|
+
#
|
|
18
|
+
# input :fact,
|
|
19
|
+
# :prompt => "The name of the fact",
|
|
20
|
+
# :description => "The fact to retrieve",
|
|
21
|
+
# :type => :string,
|
|
22
|
+
# :validation => '^[\w\-\.]+$',
|
|
23
|
+
# :optional => false,
|
|
24
|
+
# :maxlength => 40,
|
|
25
|
+
# :default => "fqdn"
|
|
26
|
+
#
|
|
27
|
+
# output :fact,
|
|
28
|
+
# :description => "The name of the fact being returned",
|
|
29
|
+
# :display_as => "Fact"
|
|
30
|
+
#
|
|
31
|
+
# output :value,
|
|
32
|
+
# :description => "The value of the fact",
|
|
33
|
+
# :display_as => "Value",
|
|
34
|
+
# :default => ""
|
|
35
|
+
#
|
|
36
|
+
# summarize do
|
|
37
|
+
# aggregate summary(:value)
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
class AgentDDL<Base
|
|
41
|
+
def initialize(plugin, plugintype=:agent, loadddl=true)
|
|
42
|
+
@process_aggregate_functions = nil
|
|
43
|
+
|
|
44
|
+
super
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def input(argument, properties)
|
|
48
|
+
raise "Input needs a :optional property" unless properties.include?(:optional)
|
|
49
|
+
|
|
50
|
+
super
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Calls the summarize block defined in the ddl. Block will not be called
|
|
54
|
+
# if the ddl is getting processed on the server side. This means that
|
|
55
|
+
# aggregate plugins only have to be present on the client side.
|
|
56
|
+
#
|
|
57
|
+
# The @process_aggregate_functions variable is used by the method_missing
|
|
58
|
+
# block to determine if it should kick in, this way we very tightly control
|
|
59
|
+
# where we activate the method_missing behavior turning it into a noop
|
|
60
|
+
# otherwise to maximise the chance of providing good user feedback
|
|
61
|
+
def summarize(&block)
|
|
62
|
+
unless @config.mode == :server
|
|
63
|
+
@process_aggregate_functions = true
|
|
64
|
+
block.call
|
|
65
|
+
@process_aggregate_functions = nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Sets the aggregate array for the given action
|
|
70
|
+
def aggregate(function, format = {:format => nil})
|
|
71
|
+
raise(DDLValidationError, "Formats supplied to aggregation functions should be a hash") unless format.is_a?(Hash)
|
|
72
|
+
raise(DDLValidationError, "Formats supplied to aggregation functions must have a :format key") unless format.keys.include?(:format)
|
|
73
|
+
raise(DDLValidationError, "Functions supplied to aggregate should be a hash") unless function.is_a?(Hash)
|
|
74
|
+
|
|
75
|
+
unless (function.keys.include?(:args)) && function[:args]
|
|
76
|
+
raise DDLValidationError, "aggregate method for action '%s' missing a function parameter" % entities[@current_entity][:action]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
entities[@current_entity][:aggregate] ||= []
|
|
80
|
+
entities[@current_entity][:aggregate] << (format[:format].nil? ? function : function.merge(format))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Sets the display preference to either :ok, :failed, :flatten or :always
|
|
84
|
+
# operates on action level
|
|
85
|
+
def display(pref)
|
|
86
|
+
if pref == :flatten
|
|
87
|
+
Log.warn("The display option :flatten is being deprecated and will be removed in the next minor release.")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# defaults to old behavior, complain if its supplied and invalid
|
|
91
|
+
unless [:ok, :failed, :flatten, :always].include?(pref)
|
|
92
|
+
raise "Display preference #{pref} is not valid, should be :ok, :failed, :flatten or :always"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
action = @current_entity
|
|
96
|
+
@entities[action][:display] = pref
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Creates the definition for an action, you can nest input definitions inside the
|
|
100
|
+
# action to attach inputs and validation to the actions
|
|
101
|
+
#
|
|
102
|
+
# action "status", :description => "Restarts a Service" do
|
|
103
|
+
# display :always
|
|
104
|
+
#
|
|
105
|
+
# input "service",
|
|
106
|
+
# :prompt => "Service Action",
|
|
107
|
+
# :description => "The action to perform",
|
|
108
|
+
# :type => :list,
|
|
109
|
+
# :optional => true,
|
|
110
|
+
# :list => ["start", "stop", "restart", "status"]
|
|
111
|
+
#
|
|
112
|
+
# output "status",
|
|
113
|
+
# :description => "The status of the service after the action"
|
|
114
|
+
#
|
|
115
|
+
# end
|
|
116
|
+
def action(name, input, &block)
|
|
117
|
+
raise "Action needs a :description property" unless input.include?(:description)
|
|
118
|
+
|
|
119
|
+
unless @entities.include?(name)
|
|
120
|
+
@entities[name] = {}
|
|
121
|
+
@entities[name][:action] = name
|
|
122
|
+
@entities[name][:input] = {}
|
|
123
|
+
@entities[name][:output] = {}
|
|
124
|
+
@entities[name][:display] = :failed
|
|
125
|
+
@entities[name][:description] = input[:description]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# if a block is passed it might be creating input methods, call it
|
|
129
|
+
# we set @current_entity so the input block can know what its talking
|
|
130
|
+
# to, this is probably an epic hack, need to improve.
|
|
131
|
+
@current_entity = name
|
|
132
|
+
block.call if block_given?
|
|
133
|
+
@current_entity = nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# If the method name matches a # aggregate function, we return the function
|
|
137
|
+
# with args as a hash. This will only be active if the @process_aggregate_functions
|
|
138
|
+
# is set to true which only happens in the #summarize block
|
|
139
|
+
def method_missing(name, *args, &block)
|
|
140
|
+
unless @process_aggregate_functions || is_function?(name)
|
|
141
|
+
raise NoMethodError, "undefined local variable or method `#{name}'", caller
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
return {:function => name, :args => args}
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Checks if a method name matches a aggregate plugin.
|
|
148
|
+
# This is used by method missing so that we dont greedily assume that
|
|
149
|
+
# every method_missing call in an agent ddl has hit a aggregate function.
|
|
150
|
+
def is_function?(method_name)
|
|
151
|
+
PluginManager.find("aggregate").include?(method_name.to_s)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# For a given action and arguments look up the DDL interface to that action
|
|
155
|
+
# and if any arguments in the DDL have a :default value assign that to any
|
|
156
|
+
# input that does not have an argument in the input arguments
|
|
157
|
+
#
|
|
158
|
+
# This is intended to only be called on clients and not on servers as the
|
|
159
|
+
# clients should never be able to publish non compliant requests and the
|
|
160
|
+
# servers should really not tamper with incoming requests since doing so
|
|
161
|
+
# might raise validation errors that were not raised on the client breaking
|
|
162
|
+
# our fail-fast approach to input validation
|
|
163
|
+
def set_default_input_arguments(action, arguments)
|
|
164
|
+
input = action_interface(action)[:input]
|
|
165
|
+
|
|
166
|
+
return unless input
|
|
167
|
+
|
|
168
|
+
input.keys.each do |key|
|
|
169
|
+
if key.is_a?(Symbol) && arguments.include?(key.to_s) && !input.include?(key.to_s)
|
|
170
|
+
compat_arg = key.to_s
|
|
171
|
+
else
|
|
172
|
+
compat_arg = key
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if !arguments.include?(compat_arg) && !input[key][:default].nil? && !input[key][:optional]
|
|
176
|
+
Log.debug("Setting default value for input '%s' to '%s'" % [key, input[key][:default]])
|
|
177
|
+
arguments[compat_arg] = input[key][:default]
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Creates a new set of arguments with string arguments mapped to symbol ones
|
|
183
|
+
#
|
|
184
|
+
# This is to assist with moving to a JSON pure world where requests might come
|
|
185
|
+
# in from REST or other languages, those languages and indeed JSON itself does
|
|
186
|
+
# not support symbols.
|
|
187
|
+
#
|
|
188
|
+
# It ensures a backward compatible mode where for rpcutil both of these requests
|
|
189
|
+
# are equivelant
|
|
190
|
+
#
|
|
191
|
+
# c.get_fact(:fact => "cluster")
|
|
192
|
+
# c.get_fact("fact" => "cluster")
|
|
193
|
+
#
|
|
194
|
+
# The case where both :fact and "fact" is in the DDL cannot be handled correctly
|
|
195
|
+
# and this code will assume the caller means "fact" in that case. There's no
|
|
196
|
+
# way to represent such a request in JSON and just in general sounds like a bad
|
|
197
|
+
# idea, so a warning is logged which would in default client configuration appear
|
|
198
|
+
# on the clients display
|
|
199
|
+
def symbolize_basic_input_arguments(input, arguments)
|
|
200
|
+
warned = false
|
|
201
|
+
|
|
202
|
+
Hash[arguments.map do |key, value|
|
|
203
|
+
if input.include?(key.intern) && input.include?(key.to_s) && !warned
|
|
204
|
+
Log.warn("String and Symbol versions of input %s found in the DDL for %s, ensure your DDL keys are unique." % [key, @pluginname])
|
|
205
|
+
warned = true
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
if key.is_a?(String) && input.include?(key.intern) && !input.include?(key)
|
|
209
|
+
[key.intern, value]
|
|
210
|
+
else
|
|
211
|
+
[key, value]
|
|
212
|
+
end
|
|
213
|
+
end]
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Helper to use the DDL to figure out if the remote call to an
|
|
217
|
+
# agent should be allowed based on action name and inputs.
|
|
218
|
+
def validate_rpc_request(action, arguments)
|
|
219
|
+
# is the action known?
|
|
220
|
+
unless actions.include?(action)
|
|
221
|
+
raise DDLValidationError, "Attempted to call action #{action} for #{@pluginname} but it's not declared in the DDL"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
input = action_interface(action)[:input] || {}
|
|
225
|
+
compatible_args = symbolize_basic_input_arguments(input, arguments)
|
|
226
|
+
|
|
227
|
+
input.keys.each do |key|
|
|
228
|
+
unless input[key][:optional]
|
|
229
|
+
unless compatible_args.include?(key)
|
|
230
|
+
raise DDLValidationError, "Action #{action} needs a #{key} argument"
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
if compatible_args.include?(key)
|
|
235
|
+
validate_input_argument(input, key, compatible_args[key])
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
true
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Returns the interface for a specific action
|
|
243
|
+
def action_interface(name)
|
|
244
|
+
@entities[name] || {}
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Returns an array of actions this agent support
|
|
248
|
+
def actions
|
|
249
|
+
@entities.keys
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|