File: fact_handler_spec.rb

package info (click to toggle)
puppet 5.5.10-4
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 21,116 kB
  • sloc: ruby: 250,669; sh: 1,620; xml: 218; makefile: 151; sql: 103
file content (152 lines) | stat: -rw-r--r-- 6,502 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#! /usr/bin/env ruby
require 'spec_helper'
require 'puppet/configurer'
require 'puppet/configurer/fact_handler'
require 'matchers/json'

class FactHandlerTester
  include Puppet::Configurer::FactHandler

  attr_accessor :environment

  def initialize(environment)
    self.environment = environment
  end

  def reload_facter
    # don't want to do this in tests
  end
end

describe Puppet::Configurer::FactHandler do
  include JSONMatchers

  let(:facthandler) { FactHandlerTester.new('production') }

  before :each do
    Puppet::Node::Facts.indirection.terminus_class = :memory
  end

  describe "when finding facts" do
    it "should use the node name value to retrieve the facts" do
      foo_facts = Puppet::Node::Facts.new('foo')
      bar_facts = Puppet::Node::Facts.new('bar')
      Puppet::Node::Facts.indirection.save(foo_facts)
      Puppet::Node::Facts.indirection.save(bar_facts)
      Puppet[:certname] = 'foo'
      Puppet[:node_name_value] = 'bar'

      expect(facthandler.find_facts).to eq(bar_facts)
    end

    it "should set the facts name based on the node_name_fact" do
      facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name')
      Puppet::Node::Facts.indirection.save(facts)
      Puppet[:node_name_fact] = 'my_name_fact'

      expect(facthandler.find_facts.name).to eq('other_node_name')
    end

    it "should set the node_name_value based on the node_name_fact" do
      facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name')
      Puppet::Node::Facts.indirection.save(facts)
      Puppet[:node_name_fact] = 'my_name_fact'

      facthandler.find_facts

      expect(Puppet[:node_name_value]).to eq('other_node_name')
    end

    it "should fail if finding facts fails" do
      Puppet::Node::Facts.indirection.expects(:find).raises RuntimeError

      expect { facthandler.find_facts }.to raise_error(Puppet::Error, /Could not retrieve local facts/)
    end

    it "should only load fact plugins once" do
      Puppet::Node::Facts.indirection.expects(:find).once
      facthandler.find_facts
    end
  end

  context "when serializing" do
    facts_with_special_characters = [
      { :hash => { 'afact' => 'a+b' }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'a%2Bb' + '%22%7D' },
      { :hash => { 'afact' => 'a b' }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'a%20b' + '%22%7D' },
      { :hash => { 'afact' => 'a&b' }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'a%26b' + '%22%7D' },
      { :hash => { 'afact' => 'a*b' }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'a%2Ab' + '%22%7D' },
      { :hash => { 'afact' => 'a=b' }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'a%3Db' + '%22%7D' },
      # different UTF-8 widths
      # 1-byte A
      # 2-byte ۿ - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191
      # 3-byte ᚠ - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160
      # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142
      { :hash => { 'afact' => "A\u06FF\u16A0\u{2070E}" }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'A%DB%BF%E1%9A%A0%F0%A0%9C%8E' + '%22%7D' },
    ]

    context "as pson" do
      before :each do
        Puppet[:preferred_serialization_format] = 'pson'
      end

      it "should serialize and CGI escape the fact values for uploading" do
        facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name')
        Puppet::Node::Facts.indirection.save(facts)
        text = Puppet::Util.uri_query_encode(facthandler.find_facts.render(:pson))

        expect(text).to include('%22values%22%3A%7B%22my_name_fact%22%3A%22other_node_name%22%7D')
        expect(facthandler.facts_for_uploading).to eq({:facts_format => :pson, :facts => text})
      end

      facts_with_special_characters.each do |test_fact|
        it "should properly accept the fact #{test_fact[:hash]}" do
          facts = Puppet::Node::Facts.new(Puppet[:node_name_value], test_fact[:hash])
          Puppet::Node::Facts.indirection.save(facts)
          text = Puppet::Util.uri_query_encode(facthandler.find_facts.render(:pson))

          to_upload = facthandler.facts_for_uploading
          expect(to_upload).to eq({:facts_format => :pson, :facts => text})
          expect(text).to include(test_fact[:encoded])

          # this is not sufficient to test whether these values are sent via HTTP GET or HTTP POST in actual catalog request
          expect(JSON.parse(URI.unescape(to_upload[:facts]))['values']).to eq(test_fact[:hash])
        end
      end
    end

    context "as json" do
      it "should serialize and CGI escape the fact values for uploading" do
        facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name')
        Puppet::Node::Facts.indirection.save(facts)
        text = Puppet::Util.uri_query_encode(facthandler.find_facts.render(:json))

        expect(text).to include('%22values%22%3A%7B%22my_name_fact%22%3A%22other_node_name%22%7D')
        expect(facthandler.facts_for_uploading).to eq({:facts_format => 'application/json', :facts => text})
      end

      facts_with_special_characters.each do |test_fact|
        it "should properly accept the fact #{test_fact[:hash]}" do
          facts = Puppet::Node::Facts.new(Puppet[:node_name_value], test_fact[:hash])
          Puppet::Node::Facts.indirection.save(facts)
          text = Puppet::Util.uri_query_encode(facthandler.find_facts.render(:json))

          to_upload = facthandler.facts_for_uploading
          expect(to_upload).to eq({:facts_format => 'application/json', :facts => text})
          expect(text).to include(test_fact[:encoded])

          expect(JSON.parse(URI.unescape(to_upload[:facts]))['values']).to eq(test_fact[:hash])
        end
      end
    end

    it "should generate valid facts data against the facts schema" do
      facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name')
      Puppet::Node::Facts.indirection.save(facts)

      # prefer URI.unescape but validate CGI also works
      encoded_facts = facthandler.facts_for_uploading[:facts]
      expect(URI.unescape(encoded_facts)).to validate_against('api/schemas/facts.json')
      expect(CGI.unescape(encoded_facts)).to validate_against('api/schemas/facts.json')
    end
  end
end