ml-puppetdb-terminus 3.2.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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +202 -0
  3. data/NOTICE.txt +17 -0
  4. data/README.md +22 -0
  5. data/puppet/lib/puppet/application/storeconfigs.rb +4 -0
  6. data/puppet/lib/puppet/face/node/deactivate.rb +37 -0
  7. data/puppet/lib/puppet/face/node/status.rb +80 -0
  8. data/puppet/lib/puppet/face/storeconfigs.rb +193 -0
  9. data/puppet/lib/puppet/indirector/catalog/puppetdb.rb +400 -0
  10. data/puppet/lib/puppet/indirector/facts/puppetdb.rb +152 -0
  11. data/puppet/lib/puppet/indirector/facts/puppetdb_apply.rb +25 -0
  12. data/puppet/lib/puppet/indirector/node/puppetdb.rb +19 -0
  13. data/puppet/lib/puppet/indirector/resource/puppetdb.rb +108 -0
  14. data/puppet/lib/puppet/reports/puppetdb.rb +188 -0
  15. data/puppet/lib/puppet/util/puppetdb.rb +108 -0
  16. data/puppet/lib/puppet/util/puppetdb/char_encoding.rb +316 -0
  17. data/puppet/lib/puppet/util/puppetdb/command.rb +116 -0
  18. data/puppet/lib/puppet/util/puppetdb/command_names.rb +8 -0
  19. data/puppet/lib/puppet/util/puppetdb/config.rb +148 -0
  20. data/puppet/lib/puppet/util/puppetdb/http.rb +121 -0
  21. data/puppet/spec/README.markdown +8 -0
  22. data/puppet/spec/spec.opts +6 -0
  23. data/puppet/spec/spec_helper.rb +38 -0
  24. data/puppet/spec/unit/face/node/deactivate_spec.rb +28 -0
  25. data/puppet/spec/unit/face/node/status_spec.rb +43 -0
  26. data/puppet/spec/unit/face/storeconfigs_spec.rb +199 -0
  27. data/puppet/spec/unit/indirector/catalog/puppetdb_spec.rb +703 -0
  28. data/puppet/spec/unit/indirector/facts/puppetdb_apply_spec.rb +27 -0
  29. data/puppet/spec/unit/indirector/facts/puppetdb_spec.rb +347 -0
  30. data/puppet/spec/unit/indirector/node/puppetdb_spec.rb +61 -0
  31. data/puppet/spec/unit/indirector/resource/puppetdb_spec.rb +199 -0
  32. data/puppet/spec/unit/reports/puppetdb_spec.rb +249 -0
  33. data/puppet/spec/unit/util/puppetdb/char_encoding_spec.rb +212 -0
  34. data/puppet/spec/unit/util/puppetdb/command_spec.rb +98 -0
  35. data/puppet/spec/unit/util/puppetdb/config_spec.rb +227 -0
  36. data/puppet/spec/unit/util/puppetdb/http_spec.rb +138 -0
  37. data/puppet/spec/unit/util/puppetdb_spec.rb +33 -0
  38. metadata +115 -0
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+ require 'puppet/reports'
5
+ require 'net/http'
6
+ require 'puppet/network/http_pool'
7
+ require 'puppet/util/puppetdb/command_names'
8
+ require 'puppet/util/puppetdb/config'
9
+ require 'json'
10
+
11
+ processor = Puppet::Reports.report(:puppetdb)
12
+
13
+ describe processor do
14
+
15
+ subject {
16
+ s = Puppet::Transaction::Report.new("foo").extend(processor)
17
+ s.configuration_version = 123456789
18
+ s.environment = "foo"
19
+ s
20
+ }
21
+
22
+ context "#process" do
23
+
24
+ let(:http) { mock "http" }
25
+ let(:httpok) { Net::HTTPOK.new('1.1', 200, '') }
26
+
27
+ def without_producer_timestamp(json_body)
28
+ parsed = JSON.parse(json_body)
29
+ parsed["payload"].delete("producer_timestamp")
30
+ parsed.to_json
31
+ end
32
+
33
+ it "should POST the report command as a URL-encoded JSON string" do
34
+ httpok.stubs(:body).returns '{"uuid": "a UUID"}'
35
+ subject.stubs(:run_duration).returns(10)
36
+
37
+ expected_body = {
38
+ :command => Puppet::Util::Puppetdb::CommandNames::CommandStoreReport,
39
+ :version => 6,
40
+ :payload => subject.send(:report_to_hash)
41
+ }.to_json
42
+
43
+ Puppet::Network::HttpPool.expects(:http_instance).returns(http)
44
+ http.expects(:post).with {|path, body, headers|
45
+ expect(path).to include(Puppet::Util::Puppetdb::Command::CommandsUrl)
46
+
47
+ # producer_timestamp is generated at submission time, so remove it from
48
+ # the comparison
49
+ expect(without_producer_timestamp(body)).to eq(without_producer_timestamp(expected_body))
50
+ }.returns(httpok)
51
+
52
+ subject.process
53
+ end
54
+ end
55
+
56
+ context "#report_to_hash" do
57
+ let (:resource) {
58
+ stub("resource",
59
+ { :pathbuilder => ["foo", "bar", "baz"],
60
+ :path => "foo",
61
+ :file => "foo",
62
+ :line => 1,
63
+ :tags => [],
64
+ :title => "foo",
65
+ :type => "foo" })
66
+ }
67
+
68
+ let (:status) {
69
+ Puppet::Resource::Status.new(resource)
70
+ }
71
+
72
+ before :each do
73
+ subject.add_resource_status(status)
74
+ end
75
+
76
+ it "should include the transaction uuid or nil" do
77
+ subject.transaction_uuid = 'abc123'
78
+ result = subject.send(:report_to_hash)
79
+ result["transaction_uuid"].should == 'abc123'
80
+ end
81
+
82
+ context "start/end time" do
83
+ before :each do
84
+ subject.add_metric("time", {"total" => 10})
85
+ end
86
+
87
+ it "should base run duration off of the 'time'->'total' metric" do
88
+ subject.send(:run_duration).should == 10
89
+ end
90
+
91
+ it "should use run_duration to calculate the end_time" do
92
+ result = subject.send(:report_to_hash)
93
+ duration = Time.parse(result["end_time"]) - Time.parse(result["start_time"])
94
+ duration.should == subject.send(:run_duration)
95
+ end
96
+ end
97
+
98
+ context "events" do
99
+ before :each do
100
+ subject.stubs(:run_duration).returns(10)
101
+ end
102
+
103
+ context "resource without events" do
104
+ it "should not include the resource" do
105
+ result = subject.send(:report_to_hash)
106
+ # the server will populate the report id, so we validate that the
107
+ # client doesn't include one
108
+ result.has_key?("report").should be_false
109
+ result["certname"].should == subject.host
110
+ result["puppet_version"].should == subject.puppet_version
111
+ result["report_format"].should == subject.report_format
112
+ result["configuration_version"].should == subject.configuration_version.to_s
113
+ result["resources"].should == []
114
+ result["noop"].should == false
115
+ end
116
+ end
117
+
118
+ context "resource with events" do
119
+ it "should include the resource" do
120
+
121
+ event = Puppet::Transaction::Event.new()
122
+ event.property = "fooprop"
123
+ event.desired_value = "fooval"
124
+ event.previous_value = "oldfooval"
125
+ event.message = "foomessage"
126
+ status.add_event(event)
127
+
128
+ result = subject.send(:report_to_hash)
129
+
130
+ result["resources"].length.should == 1
131
+ res = result["resources"][0]
132
+ res["resource_type"].should == "Foo"
133
+ res["resource_title"].should == "foo"
134
+ res["file"].should == "foo"
135
+ res["line"].should == 1
136
+ res["containment_path"].should == ["foo", "bar", "baz"]
137
+ res["events"].length.should == 1
138
+
139
+ res_event = res["events"][0]
140
+ res_event["property"].should == "fooprop"
141
+ res_event["new_value"].should == "fooval"
142
+ res_event["old_value"].should == "oldfooval"
143
+ res_event["message"].should == "foomessage"
144
+ end
145
+ end
146
+
147
+ context "skipped resource status" do
148
+ it "should include the resource" do
149
+ status.skipped = true
150
+ result = subject.send(:report_to_hash)
151
+
152
+ result["resources"].length.should == 1
153
+ resource = result["resources"][0]
154
+ resource["resource_type"].should == "Foo"
155
+ resource["resource_title"].should == "foo"
156
+ resource["containment_path"].should == ["foo", "bar", "baz"]
157
+ resource["events"].length.should == 0
158
+ end
159
+ end
160
+
161
+ context "failed resource status" do
162
+ before :each do
163
+ status.stubs(:failed).returns(true)
164
+ end
165
+
166
+ context "with no events" do
167
+ it "should have no events" do
168
+ result = subject.send(:report_to_hash)
169
+ result["resources"].length.should == 0
170
+ end
171
+ end
172
+
173
+ context "with events" do
174
+ it "should include the actual event" do
175
+ event = Puppet::Transaction::Event.new
176
+ event.property = "barprop"
177
+ event.desired_value = "barval"
178
+ event.previous_value = "oldbarval"
179
+ event.message = "barmessage"
180
+ status.add_event(event)
181
+
182
+ result = subject.send(:report_to_hash)
183
+ result["resources"].length.should == 1
184
+ resource = result["resources"][0]
185
+ resource["resource_type"].should == "Foo"
186
+ resource["resource_title"].should == "foo"
187
+ resource["file"].should == "foo"
188
+ resource["line"].should == 1
189
+ resource["containment_path"].should == ["foo", "bar", "baz"]
190
+ resource["events"].length.should == 1
191
+
192
+ res_event = resource["events"][0]
193
+ res_event["property"].should == "barprop"
194
+ res_event["new_value"].should == "barval"
195
+ res_event["old_value"].should == "oldbarval"
196
+ res_event["message"].should == "barmessage"
197
+ end
198
+ end
199
+ end
200
+
201
+ context "with unchanged resources turned on" do
202
+ let (:config) {
203
+ Puppet::Util::Puppetdb.config
204
+ }
205
+
206
+ before :each do
207
+ config.stubs(:include_unchanged_resources?).returns(true)
208
+
209
+ notify_resource =
210
+ stub("notify_resource",
211
+ { :pathbuilder => ["foo", "bar", "baz"],
212
+ :path => "foo",
213
+ :file => "foo",
214
+ :line => "foo",
215
+ :tags => [],
216
+ :type => "Notify",
217
+ :title => "Hello there" })
218
+ notify_status = Puppet::Resource::Status.new(notify_resource)
219
+ notify_status.changed = false
220
+ subject.add_resource_status(notify_status)
221
+
222
+ event = Puppet::Transaction::Event.new()
223
+ event.property = "fooprop"
224
+ event.desired_value = "fooval"
225
+ event.previous_value = "oldfooval"
226
+ event.message = "foomessage"
227
+ status.add_event(event)
228
+ end
229
+
230
+ context "with an unchanged resource" do
231
+ it "should include the actual event" do
232
+ result = subject.send(:report_to_hash)
233
+ unchanged_resources = result["resources"].select { |res| res["events"].empty? and ! (res["skipped"])}
234
+ unchanged_resources.length.should == 1
235
+ resource = unchanged_resources[0]
236
+ resource["resource_type"].should == "Notify"
237
+ resource["resource_title"].should == "Hello there"
238
+ resource["file"].should == "foo"
239
+ resource["line"].should == "foo"
240
+ resource["containment_path"].should == ["foo", "bar", "baz"]
241
+ resource["events"].length.should == 0
242
+ end
243
+ end
244
+
245
+ end
246
+
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require 'spec_helper'
5
+
6
+ require 'puppet/util/puppetdb/char_encoding'
7
+
8
+ describe Puppet::Util::Puppetdb::CharEncoding do
9
+ describe "#ruby_18_clean_utf8", :if => RUBY_VERSION =~ /^1.8/ do
10
+
11
+ def test_utf8_clean(in_bytes, expected_bytes)
12
+ instr = in_bytes.pack('c*')
13
+ out = described_class.ruby18_clean_utf8(instr)
14
+ out.should == expected_bytes.pack('c*')
15
+ end
16
+
17
+
18
+ it "should recognize (and not modify) valid multi-byte characters" do
19
+ in_bytes = [0xE2, 0x9B, 0x87]
20
+ expected_bytes = [0xE2, 0x9B, 0x87]
21
+ test_utf8_clean(in_bytes, expected_bytes)
22
+ end
23
+
24
+ Utf8ReplacementChar = [0xEF, 0xBF, 0xBD]
25
+ it "should strip invalid UTF-8 characters from an invalid multi-byte sequence" do
26
+ in_bytes = [0xE2, 0xCB, 0x87]
27
+ test_utf8_clean(in_bytes, [0xCB, 0x87])
28
+ end
29
+
30
+ it "should strip incomplete multi-byte characters" do
31
+ in_bytes = [0xE2, 0x9B]
32
+ test_utf8_clean(in_bytes, [])
33
+ end
34
+
35
+ it "should replace invalid characters with the unicode replacement character" do
36
+ # This is related to ticket #14873; our utf8_string code for 1.9 is being
37
+ # much more aggressive about replacing bytes with the unicode replacement char;
38
+ # it appears to be more correct, as the way that the 1.8/IConv approach
39
+ # was handling it was causing certain strings to decode differently in
40
+ # clojure, thus causing checksum errors.
41
+ in_bytes = [0x21, 0x7F, 0xFD, 0x80, 0xBD, 0xBB, 0xB6, 0xA1]
42
+ expected_bytes = [0x21, 0x7F]
43
+ test_utf8_clean(in_bytes, expected_bytes)
44
+ end
45
+
46
+ # A multi-byte sequence beginning with any of the following bytes is
47
+ # illegal. For more info, see http://en.wikipedia.org/wiki/UTF-8
48
+ [[[0xC0, 0xC1], 2],
49
+ [[0xF5, 0xF6, 0xF7], 4],
50
+ [[0xF8, 0xF9, 0xFA, 0xFB], 5],
51
+ [[0xFC, 0xFD, 0xFE, 0xFF], 6]].each do |bytes, num_bytes|
52
+ bytes.each do |first_byte|
53
+ it "should strip the invalid bytes from a #{num_bytes}-byte character starting with 0x#{first_byte.to_s(16)}" do
54
+ in_bytes = [first_byte]
55
+ (num_bytes - 1).times { in_bytes << 0x80 }
56
+ test_utf8_clean(in_bytes, [])
57
+ end
58
+ end
59
+ end
60
+
61
+ context "when dealing with multi-byte sequences beginning with 0xF4" do
62
+ it "should accept characters that are below the 0x10FFFF limit of Unicode" do
63
+ in_bytes = [0xF4, 0x8f, 0xbf, 0xbf]
64
+ expected_bytes = [0xF4, 0x8f, 0xbf, 0xbf]
65
+ test_utf8_clean(in_bytes, expected_bytes)
66
+ end
67
+
68
+ it "should reject characters that are above the 0x10FFFF limit of Unicode" do
69
+ in_bytes = [0xF4, 0x90, 0xbf, 0xbf]
70
+ test_utf8_clean(in_bytes, [])
71
+ end
72
+ end
73
+ end
74
+
75
+
76
+ describe "#utf8_string" do
77
+ describe "on ruby 1.8", :if => RUBY_VERSION =~ /^1.8/ do
78
+ it "should convert from ascii without a warning" do
79
+ Puppet.expects(:warning).never
80
+
81
+ str = "any ascii string"
82
+ subject.utf8_string(str, nil).should == str
83
+ end
84
+
85
+ it "should strip invalid chars from non-overlapping latin-1 with a warning" do
86
+ Puppet.expects(:warning).with {|msg| msg =~ /Ignoring invalid UTF-8 byte sequences/}
87
+
88
+ str = "a latin-1 string \xd6"
89
+ subject.utf8_string(str, nil).should == "a latin-1 string "
90
+ end
91
+
92
+ it "should strip invalid chars and warn if the string is invalid UTF-8" do
93
+ Puppet.expects(:warning).with {|msg| msg =~ /Ignoring invalid UTF-8 byte sequences/}
94
+
95
+ str = "an invalid utf-8 string \xff"
96
+ subject.utf8_string(str, nil).should == "an invalid utf-8 string "
97
+ end
98
+
99
+ it "should return a valid utf-8 string without warning" do
100
+ Puppet.expects(:warning).never
101
+
102
+ str = "a valid utf-8 string \xc3\x96"
103
+ subject.utf8_string(str, nil).should == str
104
+ end
105
+ end
106
+
107
+ describe "on ruby > 1.8", :if => RUBY_VERSION !~ /^1.8/ do
108
+ it "should convert from ascii without a warning" do
109
+ Puppet.expects(:warning).never
110
+
111
+ str = "any ascii string".force_encoding('us-ascii')
112
+ subject.utf8_string(str, nil).should == str
113
+ end
114
+
115
+ it "should convert from latin-1 without a warning" do
116
+ Puppet.expects(:warning).never
117
+
118
+ str = "a latin-1 string Ö".force_encoding('ASCII-8BIT')
119
+ subject.utf8_string(str, nil).should == "a latin-1 string Ö"
120
+ end
121
+
122
+ # UndefinedConversionError
123
+ it "should replace undefined characters and warn when converting from binary" do
124
+ Puppet.expects(:warning).with {|msg| msg =~ /Error with command ignoring invalid UTF-8 byte sequences/}
125
+
126
+ str = "an invalid binary string \xff".force_encoding('binary')
127
+ # \ufffd == unicode replacement character
128
+ subject.utf8_string(str, "Error with command").should == "an invalid binary string \ufffd"
129
+ end
130
+
131
+ # InvalidByteSequenceError
132
+ it "should replace undefined characters and warn if the string is invalid UTF-8" do
133
+ Puppet.expects(:warning).with {|msg| msg =~ /Error with command ignoring invalid UTF-8 byte sequences/}
134
+
135
+ str = "an invalid utf-8 string \xff".force_encoding('utf-8')
136
+ subject.utf8_string(str, "Error with command").should == "an invalid utf-8 string \ufffd"
137
+ end
138
+
139
+ it "should leave the string alone if it's valid UTF-8" do
140
+ Puppet.expects(:warning).never
141
+
142
+ str = "a valid utf-8 string".force_encoding('utf-8')
143
+ subject.utf8_string(str, nil).should == str
144
+ end
145
+
146
+ it "should leave the string alone if it's valid UTF-8 with non-ascii characters" do
147
+ Puppet.expects(:warning).never
148
+
149
+ str = "a valid utf-8 string Ö"
150
+ subject.utf8_string(str.dup.force_encoding('ASCII-8BIT'), nil).should == str
151
+ end
152
+
153
+ describe "Debug log testing of bad data" do
154
+ let!(:existing_log_level){ Puppet[:log_level]}
155
+
156
+ before :each do
157
+ Puppet[:log_level] = "debug"
158
+ end
159
+
160
+ after :each do
161
+ Puppet[:log_level] = "notice"
162
+ end
163
+
164
+ it "should emit a warning and debug messages when bad characters are found" do
165
+ Puppet[:log_level] = "debug"
166
+ Puppet.expects(:warning).with {|msg| msg =~ /Error encoding a 'replace facts' command for host 'foo.com' ignoring invalid/}
167
+ Puppet.expects(:debug).with do |msg|
168
+ msg =~ /Error encoding a 'replace facts' command for host 'foo.com'/ &&
169
+ msg =~ /'some valid string' followed by 1 invalid\/undefined bytes then ''/
170
+ end
171
+
172
+ # This will create a UTF-8 string literal, then switch to ASCII-8Bit when the bad
173
+ # bytes are concated on below
174
+ str = "some valid string" << [192].pack('c*')
175
+ subject.utf8_string(str, "Error encoding a 'replace facts' command for host 'foo.com'").should == "some valid string\ufffd"
176
+ end
177
+ end
178
+
179
+ it "should emit a warning and no debug messages" do
180
+ Puppet.expects(:warning).with {|msg| msg =~ /Error on replace catalog ignoring invalid UTF-8 byte sequences/}
181
+ Puppet.expects(:debug).never
182
+ str = "some valid string" << [192].pack('c*')
183
+ subject.utf8_string(str, "Error on replace catalog").should == "some valid string\ufffd"
184
+ end
185
+ end
186
+ end
187
+
188
+ describe "on ruby > 1.8", :if => RUBY_VERSION !~ /^1.8/ do
189
+ it "finds all index of a given character" do
190
+ described_class.all_indexes_of_char("a\u2192b\u2192c\u2192d\u2192", "\u2192").should == [1, 3, 5, 7]
191
+ described_class.all_indexes_of_char("abcd", "\u2192").should == []
192
+ end
193
+
194
+ it "should collapse consecutive integers into ranges" do
195
+ described_class.collapse_ranges((1..5).to_a).should == [1..5]
196
+ described_class.collapse_ranges([]).should == []
197
+ described_class.collapse_ranges([1,2,3,5,7,8,9]).should == [1..3, 5..5, 7..9]
198
+ end
199
+
200
+ it "gives error context around each bad character" do
201
+ described_class.error_char_context("abc\ufffddef", [3]).should ==
202
+ ["'abc' followed by 1 invalid/undefined bytes then 'def'"]
203
+
204
+ described_class.error_char_context("abc\ufffd\ufffd\ufffd\ufffddef", [3,4,5,6]).should ==
205
+ ["'abc' followed by 4 invalid/undefined bytes then 'def'"]
206
+
207
+ described_class.error_char_context("abc\ufffddef\ufffdg", [3, 7]).should ==
208
+ ["'abc' followed by 1 invalid/undefined bytes then 'def'",
209
+ "'def' followed by 1 invalid/undefined bytes then 'g'"]
210
+ end
211
+ end
212
+ end