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.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/bin/mco +64 -0
  3. data/lib/mcollective.rb +63 -0
  4. data/lib/mcollective/agent.rb +5 -0
  5. data/lib/mcollective/agents.rb +149 -0
  6. data/lib/mcollective/aggregate.rb +85 -0
  7. data/lib/mcollective/aggregate/average.ddl +33 -0
  8. data/lib/mcollective/aggregate/average.rb +29 -0
  9. data/lib/mcollective/aggregate/base.rb +40 -0
  10. data/lib/mcollective/aggregate/result.rb +9 -0
  11. data/lib/mcollective/aggregate/result/base.rb +25 -0
  12. data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
  13. data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
  14. data/lib/mcollective/aggregate/sum.ddl +33 -0
  15. data/lib/mcollective/aggregate/sum.rb +18 -0
  16. data/lib/mcollective/aggregate/summary.ddl +33 -0
  17. data/lib/mcollective/aggregate/summary.rb +53 -0
  18. data/lib/mcollective/application.rb +365 -0
  19. data/lib/mcollective/application/completion.rb +104 -0
  20. data/lib/mcollective/application/describe_filter.rb +87 -0
  21. data/lib/mcollective/application/facts.rb +62 -0
  22. data/lib/mcollective/application/find.rb +23 -0
  23. data/lib/mcollective/application/help.rb +28 -0
  24. data/lib/mcollective/application/inventory.rb +344 -0
  25. data/lib/mcollective/application/ping.rb +82 -0
  26. data/lib/mcollective/application/plugin.rb +369 -0
  27. data/lib/mcollective/application/rpc.rb +111 -0
  28. data/lib/mcollective/applications.rb +134 -0
  29. data/lib/mcollective/cache.rb +145 -0
  30. data/lib/mcollective/client.rb +353 -0
  31. data/lib/mcollective/config.rb +245 -0
  32. data/lib/mcollective/connector.rb +18 -0
  33. data/lib/mcollective/connector/base.rb +26 -0
  34. data/lib/mcollective/data.rb +91 -0
  35. data/lib/mcollective/data/agent_data.ddl +22 -0
  36. data/lib/mcollective/data/agent_data.rb +17 -0
  37. data/lib/mcollective/data/base.rb +67 -0
  38. data/lib/mcollective/data/collective_data.ddl +20 -0
  39. data/lib/mcollective/data/collective_data.rb +9 -0
  40. data/lib/mcollective/data/fact_data.ddl +28 -0
  41. data/lib/mcollective/data/fact_data.rb +55 -0
  42. data/lib/mcollective/data/fstat_data.ddl +89 -0
  43. data/lib/mcollective/data/fstat_data.rb +56 -0
  44. data/lib/mcollective/data/result.rb +45 -0
  45. data/lib/mcollective/ddl.rb +113 -0
  46. data/lib/mcollective/ddl/agentddl.rb +253 -0
  47. data/lib/mcollective/ddl/base.rb +217 -0
  48. data/lib/mcollective/ddl/dataddl.rb +56 -0
  49. data/lib/mcollective/ddl/discoveryddl.rb +52 -0
  50. data/lib/mcollective/ddl/validatorddl.rb +6 -0
  51. data/lib/mcollective/discovery.rb +143 -0
  52. data/lib/mcollective/discovery/flatfile.ddl +11 -0
  53. data/lib/mcollective/discovery/flatfile.rb +48 -0
  54. data/lib/mcollective/discovery/mc.ddl +11 -0
  55. data/lib/mcollective/discovery/mc.rb +30 -0
  56. data/lib/mcollective/discovery/stdin.ddl +11 -0
  57. data/lib/mcollective/discovery/stdin.rb +68 -0
  58. data/lib/mcollective/exceptions.rb +28 -0
  59. data/lib/mcollective/facts.rb +39 -0
  60. data/lib/mcollective/facts/base.rb +100 -0
  61. data/lib/mcollective/facts/yaml_facts.rb +65 -0
  62. data/lib/mcollective/generators.rb +7 -0
  63. data/lib/mcollective/generators/agent_generator.rb +51 -0
  64. data/lib/mcollective/generators/base.rb +46 -0
  65. data/lib/mcollective/generators/data_generator.rb +51 -0
  66. data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
  67. data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
  68. data/lib/mcollective/generators/templates/ddl.erb +8 -0
  69. data/lib/mcollective/generators/templates/plugin.erb +7 -0
  70. data/lib/mcollective/log.rb +118 -0
  71. data/lib/mcollective/logger.rb +5 -0
  72. data/lib/mcollective/logger/base.rb +77 -0
  73. data/lib/mcollective/logger/console_logger.rb +61 -0
  74. data/lib/mcollective/logger/file_logger.rb +53 -0
  75. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  76. data/lib/mcollective/matcher.rb +224 -0
  77. data/lib/mcollective/matcher/parser.rb +128 -0
  78. data/lib/mcollective/matcher/scanner.rb +241 -0
  79. data/lib/mcollective/message.rb +248 -0
  80. data/lib/mcollective/monkey_patches.rb +152 -0
  81. data/lib/mcollective/optionparser.rb +197 -0
  82. data/lib/mcollective/pluginmanager.rb +180 -0
  83. data/lib/mcollective/pluginpackager.rb +98 -0
  84. data/lib/mcollective/pluginpackager/agent_definition.rb +94 -0
  85. data/lib/mcollective/pluginpackager/debpackage_packager.rb +237 -0
  86. data/lib/mcollective/pluginpackager/modulepackage_packager.rb +127 -0
  87. data/lib/mcollective/pluginpackager/ospackage_packager.rb +59 -0
  88. data/lib/mcollective/pluginpackager/rpmpackage_packager.rb +180 -0
  89. data/lib/mcollective/pluginpackager/standard_definition.rb +69 -0
  90. data/lib/mcollective/pluginpackager/templates/debian/Makefile.erb +7 -0
  91. data/lib/mcollective/pluginpackager/templates/debian/changelog.erb +5 -0
  92. data/lib/mcollective/pluginpackager/templates/debian/compat.erb +1 -0
  93. data/lib/mcollective/pluginpackager/templates/debian/control.erb +15 -0
  94. data/lib/mcollective/pluginpackager/templates/debian/copyright.erb +8 -0
  95. data/lib/mcollective/pluginpackager/templates/debian/rules.erb +6 -0
  96. data/lib/mcollective/pluginpackager/templates/module/Modulefile.erb +5 -0
  97. data/lib/mcollective/pluginpackager/templates/module/README.md.erb +37 -0
  98. data/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb +9 -0
  99. data/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb +63 -0
  100. data/lib/mcollective/registration/base.rb +91 -0
  101. data/lib/mcollective/rpc.rb +182 -0
  102. data/lib/mcollective/rpc/actionrunner.rb +158 -0
  103. data/lib/mcollective/rpc/agent.rb +374 -0
  104. data/lib/mcollective/rpc/audit.rb +38 -0
  105. data/lib/mcollective/rpc/client.rb +1066 -0
  106. data/lib/mcollective/rpc/helpers.rb +321 -0
  107. data/lib/mcollective/rpc/progress.rb +63 -0
  108. data/lib/mcollective/rpc/reply.rb +87 -0
  109. data/lib/mcollective/rpc/request.rb +86 -0
  110. data/lib/mcollective/rpc/result.rb +90 -0
  111. data/lib/mcollective/rpc/stats.rb +294 -0
  112. data/lib/mcollective/runnerstats.rb +90 -0
  113. data/lib/mcollective/security.rb +26 -0
  114. data/lib/mcollective/security/base.rb +244 -0
  115. data/lib/mcollective/shell.rb +126 -0
  116. data/lib/mcollective/ssl.rb +285 -0
  117. data/lib/mcollective/util.rb +579 -0
  118. data/lib/mcollective/validator.rb +85 -0
  119. data/lib/mcollective/validator/array_validator.ddl +7 -0
  120. data/lib/mcollective/validator/array_validator.rb +9 -0
  121. data/lib/mcollective/validator/ipv4address_validator.ddl +7 -0
  122. data/lib/mcollective/validator/ipv4address_validator.rb +16 -0
  123. data/lib/mcollective/validator/ipv6address_validator.ddl +7 -0
  124. data/lib/mcollective/validator/ipv6address_validator.rb +16 -0
  125. data/lib/mcollective/validator/length_validator.ddl +7 -0
  126. data/lib/mcollective/validator/length_validator.rb +11 -0
  127. data/lib/mcollective/validator/regex_validator.ddl +7 -0
  128. data/lib/mcollective/validator/regex_validator.rb +9 -0
  129. data/lib/mcollective/validator/shellsafe_validator.ddl +7 -0
  130. data/lib/mcollective/validator/shellsafe_validator.rb +13 -0
  131. data/lib/mcollective/validator/typecheck_validator.ddl +7 -0
  132. data/lib/mcollective/validator/typecheck_validator.rb +28 -0
  133. 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,9 @@
1
+ module MCollective
2
+ module Data
3
+ class Collective_data<Base
4
+ query do |collective|
5
+ result[:member] = Config.instance.collectives.include?(collective)
6
+ end
7
+ end
8
+ end
9
+ 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