35 changes: 13 additions & 22 deletions .fixtures.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
fixtures:
repositories:
apache: 'https://github.com/puppetlabs/puppetlabs-apache'
apt: 'https://github.com/puppetlabs/puppetlabs-apt'
augeas_core:
repo: 'https://github.com/puppetlabs/puppetlabs-augeas_core'
puppet_version: '>= 6.0.0'
concat: 'https://github.com/puppetlabs/puppetlabs-concat'
cron_core:
repo: "https://github.com/puppetlabs/puppetlabs-cron_core"
puppet_version: ">= 6.0.0"
extlib: 'https://github.com/voxpupuli/puppet-extlib'
postgresql: 'https://github.com/puppetlabs/puppetlabs-postgresql'
puppet: 'https://github.com/theforeman/puppet-puppet'
redis: 'https://github.com/voxpupuli/puppet-redis'
systemd:
repo: 'https://github.com/voxpupuli/puppet-systemd'
selinux_core:
repo: 'https://github.com/puppetlabs/puppetlabs-selinux_core'
puppet_version: ">= 6.0.0"
stdlib: 'https://github.com/puppetlabs/puppetlabs-stdlib'
yumrepo_core:
repo: "https://github.com/puppetlabs/puppetlabs-yumrepo_core"
puppet_version: ">= 6.0.0"
apache: 'https://github.com/puppetlabs/puppetlabs-apache'
apt: 'https://github.com/puppetlabs/puppetlabs-apt'
augeas_core: 'https://github.com/puppetlabs/puppetlabs-augeas_core'
concat: 'https://github.com/puppetlabs/puppetlabs-concat'
cron_core: 'https://github.com/puppetlabs/puppetlabs-cron_core'
extlib: 'https://github.com/voxpupuli/puppet-extlib'
postgresql: 'https://github.com/puppetlabs/puppetlabs-postgresql'
puppet: 'https://github.com/theforeman/puppet-puppet'
redis: 'https://github.com/voxpupuli/puppet-redis'
systemd: 'https://github.com/voxpupuli/puppet-systemd'
selinux_core: 'https://github.com/puppetlabs/puppetlabs-selinux_core'
stdlib: 'https://github.com/puppetlabs/puppetlabs-stdlib'
yumrepo_core: 'https://github.com/puppetlabs/puppetlabs-yumrepo_core'
25 changes: 24 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,43 @@
# Changelog

## [19.1.0](https://github.com/theforeman/puppet-foreman/tree/19.1.0) (2022-01-25)

