beaker 0.0.0

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 (88) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +14 -0
  5. data/DOCUMENTING.md +167 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +17 -0
  8. data/README.md +332 -0
  9. data/Rakefile +121 -0
  10. data/beaker.gemspec +42 -0
  11. data/beaker.rb +10 -0
  12. data/bin/beaker +9 -0
  13. data/lib/beaker.rb +36 -0
  14. data/lib/beaker/answers.rb +29 -0
  15. data/lib/beaker/answers/version28.rb +104 -0
  16. data/lib/beaker/answers/version30.rb +194 -0
  17. data/lib/beaker/cli.rb +113 -0
  18. data/lib/beaker/command.rb +241 -0
  19. data/lib/beaker/command_factory.rb +21 -0
  20. data/lib/beaker/dsl.rb +85 -0
  21. data/lib/beaker/dsl/assertions.rb +87 -0
  22. data/lib/beaker/dsl/helpers.rb +625 -0
  23. data/lib/beaker/dsl/install_utils.rb +299 -0
  24. data/lib/beaker/dsl/outcomes.rb +99 -0
  25. data/lib/beaker/dsl/roles.rb +97 -0
  26. data/lib/beaker/dsl/structure.rb +63 -0
  27. data/lib/beaker/dsl/wrappers.rb +100 -0
  28. data/lib/beaker/host.rb +193 -0
  29. data/lib/beaker/host/aix.rb +15 -0
  30. data/lib/beaker/host/aix/file.rb +16 -0
  31. data/lib/beaker/host/aix/group.rb +35 -0
  32. data/lib/beaker/host/aix/user.rb +32 -0
  33. data/lib/beaker/host/unix.rb +54 -0
  34. data/lib/beaker/host/unix/exec.rb +15 -0
  35. data/lib/beaker/host/unix/file.rb +16 -0
  36. data/lib/beaker/host/unix/group.rb +40 -0
  37. data/lib/beaker/host/unix/pkg.rb +22 -0
  38. data/lib/beaker/host/unix/user.rb +32 -0
  39. data/lib/beaker/host/windows.rb +44 -0
  40. data/lib/beaker/host/windows/exec.rb +18 -0
  41. data/lib/beaker/host/windows/file.rb +15 -0
  42. data/lib/beaker/host/windows/group.rb +36 -0
  43. data/lib/beaker/host/windows/pkg.rb +26 -0
  44. data/lib/beaker/host/windows/user.rb +32 -0
  45. data/lib/beaker/hypervisor.rb +37 -0
  46. data/lib/beaker/hypervisor/aixer.rb +52 -0
  47. data/lib/beaker/hypervisor/blimper.rb +123 -0
  48. data/lib/beaker/hypervisor/fusion.rb +56 -0
  49. data/lib/beaker/hypervisor/solaris.rb +65 -0
  50. data/lib/beaker/hypervisor/vagrant.rb +118 -0
  51. data/lib/beaker/hypervisor/vcloud.rb +175 -0
  52. data/lib/beaker/hypervisor/vsphere.rb +80 -0
  53. data/lib/beaker/hypervisor/vsphere_helper.rb +200 -0
  54. data/lib/beaker/logger.rb +167 -0
  55. data/lib/beaker/network_manager.rb +73 -0
  56. data/lib/beaker/options_parsing.rb +323 -0
  57. data/lib/beaker/result.rb +55 -0
  58. data/lib/beaker/shared.rb +15 -0
  59. data/lib/beaker/shared/error_handler.rb +17 -0
  60. data/lib/beaker/shared/host_handler.rb +46 -0
  61. data/lib/beaker/shared/repetition.rb +28 -0
  62. data/lib/beaker/ssh_connection.rb +198 -0
  63. data/lib/beaker/test_case.rb +225 -0
  64. data/lib/beaker/test_config.rb +148 -0
  65. data/lib/beaker/test_suite.rb +288 -0
  66. data/lib/beaker/utils.rb +7 -0
  67. data/lib/beaker/utils/ntp_control.rb +42 -0
  68. data/lib/beaker/utils/repo_control.rb +92 -0
  69. data/lib/beaker/utils/setup_helper.rb +77 -0
  70. data/lib/beaker/utils/validator.rb +27 -0
  71. data/spec/beaker/command_spec.rb +94 -0
  72. data/spec/beaker/dsl/assertions_spec.rb +104 -0
  73. data/spec/beaker/dsl/helpers_spec.rb +230 -0
  74. data/spec/beaker/dsl/install_utils_spec.rb +70 -0
  75. data/spec/beaker/dsl/outcomes_spec.rb +43 -0
  76. data/spec/beaker/dsl/roles_spec.rb +86 -0
  77. data/spec/beaker/dsl/structure_spec.rb +60 -0
  78. data/spec/beaker/dsl/wrappers_spec.rb +52 -0
  79. data/spec/beaker/host_spec.rb +95 -0
  80. data/spec/beaker/logger_spec.rb +117 -0
  81. data/spec/beaker/options_parsing_spec.rb +37 -0
  82. data/spec/beaker/puppet_command_spec.rb +128 -0
  83. data/spec/beaker/ssh_connection_spec.rb +39 -0
  84. data/spec/beaker/test_case_spec.rb +6 -0
  85. data/spec/beaker/test_suite_spec.rb +44 -0
  86. data/spec/mocks_and_helpers.rb +34 -0
  87. data/spec/spec_helper.rb +15 -0
  88. metadata +359 -0
