Project

General

Profile

How to Create a Smart-Proxy Plugin » History » Version 29

Ewoud Kohl van Wijngaarden, 07/16/2019 01:57 PM

1 1 Anonymous
h1. How to Create a Smart-Proxy Plugin
2
3 4 Anonymous
This guide outlines main components of a plugin, but assumes some degree of familiarity with ruby gems, bundler, rack, and Sinatra. You'll find links to useful documentation in each of the sections below.
4 1 Anonymous
5 9 Dominic Cleal
{{toc}}
6
7 1 Anonymous
h2. Plugin Organization
8
9 11 Dominic Cleal
Smart-Proxy plugins are normal ruby gems, please follow documentation at http://guides.rubygems.org/make-your-own-gem/ for guidance on gem creation and packaging. It is strongly recommended to follow smart_proxy_<your plugin name here> naming convention for your plugin.
10 1 Anonymous
11 11 Dominic Cleal
We have some templates for creating your plugin:
12
13
* "smart_proxy_example plugin":https://github.com/theforeman/smart_proxy_example is a minimal example plugin that can be used as a skeleton
14
* "smart_proxy_dns_plugin_template":https://github.com/theforeman/smart_proxy_dns_plugin_template is a template for creating new DNS provider plugins
15
16
Also, "smart_proxy_pulp plugin":https://github.com/theforeman/smart-proxy-pulp is an example for a fully functional, yet easy to understand Smart-Proxy plugin.
17
18 6 Dominic Cleal
h2. Making your plugin official
19
20 1 Anonymous
Once you're ready to release the first version, please see [[How_to_Create_a_Plugin#Making-your-plugin-official]] for info on making your plugin part of the Foreman project.
21
22 11 Dominic Cleal
h2. Plugin definition
23 2 Anonymous
24 11 Dominic Cleal
A plugin definition is used to define plugin's name, version, location of rackup configuration, and other parameters. At a minimum, Plugin Descriptor must define plugin name and version. Note the base class of the descriptor is ::Proxy::Plugin:
25 3 Anonymous
26 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
27 8 Anonymous
module Proxy::Example
28
  class Plugin < ::Proxy::Plugin
29 1 Anonymous
    plugin :example, "0.0.1"
30 8 Anonymous
    http_rackup_path File.expand_path("http_config.ru", File.expand_path("../", __FILE__))
31 1 Anonymous
    https_rackup_path File.expand_path("https_config.ru", File.expand_path("../", __FILE__))
32
    default_settings :hello_greeting => 'Hello there!', :important_path => '/must/exist'
33
    load_classes ::Proxy::Example::ClassLoader
34
    load_programmable_settings "::Proxy::Example::ProgrammableSettings"
35
    load_dependency_injection_wirings "::Proxy::Example::DIConfiguration"
36 20 Anonymous
    load_validators :my_validator => Proxy::Example::CustomValidators::MyValidator
37 8 Anonymous
    validate_readable :optional_path, :important_path
38 1 Anonymous
    validate :a_setting, :my_validator => true, :if => lambda {|settings| !settings[:a_setting].nil?}
39
    validate :another_setting, :my_other_validator => true
40
    start_services :service_a, :service_b
41
  end
42
end
43
</code></pre>
44
45 19 Dominic Cleal
Here we defined a plugin called "example", with version "0.0.1", that is going to listen on both http and https ports.
46 1 Anonymous
47 19 Dominic Cleal
h3. Full list of descriptor parameters
48
49
Following is the full list of parameters that can be defined by the Plugin Descriptor, and the version of the Smart Proxy that they were added in.
50
51
General smart proxy configuration parameters:
52
53 17 Anonymous
 * plugin :example, "1.2.3": *required*. Sets plugin name to "example" and version to "0.0.1".
54 19 Dominic Cleal
 * http_rackup_path "path/to/http_config.ru": _optional_, _1.6+_. Sets path to http rackup configuration. If omitted, the plugin is not going to listen on the http port. Please see below for information on rackup configuration.
55
 * https_rackup_path "path/to/https_config.ru": _optional_, _1.6+_. Sets path to https rackup configuration. If omitted, the plugin is not going to listen on the https port. Please see below for information on rackup configuration.