[Full Changelog](https://github.com/theforeman/puppet-foreman/compare/19.0.0...19.1.0)

**Implemented enhancements:**

- Fixes [\#34089](https://projects.theforeman.org/issues/34089) - Add trusted proxies setting [\#1011](https://github.com/theforeman/puppet-foreman/pull/1011) ([sbernhard](https://github.com/sbernhard))
- puppetlabs/apache: Allow 7.x [\#1006](https://github.com/theforeman/puppet-foreman/pull/1006) ([bastelfreak](https://github.com/bastelfreak))
- puppetlabs/stdlib: Allow 8.x [\#1004](https://github.com/theforeman/puppet-foreman/pull/1004) ([bastelfreak](https://github.com/bastelfreak))
- Add basic `foreman_hostgroup` type [\#1002](https://github.com/theforeman/puppet-foreman/pull/1002) ([alexjfisher](https://github.com/alexjfisher))

**Fixed bugs:**

- Fixes [\#34308](https://projects.theforeman.org/issues/34308) - Explicitly notify db:seed from db:migrate [\#1020](https://github.com/theforeman/puppet-foreman/pull/1020) ([ekohl](https://github.com/ekohl))
- foreman::repo: use the package resource 'ensure' parameter to specify the desired ruby stream [\#1015](https://github.com/theforeman/puppet-foreman/pull/1015) ([bastelfreak](https://github.com/bastelfreak))
- Fixes [\#34161](https://projects.theforeman.org/issues/34161) - Run apipie:cache:index after db:migrate [\#1010](https://github.com/theforeman/puppet-foreman/pull/1010) ([ekohl](https://github.com/ekohl))
- Fix lack of idempotency in foreman\_smartproxy\_host provider [\#1009](https://github.com/theforeman/puppet-foreman/pull/1009) ([ehelms](https://github.com/ehelms))
- Fixes [\#33973](https://projects.theforeman.org/issues/33973) - Restart foreman.service when configuration changes [\#1008](https://github.com/theforeman/puppet-foreman/pull/1008) ([wbclark](https://github.com/wbclark))

**Merged pull requests:**

- Refs [\#34089](https://projects.theforeman.org/issues/34089) - Work around Kafo type parsing bug [\#1013](https://github.com/theforeman/puppet-foreman/pull/1013) ([ekohl](https://github.com/ekohl))

## [19.0.0](https://github.com/theforeman/puppet-foreman/tree/19.0.0) (2021-11-09)

[Full Changelog](https://github.com/theforeman/puppet-foreman/compare/18.2.0...19.0.0)

**Breaking changes:**

- Drop server\_ssl\_certs\_dir parameter [\#1003](https://github.com/theforeman/puppet-foreman/pull/1003) ([ekohl](https://github.com/ekohl))
- Add Ubuntu 20.04 support & drop Ubuntu 18.04 [\#981](https://github.com/theforeman/puppet-foreman/pull/981) ([ekohl](https://github.com/ekohl))
- Fixes [\#33789](https://projects.theforeman.org/issues/33789) - Mark host where the installer is running as foreman [\#965](https://github.com/theforeman/puppet-foreman/pull/965) ([adamruzicka](https://github.com/adamruzicka))

**Implemented enhancements:**

- Refs [\#33760](https://projects.theforeman.org/issues/33760) - Add host\_reports plugin [\#1000](https://github.com/theforeman/puppet-foreman/pull/1000) ([ofedoren](https://github.com/ofedoren))
- Switch to puppet/systemd [\#997](https://github.com/theforeman/puppet-foreman/pull/997) ([jovandeginste](https://github.com/jovandeginste))
- Apply version restrictions to all packages [\#996](https://github.com/theforeman/puppet-foreman/pull/996) ([nbarrientos](https://github.com/nbarrientos))
- Add Ubuntu 20.04 support & drop Ubuntu 18.04 [\#981](https://github.com/theforeman/puppet-foreman/pull/981) ([ekohl](https://github.com/ekohl))

**Fixed bugs:**

Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ gem 'puppetlabs_spec_helper', {"groups"=>["system_tests"]}
gem 'webmock', '~> 2.0'
gem 'oauth'

# Pin rdoc to prevent updating bundled psych (https://github.com/ruby/rdoc/commit/ebe185c8775b2afe844eb3da6fa78adaa79e29a4)
gem 'rdoc', '< 6.4'

# vim:ft=ruby
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ seen in _Administer > Settings_. The `cli` provider uses `foreman-rake` to chang
`foreman_smartproxy` can create and manage registered smart proxies in
Foreman's database. The `rest_v3` provider uses the API with Ruby's HTTP library, OAuth and JSON.

`foreman_hostgroup` can be used to create and destroy hostgroups. Nested hostgroups are supported
and hostgroups can be assigned to locations/organizations.
The type currently doesn't support other properties such as `environment`, `puppet classes` etc.

## Foreman ENC via hiera

There is a function `foreman::enc` to retrieve the ENC data. This returns the
Expand Down
182 changes: 182 additions & 0 deletions lib/puppet/provider/foreman_hostgroup/rest_v3.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# frozen_string_literal: true

Puppet::Type.type(:foreman_hostgroup).provide(
:rest_v3,
parent: Puppet::Type.type(:foreman_resource).provider(:rest_v3)
) do
confine feature: %i[json oauth]

def initialize(value = {})
super
@property_flush = {}
end

def exists?
!id.nil?
end

def create
Puppet.debug("Creating Foreman Hostgroup #{resource[:name]} with parent #{resource[:parent_hostgroup]}")

if resource[:parent_hostgroup] && parent_hostgroup_id.nil?
raise Puppet::Error,
"Parent hostgroup #{resource[:parent_hostgroup]} for #{resource[:name]} not found"
end

organization_ids = resource[:organizations]&.map { |org| organization_id(org) }
location_ids = resource[:locations]&.map { |loc| location_id(loc) }

post_data = {
hostgroup: {
name: resource[:name],
parent_id: parent_hostgroup_id,
description: resource[:description],
organization_ids: organization_ids,
location_ids: location_ids
}
}.to_json
path = 'api/v2/hostgroups'
r = request(:post, path, {}, post_data)

return if success?(r)

raise Puppet::Error, "Error making POST request to Foreman at #{request_uri(path)}: #{error_message(r)}"
end

def destroy
Puppet.debug("Destroying Foreman Hostgroup #{resource[:name]} with parent #{resource[:parent_hostgroup]}")
path = "api/v2/hostgroups/#{id}"
r = request(:delete, path)

unless success?(r)
error_string = "Error making DELETE request to Foreman at #{request_uri(path)}: #{error_message(r)}"
raise Puppet::Error, error_string
end

@hostgroup = nil
end

def flush
return if @property_flush.empty?

Puppet.debug "Calling API to update properties for #{resource[:name]}"

path = "api/v2/hostgroups/#{id}"
r = request(:put, path, {}, { hostgroup: @property_flush }.to_json)

return if success?(r)

raise Puppet::Error, "Error making PUT request to Foreman at #{request_uri(path)}: #{error_message(r)}"
end

# Property getters
def description
hostgroup ? hostgroup['description'] : nil
end

def organizations
hostgroup ? hostgroup['organizations'].map { |org| org['title'] } : nil
end

def locations
hostgroup ? hostgroup['locations'].map { |org| org['title'] } : nil
end

# Property setters
# If one of more properties is being modified then group all of these updates in @property_flush so that we can update them in a single API call in `flush()`
def description=(value)
@property_flush[:description] = value
end

def organizations=(value)
@property_flush[:organization_ids] = value.map { |org| organization_id(org) }
end

def locations=(value)
@property_flush[:location_ids] = value.map { |loc| location_id(loc) }
end

private

def hostgroup
@hostgroup ||= begin
path = 'api/v2/hostgroups'
search_name = resource[:name]
search_title = if resource[:parent_hostgroup]
"#{resource[:parent_hostgroup]}/#{resource[:name]}"
else
search_name
end
Puppet.debug("Searching for hostgroup with name #{search_name} and title #{search_title}")
r = request(:get, path, search: %(title="#{search_title}" and name="#{search_name}"))

raise Puppet::Error, "Error making GET request to Foreman at #{request_uri(path)}: #{error_message(r)}" unless success?(r)

results = JSON.parse(r.body)['results']
unless results.empty?
raise Puppet::Error, "Too many hostgroups found when looking for hostgroup with name #{search_name} and title #{search_title}" if results.size > 1

get_hostgroup_by_id(results[0]['id'])
end
end
end

def get_hostgroup_by_id(id)
path = "api/v2/hostgroups/#{id}"
r = request(:get, path)

raise Puppet::Error, "Error making GET request to Foreman at #{request_uri(path)}: #{error_message(r)}" unless success?(r)

JSON.parse(r.body)
end

def id
hostgroup ? hostgroup['id'] : nil
end

def parent_hostgroup
return nil unless resource[:parent_hostgroup]

@parent_hostgroup ||= begin
path = 'api/v2/hostgroups'
search_title = resource[:parent_hostgroup]
search_name = resource[:parent_hostgroup_name] || search_title.split('/').last
Puppet.debug("Searching for parent hostgroup with name #{search_name} and title #{search_title}")
r = request(:get, path, search: %(title="#{search_title}" and name="#{search_name}"))

raise Puppet::Error, "Error making GET request to Foreman at #{request_uri(path)}: #{error_message(r)}" unless success?(r)

results = JSON.parse(r.body)['results']
raise Puppet::Error, "Parent hostgroup #{resource[:parent_hostgroup]} for #{resource[:name]} not found" if results.empty?
raise Puppet::Error, "Too many hostgroups found when looking for parent hostgroup with name #{search_name} and title #{search_title}" if results.size > 1

get_hostgroup_by_id(results[0]['id'])
end
end

def parent_hostgroup_id
parent_hostgroup ? parent_hostgroup['id'] : nil
end

def organization_id(organization_title)
title_to_id('organizations', organization_title)
end

def location_id(location_title)
title_to_id('locations', location_title)
end

# Returns the id of a location/organization based on its title
def title_to_id(type, title)
path = "api/v2/#{type}"
r = request(:get, path, search: %(title="#{title}"))
unless success?(r)
raise Puppet::Error,
"Error making GET request to Foreman at #{request_uri(path)}: #{error_message(r)}"
end
results = JSON.parse(r.body)['results']
raise Puppet::Error, "#{title} not found in #{type}" unless results.size == 1

results[0]['id']
end
end
2 changes: 1 addition & 1 deletion lib/puppet/provider/foreman_smartproxy_host/rest_v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
def exists?
return false if host.nil? || proxy.nil?

host['smart_proxy_id'] == proxy_id
host.fetch('infrastructure_facet', {})['smart_proxy_id'] == proxy_id
end

def create
Expand Down
1 change: 1 addition & 0 deletions lib/puppet/type/foreman_host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Puppet::Type.newtype(:foreman_host) do
desc 'foreman_host creates a host in foreman.'

instance_eval(&PuppetX::Foreman::Common::REST_API_COMMON_PARAMS)
instance_eval(&PuppetX::Foreman::Common::FOREMAN_HOST_PARAMS)

newparam(:facts) do
Expand Down
59 changes: 59 additions & 0 deletions lib/puppet/type/foreman_hostgroup.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

require_relative '../../puppet_x/foreman/common'
Puppet::Type.newtype(:foreman_hostgroup) do
desc 'foreman_hostgroup manages hostgroups in foreman.'

instance_eval(&PuppetX::Foreman::Common::REST_API_COMMON_PARAMS)

def self.title_patterns
[
[
%r{^(.+)/(.+)$},
[
[:parent_hostgroup],
[:name]
]
],
[
%r{(.+)},
[
[:name]
]
]
]
end
newparam(:name, namevar: true) do
desc 'The name of the hostgroup.'
end

newparam(:parent_hostgroup, namevar: true) do
desc 'The full title of the parent hostgroup'
end

newparam(:parent_hostgroup_name) do
desc 'The name of the parent hostgroup. This only needs to be given if your hostgroups contain slashes!'
end

newproperty(:description) do
desc 'The hostgroup\'s `description`'
end

newproperty(:organizations, array_matching: :all) do
desc 'An array of organizations (full titles) that this hostgroup should be part of'
def insync?(is) # rubocop:disable Naming/MethodParameterName
is.sort == should.sort
end
end

newproperty(:locations, array_matching: :all) do
desc 'An array of locations (full titles) that this hostgroup should be part of'
def insync?(is) # rubocop:disable Naming/MethodParameterName
is.sort == should.sort
end
end

autorequire(:foreman_hostgroup) do
self[:parent_hostgroup] if self[:ensure] == :present
end
end
1 change: 1 addition & 0 deletions lib/puppet/type/foreman_instance_host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Puppet::Type.newtype(:foreman_instance_host) do
desc 'foreman_instance_host marks a host as belonging to the set of hosts that make up the Foreman instance/application'

instance_eval(&PuppetX::Foreman::Common::REST_API_COMMON_PARAMS)
instance_eval(&PuppetX::Foreman::Common::FOREMAN_HOST_PARAMS)

autorequire(:foreman_host) do
Expand Down
Loading