choria-mcorpc-support 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|