puppet 6.12.0 → 6.13.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +12 -12
  3. data/README.md +1 -1
  4. data/ext/project_data.yaml +1 -1
  5. data/lib/puppet.rb +22 -7
  6. data/lib/puppet/application/resource.rb +1 -1
  7. data/lib/puppet/configurer.rb +8 -13
  8. data/lib/puppet/defaults.rb +83 -49
  9. data/lib/puppet/environments.rb +26 -18
  10. data/lib/puppet/face/facts.rb +8 -5
  11. data/lib/puppet/file_system/memory_file.rb +6 -0
  12. data/lib/puppet/file_system/memory_impl.rb +13 -0
  13. data/lib/puppet/file_system/windows.rb +7 -10
  14. data/lib/puppet/http.rb +2 -0
  15. data/lib/puppet/http/client.rb +30 -0
  16. data/lib/puppet/http/errors.rb +2 -0
  17. data/lib/puppet/http/service.rb +61 -2
  18. data/lib/puppet/http/service/compiler.rb +86 -0
  19. data/lib/puppet/http/service/file_server.rb +85 -0
  20. data/lib/puppet/http/service/report.rb +4 -8
  21. data/lib/puppet/http/session.rb +8 -1
  22. data/lib/puppet/indirector/catalog/compiler.rb +10 -0
  23. data/lib/puppet/indirector/file_bucket_file/file.rb +1 -1
  24. data/lib/puppet/indirector/json.rb +1 -1
  25. data/lib/puppet/indirector/msgpack.rb +1 -1
  26. data/lib/puppet/network/http/connection.rb +4 -0
  27. data/lib/puppet/network/http/nocache_pool.rb +1 -0
  28. data/lib/puppet/network/http/pool.rb +5 -1
  29. data/lib/puppet/parser/ast/pops_bridge.rb +6 -11
  30. data/lib/puppet/pops/evaluator/access_operator.rb +2 -2
  31. data/lib/puppet/pops/evaluator/evaluator_impl.rb +1 -1
  32. data/lib/puppet/pops/loader/puppet_plan_instantiator.rb +12 -3
  33. data/lib/puppet/pops/parser/evaluating_parser.rb +5 -7
  34. data/lib/puppet/pops/types/p_object_type_extension.rb +10 -0
  35. data/lib/puppet/pops/types/type_calculator.rb +24 -0
  36. data/lib/puppet/pops/validation/checker4_0.rb +1 -1
  37. data/lib/puppet/pops/validation/tasks_checker.rb +5 -1
  38. data/lib/puppet/provider/aix_object.rb +4 -2
  39. data/lib/puppet/provider/group/aix.rb +1 -0
  40. data/lib/puppet/provider/group/groupadd.rb +52 -24
  41. data/lib/puppet/provider/package/apt.rb +14 -3
  42. data/lib/puppet/provider/package/dnfmodule.rb +9 -2
  43. data/lib/puppet/provider/package/dpkg.rb +14 -7
  44. data/lib/puppet/provider/package/fink.rb +20 -3
  45. data/lib/puppet/provider/package/openbsd.rb +13 -1
  46. data/lib/puppet/provider/package/pkg.rb +18 -5
  47. data/lib/puppet/provider/package/yum.rb +9 -5
  48. data/lib/puppet/provider/user/aix.rb +1 -0
  49. data/lib/puppet/provider/user/directoryservice.rb +30 -5
  50. data/lib/puppet/provider/user/useradd.rb +6 -7
  51. data/lib/puppet/reports/store.rb +1 -1
  52. data/lib/puppet/settings.rb +2 -0
  53. data/lib/puppet/ssl/certificate.rb +2 -1
  54. data/lib/puppet/test/test_helper.rb +4 -0
  55. data/lib/puppet/transaction/resource_harness.rb +1 -1
  56. data/lib/puppet/type/group.rb +2 -2
  57. data/lib/puppet/type/package.rb +63 -9
  58. data/lib/puppet/type/user.rb +2 -2
  59. data/lib/puppet/util/log/destinations.rb +1 -1
  60. data/lib/puppet/util/pidlock.rb +26 -6
  61. data/lib/puppet/util/plist.rb +6 -0
  62. data/lib/puppet/util/storage.rb +0 -1
  63. data/lib/puppet/util/yaml.rb +1 -1
  64. data/lib/puppet/version.rb +1 -1
  65. data/locales/puppet.pot +127 -115
  66. data/man/man5/puppet.conf.5 +21 -7
  67. data/man/man8/puppet-agent.8 +1 -1
  68. data/man/man8/puppet-apply.8 +1 -1
  69. data/man/man8/puppet-catalog.8 +1 -1
  70. data/man/man8/puppet-config.8 +1 -1
  71. data/man/man8/puppet-describe.8 +1 -1
  72. data/man/man8/puppet-device.8 +1 -1
  73. data/man/man8/puppet-doc.8 +1 -1
  74. data/man/man8/puppet-epp.8 +1 -1
  75. data/man/man8/puppet-facts.8 +1 -1
  76. data/man/man8/puppet-filebucket.8 +1 -1
  77. data/man/man8/puppet-generate.8 +1 -1
  78. data/man/man8/puppet-help.8 +1 -1
  79. data/man/man8/puppet-key.8 +1 -1
  80. data/man/man8/puppet-lookup.8 +1 -1
  81. data/man/man8/puppet-man.8 +1 -1
  82. data/man/man8/puppet-module.8 +1 -1
  83. data/man/man8/puppet-node.8 +1 -1
  84. data/man/man8/puppet-parser.8 +1 -1
  85. data/man/man8/puppet-plugin.8 +1 -1
  86. data/man/man8/puppet-report.8 +1 -1
  87. data/man/man8/puppet-resource.8 +1 -1
  88. data/man/man8/puppet-script.8 +1 -1
  89. data/man/man8/puppet-ssl.8 +1 -1
  90. data/man/man8/puppet-status.8 +1 -1
  91. data/man/man8/puppet.8 +2 -2
  92. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_fetch_if_not_on_the_local_disk.yml +0 -35
  93. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_not_update_if_content_on_disk_is_up-to-date.yml +0 -37
  94. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_update_if_content_differs_on_disk.yml +0 -37
  95. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_fetch_if_mtime_is_older_on_disk.yml +0 -35
  96. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_fetch_if_no_header_specified.yml +0 -33
  97. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_fetch_if_not_on_the_local_disk.yml +0 -35
  98. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_not_update_if_mtime_is_newer_on_disk.yml +0 -35
  99. data/spec/integration/configurer_spec.rb +26 -7
  100. data/spec/integration/indirector/facts/facter_spec.rb +4 -0
  101. data/spec/unit/application/apply_spec.rb +2 -12
  102. data/spec/unit/application/resource_spec.rb +2 -2
  103. data/spec/unit/configurer/fact_handler_spec.rb +0 -4
  104. data/spec/unit/configurer_spec.rb +0 -3
  105. data/spec/unit/defaults_spec.rb +1 -1
  106. data/spec/unit/environments_spec.rb +57 -28
  107. data/spec/unit/face/facts_spec.rb +24 -20
  108. data/spec/unit/file_system_spec.rb +16 -2
  109. data/spec/unit/http/client_spec.rb +6 -0
  110. data/spec/unit/http/service/compiler_spec.rb +322 -0
  111. data/spec/unit/http/service/file_server_spec.rb +219 -0
  112. data/spec/unit/http/service/report_spec.rb +8 -1
  113. data/spec/unit/http/service_spec.rb +4 -0
  114. data/spec/unit/http/session_spec.rb +31 -0
  115. data/spec/unit/indirector/catalog/compiler_spec.rb +46 -29
  116. data/spec/unit/network/http/connection_spec.rb +23 -1
  117. data/spec/unit/network/http/nocache_pool_spec.rb +3 -3
  118. data/spec/unit/network/http/pool_spec.rb +32 -0
  119. data/spec/unit/node/facts_spec.rb +2 -1
  120. data/spec/unit/node_spec.rb +7 -4
  121. data/spec/unit/pops/serialization/to_from_hr_spec.rb +6 -1
  122. data/spec/unit/pops/validator/validator_spec.rb +7 -2
  123. data/spec/unit/provider/aix_object_spec.rb +16 -2
  124. data/spec/unit/provider/group/groupadd_spec.rb +167 -56
  125. data/spec/unit/provider/package/apt_spec.rb +13 -2
  126. data/spec/unit/provider/package/aptitude_spec.rb +1 -0
  127. data/spec/unit/provider/package/dnfmodule_spec.rb +22 -0
  128. data/spec/unit/provider/package/dpkg_spec.rb +28 -6
  129. data/spec/unit/provider/package/openbsd_spec.rb +17 -0
  130. data/spec/unit/provider/package/pkg_spec.rb +15 -1
  131. data/spec/unit/provider/package/yum_spec.rb +50 -0
  132. data/spec/unit/provider/user/directoryservice_spec.rb +41 -0
  133. data/spec/unit/provider/user/useradd_spec.rb +13 -8
  134. data/spec/unit/puppet_pal_2pec.rb +3 -0
  135. data/spec/unit/puppet_pal_catalog_spec.rb +3 -0
  136. data/spec/unit/puppet_spec.rb +14 -0
  137. data/spec/unit/ssl/certificate_spec.rb +7 -0
  138. data/spec/unit/transaction/persistence_spec.rb +1 -10
  139. data/spec/unit/type/package_spec.rb +8 -0
  140. data/spec/unit/type/user_spec.rb +0 -1
  141. data/spec/unit/util/pidlock_spec.rb +38 -16
  142. data/spec/unit/util/plist_spec.rb +20 -0
  143. data/spec/unit/util/storage_spec.rb +1 -8
  144. metadata +10 -4
