quest 1.0.7

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f44a8be44d1dabdcf3496a45686269255c4af45c
4
+ data.tar.gz: e3c77ca330b15ba15d9b20d84f82325c6115f901
5
+ SHA512:
6
+ metadata.gz: 293f9cd835a9d13c1efc8b162ea15bdbe20a1e9bcb6a914f3a1aecb4934b79cdab8275ef5a3ce4d7bf1920a2f718c9097d6332aee9fa4a6f81f4e200df1d587a
7
+ data.tar.gz: b353bfcbb12b6ccfc0071f13d2289c21f4aac540e6ddf06baff0d78130c8b6e803cfa7f16d44d6e3dad62385f28c04ed8b187affdf2446587cf0c146e093a234
data/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ Copyright (C) 2015 Puppet Labs Inc
2
+
3
+ Puppet Labs can be contacted at: info@puppetlabs.com
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
@@ -0,0 +1,99 @@
1
+ ## Quest
2
+
3
+ Quest-driven learning with RSpec and Serverspec.
4
+
5
+ ### What it is
6
+
7
+ The quest gem provides a learner with live feedback as a learner progresses through
8
+ a series of configuration management tasks defined by RSpec tests.
9
+ It was designed to support the [Puppet Quest Guide](https://github.com/puppetlabs/puppet-quest-guide)
10
+ content on the [Puppet Learning VM](https://puppetlabs.com/download-learning-vm), but can be
11
+ used for any project with a compatible format.
12
+
13
+ ### Installation
14
+
15
+ From source:
16
+
17
+ git clone https://github.com/puppetlabs/quest.git
18
+ cd quest
19
+ gem build quest.gemspec
20
+ gem install quest<VERSION>.gem
21
+
22
+ ### Setup
23
+
24
+ This gem was designed to support the [Puppet Quest Guide](https://github.com/puppetlabs/puppet-quest-guide),
25
+ and works with any set of tests and metadata following the same pattern.
26
+
27
+ Any project you wish to use with this tool must contain a directory with the following contents:
28
+
29
+ An `index.json` file consisting with available quest names, the list of files to watch for each quest
30
+ and an optional setup command for that quest. These setup commands will be run with the directory
31
+ of your tests as the current working directory when a user begins the quest.
32
+
33
+ ```
34
+ {
35
+ "welcome": {
36
+ "setup_command": "puppet apply ./manifests/welcome.pp"
37
+ }
38
+ }
39
+ ```
40
+
41
+ A series of `*_spec.rb` files corresponding the the quest names specified in the `index.json`.
42
+ These files must contain valid Rspec tests that correspond to the tasks in that quest. For example,
43
+ `my_first_quest_spec.rb` might contain the following. (Note the callous abuse of the RSpec format to
44
+ coerce its output into something appropriate to the quest tool interface.)
45
+
46
+ ```
47
+ describe "Task 1: do
48
+ it 'create a user named test' do
49
+ file('/etc/passwd').should contain "test"
50
+ end
51
+ end
52
+ describe "Task 2: do
53
+ ...
54
+ end
55
+ ```
56
+
57
+ A `spec_helper` file with any required libraries, helper functions, or variables
58
+ needed by the tests.
59
+
60
+ ```
61
+ require 'serverspec'
62
+ require 'pathname'
63
+
64
+ PROD_PATH = '/etc/puppetlabs/code/environments/production'
65
+ MODULE_PATH = PROD_PATH + "modules/"
66
+ ```
67
+
68
+ ### Use
69
+
70
+ The `questctl` command is used to start the quest service. Run it from the
71
+ task directory, or use the `--task_dir` flag.
72
+
73
+ ```
74
+ /usr/local/bin/questctl start --task_dir /usr/src/puppet-quest-guide/tests
75
+ ```
76
+
77
+ This works best when the service is managed by an init system, for example:
78
+
79
+ ```
80
+ # /etc/systemd/system/quest.service
81
+ [Unit]
82
+ Description=Quest tool service
83
+
84
+ [Service]
85
+ ExecStart=/usr/local/bin/questctl start --task_dir /usr/src/puppet-quest-guide/tests
86
+
87
+ [Install]
88
+ WantedBy=multi-user.target
89
+ ```
90
+
91
+ ### How it works
92
+
93
+ The quest service runs a set of RSpec tests for the current test in a loop,
94
+ updating the quest status each time the test completes. The service provides
95
+ api endpoints on port 4567 that allow access to the RSpec output and a POST
96
+ endpoint to change the current quest.
97
+
98
+ The `quest` command is a wrapper for these API endpoints.
99
+
@@ -0,0 +1,10 @@
1
+ #!usr/bin/env ruby
2
+
3
+ begin
4
+ require 'quest'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'quest'
8
+ end
9
+
10
+ Rack::Handler::WEBrick.run(Quest::API.new, {:Port => 9393, :Host => '0.0.0.0', :Bind => '0.0.0.0'})
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'quest'
5
+ require 'gli'
6
+ require 'net/http'
7
+ require 'json'
8
+ rescue
9
+ require 'rubygems'
10
+ require 'quest'
11
+ require 'gli'
12
+ require 'json'
13
+ end
14
+
15
+ module GLIWrapper
16
+ include GLI::App
17
+ extend self
18
+
19
+ BASE_URI = URI('http://localhost:4567/')
20
+ OFFER_BAILOUT = false
21
+
22
+ def get_path(*path)
23
+ begin
24
+ Net::HTTP.get(URI.join(BASE_URI, *path))
25
+ rescue Errno::ECONNREFUSED => e
26
+ puts "You may need to restart the quest service: systemctl restart quest"
27
+ raise e
28
+ end
29
+ end
30
+
31
+ def post_path(form_data, *path)
32
+ begin
33
+ Net::HTTP.post_form(URI.join(BASE_URI, *path), form_data)
34
+ rescue Errno::ECONNREFUSED => e
35
+ puts "You may need to restart the quest service: systemctl restart quest"
36
+ raise e
37
+ end
38
+ end
39
+
40
+ def offer_bailout(message)
41
+ print "#{message} Continue? [Y/n]:"
42
+ raise "Cancelled" unless [ 'y', 'yes', ''].include? STDIN.gets.strip.downcase
43
+ end
44
+
45
+ def fancy_status
46
+ active_quest = get_path('active_quest')
47
+ output = "Quest: #{active_quest}\n"
48
+ examples = JSON.parse(get_path('status/', 'examples'))
49
+ examples.each do |example|
50
+ if example["status"] == "passed"
51
+ output << '√ '.green
52
+ else
53
+ output << 'X '.yellow
54
+ end
55
+ output << example["full_description"] + "\n"
56
+ end
57
+ output
58
+ end
59
+
60
+ def summary_status
61
+ active_quest = get_path('active_quest')
62
+ output = "Quest: #{active_quest} - Progress: "
63
+ summary = JSON.parse(get_path('status/', 'summary'))
64
+ complete_count = summary["example_count"].to_i - summary["failure_count"].to_i
65
+ output << "#{complete_count} of #{summary['example_count']} tasks."
66
+ end
67
+
68
+ program_desc 'Track the status of quests and tasks.'
69
+
70
+ desc 'Begin a quest'
71
+ arg :quest_name
72
+ arg_name 'quest_name'
73
+ command :begin do |c|
74
+ c.action do |global_options, options, args|
75
+ if args.length < 1
76
+ raise 'You must specify a quest name. Refer to the Quest Guide or use the "quest list" command.'
77
+ elsif not JSON.parse(get_path('quests')).include? args[0]
78
+ raise "#{args[0]} is not a valid quest name. Refer to the Quest Guide or use the \"quest list\" command."
79
+ elsif OFFER_BAILOUT and not get_path('status/', 'summary/', 'failure_count') == '0'
80
+ offer_bailout("The current quest is not complete. If you begin a new quest, your agent nodes will be reset. Your master node and Puppet code will not be affected.\n")
81
+ end
82
+ post_path({}, 'begin/', args[0])
83
+ puts "You have started the #{args[0]} quest."
84
+ end
85
+ end
86
+
87
+ desc 'List available quests'
88
+ command :list do |c|
89
+ c.action do |global_options, options, args|
90
+ puts JSON.parse(get_path('quests'))
91
+ end
92
+ end
93
+
94
+ desc 'Show status of the current quest'
95
+ command :status do |c|
96
+ c.switch [:s], :desc => 'Show status in summary form.'
97
+ c.action do |global_options, options, args|
98
+ if options[:s]
99
+ puts summary_status
100
+ else
101
+ puts fancy_status
102
+ end
103
+ end
104
+ end
105
+
106
+ exit run(ARGV)
107
+ end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'quest'
4
+ require 'gli'
5
+
6
+ module GLIWrapper
7
+ include GLI::App
8
+ extend self
9
+
10
+ # Controllers for the quest service
11
+ # Move to questctl
12
+
13
+ program_desc 'Controller for quest tool services.'
14
+
15
+ desc 'Start the quest service'
16
+ command :start do |c|
17
+ c.flag [:q, :task_dir, 'task_dir'], :desc => 'Specify the task directory.'
18
+ c.action do |global_options, options, args|
19
+ messenger = Quest::Messenger.new( {'task_dir' => options[:task_dir]} )
20
+ api = Thread.new do
21
+ Quest::API.set :messenger, messenger
22
+ Quest::API.run!
23
+ end
24
+ watcher = Thread.new do
25
+ Quest::QuestWatcher.new(messenger).run!
26
+ end
27
+ Signal.trap("INT") do
28
+ puts "Exiting"
29
+ Thread.kill watcher
30
+ Thread.kill api
31
+ exit 130
32
+ end
33
+ watcher.join
34
+ api.join
35
+ end
36
+ end
37
+ exit run(ARGV)
38
+ end
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'quest'
4
+ require 'net/http'
5
+
6
+ BASE_URI = URI('http://localhost:4567/')
7
+
8
+ def get_path(*path)
9
+ Net::HTTP.get(URI.join(BASE_URI, *path))
10
+ end
11
+
12
+ def post_path(form_data, *path)
13
+ Net::HTTP.post_form(URI.join(BASE_URI, *path), form_data)
14
+ end
15
+
16
+ quests = JSON.parse(get_path('quests'))
17
+
18
+ quests.each do |q|
19
+ completion_script = File.join('/usr/src/puppet-quest-guide/tests', 'test_tests', "#{q}.sh")
20
+ `#{completion_script}`
21
+ end
@@ -0,0 +1,6 @@
1
+ require 'quest/logger'
2
+ require 'quest/messenger'
3
+ require 'quest/rspec_runner'
4
+ require 'quest/quest_watcher'
5
+ require 'quest/colorization'
6
+ require 'quest/api'
@@ -0,0 +1,83 @@
1
+ require 'sinatra/base'
2
+
3
+ module Quest
4
+
5
+ class API < Sinatra::Base
6
+
7
+ # Set defaults
8
+ before { content_type 'application/json' }
9
+ not_found { JSON.dump("error" => "Not Found") }
10
+
11
+ set :traps, false
12
+
13
+ helpers do
14
+ def messenger
15
+ settings.messenger
16
+ end
17
+ def quest_status
18
+ settings.messenger.quest_status
19
+ end
20
+ def active_quest
21
+ settings.messenger.active_quest
22
+ end
23
+ def active_quest_status
24
+ settings.messenger.quest_status[settings.messenger.active_quest]
25
+ end
26
+ end
27
+
28
+ get '/status' do
29
+ active_quest_status.to_json
30
+ end
31
+
32
+ get '/status/examples' do
33
+ active_quest_status['examples'].to_json
34
+ end
35
+
36
+ get '/status/examples/:number' do
37
+ active_quest_status['examples'][params[:number].to_i - 1].to_json
38
+ end
39
+
40
+ get '/status/examples/count' do
41
+ content_type 'text/html'
42
+ active_quest_status['examples'].size
43
+ end
44
+
45
+ get '/status/examples/:number/description' do
46
+ active_quest_status['examples'][params[:number].to_i - 1]['description'].to_json
47
+ end
48
+
49
+ get '/status/examples/:number/file_path' do
50
+ active_quest_status['examples'][params[:number].to_i - 1]['file_path'].to_json
51
+ end
52
+
53
+ get '/status/examples/:number/status' do
54
+ active_quest_status['examples'][params[:number].to_i - 1]['status'].to_json
55
+ end
56
+
57
+ get '/status/examples/:number/run_time' do
58
+ active_quest_status['examples'][params[:number].to_i - 1]['run_time'].to_json
59
+ end
60
+
61
+ get '/status/summary' do
62
+ active_quest_status['summary'].to_json
63
+ end
64
+
65
+ get '/status/summary/failure_count' do
66
+ active_quest_status['summary']['failure_count'].to_json
67
+ end
68
+
69
+ get '/active_quest' do
70
+ content_type 'text/html'
71
+ active_quest
72
+ end
73
+
74
+ get '/quests' do
75
+ messenger.quests.to_json
76
+ end
77
+
78
+ post '/begin/:quest' do
79
+ messenger.begin_quest(params[:quest])
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,26 @@
1
+ class String
2
+ def black; "\e[30m#{self}\e[0m" end
3
+ def red; "\e[31m#{self}\e[0m" end
4
+ def green; "\e[32m#{self}\e[0m" end
5
+ def brown; "\e[33m#{self}\e[0m" end
6
+ def yellow; "\e[1;33m#{self}\e[0m" end
7
+ def blue; "\e[34m#{self}\e[0m" end
8
+ def magenta; "\e[35m#{self}\e[0m" end
9
+ def cyan; "\e[36m#{self}\e[0m" end
10
+ def gray; "\e[37m#{self}\e[0m" end
11
+
12
+ def bg_black; "\e[40m#{self}\e[0m" end
13
+ def bg_red; "\e[41m#{self}\e[0m" end
14
+ def bg_green; "\e[42m#{self}\e[0m" end
15
+ def bg_brown; "\e[43m#{self}\e[0m" end
16
+ def bg_blue; "\e[44m#{self}\e[0m" end
17
+ def bg_magenta; "\e[45m#{self}\e[0m" end
18
+ def bg_cyan; "\e[46m#{self}\e[0m" end
19
+ def bg_gray; "\e[47m#{self}\e[0m" end
20
+
21
+ def bold; "\e[1m#{self}\e[21m" end
22
+ def italic; "\e[3m#{self}\e[23m" end
23
+ def underline; "\e[4m#{self}\e[24m" end
24
+ def blink; "\e[5m#{self}\e[25m" end
25
+ def reverse_color; "\e[7m#{self}\e[27m" end
26
+ end
@@ -0,0 +1,5 @@
1
+ require 'mono_logger'
2
+
3
+ module Quest
4
+ LOGGER = MonoLogger.new(STDOUT)
5
+ end
@@ -0,0 +1,77 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ require 'json'
4
+
5
+ module Quest
6
+
7
+ # Shared state and methods for reading from the content directory
8
+ class Messenger
9
+
10
+ require 'fileutils'
11
+
12
+ attr_reader :quest_index_file
13
+ attr_reader :spec_helper
14
+ attr_accessor :active_quest
15
+ attr_accessor :quest_status
16
+
17
+ def initialize(config = {})
18
+ @task_dir = config['task_dir'] || Dir.pwd
19
+ @quest_index_file = File.join(@task_dir, 'index.json')
20
+ validate_task_dir
21
+ @spec_helper = File.join(@task_dir, 'spec_helper.rb')
22
+ @quest_status = {}
23
+ @active_quest = quests.first
24
+ run_setup_command(@active_quest)
25
+ end
26
+
27
+ def set_raw_status(quest, raw_status_hash)
28
+ @quest_status[quest] = raw_status_hash
29
+ end
30
+
31
+ def validate_task_dir
32
+ begin
33
+ JSON.parse(File.read(@quest_index_file))
34
+ rescue Errno::ENOENT
35
+ puts "No valid quest index.json file found at #{@quest_index_file}"
36
+ exit 1
37
+ end
38
+ end
39
+
40
+ def run_setup_command(quest)
41
+ if setup_command
42
+ begin
43
+ puts "Setting up the #{active_quest} quest..."
44
+ Dir.chdir(@task_dir){
45
+ setup_io = IO.popen(setup_command) do |io|
46
+ io.each do |line|
47
+ puts line
48
+ end
49
+ end
50
+ }
51
+ rescue
52
+ puts "Setup for #{active_quest} failed"
53
+ end
54
+ end
55
+ end
56
+
57
+ def spec_path(quest)
58
+ File.join(@task_dir, "#{quest}_spec.rb")
59
+ end
60
+
61
+ def quests
62
+ JSON.parse(File.read(@quest_index_file)).keys
63
+ end
64
+
65
+ def setup_command
66
+ JSON.parse(File.read(@quest_index_file))[@active_quest]["setup_command"]
67
+ end
68
+
69
+ def begin_quest(quest)
70
+ if quests.include?(quest)
71
+ @active_quest = quest
72
+ run_setup_command(quest)
73
+ end
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,31 @@
1
+ require 'timers'
2
+
3
+ module Quest
4
+ class QuestWatcher
5
+
6
+ def initialize(messenger)
7
+ @messenger = messenger
8
+ @timers = Timers::Group.new
9
+ @lock = Mutex.new
10
+ end
11
+
12
+ def start_timer
13
+ task_timer = @timers.now_and_every(5) do
14
+ unless @lock.locked?
15
+ @lock.lock
16
+ active_quest = @messenger.active_quest
17
+ runner = Quest::RSpecRunner.new(@messenger.spec_path(active_quest), @messenger.spec_helper)
18
+ @messenger.set_raw_status(active_quest, runner.result)
19
+ @lock.unlock
20
+ end
21
+ end
22
+ loop {@timers.wait}
23
+ end
24
+
25
+ # This is the main function to set up and run the watcher process
26
+ def run!
27
+ start_timer
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ module Quest
2
+ class RSpecRunner
3
+ # Based loosely on https://github.com/guard/guard-rspec/blob/master/lib/guard/rspec/rspec_process.rb
4
+
5
+ attr_reader :result
6
+
7
+ def initialize(spec_file, spec_helper)
8
+ @spec_file = spec_file
9
+ @spec_helper = spec_helper
10
+ Tempfile.open('quest-rspec-runner') do |tmp_file|
11
+ @exit_code = run_spec(@spec_file, @spec_helper, tmp_file.path)
12
+ @result = read_result(tmp_file.path)
13
+ end
14
+ end
15
+
16
+ def run_spec(spec_file, spec_helper, tmp_file)
17
+ home = ENV["HOME"] || '/tmp'
18
+ command = "HOME=#{home} rspec #{spec_file} -r #{spec_helper} -f json -o #{tmp_file}"
19
+ begin
20
+ pid = Kernel.spawn(command)
21
+ result = ::Process.wait2(pid)
22
+ result.last.exitstatus
23
+ rescue Errno::ENOENT => ex
24
+ raise Failure, "Failed: #{command} (#{ex})"
25
+ end
26
+ end
27
+
28
+ def read_result(tmp_file)
29
+ begin
30
+ JSON.parse(File.read(tmp_file))
31
+ rescue Errno::ENOENT => ex
32
+ raise Failure, "Cannot open status file #{tmp_file}, (#{ex})"
33
+ end
34
+ end
35
+
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,202 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quest
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.7
5
+ platform: ruby
6
+ authors:
7
+ - Kevin Henner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: serverspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.36'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '2.36'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: gli
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '2.12'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '2.12'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mono_logger
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '1.1'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '1.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sinatra
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: highline
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
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: net-ssh
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
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: timers
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: quest uses serverspec to track completion of configuration management
154
+ related learning tasks.
155
+ email:
156
+ - kevin@puppetlabs.com
157
+ executables:
158
+ - quest
159
+ - questctl
160
+ - test_all_quests
161
+ extensions: []
162
+ extra_rdoc_files: []
163
+ files:
164
+ - LICENSE
165
+ - README.md
166
+ - bin/ballad
167
+ - bin/quest
168
+ - bin/questctl
169
+ - bin/test_all_quests
170
+ - lib/quest.rb
171
+ - lib/quest/api.rb
172
+ - lib/quest/colorization.rb
173
+ - lib/quest/logger.rb
174
+ - lib/quest/messenger.rb
175
+ - lib/quest/quest_watcher.rb
176
+ - lib/quest/rspec_runner.rb
177
+ homepage: http://github.com/puppetlabs/quest
178
+ licenses:
179
+ - Apache 2.0
180
+ metadata: {}
181
+ post_install_message:
182
+ rdoc_options: []
183
+ require_paths:
184
+ - lib
185
+ required_ruby_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ required_rubygems_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - '>='
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ requirements: []
196
+ rubyforge_project:
197
+ rubygems_version: 2.4.8
198
+ signing_key:
199
+ specification_version: 4
200
+ summary: Track completion of configuration management tasks.
201
+ test_files: []
202
+ has_rdoc: