File: service.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (182 lines) | stat: -rw-r--r-- 5,958 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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# Represents an abstract Puppet web service.
#
# @abstract Subclass and implement methods for the service's REST APIs.
# @api public
class Puppet::HTTP::Service
  # @return [URI] the url associated with this service
  attr_reader :url

  # @return [Array<Symbol>] available services
  SERVICE_NAMES = [:ca, :fileserver, :puppet, :puppetserver, :report].freeze

  # @return [Array<Symbol>] format types that are unsupported
  EXCLUDED_FORMATS = [:yaml, :b64_zlib_yaml, :dot].freeze

  # Create a new web service, which contains the URL used to connect to the
  # service. The four services implemented are `:ca`, `:fileserver`, `:puppet`,
  # and `:report`.
  #
  # The `:ca` and `:report` services handle certs and reports, respectively. The
  # `:fileserver` service handles puppet file metadata and content requests. And
  # the default service, `:puppet`, handles nodes, facts, and catalogs.
  #
  # @param [Puppet::HTTP::Client] client the owner of the session
  # @param [Puppet::HTTP::Session] session the owner of the service
  # @param [Symbol] name the type of service to create
  # @param [<Type>] server optional, the server to connect to
  # @param [<Type>] port optional, the port to connect to
  #
  # @return [Puppet::HTTP::Service] an instance of the service type requested
  #
  # @api private
  def self.create_service(client, session, name, server = nil, port = nil)
    case name
    when :ca
      Puppet::HTTP::Service::Ca.new(client, session, server, port)
    when :fileserver
      Puppet::HTTP::Service::FileServer.new(client, session, server, port)
    when :puppet
      ::Puppet::HTTP::Service::Compiler.new(client, session, server, port)
    when :puppetserver
      ::Puppet::HTTP::Service::Puppetserver.new(client, session, server, port)
    when :report
      Puppet::HTTP::Service::Report.new(client, session, server, port)
    else
      raise ArgumentError, "Unknown service #{name}"
    end
  end

  # Check if the service named is included in the list of available services.
  #
  # @param [Symbol] name
  #
  # @return [Boolean]
  #
  # @api private
  def self.valid_name?(name)
    SERVICE_NAMES.include?(name)
  end

  # Create a new service. Services should be created by calling `Puppet::HTTP::Session#route_to`.
  #
  # @param [Puppet::HTTP::Client] client
  # @param [Puppet::HTTP::Session] session
  # @param [URI] url The url to connect to
  #
  # @api private
  def initialize(client, session, url)
    @client = client
    @session = session
    @url = url
  end

  # Return the url with the given path encoded and appended
  #
  # @param [String] path the string to append to the base url
  #
  # @return [URI] the URI object containing the encoded path
  #
  # @api public
  def with_base_url(path)
    u = @url.dup
    u.path += Puppet::Util.uri_encode(path)
    u
  end

  # Open a connection using the given ssl context.
  #
  # @param [Puppet::SSL::SSLContext] ssl_context An optional ssl context to connect with
  # @return [void]
  #
  # @api public
  def connect(ssl_context: nil)
    @client.connect(@url, options: {ssl_context: ssl_context})
  end

  protected

  def add_puppet_headers(headers)
    modified_headers = headers.dup

    # Add 'X-Puppet-Profiling' to enable performance profiling if turned on
    modified_headers['X-Puppet-Profiling'] = 'true' if Puppet[:profile]

    # Add additional user-defined headers if they are defined
    Puppet[:http_extra_headers].each do |name, value|
      if modified_headers.keys.find { |key| key.casecmp(name) == 0 }
        Puppet.warning(_('Ignoring extra header "%{name}" as it was previously set.') % { name: name })
      else
        if value.nil? || value.empty?
          Puppet.warning(_('Ignoring extra header "%{name}" as it has no value.') % { name: name })
        else
          modified_headers[name] = value
        end
      end
    end
    modified_headers
  end

  def build_url(api, server, port)
    URI::HTTPS.build(host: server,
                     port: port,
                     path: api
                    ).freeze
  end

  def get_mime_types(model)
    network_formats = model.supported_formats - EXCLUDED_FORMATS
    network_formats.map { |f| model.get_format(f).mime }
  end

  def formatter_for_response(response)
    header = response['Content-Type']
    raise Puppet::HTTP::ProtocolError.new(_("No content type in http response; cannot parse")) unless header

    header.gsub!(/\s*;.*$/,'') # strip any charset

    formatter = Puppet::Network::FormatHandler.mime(header)
    raise Puppet::HTTP::ProtocolError.new("Content-Type is unsupported") if EXCLUDED_FORMATS.include?(formatter.name)

    formatter
  end

  def serialize(formatter, object)
    begin
      formatter.render(object)
    rescue => err
      raise Puppet::HTTP::SerializationError.new("Failed to serialize #{object.class} to #{formatter.name}: #{err.message}", err)
    end
  end

  def serialize_multiple(formatter, object)
    begin
      formatter.render_multiple(object)
    rescue => err
      raise Puppet::HTTP::SerializationError.new("Failed to serialize multiple #{object.class} to #{formatter.name}: #{err.message}", err)
    end
  end

  def deserialize(response, model)
    formatter = formatter_for_response(response)
    begin
      formatter.intern(model, response.body.to_s)
    rescue => err
      raise Puppet::HTTP::SerializationError.new("Failed to deserialize #{model} from #{formatter.name}: #{err.message}", err)
    end
  end

  def deserialize_multiple(response, model)
    formatter = formatter_for_response(response)
    begin
      formatter.intern_multiple(model, response.body.to_s)
    rescue => err
      raise Puppet::HTTP::SerializationError.new("Failed to deserialize multiple #{model} from #{formatter.name}: #{err.message}", err)
    end
  end

  def process_response(response)
    @session.process_response(response)

    raise Puppet::HTTP::ResponseError.new(response) unless response.success?
  end
end