Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a foreman::enc function #742

Merged
merged 1 commit into from
Aug 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,34 @@ Foreman's database. Providers:
* `rest_v3` provider uses API v2 with Ruby HTTP library, OAuth and JSON (default)
* `rest_v2` provider uses API v2 with apipie-bindings and OAuth

## Foreman ENC via hiera

There is a function `foreman::enc` to retrieve the ENC data. This returns the
data as a hash and can be used in Hiera. This requires the URL to use the
Puppet CA infrastructure:

```yaml
---
version: 5
hierarchy:
- name: "Foreman ENC"
data_hash: foreman::enc
options:
url: https://foreman.example.com
```

It is also possible to use HTTP basic auth by adding a username/password to the
URL in the form of `https://username:password@foreman.example.com`.

Then within your manifests you can use `lookup`. For example, in
`manifests/site.pp`:

```puppet
node default {
lookup('classes', {merge => unique}).include
}
```

# Contributing

* Fork the project
Expand Down
109 changes: 109 additions & 0 deletions lib/puppet/functions/foreman/enc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
Puppet::Functions.create_function(:'foreman::enc') do
dispatch :foreman_enc do
# Copied from Stdlib::HTTPUrl
param 'Struct[{url=>Pattern[/(?i:^https?:\/\/)/]}]', :options
param 'Puppet::LookupContext', :context
end

argument_mismatch :missing_path do
param 'Hash', :options
param 'Puppet::LookupContext', :context
end

def parse_error(body)
begin
require 'json'
content = JSON.parse(body)
rescue
content = body
end

begin
content['error']['message']
rescue
"Check Foreman's production log for more information."
end
end

def enc(url, certname)
# Doesn't support JSON
headers = { 'Accept' => 'application/yaml' }
options = {}
uri = URI.parse("#{url}/node/#{certname}?format=yml")

# The API doesn't accept SSL certificate authentication
if uri.user && uri.password
headers['Accept'] = 'application/json'
uri = URI.parse("#{url}/api/hosts/#{certname}/enc")
options[:basic_auth] = {
:user => uri.user,
:password => uri.password
}
end

use_ssl = uri.scheme == 'https'
ssl_context = use_ssl ? Puppet.lookup(:ssl_context) : nil
conn = Puppet::Network::HttpPool.connection(uri.host, uri.port, use_ssl: use_ssl, ssl_context: ssl_context)

# Puppetserver doesn't implement HTTP auth on get requests
# https://tickets.puppetlabs.com/browse/SERVER-2597
if defined?(Puppet::Server::HttpClient) && conn.is_a?(Puppet::Server::HttpClient) && options[:basic_auth]
require 'base64'
encoded = Base64.strict_encode64("#{options[:basic_auth][:user]}:#{options[:basic_auth][:password]}")
headers["Authorization"] = "Basic #{encoded}"
end

path = uri.path
path += "?#{uri.query}" if uri.query
response = conn.get(path, headers, options)

raise "#{response.class}; #{parse_error(response.body)} using #{conn}" unless response.code == "200"

case response['Content-Type'].split(';').first
when 'application/json'
require 'json'
data = JSON.parse(response.body)
raise Exception, "Empty JSON response from ENC" if data.nil?
data['data']
when 'application/yaml'
Puppet::Util::Yaml.safe_load(response.body, [Symbol])
when 'text/plain'
# The node data sends it as text/plain rather than application/yaml
Puppet::Util::Yaml.safe_load(response.body, [Symbol])
else
raise Exception, "Unable to handle content type #{response['Content-Type']}"
end
end

def foreman_enc(options, context)
begin
data = enc(options['url'], Puppet[:certname])
rescue Puppet::Util::Yaml::YamlLoadError => ex
raise Puppet::DataBinding::LookupError, _("Unable to parse %{message}") % { message: ex.message }
rescue Exception => ex
raise Puppet::DataBinding::LookupError, _("Unable to load ENC for %{certname} %{message}") % { certname: Puppet[:certname], message: ex.message }
end

result = {}

result.update(data['parameters']) if data['parameters']

if data['classes'].is_a?(Hash)
result['classes'] = data['classes'].keys

data['classes'].each_pair do |cls, parameters|
parameters.each_pair do |parameter, value|
result["#{cls}::#{parameter}"] = value
end
end
elsif data['classes'].is_a?(Array)
result['classes'] = data['classes']
end

result
end

def missing_path(options, context)
"'url' must be declared in hiera.yaml when using this data_hash function"
end
end