56
57
Loading and dependencies:
58
59
 * requires :another_plugin, '~> 1.2.0': _optional_, _1.6+_. Specifies plugin dependencies, where ":another_plugin" is another plugin name, and '~> 1.2.0' is version specification (pls. see http://guides.rubygems.org/patterns/#pessimistic_version_constraint for details on version specification).
60
 * bundler_group :my_plugin_group: _optional_, _1.6+_.  Sets the name of the bundler group for plugin dependencies. If omitted the plugin name is used.
61
 * after_activation { do_something }: _optional_, _1.6+_. Supplied block is going to be executed after the plugin has been loaded and enabled. Note that the block is going to be executed in the context of the Plugin Descriptor class.
62 20 Anonymous
 * load_classes: _1.12+_. must be a class, class name, or a block. Specified class must implement "load_classes" instance method that loads module's dependencies.
63
 * load_dependency_injection_wirings: can be a class, class name, or a block. _1.12+_. The class must implement load_dependency_injection_wirings(di_container, settings_hash) instance method.
64 19 Dominic Cleal
 * start_services: _1.12+_. list of dependency injection wiring labels. Services that perform work independently (asynchroniously) of http requests should implement #start method.
65
66
Settings related:
67
68 1 Anonymous
 * default_settings :first => 'my first setting', :another => 'my other setting': _optional_. _1.6+_. Defines default values for plugin parameters. These parameters can be overridden in plugin settings file. Setting any of the parameters in default_settings to nil will trigger a validation error.
69 20 Anonymous
 * load_programmable_settings: can be a class, class name, or a block. _1.12+_. Specified class must implement load_programmable_settings(settings_hash) instance method that returns new or updated settings.
70
 * load_validators: a hash of validator name (a symbol) to validator class mappings. _1.12+_.
71 19 Dominic Cleal
 * validate: validate :setting_one, :setting_two, ..., :setting_n, :validator_name => { :validator_param_one => 'value one', ...,}, :if => lambda {|settings| ... }, alternatively use :validator_name => true if validator has no parameters. If predicate is specified, the validator will be called only if the lambda evaluates to true. Predicate's lambda expects module's settings passed as a parameter. (_1.12+_)
72
 * validate_readable :optional_path, :important_path: _optional_, _1.10+_. Verifies that settings listed here contain paths to files that exist and are readable. Optional settings (not listed under default_settings) will be skipped if left uninitialized. 
73 20 Anonymous
 * validate_presence :setting_one, ..., :setting_n: _optional_, _1.10+_. Verifies that settings listed are not equal to nil. Executed automatically for each of the default settings.
74 19 Dominic Cleal
75 1 Anonymous
h3. Provider definition
76
77
Some plugins are *providers* for an existing plugin or module in the Smart Proxy, e.g. a DNS provider.
78
79
These are registered almost identically, but use Proxy::Provider instead of Proxy::Plugin. No rackup_paths are used for providers, since they don't add any new REST API, they only add functionality to an existing module.
80
81 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
82 1 Anonymous
module Proxy::Dns::PluginTemplate
83
  class Plugin < ::Proxy::Provider
84
    plugin :dns_plugin_template, ::Proxy::Dns::PluginTemplate::VERSION
85
86
    requires :dns, '>= 1.11'
87
88
    after_activation do
89
      require 'smart_proxy_dns_plugin_template/dns_plugin_template_main'
90
      require 'smart_proxy_dns_plugin_template/dns_plugin_template_dependencies'
91
    end
92
  end
93 11 Dominic Cleal
end
94 25 Ewoud Kohl van Wijngaarden
</code></pre>
95 1 Anonymous
96 11 Dominic Cleal
Additionally, each provider must specify which class implements interface expected by the main plugin. This is done by declaring an association for module's dependency injection container.
97 13 Anonymous
98 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
99 11 Dominic Cleal
require 'dns_common/dependency_injection/dependencies'
100
101
class Proxy::Dns::DependencyInjection::Dependencies
102 13 Anonymous
  dependency :dns_provider, Proxy::Dns::PluginTemplate::Record
103 1 Anonymous
end
104 25 Ewoud Kohl van Wijngaarden
</code></pre>
105 11 Dominic Cleal
106 19 Dominic Cleal
h2. Plugin Initialization
107
108
The initialization process can be thought of as consisting of two phases: loading and validation of settings and runtime initialization -- selection of classes, their parameters, and how they will be instantiated.
109
110
During the first phase of the process, all modules are gathered into groups consisting of the main plugin and one or more providers. If any of the members of the group fail at any time during initialization, the rest of the modules in the group will be failed as well. Initialization starts with all loaded and enabled plugin (main modules) classes being collected, then for each:
111
* configuration file is loaded
112
* dependencies are loaded (also see load_classes)
113
* configuration-related code executed, and configuration updated (also see load_runtime_configuration)
114
* validators executed (also see load_validators)
115
* provider names resolved to provider classes
116
117
At this point the steps above are repeated for all providers, one module group at a time. During the second phase, for each of the modules:
118
* dependency injection wirings are resolved (also see load_dependency_injection_wirings)
119
* services started (also see start_services)
120
* module's versions are checked against other modules stated requirements (also see)
121
122 1 Anonymous
h2. How to Load Dependencies
123 17 Anonymous
124 19 Dominic Cleal
_This technique requires Smart Proxy 1.12 or higher._
125
126 17 Anonymous
The class loader must implement load_classes instance method:
127
128 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
129 17 Anonymous
class ::Proxy::Example::ClassLoader
130
  def load_classes
131
    require 'example/class_a'
132
    require 'example/class_b'
133
  end
134
end
135 25 Ewoud Kohl van Wijngaarden
</code></pre>
136 1 Anonymous
137
alternatively, a block can be used to load dependencies:
138
139 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
140 1 Anonymous
module Proxy::Example
141 18 Anonymous
  class Plugin < ::Proxy::Plugin
142
    ...
143
    load_classes do
144
      require 'example/class_a'
145
      require 'example/class_b'
146
    end
147
    ...
148
  end
149
end
150 25 Ewoud Kohl van Wijngaarden
</code></pre>
151 18 Anonymous
152
h2. How to Programmatically Update Settings
153
154 19 Dominic Cleal
_This technique requires Smart Proxy 1.12 or higher._
155
156 17 Anonymous
The class must implement load_programmable_settings(settings_hash) instance method that returns new or updated settings:
157 1 Anonymous
158 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
159 1 Anonymous
class ::Proxy::Example::RuntimeConfiguration
160 18 Anonymous
  def load_programmable_settings(settings_hash)
161 17 Anonymous
    settings_hash[:a_setting] = "Hello, world"
162
    settings_hash
163
  end
164 18 Anonymous
end
165 25 Ewoud Kohl van Wijngaarden
</code></pre>
166 17 Anonymous
167 27 Justin Sherrill
h2. How to expose settings via the v2 features api.
168
169
A plugin can choose settings that can be exposed via the v2 features api:
170
171 28 Ewoud Kohl van Wijngaarden
<pre>
172 27 Justin Sherrill
module Proxy::Example
173
  class Plugin < ::Proxy::Plugin
174
    ...
175
        expose_setting :backend_url
176
    ...
177
  end
178
end
179
</pre>
180
181
h2. How to expose Capabilities via the v2 features api.
182 1 Anonymous
183
A plugin can expose Capabilities that the plugin provides.  These can be statically defined, or defined dynamically via a proc.  The main foreman server only fetches new capabilities at Refresh time, so the intent is not have frequently changing dynamic capabilities at this time. 
184 27 Justin Sherrill
185 29 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
186 27 Justin Sherrill
module Proxy::Example
187
  class Plugin < ::Proxy::Plugin
188
    ...
189
    # static capability
190
    capability :TYPE_A
191
    capability :TYPE_AAAA
192
    capability :TYPE_CNAME
193
194
    # dyanamic capability can return a single symbol/string or an array
195
    capability(proc{ "FOO" + "BAR" }) #a single capability 'FOOBAR'
196 1 Anonymous
    capability(lambda{ ["FOO", "BAR" ] }) #two capabilities 'FOO' and 'Bar'
197 27 Justin Sherrill
    ...
198 1 Anonymous
  end
199 28 Ewoud Kohl van Wijngaarden
end
200 29 Ewoud Kohl van Wijngaarden
</code></pre>
201 27 Justin Sherrill
202 17 Anonymous
h2. How to Define Dependency Injection Wirings
203
204 19 Dominic Cleal
_This technique requires Smart Proxy 1.12 or higher._
205
206 17 Anonymous
The class must implement load_dependency_injection_wirings instance method that has dependency injection container and settings hash as its parameters:
207
208 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
209 17 Anonymous
class ::Proxy::Example::DIConfiguration
210
  def load_dependency_injection_wirings(container_instance, settings)
211
    container_instance.dependency :depedency_a, ::Proxy::Example::ClassA
212
    container_instance.dependency :dependency_b, ::Proxy::Example::ClassB
213
    container_instance.singleton_dependency :service_a, lambda {|container| ::Proxy::Example::ServiceA.new(settings[:example_setting], container_instance.get_dependency(:dependency_a))}
214
  end
215
end
216 25 Ewoud Kohl van Wijngaarden
</code></pre>
217 17 Anonymous
218
When Proxy::DependencyInjection::Container#dependency is used to define a dependency, a new instance of a class will be returned, or lambda executed every time the dependency is requested.
219 1 Anonymous
If only a single instance of a class is ever required, use Proxy::DependencyInjection::Container#singleton_dependency: the class will be instantiated first time the dependency is requested, and then reused on all subsequent requests. 
220 17 Anonymous
221 21 Anonymous
h2. How to Create Custom Validators
222 17 Anonymous
223 19 Dominic Cleal
_This technique requires Smart Proxy 1.12 or higher._
224
225 20 Anonymous
A validator must use ::Proxy::PluginValidators::Base as its base class and implement validate!(settings) instance method that accepts a hash containing module settings. validate! should raise an exception if the check it's making fails.
226 17 Anonymous
227 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
228 17 Anonymous
class Proxy::Example::CustomValidators
229
  class MyValidator < ::Proxy::PluginValidators::Base
230
    def validate!(settings)
231
      raise ::Proxy::Error::ConfigurationError("Unsupported greeting") if settings[@setting_name] != "Hello, world"
232
    end
233
  end
234
end
235 25 Ewoud Kohl van Wijngaarden
</code></pre>
236 17 Anonymous
237 4 Anonymous
h2. API
238 1 Anonymous
239
Modular Sinatra app is used to define plugin API. Note the base class Sinatra::Base and inclusion of ::Proxy::Helpers:
240 24 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
241 8 Anonymous
module Proxy::Example
242 4 Anonymous
 class Api < Sinatra::Base
243
  helpers ::Proxy::Helpers
244
245 8 Anonymous
  get "/hello" do
246 4 Anonymous
    Proxy::Example::Plugin.settings.hello_greeting
247
  end
248
end
249
</code></pre>
250 1 Anonymous
251 4 Anonymous
Here we return a string defined in 'hello_greeting' parameter (see Plugin Descriptor above and settings file below) when a client performs a GET /hello. Please refer to "Sinatra documentation":http://www.sinatrarb.com/intro.html on details about routing, template rendering, available helpers, etc.
252
253
h2. Rackup Configuration
254
255
During startup Smart-Proxy assembles web applications listening on http and https ports using rackup files of enabled plugins. Plugin rackup files define mounting points of plugin API:
256
<pre><code class="ruby">
257
require 'example_plugin/example_api'
258
259 8 Anonymous
map "/example" do
260 1 Anonymous
  run Proxy::Example::Api
261 4 Anonymous
end
262
</code></pre>
263
264 26 Lukas Zapletal
The example above should be sufficient for the majority of plugins. Please refer to "Sinatra+Rack":http://www.sinatrarb.com/intro.html documentation for additional information.
265 4 Anonymous
266
h2. Plugin Settings
267
268
On startup Smart-Proxy will load and parse plugin configuration files located in its settings.d/ directory. Each plugin config file is named after the plugin and is a yaml-encoded collection of key-value pairs and used to override default values of plugin parameters. 
269
<pre>
270
---
271
:enabled: true
272
:hello_greeting: "O hai!"
273
</pre>
274 1 Anonymous
275 26 Lukas Zapletal
This settings file enables the plugin (by default all plugins are disabled), and overrides :hello_greeting parameter. Plugin settings can be accessed through .settings method of the Plugin class, for example: ExamplePlugin.settings.hello_greeting. Global Smart-Proxy parameters are accessible through Proxy::SETTINGS, for example Proxy::SETTINGS.foreman_url returns Foreman url configured for this Smart-Proxy.
276
277
Prefer underscore naming scheme (@hello_there@) to camel-case (@helloThere@).
278 4 Anonymous
279
h2. Bundler Configuration
280
281
Smart-Proxy relies on bundler to load its dependencies and plugins. We recommend to create a dedicated bundler config file for your plugin, and name it after the plugin. For example:
282
<pre><code class="ruby">
283
  gem 'smart_proxy_example'
284
  group :example do
285
    gem 'json'
286
  end
287 1 Anonymous
</code></pre>
288
 
289
You'll need to create a dedicated bundler group for additional dependencies of your plugin. By default the group shares the name with the plugin, but you can override it using bundler_group parameter in Plugin Descriptor. Please refer to [[How_to_Install_a_Smart-Proxy_Plugin]] for additional details on "from source" plugin installations.
290 11 Dominic Cleal
291
h2. Adding a DNS provider
292 15 Dominic Cleal
293 22 Ewoud Kohl van Wijngaarden
_Requires Smart Proxy 1.15 or higher (1.14 has a different interface.)_
294 16 Anonymous
295 22 Ewoud Kohl van Wijngaarden
When extending the 'dns' smart proxy module, the plugin needs to create a new Proxy::Dns::Record class with @do_create@ and @do_remove@ methods for adding and removing of DNS records.
296 11 Dominic Cleal
297
The easiest way to do this is using the "Smart Proxy DNS plugin template":https://github.com/theforeman/smart_proxy_dns_plugin_template which can get you up and running with a new DNS provider plugin in minutes.
298 16 Anonymous
299 11 Dominic Cleal
DNS Provider classes are instantiated by DNS module's dependency injection container.
300
301 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
302 11 Dominic Cleal
plugin :dns_plugin_template, ::Proxy::Dns::PluginTemplate::VERSION
303 25 Ewoud Kohl van Wijngaarden
</code></pre>
304 11 Dominic Cleal
305 1 Anonymous
And then in the main file of the plugin:
306
307 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
308 14 Anonymous
require 'dns_common/dns_common'
309 11 Dominic Cleal
310 1 Anonymous
module Proxy::Dns::PluginTemplate
311
  class Record < ::Proxy::Dns::Record
312
    include Proxy::Log
313
314 22 Ewoud Kohl van Wijngaarden
    attr_reader :example_setting, :optional_path, :required_setting, :required_path
315 14 Anonymous
316 22 Ewoud Kohl van Wijngaarden
    def initialize(required_setting, example_setting, required_path, optional_path, dns_ttl)
317
      @required_setting = required_setting # never nil
318
      @example_setting = example_setting # can be nil
319
      @required_path = required_path # file exists and is readable
320
      @optional_path = optional_path # nil, or file exists and is readable
321 1 Anonymous
322 22 Ewoud Kohl van Wijngaarden
      # Common settings can be defined by the main plugin, it's ok to use them locally.
323
      # Please note that providers must not rely on settings defined by other providers or plugins they are not related to.
324
      super('localhost', dns_ttl)
325 1 Anonymous
    end
326
327 22 Ewoud Kohl van Wijngaarden
    def do_create(name, value, type)
328
      # FIXME: There is no trailing dot. Your backend might require it.
329
      if false
330
        name += '.'
331
        value += '.' if ['PTR', 'CNAME'].include?(type)
332
      end
333
334
      # FIXME: Create a record with the correct name, value and type
335
      raise Proxy::Dns::Error.new("Failed to point #{name} to #{value} with type #{type}")
336 14 Anonymous
    end
337
338 22 Ewoud Kohl van Wijngaarden
    def do_remove(name, type)
339
      # FIXME: There is no trailing dot. Your backend might require it.
340
      if false
341
        name += '.'
342
      end
343
344
      # FIXME: Remove a record with the correct name and type
345
      raise Proxy::Dns::Error.new("Failed to remove #{name} of type #{type}")
346 11 Dominic Cleal
    end
347
  end
348 1 Anonymous
end
349 25 Ewoud Kohl van Wijngaarden
</code></pre>
350 1 Anonymous
351 22 Ewoud Kohl van Wijngaarden
DNS provider support was first added in version 1.10, but the interface was updated between 1.10 and 1.11. Later in 1.15 it was further modified. Please see the history of this page for 1.14-compatible recommendations and the 1.14-stable branch of the example DNS plugin instead of master.
352 16 Anonymous
353
h2. Adding a DHCP provider
354
355 19 Dominic Cleal
_Requires Smart Proxy 1.11 or higher._
356 16 Anonymous
357
When creating a new 'dhcp' provider smart proxy module, the plugin needs to create a new Proxy::DHCP::Server class that implements @load_subnets@, @load_subnet_data@, @find_subnet@, @subnets@, @all_hosts@, @unused_ip@, @find_record@, @add_record@, and @del_record@ methods.
358
359
Provider classes are instantiated by DHCP module's dependency injection container.
360
361 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
362 16 Anonymous
plugin :example_dhcp_provider, ::ExampleDhcpPlugin::Provider::VERSION
363 25 Ewoud Kohl van Wijngaarden
</code></pre>
364 16 Anonymous
365
And then in the main file of the plugin:
366
367 25 Ewoud Kohl van Wijngaarden
<pre><code class="ruby">
368 16 Anonymous
require 'dhcp_common/server'
369
370
module ::ExampleDhcpPlugin
371
  class Provider < ::Proxy::DHCP::Server
372
    include Proxy::Log
373
    include Proxy::Util
374
375
    def initialize
376
      super('localhost')
377
    end
378
379
    def load_subnets
380
      # loads subnet data into memory
381
    end
382
383
    def find_subnet(network_address)
384
      # returns Proxy::DHCP::Subnet that has network_address or nil if none was found
385
    end
386
387
    def load_subnet_data(a_subnet)
388
      # loads lease- and host-records for a Proxy::DHCP::Subnet
389
    end
390
391
    def subnets
392
      # returns all available subnets (instances of Proxy::DHCP::Subnet)
393
    end
394
395
    def all_hosts(network_address)
396
      # returns all reservations in a subnet with network_address
397
    end
398
399
    def unused_ip(network_address, mac_address, from_ip_address, to_ip_address)
400
      # returns first available ip address in a subnet with network_address, for a host with mac_address, in the range of ip addresses: from_ip_address, to_ip_address
401
    end
402
403
    def find_record(network_address, ip_or_mac_address)
404
      # returns a Proxy::DHCP::Record from a subnet with network_address that has ip- or mac-address specified in ip_or_mac_address, or nil of none was found 
405
    end
406
407
    def add_record(params)
408
      # creates a record
409
    end
410
411
    def del_record(network_address,a_record)
412
      # removes a Proxy::DHCP::Record from a subnet with network_address
413
    end
414
  end
415
end
416 25 Ewoud Kohl van Wijngaarden
</code></pre>
417 16 Anonymous
418
DHCP provider support was first added in version 1.11.
419 5 Anonymous
420
h2. Testing
421 1 Anonymous
422 23 Ian Ballou
Make sure that your Gemfile includes the "smart-proxy" gem as a development dependency:
423 7 Anonymous
424
<pre><code class="ruby">
425
group :development do
426
  gem 'smart_proxy', :git => "https://github.com/theforeman/smart-proxy.git"
427
end
428
</code></pre>
429 1 Anonymous
430 23 Ian Ballou
Ensure that your plugin has a Rakefile, for example:
431
432
<pre><code class="ruby">
433
require 'rake'
434
require 'rake/testtask'
435
436
desc 'Default: run unit tests.'
437
task :default => :test
438
439
desc 'Test Pulp Plugin'
440
Rake::TestTask.new(:test) do |t|
441
  t.libs << '.'
442
  t.libs << 'lib'
443
  t.libs << 'test'
444
  t.test_files = FileList['test/**/*_test.rb']
445
  t.verbose = true
446
end
447
</code></pre>
448
449 1 Anonymous
Load 'smart_proxy_for_testing' in your tests:
450
451 23 Ian Ballou
<pre><code class="ruby">
452 5 Anonymous
$: << File.join(File.dirname(__FILE__), '..', 'lib')
453
454
require 'smart_proxy_for_testing'
455
require 'test/unit'
456
require 'webmock/test_unit'
457
require 'mocha/test_unit'
458
require "rack/test"
459
460
require 'smart_proxy_pulp_plugin/pulp_plugin'
461
require 'smart_proxy_pulp_plugin/pulp_api'
462
463
class PulpApiTest < Test::Unit::TestCase
464
  include Rack::Test::Methods
465
466
  def app
467
    PulpProxy::Api.new
468
  end
469
470
  def test_returns_pulp_status_on_200
471
    stub_request(:get, "#{::PulpProxy::Plugin.settings.pulp_url.to_s}/api/v2/status/").to_return(:body => "{\"api_version\":\"2\"}")
472
    get '/status'
473
474
    assert last_response.ok?, "Last response was not ok: #{last_response.body}"
475
    assert_equal("{\"api_version\":\"2\"}", last_response.body)
476
  end
477
end
478
</code></pre>
479
480 10 Anonymous
To execute all tests <code><pre>bundle exec rake test</code></pre>.  To save time during development it is possible to execute tests in a single file: <code><pre>bundle exec rake test TEST=path/to/test/file</pre></code>
481
482 1 Anonymous
Please refer to "Sinatra documention":http://www.sinatrarb.com/testing.html for detailed information on testing of Sinatra applications.
483 9 Dominic Cleal
484 25 Ewoud Kohl van Wijngaarden
Once you have tests, see "Jenkins":https://projects.theforeman.org/projects/foreman/wiki/Jenkins#Smart-proxy-plugin-testing for info on setting up tests under Jenkins.