kitchen_hooks 1.8.11 → 2.0.0
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 +4 -4
- data/.ruby-version +1 -1
- data/Readme.md +2 -1
- data/VERSION +1 -1
- data/etc/commit-constraint.json +1 -1
- data/etc/config.json +1 -1
- data/kitchen_hooks.gemspec +2 -2
- data/lib/kitchen_hooks/app.rb +17 -3
- data/lib/kitchen_hooks/helpers.rb +144 -15
- data/tasks/package/_package.sh +1 -1
- data/tasks/run/Dockerfile +13 -0
- data/tasks/run/run.sh +20 -0
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5575ea1aa7831c073d5d219133294149cbf061b0
|
4
|
+
data.tar.gz: 2918acee2bf88ef5ed323d3ce4d7ca45c04d5f16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 850b070cfc0efde41badfce1928e9d40593870448246a91e54271cd6ee467639ad31be6dbfaceee3a2ac374db4f4f3ffc8c52662e1b7f848e2917cbb9b425bd6
|
7
|
+
data.tar.gz: 590daa062aed047b1419ccbb7f6bc681008bea246e5d5f975820a2256b01bccc29aa298a2fd83fdfb240fd7268e4fbba212bc5759eed691a68638e9917d8d6a8
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.3.0
|
data/Readme.md
CHANGED
@@ -99,6 +99,7 @@ The configuration file is just JSON. Hopefully it's obvious:
|
|
99
99
|
"room": "test",
|
100
100
|
"token": "your_v1_api_token"
|
101
101
|
},
|
102
|
+
"git_protocol": "daemon",
|
102
103
|
"knives": {
|
103
104
|
"user": "~/.chef/knife.rb",
|
104
105
|
"system": "/etc/chef/knife.rb",
|
@@ -169,4 +170,4 @@ The `server` command also exposes some options for Sinatra configuration. See
|
|
169
170
|
|
170
171
|
#### 1.0
|
171
172
|
|
172
|
-
* Initial release. Gem structure in place, but lacking functionaily
|
173
|
+
* Initial release. Gem structure in place, but lacking functionaily
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0
|
data/etc/commit-constraint.json
CHANGED
data/etc/config.json
CHANGED
data/kitchen_hooks.gemspec
CHANGED
@@ -17,12 +17,12 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.add_runtime_dependency 'hipchat', '~> 1.4.0'
|
18
18
|
s.add_runtime_dependency 'daybreak', '~> 0.3'
|
19
19
|
s.add_runtime_dependency 'retryable', '~> 2'
|
20
|
-
s.add_runtime_dependency 'berkshelf', '
|
20
|
+
s.add_runtime_dependency 'berkshelf', '~> 4'
|
21
21
|
s.add_runtime_dependency 'thor', '~> 0'
|
22
22
|
s.add_runtime_dependency 'git', '~> 1.2'
|
23
23
|
s.add_runtime_dependency 'sinatra', '~> 1.4'
|
24
24
|
s.add_runtime_dependency 'ridley', '~> 4.1'
|
25
|
-
s.add_runtime_dependency 'chef', '~>
|
25
|
+
s.add_runtime_dependency 'chef', '~> 12'
|
26
26
|
s.add_runtime_dependency 'faraday', '= 0.9.1'
|
27
27
|
s.add_runtime_dependency 'eventmachine', '= 1.0.4'
|
28
28
|
s.add_runtime_dependency 'thin', '= 1.6.3'
|
data/lib/kitchen_hooks/app.rb
CHANGED
@@ -10,7 +10,6 @@ require_relative 'helpers'
|
|
10
10
|
require_relative 'metadata'
|
11
11
|
|
12
12
|
|
13
|
-
|
14
13
|
module KitchenHooks
|
15
14
|
class App < Sinatra::Application
|
16
15
|
set :root, File.join(KitchenHooks::ROOT, 'web')
|
@@ -65,6 +64,7 @@ module KitchenHooks
|
|
65
64
|
@@hipchat_nick = config['hipchat']['nick'] || raise('No HipChat "nick" provided')
|
66
65
|
@@hipchat_room = config['hipchat']['room'] || raise('No HipChat "room" provided')
|
67
66
|
end
|
67
|
+
@@git_protocol = config.fetch('git_protocol', 'daemon')
|
68
68
|
@@knives = config['knives'].map do |_, knife|
|
69
69
|
Pathname.new(knife).expand_path.realpath.to_s
|
70
70
|
end
|
@@ -102,6 +102,7 @@ module KitchenHooks
|
|
102
102
|
post '/' do
|
103
103
|
request.body.rewind
|
104
104
|
event = JSON::parse request.body.read rescue nil
|
105
|
+
event['repository']['protocol'] = @@git_protocol
|
105
106
|
@@backlog.push event
|
106
107
|
end
|
107
108
|
|
@@ -177,6 +178,8 @@ module KitchenHooks
|
|
177
178
|
|
178
179
|
|
179
180
|
def self.process event
|
181
|
+
Thread.abort_on_exception = true
|
182
|
+
|
180
183
|
if event.nil? # JSON parse failed
|
181
184
|
mark event, 'failure', 'Could not parse WebHook payload'
|
182
185
|
return
|
@@ -191,8 +194,19 @@ module KitchenHooks
|
|
191
194
|
mark event, 'kitchen upload', possible_error
|
192
195
|
end
|
193
196
|
|
197
|
+
if commit_to_data_bags?(event) ||
|
198
|
+
commit_to_environments?(event) ||
|
199
|
+
commit_to_roles?(event)
|
200
|
+
possible_error = begin
|
201
|
+
perform_upload_from_file event, knives
|
202
|
+
rescue Exception => e
|
203
|
+
report_error e, 'Could not perform upload from files: <i>%s</i>' % e.message.lines.first
|
204
|
+
end
|
205
|
+
mark event, 'upload from files', possible_error
|
206
|
+
end
|
207
|
+
|
194
208
|
if tagged_commit_to_cookbook?(event) &&
|
195
|
-
tag_name(event) =~ /^v
|
209
|
+
tag_name(event) =~ /^v?\d+/ # Cookbooks tagged with a version
|
196
210
|
possible_error = begin
|
197
211
|
perform_cookbook_upload event, knives
|
198
212
|
rescue Exception => e
|
@@ -212,4 +226,4 @@ module KitchenHooks
|
|
212
226
|
end
|
213
227
|
end
|
214
228
|
end
|
215
|
-
end
|
229
|
+
end
|
@@ -17,7 +17,6 @@ Celluloid.logger = nil
|
|
17
17
|
Berkshelf.logger = Logger.new $stdout
|
18
18
|
|
19
19
|
|
20
|
-
|
21
20
|
module KitchenHooks
|
22
21
|
class App < Sinatra::Application
|
23
22
|
|
@@ -28,6 +27,7 @@ module KitchenHooks
|
|
28
27
|
"#{n} #{plural}"
|
29
28
|
end
|
30
29
|
|
30
|
+
|
31
31
|
# http://stackoverflow.com/questions/4136248/how-to-generate-a-human-readable-time-range-using-ruby-on-rails
|
32
32
|
def self.humanize_seconds secs
|
33
33
|
[
|
@@ -53,7 +53,6 @@ module KitchenHooks
|
|
53
53
|
end
|
54
54
|
|
55
55
|
|
56
|
-
|
57
56
|
def self.perform_constraint_application event, knives
|
58
57
|
$stdout.puts 'started perform_constraint_application event=%s, knives=%s' % [
|
59
58
|
event['after'], knives.inspect
|
@@ -121,7 +120,11 @@ module KitchenHooks
|
|
121
120
|
|
122
121
|
tmp_clone event, :tagged_commit do |clone|
|
123
122
|
tagged_version = tag_name(event).delete('v')
|
124
|
-
|
123
|
+
if File.exist? File.join(clone, 'VERSION')
|
124
|
+
cookbook_version = File.read(File.join(clone, 'VERSION')).strip
|
125
|
+
else
|
126
|
+
cookbook_version = File.foreach(File.join(clone, 'metadata.rb')).grep(/version/)[0][/\"(.*)\"/,1]
|
127
|
+
end
|
125
128
|
unless tagged_version == cookbook_version
|
126
129
|
raise 'Tagged version does not match cookbook version'
|
127
130
|
end
|
@@ -167,6 +170,31 @@ module KitchenHooks
|
|
167
170
|
end
|
168
171
|
|
169
172
|
|
173
|
+
def self.perform_upload_from_file event, knives
|
174
|
+
return false unless commit_to_master?(event)
|
175
|
+
$stdout.puts 'started perform_upload_from_file event=%s, knives=%s' % [
|
176
|
+
event['after'], knives.inspect
|
177
|
+
]
|
178
|
+
|
179
|
+
tmp_clone event, :latest_commit do |clone|
|
180
|
+
Dir.chdir clone do
|
181
|
+
if commit_to_data_bags?(event)
|
182
|
+
data_bag_from_file repo_name(event), files_pushed(event), knives
|
183
|
+
end
|
184
|
+
if commit_to_environments?(event)
|
185
|
+
environment_from_file files_pushed(event), knives
|
186
|
+
end
|
187
|
+
if commit_to_roles?(event)
|
188
|
+
role_from_file files_pushed(event), knives
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
$stdout.puts "finished perform_upload_from_file: #{event['after']}"
|
194
|
+
return # no error
|
195
|
+
end
|
196
|
+
|
197
|
+
|
170
198
|
|
171
199
|
def self.kitchen_upload knives
|
172
200
|
if Dir.exist? 'data_bags'
|
@@ -192,8 +220,8 @@ module KitchenHooks
|
|
192
220
|
knives.peach do |k|
|
193
221
|
begin
|
194
222
|
Dir['environments/*'].each do |e|
|
195
|
-
|
196
|
-
|
223
|
+
# Can't use the default logic, as we maintain our own pins
|
224
|
+
upload_environment e, k
|
197
225
|
end
|
198
226
|
rescue
|
199
227
|
raise 'Could not upload environments'
|
@@ -203,6 +231,47 @@ module KitchenHooks
|
|
203
231
|
end
|
204
232
|
|
205
233
|
|
234
|
+
def self.data_bag_from_file data_bag, items, knives
|
235
|
+
$stdout.puts 'Uploading data_bags'
|
236
|
+
begin
|
237
|
+
items.each do |item|
|
238
|
+
# Try to guess if there is one repo per data bag or
|
239
|
+
# all data bags are in the sub-folders in the same repo.
|
240
|
+
if item.split('/').length > 1
|
241
|
+
data_bag = item.split('/')[-2]
|
242
|
+
end
|
243
|
+
with_each_knife_do 'data bag from file ' + data_bag + ' ' + item, knives
|
244
|
+
end
|
245
|
+
rescue
|
246
|
+
raise 'Could not upload data bags'
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
def self.role_from_file roles, knives
|
252
|
+
$stdout.puts 'Uploading roles'
|
253
|
+
begin
|
254
|
+
roles.each do |role|
|
255
|
+
with_each_knife_do 'role from file ' + role, knives
|
256
|
+
end
|
257
|
+
rescue
|
258
|
+
raise 'Could not upload roles'
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
def self.environment_from_file environments, knives
|
264
|
+
$stdout.puts 'Uploading environments'
|
265
|
+
begin
|
266
|
+
environments.each do |environment|
|
267
|
+
with_each_knife_do 'environment from file ' + environment, knives
|
268
|
+
end
|
269
|
+
rescue
|
270
|
+
raise 'Could not upload environments'
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
|
206
275
|
def self.berkshelf_config knife
|
207
276
|
ridley = Ridley::from_chef_config knife, ssl: { verify: false }
|
208
277
|
config = {
|
@@ -230,8 +299,8 @@ module KitchenHooks
|
|
230
299
|
env_git_work_tree = ENV.delete 'GIT_WORK_TREE'
|
231
300
|
|
232
301
|
knife_args = if knife
|
233
|
-
|
234
|
-
|
302
|
+
'--config %s' % Shellwords::escape(berkshelf_config knife)
|
303
|
+
end
|
235
304
|
|
236
305
|
cmd = 'berks install --debug --berksfile %s %s 2>&1' % [
|
237
306
|
Shellwords::escape(berksfile), knife_args
|
@@ -286,7 +355,14 @@ module KitchenHooks
|
|
286
355
|
dir = File::join root, Time.now.to_f.to_s, cookbook_name(event)
|
287
356
|
FileUtils.mkdir_p dir
|
288
357
|
|
289
|
-
|
358
|
+
git_protocol = event['repository']['protocol']
|
359
|
+
if git_protocol == 'daemon'
|
360
|
+
git_clone_url = git_daemon_style_url(event)
|
361
|
+
else
|
362
|
+
git_clone_url = event['repository']["git_#{git_protocol}_url"]
|
363
|
+
end
|
364
|
+
|
365
|
+
repo = Git.clone git_clone_url, dir, log: $stdout
|
290
366
|
|
291
367
|
commit = self.send(commit_method, event)
|
292
368
|
|
@@ -428,6 +504,8 @@ module KitchenHooks
|
|
428
504
|
%Q| <i>#{author(event)}</i> constrained <a href="#{gitlab_tag_url(event)}">#{tag_name(event)}</a> with <a href="#{gitlab_url(event)}">#{cookbook_name(event)}</a> #{version_link event} |
|
429
505
|
when 'release'
|
430
506
|
%Q| Kitchen Hooks <b>v#{event}</b> released! |
|
507
|
+
when 'upload from files'
|
508
|
+
%Q| <i>#{author(event)}</i> uploaded <a href="#{gitlab_url(event)}">files</a> to chef |
|
431
509
|
else
|
432
510
|
raise entry.inspect
|
433
511
|
end.strip
|
@@ -440,17 +518,27 @@ module KitchenHooks
|
|
440
518
|
return if event.nil?
|
441
519
|
%Q|
|
442
520
|
<i>#{author(event)}</i> pushed #{push_details(event)}
|
443
|
-
|
521
|
+
|.strip
|
444
522
|
end
|
445
523
|
|
446
524
|
|
447
525
|
def push_details e ; App.push_details e end
|
448
526
|
|
527
|
+
|
449
528
|
def self.push_details event
|
450
529
|
return if event.nil?
|
451
530
|
%Q|
|
452
531
|
<a href="#{gitlab_url(event)}">#{event['after']}</a> to <a href="#{repo_url(event)}">#{repo_name(event)}</a>
|
453
|
-
|
532
|
+
|.strip
|
533
|
+
end
|
534
|
+
|
535
|
+
|
536
|
+
def self.files_pushed event
|
537
|
+
files = []
|
538
|
+
event.fetch('commits').each do |commit|
|
539
|
+
files << commit.select { |k,v| k =~ /(added|modified)/ }.values.flatten
|
540
|
+
end
|
541
|
+
files.flatten.uniq
|
454
542
|
end
|
455
543
|
|
456
544
|
|
@@ -464,13 +552,37 @@ module KitchenHooks
|
|
464
552
|
end
|
465
553
|
|
466
554
|
|
555
|
+
def self.repo_namespace event
|
556
|
+
event['project']['path_with_namespace'].split('/')[0] rescue nil
|
557
|
+
end
|
558
|
+
|
467
559
|
def self.cookbook_name event
|
468
560
|
repo_name(event).sub(/^(app|base|realm|fork)_/, 'bjn_')
|
469
561
|
end
|
470
562
|
|
471
563
|
|
472
564
|
def self.cookbook_repo? event
|
473
|
-
repo_name(event) =~ /^(app|base|realm|fork)_/
|
565
|
+
repo_name(event) =~ /^(app|base|realm|fork)_/ ||
|
566
|
+
repo_name(event) =~ /cookbook/ ||
|
567
|
+
repo_namespace(event) =~ /cookbook/
|
568
|
+
end
|
569
|
+
|
570
|
+
|
571
|
+
def self.data_bag_repo? event
|
572
|
+
repo_name(event) =~ /data.*bag/ ||
|
573
|
+
repo_namespace(event) =~ /data.*bag/
|
574
|
+
end
|
575
|
+
|
576
|
+
|
577
|
+
def self.environment_repo? event
|
578
|
+
repo_name(event) =~ /environment/ ||
|
579
|
+
repo_namespace(event) =~ /environment/
|
580
|
+
end
|
581
|
+
|
582
|
+
|
583
|
+
def self.role_repo? event
|
584
|
+
repo_name(event) =~ /role/ ||
|
585
|
+
repo_namespace(event) =~ /role/
|
474
586
|
end
|
475
587
|
|
476
588
|
|
@@ -527,6 +639,21 @@ module KitchenHooks
|
|
527
639
|
end
|
528
640
|
|
529
641
|
|
642
|
+
def self.commit_to_roles? event
|
643
|
+
role_repo?(event) && not_deleted?(event)
|
644
|
+
end
|
645
|
+
|
646
|
+
|
647
|
+
def self.commit_to_environments? event
|
648
|
+
environment_repo?(event) && not_deleted?(event)
|
649
|
+
end
|
650
|
+
|
651
|
+
|
652
|
+
def self.commit_to_data_bags? event
|
653
|
+
data_bag_repo?(event) && not_deleted?(event)
|
654
|
+
end
|
655
|
+
|
656
|
+
|
530
657
|
def self.commit_to_realm? event
|
531
658
|
repo_name(event) =~ /^realm_/
|
532
659
|
end
|
@@ -534,26 +661,28 @@ module KitchenHooks
|
|
534
661
|
|
535
662
|
def self.tagged_commit_to_cookbook? event
|
536
663
|
cookbook_repo?(event) &&
|
537
|
-
|
538
|
-
|
664
|
+
event['ref'] =~ %r{/tags/} &&
|
665
|
+
not_deleted?(event)
|
539
666
|
end
|
540
667
|
|
541
668
|
|
542
669
|
def self.tagged_commit_to_realm? event
|
543
670
|
tagged_commit_to_cookbook?(event) &&
|
544
|
-
|
671
|
+
commit_to_realm?(event)
|
545
672
|
end
|
546
673
|
|
674
|
+
|
547
675
|
def self.version_url event
|
548
676
|
return unless v = event['version']
|
549
677
|
url = git_daemon_style_url(event).sub(/^git/, 'http').sub(/\.git$/, '')
|
550
678
|
"#{url}/commits/#{v}"
|
551
679
|
end
|
552
680
|
|
681
|
+
|
553
682
|
def self.version_link event
|
554
683
|
return unless v = event['version']
|
555
684
|
url = version_url(event)
|
556
685
|
'at <a href="%s">%s</a>' % [ url, v ]
|
557
686
|
end
|
558
687
|
end
|
559
|
-
end
|
688
|
+
end
|
data/tasks/package/_package.sh
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
FROM sczizzo/trusty-tool:latest
|
2
|
+
MAINTAINER Sean Clemmer <sczizzo@gmail.com>
|
3
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
4
|
+
|
5
|
+
COPY . /kitchen_hooks
|
6
|
+
|
7
|
+
RUN mkdir -p /etc/kitchen_hooks/ && touch /etc/kitchen_hooks/app.db
|
8
|
+
RUN ln -s /kitchen_hooks/etc/config.json /etc/config.json
|
9
|
+
RUN cd /kitchen_hooks && bundle update && bundle package --all && rake build && rake install
|
10
|
+
|
11
|
+
EXPOSE 80
|
12
|
+
|
13
|
+
CMD kitchen_hooks server -c etc/config.json -p 80 -e production
|
data/tasks/run/run.sh
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
set -x
|
3
|
+
set -e
|
4
|
+
|
5
|
+
project="$(basename `pwd`)"
|
6
|
+
dockerfile="${2:-tasks/run/Dockerfile}"
|
7
|
+
task_name="${3:-$project-run-$(date +%s)}"
|
8
|
+
base_image="$(grep '^FROM' "$dockerfile" | awk '{ print $2 }')"
|
9
|
+
|
10
|
+
cleanup() {
|
11
|
+
docker stop "$task_name"
|
12
|
+
docker rm "$task_name"
|
13
|
+
docker rmi "$task_name"
|
14
|
+
}
|
15
|
+
|
16
|
+
trap cleanup EXIT
|
17
|
+
|
18
|
+
docker pull "$base_image"
|
19
|
+
docker build -t "$task_name" -f "$dockerfile" .
|
20
|
+
docker run -v ~/.chef:/root/.chef --name "$task_name" -t "$task_name"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kitchen_hooks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Clemmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hipchat
|
@@ -56,14 +56,14 @@ dependencies:
|
|
56
56
|
name: berkshelf
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '4'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '4'
|
69
69
|
- !ruby/object:Gem::Dependency
|
@@ -128,14 +128,14 @@ dependencies:
|
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
131
|
+
version: '12'
|
132
132
|
type: :runtime
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
138
|
+
version: '12'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: faraday
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -238,6 +238,8 @@ files:
|
|
238
238
|
- tasks/package/kitchen_hooks.sh
|
239
239
|
- tasks/package/post-install.sh
|
240
240
|
- tasks/package/run.sh
|
241
|
+
- tasks/run/Dockerfile
|
242
|
+
- tasks/run/run.sh
|
241
243
|
- tasks/test/Dockerfile
|
242
244
|
- tasks/test/run.sh
|
243
245
|
- test/test_helper.rb
|
@@ -278,7 +280,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
278
280
|
version: '0'
|
279
281
|
requirements: []
|
280
282
|
rubyforge_project:
|
281
|
-
rubygems_version: 2.
|
283
|
+
rubygems_version: 2.5.1
|
282
284
|
signing_key:
|
283
285
|
specification_version: 4
|
284
286
|
summary: GitLab WebHoook for automated Chef Server uploads
|