@@ -12,9 +12,12 @@ describe Puppet::Face[:facts, '0.0.1'] do
12
12
  let(:model) { Puppet::Node::Facts }
13
13
  let(:test_data) { model.new('puppet.node.test', {test_fact: 'test value'}) }
14
14
  let(:facter_terminus) { model.indirection.terminus(:facter) }
15
- let(:rest_terminus) { model.indirection.terminus(:rest) }
16
15
 
17
16
  before(:each) do
17
+ Puppet[:facts_terminus] = :memory
18
+ Puppet::Node::Facts.indirection.save(test_data)
19
+ allow(Puppet::Node::Facts.indirection).to receive(:terminus_class=).with(:facter)
20
+
18
21
  Puppet.settings.parse_config(<<-CONF)
19
22
  [main]
20
23
  server=puppet.server.invalid
@@ -26,45 +29,46 @@ CONF
26
29
 
27
30
  # Faces start in :user run mode
28
31
  Puppet.settings.preferred_run_mode = :user
29
-
30
- allow(facter_terminus).to receive(:find).with(instance_of(Puppet::Indirector::Request)).and_return(test_data)
31
- allow(rest_terminus).to receive(:save).with(instance_of(Puppet::Indirector::Request)).and_return(nil)
32
- end
33
-
34
- it { is_expected.to be_action :upload }
35
-
36
- it "finds facts from terminus_class :facter" do
37
- expect(facter_terminus).to receive(:find).with(instance_of(Puppet::Indirector::Request)).and_return(test_data)
38
-
39
- subject.upload
40
32
  end
