Project

General

Profile

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

Dominic Cleal, 06/03/2015 04:29 AM
run tests on Jenkins

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 8 Anonymous
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. The "smart_proxy_example plugin":https://github.com/theforeman/smart_proxy_example is a minimal example plugin that can be used as a skeleton. 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.
10 1 Anonymous
11 6 Dominic Cleal
h2. Making your plugin official
12
13
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.
14
15 1 Anonymous
h2. Plugin Definition
16 2 Anonymous
17 3 Anonymous
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:
18
19 1 Anonymous
<pre><code class='ruby'>
20 8 Anonymous
module Proxy::Example
21
  class Plugin < ::Proxy::Plugin
22
    plugin :example, "0.0.1"
23
    http_rackup_path File.expand_path("http_config.ru", File.expand_path("../", __FILE__))
24
    https_rackup_path File.expand_path("https_config.ru", File.expand_path("../", __FILE__))
25
    default_settings :hello_greeting => 'Hello there!'
26
  end
27 3 Anonymous
end
28
</code></pre>
29
30
Here we defined a plugin called "example", with version "0.0.1", that is going to listen on both http and https ports. Following is the full list of parameters that can be defined by the Plugin Descriptor.
31
32
 * plugin :example, "1.2.3": *required*. Sets plugin name to "example" and version to "0.0.1".
33
 * http_rackup_path "path/to/http_config.ru": *optional*. 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.
34
 * https_rackup_path "path/to/https_config.ru": *optional*. 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.
35
 * requires :another_plugin, '~> 1.2.0': *optional*. 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).
36 1 Anonymous
 * default_settings :first => 'my first setting', :another => 'my other setting': *optional*. Defines default values for plugin parameters. These parameters can be overridden in plugin settings file.
37 4 Anonymous
 * after_activation { do_something }: *optional*. 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.
38 1 Anonymous
 * bundler_group :my_plugin_group: *optional*.  Sets the name of the bundler group for plugin dependencies. If omitted the plugin name is used. 
39
40 4 Anonymous
41
h2. API
42 1 Anonymous
43 4 Anonymous
Modular Sinatra app is used to define plugin API. Note the base class Sinatra::Base and inclusion of ::Proxy::Helpers:
44
<pre><code class='ruby'>
45 8 Anonymous
module Proxy::Example
46
 class Api < Sinatra::Base
47 4 Anonymous
  helpers ::Proxy::Helpers
48
49
  get "/hello" do
50 8 Anonymous
    Proxy::Example::Plugin.settings.hello_greeting
51 4 Anonymous
  end
52
end
53
</code></pre>
54
55 1 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.
56 4 Anonymous
57
h2. Rackup Configuration
58
59
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:
60
<pre><code class="ruby">
61
require 'example_plugin/example_api'
62
63
map "/example" do
64 8 Anonymous
  run Proxy::Example::Api
65 4 Anonymous
end
66
</code></pre>
67
68
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. 
69
70
h2. Plugin Settings
71
72
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. 
73
<pre>
74
---
75
:enabled: true
76
:hello_greeting: "O hai!"
77
</pre>
78
79
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. 
80
81
h2. Bundler Configuration
82
83
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:
84
<pre><code class="ruby">
85
  gem 'smart_proxy_example'
86
  group :example do
87
    gem 'json'
88
  end
89
</code></pre>
90
 
91
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.
92 5 Anonymous
93
h2. Testing
94 1 Anonymous
95 9 Dominic Cleal
Make sure that Gemfile includes "smart-proxy" gem as a development dependency:
96 7 Anonymous
97
<pre><code class="ruby">
98
group :development do
99
  gem 'smart_proxy', :git => "https://github.com/theforeman/smart-proxy.git"
100
end
101
</code></pre>
102
103
Load 'smart_proxy_for_testing' in your tests:
104 5 Anonymous
105
<pre><code class = "ruby">
106
$: << File.join(File.dirname(__FILE__), '..', 'lib')
107
108
require 'smart_proxy_for_testing'
109
require 'test/unit'
110
require 'webmock/test_unit'
111
require 'mocha/test_unit'
112
require "rack/test"
113
114
require 'smart_proxy_pulp_plugin/pulp_plugin'
115
require 'smart_proxy_pulp_plugin/pulp_api'
116
117
class PulpApiTest < Test::Unit::TestCase
118
  include Rack::Test::Methods
119
120
  def app
121
    PulpProxy::Api.new
122
  end
123
124
  def test_returns_pulp_status_on_200
125
    stub_request(:get, "#{::PulpProxy::Plugin.settings.pulp_url.to_s}/api/v2/status/").to_return(:body => "{\"api_version\":\"2\"}")
126
    get '/status'
127
128
    assert last_response.ok?, "Last response was not ok: #{last_response.body}"
129
    assert_equal("{\"api_version\":\"2\"}", last_response.body)
130
  end
131
end
132
</code></pre>
133
134 1 Anonymous
Please refer to "Sinatra documention":http://www.sinatrarb.com/testing.html for detailed information on testing of Sinatra applications.
135 9 Dominic Cleal
136
Once you have tests, see "Jenkins":http://projects.theforeman.org/projects/foreman/wiki/Jenkins#Smart-proxy-plugin-testing for info on setting up tests under Jenkins.