Skip to content

Commit 15a9b2f

Browse files
authored
Feature/puppet v3 (#8)
Feature: Add more PuppetV3 endpoints Changed PuppetServerV3 to PuppetV3 to match API naming. Removed return types for Client HTTP actions as responses vary widely between API endpoints. Added HTTP HEAD action.
1 parent 28d9fbd commit 15a9b2f

File tree

16 files changed

+1134
-108
lines changed

16 files changed

+1134
-108
lines changed

lib/pe_client/client.rb

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def initialize(api_key:, base_url:, ca_file:, &block)
4343
conn.headers["User-Agent"] = "PEClient/#{PEClient::VERSION} Ruby/#{RUBY_VERSION}".freeze
4444
conn.headers["X-Authentication"] = @api_key
4545
conn.ssl[:ca_file] = ca_file
46+
conn.options.params_encoder = Faraday::FlatParamsEncoder
4647

4748
block&.call(conn)
4849

@@ -68,7 +69,7 @@ def deep_dup
6869
# @param params [Hash] Query parameters
6970
# @param headers [Hash]
7071
#
71-
# @return [Hash, Array] Parsed JSON response
72+
# @return Parsed JSON response
7273
def get(path, params: {}, headers: {})
7374
handle_response connection.get(path, params, headers)
7475
end
@@ -80,7 +81,7 @@ def get(path, params: {}, headers: {})
8081
# @param params [Hash] Query parameters
8182
# @param headers [Hash]
8283
#
83-
# @return [Hash, Array] Parsed JSON response
84+
# @return Parsed JSON response
8485
def post(path, body: {}, params: {}, headers: {})
8586
path = "#{path}?#{URI.encode_www_form(params)}" unless params.empty?
8687
handle_response connection.post(path, body, headers)
@@ -93,7 +94,7 @@ def post(path, body: {}, params: {}, headers: {})
9394
# @param params [Hash] Query parameters
9495
# @param headers [Hash]
9596
#
96-
# @return [Hash, Array] Parsed JSON response
97+
# @return Parsed JSON response
9798
def put(path, body: {}, params: {}, headers: {})
9899
path = "#{path}?#{URI.encode_www_form(params)}" unless params.empty?
99100
handle_response connection.put(path, body, headers)
@@ -106,7 +107,7 @@ def put(path, body: {}, params: {}, headers: {})
106107
# @param params [Hash] Query parameters
107108
# @param headers [Hash]
108109
#
109-
# @return [Hash, Array] Parsed JSON response
110+
# @return Parsed JSON response
110111
def delete(path, body: nil, params: {}, headers: {})
111112
if body
112113
response = connection.delete(path, params, headers) do |req|
@@ -118,6 +119,17 @@ def delete(path, body: nil, params: {}, headers: {})
118119
end
119120
end
120121

122+
# HTTP HEAD request
123+
#
124+
# @param path [String] API endpoint path
125+
# @param params [Hash] Query parameters
126+
# @param headers [Hash]
127+
#
128+
# @return [Hash] HTTP Headers
129+
def head(path, params: {}, headers: {})
130+
handle_response connection.head(path, params, headers), headers_only: true
131+
end
132+
121133
# @return [Resource::NodeInventoryV1]
122134
def node_inventory_v1
123135
require_relative "resources/node_inventory.v1"
@@ -190,28 +202,29 @@ def puppet_admin_v1
190202
@puppet_admin_v1 ||= Resource::PuppetAdminV1.new(self)
191203
end
192204

193-
# @return [Resource::PuppetServerV3]
194-
def puppet_server_v3
195-
require_relative "resources/puppet_server.v3"
196-
@puppet_server_v3 ||= Resource::PuppetServerV3.new(self)
205+
# @return [Resource::PuppetV3]
206+
def puppet_v3
207+
require_relative "resources/puppet.v3"
208+
@puppet_v3 ||= Resource::PuppetV3.new(self)
197209
end
198210

199211
private
200212

201213
# Handle HTTP response
202214
#
203215
# @param response [Faraday::Response] HTTP response
216+
# @param headers_only [Boolean] Whether to return only headers
204217
#
205-
# @return [Hash, Array] Parsed JSON response
218+
# @return Parsed JSON response, headers, or location
206219
#
207220
# @raise [PEClient::HTTPError] Raises specific errors based on status code
208-
def handle_response(response)
221+
def handle_response(response, headers_only: false)
209222
case response.status
210223
when 204 # No Content
211-
{}
224+
headers_only ? response.headers : {}
212225
when 200..299
213-
response.body
214-
when 303
226+
headers_only ? response.headers : response.body
227+
when 303 # See Other
215228
{"location" => response.headers["Location"]}
216229
when 400
217230
raise BadRequestError, response

lib/pe_client/resources/node_classifier.v1/groups.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def create(name:, parent:, id: nil, environment: nil, environment_trumps: nil, d
8282
variables:,
8383
classes:,
8484
config_data:
85-
}.compact!
85+
}.compact
8686