41
33
 
42
- it "saves facts to terminus_class :rest" do
43
- expect(rest_terminus).to receive(:save).with(instance_of(Puppet::Indirector::Request)).and_return(nil)
34
+ it "uploads facts as application/json" do
35
+ stub_request(:put, 'https://puppet.server.test:8140/puppet/v3/facts/puppet.node.test?environment=*root*')
36
+ .with(
37
+ headers: { 'Content-Type' => 'application/json' },
38
+ body: hash_including(
39
+ {
40
+ "name" => "puppet.node.test",
41
+ "values" => {
42
+ "test_fact" => "test value"
43
+ }
44
+ }
45
+ )
46
+ )
44
47
 
45
48
  subject.upload
46
49
  end
47
50
 
48
51
  it "passes the current environment" do
49
- env = Puppet::Node::Environment.remote('qa')
50
- expect(model.indirection).to receive(:save).with(anything, nil, :environment => env)
52
+ stub_request(:put, 'https://puppet.server.test:8140/puppet/v3/facts/puppet.node.test?environment=qa')
51
53
 
52
- Puppet.override(:current_environment => env) do
54
+ Puppet.override(:current_environment => Puppet::Node::Environment.remote('qa')) do
53
55
  subject.upload
54
56
  end
55
57
  end
56
58
 
