quest 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: