lita-puppet 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +19 -0
- data/.travis.yml +7 -0
- data/Gemfile +1 -1
- data/README.md +5 -0
- data/Rakefile +5 -3
- data/lib/lita-puppet.rb +8 -8
- data/lib/lita/handlers/puppet.rb +80 -120
- data/lib/utils/lita_puppet/puppetdb.rb +39 -0
- data/lib/utils/lita_puppet/ssh.rb +47 -0
- data/lib/utils/lita_puppet/text.rb +42 -0
- data/lita-puppet.gemspec +21 -18
- data/locales/en.yml +40 -0
- data/spec/lita/handlers/puppet_spec.rb +20 -1
- data/spec/spec_helper.rb +9 -2
- metadata +48 -4
- data/lib/utils/puppetdb.rb +0 -31
- data/lib/utils/ssh.rb +0 -46
- data/lib/utils/text.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d628affc13749d488e0425795d1c186529d6aa1
|
4
|
+
data.tar.gz: 9d6b10693e3ecccab9e6250b5a6dda87fc7abc25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33df42ee1cb7799138a0b829423cbe5d6316da8eff92f0c856695bf5143e8f4214f5ebab06291d9297e2524628ef6a09ae021a77fece2cd25b3696cbe29b2709
|
7
|
+
data.tar.gz: a7e34d69a70957729c495f40bef7d835d140a99a722bbdf57e16d192758f741d9a49a0a887b7727e458a4352b65f5e577f41a8860fe3195f3564dbf7a2b9b026
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Documentation:
|
2
|
+
Exclude:
|
3
|
+
- lib/lita/handlers/puppet.rb
|
4
|
+
|
5
|
+
FileName:
|
6
|
+
Exclude:
|
7
|
+
- lib/lita-puppet.rb
|
8
|
+
|
9
|
+
Metrics/LineLength:
|
10
|
+
Max: 100
|
11
|
+
|
12
|
+
Metrics/AbcSize:
|
13
|
+
Max: 22
|
14
|
+
|
15
|
+
Metrics/MethodLength:
|
16
|
+
Max: 20
|
17
|
+
|
18
|
+
Metrics/ClassLength:
|
19
|
+
Max: 150
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# lita-puppet
|
2
2
|
|
3
|
+
[![Build Status](https://img.shields.io/travis/knuedge/lita-puppet/master.svg)](https://travis-ci.org/knuedge/lita-puppet)
|
4
|
+
[![MIT License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://tldrlegal.com/license/mit-license)
|
5
|
+
[![RubyGems :: RMuh Gem Version](http://img.shields.io/gem/v/lita-puppet.svg)](https://rubygems.org/gems/lita-puppet)
|
6
|
+
[![Code Climate](https://img.shields.io/codeclimate/github/knuedge/lita-puppet.svg)](https://codeclimate.com/github/knuedge/lita-puppet)
|
7
|
+
|
3
8
|
A [Lita](https://www.lita.io/) handler plugin for some basic [Puppet](https://puppet.com/) operations.
|
4
9
|
|
5
10
|
## Installation
|
data/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rubocop/rake_task'
|
3
4
|
|
4
5
|
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
RuboCop::RakeTask.new(:rubocop)
|
5
7
|
|
6
|
-
task default: :spec
|
8
|
+
task default: [:spec, :rubocop]
|
data/lib/lita-puppet.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
require
|
1
|
+
require 'lita'
|
2
2
|
require 'rye'
|
3
3
|
require 'timeout'
|
4
4
|
require 'puppetdb'
|
5
5
|
|
6
6
|
Lita.load_locales Dir[File.expand_path(
|
7
|
-
File.join(
|
7
|
+
File.join('..', '..', 'locales', '*.yml'), __FILE__
|
8
8
|
)]
|
9
9
|
|
10
|
-
require 'utils/puppetdb'
|
11
|
-
require 'utils/ssh'
|
12
|
-
require 'utils/text'
|
13
|
-
require
|
10
|
+
require 'utils/lita_puppet/puppetdb'
|
11
|
+
require 'utils/lita_puppet/ssh'
|
12
|
+
require 'utils/lita_puppet/text'
|
13
|
+
require 'lita/handlers/puppet'
|
14
14
|
|
15
15
|
Lita::Handlers::Puppet.template_root File.expand_path(
|
16
|
-
File.join(
|
17
|
-
|
16
|
+
File.join('..', '..', 'templates'),
|
17
|
+
__FILE__
|
18
18
|
)
|
data/lib/lita/handlers/puppet.rb
CHANGED
@@ -3,225 +3,185 @@ module Lita
|
|
3
3
|
class Puppet < Handler
|
4
4
|
namespace 'Puppet'
|
5
5
|
config :master_hostname, required: true, type: String
|
6
|
-
config :ssh_user, required: false, type: String
|
7
|
-
config :control_repo_path, required: false, type: String
|
6
|
+
config :ssh_user, required: false, type: String, default: 'lita'
|
7
|
+
config :control_repo_path, required: false, type: String, default: '/opt/puppet/control'
|
8
8
|
config :puppetdb_url, required: false, type: String
|
9
9
|
|
10
10
|
route(
|
11
11
|
/(puppet|pp)(\s+agent)?\s+(run)(\s+on)?\s+(\S+)/i,
|
12
12
|
:puppet_agent_run,
|
13
13
|
command: true,
|
14
|
-
help: {
|
15
|
-
"puppet agent run on <host>" => "Run the puppet agent on <host>."
|
16
|
-
}
|
14
|
+
help: { t('help.puppet_agent_run.syntax') => t('help.puppet_agent_run.desc') }
|
17
15
|
)
|
18
16
|
|
19
17
|
route(
|
20
18
|
/(puppet|pp)\s+(cert)\s+(clean)\s+(\S+)/i,
|
21
19
|
:cert_clean,
|
22
20
|
command: true,
|
23
|
-
help: {
|
24
|
-
"puppet cert clean <host>" => "Remove all traces of the SSL cert for <host> on the Puppet Master."
|
25
|
-
}
|
21
|
+
help: { t('help.cert_clean.syntax') => t('help.cert_clean.desc') }
|
26
22
|
)
|
27
23
|
|
28
24
|
route(
|
29
25
|
/(puppet|pp)\s+(catalog|node)\s+(\S+)\s+(profiles)/i,
|
30
26
|
:node_profiles,
|
31
27
|
command: true,
|
32
|
-
help: {
|
33
|
-
"puppet catalog <host> profiles" => "Query PuppetDB to get a list of all roles and profiles applied to <host>."
|
34
|
-
}
|
28
|
+
help: { t('help.node_profiles.syntax') => t('help.node_profiles.desc') }
|
35
29
|
)
|
36
30
|
|
37
31
|
route(
|
38
32
|
/(puppet|pp)\s+(class)\s+(nodes)\s+(\S+)/i,
|
39
33
|
:nodes_with_class,
|
40
34
|
command: true,
|
41
|
-
help: {
|
42
|
-
"puppet class nodes <class>" => "Query PuppetDB to get a list of all nodes containing a class."
|
43
|
-
}
|
35
|
+
help: { t('help.nodes_with_class.syntax') => t('help.nodes_with_class.desc') }
|
44
36
|
)
|
45
37
|
|
46
38
|
route(
|
47
39
|
/(puppet|pp)\s+(r10k|deploy)(\s+(\S+)(\s+(\S+))?)?/i,
|
48
40
|
:r10k_deploy,
|
49
41
|
command: true,
|
50
|
-
help: {
|
51
|
-
"puppet r10k [env [module]]" => "Deploy the latest puppet code on the puppet master via r10k, optionally specifying an environment, and possibly a module."
|
52
|
-
}
|
42
|
+
help: { t('help.r10k_deploy.syntax') => t('help.r10k_deploy.desc') }
|
53
43
|
)
|
54
44
|
|
55
|
-
include ::Utils::PuppetDB
|
56
|
-
include ::Utils::SSH
|
57
|
-
include ::Utils::Text
|
45
|
+
include ::Utils::LitaPuppet::PuppetDB
|
46
|
+
include ::Utils::LitaPuppet::SSH
|
47
|
+
include ::Utils::LitaPuppet::Text
|
58
48
|
|
59
49
|
def cert_clean(response)
|
60
50
|
cert = response.matches[0][3]
|
61
|
-
user = config.ssh_user || 'lita'
|
62
|
-
username = friendly_name(response.user.name)
|
63
51
|
|
64
|
-
response.
|
52
|
+
response.reply_with_mention(t('replies.cert_clean.working'))
|
65
53
|
|
66
|
-
result =
|
67
|
-
server.cd '/tmp'
|
68
|
-
# Need to use sudo
|
69
|
-
server.enable_sudo
|
70
|
-
# scary...
|
71
|
-
server.disable_safe_mode
|
72
|
-
|
73
|
-
server.execute "puppet cert clean #{cert} 2>&1"
|
74
|
-
end
|
54
|
+
result = cert_clean_result(config.master_hostname, config.ssh_user, cert)
|
75
55
|
|
76
56
|
if result[:exception]
|
77
|
-
|
78
|
-
|
79
|
-
|
57
|
+
fail_message t('replies.cert_clean.failure'), result[:exception].message
|
58
|
+
else
|
59
|
+
success_message(
|
60
|
+
t('replies.cert_clean.success'),
|
61
|
+
(result[:stdout] + result[:stderr]).join("\n")
|
62
|
+
)
|
80
63
|
end
|
81
|
-
|
82
|
-
# build a reply
|
83
|
-
response.reply("#{username}, your `puppet cert clean` is all done!")
|
84
|
-
reply_content = [result[:stdout].join("\n"), result[:stderr].join("\n")].join("\n")
|
85
|
-
response.reply as_code(reply_content)
|
86
64
|
end
|
87
65
|
|
88
66
|
def puppet_agent_run(response)
|
89
67
|
host = response.matches[0][4]
|
90
|
-
user = config.ssh_user || 'lita'
|
91
|
-
username = friendly_name(response.user.name)
|
92
68
|
|
93
|
-
response.
|
69
|
+
response.reply_with_mention(t('replies.puppet_agent_run.working'))
|
94
70
|
|
95
|
-
result =
|
96
|
-
server.cd '/tmp'
|
97
|
-
|
98
|
-
# Need to use sudo from here on
|
99
|
-
server.enable_sudo
|
100
|
-
|
101
|
-
# scary...
|
102
|
-
server.disable_safe_mode
|
103
|
-
|
104
|
-
# build up the command
|
105
|
-
command = 'puppet agent'
|
106
|
-
command << ' --onetime --verbose --no-daemonize'
|
107
|
-
command << ' --no-usecacheonfailure'
|
108
|
-
command << ' --no-splay --show_diff 2>&1'
|
109
|
-
|
110
|
-
server.execute command
|
111
|
-
end
|
71
|
+
result = simple_ssh_command(host, config.ssh_user, agent_command)
|
112
72
|
|
113
73
|
# build a reply
|
114
|
-
if
|
115
|
-
|
116
|
-
# Send the standard out, but strip off the bash color code stuff...
|
117
|
-
response.reply as_code(result[:stdout].join("\n"))
|
74
|
+
if result[:exception]
|
75
|
+
fail_message t('replies.puppet_agent_run.failure'), result[:exception].message
|
118
76
|
else
|
119
|
-
|
120
|
-
|
77
|
+
success_message(
|
78
|
+
t('replies.puppet_agent_run.success', status: result[:exit_status]),
|
79
|
+
result[:stdout].join("\n")
|
80
|
+
)
|
121
81
|
end
|
122
82
|
end
|
123
83
|
|
124
84
|
def node_profiles(response)
|
125
85
|
host = response.matches[0][2]
|
126
86
|
url = config.puppetdb_url
|
127
|
-
username = friendly_name(response.user.name)
|
128
87
|
|
129
88
|
unless url
|
130
|
-
|
131
|
-
cant_reply << "Edit my config and add `config.handlers.puppet.puppetdb_url`."
|
132
|
-
response.reply(cant_reply)
|
89
|
+
response.reply(t('replies.node_profiles.notconf'))
|
133
90
|
return false
|
134
91
|
end
|
135
92
|
|
136
|
-
response.
|
93
|
+
response.reply_with_mention(t('replies.node_profiles.working'))
|
137
94
|
|
138
95
|
profiles = node_roles_and_profiles(url, host)
|
96
|
+
|
139
97
|
if profiles.is_a? String
|
140
|
-
|
141
|
-
return false
|
98
|
+
fail_message t('replies.node_profiles.failure', error: profiles)
|
142
99
|
else
|
143
|
-
|
144
|
-
response.reply as_code(profiles.join("\n"))
|
100
|
+
success_message t('replies.node_profiles.success', host: host), profiles.join("\n")
|
145
101
|
end
|
146
102
|
end
|
147
103
|
|
148
104
|
def nodes_with_class(response)
|
149
105
|
puppet_class = response.matches[0][3]
|
150
106
|
url = config.puppetdb_url
|
151
|
-
username = friendly_name(response.user.name)
|
152
107
|
|
153
108
|
unless url
|
154
|
-
|
155
|
-
cant_reply << "Edit my config and add `config.handlers.puppet.puppetdb_url`."
|
156
|
-
response.reply(cant_reply)
|
109
|
+
response.reply(t('replies.nodes_with_class.notconf'))
|
157
110
|
return false
|
158
111
|
end
|
159
112
|
|
160
|
-
response.
|
161
|
-
|
162
|
-
|
163
|
-
puppet_classes = class_nodes(url, search)
|
113
|
+
response.reply_with_mention(t('replies.nodes_with_class.working'))
|
114
|
+
|
115
|
+
puppet_classes = class_nodes(url, class_camel(puppet_class))
|
164
116
|
if puppet_classes.empty?
|
165
|
-
|
166
|
-
return false
|
117
|
+
fail_message t('replies.nodes_with_class.failure', pclass: puppet_class)
|
167
118
|
else
|
168
|
-
|
169
|
-
|
119
|
+
success_message(
|
120
|
+
t('replies.nodes_with_class.success', pclass: puppet_class),
|
121
|
+
puppet_classes.join("\n")
|
122
|
+
)
|
170
123
|
end
|
171
124
|
end
|
172
125
|
|
173
|
-
|
126
|
+
# rubocop:disable Metrics/AbcSize
|
174
127
|
def r10k_deploy(response)
|
175
128
|
environment = response.matches[0][3]
|
176
129
|
mod = response.matches[0][5]
|
177
|
-
|
178
|
-
user = config.ssh_user || 'lita'
|
179
|
-
username = friendly_name(response.user.name)
|
130
|
+
user = config.ssh_user
|
180
131
|
|
181
|
-
response.
|
132
|
+
response.reply_with_mention(t('replies.r10k_deploy.working'))
|
182
133
|
|
183
|
-
result1 =
|
184
|
-
# Need to use sudo
|
185
|
-
server.enable_sudo
|
186
|
-
server[control_repo].git :pull
|
187
|
-
end
|
134
|
+
result1 = r10k_git_result(config.master_hostname, user, config.control_repo_path)
|
188
135
|
|
189
136
|
if result1[:exception]
|
190
|
-
|
191
|
-
response.reply as_code(result1[:exception].message)
|
137
|
+
fail_message t('replies.r10k_deploy.gitfail'), result1[:exception].message
|
192
138
|
return false
|
193
139
|
end
|
194
140
|
|
195
|
-
result2 =
|
141
|
+
result2 = simple_ssh_command(config.master_hostname, user, r10k_command(environment, mod))
|
142
|
+
|
143
|
+
if result2[:exception]
|
144
|
+
fail_message t('replies.r10k_deploy.pupfail'), result2[:exception].message
|
145
|
+
else
|
146
|
+
success_message(
|
147
|
+
t('replies.r10k_deploy.success'),
|
148
|
+
[result1[:stdout].join("\n"), result2[:stderr].join("\n")].join("\n")
|
149
|
+
)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def fail_message(message, data = nil)
|
156
|
+
response.reply_with_mention(message)
|
157
|
+
response.reply(as_code(data)) if data
|
158
|
+
end
|
159
|
+
|
160
|
+
alias success_message fail_message
|
161
|
+
|
162
|
+
def simple_ssh_command(host, user, command, timeout = 300)
|
163
|
+
over_ssh(host: host, user: user, timeout: timeout) do |server|
|
164
|
+
server.cd '/tmp'
|
196
165
|
# Need to use sudo
|
197
166
|
server.enable_sudo
|
198
167
|
# scary...
|
199
168
|
server.disable_safe_mode
|
200
169
|
|
201
|
-
command = "r10k deploy"
|
202
|
-
if environment && mod
|
203
|
-
command << ' module'
|
204
|
-
command << " -e #{environment}"
|
205
|
-
command << " #{mod}"
|
206
|
-
command << " -v"
|
207
|
-
else
|
208
|
-
command << " environment"
|
209
|
-
command << " #{environment}" if environment
|
210
|
-
command << ' -pv'
|
211
|
-
end
|
212
170
|
server.execute command
|
213
171
|
end
|
172
|
+
end
|
214
173
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
end
|
174
|
+
def cert_clean_result(host, user, cert)
|
175
|
+
cmd = "puppet cert clean #{cert} 2>&1"
|
176
|
+
simple_ssh_command(host, user, cmd, 120)
|
177
|
+
end
|
220
178
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
179
|
+
def r10k_git_result(host, user, repo_location)
|
180
|
+
over_ssh(host: host, user: user, timeout: 120) do |server|
|
181
|
+
# Need to use sudo
|
182
|
+
server.enable_sudo
|
183
|
+
server[repo_location].git :pull
|
184
|
+
end
|
225
185
|
end
|
226
186
|
|
227
187
|
Lita.register_handler(self)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Utils
|
2
|
+
module LitaPuppet
|
3
|
+
# Utility methods for working with PuppetDB
|
4
|
+
module PuppetDB
|
5
|
+
def dbquery(url, q)
|
6
|
+
# TODO: validate incoming query structure
|
7
|
+
client = ::PuppetDB::Client.new(server: url)
|
8
|
+
client.request(*q)
|
9
|
+
end
|
10
|
+
|
11
|
+
def node_roles_and_profiles(url, nodename)
|
12
|
+
# TODO: validate url and nodename
|
13
|
+
::PuppetDB::Client.new(server: url) # this is weird but required
|
14
|
+
d = ::PuppetDB::Client.get("/catalogs/#{nodename}")
|
15
|
+
return d['error'] if d['error']
|
16
|
+
|
17
|
+
tags = []
|
18
|
+
d['data']['resources'].each { |r| tags.concat(r['tags']) }
|
19
|
+
|
20
|
+
# return all the tags related to profile:: or role::
|
21
|
+
tags.sort.uniq.select { |t| t.match(/^(profile|role)::/) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def class_nodes(url, classname)
|
25
|
+
client = ::PuppetDB::Client.new(server: url)
|
26
|
+
q = client.request(
|
27
|
+
'resources',
|
28
|
+
[
|
29
|
+
:and,
|
30
|
+
[:'=', 'type', 'Class'],
|
31
|
+
[:'=', 'title', classname.to_s]
|
32
|
+
]
|
33
|
+
)
|
34
|
+
|
35
|
+
q.data.map { |node| node['certname'] }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Utils
|
2
|
+
module LitaPuppet
|
3
|
+
# Utility methods for doing things over SSH
|
4
|
+
module SSH
|
5
|
+
# Intelligently do some things over SSH
|
6
|
+
def over_ssh(opts = {})
|
7
|
+
raise 'MissingSSHHost' unless opts[:host]
|
8
|
+
raise 'MissingSSHUser' unless opts[:user]
|
9
|
+
opts[:timeout] ||= 300 # default to a 5 minute timeout
|
10
|
+
|
11
|
+
remote = Rye::Box.new(
|
12
|
+
opts[:host],
|
13
|
+
user: opts[:user],
|
14
|
+
auth_methods: ['publickey'],
|
15
|
+
password_prompt: false,
|
16
|
+
error: STDOUT # send STDERR to STDOUT for things that actually print
|
17
|
+
)
|
18
|
+
|
19
|
+
exception = nil
|
20
|
+
|
21
|
+
# Getting serious about not crashing Lita...
|
22
|
+
output = begin
|
23
|
+
# pass our host back to the user to work with
|
24
|
+
Timeout.timeout(opts[:timeout]) { yield remote }
|
25
|
+
rescue Rye::Err, StandardError => e
|
26
|
+
exception = e
|
27
|
+
ensure
|
28
|
+
remote.disconnect
|
29
|
+
end
|
30
|
+
|
31
|
+
calculate_result(output, exception)
|
32
|
+
end
|
33
|
+
|
34
|
+
def calculate_result(output, exception)
|
35
|
+
result = {}
|
36
|
+
if exception
|
37
|
+
result[:exception] = exception
|
38
|
+
else
|
39
|
+
result[:exit_status] = output.exit_status
|
40
|
+
result[:stdout] = output.stdout
|
41
|
+
result[:stderr] = output.stderr
|
42
|
+
end
|
43
|
+
result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Utils
|
2
|
+
module LitaPuppet
|
3
|
+
# Utility methods for manipulating text
|
4
|
+
module Text
|
5
|
+
# Strip off bad characters
|
6
|
+
def sanitze_for_chat(text)
|
7
|
+
# Remove bash colorings
|
8
|
+
text.gsub(/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]/, '')
|
9
|
+
end
|
10
|
+
|
11
|
+
# camel case puppet classes
|
12
|
+
def class_camel(text)
|
13
|
+
text.split('::').map(&:capitalize).join('::')
|
14
|
+
end
|
15
|
+
|
16
|
+
# Format some text as code
|
17
|
+
# Note that this is HipChat specific for the moment
|
18
|
+
# TODO: Make this *not* HipChat specific
|
19
|
+
def as_code(text)
|
20
|
+
'/code ' + sanitze_for_chat(text)
|
21
|
+
end
|
22
|
+
|
23
|
+
def r10k_command(environment, mod)
|
24
|
+
command = 'r10k deploy'
|
25
|
+
if environment && mod
|
26
|
+
command << " module -e #{environment} #{mod} -v"
|
27
|
+
else
|
28
|
+
command << ' environment'
|
29
|
+
command << " #{environment}" if environment
|
30
|
+
command << ' -pv'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def agent_command
|
35
|
+
command = 'puppet agent'
|
36
|
+
command << ' --onetime --verbose --no-daemonize'
|
37
|
+
command << ' --no-usecacheonfailure'
|
38
|
+
command << ' --no-splay --show_diff 2>&1'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lita-puppet.gemspec
CHANGED
@@ -1,26 +1,29 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
|
-
spec.name =
|
3
|
-
spec.version =
|
4
|
-
spec.authors = [
|
5
|
-
spec.email = [
|
6
|
-
spec.description =
|
7
|
-
spec.summary =
|
8
|
-
spec.homepage =
|
2
|
+
spec.name = 'lita-puppet'
|
3
|
+
spec.version = '0.6.2'
|
4
|
+
spec.authors = ['Daniel Schaaff', 'Jonathan Gnagy'].sort
|
5
|
+
spec.email = ['jgnagy@knuedge.com']
|
6
|
+
spec.description = 'Some basic Puppet interactions for Lita'
|
7
|
+
spec.summary = 'Allow the Lita bot to handle requests for puppet tasks'
|
8
|
+
spec.homepage = 'https://github.com/knuedge/lita-puppet'
|
9
9
|
spec.license = 'MIT'
|
10
|
-
spec.metadata = {
|
10
|
+
spec.metadata = { 'lita_plugin_type' => 'handler' }
|
11
11
|
|
12
|
-
spec.files = `git ls-files`.split(
|
12
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
13
13
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
14
14
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
15
|
-
spec.require_paths = [
|
15
|
+
spec.require_paths = ['lib']
|
16
16
|
|
17
|
-
spec.add_runtime_dependency
|
18
|
-
spec.add_runtime_dependency
|
19
|
-
spec.add_runtime_dependency
|
17
|
+
spec.add_runtime_dependency 'lita', '~> 4.7'
|
18
|
+
spec.add_runtime_dependency 'rye'
|
19
|
+
spec.add_runtime_dependency 'puppetdb-ruby'
|
20
20
|
|
21
|
-
spec.add_development_dependency
|
22
|
-
spec.add_development_dependency
|
23
|
-
spec.add_development_dependency
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
22
|
+
spec.add_development_dependency 'pry-byebug'
|
23
|
+
spec.add_development_dependency 'rake'
|
24
|
+
spec.add_development_dependency 'rack-test'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
26
|
+
spec.add_development_dependency 'simplecov'
|
27
|
+
spec.add_development_dependency 'coveralls'
|
28
|
+
spec.add_development_dependency 'rubocop'
|
26
29
|
end
|
data/locales/en.yml
CHANGED
@@ -2,3 +2,43 @@ en:
|
|
2
2
|
lita:
|
3
3
|
handlers:
|
4
4
|
puppet:
|
5
|
+
help:
|
6
|
+
puppet_agent_run:
|
7
|
+
syntax: puppet agent run on <host>
|
8
|
+
desc: Run the puppet agent on <host>.
|
9
|
+
cert_clean:
|
10
|
+
syntax: puppet cert clean <host>
|
11
|
+
desc: Remove all traces of the SSL cert for <host> on the Puppet Master.
|
12
|
+
node_profiles:
|
13
|
+
syntax: puppet catalog <host> profiles
|
14
|
+
desc: Query PuppetDB to get a list of all roles and profiles applied to <host>.
|
15
|
+
nodes_with_class:
|
16
|
+
syntax: puppet class nodes <class>
|
17
|
+
desc: Query PuppetDB to get a list of all nodes containing a class.
|
18
|
+
r10k_deploy:
|
19
|
+
syntax: puppet r10k [env [module]]
|
20
|
+
desc: Deploy the latest puppet code on the puppet master via r10k, optionally specifying an environment, and possibly a module.
|
21
|
+
replies:
|
22
|
+
cert_clean:
|
23
|
+
working: "working on that `puppet cert clean`. I'll get right back to you."
|
24
|
+
failure: "your `puppet cert clean` didn't seem to work... ;-("
|
25
|
+
success: "your `puppet cert clean` is all done!"
|
26
|
+
puppet_agent_run:
|
27
|
+
working: "I'll run puppet right away. Give me a sec and I'll let you know how it goes."
|
28
|
+
failure: "your puppet run is done, but didn't seem to work... I think it may have timed out."
|
29
|
+
success: "that puppet run is complete! It exited with status %{status}."
|
30
|
+
node_profiles:
|
31
|
+
notconf: "I would do that, but I don't know how to connect to PuppetDB. Edit my config and add `config.handlers.puppet.puppetdb_url`."
|
32
|
+
working: "let me see what I can find in PuppetDB for you."
|
33
|
+
failure: "Hmmm, that didn't work. Here's what PuppetDB responded with: '%{error}'"
|
34
|
+
success: "Here are the profiles and roles for %{host}:"
|
35
|
+
nodes_with_class:
|
36
|
+
notconf: "I would do that, but I don't know how to connect to PuppetDB. Edit my config and add `config.handlers.puppet.puppetdb_url`."
|
37
|
+
working: "let me see what I can find in PuppetDB for you."
|
38
|
+
failure: "There are no nodes with %{pclass} class, are you sure it's a valid class?"
|
39
|
+
success: "Here are all the nodes with class %{pclass}:"
|
40
|
+
r10k_deploy:
|
41
|
+
working: "I'll get right on that. Give me a moment and I'll let you know how it went."
|
42
|
+
gitfail: "your r10k run didn't seem to work. Looks like there was a problem with Git:"
|
43
|
+
pupfail: "your r10k run didn't seem to work. Here's what went wrong:"
|
44
|
+
success: "your r10k deployment is done!"
|
@@ -1,4 +1,23 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Lita::Handlers::Puppet, lita_handler: true do
|
4
|
+
it 'routes agent run commands properly' do
|
5
|
+
is_expected.to route_command('puppet agent run on foo').to(:puppet_agent_run)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'routes cert clean commands properly' do
|
9
|
+
is_expected.to route_command('puppet cert clean foo').to(:cert_clean)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'routes catalog profiles commands properly' do
|
13
|
+
is_expected.to route_command('puppet catalog foo profiles').to(:node_profiles)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'routes class nodes commands properly' do
|
17
|
+
is_expected.to route_command('puppet class nodes foo').to(:nodes_with_class)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'routes r10k commands properly' do
|
21
|
+
is_expected.to route_command('puppet r10k').to(:r10k_deploy)
|
22
|
+
end
|
4
23
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'simplecov'
|
2
|
+
# require 'coveralls'
|
3
|
+
SimpleCov.formatters = [
|
4
|
+
SimpleCov::Formatter::HTMLFormatter
|
5
|
+
]
|
6
|
+
SimpleCov.start { add_filter '/spec/' }
|
7
|
+
|
8
|
+
require 'lita-puppet'
|
9
|
+
require 'lita/rspec'
|
3
10
|
|
4
11
|
# A compatibility mode is provided for older plugins upgrading from Lita 3. Since this plugin
|
5
12
|
# was generated with Lita 4, the compatibility mode should be left disabled.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lita-puppet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Schaaff
|
@@ -123,6 +123,48 @@ dependencies:
|
|
123
123
|
- - "~>"
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: '3.0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: simplecov
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
- !ruby/object:Gem::Dependency
|
141
|
+
name: coveralls
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
- !ruby/object:Gem::Dependency
|
155
|
+
name: rubocop
|
156
|
+
requirement: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
type: :development
|
162
|
+
prerelease: false
|
163
|
+
version_requirements: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
126
168
|
description: Some basic Puppet interactions for Lita
|
127
169
|
email:
|
128
170
|
- jgnagy@knuedge.com
|
@@ -131,6 +173,8 @@ extensions: []
|
|
131
173
|
extra_rdoc_files: []
|
132
174
|
files:
|
133
175
|
- ".gitignore"
|
176
|
+
- ".rubocop.yml"
|
177
|
+
- ".travis.yml"
|
134
178
|
- CONTRIBUTING.md
|
135
179
|
- Gemfile
|
136
180
|
- LICENSE
|
@@ -138,9 +182,9 @@ files:
|
|
138
182
|
- Rakefile
|
139
183
|
- lib/lita-puppet.rb
|
140
184
|
- lib/lita/handlers/puppet.rb
|
141
|
-
- lib/utils/puppetdb.rb
|
142
|
-
- lib/utils/ssh.rb
|
143
|
-
- lib/utils/text.rb
|
185
|
+
- lib/utils/lita_puppet/puppetdb.rb
|
186
|
+
- lib/utils/lita_puppet/ssh.rb
|
187
|
+
- lib/utils/lita_puppet/text.rb
|
144
188
|
- lita-puppet.gemspec
|
145
189
|
- locales/en.yml
|
146
190
|
- spec/lita/handlers/puppet_spec.rb
|
data/lib/utils/puppetdb.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
module Utils
|
2
|
-
# Utility methods for working with PuppetDB
|
3
|
-
module PuppetDB
|
4
|
-
def dbquery(url, q)
|
5
|
-
# TODO: validate incoming query structure
|
6
|
-
client = ::PuppetDB::Client.new(server: url)
|
7
|
-
client.request *q
|
8
|
-
end
|
9
|
-
|
10
|
-
def node_roles_and_profiles(url, nodename)
|
11
|
-
# TODO: validate url and nodename
|
12
|
-
::PuppetDB::Client.new(server: url) # this is weird but required
|
13
|
-
d = ::PuppetDB::Client.get("/catalogs/#{nodename}")
|
14
|
-
return d["error"] if d['error']
|
15
|
-
|
16
|
-
tags = []
|
17
|
-
d["data"]["resources"].each {|r| tags.concat(r['tags'])}
|
18
|
-
|
19
|
-
# return all the tags related to profile:: or role::
|
20
|
-
tags.sort.uniq.select {|t| t.match /^(profile|role)::/ }
|
21
|
-
end
|
22
|
-
|
23
|
-
def class_nodes(url, classname)
|
24
|
-
client = ::PuppetDB::Client.new(server: url)
|
25
|
-
q = client.request('resources',[:and,[:'=','type', 'Class'],[:'=','title',"#{classname}"]])
|
26
|
-
|
27
|
-
q.data.map { |node| node['certname'] }
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
end
|
data/lib/utils/ssh.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
module Utils
|
2
|
-
# Utility methods for doing things over SSH
|
3
|
-
module SSH
|
4
|
-
# Intelligently do some things over SSH
|
5
|
-
def over_ssh(opts = {})
|
6
|
-
result = {}
|
7
|
-
fail "MissingSSHHost" unless opts[:host]
|
8
|
-
fail "MissingSSHUser" unless opts[:user]
|
9
|
-
opts[:timeout] ||= 300 # default to a 5 minute timeout
|
10
|
-
|
11
|
-
remote = Rye::Box.new(
|
12
|
-
opts[:host],
|
13
|
-
user: opts[:user],
|
14
|
-
auth_methods: ['publickey'],
|
15
|
-
password_prompt: false,
|
16
|
-
error: STDOUT # send STDERR to STDOUT for things that actually print
|
17
|
-
)
|
18
|
-
|
19
|
-
exception = nil
|
20
|
-
|
21
|
-
# Getting serious about not crashing Lita...
|
22
|
-
output = begin
|
23
|
-
Timeout::timeout(opts[:timeout]) do
|
24
|
-
yield remote # pass our host back to the user to work with
|
25
|
-
end
|
26
|
-
rescue Rye::Err => e
|
27
|
-
exception = e
|
28
|
-
rescue StandardError => e
|
29
|
-
exception = e
|
30
|
-
rescue Exception => e
|
31
|
-
exception = e
|
32
|
-
ensure
|
33
|
-
remote.disconnect
|
34
|
-
end
|
35
|
-
|
36
|
-
if exception
|
37
|
-
result[:exception] = exception
|
38
|
-
else
|
39
|
-
result[:exit_status] = output.exit_status
|
40
|
-
result[:stdout] = output.stdout
|
41
|
-
result[:stderr] = output.stderr
|
42
|
-
end
|
43
|
-
return result
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
data/lib/utils/text.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
module Utils
|
2
|
-
# Utility methods for manipulating text
|
3
|
-
module Text
|
4
|
-
# Try to make names more friendly
|
5
|
-
def friendly_name(long_name)
|
6
|
-
long_name.split(/\s/).first
|
7
|
-
end
|
8
|
-
|
9
|
-
# Strip off bad characters
|
10
|
-
def sanitze_for_chat(text)
|
11
|
-
# Remove bash colorings
|
12
|
-
text.gsub(/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]/, '')
|
13
|
-
end
|
14
|
-
|
15
|
-
# camel case puppet classes
|
16
|
-
def class_camel(text)
|
17
|
-
text.split('::').map(&:capitalize).join('::')
|
18
|
-
end
|
19
|
-
|
20
|
-
# Format some text as code
|
21
|
-
# Note that this is HipChat specific for the moment
|
22
|
-
# TODO: Make this *not* HipChat specific
|
23
|
-
def as_code(text)
|
24
|
-
"/code " + sanitze_for_chat(text)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|