57
- it "uses settings from the agent section of puppet.conf" do
58
- expect(facter_terminus).to receive(:find).with(have_attributes(key: 'puppet.node.test')).and_return(test_data)
59
+ it "uses settings from the agent section of puppet.conf to resolve the node name" do
60
+ stub_request(:put, /puppet.node.test/)
59
61
 
60
62
  subject.upload
61
63
  end
62
64
 
63
65
  it "logs the name of the server that received the upload" do
66
+ stub_request(:put, 'https://puppet.server.test:8140/puppet/v3/facts/puppet.node.test?environment=*root*')
67
+
64
68
  subject.upload
65
69
 
66
70
  expect(@logs).to be_any {|log| log.level == :notice &&
67
- log.message =~ /Uploading facts for '.*' to: 'puppet\.server\.test'/}
71
+ log.message =~ /Uploading facts for '.*' to 'puppet\.server\.test'/}
68
72
  end
69
73
  end
70
74
  end
@@ -988,7 +988,7 @@ describe "Puppet::FileSystem" do
988
988
  it 'rejects unsupported modes' do
989
989
  expect {
990
990
  Puppet::FileSystem.replace_file(dest, 0755) { |_| }
991
- }.to raise_error(ArgumentError, /Only modes 0644, 0640 and 0600 are allowed/)
991
+ }.to raise_error(ArgumentError, /Only modes 0644, 0640, 0660, and 0440 are allowed/)
992
992
  end
993
993
  end
994
994
  end
@@ -1065,12 +1065,26 @@ describe "Puppet::FileSystem" do
1065
1065
  end
1066
1066
  end
1067
1067
 
1068
- it 'applies the specified mode' do
1068
+ it 'applies 0644 mode' do
1069
1069
  Puppet::FileSystem.replace_file(dest, 0644) { |f| f.write(content) }
1070
1070
 
1071
1071
  expects_public_file(dest)
1072
1072
  end
1073
1073
 
1074
+ [0660, 0640, 0600, 0440].each do |mode|
1075
+ it "applies #{mode} mode" do
1076
+ Puppet::FileSystem.replace_file(dest, mode) { |f| f.write(content) }
1077
+ current_sid = Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name)
1078
+ sd = Puppet::Util::Windows::Security.get_security_descriptor(dest)
1079
+
1080
+ expect(sd.dacl).to contain_exactly(
1081
+ an_object_having_attributes(sid: Puppet::Util::Windows::SID::LocalSystem, mask: 0x1f01ff),
1082
+ an_object_having_attributes(sid: Puppet::Util::Windows::SID::BuiltinAdministrators, mask: 0x1f01ff),
1083
+ an_object_having_attributes(sid: current_sid, mask: 0x1f01ff),
1084
+ )
1085
+ end
1086
+ end
1087
+
1074
1088
  it 'raises Errno::EACCES if access is denied' do