8787
if id
8888
@client.put "#{BASE_PATH}/#{id}", body:
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright 2025 Perforce Software Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
require_relative "base_with_port"
18+
19+
module PEClient
20+
module Resource
21+
# Server-specific Puppet API endpoints.
22+
#
23+
# @see https://help.puppet.com/core/current/Content/PuppetCore/server/http_api/puppet_v3_api.htm
24+
class PuppetV3 < BaseWithPort
25+
# The base path for Puppet API v3 endpoints.
26+
BASE_PATH = "/puppet/v3"
27+
28+
# Default Puppet API Port
29+
PORT = 8140
30+
31+
# Returns a catalog for the specified node name given the provided facts.
32+
#
33+
# @param node_name [String] The name of the node to retrieve the catalog for.
34+
# @param environment [String] The environment to use when compiling the catalog.
35+
#
36+
# @return [Hash]
37+
#
38+
# @see https://help.puppet.com/core/current/Content/PuppetCore/server/http_api/http_catalog.htm
39+
def catalog(node_name:, environment: nil)
40+
@client.get File.join("#{BASE_PATH}/catalog", node_name), params: {environment:}.compact
41+
end
42+
43+
# The returned information includes the node name and environment, and optionally any classes set by an External Node Classifier and a hash of parameters which may include the node's facts.
44+
# The returned node may have a different environment from the one given in the request if Puppet is configured with an ENC.
45+
#
46+
# @param certname [String] The name of the node to retrieve information for.
47+
# @param environment [String] The environment to use when retrieving the node information.
48+
# @param transaction_uuid [String] A transaction uuid identifying the entire transaction (shows up in the report as well).
49+
# @param configured_environment [String] The environment configured on the client.
50+
#
51+
# @return [Hash]
52+
#
53+
# @see https://help.puppet.com/core/current/Content/PuppetCore/server/http_api/http_node.htm
54+
def node(certname:, environment: nil, transaction_uuid: nil, configured_environment: nil)
55+
@client.get File.join("#{BASE_PATH}/node", certname), params: {environment:, transaction_uuid:, configured_environment:}.compact
56+
end
57+
58+
# Allows setting the facts for the specified node name.
59+
#
60+
# @param node_name [String] The name of the node to set the facts for.
61+
# @param facts [Hash] The facts to set for the node.
62+
# @param environment [String] The environment to use when setting the facts.
63+
#
64+
# @return [Hash]
65+
#
66+
# @see https://help.puppet.com/core/current/Content/PuppetCore/server/http_api/http_facts.htm
67+
def facts(node_name:, facts:, environment: nil)
68+
@client.put File.join("#{BASE_PATH}/facts", node_name), body: facts.to_json, params: {environment:}.compact
69+
end
70+
71+
# Returns the contents of the specified file.
72+
#
73+
# @param mount_point [String] One of the following types:
74+
# - Custom file serving mounts as specified in fileserver.conf
75+
# - `modules/<MODULE>` --- a semi-magical mount point which allows access to the files subdirectory of <MODULE>
76+
# - `plugins` --- a highly magical mount point which merges the lib directory of every module together.
77+
# Used for syncing plugins; not intended for general consumption.
78+
# Per-module sub-paths can not be specified.
79+
# - `pluginfacts` --- a highly magical mount point which merges the facts.d directory of every module together.
80+
# Used for syncing external facts; not intended for general consumption.
81+
# Per-module sub-paths can not be specified.
82+
# - `tasks/<MODULE>` --- a semi-magical mount point which allows access to files in the tasks subdirectory of <MODULE>
83+
# @param name [String]
84+
#
85+
# @return [String]
86+
#
87+
# @see https://help.puppet.com/core/current/Content/PuppetCore/server/http_api/http_file_content.htm
88+
def file_content(mount_point:, name:)
89+
@client.get File.join("#{BASE_PATH}/file_content", mount_point, name), params: {"Content-Type": "application/octet-stream", Accept: "application/octet-stream"}.freeze
90+
end
91+
92+
# This endpoint allows clients to send reports to the master.
93+
# Once received by the master they are processed by the `report processors` configured to be triggered when a report is received.
94+
# As an example, storing reports in PuppetDB is handled by one such report processor.
95+
#
96+
# @param node_name [String] The name of the node the report is for.
97+
# @param environment [String] The environment to use when submitting the report.
98+
# @param report [Hash] The report to submit.
99+
#
100+
# @return [Hash]
101+
#
102+
# @see https://help.puppet.com/core/current/Content/PuppetCore/server/http_api/http_report.htm
103+
def report(node_name:, environment:, report:)
104+
@client.put File.join("#{BASE_PATH}/report", node_name), body: report, params: {environment:}
105+
end
106+
107+
# The environment classes API serves as a replacement for the Puppet resource type API for classes, which was removed in Puppet.
108+
#
109+
# @param environment [String] The environment to query.
110+
#
111+
# @return [Hash]
112+
#
113+
# @see https://help.puppet.com/core/current/Content/PuppetCore/server/http_api/puppet-api/v3/environment_classes.htm
114+
def environment_classes(environment:)
115+
@client.get "#{BASE_PATH}/environment_classes", params: {environment:}
116+
end
117+
118+
# The environment modules API returns information about what modules are installed for the requested environment.
119+
#
120+
# @param environment [String] The environment to query.
121+
#
122+
# @return [Array<Hash>, Hash]
123+
#
124+
# @see https://help.puppet.com/core/current/Content/PuppetCore/server/http_api/puppet-api/v3/environment_modules.htm
125+
def environment_modules(environment: nil)
126+
@client.get "#{BASE_PATH}/environment_modules", params: {environment:}.compact
127+
end
128+
129+
# The static_file_content endpoint returns the standard output of a `code-content-command` script, which should output the contents of a specific version of a `file resource` that has a source attribute with a `puppet:///` URI value.
130+
# That source must be a file from the files or tasks directory of a module in a specific environment.
131+
#
132+
# @param file_path [String] The path corresponds to the requested file's path on the Server relative to the given environment's root directory, and must point to a file in the */*/files/**, */*/lib/**, */*/scripts/**, or */*/tasks/** glob.
133+
#
134+
# @return [String]
135+
#
136+
# @see https://help.puppet.com/core/current/Content/PuppetCore/server/http_api/puppet-api/v3/static_file_content.htm
137+
def static_file_content(file_path:)
138+
@client.get File.join("#{BASE_PATH}/static_file_content", file_path)
139+
end
140+
141+
# @return [PEClient::Resource::PuppetV3::FileBucket]
142+
def file_bucket
143+
require_relative "puppet.v3/file_bucket"
144+
@file_bucket ||= PuppetV3::FileBucket.new(@client)
145+
end
146+
147+
# @return [PEClient::Resource::PuppetV3::FileMetadata]
148+
def file_metadata
149+
require_relative "puppet.v3/file_metadata"
150+
@file_metadata ||= PuppetV3::FileMetadata.new(@client)
151+
end
152+
end
153+
end
154+
end
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright 2025 Perforce Software Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
require_relative "../base"
18+
19+
module PEClient
20+
module Resource
21+
class PuppetV3
22+
# Manages the contents of files in the file bucket.
23+
# All access to files is managed with the md5 checksum of the file contents, represented as `:md5`.
24+
# Where used, `:filename` means the full absolute path of the file on the client system.
25+
# This is usually optional and used as an error check to make sure correct file is retrieved.
26+
# The environment is required in all requests but ignored, as the file bucket does not distinguish between environments.
27+
#
28+
# @see https://help.puppet.com/core/current/Content/PuppetCore/server/http_api/http_file_bucket_file.htm
29+
class FileBucket < Base
30+
# The base path for Puppet API v3 File Bucket endpoints.
31+
BASE_PATH = "#{PuppetV3::BASE_PATH}/file_bucket_file".freeze
32+
33+
# Common headers for file bucket requests
34+
HEADERS = {"Content-Type": "application/octet-stream", Accept: "application/octet-stream"}.freeze
35+
36+
# Retrieve the contents of a file.
37+
#
38+
# @param md5 [String] The MD5 checksum of the file.
39+
# @param environment [String] Required but ignored.
40+
# @param filename [String] The full absolute path of the file on the client system.
41+
#
42+
# @return [String]
43+
def get(md5:, environment:, filename: nil)
44+
@client.get path(md5, filename), params: {environment:}.merge(HEADERS)
45+
end
46+
47+
# Check if a file is present in the filebucket
48+
# This behaves identically to {#get}, only returning headers.
49+
#
50+
# @param md5 [String] The MD5 checksum of the file.
51+
# @param environment [String] Required but ignored.
52+
# @param filename [String] The full absolute path of the file on the client system.
53+
#
54+
# @return [Hash]
55+
def head(md5:, environment:, filename: nil)
56+
@client.head path(md5, filename), params: {environment:}.merge(HEADERS)
57+
end
58+
59+
# Save a file to the filebucket
60+
# The body should contain the file contents. This saves the file using the md5 sum of the file contents.
61+
# If `:filename` is provided, it adds the path to a list for the given file.
62+
# If the md5 sum in the request is incorrect, the file will be instead saved under the correct checksum.
63+
#
64+
# @param md5 [String] The MD5 checksum of the file.
65+
# @param file [String] The contents of the file to save.
66+
# @param environment [String] Required but ignored.
67+
# @param filename [String] The full absolute path of the file on the client system
68+
#
69+
# @return [String]
70+
def save(md5:, file:, environment:, filename: nil)
71+
@client.put path(md5, filename), body: file, params: {environment:}.merge(HEADERS)
72+
end
73+
74+
private
75+
76+
# Construct the path for file bucket operations
77+
#
78+
# @param md5 [String]
79+
# @param filename [String]
80+
#
81+
# @return [String]
82+
def path(md5, filename = nil)
83+
if filename
84+
"#{File.join(BASE_PATH, md5)}/#{filename}"
85+
else
86+
File.join(BASE_PATH, md5)
87+
end
88+
end
89+
end
90+
end
91+
end
92+
end

0 commit comments

Comments
 (0)