puppet-classroom-manager 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
+ SHA1:
3
+ metadata.gz: 91bd91d0dc4b47cffaf1dcc62cb23f167e9ac4e5
4
+ data.tar.gz: 1f74d7f92c3b89f52528cbdf1771749d512508f9
5
+ SHA512:
6
+ metadata.gz: c2a71798353538217e3e563360204406b0004ddb540969c80a2f56e0701afcec7506eeb8c18ce6cd50a819695289d4cd25ef161bad623f4949bf274c500c55c4
7
+ data.tar.gz: c2d572e1ac680bf66210f9e9b210bee4c372e66fff808c54f91c6058d73b45367e314b8f3b3c9cc40af4984f3404162a696ef2a122744dfda3062c19fa2f47bd
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright {yyyy} {name of copyright owner}
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Puppet Classroom Manager
2
+
3
+ Usage : `classroom <action> [subject] [options]`
4
+
5
+ Manage Puppet classroom VMs and updating courseware. This includes
6
+ troubleshooting and maintenance tasks to be used as needed during
7
+ the delivery and facilities for resetting the VM for use across
8
+ multiple deliveries. This is pre-installed on the classroom VM and
9
+ is only of use to trainers delivering Puppet Inc. classes.
10
+
11
+ Available actions:
12
+
13
+ * `classroom update`
14
+ This updates your VM to the latest released version. This includes all
15
+ cached packages, gems, modules, etc. and pre-installed courseware.
16
+
17
+ You should run this command before each delivery. It may take several
18
+ minutes to complete. When finished, it will run the validate task.
19
+
20
+ * `classroom validate`
21
+ This runs built in serverspec tests to check the VM for consistency.
22
+ It is run automatically by the update task so you likely don't need
23
+ to run it yourself.
24
+
25
+ * `classroom submit`
26
+ This task replaces the old `rake submit` tasks and uploads classroom
27
+ statistics to the EDU department for analysis.
28
+
29
+ * `classroom sanitize`
30
+ Running this task will remove all traces of students in preparation
31
+ for using the same VM for the next delivery.
32
+
33
+ * `classroom performance log <message>`
34
+ The VM periodically records performance metrics. These are included
35
+ in the information uploaded with the `submit` task. This task allows
36
+ you to annotate those logs with arbitrary information.
37
+
38
+ * `classroom reset password | certificates | filesync`
39
+ This task resets the specified information. Using this to reset the
40
+ root password ensures that it is still displayed on the login console.
41
+ This can also regenerate all monolithic PE certificates or use the
42
+ so-called "nuclear" option to blow away and redeploy filesync.
43
+
44
+ * `classroom restart <PE services>`
45
+ Gracefully restart PE services in the proper order. Run without a
46
+ service to see usage information.
47
+
48
+ * `classroom troubleshoot`
49
+ Run through various troubleshooting checks and fixes.
50
+
51
+ * `classroom page`
52
+ This will page on call classroom support in case of emergency.
data/bin/classroom ADDED
@@ -0,0 +1,115 @@
1
+ #! /usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'logger'
4
+
5
+ require 'classroom'
6
+
7
+ $logger = Logger.new(STDOUT)
8
+ $logger.level = Logger::INFO
9
+ $logger.formatter = proc do |severity, datetime, progname, msg|
10
+ "#{severity}: #{msg}\n"
11
+ end
12
+
13
+ config = {}
14
+ optparse = OptionParser.new { |opts|
15
+ opts.banner = "Usage : classroom <action> [subject] [options]
16
+
17
+ Manage Puppet classroom VMs and updating courseware. This includes
18
+ troubleshooting and maintenance tasks to be used as needed during
19
+ the delivery and facilities for resetting the VM for use across
20
+ multiple deliveries. This is pre-installed on the classroom VM.
21
+
22
+ Type courseware help for full usage instructions.
23
+
24
+ "
25
+
26
+ opts.on("-d", "--debug", "Display debugging messages") do
27
+ $logger.level = Logger::DEBUG
28
+ end
29
+
30
+ opts.on("-f", "--force", "When restarting services, force a hard restart.") do
31
+ config[:force] = true
32
+ end
33
+
34
+ opts.separator('')
35
+
36
+ opts.on("-v", "--version", "Print out the version") do
37
+ require 'courseware/version'
38
+ puts Courseware::VERSION
39
+ exit
40
+ end
41
+
42
+ opts.on("-h", "--help", "Displays this help") do
43
+ puts opts
44
+ puts
45
+ exit 1
46
+ end
47
+ }
48
+ optparse.parse!
49
+
50
+ # hardcode for now. Decide if we need configurability later
51
+ config[:basedir] = '/opt/pltraining'
52
+ config[:bindir] = "#{config[:basedir]}/bin"
53
+ config[:confdir] = "#{config[:basedir]}/etc/puppet"
54
+ config[:specdir] = "#{config[:basedir]}/spec"
55
+ config[:learndot] = 'https://learn.puppet.com/manage/instructor/dashboard.html'
56
+
57
+ # grab the arguments after we've scraped the options out
58
+ verb, *subject = ARGV.collect {|arg| arg.to_sym }
59
+
60
+ classroom = Classroom.new(config)
61
+ begin
62
+ case verb
63
+ when :update
64
+ classroom.update
65
+ classroom.validate
66
+
67
+ when :validate
68
+ classroom.validate
69
+ # serverspec tests on the master MV
70
+
71
+ when :submit
72
+ classroom.submit
73
+ # submit classroom presentation stats
74
+
75
+ when :sanitize
76
+ classroom.sanitize
77
+ # remove users, etc to start a new delivery
78
+
79
+ when :performance
80
+ classroom.performance subject
81
+ # reset certificates, environments, node groups, courseware, etc
82
+ # is this even a thing???
83
+
84
+ when :reset
85
+ classroom.reset subject
86
+ # reset master certificates
87
+ # reset VM password
88
+
89
+ when :restart
90
+ classroom.restart subject
91
+ # restart PE services
92
+
93
+ when :troubleshoot
94
+ classroom.troubleshoot
95
+ # run troubleshooting scripts
96
+
97
+ when :page
98
+ classroom.page
99
+
100
+ when :help
101
+ classroom.help
102
+
103
+ when :shell
104
+ classroom.debug
105
+
106
+ else
107
+ $logger.warn "Unknown verb: #{verb}"
108
+ classroom.help
109
+
110
+ end
111
+ rescue RuntimeError => e
112
+ puts "Error: #{e.message}"
113
+ puts e.backtrace.join("\n\t") if config[:debug]
114
+ exit 1
115
+ end
@@ -0,0 +1,59 @@
1
+ class Classroom
2
+ HELP = <<-EOF
3
+
4
+ Usage : classroom <action> [subject] [options]
5
+
6
+ Manage Puppet classroom VMs and updating courseware. This includes
7
+ troubleshooting and maintenance tasks to be used as needed during
8
+ the delivery and facilities for resetting the VM for use across
9
+ multiple deliveries. This is pre-installed on the classroom VM and
10
+ is only of use to trainers delivering Puppet Inc. classes.
11
+
12
+ Available actions:
13
+
14
+ * classroom update:
15
+ This updates your VM to the latest released version. This includes all
16
+ cached packages, gems, modules, etc. and pre-installed courseware.
17
+
18
+ You should run this command before each delivery. It may take several
19
+ minutes to complete. When finished, it will run the validate task.
20
+
21
+ * classroom validate
22
+ This runs built in serverspec tests to check the VM for consistency.
23
+ It is run automatically by the update task so you likely don't need
24
+ to run it yourself.
25
+
26
+ * classroom submit
27
+ This task replaces the old `rake submit` tasks and uploads classroom
28
+ statistics to the EDU department for analysis.
29
+
30
+ * classroom sanitize
31
+ Running this task will remove all traces of students in preparation
32
+ for using the same VM for the next delivery.
33
+
34
+ * classroom performance log <message>
35
+ The VM periodically records performance metrics. These are included
36
+ in the information uploaded with the `submit` task. This task allows
37
+ you to annotate those logs with arbitrary information.
38
+
39
+ * classroom reset password | certificates | filesync
40
+ This task resets the specified information. Using this to reset the
41
+ root password ensures that it is still displayed on the login console.
42
+ This can also regenerate all monolithic PE certificates or use the
43
+ so-called "nuclear" option to blow away and redeploy filesync.
44
+
45
+ * classroom restart <PE services>
46
+ Gracefully restart PE services in the proper order. Run without a
47
+ service to see usage information.
48
+
49
+ * classroom troubleshoot
50
+ Run through various troubleshooting checks and fixes.
51
+
52
+ * classroom page
53
+ This will page on call classroom support in case of emergency.
54
+
55
+ * classroom help
56
+ You're looking at it!
57
+
58
+ EOF
59
+ end
@@ -0,0 +1,41 @@
1
+ class Classroom
2
+ def page
3
+ require 'json'
4
+ require 'rest-client'
5
+
6
+ begin
7
+ config = JSON.parse(File.read('/opt/pltraining/etc/classroom.json'))
8
+ pd_key = File.read('/opt/pltraining/etc/pagerduty.key')
9
+ rescue => e
10
+ puts "Cannot load configuration"
11
+ puts e.message
12
+ exit 1
13
+ end
14
+
15
+ puts "--------- You're about to page and possibly wake somone up. ---------"
16
+ puts "Please check the Troubleshooting Guide for solutions to common problems."
17
+ puts
18
+ puts "https://github.com/puppetlabs/courseware/blob/master/TroubleshootingGuide.md"
19
+ puts
20
+ if confirm?("Have you done everything in the troubleshooting guide?") then
21
+ puts "Sending page. Make sure you've posted about the issue in HipChat."
22
+ config = load_metadata
23
+ page_message = "Instructor: #{config['name']}\n" +
24
+ "Email: #{config['email']}\n" +
25
+ "Course: #{config['course']}\n" +
26
+ "ID: #{config['event_id']}"
27
+ page_data = {
28
+ "service_key" => pd_key,
29
+ "event_type" => "trigger",
30
+ "description" => page_message
31
+ }
32
+ response = RestClient.post(
33
+ "https://events.pagerduty.com/generic/2010-04-15/create_event.json",
34
+ page_data.to_json,
35
+ :content_type => :json,
36
+ :accept => :json
37
+ )
38
+ puts response
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ class Classroom
2
+ def performance(subject)
3
+ if subject.empty?
4
+ puts "This will take performance related snapshots, which will be uploaded for"
5
+ puts "engineering analysis. You may also record performance notes into the log."
6
+ puts
7
+ puts "Usage: classroom performance [ log <message> | snapshot ]"
8
+ puts
9
+ puts " * log: Record a message into classroom log and take a snapshot."
10
+ puts " * snapshot: Save snapshot of classroom statistics."
11
+ puts
12
+ exit 1
13
+ end
14
+
15
+ case subject.shift
16
+ when 'log'
17
+ message = subject.empty? ? "Misc performance issue noted." : subject.join(' ')
18
+ $logger.warn message
19
+ record_snapshot
20
+
21
+ when 'snapshot'
22
+ $logger.debug "Scheduled performance snapshot"
23
+ record_snapshot
24
+
25
+ else
26
+ raise "No such action"
27
+ end
28
+
29
+ def record_snapshot
30
+ $logger.debug "-------------------------------- top -bn1 ----------------------------------\n#{`top -bn1`}"
31
+ $logger.debug "-------------------------------- vmstat ------------------------------------\n#{`vmstat`}"
32
+ $logger.debug "-------------------------------- netstat -a --------------------------------\n#{`netstat -a`}"
33
+ $logger.debug "-------------------------------- iostat ------------------------------------\n#{`iostat`}"
34
+ $logger.debug "-------------------------------- mpstat -P ALL -----------------------------\n#{`mpstat -P ALL`}"
35
+
36
+ FileUtils.mkdir_p '/var/log/puppetlabs/classroom-traffic'
37
+ `tcpdump -G 15 -W 1 -w /var/log/puppetlabs/classroom-traffic/#{Time.now.to_i}.pcap -i any > /dev/null 2>&1 &`
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,121 @@
1
+ class Classroom
2
+ def reset(subject)
3
+ if subject.size != 1
4
+ puts <<-EOF
5
+ Usage: classroom reset <password | certificates | filesync>
6
+
7
+ This tool will reset or regenerate:
8
+ * root's login password and update the /etc/issue screen
9
+ * delete and redeploy all FileSync caches
10
+ * *all* ssl certificates in the PE stack. (warning: destructive!)
11
+
12
+ EOF
13
+ exit 1
14
+ end
15
+
16
+ case subject.first
17
+ when :password
18
+ reset_password
19
+ when :certificates
20
+ reset_certificates
21
+ when :filesync
22
+ reset_filesync
23
+ else
24
+ raise "Unknown action."
25
+ end
26
+ end
27
+
28
+ def reset_password
29
+ print "Enter new root password: "
30
+ password = gets.chomp
31
+
32
+ %x(echo "root:#{password}"|chpasswd)
33
+
34
+ File.open('/var/local/password','w') do |f|
35
+ f.puts password
36
+ end
37
+
38
+ %x(/etc/rc.local 2>/dev/null)
39
+ end
40
+
41
+ def reset_certificates
42
+ require "fileutils"
43
+
44
+ # Automate the process of regenerating certificates on a monolithic master
45
+ # https://docs.puppet.com/pe/latest/trouble_regenerate_certs_monolithic.html
46
+ #
47
+ timestamp = Time.now.to_i
48
+ certname = `puppet master --configprint certname`.strip
49
+ ssldir = '/etc/puppetlabs/puppet/ssl'
50
+ puppetdbcerts = '/etc/puppetlabs/puppetdb/ssl'
51
+ consolecerts = '/opt/puppetlabs/server/data/console-services/certs'
52
+ pgsqlcerts = '/opt/puppetlabs/server/data/postgresql/9.4/data/certs'
53
+ orchcerts = '/etc/puppetlabs/orchestration-services/ssl'
54
+
55
+ ["puppet", "puppetdb", "console-services", "postgresql", "orchestration"].each do |path|
56
+ FileUtils.mkdir_p("/root/certificates.bak/#{path}")
57
+ end
58
+ FileUtils.cp_r("#{ssldir}", "/root/certificates.bak/puppet/#{timestamp}")
59
+ FileUtils.cp_r("#{puppetdbcerts}", "/root/certificates.bak/puppetdb/#{timestamp}")
60
+ FileUtils.cp_r("#{consolecerts}", "/root/certificates.bak/console-services/#{timestamp}")
61
+ FileUtils.cp_r("#{pgsqlcerts}", "/root/certificates.bak/postgresql/#{timestamp}")
62
+ FileUtils.cp_r("#{orchcerts}", "/root/certificates.bak/orchestration/#{timestamp}")
63
+
64
+ puts "Certificates backed up to ~/certificates.bak"
65
+
66
+ puts
67
+ puts
68
+ puts "#####################################################################"
69
+ puts
70
+ puts " If you regenerate the Puppet CA to start fresh, then"
71
+ puts "ALL client certificates will be invalidated and must be regenerated!"
72
+ puts
73
+ puts " -- This should only be done as a last resort --"
74
+ puts
75
+ puts "#####################################################################"
76
+ puts
77
+ if confirm?('Would you like to regenerate the CA?', false)
78
+ FileUtils.rm_rf("#{ssldir}/*")
79
+ system("puppet cert list -a")
80
+ end
81
+
82
+ FileUtils.rm_f("/opt/puppetlabs/puppet/cache/client_data/catalog/#{certname}.json")
83
+ system("puppet cert clean #{certname}")
84
+ system("puppet infrastructure configure --no-recover")
85
+ system("puppet agent -t")
86
+
87
+ puts "All done. If you regenerated the CA, then regenerate all client certificates now."
88
+ end
89
+
90
+ def reset_filesync
91
+ puts
92
+ puts
93
+ puts "################################################################################"
94
+ puts
95
+ puts "This script will completely delete and redeploy all environments without backup!"
96
+ puts " The operation may take up to five minutes to complete."
97
+ puts
98
+ puts "################################################################################"
99
+ puts
100
+ bailout?
101
+
102
+ system("systemctl stop pe-puppetserver")
103
+
104
+ # filesync cache
105
+ FileUtils.rm_rf("/opt/puppetlabs/server/data/puppetserver/filesync")
106
+
107
+ # r10k cache
108
+ FileUtils.rm_rf("/opt/puppetlabs/server/data/code-manager/git")
109
+
110
+ # code manager worker thread caches
111
+ FileUtils.rm_rf("/opt/puppetlabs/server/data/code-manager/worker-caches")
112
+ FileUtils.rm_rf("/opt/puppetlabs/server/data/code-manager/cache")
113
+
114
+ # possibly stale environment codebases
115
+ FileUtils.rm_rf("/etc/puppetlabs/code/*")
116
+ FileUtils.rm_rf("/etc/puppetlabs/code-staging/environments")
117
+
118
+ system("systemctl start pe-puppetserver")
119
+ system("puppet code deploy --all --wait")
120
+ end
121
+ end
@@ -0,0 +1,91 @@
1
+ class Classroom
2
+ def restart(subject)
3
+ if subject.empty?
4
+ puts <<-EOF
5
+ This tool simply helps restart the PE services in the right order.
6
+ It will send a HUP signal to Puppetserver by default which is much
7
+ faster than a full restart.
8
+
9
+ You can also restart Docker containers in classes that use them.
10
+
11
+ If you do need the full restart, please pass the -f option.
12
+
13
+ Service names:
14
+ * puppetserver
15
+ * console
16
+ * puppetdb
17
+ * orchestration
18
+ * mcollective
19
+ * containers
20
+ * all (restart all PE services in the proper order)
21
+
22
+ Examples:
23
+ * classroom restart puppetdb puppetserver
24
+ * classroom restart puppetserver console -f
25
+ * classroom restart all -f
26
+
27
+ EOF
28
+ exit 1
29
+ end
30
+
31
+ # normalize to lowercase strings so we can pattern match
32
+ subject.map! { |x| x.to_s.downcase }
33
+
34
+ if subject.include? 'all'
35
+ puts "Restarting all PE stack services. This may take a few minutes..."
36
+ subject.concat ['puppetdb', 'puppetserver', 'orchestrator', 'console', 'mcollective', 'puppet']
37
+ subject.uniq!
38
+ else
39
+ puts "Restarting selected PE components."
40
+ end
41
+
42
+ if subject.grep(/puppetdb|pdb/).any?
43
+ restart_service('pe-postgresql.service')
44
+ restart_service('pe-puppetdb.service')
45
+ end
46
+
47
+ if subject.grep(/puppetserver|server/).any?
48
+ if @config[:force]
49
+ restart_service('pe-puppetserver.service')
50
+ else
51
+ reload_service('pe-puppetserver.service', 'puppet-server')
52
+ end
53
+ end
54
+
55
+ if subject.grep(/orch|pxp/).any?
56
+ restart_service('pe-orchestration-services.service')
57
+ restart_service('pxp-agent.service')
58
+ end
59
+
60
+ if subject.grep(/console/).any?
61
+ restart_service('pe-console-services.service')
62
+ restart_service('pe-nginx.service')
63
+ end
64
+
65
+ if subject.grep(/mco/).any?
66
+ restart_service('pe-activemq.service')
67
+ restart_service('mcollective.service')
68
+ end
69
+
70
+ if subject.include? 'puppet'
71
+ restart_service('puppet.service')
72
+ end
73
+
74
+ if subject.include? 'containers'
75
+ `systemctl list-units`.each_line do |line|
76
+ restart_service($1) if line =~ /^(docker-\S+)/
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ def restart_service(service)
83
+ puts "- Restarting #{service}..."
84
+ system("systemctl restart #{service}")
85
+ end
86
+
87
+ def reload_service(service, pattern)
88
+ puts "> Reloading #{service}..."
89
+ system("kill -HUP `pgrep -f #{pattern}`")
90
+ end
91
+ end
@@ -0,0 +1,76 @@
1
+ class Classroom
2
+ def sanitize
3
+ require 'yaml'
4
+ require 'fileutils'
5
+ require 'puppetclassify'
6
+
7
+ puts 'Sanitizing your VM for your next delivery...'
8
+
9
+ certname = `puppet master --configprint certname`.strip
10
+ master = `puppet agent --configprint server`.strip
11
+ classifier = "http://#{master}:4433/classifier-api"
12
+ known_groups = [ 'All Nodes', 'Agent-specified environment', 'Production environment', /PE / ]
13
+ known_users = [ 'admin"=', 'api_user', 'deployer' ]
14
+ auth_info = {
15
+ 'ca_certificate_path' => `puppet master --configprint localcacert`.strip,
16
+ 'certificate_path' => `puppet master --configprint hostcert`.strip,
17
+ 'private_key_path' => `puppet master --configprint hostprivkey`.strip,
18
+ }
19
+
20
+ group_pattern = Regexp.union(known_groups)
21
+ puppetclassify = PuppetClassify.new(classifier, auth_info)
22
+ puppetclassify.groups.get_groups.each do |group|
23
+ next if group['name'].match(group_pattern)
24
+ puppetclassify.groups.delete_group(group['id'])
25
+ print '.'
26
+ end
27
+ puts
28
+
29
+ # depends on pltraining/rbac module
30
+ users = YAML.load(`puppet resource rbac_user --to_yaml`)
31
+ users['rbac_user'].each do |user, data|
32
+ next if known_users.include? user
33
+ puts "puppet resource rbac_user #{user} ensure=absent"
34
+ system("puppet resource rbac_user #{user} ensure=absent")
35
+ print '.'
36
+ end
37
+ puts
38
+
39
+ `puppet cert list --all --machine-readable`.each_line do |line|
40
+ next unless line.start_with? '+'
41
+ name = line.gsub('"', '').split[1]
42
+ next if name.start_with? 'pe-internal'
43
+ next if name == certname
44
+
45
+ system("puppet node deactivate #{name}")
46
+ system("puppet cert clean #{name}")
47
+ print '.'
48
+ end
49
+ puts
50
+
51
+ Dir.glob('/home/*').each do |path|
52
+ next if ['/home/training', '/home/showoff'].include? path
53
+ system("userdel #{File.basename(path)}")
54
+ FileUtils.rm_rf path
55
+ print '.'
56
+ end
57
+ puts
58
+
59
+ Dir.glob('/etc/puppetlabs/code-staging/environments/*').each do |path|
60
+ next if File.basename(path) == 'production'
61
+ FileUtils.rm_rf path
62
+ print '.'
63
+ end
64
+ puts
65
+
66
+ Dir.glob('/etc/puppetlabs/code/environments/*').each do |path|
67
+ next if File.basename(path) == 'production'
68
+ FileUtils.rm_rf path
69
+ print '.'
70
+ end
71
+ puts
72
+
73
+ FileUtils.rm_rf('/var/repositories/*')
74
+ end
75
+
76
+ end
@@ -0,0 +1,72 @@
1
+ class Classroom
2
+ def submit
3
+ require 'fileutils'
4
+ require 'aws-sdk'
5
+
6
+ if puppetlabs_instructor?
7
+ puts "Please go to your learn dashboard and ensure that attendance is accurate"
8
+ puts "and then close this class delivery to mark it as complete."
9
+ puts " -- #{@config[:learndot]}"
10
+ puts
11
+ end
12
+
13
+ presentation = showoff_working_directory()
14
+
15
+ data = JSON.parse(File.read("#{presentation}/stats/metadata.json")) rescue {}
16
+ event_id = data['event_id'] || Time.now.to_i
17
+ course = data['course'] || 'none'
18
+ email = data['email'] || 'none'
19
+
20
+ begin
21
+ # depends on root's credentials as managed by bootstrap
22
+ s3 = Aws::S3::Resource.new(region:'us-west-2')
23
+
24
+ # record the module versions in use
25
+ system("puppet module list > /var/log/puppetlabs/classroom-modules")
26
+
27
+ filename = "classroom-perflogs-#{course}-#{email}-#{event_id}.tar.gz"
28
+ system("tar -cf /var/cache/#{filename} /var/log/puppetlabs/")
29
+ obj = s3.bucket(PERF_BUCKET).object(filename)
30
+ obj.upload_file("/var/cache/#{filename}")
31
+ FileUtils.rm(filename)
32
+
33
+ filename = "classroom-stats-#{course}-#{email}-#{event_id}.tar.gz"
34
+ system("tar -cf /var/cache/#{filename} #{presentation}/stats/")
35
+ obj = s3.bucket(STATS_BUCKET).object(filename)
36
+ obj.upload_file("/var/cache/#{filename}")
37
+ FileUtils.rm(filename)
38
+
39
+ rescue LoadError, StandardError => e
40
+ LOGGER.warn "S3 upload failed. No network?"
41
+ LOGGER.warn e.message
42
+ LOGGER.debug e.backtrace
43
+ end
44
+
45
+ # clean up for next delivery
46
+ system("puppet resource service showoff-courseware ensure=stopped")
47
+ FileUtils.rm_rf("#{presentation}/stats")
48
+ FileUtils.rm_f("#{presentation}/courseware.yaml")
49
+ FileUtils.rm_f("#{presentation}/_files/share/nearby_events.html")
50
+ system("puppet resource service showoff-courseware ensure=running")
51
+
52
+ end
53
+
54
+ def puppetlabs_instructor?
55
+ # TODO: how do?
56
+ false
57
+ end
58
+
59
+ def showoff_working_directory
60
+ # get the path of the currently configured showoff presentation
61
+ data = {}
62
+ path = '/usr/lib/systemd/system/showoff-courseware.service'
63
+ File.read(path).each_line do |line|
64
+ setting = line.split('=')
65
+ next unless setting.size == 2
66
+
67
+ data[setting.first] = setting.last
68
+ end
69
+ data['WorkingDirectory']
70
+ end
71
+
72
+ end
@@ -0,0 +1,178 @@
1
+ class Classroom
2
+ def troubleshoot
3
+ master = `puppet agent --configprint server`.strip
4
+ codedir = `puppet master --configprint codedir`.strip
5
+ filesync = '/etc/puppetlabs/puppetserver/conf.d/file-sync.conf'
6
+ release = File.read('/etc/puppetlabs-release').to_f rescue 0
7
+ legacy = release < 7.0
8
+
9
+ if File.exist? filesync
10
+ # why isn't there a configprint setting for this?
11
+ staging = `hocon -f #{filesync} get file-sync.repos.puppet-code.staging-dir`.strip
12
+ puts "Running checks for Code Manager configurations:"
13
+ else
14
+ staging = codedir
15
+ puts "Running checks for configurations without Code Manager:"
16
+ end
17
+
18
+ print "Cleaning any stray .git directories in: #{codedir}..."
19
+ sleep 1
20
+ system("find #{codedir} -name .git -type d -print -exec rm -rf {} \\;")
21
+ check_success
22
+
23
+ print "Validating permissions on: #{codedir}..."
24
+ sleep 1
25
+ system("find #{codedir} '!' -user pe-puppet -print -exec chown pe-puppet:pe-puppet {} \\;")
26
+ check_success
27
+
28
+ if codedir != staging
29
+ puts "Validating permissions on: #{staging}..."
30
+ sleep 1
31
+ system("find #{staging} '!' -user pe-puppet -print -exec chown pe-puppet:pe-puppet {} \\;")
32
+ check_success
33
+ end
34
+
35
+ # only check legacy systems that rely on manual installs
36
+ if legacy
37
+ if File.exist? '/home/training/courseware'
38
+ print "Sanitizing uploaded courseware..."
39
+ sleep 1
40
+ FileUtils.rm_f '/home/training/courseware/stats/viewstats.json'
41
+ FileUtils.rm_f '/home/training/courseware/stats/forms.json'
42
+ check_success
43
+ else
44
+ check_success(false)
45
+ puts "\tYou don't seem to have uploaded the courseware from your host system"
46
+ end
47
+ end
48
+
49
+ print "Checking Forge connection..."
50
+ if system("ping -c1 -W2 forge.puppet.com >/dev/null 2>&1")
51
+ if legacy
52
+ puts "Ensuring the latest version of pltraining/classroom in #{staging}..."
53
+ system("puppet module upgrade pltraining/classroom --modulepath #{staging}")
54
+ check_success
55
+ else
56
+ check_success(true)
57
+ end
58
+ else
59
+ if `awk '$1 == "server" {print $2}' /etc/ntp.conf` != master
60
+ check_success(false)
61
+ puts "\tCould not reach the Forge. You should classify your master as $offline => true"
62
+ else
63
+ puts "\tYou appear to be in offline mode."
64
+ end
65
+ end
66
+
67
+ if codedir != staging
68
+ print "Ensuring you have a valid deploy token..."
69
+ if File.exist? '/root/.puppetlabs/token'
70
+ token = `puppet access show`
71
+ api = 'https://#{master}:4433/rbac-api/v1/users/current'
72
+ status = `curl -k --write-out "%{http_code}" --silent --output /dev/null #{api} -H "X-Authentication:#{token}"`.strip
73
+ if status != "200"
74
+ print "\nRegenerating invalid token..."
75
+ FileUtils.rm_f('/root/.puppetlabs/token')
76
+ check_success
77
+ end
78
+ end
79
+
80
+ unless File.exist? '/root/.puppetlabs/token'
81
+ print "\nGenerating new token."
82
+ system('puppet plugin download > /dev/null')
83
+ system('puppet resource rbac_user deployer ensure=present display_name=deployer email=deployer@puppetlabs.vm password=puppetlabs roles=4 > /dev/null')
84
+ system('echo "puppetlabs" | HOME=/root /opt/puppetlabs/bin/puppet-access login deployer --lifetime 14d > /dev/null')
85
+ check_success
86
+ else
87
+ check_success(true)
88
+ end
89
+
90
+ puts
91
+ puts "If you're having trouble with Code Manager or FileSync, deleting all deployed"
92
+ puts "code and destroying all caches can sometimes help you get going again."
93
+ puts
94
+ if confirm?('Would you like to nuke it all and start over?', false)
95
+ reset([:filesync])
96
+ end
97
+ end
98
+
99
+ print "Validating SSL certificates..."
100
+ if valid_certificates
101
+ check_success(true)
102
+ else
103
+ puts
104
+ puts "It looks like there is an inconsistency with your master's SSL certificates."
105
+ puts "Regenerating certificates may take up to five minutes."
106
+ puts
107
+ if confirm?('Would you like to try regenerating certificates?', false)
108
+ reset([:certificates])
109
+ end
110
+ end
111
+
112
+ puts
113
+ puts 'Done checking. Fix any errors noted above and try again.'
114
+ puts 'If still having troubles, try some of the following steps.'
115
+ puts 'Note that both tail and journalctl have a "-f" follow mode.'
116
+ puts
117
+ puts 'Log files:'
118
+ puts ' * tail /var/log/puppetlabs/puppetserver/puppetserver.log'
119
+ puts ' * tail /var/log/puppetlabs/console-services/console-services.log'
120
+ puts ' * tail any other interesting log files in /var/log/puppetlabs'
121
+ puts 'System logs:'
122
+ puts ' * journalctl -eu pe-puppetserver'
123
+ puts ' * journalctl -eu pe-console-services'
124
+ puts ' * systemctl list-units | egrep "pe-|puppet"'
125
+ puts 'Edu tools:'
126
+ puts ' * tail /var/log/puppetfactory'
127
+ puts ' * journalctl -eu abalone'
128
+ puts ' * journalctl -eu puppetfactory'
129
+ puts ' * journalctl -eu showoff-courseware'
130
+ puts ' * reset_ssl_certificates.sh'
131
+ puts ' * restart_classroom_services.rb'
132
+ puts ' * dependency_nuke.rb'
133
+ puts
134
+ puts 'Have you searched the Troubleshooting Guide for your issue?'
135
+ puts "If you're still stuck, page the on-call support with 'classroom page'"
136
+ end
137
+
138
+ def valid_certificates
139
+ certname = `puppet master --configprint certname`.strip
140
+ ssldir = '/etc/puppetlabs/puppet/ssl'
141
+ puppetdbcerts = '/etc/puppetlabs/puppetdb/ssl'
142
+ consolecerts = '/opt/puppetlabs/server/data/console-services/certs'
143
+ pgsqlcerts = '/opt/puppetlabs/server/data/postgresql/9.4/data/certs'
144
+ orchcerts = '/etc/puppetlabs/orchestration-services/ssl'
145
+
146
+ cert = same_file("#{ssldir}/certs/#{certname}.pem", [
147
+ "#{puppetdbcerts}/#{certname}.cert.pem",
148
+ "#{pgsqlcerts}/_local.cert.pem",
149
+ "#{consolecerts}/#{certname}.cert.pem",
150
+ "#{orchcerts}/#{certname}.cert.pem",
151
+ ])
152
+ public_key = same_file("#{ssldir}/public_keys/#{certname}.pem", [
153
+ "#{puppetdbcerts}/#{certname}.public_key.pem",
154
+ "#{consolecerts}/#{certname}.public_key.pem",
155
+ "#{orchcerts}/#{certname}.public_key.pem",
156
+ ])
157
+ private_key = same_file("#{ssldir}/private_keys/#{certname}.pem", [
158
+ "#{puppetdbcerts}/#{certname}.private_key.pem",
159
+ "#{pgsqlcerts}/_local.private_key.pem",
160
+ "#{consolecerts}/#{certname}.private_key.pem",
161
+ "#{orchcerts}/#{certname}.private_key.pem",
162
+ ])
163
+
164
+ return (cert and public_key and private_key)
165
+ end
166
+
167
+ def same_file(filename, list)
168
+ require 'digest'
169
+ list = Array(list) # coerce if needed
170
+ left = Digest::MD5.hexdigest(File.read(filename))
171
+
172
+ list.each do |path|
173
+ return false unless (left == Digest::MD5.hexdigest(File.read(path)))
174
+ end
175
+
176
+ return true
177
+ end
178
+ end
@@ -0,0 +1,17 @@
1
+ class Classroom
2
+ def validate
3
+ require 'rake'
4
+ require 'rspec/core/rake_task'
5
+
6
+ puts "Validating configuration..."
7
+ Dir.chdir(@config[:specdir]) do
8
+ RSpec::Core::RakeTask.new(:spec) do |t|
9
+ t.rspec_opts = "-I #{@config[:specdir]}"
10
+ t.pattern = 'localhost/*_spec.rb'
11
+ end
12
+
13
+ Rake::Task[:spec].invoke
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ class Classroom
2
+ VERSION = '0.0.1'
3
+ end
data/lib/classroom.rb ADDED
@@ -0,0 +1,54 @@
1
+ class Classroom
2
+ require 'classroom/performance'
3
+ require 'classroom/reset'
4
+ require 'classroom/restart'
5
+ require 'classroom/sanitize'
6
+ require 'classroom/submit'
7
+ require 'classroom/troubleshoot'
8
+ require 'classroom/validate'
9
+ require 'classroom/version'
10
+
11
+ def initialize(config)
12
+ @config = config
13
+ end
14
+
15
+ def update
16
+ puts "Updating system and courseware..."
17
+ system("#{@config[:bindir]}/puppet agent -t --confdir #{@config[:confdir]}")
18
+ end
19
+
20
+ def confirm?(message = 'Continue?', default = true)
21
+ if default
22
+ print "#{message} [Y/n]: "
23
+ return [ 'y', 'yes', '' ].include? STDIN.gets.strip.downcase
24
+ else
25
+ print "#{message} [y/N]: "
26
+ return [ 'y', 'yes' ].include? STDIN.gets.strip.downcase
27
+ end
28
+ end
29
+
30
+ def bailout?(message = 'Continue?')
31
+ raise "User cancelled" unless confirm?(message)
32
+ end
33
+
34
+ def check_success(status=nil)
35
+ status = status.nil? ? ($? == 0) : status
36
+
37
+ if status
38
+ printf("\[\033[32m OK \033[0m]\n")
39
+ else
40
+ printf("[\033[31m FAIL \033[0m]\n")
41
+ end
42
+ end
43
+
44
+ def help
45
+ require 'classroom/help'
46
+ puts Classroom::HELP
47
+ end
48
+
49
+ def debug
50
+ require 'pry'
51
+ binding.pry
52
+ end
53
+
54
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puppet-classroom-manager
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ben Ford
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
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: puppetclassify
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: serverspec
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: hocon
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: rest-client
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
+ description: |2
84
+ Manage Puppet classroom VMs and updating courseware. This includes
85
+ troubleshooting and maintenance tasks to be used as needed during
86
+ the delivery and facilities for resetting the VM for use across
87
+ multiple deliveries. This is pre-installed on the classroom VM.
88
+
89
+ If you are not teaching Puppet Inc. classes, this is not for you.
90
+ email: education@puppetlabs.com
91
+ executables:
92
+ - classroom
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - README.md
97
+ - LICENSE
98
+ - bin/classroom
99
+ - lib/classroom/help.rb
100
+ - lib/classroom/page.rb
101
+ - lib/classroom/performance.rb
102
+ - lib/classroom/reset.rb
103
+ - lib/classroom/restart.rb
104
+ - lib/classroom/sanitize.rb
105
+ - lib/classroom/submit.rb
106
+ - lib/classroom/troubleshoot.rb
107
+ - lib/classroom/validate.rb
108
+ - lib/classroom/version.rb
109
+ - lib/classroom.rb
110
+ homepage: http://github.com/puppetlabs/puppet-classroom-manager
111
+ licenses:
112
+ - Apache-2.0
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.0.14.1
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Manage Puppet classroom VMs and updating courseware.
134
+ test_files: []