choria-mcorpc-support 0.0.1

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