puppet_webhook 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b4b775f68616833562447615df1985dbe31e30227493a4dc027a0d442e797f6d
4
+ data.tar.gz: f5c53e8e3f2a6ec6bc544f7cc97f5d41cc4af944290859b2286082f4f20ea2c9
5
+ SHA512:
6
+ metadata.gz: e2f4b7b73d988281ab0b7d081eb2d1e38f9b622ffe0aabfb903452eed7efe3dcf42ef892b9467ad575b14cdeb311a867bdff36480cf8de4b6b26e5644157055f
7
+ data.tar.gz: aa402a350c235665813ecc06c55b221afbb736a36caee827d732543c72920e0c34b096323041ba92c03c7b908cb89ff6f5a251c1bdfc5aa003d30230cd7e4244
data/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,209 @@
1
+ # Puppet Webhook Server
2
+
3
+ [![License](https://img.shields.io/github/license/voxpupuli/puppet_webhook.svg)](https://github.com/voxpupuli/puppet_webhook/blob/master/LICENSE)
4
+ [![Build Status](https://img.shields.io/travis/voxpupuli/puppet_webhook.svg)](https://travis-ci.org/voxpupuli/puppet_webhook)
5
+ [![Gem Version](https://img.shields.io/gem/v/puppet_webhook.svg)](https://rubygems.org/gems/puppet_webhook)
6
+ [![Gem Downloads](https://img.shields.io/gem/dt/puppet_webhook.svg)](https://rubygems.org/gems/puppet_webhook)
7
+ [![Coverage Status](https://coveralls.io/repos/github/voxpupuli/puppet_webhook/badge.svg?branch=master)](https://coveralls.io/github/voxpupuli/puppet_webhook?branch=master)
8
+ [![Dependency Status](https://gemnasium.com/badges/github.com/voxpupuli/puppet_webhook.svg)](https://gemnasium.com/github.com/voxpupuli/puppet_webhook)
9
+
10
+ ## What is puppet_webhook
11
+
12
+ puppet_webhook is a Sinatra-based application receiving REST-based calls to trigger Puppet and r10k-related tasks such as:
13
+
14
+ * Webhooks from Source Code systems to trigger r10k environment and module deploys
15
+ * REST calls from systems to trigger Puppet Decommissions such as:
16
+ * `puppet node clean`
17
+ * `puppet cert clean`
18
+ * `puppet cert revoke`
19
+ * etc.
20
+ * Send notifications via Slack
21
+
22
+ ## Prerequisites
23
+
24
+ * Ruby 2.1.9 or greater
25
+ * Puppet 4.7.1 or greater
26
+ * r10k gem
27
+ * MCollective and MCollective-r10k (Only for multi-master syncronization)
28
+ * Currently Mcollective-r10k is only available from [puppet-r10k](https://github.com/voxpupuli/puppet-r10k)
29
+
30
+ ## Installation
31
+
32
+ Currently the only supported installation method is via RubyGems.
33
+
34
+ `gem install puppet_webhook`
35
+
36
+ NOTE: RPM, DEB, and Arch packages are planned for future releases.
37
+
38
+ ## Usage
39
+
40
+ ### Running puppet_webhook
41
+
42
+ Once installed, you can run the application by simply executing the `puppet_webhook` binary.
43
+
44
+ This binary will default to using the bundled configuration files and run in a non-daemon mode. This mode is useful for debugging purposes, but it probably not ideal for production use.
45
+ You can also set the `server_type` option in `/etc/puppet_webhook/server.yml` to `daemon` to run the application in the background. By default the application will log to `/var/log/puppet_webhook/access.log`.
46
+
47
+ NOTE: During the Prerelease stage, the `/etc/puppet_webhook` and `/var/log/puppet_webhook` directories need to be manually created. This will be fixed in for General Availability.
48
+
49
+ ### Configuring puppet_webhook
50
+
51
+ Puppet_webhook also has several configuration options that can be configured to each user's needs.
52
+
53
+ The configuration is separated out into Server config (`server.yml`) and Application config (`app.yml`).
54
+ There are default configuration files included in the application's config directory. While these files are editable, it is preferable to create these config files in `/etc/puppet_webhook` to limit potential problems with package updates.
55
+ Any configuration option is placed in `/etc/puppet_webhook/server.yml` or `/etc/puppet_webhook/app.yml` will override the default config defined in `APPDIR/config/server.yml` and `APPDIR/config/app.yml`.
56
+
57
+ #### Configuration options
58
+
59
+ #### server.yml
60
+
61
+ #####`server_type`
62
+ Determines if the Webrick server should run in Simple or Daemon mode.
63
+ * Valid options: [ `simple`, `daemon` ].
64
+ * Default: `simple`
65
+
66
+ ##### `logfile`
67
+ Location to write the log file to.
68
+ * Default: `/var/log/puppet_webhook/access.log`
69
+
70
+ ##### `pidfile`
71
+ Location of the application's PID file
72
+ * Default: `/var/run/puppet_webhook/webhook.pid`
73
+
74
+ ##### `lockfile`
75
+ Location of the application's Lockfile
76
+ * Default: `/var/run/puppet_webhook/webhook.lock`
77
+
78
+ ##### `approot`
79
+ Location of the Root of the application's directory
80
+ * Default: `./`
81
+ NOTE: Currently unused
82
+
83
+ ##### `bind_address`
84
+ IPv4 Address to bind to.
85
+ * MUST BE VALID IPv4 ADDRESS.
86
+ * Default: `0.0.0.0`
87
+
88
+ ##### `port`
89
+ Port number to bind to.
90
+ * Valid options: Integer between `1024` and `65535`
91
+ * Default: `8088`
92
+
93
+ ##### `enable_ssl`
94
+ Whether or not to enable SSL communication.
95
+ * Valid options: [ `true`, `false` ]
96
+ * Default: `false`
97
+
98
+ ##### `verify_ssl`
99
+ Whether or not to verify the SSL CA/Peer on the certifcate. Set to false if using a self-signed certificate and the CA is not installed locally.
100
+ * Valid options: [ `true`, `false` ]
101
+ * Default: `false`
102
+
103
+ ##### `public_key_path`
104
+ Path to the public SSL certificate for puppet_webhook. REQUIRED IF `ssl_enable` IS SET TO `true`
105
+ * Default: ''
106
+
107
+ ##### `private_key_path`
108
+ Path to the SSL Private Key for puppet_webhook. REQUIRED IF `ssl_enable` IS SET TO `true`
109
+ * Default: ''
110
+
111
+ ##### `command_prefix`
112
+ Command to prefix the r10k and puppet commands with when executed.
113
+ * Default: 'umask 0022;'
114
+
115
+ #### app.yml
116
+
117
+ ##### `enable_mutex_lock`
118
+ Force all requests to syncronize on a mutex lock, ensuring that only a single request is processed at a time.
119
+ * Valid options: [ `true`, `false` ]
120
+ * Default: `false`
121
+
122
+ ##### `user`
123
+ User for which the sending application must authenticate with.
124
+ * Default: `puppet`
125
+
126
+ ##### `pass`
127
+ Password for which the sending application must authenticate with.
128
+ * Default: `puppet`
129
+
130
+ ##### `protected`
131
+ Whether or not to require authentication when sending to puppet_webhook.
132
+ * Valid options: [ `true`, `false` ]
133
+ * Default: `true`
134
+
135
+ ##### client_cfg
136
+ Mcollective client configuration file.
137
+ * Default: `/var/lib/peadmin/.mcollective`
138
+
139
+ ##### client_timeout
140
+ Mcollective client timeout in seconds.
141
+ * MUST BE A STRING
142
+ * Default: `"120"`
143
+
144
+ ##### use_mco_ruby
145
+ Whether or not to execute MCollective via Ruby Client Library or not. REQUIRES MCOLLECTIVE AND MCOLLECTIVE R10K!
146
+ * Valid options: [ `true`, `false` ]
147
+ * Default: `false`
148
+
149
+ ##### discovery_timeout
150
+ MCollective Ruby discovery timeout. REQUIRES `use_mco_ruby` TO BE `true`.
151
+ * Default: `'10'`
152
+
153
+ ##### use_mcollective
154
+ Whether or not to use MCollective CLI command. REQUIRES MCOLLECTIVE AND MCOLLECTIVE R10K.
155
+ * Valid options: [ `true`, `false` ]
156
+ * Default: `false`
157
+
158
+ ##### slack_webhook
159
+ Whether or not to use Slack Notifications.
160
+ * Valid options: [ `true`, `false` ]
161
+ * Default: `false`
162
+
163
+ ##### slack_channel
164
+ Slack channel to notify.
165
+ * Default: `'#default'`
166
+
167
+ ##### slack_user
168
+ Slack user to notify as.
169
+ * Default: `'r10k'`
170
+
171
+ ##### slack_proxy_url
172
+ The proxy URL for Slack if used.
173
+ * MUST BE A VALID URL.
174
+ * Default: `nil`
175
+
176
+ ##### default_branch
177
+ The default git branch to use with the r10k Control Repo.
178
+ * Default: `production`
179
+
180
+ ##### ignore_environment
181
+ An Array of environments for r10k to ignore during deployment.
182
+ * Default: []
183
+
184
+ ##### prefix
185
+ r10k Environment Prefix to use. When set to `repo`, `user`, or `command`, the prefix will be generated from the repo_name, repo_user, or `prefix_command`. Otherwise it will set the prefix to the passed string. `false` disables prefix.
186
+ * Valid Options: [ `repo`, `user`, `command`, `<String_value>`, `false` ]
187
+ * Default: `false`
188
+
189
+ ##### prefix_command
190
+ Command to execute that will generate an r10k environment prefix.
191
+ * Default: ''
192
+
193
+ ##### r10k_deploy_arguments
194
+ r10k command arguments to pass to the `r10k deploy environment` command.
195
+ * Default: `"-pv"`
196
+
197
+ ##### allow_uppercase
198
+ Whether or not to allow uppercase letters in environment names. If false, then puppet_webhook assumes environment names are downcase. If `true`, then puppet_webhook will normalize the environment name.
199
+ * Valid options: [ `true`, `false` ]
200
+ * Default: `true`
201
+
202
+ ##### github_secret
203
+ Used to verify the signature on a repo. Currently only supported for Github repos.
204
+ * MUST BE A VALID OPENSSL `sha1` HASH.
205
+ * Default: `nil`
206
+
207
+ ##### repository_events
208
+ Array of webhook events to ignore.
209
+ * Default: `nil`
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ require 'openssl'
3
+ require 'sinatra'
4
+ require 'sinatra/config_file'
5
+ require 'webrick'
6
+ require 'webrick/https'
7
+ require 'puppet_webhook'
8
+
9
+ config_file(File.join(__dir__, '..', 'config', 'server.yml'), '/etc/puppet_webhook/server.yml')
10
+
11
+ PIDFILE = settings.pidfile
12
+ LOCKFILE = settings.lockfile
13
+ APP_ROOT = settings.approot
14
+ COMMAND_PREFIX = settings.command_prefix
15
+ LOGGER = WEBrick::Log.new(settings.logfile, WEBrick::Log::DEBUG)
16
+
17
+ case settings.server_type
18
+ when 'simple'
19
+ server_type = WEBrick::SimpleServer
20
+ when 'daemon'
21
+ server_type = WEBrick::Daemon
22
+ end
23
+
24
+ opts = {
25
+ Host: settings.bind_address,
26
+ Port: settings.port,
27
+ Logger: LOGGER,
28
+ ServerType: server_type,
29
+ ServerSoftware: settings.server_software,
30
+ SSLEnable: settings.enable_ssl,
31
+ StartCallBack: proc { File.open(PIDFILE, 'w') { |f| f.write Process.pid } }
32
+ }
33
+
34
+ if settings.enable_ssl
35
+ opts[:SSLVerifyClient] = settings.verify_ssl
36
+ opts[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.open(settings.public_key_path).read)
37
+ opts[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(File.open(settings.private_key_path).read)
38
+ opts[:SSLCertName] = [['CN', WEBrick::Utils.getservername]]
39
+ end
40
+
41
+ Rack::Handler::WEBrick.run(PuppetWebhook, opts) do |server|
42
+ %i[INT TERM].each { |sig| trap(sig) { server.stop } }
43
+ end
data/config/app.yml ADDED
@@ -0,0 +1,21 @@
1
+ enable_mutex_lock: false
2
+ user: puppet
3
+ pass: puppet
4
+ protected: true
5
+ client_cfg: "/var/lib/peadmin/.mcollective"
6
+ client_timeout: "120"
7
+ use_mco_ruby: false
8
+ use_mcollective: false
9
+ slack_webhook: false
10
+ slack_channel: '#default'
11
+ slack_username: 'r10k'
12
+ slack_proxy_url: ~
13
+ default_branch: production
14
+ discovery_timeout: '10'
15
+ ignore_environments: []
16
+ prefix: ~
17
+ prefix_command: ''
18
+ r10k_deploy_arguments: "-pv"
19
+ allow_uppercase: true
20
+ github_secret: ~
21
+ repository_events: ~
data/config/server.yml ADDED
@@ -0,0 +1,13 @@
1
+ server_type: simple
2
+ logfile: /var/log/puppet_webhook/access.log
3
+ pidfile: /var/run/puppet_webhook/webhook.pid
4
+ lockfile: /var/run/puppet_webhook/webhook.lock
5
+ approot: './'
6
+ server_software: PuppetWebhook
7
+ bind_address: 0.0.0.0
8
+ port: 8088
9
+ enable_ssl: false
10
+ verify_ssl: false
11
+ public_key_path: ''
12
+ private_key_path: ''
13
+ command_prefix: 'umask 0022;'
@@ -0,0 +1,18 @@
1
+ require 'shellwords'
2
+
3
+ module DataParsers # rubocop:disable Style/Documentation
4
+ def sanitize_input(input_string)
5
+ sanitized = Shellwords.shellescape(input_string)
6
+ LOGGER.info("Module or Branch name #{sanitized} had to be escaped") unless input_string == sanitized
7
+ sanitized
8
+ end
9
+
10
+ def normalize(str)
11
+ settings.allow_uppercase ? str : str.downcase
12
+ end
13
+
14
+ def verify_signature(payload_body)
15
+ signature = 'sha1=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), settings.github_secret, payload_body)
16
+ throw(:halt, [500, "Signatures didn't match!\n"]) unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE'])
17
+ end
18
+ end
@@ -0,0 +1,50 @@
1
+ module Deployments # rubocop:disable Style/Documentation
2
+ def deploy(branch, deleted)
3
+ if settings.use_mco_ruby
4
+ result = mco(branch).first
5
+ raise result.results[:statusmsg] unless result.results[:statuscode].zero?
6
+
7
+ message = result.results[:statusmsg]
8
+ else
9
+ command = if settings.use_mcollective
10
+ "#{COMMAND_PREFIX} mco r10k deploy #{branch} #{settings.mco_arguments}"
11
+ else
12
+ # If you don't use mcollective then this hook needs to be running as r10k's user i.e. root
13
+ "#{COMMAND_PREFIX} r10k deploy environment #{branch} #{settings.r10k_deploy_arguments}"
14
+ end
15
+ message = run_command(command)
16
+ end
17
+ status_message = { status: :success, message: message.to_s, branch: branch, status_code: 200 }
18
+ LOGGER.info("message: #{message} branch: #{branch}")
19
+ unless deleted
20
+ generate_types(branch) if types?
21
+ end
22
+ notify_slack(status_message) if slack?
23
+ status_message.to_json
24
+ rescue StandardError => e
25
+ status_message = { status: :fail, message: e.message, trace: e.backtrace, branch: branch, status_code: 500 }
26
+ LOGGER.error("message: #{e.message} trace: #{e.backtrace}")
27
+ status 500
28
+ notify_slack(status_message) if slack?
29
+ status_message.to_json
30
+ end
31
+
32
+ def deploy_module(module_name)
33
+ command = if settings.use_mcollective
34
+ "#{COMMAND_PREFIX} mco r10k deploy_module #{module_name} #{settings.mco_arguments}"
35
+ else
36
+ "#{COMMAND_PREFIX} r10k deploy module #{module_name}"
37
+ end
38
+ message = run_command(command)
39
+ LOGGER.info("message: #{message} module_name: #{module_name}")
40
+ status_message = { status: :success, message: message.to_s, module_name: module_name, status_code: 200 }
41
+ notify_slack(status_message) if slack?
42
+ status_message.to_json
43
+ rescue StandardError => e
44
+ LOGGER.error("message: #{e.message} trace: #{e.backtrace}")
45
+ status 500
46
+ status_message = { status: :fail, message: e.message, trace: e.backtrace, module_name: module_name, status_code: 500 }
47
+ notify_slack(status_message) if slack?
48
+ status_message.to_json
49
+ end
50
+ end
@@ -0,0 +1,6 @@
1
+ require 'helpers/data_parsers'
2
+ require 'helpers/deployments'
3
+ require 'helpers/tasks'
4
+ PuppetWebhook.helpers DataParsers
5
+ PuppetWebhook.helpers Deployments
6
+ PuppetWebhook.helpers Tasks
@@ -0,0 +1,174 @@
1
+ require 'open3'
2
+ require 'slack-notifier'
3
+ require 'mcollective'
4
+ include MCollective::RPC
5
+
6
+ module Tasks # rubocop:disable Style/Documentation
7
+ def ignore_env?(env)
8
+ list = settings.ignore_environments
9
+ return false if list.nil? || list.empty?
10
+
11
+ list.each do |l|
12
+ # Even unquoted array elements wrapped by slashes becomes strings after YAML parsing
13
+ # So we need to convert it into Regexp manually
14
+ if l =~ %r{^/.+/$}
15
+ return true if env =~ Regexp.new(l[1..-2])
16
+ elsif env == 1
17
+ return true
18
+ end
19
+ end
20
+
21
+ false
22
+ end
23
+
24
+ # Check to see if this is an event we care about. Default to responding to all events
25
+ def ignore_event?
26
+ # Explicitly ignore Github ping events
27
+ return true if request.env['HTTP_X_GITHUB_EVENT'] == 'ping'
28
+
29
+ list = nil unless settings.repository_events
30
+ event = request.env['HTTP_X_GITHUB_EVENT']
31
+
32
+ # Negate this, because we should respond if any of these conditions are true
33
+ !(list.nil? || (list == event) || list.include?(event))
34
+ end
35
+
36
+ def run_prefix_command(payload)
37
+ IO.popen(settings.prefix_command, 'r+') do |io|
38
+ io.write payload.to_s
39
+ io.close_write
40
+ begin
41
+ io.readlines.first.chomp
42
+ rescue StandardError
43
+ ''
44
+ end
45
+ end
46
+ end
47
+
48
+ def run_command(command)
49
+ message = ''
50
+ File.open(LOCKFILE, 'w+') do |file|
51
+ # r10k has a small race condition which can cause failed deploys if two happen
52
+ # more or less simultaneously. To mitigate, we just lock on a file and wait for
53
+ # the other one to complete.
54
+ file.flock(File::LOCK_EX)
55
+
56
+ if Open3.respond_to?('capture3')
57
+ stdout, stderr, exit_status = Open3.capture3(command)
58
+ message = "triggered: #{command}\n#{stdout}\n#{stderr}"
59
+ else
60
+ message = "forked: #{command}"
61
+ Process.detach(fork { exec "#{command} &" })
62
+ exit_status = 0
63
+ end
64
+ raise "#{stdout}\n#{stderr}" if exit_status != 0
65
+ end
66
+ message
67
+ end
68
+
69
+ def generate_types(environment)
70
+ command = "#{COMMAND_PREFIX} /opt/puppetlabs/puppet/bin generate types --environment #{environment}"
71
+
72
+ message = run_command(command)
73
+ LOGGER.info("message: #{message} environment: #{environment}")
74
+ status_message = { status: :success, message: message.to_s, environment: environment, status_code: 200 }
75
+ notify_slack(status_message) if slack?
76
+ rescue StandardError => e
77
+ LOGGER.error("message: #{e.message} trace: #{e.backtrace}")
78
+ status_message = { status: :fail, message: e.message, trace: e.backtrace, environment: environment, status_code: 500 }
79
+ notify_slack(status_message) if slack?
80
+ end
81
+
82
+ def notify_slack(status_message)
83
+ return unless settings.slack_webhook
84
+
85
+ slack_channel = settings.slack_channel || '#default'
86
+ slack_user = settings.slack_username || 'r10k'
87
+
88
+ if settings.slack_proxy_url
89
+ uri = URI(settings.slack_proxy_url)
90
+ http_options = {
91
+ proxy_address: uri.hostname,
92
+ proxy_port: uri.port,
93
+ proxy_from_env: false
94
+ }
95
+ else
96
+ http_options = {}
97
+ end
98
+
99
+ notifier = Slack::Notifier.new settings.slack_webhook do
100
+ defaults channel: slack_channel,
101
+ username: slack_user,
102
+ icon_emoji: ':ocean:',
103
+ http_options: http_options
104
+ end
105
+
106
+ if status_message[:branch]
107
+ target = status_message[:branch]
108
+ elsif status_message[:module]
109
+ target = status_message[:module]
110
+ end
111
+
112
+ message = {
113
+ author: 'r10k for Puppet',
114
+ title: "r10k deployment of Puppet environment #{target}"
115
+ }
116
+
117
+ case status_message[:status_code]
118
+ when 200
119
+ message.merge!(
120
+ color: 'good',
121
+ text: "Successfully deployed #{target}",
122
+ fallback: "Successfully deployed #{target}"
123
+ )
124
+ when 500
125
+ message.merge!(
126
+ color: 'bad',
127
+ text: "Failed to deploy #{target}",
128
+ fallback: "Failed to deploy #{target}"
129
+ )
130
+ end
131
+
132
+ notifier.post text: message[:fallback], attachments: [message]
133
+ end
134
+
135
+ def slack?
136
+ return false if settings.slack_webhook.nil?
137
+ settings.slack_webhook
138
+ end
139
+
140
+ def types?
141
+ return false if settings.generate_tasks.nil?
142
+ settings.generate_tasks
143
+ end
144
+
145
+ def authorized?
146
+ # TODO: add token-based authentication?
147
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
148
+ @auth.provided? && @auth.basic? && @auth.credentials &&
149
+ @auth.credentials == [settings.user, settings.pass]
150
+ end
151
+
152
+ def verify_signature?
153
+ true unless settings.github_secret.nil?
154
+ end
155
+
156
+ def protected!
157
+ if authorized?
158
+ LOGGER.info("Authenticated as user #{settings.user} from IP #{request.ip}")
159
+ else
160
+ response['WWW-Authenticate'] = %(Basic realm="Restricted Area")
161
+ LOGGER.error("Authentication failure from IP #{request.ip}")
162
+ throw(:halt, [401, "Not authorized\n"])
163
+ end
164
+ end
165
+
166
+ def mco(branch)
167
+ options = MCollective::Util.default_options
168
+ options[:config] = settings.client_cfg
169
+ client = rpcclient('r10k', exit_on_failure: false, options: options)
170
+ client.discovery_timeout = settings.discovery_timeout
171
+ client.timeout = settings.client_timeout
172
+ client.send('deploy', environment: branch)
173
+ end
174
+ end
@@ -0,0 +1,128 @@
1
+ require 'rack/parser'
2
+ require 'json'
3
+
4
+ module Sinatra
5
+ module Parsers
6
+ class WebhookJsonParser # rubocop:disable Style/Documentation
7
+ def call(body)
8
+ @data = JSON.parse(body, quirks_mode: true)
9
+ @vcs = detect_vcs
10
+ {
11
+ branch: branch,
12
+ deleted: deleted?,
13
+ module_name: repo_name.sub(%r{^.*-}, ''),
14
+ repo_name: repo_name,
15
+ repo_user: repo_user
16
+ }.delete_if { |_k, v| v.nil? }
17
+ end
18
+
19
+ def detect_vcs
20
+ return 'github' if github_webhook?
21
+ return 'gitlab' if gitlab_webhook?
22
+ return 'stash' if stash_webhook?
23
+ return 'bitbucket' if bitbucket_webhook?
24
+ return 'tfs' if tfs_webhook?
25
+ raise StandardError, 'payload not recognised'
26
+ end
27
+
28
+ def github_webhook?
29
+ # https://developer.github.com/v3/activity/events/types/#pushevent
30
+ # X-GitHub-Event header is set, but not accessible here.
31
+ return false unless @data.key? 'repository'
32
+ return false unless @data['repository'].key? 'id'
33
+ return false unless @data['repository'].key? 'html_url'
34
+ return false unless @data['repository']['html_url'] =~ %r{github\.com}
35
+ true
36
+ end
37
+
38
+ def gitlab_webhook?
39
+ # https://docs.gitlab.com/ce/user/project/integrations/webhooks.html
40
+ # X-Gitlab-Event is set, but not accessible here.
41
+ return false unless @data.key? 'object_kind'
42
+ return false unless @data.key? 'ref'
43
+ true
44
+ end
45
+
46
+ # stash/bitbucket server
47
+ def stash_webhook?
48
+ # https://confluence.atlassian.com/bitbucketserver/post-service-webhook-for-bitbucket-server-776640367.html
49
+ return false unless @data.key? 'refChanges'
50
+ true
51
+ end
52
+
53
+ def bitbucket_webhook?
54
+ # https://confluence.atlassian.com/bitbucket/event-payloads-740262817.html
55
+ return false unless @data.key? 'actor'
56
+ return false unless @data.key? 'repository'
57
+ return false unless @data.key? 'push'
58
+ true
59
+ end
60
+
61
+ def tfs_webhook?
62
+ # https://docs.microsoft.com/en-us/vsts/service-hooks/services/webhooks
63
+ return false unless @data.key? 'resource'
64
+ return false unless @data.key? 'eventType'
65
+ true
66
+ end
67
+
68
+ def branch
69
+ case @vcs
70
+ when 'github'
71
+ if @data.key? 'ref'
72
+ @data['ref'].sub('refs/heads/', '')
73
+ else
74
+ @data['repository']['default_branch']
75
+ end
76
+ when 'gitlab'
77
+ @data['ref'].sub('refs/heads/', '')
78
+ when 'stash'
79
+ @data['refChanges'][0]['refId'].sub('refs/heads/', '')
80
+ when 'bitbucket'
81
+ return @data['push']['changes'][0]['new']['name'] unless deleted?
82
+ @data['push']['changes'][0]['old']['name']
83
+ when 'tfs'
84
+ @data['resource']['refUpdates'][0]['name'].sub('refs/heads/', '')
85
+ end
86
+ end
87
+
88
+ def deleted?
89
+ case @vcs
90
+ when 'github'
91
+ @data['deleted']
92
+ when 'gitlab'
93
+ @data['after'] == '0000000000000000000000000000000000000000'
94
+ when 'stash'
95
+ @data['refChanges'][0]['type'] == 'DELETE'
96
+ when 'bitbucket'
97
+ @data['push']['changes'][0]['closed']
98
+ when 'tfs'
99
+ @data['resource']['refUpdates'][0]['newObjectId'] == '0000000000000000000000000000000000000000'
100
+ else
101
+ false
102
+ end
103
+ end
104
+
105
+ def repo_name
106
+ if @vcs == 'gitlab'
107
+ @data['project']['name']
108
+ elsif @vcs == 'tfs'
109
+ @data['resource']['repository']['name']
110
+ else
111
+ @data['repository']['name']
112
+ end
113
+ end
114
+
115
+ def repo_user
116
+ # TODO: Clarify what repo_user actually is.
117
+ # github is currently using the repo's 'owner', gitlab is using the user who pushed.
118
+ case @vcs
119
+ when 'github'
120
+ @data['repository']['owner']['login']
121
+ when 'gitlab'
122
+ @data['user_username']
123
+ end
124
+ # TODO: Bitbucket, Stash/Bitbucket Server, TFS
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,153 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/config_file'
3
+ require 'json'
4
+ require 'cgi'
5
+ require 'parsers/webhook_json_parser'
6
+
7
+ class PuppetWebhook < Sinatra::Base # rubocop:disable Style/Documentation
8
+ set :root, File.dirname(__FILE__)
9
+ use Rack::Parser,
10
+ parsers: { 'application/json' => Sinatra::Parsers::WebhookJsonParser.new },
11
+ handlers: { 'application/json' => proc { |e, type| [400, { 'Content-Type' => type }, [{ error: e.to_s }.to_json]] } }
12
+ register Sinatra::ConfigFile
13
+
14
+ config_file(File.join(__dir__, '..', 'config', 'app.yml'), '/etc/puppet_webhook/app.yml')
15
+
16
+ set :static, false
17
+ set :lock, true if settings.enable_mutex_lock
18
+
19
+ require 'helpers/init'
20
+
21
+ get '/' do
22
+ raise Sinatra::NotFound
23
+ end
24
+
25
+ get '/heartbeat' do
26
+ return 200, { status: :success, message: 'running' }.to_json
27
+ end
28
+
29
+ # TODO: Move examples into the README.md
30
+ # Simulate a github post:
31
+ # curl -X POST \
32
+ # -H "Content-Type: application/json" \
33
+ # -d '{ \
34
+ # "repository": { \
35
+ # "id": 12345, \
36
+ # "name": "puppetlabs-stdlib", \
37
+ # "html_url": "http://github.com", \
38
+ # "owner": {\
39
+ # "login": "foo"
40
+ # } \
41
+ # } \
42
+ # }' \
43
+ # 'https://puppet:puppet@localhost:8088/module' -k -q
44
+ #
45
+ # Simulate a BitBucket post:
46
+ # curl -X POST \
47
+ # -H "Content-Type: application/json" \
48
+ # -d '{ \
49
+ # "repository": { \
50
+ # "full_name": "puppetlabs/puppetlabs-stdlib", \
51
+ # "name": "PuppetLabs : StdLib"
52
+ # } }' \
53
+ # 'https://puppet:puppet@localhost:8088/module' -k -q
54
+ #
55
+ # This example shows that, unlike github, BitBucket allows special characters
56
+ # in repository names but translates it to generate a full_name which
57
+ # is used in the repository URL and is most useful for this webhook handler.
58
+ post '/module' do
59
+ protected! if settings.protected
60
+ request.body.rewind # in case someone has already read it
61
+
62
+ # Short circuit if we're ignoring this event
63
+ return 200 if ignore_event?
64
+
65
+ # TODO: Move these two lines of code into the parser
66
+ decoded = request.body.read
67
+ verify_signature(decoded) if verify_signature?
68
+
69
+ module_name = params['module_name']
70
+
71
+ module_name = sanitize_input(module_name)
72
+ LOGGER.info("Deploying module #{module_name}")
73
+ deploy_module(module_name)
74
+ end
75
+
76
+ # Simulate a github post:
77
+ # curl -d '{ "ref": "refs/heads/production" }' -H "Accept: application/json" 'https://puppet:puppet@localhost:8088/payload' -k -q
78
+ #
79
+ # If using stash look at the stash_mco.rb script included here.
80
+ # It will filter the stash post and make it look like a github post.
81
+ #
82
+ # Simulate a Gitorious post:
83
+ # curl -X POST -d '%7b%22ref%22%3a%22master%22%7d' 'http://puppet:puppet@localhost:8088/payload' -q
84
+ # Yes, Gitorious does not support https...
85
+ #
86
+ # Simulate a BitBucket post:
87
+ # curl -X POST -d '{ "push": { "changes": [ { "new": { "name": "production" } } ] } }' \
88
+ # 'https://puppet:puppet@localhost:8088/payload' -k -q
89
+
90
+ post '/payload' do # rubocop:disable Metrics/BlockLength
91
+ LOGGER.info "params = #{params}"
92
+ protected! if settings.protected
93
+ request.body.rewind # in case someone already read it
94
+
95
+ # Short circuit if we're ignoring this event
96
+ return 200 if ignore_event?
97
+
98
+ # Check if content type is x-www-form-urlencoded
99
+ decoded = if request.content_type.to_s.casecmp('application/x-www-form-urlencoded').zero?
100
+ CGI.unescape(request.body.read).gsub(%r{^payload\=}, '')
101
+ else
102
+ request.body.read
103
+ end
104
+ verify_signature(decoded) if verify_signature?
105
+ data = JSON.parse(decoded, quirks_mode: true)
106
+
107
+ # Iterate the data structure to determine what's should be deployed
108
+ branch = params['branch']
109
+
110
+ # If prefix is enabled in our config file, determine what the prefix should be
111
+ prefix = case settings.prefix
112
+ when :repo
113
+ params['repo_name']
114
+ when :user
115
+ params['repo_user']
116
+ when :command, TrueClass
117
+ run_prefix_command(data.to_json)
118
+ when String
119
+ settings.prefix
120
+ end
121
+
122
+ # When a branch is being deleted, a deploy against it will result in a failure, as it no longer exists.
123
+ # Instead, deploy the default branch, which will purge deleted branches per the user's configuration
124
+ deleted = params['deleted']
125
+
126
+ branch = if deleted
127
+ settings.default_branch
128
+ else
129
+ sanitize_input(branch)
130
+ end
131
+
132
+ # r10k doesn't yet know how to deploy all branches from a single source.
133
+ # The best we can do is just deploy all environments by passing nil to
134
+ # deploy() if we don't know the correct branch.
135
+ env = if prefix.nil? || prefix.empty? || branch.nil? || branch.empty?
136
+ normalize(branch)
137
+ else
138
+ normalize("#{prefix}_#{branch}")
139
+ end
140
+
141
+ if ignore_env?(env)
142
+ LOGGER.info("Skipping deployment of environment #{env} according to ignore_environments configuration parameter")
143
+ return 200
144
+ else
145
+ LOGGER.info("Deploying environment #{env}")
146
+ deploy(env, deleted)
147
+ end
148
+ end
149
+
150
+ not_found do
151
+ halt 404, "You shall not pass! (page not found)\n"
152
+ end
153
+ end
metadata ADDED
@@ -0,0 +1,252 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puppet_webhook
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Vox Pupuli
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-11-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mcollective-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack-parser
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sinatra-contrib
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: slack-notifier
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webrick
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: coveralls
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: github_changelog_generator
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rack-test
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rake
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rspec
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rubocop
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: simplecov-console
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ description:
210
+ email: voxpupuli@groups.io
211
+ executables:
212
+ - puppet_webhook
213
+ extensions: []
214
+ extra_rdoc_files: []
215
+ files:
216
+ - LICENSE
217
+ - README.md
218
+ - bin/puppet_webhook
219
+ - config/app.yml
220
+ - config/server.yml
221
+ - lib/helpers/data_parsers.rb
222
+ - lib/helpers/deployments.rb
223
+ - lib/helpers/init.rb
224
+ - lib/helpers/tasks.rb
225
+ - lib/parsers/webhook_json_parser.rb
226
+ - lib/puppet_webhook.rb
227
+ homepage: https://github.com/voxpupuli/puppet_webhook
228
+ licenses:
229
+ - apache
230
+ metadata: {}
231
+ post_install_message:
232
+ rdoc_options: []
233
+ require_paths:
234
+ - lib
235
+ - config
236
+ required_ruby_version: !ruby/object:Gem::Requirement
237
+ requirements:
238
+ - - ">="
239
+ - !ruby/object:Gem::Version
240
+ version: 2.1.9
241
+ required_rubygems_version: !ruby/object:Gem::Requirement
242
+ requirements:
243
+ - - ">="
244
+ - !ruby/object:Gem::Version
245
+ version: '0'
246
+ requirements: []
247
+ rubyforge_project:
248
+ rubygems_version: 2.7.2
249
+ signing_key:
250
+ specification_version: 4
251
+ summary: Sinatra Webhook Server for Puppet/R10K
252
+ test_files: []