puppet-twitch 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MmY4YzJlMTY4ZTZkYjAwOTRmNDdiMzdhNGE2Yjc5NGNiZDNhYWY2Zg==
5
+ data.tar.gz: !binary |-
6
+ YzQ3NDFlYThmYzNkMmMwZTNlMDE2ZjhiNjVlY2M4NzUyM2Y5NGQ5YQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ODQzNzQ0ODEzNGU5NWZhNzYxNGM1NjAwZTE2ZmUzYjNiNGM0MDA0OWIwNTEz
10
+ N2Q2YjgzMTY2ZWMwZDQ4NDM4ZjBkMjM5N2E2ZWE2MWVjZmM2YjY2MjdhNDJh
11
+ Mzk5ZTFkYWU2YWQ4MGNhYTkyZDk5MmM2MjhhNjA2ZWU4ZTRjOTg=
12
+ data.tar.gz: !binary |-
13
+ Mzc5MDVjMmYwOWYxNmUwMjMwY2Q5NTAyOWM3ODZmMzRmYTI1MzVkNThiODlj
14
+ NGQyMjYwZjAzM2M4ZWVmMWJkY2E3MWZjNGZmZWNhMzkwMDdlNTQ4Yzk0NDdl
15
+ YWEwOGRkMDE1YzhkODkwOGRiMjczMWRlYzg1MzJjNDlmOTMyMzk=
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org/'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'puppet'
7
+ gem 'rake'
8
+ end
9
+
10
+ group :test do
11
+ gem 'rspec', '~> 3.2.0'
12
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 TomPoulton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,102 @@
1
+ # Puppet Twitch
2
+
3
+ Puppet Twitch provides a lightweight http interface for remotely triggering puppet runs. That's it! The plan was to not use more resources than it had to, and also not open up any security holes.
4
+
5
+ Currently this project is definitely in the beta phase, there are probably a few kinks to work out, and it's worth highlighting that at the moment **puppet twitch doesn't use a "proper" http server**, it's just a TCP socket with an http shaped regex. This makes it lightweight, but there might be a whole bunch of compatibility/security issues with it, so I'm looking to swap that out (see ToDo section)
6
+
7
+ ### Triggering puppet
8
+
9
+ To trigger a puppet run on a node running puppet-twitch, simply hit the `/puppet/twitch` endpoint over http. For example, to trigger puppet on `node01.example.com` running puppet-twitch on the default port:
10
+
11
+ `$ curl http://node01.example.com:2023/puppet/twitch`
12
+
13
+ Puppet-twitch will respond with one of the following (`<status code>, <body>`):
14
+ - `202, Triggered puppet run` - The puppet run has been triggered asynchronously
15
+ - `409, Puppet already running` - Either the server is already processing a request, or puppet's lock file exists indicating the agent is already running
16
+ - `500, <error message>` - Something went wrong!
17
+
18
+ ##### Async
19
+
20
+ You can use the `async` parameter (default `true`) to make the request wait for the puppet run to finish before returning. Note that this can be a few minutes, so timeouts etc will need to be taken into account:
21
+
22
+ `$ curl http://node01.example.com:2023/puppet/twitch?async=false`
23
+
24
+ In this case puppet-twitch will respond with `200, Puppet run complete`
25
+
26
+ ### Puppet Install
27
+
28
+ There is a puppet module called `twitch` in the source that can be used to setup and install puppet-twitch. The module isn't in the forge yet so copy and paste for now.
29
+
30
+ To use the defaults on Linux, simply `include twitch`. This will:
31
+ - Create a user and group called `twitch`
32
+ - Add specific puppet commands to `/etc/sudoers.d/twitch` (see `init.pp` in the `twitch` module for the list of commands)
33
+ - Create the `/var/run/puppet-twitch` directory that is writable by the `twitch` user
34
+ - Install the gem
35
+ - Start the server as the `twitch` user on the default port (2023)
36
+
37
+ The `twitch` class is parameterized, so user, port, run directory, etc can be configured if necessary
38
+
39
+ ##### Prerequisites
40
+ - **ruby 1.9.3** or above as `require_relative` isn't supported by lower versions
41
+ - Open the port to incoming connections, the `twitch` module will not change any network configuration.
42
+ - Add `/etc/sudoers.d` to the `sudoers` file. This is a common configuration and is done by default on some VMs (e.g.Vagrant boxes, EC2 instances) but the `twitch` module assumes this is already setup.
43
+
44
+ ### Manual Install:
45
+
46
+ `$ gem install puppet-twitch` (run as root so that it's available to all users)
47
+
48
+ Use the `puppet-twitch` command to start/stop the server
49
+
50
+ `$ puppet-twitch {start|stop|restart|status} dir=</path/to/run/dir> [-- [bind=<binding>] [port=<port>]]`
51
+
52
+ For example to start the server on a custom port
53
+
54
+ `$ puppet-twitch start dir=/var/run/puppet-twitch -- port=8090`
55
+
56
+ And then stop it
57
+
58
+ `$ puppet-twitch stop dir=/var/run/puppet-twitch`
59
+
60
+ ##### Prerequisites
61
+ - All prerequisites from the puppet install apply
62
+ - Create a user to run the server (one that doesn't have root permissions)
63
+ - Allow user to run the necessary puppet commands with `sudo` and without password (see `init.pp` for commands)
64
+ - Create a run directory for pids and logs (writable by the user)
65
+
66
+ ##### Args:
67
+ - `dir`: Absolute path for directory that will store the pids and logs. **The directory must be manually created and must be writable by the user that is running the server**. It must be provided for every action (`start`/`stop`/`status` etc), there is a ToDo to fix this
68
+ - `bind`: The IP address to bind to, defaults to `0.0.0.0`
69
+ - `port`: The port to listen on, defaults to `2023`
70
+
71
+ ### Running from source (for development/testing)
72
+
73
+ You can use `rake install` to build and install the gem locally (I recommend using `rvm` gemsets to isolate gem installs), but you can also run the code direct from source in two ways:
74
+ - `ruby lib/puppet-twitch/controller.rb [bind=<binding>] [port=<port>]`
75
+ - `ruby lib/puppet-twitch/server.rb dir=</path/to/run/dir> [-- [bind=<binding>] [port=<port>]]`
76
+
77
+ The difference is that running `controller.rb` will run the server in the foreground and output will be logged to `STDOUT`. Running `server.rb` will start the server as a daemon and output will be logged to a logfile in the run directory
78
+
79
+ If you want to run the server in single threaded mode (i.e. the server doesn't fork threads for incoming connections) then set `async = false` in `controller.rb`, this is useful for debugging issues that aren't related to threading.
80
+
81
+ ##### Vagrant
82
+
83
+ To test in a more isolated environment you can use vagrant, just spin up the boxes (see `nodes.json`), ssh into a node, then run puppet. After that, puppet-twitch should be running on the node and you can test by hitting the endpoint:
84
+
85
+ ```
86
+ [user@localhost] $ vagrant up
87
+ [user@localhost] $ vagrant ssh node01
88
+ [vagrant@node01] $ sudo puppet agent -t
89
+ [vagrant@node01] $ curl http://localhost:2023/puppet/twitch
90
+ ```
91
+
92
+ The puppet class that's applied to the nodes (`twitch::test::vagrant`) inherits from the normal `twitch` class but also:
93
+ - Gives the `twitch` user a password (set to `twitch`) and a shell (`/bin/bash`) so you can switch to the `twitch` user for debugging
94
+ - Installs the gem from the project's `pkg` directory that is mounted by vagrant, rather than downloading the gem from rubygems
95
+
96
+ In order to get local changes running on the vagrant machines, use the `rake latest` command which builds the gem as normal, but then also creates a symlink called `puppet-twitch-latest.gem` to the built gem. This is the file that the `twitch::test::vagrant` class looks for so that you don't have to specify a version
97
+
98
+ ### ToDo:
99
+ - Proper http server - I tried using Sinatra but it wasn't playing nice with Daemons and logging
100
+ - Auth key/password - At the moment anyone with network access to the port can trigger a run, not a massive deal as all they can do is trigger a puppet run, but it might be nice to add a configurable key/phrase/password
101
+ - Add support for Windows - It should be easy(-ish) to configure, I tried to write everything in an agnostic manner, but need to test and fix
102
+ - Cache run directory - It's annoying having to provide the run directory for each action
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'puppet_twitch/server'
@@ -0,0 +1,85 @@
1
+ require 'socket'
2
+ require_relative 'logger'
3
+ require_relative 'param_parser'
4
+
5
+ module PuppetTwitch
6
+
7
+ class BasicHttpServer
8
+
9
+ attr_accessor :logger
10
+
11
+ def initialize(bind, port, multiple_threads = true)
12
+ @bind = bind
13
+ @port = port
14
+ @multiple_threads = multiple_threads
15
+ @logger = PuppetTwitch::Logger.new()
16
+ @logger.level = PuppetTwitch::Logger::DEBUG
17
+ @actions = {}
18
+ end
19
+
20
+ def start()
21
+ @logger.info 'Starting server ...'
22
+ server = TCPServer.new(@bind, @port)
23
+ @logger.info "Listening on #{@bind}:#{@port}"
24
+
25
+ server_loop = proc { |socket|
26
+ begin
27
+ process_request socket
28
+ rescue => e
29
+ @logger.error e.to_s
30
+ socket.puts form_response(500, 'Unexpected error')
31
+ ensure
32
+ socket.close
33
+ end
34
+ }
35
+
36
+ loop do
37
+ if @multiple_threads
38
+ Thread.fork(server.accept, &server_loop)
39
+ else
40
+ server_loop.call server.accept
41
+ end
42
+ end
43
+ end
44
+
45
+ def process_request(socket)
46
+ client_ip = socket.peeraddr[3]
47
+ client_hostname = socket.peeraddr[2]
48
+ request = socket.gets
49
+
50
+ endpoint, params = get_endpoint_and_params(request)
51
+ @logger.info "Connection from: #{client_hostname} (#{client_ip}) | Endpoint: #{endpoint} | Params: #{params}"
52
+
53
+ response = @actions.has_key?(endpoint) ? @actions[endpoint].call(params) : [400, "Unrecognised endpoint: #{endpoint}"]
54
+ @logger.debug "#{response[0]}: #{response[1]}"
55
+
56
+ socket.puts form_response(response[0], response[1])
57
+ @logger.debug "Closing connection from #{client_ip}"
58
+ end
59
+
60
+ def endpoint(endpoint, &block)
61
+ @actions[endpoint] = block
62
+ end
63
+
64
+ def get_endpoint_and_params(request)
65
+ match = request.match(/^\s*(?<verb>GET|POST|PUT|DELETE) (?<endpoint>[\w\-\/]*)(\?(?<params>\S*)\s*|\s*)HTTP.*/)
66
+ return match['endpoint'], parse_params(match['params'])
67
+ end
68
+
69
+ def parse_params(param_string)
70
+ PuppetTwitch::ParamParser.parse(param_string.to_s.strip.split('&'))
71
+ end
72
+
73
+ def form_response(status, body)
74
+ <<-EOF
75
+ HTTP/1.1 #{status}
76
+ Content-Type: text/plain
77
+ Content-Length: #{body.bytesize}
78
+ Connection: close
79
+
80
+ #{body}
81
+ EOF
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative 'basic_http_server'
4
+ require_relative 'param_parser'
5
+ require_relative 'puppet'
6
+ require_relative 'version'
7
+
8
+ module PuppetTwitch
9
+
10
+ args = ParamParser.parse(ARGV)
11
+
12
+ bind = args[:bind] || '0.0.0.0'
13
+ port = args[:port] || 2023
14
+ threads = true
15
+
16
+ @processing_request = Mutex.new
17
+ http_server = PuppetTwitch::BasicHttpServer.new(bind, port, threads)
18
+
19
+ http_server.endpoint '/puppet/twitch' do |params|
20
+ response = []
21
+ if @processing_request.try_lock
22
+ begin
23
+ if PuppetTwitch::Puppet.is_running?
24
+ response = [409, 'Puppet already running']
25
+ else
26
+ async = (params[:async].to_s != 'false')
27
+ PuppetTwitch::Puppet.run_puppet(async)
28
+ response = async ? [202, 'Triggered puppet run'] : [200, 'Puppet run complete']
29
+ end
30
+ rescue => e
31
+ http_server.logger.error e.to_s
32
+ response = [500, 'Error running puppet']
33
+ ensure
34
+ @processing_request.unlock
35
+ end
36
+ else
37
+ response = [409, 'Puppet already running']
38
+ end
39
+ response
40
+ end
41
+
42
+ http_server.endpoint '/version' do
43
+ [200, PuppetTwitch::VERSION]
44
+ end
45
+
46
+ http_server.endpoint '/info' do
47
+ [200, 'Puppet Twitch - A simple endpoint for triggering puppet']
48
+ end
49
+
50
+ http_server.start
51
+
52
+ end
@@ -0,0 +1,43 @@
1
+ module PuppetTwitch
2
+
3
+ # Logger just prints to STDOUT.
4
+ # When the server is running in the foreground (not Daemonized),
5
+ # all output is visible in the terminal.
6
+ # When the server is running as a Daemon,
7
+ # STDOUT is redirected to a logfile,
8
+ # so this simple logger works for both scenarios
9
+
10
+ class Logger
11
+
12
+ DEBUG = 0
13
+ INFO = 1
14
+ WARN = 2
15
+ ERROR = 3
16
+
17
+ attr_accessor :level
18
+
19
+ def initialize(level = INFO)
20
+ @level = level
21
+ end
22
+
23
+ def debug(message)
24
+ log DEBUG, "Log [DEBUG]: #{message}"
25
+ end
26
+ def info(message)
27
+ log INFO, "Log [INFO]: #{message}"
28
+ end
29
+ def warn(message)
30
+ log WARN, "Log [WARN]: #{message}"
31
+ end
32
+ def error(message)
33
+ log ERROR, "Log [ERROR]: #{message}"
34
+ end
35
+
36
+ private
37
+
38
+ def log(level, message)
39
+ puts message if level >= @level
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ module PuppetTwitch
2
+
3
+ class ParamParser
4
+
5
+ # Takes an array of parameter strings and
6
+ # returns a hash of the parameters and their values where
7
+ # the keys are the parameter names as symbols.
8
+ #
9
+ # Parameter strings can have the following formats:
10
+ # 'name=value' --> { :name => 'value' }
11
+ # 'name' --> { :name => true }
12
+ #
13
+ def self.parse(param_strings)
14
+ params = {}
15
+ param_strings.each { |pair|
16
+ key_value = pair.split('=')
17
+ if key_value.size > 2 || pair[-1] == '='
18
+ raise StandardError, "Invalid parameter format: #{pair}"
19
+ else
20
+ params[key_value[0].to_sym] = (key_value.size == 1) ? true : key_value[1]
21
+ end
22
+ }
23
+ params
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ module PuppetTwitch
2
+
3
+ class Puppet
4
+ class << self
5
+
6
+ def is_running?
7
+ lock_file_path_cmd = 'puppet config print agent_catalog_run_lockfile'
8
+ lock_file_path = `#{sudo_if_required lock_file_path_cmd}`.strip
9
+ if $?.to_i != 0
10
+ raise StandardError, "Failed to find puppet lock file path: #{lock_file_path}"
11
+ end
12
+ File.exists?(lock_file_path)
13
+ end
14
+
15
+ def run_puppet(async = true)
16
+ command = async ? 'puppet agent --onetime' : 'puppet agent --onetime --no-daemonize'
17
+ output = `#{sudo_if_required command}`
18
+ exit_code = $?
19
+ unless [0, 2].include?(exit_code.to_i) # 2 indicates a puppet change, 3 is a failure
20
+ raise StandardError, "Error running puppet: #{exit_code}: #{output}"
21
+ end
22
+ end
23
+
24
+ def sudo_if_required(command)
25
+ "sudo #{command}" unless Gem.win_platform?
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ require 'daemons'
2
+ require_relative 'param_parser'
3
+
4
+ args = PuppetTwitch::ParamParser.parse(ARGV.reject {|arg| arg == '--'})
5
+ run_dir = args[:dir]
6
+ abort 'Please provide a run directory' unless run_dir
7
+
8
+ # Pids and log files will be written to run_dir directory
9
+ options = {
10
+ :dir_mode => :normal,
11
+ :dir => run_dir,
12
+ :log_output => true,
13
+ :monitor => true,
14
+ }
15
+
16
+ Daemons.run(File.expand_path('../controller.rb', __FILE__), options)
@@ -0,0 +1,3 @@
1
+ module PuppetTwitch
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'puppet_twitch/version'
6
+
7
+ excude = ['.gitignore', 'Rakefile', 'puppet/.*', 'nodes.json', 'Vagrantfile']
8
+
9
+ Gem::Specification.new do |gem|
10
+ gem.name = 'puppet-twitch'
11
+ gem.version = PuppetTwitch::VERSION
12
+ gem.description = 'Trigger a puppet run remotely'
13
+ gem.summary = 'http trigger for puppet'
14
+ gem.author = 'Tom Poulton'
15
+ gem.license = 'MIT'
16
+ gem.homepage = 'http://github.com/Accuity/puppet-twitch'
17
+
18
+ gem.files = `git ls-files`.split($/).reject { |file| file =~ /^(#{excude.join('|')})$/ }
19
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
+ gem.require_paths = ['lib']
22
+
23
+ gem.add_dependency('daemons', '~> 1.2.2')
24
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require_relative '../../lib/puppet_twitch/basic_http_server'
3
+
4
+ module PuppetTwitch
5
+
6
+ describe BasicHttpServer do
7
+
8
+ describe '#get_endpoint_and_params' do
9
+
10
+ let(:basic_http_server) { BasicHttpServer.new('0.0.0.0', 1234) }
11
+
12
+ it 'returns endpoint' do
13
+ endpoint, params = basic_http_server.get_endpoint_and_params('GET /foo HTTP')
14
+ expect(endpoint).to eq '/foo'
15
+ end
16
+
17
+ it 'returns endpoint containing punctuation' do
18
+ endpoint, params = basic_http_server.get_endpoint_and_params('GET /foo_bar/baz-baa HTTP')
19
+ expect(endpoint).to eq '/foo_bar/baz-baa'
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+ require_relative '../../lib/puppet_twitch/param_parser'
3
+
4
+ module PuppetTwitch
5
+
6
+ describe ParamParser do
7
+
8
+ describe '.parse' do
9
+
10
+ it 'converts param keys to symbols' do
11
+ params = ParamParser.parse ['foo=bar']
12
+ expect(params).to have_key :foo
13
+ end
14
+
15
+ it 'parses multiple key value params' do
16
+ params = ParamParser.parse ['foo=bar', 'baz=baa']
17
+ expect(params).to eq( {:foo => 'bar', :baz => 'baa'} )
18
+ end
19
+
20
+ it 'empty params are set to true' do
21
+ params = ParamParser.parse ['foo', 'bar']
22
+ expect(params).to eq( {:foo => true, :bar => true} )
23
+ end
24
+
25
+ it 'returns empty hash for empty input array' do
26
+ params = ParamParser.parse []
27
+ expect(params).to eq( {} )
28
+ end
29
+
30
+ it 'raises error when param is malformed' do
31
+ expect {
32
+ ParamParser.parse ['foo=bar=baz']
33
+ }.to raise_error StandardError, /Invalid parameter format/
34
+ end
35
+
36
+ it 'raises error when param value is missing' do
37
+ expect {
38
+ ParamParser.parse ['foo=']
39
+ }.to raise_error StandardError, /Invalid parameter format/
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ require 'rspec'
2
+
3
+ RSpec.configure do |c|
4
+ c.color = true
5
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puppet-twitch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Poulton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: daemons
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 1.2.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.2.2
27
+ description: Trigger a puppet run remotely
28
+ email:
29
+ executables:
30
+ - puppet-twitch
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - Gemfile
35
+ - LICENSE.txt
36
+ - README.md
37
+ - bin/puppet-twitch
38
+ - lib/puppet_twitch/basic_http_server.rb
39
+ - lib/puppet_twitch/controller.rb
40
+ - lib/puppet_twitch/logger.rb
41
+ - lib/puppet_twitch/param_parser.rb
42
+ - lib/puppet_twitch/puppet.rb
43
+ - lib/puppet_twitch/server.rb
44
+ - lib/puppet_twitch/version.rb
45
+ - puppet-twitch.gemspec
46
+ - spec/puppet_twitch/basic_http_server_spec.rb
47
+ - spec/puppet_twitch/param_parser_spec.rb
48
+ - spec/spec_helper.rb
49
+ homepage: http://github.com/Accuity/puppet-twitch
50
+ licenses:
51
+ - MIT
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 2.4.5
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: http trigger for puppet
73
+ test_files:
74
+ - spec/puppet_twitch/basic_http_server_spec.rb
75
+ - spec/puppet_twitch/param_parser_spec.rb
76
+ - spec/spec_helper.rb