1075
1089
  allow(Puppet::Util::Windows::Security).to receive(:get_security_descriptor).and_raise(Puppet::Util::Windows::Error.new('access denied', 5))
1076
1090
 
@@ -99,6 +99,12 @@ describe Puppet::HTTP::Client do
99
99
  client.get(uri, params: {:foo => "bar=baz"})
100
100
  end
101
101
 
102
+ it "fails if a user passes in an invalid param type" do
103
+ environment = Puppet::Node::Environment.create(:testing, [])
104
+
105
+ expect{client.get(uri, params: {environment: environment})}.to raise_error(Puppet::HTTP::SerializationError, /HTTP REST queries cannot handle values of type/)
106
+ end
107
+
102
108
  it "merges custom headers with default ones" do
103
109
  stub_request(:get, uri).with(headers: { 'X-Foo' => 'Bar', 'X-Puppet-Version' => /./, 'User-Agent' => /./ })
104
110
 
@@ -0,0 +1,322 @@
1
+ # coding: utf-8
2
+ require 'spec_helper'
3
+ require 'webmock/rspec'
4
+ require 'puppet/http'
5
+
6
+ describe Puppet::HTTP::Service::Compiler do
7
+ let(:ssl_context) { Puppet::SSL::SSLContext.new }
8
+ let(:client) { Puppet::HTTP::Client.new(ssl_context: ssl_context) }
9
+ let(:subject) { client.create_session.route_to(:puppet) }
10
+ let(:environment) { 'testing' }
11
+ let(:certname) { 'ziggy' }
12
+ let(:node) { Puppet::Node.new(certname) }
13
+ let(:facts) { Puppet::Node::Facts.new(certname) }
14
+ let(:catalog) { Puppet::Resource::Catalog.new(certname) }
15
+ let(:formatter) { Puppet::Network::FormatHandler.format(:json) }
16
+
17
+ before :each do
18
+ Puppet[:server] = 'compiler.example.com'
19
+ Puppet[:masterport] = 8140
20
+
21
+ Puppet::Node::Facts.indirection.terminus_class = :memory
22
+ end
23
+
24
+ context 'when making requests' do
25
+ let(:uri) {"https://compiler.example.com:8140/puppet/v3/catalog/ziggy?environment=testing"}
26
+
27
+ it 'includes default HTTP headers' do
28
+ stub_request(:post, uri).with do |request|
29
+ expect(request.headers).to include({'X-Puppet-Version' => /./, 'User-Agent' => /./})
30
+ expect(request.headers).to_not include('X-Puppet-Profiling')
31
+ end.to_return(body: formatter.render(catalog), headers: {'Content-Type' => formatter.mime })
32
+
33
+ subject.get_catalog(certname, environment: environment, facts: facts)
34
+ end
35
+
36
+ it 'includes the X-Puppet-Profiling header in requests when Puppet[:profile] is true' do
37
+ stub_request(:post, uri).with(headers: {'X-Puppet-Version' => /./, 'User-Agent' => /./, 'X-Puppet-Profiling' => 'true'})
38
+ .to_return(body: formatter.render(catalog), headers: {'Content-Type' => formatter.mime })
39
+
40
+ Puppet[:profile] = true
41
+
42
+ subject.get_catalog(certname, environment: environment, facts: facts)
43
+ end
44
+ end
45
+
46
+ context 'when routing to the compiler service' do
47
+ it 'defaults the server and port based on settings' do
48
+ Puppet[:server] = 'compiler2.example.com'
49
+ Puppet[:masterport] = 8141
50
+
51
+ stub_request(:post, "https://compiler2.example.com:8141/puppet/v3/catalog/ziggy?environment=testing")
52
+ .to_return(body: formatter.render(catalog), headers: {'Content-Type' => formatter.mime })
53
+
54
+ subject.get_catalog(certname, environment: environment, facts: facts)
55
+ end
56
+ end
57
+
58
+ context 'when posting for a catalog' do
59
+ let(:uri) { %r{/puppet/v3/catalog/ziggy} }
60
+ let(:catalog_response) { { body: formatter.render(catalog), headers: {'Content-Type' => formatter.mime } } }
61
+
62
+ it 'submits facts as application/json by default' do
63
+ stub_request(:post, uri)
64
+ .with(body: hash_including("facts_format" => /application\/json/))
65
+ .to_return(**catalog_response)
66
+
67
+ subject.get_catalog(certname, environment: environment, facts: facts)
68
+ end
69
+
70
+ it 'submits facts as pson if set as the preferred format' do
71
+ Puppet[:preferred_serialization_format] = "pson"
72
+
73
+ stub_request(:post, uri)
74
+ .with(body: hash_including("facts_format" => /pson/))
75
+ .to_return(**catalog_response)
76
+
77
+ subject.get_catalog(certname, environment: environment, facts: facts)
78
+ end
79
+
80
+ it 'includes environment as a query parameter AND in the POST body' do
81
+ stub_request(:post, uri)
82
+ .with(query: {"environment" => "outerspace"},
83
+ body: hash_including("environment" => 'outerspace'))
84
+ .to_return(**catalog_response)
85
+
86
+ subject.get_catalog(certname, environment: 'outerspace', facts: facts)
87
+ end
88
+
89
+ it 'includes configured_environment' do
90
+ stub_request(:post, uri)
91
+ .with(body: hash_including("configured_environment" => 'agent_specified'))
92
+ .to_return(**catalog_response)
93
+
94
+ subject.get_catalog(certname, environment: 'production', facts: facts, configured_environment: 'agent_specified')
95
+ end
96
+
97
+ it 'includes transaction_uuid' do
98
+ uuid = "ec3d2844-b236-4287-b0ad-632fbb4d1ff0"
99
+
100
+ stub_request(:post, uri)
101
+ .with(body: hash_including("transaction_uuid" => uuid))
102
+ .to_return(**catalog_response)
103
+
104
+ subject.get_catalog(certname, environment: 'production', facts: facts, transaction_uuid: uuid)
105
+ end
106
+
107
+ it 'includes job_uuid' do
108
+ uuid = "3dd13eec-1b6b-4b5d-867b-148193e0593e"
109
+
110
+ stub_request(:post, uri)
111
+ .with(body: hash_including("job_uuid" => uuid))
112
+ .to_return(**catalog_response)
113
+
114
+ subject.get_catalog(certname, environment: 'production', facts: facts, job_uuid: uuid)
115
+ end
116
+
117
+ it 'includes static_catalog' do
118
+ stub_request(:post, uri)
119
+ .with(body: hash_including("static_catalog" => "false"))
120
+ .to_return(**catalog_response)
121
+
122
+ subject.get_catalog(certname, environment: 'production', facts: facts, static_catalog: false)
123
+ end
124
+
125
+ it 'includes dot-separated list of checksum_types' do
126
+ stub_request(:post, uri)
127
+ .with(body: hash_including("checksum_type" => "sha256.sha384"))
128
+ .to_return(**catalog_response)
129
+
130
+ subject.get_catalog(certname, environment: 'production', facts: facts, checksum_type: %w[sha256 sha384])
131
+ end
132
+
133
+ it 'returns a deserialized catalog' do
134
+ stub_request(:post, uri)
135
+ .to_return(**catalog_response)
136
+
137
+ cat = subject.get_catalog(certname, environment: 'production', facts: facts)
138
+ expect(cat).to be_a(Puppet::Resource::Catalog)
139
+ expect(cat.name).to eq(certname)
140
+ end
141
+
142
+ it 'raises a response error if unsuccessful' do
143
+ stub_request(:post, uri)
144
+ .to_return(status: [500, "Server Error"])
145
+
146
+ expect {
147
+ subject.get_catalog(certname, environment: 'production', facts: facts)
148
+ }.to raise_error do |err|
149
+ expect(err).to be_an_instance_of(Puppet::HTTP::ResponseError)
150
+ expect(err.message).to eq('Server Error')
151
+ expect(err.response.code).to eq(500)
152
+ end
153
+ end
154
+
155
+ it 'raises a protocol error if the content-type header is missing' do
156
+ stub_request(:post, uri)
157
+ .to_return(body: "content-type is missing")
158
+
159
+ expect {
160
+ subject.get_catalog(certname, environment: 'production', facts: facts)
161
+ }.to raise_error(Puppet::HTTP::ProtocolError, /No content type in http response; cannot parse/)
162
+ end
163
+
164
+ it 'raises a serialization error if the content is invalid' do
165
+ stub_request(:post, uri)
166
+ .to_return(body: "this isn't valid JSON", headers: {'Content-Type' => 'application/json'})
167
+
168
+ expect {
169
+ subject.get_catalog(certname, environment: 'production', facts: facts)
170
+ }.to raise_error(Puppet::HTTP::SerializationError, /Failed to deserialize Puppet::Resource::Catalog from json/)
171
+ end
172
+
173
+ context 'serializing facts' do
174
+ facts_with_special_characters = [
175
+ { :hash => { 'afact' => 'a+b' }, :encoded => 'a%2Bb' },
176
+ { :hash => { 'afact' => 'a b' }, :encoded => 'a%20b' },
177
+ { :hash => { 'afact' => 'a&b' }, :encoded => 'a%26b' },
178
+ { :hash => { 'afact' => 'a*b' }, :encoded => 'a%2Ab' },
179
+ { :hash => { 'afact' => 'a=b' }, :encoded => 'a%3Db' },
180
+ # different UTF-8 widths
181
+ # 1-byte A
182
+ # 2-byte ۿ - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191
183
+ # 3-byte ᚠ - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160
184
+ # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142
185
+ { :hash => { 'afact' => "A\u06FF\u16A0\u{2070E}" }, :encoded => 'A%DB%BF%E1%9A%A0%F0%A0%9C%8E' },
186
+ ]
187
+
188
+ facts_with_special_characters.each do |test_fact|
189
+ it "escapes special characters #{test_fact[:hash]}" do
190
+ facts = Puppet::Node::Facts.new(certname, test_fact[:hash])
191
+ Puppet::Node::Facts.indirection.save(facts)
192
+
193
+ stub_request(:post, uri)
194
+ .with(body: hash_including("facts" => /#{test_fact[:encoded]}/))
195
+ .to_return(**catalog_response)
196
+
197
+ subject.get_catalog(certname, environment: environment, facts: facts)
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ context 'when getting a node' do
204
+ let(:uri) { %r{/puppet/v3/node/ziggy} }
205
+ let(:node_response) { { body: formatter.render(node), headers: {'Content-Type' => formatter.mime } } }
206
+
207
+ it 'includes environment' do
208
+ stub_request(:get, uri)
209
+ .with(query: hash_including("environment" => "outerspace"))
210
+ .to_return(**node_response)
211
+
212
+ subject.get_node(certname, environment: 'outerspace')
213
+ end
214
+
215
+ it 'includes configured_environment' do
216
+ stub_request(:get, uri)
217
+ .with(query: hash_including("configured_environment" => 'agent_specified'))
218
+ .to_return(**node_response)
219
+
220
+ subject.get_node(certname, environment: 'production', configured_environment: 'agent_specified')
221
+ end
222
+
223
+ it 'includes transaction_uuid' do
224
+ uuid = "ec3d2844-b236-4287-b0ad-632fbb4d1ff0"
225
+
226
+ stub_request(:get, uri)
227
+ .with(query: hash_including("transaction_uuid" => uuid))
228
+ .to_return(**node_response)
229
+
230
+ subject.get_node(certname, environment: 'production', transaction_uuid: uuid)
231
+ end
232
+
233
+ it 'returns a deserialized node' do
234
+ stub_request(:get, uri)
235
+ .to_return(**node_response)
236
+
237
+ n = subject.get_node(certname, environment: 'production')
238
+ expect(n).to be_a(Puppet::Node)
239
+ expect(n.name).to eq(certname)
240
+ end
241
+
242
+ it 'raises a response error if unsuccessful' do
243
+ stub_request(:get, uri)
244
+ .to_return(status: [500, "Server Error"])
245
+
246
+ expect {
247
+ subject.get_node(certname, environment: 'production')
248
+ }.to raise_error do |err|
249
+ expect(err).to be_an_instance_of(Puppet::HTTP::ResponseError)
250
+ expect(err.message).to eq('Server Error')
251
+ expect(err.response.code).to eq(500)
252
+ end
253
+ end
254
+
255
+ it 'raises a protocol error if the content-type header is missing' do
256
+ stub_request(:get, uri)
257
+ .to_return(body: "content-type is missing")
258
+
259
+ expect {
260
+ subject.get_node(certname, environment: 'production')
261
+ }.to raise_error(Puppet::HTTP::ProtocolError, /No content type in http response; cannot parse/)
262
+ end
263
+
264
+ it 'raises a serialization error if the content is invalid' do
265
+ stub_request(:get, uri)
266
+ .to_return(body: "this isn't valid JSON", headers: {'Content-Type' => 'application/json'})
267
+
268
+ expect {
269
+ subject.get_node(certname, environment: 'production')
270
+ }.to raise_error(Puppet::HTTP::SerializationError, /Failed to deserialize Puppet::Node from json/)
271
+ end
272
+ end
273
+
274
+ context 'when putting facts' do
275
+ let(:uri) { %r{/puppet/v3/facts/ziggy} }
276
+
277
+ it 'serializes facts in the body' do
278
+ facts = Puppet::Node::Facts.new(certname, { 'domain' => 'zork'})
279
+ Puppet::Node::Facts.indirection.save(facts)
280
+
281
+ stub_request(:put, uri)
282
+ .with(body: hash_including("name" => "ziggy", "values" => {"domain" => "zork"}))
283
+
284
+ subject.put_facts(certname, environment: environment, facts: facts)
285
+ end
286
+
287
+ it 'includes environment' do
288
+ stub_request(:put, uri)
289
+ .with(query: {"environment" => "outerspace"})
290
+
291
+ subject.put_facts(certname, environment: 'outerspace', facts: facts)
292
+ end
293
+
294
+ it 'returns true' do
295
+ # the REST API returns the filename, good grief
296
+ stub_request(:put, uri)
297
+ .to_return(status: 200, body: "/opt/puppetlabs/server/data/puppetserver/yaml/facts/#{certname}.yaml")
298
+
299
+ expect(subject.put_facts(certname, environment: environment, facts: facts)).to eq(true)
300
+ end
301
+
302
+ it 'raises a response error if unsuccessful' do
303
+ stub_request(:put, uri)
304
+ .to_return(status: [500, "Server Error"])
305
+
306
+ expect {
307
+ subject.put_facts(certname, environment: environment, facts: facts)
308
+ }.to raise_error do |err|
309
+ expect(err).to be_an_instance_of(Puppet::HTTP::ResponseError)
310
+ expect(err.message).to eq('Server Error')
311
+ expect(err.response.code).to eq(500)
312
+ end
313
+ end
314
+
315
+ it 'raises a serialization error if the report cannot be serialized' do
316
+ invalid_facts = Puppet::Node::Facts.new(certname, {'invalid_utf8_sequence' => "\xE2\x82".force_encoding('binary')})
317
+ expect {
318
+ subject.put_facts(certname, environment: 'production', facts: invalid_facts)
319
+ }.to raise_error(Puppet::HTTP::SerializationError, /Failed to serialize Puppet::Node::Facts to json: "\\xE2" from ASCII-8BIT to UTF-8/)
320
+ end
321
+ end
322
+ end