puppet-classroom-manager 0.0.1

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