@@ -0,0 +1,299 @@
1
+ require 'pathname'
2
+
3
+ module Beaker
4
+ module DSL
5
+ #
6
+ # This module contains methods to help cloning, extracting git info,
7
+ # ordering of Puppet packages, and installing ruby projects that
8
+ # contain an `install.rb` script.
9
+ module InstallUtils
10
+
11
+ # The default install path
12
+ SourcePath = "/opt/puppet-git-repos"
13
+
14
+ # A regex to know if the uri passed is pointing to a git repo
15
+ GitURI = %r{^(git|https?|file)://|^git@}
16
+
17
+ # Github's ssh signature for cloning via ssh
18
+ GitHubSig = 'github.com,207.97.227.239 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=='
19
+
20
+ # @param [String] uri A uri in the format of <git uri>#<revision>
21
+ # the `git://`, `http://`, `https://`, and ssh
22
+ # (if cloning as the remote git user) protocols
23
+ # are valid for <git uri>
24
+ #
25
+ # @example Usage
26
+ # project = extract_repo_info_from 'git@github.com:puppetlabs/SuperSecretSauce#what_is_justin_doing'
27
+ #
28
+ # puts project[:name]
29
+ # #=> 'SuperSecretSauce'
30
+ #
31
+ # puts project[:rev]
32
+ # #=> 'what_is_justin_doing'
33
+ #
34
+ # @return [Hash{Symbol=>String}] Returns a hash containing the project
35
+ # name, repository path, and revision
36
+ # (defaults to HEAD)
37
+ #
38
+ # @api dsl
39
+ def extract_repo_info_from uri
40
+ project = {}
41
+ repo, rev = uri.split('#', 2)
42
+ project[:name] = Pathname.new(repo).basename('.git').to_s
43
+ project[:path] = repo
44
+ project[:rev] = rev || 'HEAD'
45
+ return project
46
+ end
47
+
48
+ # Takes an array of package info hashes (like that returned from
49
+ # {#extract_repo_info_from}) and sorts the `puppet`, `facter`, `hiera`
50
+ # packages so that puppet's dependencies will be installed first.
51
+ #
52
+ # @!visibility private
53
+ def order_packages packages_array
54
+ puppet = packages_array.select {|e| e[:name] == 'puppet' }
55
+ puppet_depends_on = packages_array.select do |e|
56
+ e[:name] == 'hiera' or e[:name] == 'facter'
57
+ end
58
+ depends_on_puppet = (packages_array - puppet) - puppet_depends_on
59
+ [puppet_depends_on, puppet, depends_on_puppet].flatten
60
+ end
61
+
62
+ # @param [Host] host An object implementing {Beaker::Hosts}'s
63
+ # interface.
64
+ # @param [String] path The path on the remote [host] to the repository
65
+ # @param [Hash{Symbol=>String}] repository A hash representing repo
66
+ # info like that emitted by
67
+ # {#extract_repo_info_from}
68
+ #
69
+ # @example Getting multiple project versions
70
+ # versions = [puppet_repo, facter_repo, hiera_repo].inject({}) do |vers, repo_info|
71
+ # vers.merge(find_git_repo_versions(host, '/opt/git-puppet-repos', repo_info) )
72
+ # end
73
+ # @return [Hash] Executes git describe on [host] and returns a Hash
74
+ # with the key of [repository[:name]] and value of
75
+ # the output from git describe.
76
+ #
77
+ # @note This requires the helper methods:
78
+ # * {Beaker::DSL::Structure#step}
79
+ # * {Beaker::DSL::Helpers#on}
80
+ #
81
+ # @api dsl
82
+ def find_git_repo_versions host, path, repository
83
+ version = {}
84
+ step "Grab version for #{repository[:name]}" do
85
+ on host, "cd #{path}/#{repository[:name]} && " +
86
+ "git describe || true" do
87
+ version[repository[:name]] = stdout.chomp
88
+ end
89
+ end
90
+ version
91
+ end
92
+
93
+ #
94
+ # @see #find_git_repo_versions
95
+ def install_from_git host, path, repository
96
+ name = repository[:name]
97
+ repo = repository[:path]
98
+ rev = repository[:rev]
99
+ target = "#{path}/#{name}"
100
+
101
+ step "Clone #{repo} if needed" do
102
+ on host, "test -d #{path} || mkdir -p #{path}"
103
+ on host, "test -d #{target} || git clone #{repo} #{target}"
104
+ end
105
+
106
+ step "Update #{name} and check out revision #{rev}" do
107
+ commands = ["cd #{target}",
108
+ "remote rm origin",
109
+ "remote add origin #{repo}",
110
+ "fetch origin",
111
+ "clean -fdx",
112
+ "checkout -f #{rev}"]
113
+ on host, commands.join(" && git ")
114
+ end
115
+
116
+ step "Install #{name} on the system" do
117
+ # The solaris ruby IPS package has bindir set to /usr/ruby/1.8/bin.
118
+ # However, this is not the path to which we want to deliver our
119
+ # binaries. So if we are using solaris, we have to pass the bin and
120
+ # sbin directories to the install.rb
121
+ install_opts = ''
122
+ install_opts = '--bindir=/usr/bin --sbindir=/usr/sbin' if
123
+ host['platform'].include? 'solaris'
124
+
125
+ on host, "cd #{target} && " +
126
+ "if [ -f install.rb ]; then " +
127
+ "ruby ./install.rb #{install_opts}; " +
128
+ "else true; fi"
129
+ end
130
+ end
131
+
132
+ def do_install hosts, version, path, pre_30, options = {}
133
+ #convenience methods for installation
134
+ ########################################################
135
+ def installer_cmd(host, version, installer)
136
+ if host['platform'] =~ /windows/
137
+ "cd #{host['working_dir']} && msiexec.exe /qn /i puppet-enterprise-#{version}.msi"
138
+ else
139
+ "cd #{host['working_dir']}/#{host['dist']} && ./#{installer}"
140
+ end
141
+ end
142
+ def link_exists?(link)
143
+ require "net/http"
144
+ require "open-uri"
145
+ url = URI.parse(link)
146
+ Net::HTTP.start(url.host, url.port) do |http|
147
+ return http.head(url.request_uri).code == "200"
148
+ end
149
+ end
150
+ def fetch_puppet(hosts, version, path)
151
+ local = File.directory?(path)
152
+ hosts.each do |host|
153
+ filename = ""
154
+ extension = ""
155
+ if host['platform'] =~ /windows/
156
+ filename = "puppet-enterprise-#{version}"
157
+ extension = ".msi"
158
+ else
159
+ filename = "#{host['dist']}"
160
+ extension = ""
161
+ if local
162
+ extension = File.exists?("#{path}/#{filename}.tar.gz") ? ".tar.gz" : ".tar"
163
+ else
164
+ extension = link_exists?("#{path}/#{filename}.tar.gz") ? ".tar.gz" : ".tar"
165
+ end
166
+ end
167
+ if local
168
+ if not File.exists?("#{path}/#{filename}#{extension}")
169
+ raise "attempting installation on #{host}, #{path}/#{filename}#{extension} does not exist"
170
+ end
171
+ scp_to host, "#{path}/#{filename}#{extension}", "#{host['working_dir']}/#{filename}#{extension}"
172
+ else
173
+ if not link_exists?("#{path}/#{filename}#{extension}")
174
+ raise "attempting installation on #{host}, #{path}/#{filename}#{extension} does not exist"
175
+ end
176
+ on host, "cd #{host['working_dir']}; curl #{path}/#{filename}#{extension} -o #{filename}#{extension}"
177
+ end
178
+ if extension =~ /gz/
179
+ on host, "cd #{host['working_dir']}; gunzip #{filename}#{extension}"
180
+ end
181
+ if extension =~ /tar/
182
+ on host, "cd #{host['working_dir']}; tar -xvf #{filename}.tar"
183
+ end
184
+ end
185
+ end
186
+ ########################################################
187
+ #start installation steps here
188
+ options[:installer] = 'puppet-enterprise-installer' unless options[:installer]
189
+ options[:type] = :install unless options[:type]
190
+ hostcert='uname | grep -i sunos > /dev/null && hostname || hostname -s'
191
+ master_certname = on(master, hostcert).stdout.strip
192
+ answers = Beaker::Answers.answers(version, hosts, master_certname, options)
193
+ special_nodes = [master, database, dashboard].uniq
194
+ real_agents = agents - special_nodes
195
+
196
+ # Set PE distribution for all the hosts, create working dir
197
+ use_all_tar = ENV['PE_USE_ALL_TAR'] == 'true'
198
+ hosts.each do |host|
199
+ platform = use_all_tar ? 'all' : host['platform']
200
+ host['dist'] = "puppet-enterprise-#{version}-#{platform}"
201
+ host['working_dir'] = "/tmp/" + Time.new.strftime("%Y-%m-%d_%H.%M.%S") #unique working dirs make me happy
202
+ on host, "mkdir #{host['working_dir']}"
203
+ end
204
+
205
+ fetch_puppet(hosts, version, path)
206
+
207
+ hosts.each do |host|
208
+ # Database host was added in 3.0. Skip it if installing an older version
209
+ next if host == database and host != master and host != dashboard and pre_30
210
+ if host['platform'] =~ /windows/
211
+ on host, "#{installer_cmd(host, version, options[:installer])} PUPPET_MASTER_SERVER=#{master} PUPPET_AGENT_CERTNAME=#{host}"
212
+ else
213
+ create_remote_file host, "#{host['working_dir']}/answers", Beaker::Answers.answer_string(host, answers)
214
+
215
+ on host, "#{installer_cmd(host, version, options[:installer])} -a #{host['working_dir']}/answers"
216
+ end
217
+ end
218
+
219
+
220
+ # If we're installing a version less than 3.0, ignore the database host
221
+ install_hosts = hosts.dup
222
+ install_hosts.delete(database) if pre_30 and database != master and database != dashboard
223
+
224
+ # On each agent, we ensure the certificate is signed then shut down the agent
225
+ install_hosts.each do |host|
226
+ sign_certificate(host)
227
+ stop_agent(host)
228
+ end
229
+
230
+ # Wait for PuppetDB to be totally up and running
231
+ sleep_until_puppetdb_started(database) unless pre_30
232
+
233
+ # Run the agent once to ensure everything is in the dashboard
234
+ install_hosts.each do |host|
235
+ on host, puppet_agent('-t'), :acceptable_exit_codes => [0,2]
236
+
237
+ # Workaround for PE-1105 when deploying 3.0.0
238
+ # The installer did not respect our database host answers in 3.0.0,
239
+ # and would cause puppetdb to be bounced by the agent run. By sleeping
240
+ # again here, we ensure that if that bounce happens during an upgrade
241
+ # test we won't fail early in the install process.
242
+ if version == '3.0.0' and host == database
243
+ sleep_until_puppetdb_started(database)
244
+ end
245
+ end
246
+
247
+ install_hosts.each do |host|
248
+ wait_for_host_in_dashboard(host)
249
+ end
250
+
251
+ if pre_30
252
+ task = 'nodegroup:add_all_nodes group=default'
253
+ else
254
+ task = 'defaultgroup:ensure_default_group'
255
+ end
256
+ on dashboard, "/opt/puppet/bin/rake -sf /opt/puppet/share/puppet-dashboard/Rakefile #{task} RAILS_ENV=production"
257
+
258
+ # Now that all hosts are in the dashbaord, run puppet one more
259
+ # time to configure mcollective
260
+ on install_hosts, puppet_agent('-t'), :acceptable_exit_codes => [0,2]
261
+ end
262
+
263
+ #is version a < version b
264
+ #3.0.0-160-gac44cfb is greater than 3.0.0, and 2.8.2
265
+ def version_is_less a, b
266
+ a = a.split('-')[0].split('.')
267
+ b = b.split('-')[0].split('.')
268
+ (0...a.length).each do |i|
269
+ if i < b.length
270
+ if a[i] < b[i]
271
+ return true
272
+ elsif a[i] > b[i]
273
+ return false
274
+ end
275
+ else
276
+ return false
277
+ end
278
+ end
279
+ return false
280
+ end
281
+
282
+ def install_pe version, path
283
+ pre_30 = version_is_less(version, '3.0')
284
+ step "Install #{version} PE on #{path}"
285
+ do_install hosts, version, path, pre_30
286
+ end
287
+
288
+ def upgrade_pe version, path, from
289
+ pre_30 = version_is_less(version, '3.0')
290
+ if pre_30
291
+ do_install(hosts, version, path, pre_30, :type => :upgrade, :installer => 'puppet-enterprise-upgrader', :from => from)
292
+ else
293
+ do_install(hosts, version, path, pre_30, :type => :upgrade, :from => from)
294
+ end
295
+ end
296
+
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,99 @@
1
+ module Beaker
2
+ module DSL
3
+ # This module includes dsl helpers for setting the state of a test case.
4
+ # They do not need inclusion if using third party test runner. The
5
+ # Exception classes that they raise however should be defined as other
6
+ # DSL helpers will raise them as needed. See individual DSL modules
7
+ # for their specific dependencies. A class that mixes in this module
8
+ # must have a method #logger which will yield an object that responds to
9
+ # #notify and #warn. NOTE: the interface to logger may change shortly and
10
+ # {Beaker::Logger} should be consulted for the appropriate
11
+ # interface.
12
+ #
13
+ # Simply these methods log a message and raise the appropriate Exception
14
+ # The exceptions are are caught by {Beaker::TestCase} and are
15
+ # designed to allow some degree of freedom from the individual third
16
+ # party test runners that could be used.
17
+ module Outcomes
18
+
19
+ # Raise this class if it is determined that a test case should not
20
+ # be executed because the feature in question is still a
21
+ # "Work in Progress"
22
+ class PendingTest < Exception; end
23
+
24
+ # Raise this class if execution should be stopped because the test
25
+ # is not applicable within a given environment.
26
+ class SkipTest < Exception; end
27
+
28
+ # Raise this class if some criteria has been met that proves a failure.
29
+ class FailTest < Exception; end
30
+
31
+ # Raise this class if execution should stop because enough criteria has
32
+ # shown itself to pass the test.
33
+ class PassTest < Exception; end
34
+
35
+
36
+ # Raises FailTest Exception and logs an error message
37
+ #
38
+ # @param [String] msg An optional message to log
39
+ # @raise [FailTest]
40
+ # @api dsl
41
+ def fail_test msg = nil
42
+ message = formatted_message( msg, 'Failed' )
43
+ logger.warn( [message, logger.pretty_backtrace].join("\n") )
44
+
45
+ raise( FailTest, message )
46
+ end
47
+
48
+ # Raises PassTest Exception and logs a message
49
+ #
50
+ # @param [String] msg An optional message to log
51
+ # @raise [PassTest]
52
+ # @api dsl
53
+ def pass_test msg = nil
54
+ message = formatted_message( msg, 'Passed' )
55
+ logger.notify( message )
56
+
57
+ raise( PassTest, message )
58
+ end
59
+
60
+ # Raises PendingTest Exception and logs an error message
61
+ #
62
+ # @param [String] msg An optional message to log
63
+ # @raise [PendingTest]
64
+ # @api dsl
65
+ def pending_test msg = nil
66
+ message = formatted_message( msg, 'is Pending' )
67
+ logger.warn( message )
68
+
69
+ raise( PendingTest, message )
70
+ end
71
+
72
+ # Raises SkipTest Exception and logs a message
73
+ #
74
+ # @param [String] msg An optional message to log
75
+ # @raise [SkipTest]
76
+ # @api dsl
77
+ def skip_test msg = nil
78
+ message = formatted_message( msg, 'was Skipped' )
79
+ logger.notify( message )
80
+
81
+ raise( SkipTest, message )
82
+ end
83
+
84
+ # Formats an optional message or self appended by a state, either
85
+ # bracketted in newlines
86
+ #
87
+ # @param [String, nil] message The message (or nil) to format
88
+ # @param [String] default_str The string to be appended to self if
89
+ # message is nil
90
+ #
91
+ # @return [String] A prettier string with helpful info
92
+ # @!visibility private
93
+ def formatted_message(message, default_str )
94
+ msg = message ? "\n#{message}\n" : "\n#{self} #{default_str}.\n"
95
+ return msg
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,97 @@
1
+ module Beaker
2
+ module DSL
3
+ #
4
+ # Identifying hosts.
5
+ #
6
+ # This aids in reaching common subsets of hosts in a testing matrix.
7
+ #
8
+ # It requires the class it is mixed into to provide the attribute
9
+ # `hosts` which contain the hosts to search, these should implement
10
+ # {Beaker::Host}'s interface. They, at least, must have #[]
11
+ # and #to_s available and provide an array when #[]('roles') is called.
12
+ #
13
+ # Also the constant {FailTest} needs to be defined it will be raised
14
+ # in error conditions
15
+ #
16
+ # @api dsl
17
+ module Roles
18
+
19
+ # The hosts for which ['roles'] include 'agent'
20
+ #
21
+ # @return [Array<Host>] May be empty
22
+ #
23
+ # @example Basic usage
24
+ # agents.each do |agent|
25
+ # ...test each agent in turn...
26
+ # end
27
+ #
28
+ def agents
29
+ hosts_as 'agent'
30
+ end
31
+
32
+ # The host for which ['roles'] include 'master'
33
+ #
34
+ # @return [Array<Host>]
35
+ # @raise [Beaker::DSL::Outcomes::FailTest] if there are less
36
+ # or more than 1 master is found.
37
+ #
38
+ # @example Basic usage
39
+ # on, master, 'cat /etc/puppet/puppet.conf'
40
+ #
41
+ def master
42
+ find_only_one :master
43
+ end
44
+
45
+ # The host for which ['roles'] include 'database'
46
+ #
47
+ # @return [Array<Host>]
48
+ # @raise [Beaker::DSL::Outcomes::FailTest] if there are less
49
+ # or more than 1 database is found.
50
+ #
51
+ # @example Basic usage
52
+ # on, agent, "curl -k http://#{database}:8080"
53
+ #
54
+ def database
55
+ find_only_one :database
56
+ end
57
+
58
+ # The host for which ['roles'] include 'dashboard'
59
+ #
60
+ # @return [Array<Host>]
61
+ # @raise [Beaker::DSL::Outcomes::FailTest] if there are less
62
+ # or more than 1 dashboard is found.
63
+ #
64
+ # @example Basic usage
65
+ # on, agent, "curl https://#{database}/nodes/#{agent}"
66
+ #
67
+ def dashboard
68
+ find_only_one :dashboard
69
+ end
70
+
71
+ # Select hosts that include a desired role from #hosts
72
+ #
73
+ # @param [String, Symbol] desired_role The role to select for
74
+ # @return [Array<Host>] The hosts that match
75
+ # desired_role, may be empty
76
+ #
77
+ # @example Basic usage
78
+ # hairy = hosts_as :yak
79
+ # hairy.each do |yak|
80
+ # on yak, 'shave'
81
+ # end
82
+ #
83
+ # @api public
84
+ def hosts_as(desired_role = nil)
85
+ hosts_with_role(hosts, desired_role)
86
+ end
87
+
88
+ # @param [Symbol, String] role The role to find a host for
89
+ # @return [Host] Returns the host, if one and only one is found
90
+ # @raise Raises a failure exception if one and only one host that matches
91
+ # the specified role is NOT found.
92
+ def find_only_one role
93
+ only_host_with_role(hosts, role)
94
+ end
95
+ end
96
+ end
97
+ end