puppet_webhook 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.
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: []