foreman_webhooks 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +619 -0
- data/README.md +30 -0
- data/Rakefile +49 -0
- data/app/controllers/api/v2/webhook_templates_controller.rb +110 -0
- data/app/controllers/api/v2/webhooks_controller.rb +61 -0
- data/app/controllers/concerns/foreman_webhooks/controller/parameters/webhook.rb +34 -0
- data/app/controllers/concerns/foreman_webhooks/controller/parameters/webhook_template.rb +34 -0
- data/app/controllers/webhook_templates_controller.rb +5 -0
- data/app/controllers/webhooks_controller.rb +39 -0
- data/app/jobs/foreman_webhooks/deliver_webhook_job.rb +27 -0
- data/app/lib/foreman_webhooks/renderer/scope/webhook_template.rb +36 -0
- data/app/models/webhook.rb +141 -0
- data/app/models/webhook_template.rb +59 -0
- data/app/services/foreman_webhooks/webhook_service.rb +111 -0
- data/app/subscribers/foreman_webhooks/event_subscriber.rb +9 -0
- data/app/views/api/v2/webhook_templates/base.json.rabl +5 -0
- data/app/views/api/v2/webhook_templates/create.json.rabl +5 -0
- data/app/views/api/v2/webhook_templates/index.json.rabl +5 -0
- data/app/views/api/v2/webhook_templates/main.json.rabl +7 -0
- data/app/views/api/v2/webhook_templates/show.json.rabl +11 -0
- data/app/views/api/v2/webhook_templates/update.json.rabl +5 -0
- data/app/views/api/v2/webhooks/base.json.rabl +5 -0
- data/app/views/api/v2/webhooks/create.json.rabl +5 -0
- data/app/views/api/v2/webhooks/index.json.rabl +5 -0
- data/app/views/api/v2/webhooks/main.json.rabl +8 -0
- data/app/views/api/v2/webhooks/show.json.rabl +17 -0
- data/app/views/api/v2/webhooks/update.json.rabl +5 -0
- data/app/views/foreman_webhooks/webhook_templates/ansible_tower_-_host_in_inventory.erb +20 -0
- data/app/views/foreman_webhooks/webhook_templates/empty_payload.erb +6 -0
- data/app/views/foreman_webhooks/webhook_templates/webhook_template_-_payload_default.erb +11 -0
- data/app/views/webhook_templates/_alerts.html.erb +3 -0
- data/app/views/webhook_templates/_custom_tab_headers.html.erb +1 -0
- data/app/views/webhook_templates/_custom_tabs.html.erb +5 -0
- data/app/views/webhook_templates/edit.html.erb +3 -0
- data/app/views/webhook_templates/index.html.erb +29 -0
- data/app/views/webhook_templates/new.html.erb +3 -0
- data/app/views/webhooks/_form.html.erb +37 -0
- data/app/views/webhooks/_templates.html.erb +5 -0
- data/app/views/webhooks/edit.html.erb +3 -0
- data/app/views/webhooks/new.html.erb +3 -0
- data/config/routes.rb +45 -0
- data/db/migrate/20191016100128_create_webhook_targets.rb +13 -0
- data/db/migrate/20200831194208_rename_webhook_targets_to_webhooks.rb +7 -0
- data/db/migrate/20200831194514_add_template_to_webhooks.rb +7 -0
- data/db/migrate/20200907232758_rename_webhook_permissions.rb +22 -0
- data/db/migrate/20200908004234_add_columns_to_webhooks.rb +13 -0
- data/db/migrate/20201014115147_rename_ca_file_column.rb +7 -0
- data/db/migrate/20201109135301_add_http_headers.rb +8 -0
- data/db/seeds.d/95_webhook_templates.rb +7 -0
- data/lib/foreman_webhooks.rb +6 -0
- data/lib/foreman_webhooks/engine.rb +80 -0
- data/lib/foreman_webhooks/version.rb +5 -0
- data/lib/tasks/foreman_webhooks_tasks.rake +47 -0
- data/package.json +45 -0
- data/test/controllers/api/v2/webhook_templates_controller_test.rb +200 -0
- data/test/controllers/api/v2/webhooks_controller_test.rb +108 -0
- data/test/factories/webhook.rb +20 -0
- data/test/factories/webhook_target.rb +9 -0
- data/test/factories/webhook_template.rb +16 -0
- data/test/jobs/foreman_webhooks/deliver_webhook_job_test.rb +17 -0
- data/test/models/webhook_test.rb +38 -0
- data/test/test_plugin_helper.rb +8 -0
- data/test/unit/foreman_webhooks/webhook_service_test.rb +53 -0
- data/webpack/ForemanWebhooks/Routes/ForemanWebhooksRoutes.js +12 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/EmptyWebhooksTable/index.js +29 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhookDeleteModal.js +43 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/EnabledCell.js +16 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/__tests__/__snapshots__/enabledCellFormatter.test.js.snap +7 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/__tests__/enabledCellFormatter.test.js +7 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/enabledCellFormatter.js +6 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/index.js +1 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/__tests__/EnabledCell.test.js +14 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/__tests__/__snapshots__/EnabledCell.test.js.snap +17 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/WebhooksTable.js +75 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/WebhooksTableSchema.js +41 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/__tests__/WebhooksTable.test.js +57 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/__tests__/__snapshots__/WebhooksTable.test.js.snap +115 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/index.js +25 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/WebhooksIndexPage.js +87 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/__tests__/WebhooksIndexPage.fixtures.js +74 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/__tests__/WebhooksIndexPage.test.js +21 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/__tests__/__snapshots__/WebhooksIndexPage.test.js.snap +68 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/index.js +51 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksPageActions.js +51 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksPageHelpers.js +29 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksPageSelectors.js +85 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/__tests__/WebhooksPageHelpers.test.js +20 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/__tests__/WebhooksPageSelectors.test.js +45 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/__tests__/__snapshots__/WebhooksPageSelectors.test.js.snap +50 -0
- data/webpack/ForemanWebhooks/Routes/Webhooks/constants.js +13 -0
- data/webpack/__mocks__/foremanReact/common/HOC.js +2 -0
- data/webpack/__mocks__/foremanReact/common/I18n.js +7 -0
- data/webpack/__mocks__/foremanReact/common/helpers.js +7 -0
- data/webpack/__mocks__/foremanReact/common/urlHelpers.js +1 -0
- data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js +2 -0
- data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js +10 -0
- data/webpack/__mocks__/foremanReact/components/ForemanModal/index.js +18 -0
- data/webpack/__mocks__/foremanReact/components/Layout/LayoutActions.js +2 -0
- data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +2 -0
- data/webpack/__mocks__/foremanReact/components/common/EmptyState.js +5 -0
- data/webpack/__mocks__/foremanReact/components/common/table.js +5 -0
- data/webpack/__mocks__/foremanReact/constants.js +24 -0
- data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +6 -0
- data/webpack/__mocks__/foremanReact/redux/API/index.js +10 -0
- data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +8 -0
- data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
- data/webpack/index.js +0 -0
- data/webpack/routes_index.js +4 -0
- metadata +195 -0
data/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# ForemanWebhooks
|
2
|
+
|
3
|
+
Don't call us, we'll call you. This plugins allows to configure Webhooks for Foreman.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
See [How_to_Install_a_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Plugin)
|
8
|
+
for how to install Foreman plugins
|
9
|
+
|
10
|
+
## Contributing
|
11
|
+
|
12
|
+
Fork and send a Pull Request. Thanks!
|
13
|
+
|
14
|
+
## Copyright
|
15
|
+
|
16
|
+
Copyright (c) 2019 Timo Goebel
|
17
|
+
|
18
|
+
This program is free software: you can redistribute it and/or modify
|
19
|
+
it under the terms of the GNU General Public License as published by
|
20
|
+
the Free Software Foundation, either version 3 of the License, or
|
21
|
+
(at your option) any later version.
|
22
|
+
|
23
|
+
This program is distributed in the hope that it will be useful,
|
24
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
25
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
26
|
+
GNU General Public License for more details.
|
27
|
+
|
28
|
+
You should have received a copy of the GNU General Public License
|
29
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
30
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'bundler/setup'
|
6
|
+
rescue LoadError
|
7
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
8
|
+
end
|
9
|
+
begin
|
10
|
+
require 'rdoc/task'
|
11
|
+
rescue LoadError
|
12
|
+
require 'rdoc/rdoc'
|
13
|
+
require 'rake/rdoctask'
|
14
|
+
RDoc::Task = Rake::RDocTask
|
15
|
+
end
|
16
|
+
|
17
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'ForemanWebhooks'
|
20
|
+
rdoc.options << '--line-numbers'
|
21
|
+
rdoc.rdoc_files.include('README.rdoc')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
24
|
+
|
25
|
+
APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
|
26
|
+
|
27
|
+
Bundler::GemHelper.install_tasks
|
28
|
+
|
29
|
+
require 'rake/testtask'
|
30
|
+
|
31
|
+
Rake::TestTask.new(:test) do |t|
|
32
|
+
t.libs << 'lib'
|
33
|
+
t.libs << 'test'
|
34
|
+
t.pattern = 'test/**/*_test.rb'
|
35
|
+
t.verbose = false
|
36
|
+
end
|
37
|
+
|
38
|
+
task default: :test
|
39
|
+
|
40
|
+
begin
|
41
|
+
require 'rubocop/rake_task'
|
42
|
+
RuboCop::RakeTask.new
|
43
|
+
rescue StandardError => _e
|
44
|
+
puts 'Rubocop not loaded.'
|
45
|
+
end
|
46
|
+
|
47
|
+
task :default do
|
48
|
+
Rake::Task['rubocop'].execute
|
49
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Api
|
4
|
+
module V2
|
5
|
+
class WebhookTemplatesController < V2::BaseController
|
6
|
+
include Api::Version2
|
7
|
+
include ForemanWebhooks::Controller::Parameters::WebhookTemplate
|
8
|
+
include Foreman::Controller::TemplateImport
|
9
|
+
|
10
|
+
before_action :find_resource, only: %i[show update destroy clone export]
|
11
|
+
|
12
|
+
api :GET, '/webhook_templates/', N_('List webhook templates')
|
13
|
+
param_group :search_and_pagination, ::Api::V2::BaseController
|
14
|
+
add_scoped_search_description_for(WebhookTemplate)
|
15
|
+
def index
|
16
|
+
@webhook_templates = resource_scope_for_index
|
17
|
+
end
|
18
|
+
|
19
|
+
api :GET, '/webhook_templates/:id', N_('Show webhook template details')
|
20
|
+
param :id, :identifier, required: true
|
21
|
+
def show; end
|
22
|
+
|
23
|
+
def_param_group :webhook_template do
|
24
|
+
param :webhook_template, Hash, action_aware: true, required: true do
|
25
|
+
param :name, String, required: true
|
26
|
+
param :description, String
|
27
|
+
param :template, String, required: true
|
28
|
+
param :snippet, :bool, allow_nil: true
|
29
|
+
param :audit_comment, String, allow_nil: true
|
30
|
+
param :locked, :bool, desc: N_('Whether or not the template is locked for editing')
|
31
|
+
param :default, :bool, desc: N_('Whether or not the template is added automatically to new organizations and locations')
|
32
|
+
param_group :taxonomies, ::Api::V2::BaseController
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def_param_group :webhook_template_clone do
|
37
|
+
param :webhook_template, Hash, required: true, action_aware: true do
|
38
|
+
param :name, String, required: true, desc: N_('Template name')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
api :POST, '/webhook_templates/', N_('Create a webhook template')
|
43
|
+
param_group :webhook_template, as: :create
|
44
|
+
def create
|
45
|
+
@webhook_template = WebhookTemplate.new(webhook_template_params)
|
46
|
+
process_response @webhook_template.save
|
47
|
+
end
|
48
|
+
|
49
|
+
api :POST, '/webhook_templates/import', N_('Import a webhook template')
|
50
|
+
param :webhook_template, Hash, required: true, action_aware: true do
|
51
|
+
param :name, String, required: true, desc: N_('Template name')
|
52
|
+
param :template, String, required: true, desc: N_('Template contents including metadata')
|
53
|
+
param_group :taxonomies, ::Api::V2::BaseController
|
54
|
+
end
|
55
|
+
param_group :template_import_options, ::Api::V2::BaseController
|
56
|
+
def import
|
57
|
+
@webhook_template = WebhookTemplate.import!(*import_attrs_for(:webhook_template))
|
58
|
+
process_response @webhook_template
|
59
|
+
end
|
60
|
+
|
61
|
+
api :PUT, '/webhook_templates/:id', N_('Update a webhook template')
|
62
|
+
param :id, :identifier, required: true
|
63
|
+
param_group :webhook_template, as: :update
|
64
|
+
def update
|
65
|
+
process_response @webhook_template.update(webhook_template_params)
|
66
|
+
end
|
67
|
+
|
68
|
+
api :DELETE, '/webhook_templates/:id', N_('Delete a webhook template')
|
69
|
+
param :id, :identifier, required: true
|
70
|
+
def destroy
|
71
|
+
process_response @webhook_template.destroy
|
72
|
+
end
|
73
|
+
|
74
|
+
api :POST, '/webhook_templates/:id/clone', N_('Clone a template')
|
75
|
+
param :id, :identifier, required: true
|
76
|
+
param_group :webhook_template_clone, as: :create
|
77
|
+
def clone
|
78
|
+
@webhook_template = @webhook_template.dup
|
79
|
+
@webhook_template.name = params[:webhook_template][:name]
|
80
|
+
process_response @webhook_template.save
|
81
|
+
end
|
82
|
+
|
83
|
+
api :GET, '/webhook_templates/:id/export', N_('Export a webhook template to ERB')
|
84
|
+
param :id, :identifier, required: true
|
85
|
+
def export
|
86
|
+
send_data @webhook_template.to_erb, type: 'text/plain',
|
87
|
+
disposition: 'attachment',
|
88
|
+
filename: @webhook_template.filename
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Overload this method to avoid using search_for method
|
94
|
+
def resource_scope_for_index(options = {})
|
95
|
+
resource_scope(options).paginate(paginate_options)
|
96
|
+
end
|
97
|
+
|
98
|
+
def action_permission
|
99
|
+
case params[:action]
|
100
|
+
when 'clone', 'import'
|
101
|
+
'create'
|
102
|
+
when 'export'
|
103
|
+
'view'
|
104
|
+
else
|
105
|
+
super
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Api
|
4
|
+
module V2
|
5
|
+
class WebhooksController < V2::BaseController
|
6
|
+
include Api::Version2
|
7
|
+
include ForemanWebhooks::Controller::Parameters::Webhook
|
8
|
+
include Foreman::Controller::TemplateImport
|
9
|
+
|
10
|
+
before_action :find_resource, only: %i[show update destroy]
|
11
|
+
|
12
|
+
api :GET, '/webhooks/', N_('List Webhooks')
|
13
|
+
param_group :search_and_pagination, ::Api::V2::BaseController
|
14
|
+
def index
|
15
|
+
@webhooks = resource_scope_for_index
|
16
|
+
end
|
17
|
+
|
18
|
+
api :GET, '/webhooks/:id', N_('Show Webhook details')
|
19
|
+
param :id, :identifier, required: true
|
20
|
+
def show; end
|
21
|
+
|
22
|
+
def_param_group :webhook do
|
23
|
+
param :webhook, Hash, action_aware: true, required: true do
|
24
|
+
param :name, String, required: true
|
25
|
+
param :target_url, String, required: true
|
26
|
+
param :http_method, Webhook::ALLOWED_HTTP_METHODS
|
27
|
+
param :http_content_type, String
|
28
|
+
param :event, String, required: true
|
29
|
+
param :webhook_template_id, :identifier
|
30
|
+
param :enabled, :boolean
|
31
|
+
param :verify_ssl, :boolean
|
32
|
+
param :ssl_ca_certs, String, N_('X509 Certification Authorities concatenated in PEM format')
|
33
|
+
param :user, String
|
34
|
+
param :password, String
|
35
|
+
param :http_headers, String
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
api :POST, '/webhooks/', N_('Create a Webhook')
|
40
|
+
param_group :webhook, as: :create
|
41
|
+
|
42
|
+
def create
|
43
|
+
@webhook = Webhook.new(webhook_params)
|
44
|
+
process_response @webhook.save
|
45
|
+
end
|
46
|
+
|
47
|
+
api :PUT, '/webhooks/:id', N_('Update a Webhook')
|
48
|
+
param :id, :identifier, required: true
|
49
|
+
param_group :webhook, as: :update
|
50
|
+
def update
|
51
|
+
process_response @webhook.update(webhook_params)
|
52
|
+
end
|
53
|
+
|
54
|
+
api :DELETE, '/webhooks/:id', N_('Delete a Webhook')
|
55
|
+
param :id, :identifier, required: true
|
56
|
+
def destroy
|
57
|
+
process_response @webhook.destroy
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanWebhooks
|
4
|
+
module Controller
|
5
|
+
module Parameters
|
6
|
+
module Webhook
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
def webhook_params_filter
|
11
|
+
Foreman::ParameterFilter.new(::Webhook).tap do |filter|
|
12
|
+
filter.permit :name,
|
13
|
+
:target_url,
|
14
|
+
:webhook_template_id,
|
15
|
+
:event,
|
16
|
+
:http_method,
|
17
|
+
:http_content_type,
|
18
|
+
:enabled,
|
19
|
+
:verify_ssl,
|
20
|
+
:ssl_ca_certs,
|
21
|
+
:user,
|
22
|
+
:password,
|
23
|
+
:http_headers
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def webhook_params
|
29
|
+
self.class.webhook_params_filter.filter_params(params, parameter_filter_context)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanWebhooks
|
4
|
+
module Controller
|
5
|
+
module Parameters
|
6
|
+
module WebhookTemplate
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
include Foreman::Controller::Parameters::Taxonomix
|
9
|
+
include Foreman::Controller::Parameters::Template
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
def webhook_template_params_filter
|
13
|
+
Foreman::ParameterFilter.new(::WebhookTemplate).tap do |filter|
|
14
|
+
add_taxonomix_params_filter(filter)
|
15
|
+
add_template_params_filter(filter)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def webhook_template_params
|
21
|
+
self.class.webhook_template_params_filter.filter_params(params, parameter_filter_context)
|
22
|
+
end
|
23
|
+
|
24
|
+
def organization_params
|
25
|
+
self.class.organization_params_filter(::WebhookTemplate).filter_params(params, parameter_filter_context)
|
26
|
+
end
|
27
|
+
|
28
|
+
def location_params
|
29
|
+
self.class.location_params_filter(::WebhookTemplate).filter_params(params, parameter_filter_context)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class WebhooksController < ::ApplicationController
|
4
|
+
include ForemanWebhooks::Controller::Parameters::Webhook
|
5
|
+
include Foreman::Controller::AutoCompleteSearch
|
6
|
+
|
7
|
+
before_action :find_resource, only: %i[edit update destroy]
|
8
|
+
|
9
|
+
def new
|
10
|
+
@webhook = Webhook.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
@webhook = Webhook.new(webhook_params)
|
15
|
+
if @webhook.save
|
16
|
+
process_success success_redirect: '/webhooks'
|
17
|
+
else
|
18
|
+
process_error
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def edit; end
|
23
|
+
|
24
|
+
def update
|
25
|
+
if @webhook.update(webhook_params)
|
26
|
+
process_success success_redirect: '/webhooks'
|
27
|
+
else
|
28
|
+
process_error
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def destroy
|
33
|
+
if @webhook.destroy
|
34
|
+
process_success
|
35
|
+
else
|
36
|
+
process_error
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanWebhooks
|
4
|
+
class DeliverWebhookJob < ::ApplicationJob
|
5
|
+
queue_as :default
|
6
|
+
|
7
|
+
def perform(options)
|
8
|
+
webhook = Webhook.unscoped.find_by(id: options[:webhook_id])
|
9
|
+
WebhookService.new(
|
10
|
+
webhook: webhook,
|
11
|
+
headers: options[:headers],
|
12
|
+
url: options[:url],
|
13
|
+
event_name: options[:event_name],
|
14
|
+
payload: options[:payload]
|
15
|
+
).execute
|
16
|
+
end
|
17
|
+
|
18
|
+
def webhook_id
|
19
|
+
arguments.first['webhook_id'] unless arguments.first.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def humanized_name
|
23
|
+
webhook = webhook_id && Webhook.unscoped.find_by(id: webhook_id)
|
24
|
+
(webhook && (_('Deliver webhook %s') % webhook.name)) || _('Deliver webhook')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanWebhooks
|
4
|
+
module Renderer
|
5
|
+
module Scope
|
6
|
+
class WebhookTemplate < ::Foreman::Renderer::Scope::Template
|
7
|
+
extend ApipieDSL::Class
|
8
|
+
|
9
|
+
apipie :class, 'Macros related to Webhook payload customization' do
|
10
|
+
name 'Webhook Template'
|
11
|
+
sections only: %w[all webhooks]
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(**args)
|
15
|
+
super
|
16
|
+
@defaults = {
|
17
|
+
event_name: @event_name,
|
18
|
+
webhook_id: @webhook_id,
|
19
|
+
context: @context
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
apipie :method, 'Creates final payload' do
|
24
|
+
required :hash, Hash, 'Key=value object with with data that should be present in payload'
|
25
|
+
keyword :with_defaults, [true, false], 'If set to true, adds default entries to the payload', default: true
|
26
|
+
returns String, 'JSON string with the final payload'
|
27
|
+
example 'payload({ id: @object.id, name: @object.name }) #=> "{ "id": 1, "name": "host.example.com", "context": { ... }, "event_name": "host_created.event.foreman" }"'
|
28
|
+
end
|
29
|
+
def payload(hash, with_defaults: true)
|
30
|
+
hash.merge!(@defaults) if with_defaults
|
31
|
+
hash.to_json
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Webhook < ApplicationRecord
|
4
|
+
audited
|
5
|
+
include Authorizable
|
6
|
+
include Encryptable
|
7
|
+
|
8
|
+
extend FriendlyId
|
9
|
+
friendly_id :name
|
10
|
+
include Parameterizable::ByIdName
|
11
|
+
|
12
|
+
EVENT_POSTFIX = ".#{Foreman::Observable::DEFAULT_NAMESPACE}"
|
13
|
+
|
14
|
+
EVENT_ALLOWLIST = %w[
|
15
|
+
host_created host_updated host_destroyed
|
16
|
+
hostgroup_created hostgroup_updated hostgroup_destroyed
|
17
|
+
user_created user_updated user_destroyed
|
18
|
+
domain_created domain_updated domain_destroyed
|
19
|
+
subnet_created subnet_updated subnet_destroyed
|
20
|
+
build_entered build_exited status_changed
|
21
|
+
].map { |e| e + EVENT_POSTFIX }.freeze
|
22
|
+
|
23
|
+
DEFAULT_PAYLOAD_TEMPLATE = 'Webhook Template - Payload Default'
|
24
|
+
|
25
|
+
ALLOWED_HTTP_METHODS = %w[POST GET PUT DELETE PATCH].freeze
|
26
|
+
|
27
|
+
encrypts :password
|
28
|
+
|
29
|
+
attribute :events, :string, array: true, default: []
|
30
|
+
|
31
|
+
validates_lengths_from_database
|
32
|
+
validates :name, :target_url, :events, presence: true
|
33
|
+
validates :target_url, format: { with: URI::DEFAULT_PARSER.make_regexp(%w[http https]),
|
34
|
+
message: _('URL must be valid and schema must be one of: %s') % 'http, https' }
|
35
|
+
validates :http_method, inclusion: { in: ALLOWED_HTTP_METHODS }
|
36
|
+
|
37
|
+
belongs_to :webhook_template, foreign_key: :webhook_template_id
|
38
|
+
|
39
|
+
before_save :set_default_template, unless: :webhook_template
|
40
|
+
|
41
|
+
scope :for_event, ->(events) { where('events @> ARRAY[?]::varchar[]', Array(events)) }
|
42
|
+
|
43
|
+
default_scope -> { order('webhooks.name') }
|
44
|
+
|
45
|
+
scoped_search on: :name, complete_value: true, default_order: true
|
46
|
+
scoped_search on: :target_url, complete_value: :true
|
47
|
+
scoped_search on: :enabled, complete_value: { true: true, false: false }
|
48
|
+
|
49
|
+
def self.available_events
|
50
|
+
::Foreman::EventSubscribers.all_observable_events & EVENT_ALLOWLIST
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.deliver(event_name:, payload:)
|
54
|
+
for_event(event_name).includes([:webhook_template]).each do |target|
|
55
|
+
target.deliver(event_name: event_name, payload: payload) if target.enabled?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def events=(events)
|
60
|
+
events = Array(events).map do |event|
|
61
|
+
next event if event.end_with?(EVENT_POSTFIX)
|
62
|
+
|
63
|
+
event.to_s.underscore + EVENT_POSTFIX
|
64
|
+
end
|
65
|
+
super(self.class.available_events & events)
|
66
|
+
end
|
67
|
+
|
68
|
+
def event=(event)
|
69
|
+
self.events = event
|
70
|
+
end
|
71
|
+
|
72
|
+
def event
|
73
|
+
events.first
|
74
|
+
end
|
75
|
+
|
76
|
+
def deliver(event_name:, payload:)
|
77
|
+
payload_to_deliver = rendered_payload(event_name, payload)
|
78
|
+
headers_to_deliver = rendered_headers(event_name, payload)
|
79
|
+
url_to_deliver = rendered_targed_url(event_name, payload)
|
80
|
+
::ForemanWebhooks::DeliverWebhookJob.perform_later(
|
81
|
+
event_name: event_name,
|
82
|
+
payload: payload_to_deliver,
|
83
|
+
headers: headers_to_deliver,
|
84
|
+
url: url_to_deliver,
|
85
|
+
webhook_id: id
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
def ca_certs_store
|
90
|
+
store = OpenSSL::X509::Store.new
|
91
|
+
return store if ssl_ca_certs.blank?
|
92
|
+
|
93
|
+
ssl_ca_certs.split(/(?=-----BEGIN)/).each do |cert|
|
94
|
+
store.add_cert(OpenSSL::X509::Certificate.new(cert))
|
95
|
+
end
|
96
|
+
store
|
97
|
+
rescue StandardError => e
|
98
|
+
raise _(format('Failed to build X509 certificate store for HTTPS client, error: %s', e.message))
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def set_default_template
|
104
|
+
self.webhook_template = WebhookTemplate.find_by!(name: DEFAULT_PAYLOAD_TEMPLATE)
|
105
|
+
end
|
106
|
+
|
107
|
+
def variables(event_name, payload)
|
108
|
+
{
|
109
|
+
event_name: event_name,
|
110
|
+
object: payload[:object],
|
111
|
+
context: payload[:context],
|
112
|
+
payload: payload,
|
113
|
+
webhook_id: id
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
def render_source(source, event_name, payload)
|
118
|
+
scope = Foreman::Renderer.get_scope(
|
119
|
+
klass: ForemanWebhooks::Renderer::Scope::WebhookTemplate,
|
120
|
+
variables: variables(event_name, payload),
|
121
|
+
source: source
|
122
|
+
)
|
123
|
+
Foreman::Renderer.render(source, scope)
|
124
|
+
end
|
125
|
+
|
126
|
+
def rendered_payload(event_name, payload)
|
127
|
+
webhook_template.render(variables: variables(event_name, payload))
|
128
|
+
end
|
129
|
+
|
130
|
+
def rendered_headers(event_name, payload)
|
131
|
+
return nil if http_headers.empty?
|
132
|
+
|
133
|
+
source = Foreman::Renderer::Source::String.new(name: 'HTTP header template', content: http_headers)
|
134
|
+
render_source(source, event_name, payload)
|
135
|
+
end
|
136
|
+
|
137
|
+
def rendered_targed_url(event_name, payload)
|
138
|
+
source = Foreman::Renderer::Source::String.new(name: 'HTTP target URL template', content: target_url)
|
139
|
+
render_source(source, event_name, payload)
|
140
|
+
end
|
141
|
+
end
|