From 4cf5a6aeffa178ea11e4bb4acd2efcbf6cd86183 Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 13:44:00 +0200 Subject: [PATCH 01/13] add search method on foreman_resource --- lib/puppet/provider/foreman_resource/rest_v3.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/puppet/provider/foreman_resource/rest_v3.rb b/lib/puppet/provider/foreman_resource/rest_v3.rb index 00b653bc5..974dff741 100644 --- a/lib/puppet/provider/foreman_resource/rest_v3.rb +++ b/lib/puppet/provider/foreman_resource/rest_v3.rb @@ -131,4 +131,15 @@ def error_message(response) JSON.parse(response.body)['error']['full_messages'].join(' ') rescue "Response: #{response.code} #{response.message}" end end + + def search(resource, key, value) + path = "api/v2/#{resource}" + req = request(:get, path, search: %(#{key}="#{value}")) + + unless success?(req) + raise Puppet::Error, "Error making GET request to Foreman at #{request_uri(path)}: #{error_message(req)}" + end + + JSON.parse(req.body)['results'] + end end From 3dd2a5be666c27475ce4d1572dd8eecee5ee7bcd Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 14:24:50 +0200 Subject: [PATCH 02/13] add location_id and organization_id helpers to foreman_resource --- lib/puppet/provider/foreman_resource/rest_v3.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/puppet/provider/foreman_resource/rest_v3.rb b/lib/puppet/provider/foreman_resource/rest_v3.rb index 974dff741..b4d4b3d9a 100644 --- a/lib/puppet/provider/foreman_resource/rest_v3.rb +++ b/lib/puppet/provider/foreman_resource/rest_v3.rb @@ -142,4 +142,14 @@ def search(resource, key, value) JSON.parse(req.body)['results'] end + + def location_id(name) + res = search('locations', 'name', name).first + res.nil? ? nil : res['id'] + end + + def organization_id(name) + res = search('organizations', 'name', name).first + res.nil? ? nil : res['id'] + end end From 13a5bce06076bd9787d08e0b818ed31bc8ac858a Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 14:30:05 +0200 Subject: [PATCH 03/13] add foreman_location type --- lib/puppet/type/foreman_location.rb | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 lib/puppet/type/foreman_location.rb diff --git a/lib/puppet/type/foreman_location.rb b/lib/puppet/type/foreman_location.rb new file mode 100644 index 000000000..81b339f8f --- /dev/null +++ b/lib/puppet/type/foreman_location.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'puppet_x/foreman/common' + +Puppet::Type.newtype(:foreman_location) do + desc 'foreman_location configures a location in foreman.' + + instance_eval(&PuppetX::Foreman::Common::REST_API_COMMON_PARAMS) + + newparam(:name) do + desc 'The name of the location' + end + + newproperty(:parent) do + desc 'The name of the parent location' + end + + newproperty(:description) do + desc 'The description of this location' + end + + newproperty(:select_all_types, array_matching: :all) do + desc 'List of resource types for which to "Select All"' + end + + newproperty(:domains, array_matching: :all) do + desc 'A list of domain names to allow for this location' + end + + newproperty(:organizations, array_matching: :all) do + desc 'A list of organization names to allow for this location' + end + + autorequire(:foreman_location) do + self[:parent] if self[:ensure] == :present + end +end From 2b4cf5b88c7cbb334976de923021d2ff9a710ae3 Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 14:30:30 +0200 Subject: [PATCH 04/13] add foreman_location provider --- .../provider/foreman_location/rest_v3.rb | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 lib/puppet/provider/foreman_location/rest_v3.rb diff --git a/lib/puppet/provider/foreman_location/rest_v3.rb b/lib/puppet/provider/foreman_location/rest_v3.rb new file mode 100644 index 000000000..c732f9bfd --- /dev/null +++ b/lib/puppet/provider/foreman_location/rest_v3.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +Puppet::Type.type(:foreman_location).provide( # rubocop:disable Metrics/BlockLength + :rest_v3, + parent: Puppet::Type.type(:foreman_resource).provider(:rest_v3) +) do + desc 'foreman_location configures a location in foreman.' + confine feature: %i[json oauth] + + def initialize(value = {}) + super(value) + @property_flush = {} + end + + def location # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + @location ||= begin + loc_search = search('locations', 'name', resource[:name]).first + + return nil if loc_search.nil? + + path = "api/v2/locations/#{loc_search['id']}" + req = request(:get, path) + + unless success?(req) + raise Puppet::Error, "Error making GET request to Foreman at #{request_uri(path)}: #{error_message(req)}" + end + + result = JSON.parse(req.body) + debug("Using Foreman Location '#{resource[:name]}' with: #{result}") + result + end + end + + def id + location['id'] if location + end + + def exists? + !id.nil? + end + + def create # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + debug("Creating Foreman Location #{resource[:name]}") + + path = 'api/v2/locations' + payload = { + location: { + name: resource[:name], + parent_id: resource[:parent] ? location_id(resource[:parent]) : nil, + description: resource[:description], + domain_ids: domain_ids(resource[:domains]), + organization_ids: organization_ids(resource[:organizations]), + ignore_types: resource[:select_all_types] + } + } + req = request(:post, path, {}, payload.to_json) + + return if success?(req) + + raise Puppet::Error, "Error making POST request to Foreman at #{request_uri(path)}: #{error_message(req)}" + end + + def destroy + debug("Destroying Foreman Location #{resource[:name]}") + + path = "api/v2/locations/#{id}" + req = request(:delete, path) + + unless success?(req) + raise Puppet::Error, "Error making DELETE request to Foreman at #{request_uri(path)}: #{error_message(req)}" + end + + @location = nil + end + + def flush + return if @property_flush.empty? + + debug("Calling API to update properties for Foreman Location #{resource[:name]} with: #{@property_flush}") + + path = "api/v2/locations/#{id}" + req = request(:put, path, {}, { location: @property_flush }.to_json) + + return if success?(req) + + raise Puppet::Error, "Error making PUT request to Foreman at #{request_uri(path)}: #{error_message(r)}" + end + + def parent + location && location['parent_name'] ? locations['parent_name'].split('/')[-1] : nil + end + + def parent=(value) + if value.nil? + @property_flush[:parent_id] = nil + else + parent_id = location_id(value) + if parent_id.nil? + raise Puppet::Error, + "Could not find Foreman Location with name #{value} as parent for Location with name #{resource[:name]}" + end + + @property_flush[:parent_id] = parent_id + end + end + + def description + location ? location['description'] : nil + end + + def description=(value) + @property_flush[:description] = value + end + + def select_all_types + location ? location['select_all_types'] : [] + end + + def select_all_types=(value) + @property_flush[:ignore_types] = value + end + + def domains + location ? location['domains'].reject { |d| d['inherited'] }.map { |d| d['name'] } : nil + end + + def domain_ids(domains) + return nil if domains.nil? + + domains.map do |domain| + res = search('domains', 'name', domain).first + + raise Puppet::Error, "Could not find Foreman Domain with name '#{domain}'" if res.nil? + + res['id'] + end + end + + def domains=(value) + @property_flush[:domain_ids] = domain_ids(value) + end + + def organizations + location ? location['organizations'].reject { |o| o['inherited'] }.map { |o| o['name'] } : nil + end + + def organization_ids(orgs) + return nil if orgs.nil? + + orgs.map do |organization| + org_id = organization_id(organization) + + raise Puppet::Error, "Could not find Foreman Organization with name '#{organization}'" if org_id.nil? + + org_id + end + end + + def organizations=(value) + @property_flush[:organization_ids] = organization_ids(value) + end +end From cb91c5edb85640789fd64ea16d252a2ec4fb76f7 Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 15:26:52 +0200 Subject: [PATCH 05/13] fix pagination on foreman_resource.search --- lib/puppet/provider/foreman_resource/rest_v3.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/puppet/provider/foreman_resource/rest_v3.rb b/lib/puppet/provider/foreman_resource/rest_v3.rb index b4d4b3d9a..ad6a29936 100644 --- a/lib/puppet/provider/foreman_resource/rest_v3.rb +++ b/lib/puppet/provider/foreman_resource/rest_v3.rb @@ -134,7 +134,7 @@ def error_message(response) def search(resource, key, value) path = "api/v2/#{resource}" - req = request(:get, path, search: %(#{key}="#{value}")) + req = request(:get, path, { search: %(#{key}="#{value}"), per_page: 'all' }) unless success?(req) raise Puppet::Error, "Error making GET request to Foreman at #{request_uri(path)}: #{error_message(req)}" From 257b5d339967ca86585f0d8dc78571c4c09aedcb Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 15:54:29 +0200 Subject: [PATCH 06/13] add foreman_location unit tests --- spec/unit/foreman_location_rest_v3_spec.rb | 198 +++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 spec/unit/foreman_location_rest_v3_spec.rb diff --git a/spec/unit/foreman_location_rest_v3_spec.rb b/spec/unit/foreman_location_rest_v3_spec.rb new file mode 100644 index 000000000..5a888175f --- /dev/null +++ b/spec/unit/foreman_location_rest_v3_spec.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Puppet::Type.type(:foreman_location).provider(:rest_v3) do # rubocop:disable Metrics/BlockLength + let(:basic_params) do + { + name: 'example_location', + base_url: 'https://foreman.example.com', + consumer_key: 'oauth_key', + consumer_secret: 'oauth_secret', + effective_user: 'admin' + } + end + + let(:resource) do + Puppet::Type.type(:foreman_location).new( + basic_params + ) + end + + let(:provider) do + provider = described_class.new + provider.resource = resource + provider + end + + describe '#create' do # rubocop:disable Metrics/BlockLength + let(:expected_post_data) do + { + 'location' => { + 'name' => 'example_location', + 'parent_id' => nil, + 'description' => nil, + 'domain_ids' => nil, + 'organization_ids' => nil, + 'ignore_types' => nil + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:request).with(:post, 'api/v2/locations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + + context 'with parent set' do + let(:resource) do + Puppet::Type.type(:foreman_location).new( + basic_params.merge(parent: 'parent_location') + ) + end + let(:expected_post_data) do + { + 'location' => { + 'name' => 'example_location', + 'parent_id' => 101, + 'description' => nil, + 'domain_ids' => nil, + 'organization_ids' => nil, + 'ignore_types' => nil + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:location_id).with('parent_location').and_return(101) + allow(provider).to receive(:request).with(:post, 'api/v2/locations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + end + + context 'with description set' do + let(:resource) do + Puppet::Type.type(:foreman_location).new( + basic_params.merge(description: 'example description') + ) + end + let(:expected_post_data) do + { + 'location' => { + 'name' => 'example_location', + 'parent_id' => nil, + 'description' => 'example description', + 'domain_ids' => nil, + 'organization_ids' => nil, + 'ignore_types' => nil + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:request).with(:post, 'api/v2/locations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + end + + context 'with domains set' do + let(:resource) do + Puppet::Type.type(:foreman_location).new( + basic_params.merge(domains: ['example.com', 'example.org']) + ) + end + let(:expected_post_data) do + { + 'location' => { + 'name' => 'example_location', + 'parent_id' => nil, + 'description' => nil, + 'domain_ids' => [301, 302], + 'organization_ids' => nil, + 'ignore_types' => nil + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:domain_ids).with(['example.com', 'example.org']).and_return([301, 302]) + allow(provider).to receive(:request).with(:post, 'api/v2/locations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + end + + context 'with organizations set' do + let(:resource) do + Puppet::Type.type(:foreman_location).new( + basic_params.merge(organizations: %w[org1 org2]) + ) + end + let(:expected_post_data) do + { + 'location' => { + 'name' => 'example_location', + 'parent_id' => nil, + 'description' => nil, + 'domain_ids' => nil, + 'organization_ids' => [201, 202], + 'ignore_types' => nil + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:organization_id).with('org1').and_return(201) + allow(provider).to receive(:organization_id).with('org2').and_return(202) + allow(provider).to receive(:request).with(:post, 'api/v2/locations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + end + + context 'with select_all_types set' do + let(:resource) do + Puppet::Type.type(:foreman_location).new( + basic_params.merge(select_all_types: %w[SmartProxies]) + ) + end + let(:expected_post_data) do + { + 'location' => { + 'name' => 'example_location', + 'parent_id' => nil, + 'description' => nil, + 'domain_ids' => nil, + 'organization_ids' => nil, + 'ignore_types' => %w[SmartProxies] + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:request).with(:post, 'api/v2/locations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + end + end + + describe '#destroy' do + it 'sends DELETE request' do + allow(provider).to receive(:id).and_return(42) + allow(provider).to receive(:request).with(:delete, 'api/v2/locations/42').and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.destroy + end + end +end From 9b7cba1752c1e00576651a775f81ac097f85e02c Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 15:55:04 +0200 Subject: [PATCH 07/13] add foreman_organization type --- lib/puppet/type/foreman_organization.rb | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 lib/puppet/type/foreman_organization.rb diff --git a/lib/puppet/type/foreman_organization.rb b/lib/puppet/type/foreman_organization.rb new file mode 100644 index 000000000..2f7567e73 --- /dev/null +++ b/lib/puppet/type/foreman_organization.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'puppet_x/foreman/common' + +Puppet::Type.newtype(:foreman_organization) do + desc 'foreman_organization configures a organization in foreman.' + + instance_eval(&PuppetX::Foreman::Common::REST_API_COMMON_PARAMS) + + newparam(:name) do + desc 'The name of the organization' + end + + newproperty(:parent) do + desc 'The name of the parent organization' + end + + newproperty(:description) do + desc 'The description of this organization' + end + + newproperty(:select_all_types, array_matching: :all) do + desc 'List of resource types for which to "Select All"' + end + + newproperty(:domains, array_matching: :all) do + desc 'A list of domain names to allow for this organization' + end + + newproperty(:locations, array_matching: :all) do + desc 'A list of location names to allow for this organization' + end + + autorequire(:foreman_organization) do + self[:parent] if self[:ensure] == :present + end +end From c80e339c9ca337b1252fea98fda73341d505c556 Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 16:03:17 +0200 Subject: [PATCH 08/13] add foreman_organization provider --- .../provider/foreman_organization/rest_v3.rb | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 lib/puppet/provider/foreman_organization/rest_v3.rb diff --git a/lib/puppet/provider/foreman_organization/rest_v3.rb b/lib/puppet/provider/foreman_organization/rest_v3.rb new file mode 100644 index 000000000..1a534ed7c --- /dev/null +++ b/lib/puppet/provider/foreman_organization/rest_v3.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +Puppet::Type.type(:foreman_organization).provide( # rubocop:disable Metrics/BlockLength + :rest_v3, + parent: Puppet::Type.type(:foreman_resource).provider(:rest_v3) +) do + desc 'foreman_organization configures a organization in foreman.' + confine feature: %i[json oauth] + + def initialize(value = {}) + super(value) + @property_flush = {} + end + + def organization # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + @organization ||= begin + org_search = search('organizations', 'name', resource[:name]).first + + return nil if org_search.nil? + + path = "api/v2/organizations/#{org_search['id']}" + req = request(:get, path) + + unless success?(req) + raise Puppet::Error, "Error making GET request to Foreman at #{request_uri(path)}: #{error_message(req)}" + end + + result = JSON.parse(req.body) + debug("Using Foreman Organization '#{resource[:name]}' with: #{result}") + result + end + end + + def id + organization['id'] if organization + end + + def exists? + !id.nil? + end + + def create # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + debug("Creating Foreman Organization #{resource[:name]}") + + path = 'api/v2/organizations' + payload = { + organization: { + name: resource[:name], + parent_id: resource[:parent] ? organization_id(resource[:parent]) : nil, + description: resource[:description], + ignore_types: resource[:select_all_types], + domain_ids: domain_ids(resource[:domains]), + location_ids: location_ids(resource[:locations]) + } + } + req = request(:post, path, {}, payload.to_json) + + return if success?(req) + + raise Puppet::Error, "Error making POST request to Foreman at #{request_uri(path)}: #{error_message(req)}" + end + + def destroy + debug("Destroying Foreman Organization #{resource[:name]}") + + path = "api/v2/organizations/#{id}" + req = request(:delete, path) + + unless success?(req) + raise Puppet::Error, "Error making DELETE request to Foreman at #{request_uri(path)}: #{error_message(req)}" + end + + @organization = nil + end + + def flush + return if @property_flush.empty? + + debug("Calling API to update properties for Foreman Organization #{resource[:name]} with: #{@property_flush}") + + path = "api/v2/organizations/#{id}" + req = request(:put, path, {}, { organization: @property_flush }.to_json) + + return if success?(req) + + raise Puppet::Error, "Error making PUT request to Foreman at #{request_uri(path)}: #{error_message(r)}" + end + + def parent + organization && organization['parent_name'] ? organization['parent_name'].split('/')[-1] : nil + end + + def parent=(value) + if value.nil? + @property_flush[:parent_id] = nil + else + parent_id = organization_id(value) + + raise Puppet::Error, "Could not find Foreman Organzation with name #{value} as parent." if parent_id.nil? + + @property_flush[:parent_id] = parent_id + end + end + + def description + organization ? organization['description'] : nil + end + + def description=(value) + @property_flush[:description] = value + end + + def select_all_types + organization ? organization['select_all_types'] : [] + end + + def select_all_types=(value) + @property_flush[:ignore_types] = value + end + + def domains + organization ? organization['domains'].reject { |d| d['inherited'] }.map { |d| d['name'] } : nil + end + + def domain_ids(domains) + return nil if domains.nil? + + domains.map do |domain| + res = search('domains', 'name', domain).first + + raise Puppet::Error, "Could not find Foreman Domain with name '#{domain}'" if res.nil? + + res['id'] + end + end + + def domains=(value) + @property_flush[:domain_ids] = domain_ids(value) + end + + def locations + organization ? organization['locations'].reject { |l| l['inherited'] }.map { |l| l['name'] } : nil + end + + def location_ids(locations) + return nil if locations.nil? + + locations.map do |location| + location_id = location_id(location) + + raise Puppet::Error, "Could not find Foreman Location with name '#{location}'" if location_id.nil? + + lococation_id + end + end + + def locations=(value) + @property_flush[:location_ids] = location_ids(value) + end +end From 560246f685f0479ed43116e80d3707ef62a3ed89 Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 16:03:34 +0200 Subject: [PATCH 09/13] tweak foreman_location provider --- lib/puppet/provider/foreman_location/rest_v3.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/puppet/provider/foreman_location/rest_v3.rb b/lib/puppet/provider/foreman_location/rest_v3.rb index c732f9bfd..6f27cd97a 100644 --- a/lib/puppet/provider/foreman_location/rest_v3.rb +++ b/lib/puppet/provider/foreman_location/rest_v3.rb @@ -48,9 +48,9 @@ def create # rubocop:disable Metrics/AbcSize, Metrics/MethodLength name: resource[:name], parent_id: resource[:parent] ? location_id(resource[:parent]) : nil, description: resource[:description], + ignore_types: resource[:select_all_types], domain_ids: domain_ids(resource[:domains]), - organization_ids: organization_ids(resource[:organizations]), - ignore_types: resource[:select_all_types] + organization_ids: organization_ids(resource[:organizations]) } } req = request(:post, path, {}, payload.to_json) @@ -95,10 +95,8 @@ def parent=(value) @property_flush[:parent_id] = nil else parent_id = location_id(value) - if parent_id.nil? - raise Puppet::Error, - "Could not find Foreman Location with name #{value} as parent for Location with name #{resource[:name]}" - end + + raise Puppet::Error, "Could not find Foreman Location with name #{value} as parent." if parent_id.nil? @property_flush[:parent_id] = parent_id end From 7dd49877f1937c4a45c97908050a3ff04a158730 Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 16:09:00 +0200 Subject: [PATCH 10/13] fix typo --- lib/puppet/provider/foreman_organization/rest_v3.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/puppet/provider/foreman_organization/rest_v3.rb b/lib/puppet/provider/foreman_organization/rest_v3.rb index 1a534ed7c..09e067254 100644 --- a/lib/puppet/provider/foreman_organization/rest_v3.rb +++ b/lib/puppet/provider/foreman_organization/rest_v3.rb @@ -150,7 +150,7 @@ def location_ids(locations) raise Puppet::Error, "Could not find Foreman Location with name '#{location}'" if location_id.nil? - lococation_id + location_id end end From ab5b1436614ce78f660367bc02f10b72366a1723 Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 16:52:08 +0200 Subject: [PATCH 11/13] fix hash ordering in foreman_location unit tests --- spec/unit/foreman_location_rest_v3_spec.rb | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/unit/foreman_location_rest_v3_spec.rb b/spec/unit/foreman_location_rest_v3_spec.rb index 5a888175f..1f1d1ee30 100644 --- a/spec/unit/foreman_location_rest_v3_spec.rb +++ b/spec/unit/foreman_location_rest_v3_spec.rb @@ -32,9 +32,9 @@ 'name' => 'example_location', 'parent_id' => nil, 'description' => nil, + 'ignore_types' => nil, 'domain_ids' => nil, - 'organization_ids' => nil, - 'ignore_types' => nil + 'organization_ids' => nil } }.to_json end @@ -58,9 +58,9 @@ 'name' => 'example_location', 'parent_id' => 101, 'description' => nil, + 'ignore_types' => nil, 'domain_ids' => nil, - 'organization_ids' => nil, - 'ignore_types' => nil + 'organization_ids' => nil } }.to_json end @@ -86,9 +86,9 @@ 'name' => 'example_location', 'parent_id' => nil, 'description' => 'example description', + 'ignore_types' => nil, 'domain_ids' => nil, - 'organization_ids' => nil, - 'ignore_types' => nil + 'organization_ids' => nil } }.to_json end @@ -113,9 +113,9 @@ 'name' => 'example_location', 'parent_id' => nil, 'description' => nil, + 'ignore_types' => nil, 'domain_ids' => [301, 302], - 'organization_ids' => nil, - 'ignore_types' => nil + 'organization_ids' => nil } }.to_json end @@ -141,9 +141,9 @@ 'name' => 'example_location', 'parent_id' => nil, 'description' => nil, + 'ignore_types' => nil, 'domain_ids' => nil, - 'organization_ids' => [201, 202], - 'ignore_types' => nil + 'organization_ids' => [201, 202] } }.to_json end @@ -170,9 +170,9 @@ 'name' => 'example_location', 'parent_id' => nil, 'description' => nil, + 'ignore_types' => %w[SmartProxies], 'domain_ids' => nil, - 'organization_ids' => nil, - 'ignore_types' => %w[SmartProxies] + 'organization_ids' => nil } }.to_json end From 45608c857d692128338ca92120ca02a5d881fc28 Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 28 Aug 2024 17:14:00 +0200 Subject: [PATCH 12/13] add foreman_organization unit tests --- .../unit/foreman_organization_rest_v3_spec.rb | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 spec/unit/foreman_organization_rest_v3_spec.rb diff --git a/spec/unit/foreman_organization_rest_v3_spec.rb b/spec/unit/foreman_organization_rest_v3_spec.rb new file mode 100644 index 000000000..3307d99bd --- /dev/null +++ b/spec/unit/foreman_organization_rest_v3_spec.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Puppet::Type.type(:foreman_organization).provider(:rest_v3) do # rubocop:disable Metrics/BlockLength + let(:basic_params) do + { + name: 'example_organization', + base_url: 'https://foreman.example.com', + consumer_key: 'oauth_key', + consumer_secret: 'oauth_secret', + effective_user: 'admin' + } + end + + let(:resource) do + Puppet::Type.type(:foreman_organization).new( + basic_params + ) + end + + let(:provider) do + provider = described_class.new + provider.resource = resource + provider + end + + describe '#create' do # rubocop:disable Metrics/BlockLength + let(:expected_post_data) do + { + 'organization' => { + 'name' => 'example_organization', + 'parent_id' => nil, + 'description' => nil, + 'ignore_types' => nil, + 'domain_ids' => nil, + 'location_ids' => nil + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:request).with(:post, 'api/v2/organizations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + + context 'with parent set' do + let(:resource) do + Puppet::Type.type(:foreman_organization).new( + basic_params.merge(parent: 'parent_organization') + ) + end + let(:expected_post_data) do + { + 'organization' => { + 'name' => 'example_organization', + 'parent_id' => 101, + 'description' => nil, + 'ignore_types' => nil, + 'domain_ids' => nil, + 'location_ids' => nil + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:organization_id).with('parent_organization').and_return(101) + allow(provider).to receive(:request).with(:post, 'api/v2/organizations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + end + + context 'with description set' do + let(:resource) do + Puppet::Type.type(:foreman_organization).new( + basic_params.merge(description: 'example description') + ) + end + let(:expected_post_data) do + { + 'organization' => { + 'name' => 'example_organization', + 'parent_id' => nil, + 'description' => 'example description', + 'ignore_types' => nil, + 'domain_ids' => nil, + 'location_ids' => nil + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:request).with(:post, 'api/v2/organizations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + end + + context 'with select_all_types set' do + let(:resource) do + Puppet::Type.type(:foreman_organization).new( + basic_params.merge(select_all_types: %w[SmartProxies]) + ) + end + let(:expected_post_data) do + { + 'organization' => { + 'name' => 'example_organization', + 'parent_id' => nil, + 'description' => nil, + 'ignore_types' => %w[SmartProxies], + 'domain_ids' => nil, + 'location_ids' => nil + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:request).with(:post, 'api/v2/organizations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + end + + context 'with domains set' do + let(:resource) do + Puppet::Type.type(:foreman_organization).new( + basic_params.merge(domains: ['example.com', 'example.org']) + ) + end + let(:expected_post_data) do + { + 'organization' => { + 'name' => 'example_organization', + 'parent_id' => nil, + 'description' => nil, + 'ignore_types' => nil, + 'domain_ids' => [301, 302], + 'location_ids' => nil + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:domain_ids).with(['example.com', 'example.org']).and_return([301, 302]) + allow(provider).to receive(:request).with(:post, 'api/v2/organizations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + end + + context 'with locations set' do + let(:resource) do + Puppet::Type.type(:foreman_organization).new( + basic_params.merge(locations: %w[loc1 loc2]) + ) + end + let(:expected_post_data) do + { + 'organization' => { + 'name' => 'example_organization', + 'parent_id' => nil, + 'description' => nil, + 'ignore_types' => nil, + 'domain_ids' => nil, + 'location_ids' => [201, 202] + } + }.to_json + end + + it 'sends POST request' do + allow(provider).to receive(:location_id).with('loc1').and_return(201) + allow(provider).to receive(:location_id).with('loc2').and_return(202) + allow(provider).to receive(:request).with(:post, 'api/v2/organizations', {}, expected_post_data).and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.create + end + end + end + + describe '#destroy' do + it 'sends DELETE request' do + allow(provider).to receive(:id).and_return(42) + allow(provider).to receive(:request).with(:delete, 'api/v2/organizations/42').and_return( + instance_double(Net::HTTPOK, code: '201', body: {}.to_json) + ) + provider.destroy + end + end +end From 1455e4ba7a5f5b290517d73efb351e285f3f4350 Mon Sep 17 00:00:00 2001 From: Dieter Maes Date: Wed, 13 Nov 2024 13:29:38 +0100 Subject: [PATCH 13/13] Fix typo --- lib/puppet/provider/foreman_location/rest_v3.rb | 2 +- lib/puppet/provider/foreman_organization/rest_v3.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/puppet/provider/foreman_location/rest_v3.rb b/lib/puppet/provider/foreman_location/rest_v3.rb index 6f27cd97a..f1c8e5b9a 100644 --- a/lib/puppet/provider/foreman_location/rest_v3.rb +++ b/lib/puppet/provider/foreman_location/rest_v3.rb @@ -83,7 +83,7 @@ def flush return if success?(req) - raise Puppet::Error, "Error making PUT request to Foreman at #{request_uri(path)}: #{error_message(r)}" + raise Puppet::Error, "Error making PUT request to Foreman at #{request_uri(path)}: #{error_message(req)}" end def parent diff --git a/lib/puppet/provider/foreman_organization/rest_v3.rb b/lib/puppet/provider/foreman_organization/rest_v3.rb index 09e067254..7978a3d1f 100644 --- a/lib/puppet/provider/foreman_organization/rest_v3.rb +++ b/lib/puppet/provider/foreman_organization/rest_v3.rb @@ -83,7 +83,7 @@ def flush return if success?(req) - raise Puppet::Error, "Error making PUT request to Foreman at #{request_uri(path)}: #{error_message(r)}" + raise Puppet::Error, "Error making PUT request to Foreman at #{request_uri(path)}: #{error_message(req)}" end def parent