foreman_webhooks 0.0.1

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 (110) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +619 -0
  3. data/README.md +30 -0
  4. data/Rakefile +49 -0
  5. data/app/controllers/api/v2/webhook_templates_controller.rb +110 -0
  6. data/app/controllers/api/v2/webhooks_controller.rb +61 -0
  7. data/app/controllers/concerns/foreman_webhooks/controller/parameters/webhook.rb +34 -0
  8. data/app/controllers/concerns/foreman_webhooks/controller/parameters/webhook_template.rb +34 -0
  9. data/app/controllers/webhook_templates_controller.rb +5 -0
  10. data/app/controllers/webhooks_controller.rb +39 -0
  11. data/app/jobs/foreman_webhooks/deliver_webhook_job.rb +27 -0
  12. data/app/lib/foreman_webhooks/renderer/scope/webhook_template.rb +36 -0
  13. data/app/models/webhook.rb +141 -0
  14. data/app/models/webhook_template.rb +59 -0
  15. data/app/services/foreman_webhooks/webhook_service.rb +111 -0
  16. data/app/subscribers/foreman_webhooks/event_subscriber.rb +9 -0
  17. data/app/views/api/v2/webhook_templates/base.json.rabl +5 -0
  18. data/app/views/api/v2/webhook_templates/create.json.rabl +5 -0
  19. data/app/views/api/v2/webhook_templates/index.json.rabl +5 -0
  20. data/app/views/api/v2/webhook_templates/main.json.rabl +7 -0
  21. data/app/views/api/v2/webhook_templates/show.json.rabl +11 -0
  22. data/app/views/api/v2/webhook_templates/update.json.rabl +5 -0
  23. data/app/views/api/v2/webhooks/base.json.rabl +5 -0
  24. data/app/views/api/v2/webhooks/create.json.rabl +5 -0
  25. data/app/views/api/v2/webhooks/index.json.rabl +5 -0
  26. data/app/views/api/v2/webhooks/main.json.rabl +8 -0
  27. data/app/views/api/v2/webhooks/show.json.rabl +17 -0
  28. data/app/views/api/v2/webhooks/update.json.rabl +5 -0
  29. data/app/views/foreman_webhooks/webhook_templates/ansible_tower_-_host_in_inventory.erb +20 -0
  30. data/app/views/foreman_webhooks/webhook_templates/empty_payload.erb +6 -0
  31. data/app/views/foreman_webhooks/webhook_templates/webhook_template_-_payload_default.erb +11 -0
  32. data/app/views/webhook_templates/_alerts.html.erb +3 -0
  33. data/app/views/webhook_templates/_custom_tab_headers.html.erb +1 -0
  34. data/app/views/webhook_templates/_custom_tabs.html.erb +5 -0
  35. data/app/views/webhook_templates/edit.html.erb +3 -0
  36. data/app/views/webhook_templates/index.html.erb +29 -0
  37. data/app/views/webhook_templates/new.html.erb +3 -0
  38. data/app/views/webhooks/_form.html.erb +37 -0
  39. data/app/views/webhooks/_templates.html.erb +5 -0
  40. data/app/views/webhooks/edit.html.erb +3 -0
  41. data/app/views/webhooks/new.html.erb +3 -0
  42. data/config/routes.rb +45 -0
  43. data/db/migrate/20191016100128_create_webhook_targets.rb +13 -0
  44. data/db/migrate/20200831194208_rename_webhook_targets_to_webhooks.rb +7 -0
  45. data/db/migrate/20200831194514_add_template_to_webhooks.rb +7 -0
  46. data/db/migrate/20200907232758_rename_webhook_permissions.rb +22 -0
  47. data/db/migrate/20200908004234_add_columns_to_webhooks.rb +13 -0
  48. data/db/migrate/20201014115147_rename_ca_file_column.rb +7 -0
  49. data/db/migrate/20201109135301_add_http_headers.rb +8 -0
  50. data/db/seeds.d/95_webhook_templates.rb +7 -0
  51. data/lib/foreman_webhooks.rb +6 -0
  52. data/lib/foreman_webhooks/engine.rb +80 -0
  53. data/lib/foreman_webhooks/version.rb +5 -0
  54. data/lib/tasks/foreman_webhooks_tasks.rake +47 -0
  55. data/package.json +45 -0
  56. data/test/controllers/api/v2/webhook_templates_controller_test.rb +200 -0
  57. data/test/controllers/api/v2/webhooks_controller_test.rb +108 -0
  58. data/test/factories/webhook.rb +20 -0
  59. data/test/factories/webhook_target.rb +9 -0
  60. data/test/factories/webhook_template.rb +16 -0
  61. data/test/jobs/foreman_webhooks/deliver_webhook_job_test.rb +17 -0
  62. data/test/models/webhook_test.rb +38 -0
  63. data/test/test_plugin_helper.rb +8 -0
  64. data/test/unit/foreman_webhooks/webhook_service_test.rb +53 -0
  65. data/webpack/ForemanWebhooks/Routes/ForemanWebhooksRoutes.js +12 -0
  66. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/EmptyWebhooksTable/index.js +29 -0
  67. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhookDeleteModal.js +43 -0
  68. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/EnabledCell.js +16 -0
  69. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/__tests__/__snapshots__/enabledCellFormatter.test.js.snap +7 -0
  70. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/__tests__/enabledCellFormatter.test.js +7 -0
  71. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/enabledCellFormatter.js +6 -0
  72. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/Formatters/index.js +1 -0
  73. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/__tests__/EnabledCell.test.js +14 -0
  74. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/Components/__tests__/__snapshots__/EnabledCell.test.js.snap +17 -0
  75. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/WebhooksTable.js +75 -0
  76. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/WebhooksTableSchema.js +41 -0
  77. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/__tests__/WebhooksTable.test.js +57 -0
  78. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/__tests__/__snapshots__/WebhooksTable.test.js.snap +115 -0
  79. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/Components/WebhooksTable/index.js +25 -0
  80. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/WebhooksIndexPage.js +87 -0
  81. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/__tests__/WebhooksIndexPage.fixtures.js +74 -0
  82. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/__tests__/WebhooksIndexPage.test.js +21 -0
  83. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/__tests__/__snapshots__/WebhooksIndexPage.test.js.snap +68 -0
  84. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksIndexPage/index.js +51 -0
  85. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksPageActions.js +51 -0
  86. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksPageHelpers.js +29 -0
  87. data/webpack/ForemanWebhooks/Routes/Webhooks/WebhooksPageSelectors.js +85 -0
  88. data/webpack/ForemanWebhooks/Routes/Webhooks/__tests__/WebhooksPageHelpers.test.js +20 -0
  89. data/webpack/ForemanWebhooks/Routes/Webhooks/__tests__/WebhooksPageSelectors.test.js +45 -0
  90. data/webpack/ForemanWebhooks/Routes/Webhooks/__tests__/__snapshots__/WebhooksPageSelectors.test.js.snap +50 -0
  91. data/webpack/ForemanWebhooks/Routes/Webhooks/constants.js +13 -0
  92. data/webpack/__mocks__/foremanReact/common/HOC.js +2 -0
  93. data/webpack/__mocks__/foremanReact/common/I18n.js +7 -0
  94. data/webpack/__mocks__/foremanReact/common/helpers.js +7 -0
  95. data/webpack/__mocks__/foremanReact/common/urlHelpers.js +1 -0
  96. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js +2 -0
  97. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalHooks.js +10 -0
  98. data/webpack/__mocks__/foremanReact/components/ForemanModal/index.js +18 -0
  99. data/webpack/__mocks__/foremanReact/components/Layout/LayoutActions.js +2 -0
  100. data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +2 -0
  101. data/webpack/__mocks__/foremanReact/components/common/EmptyState.js +5 -0
  102. data/webpack/__mocks__/foremanReact/components/common/table.js +5 -0
  103. data/webpack/__mocks__/foremanReact/constants.js +24 -0
  104. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +6 -0
  105. data/webpack/__mocks__/foremanReact/redux/API/index.js +10 -0
  106. data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +8 -0
  107. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
  108. data/webpack/index.js +0 -0
  109. data/webpack/routes_index.js +4 -0
  110. metadata +195 -0
@@ -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
+
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class WebhookTemplatesController < TemplatesController
4
+ include ForemanWebhooks::Controller::Parameters::WebhookTemplate
5
+ 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