From 23d40588270e24df052ebb1fd2d5ff201dc7a85a Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Mon, 2 May 2022 19:45:16 -0700 Subject: [PATCH] Add domains and subnets to the puppet API --- lib/puppet/provider/foreman_domain/rest_v3.rb | 56 +++++++++++++++ lib/puppet/provider/foreman_subnet/rest_v3.rb | 63 ++++++++++++++++ lib/puppet/type/foreman_domain.rb | 9 +++ lib/puppet/type/foreman_subnet.rb | 9 +++ lib/puppet_x/foreman/common.rb | 44 ++++++++++++ spec/unit/foreman_domain_rest_v3_spec.rb | 71 +++++++++++++++++++ 6 files changed, 252 insertions(+) create mode 100644 lib/puppet/provider/foreman_domain/rest_v3.rb create mode 100644 lib/puppet/provider/foreman_subnet/rest_v3.rb create mode 100644 lib/puppet/type/foreman_domain.rb create mode 100644 lib/puppet/type/foreman_subnet.rb create mode 100644 spec/unit/foreman_domain_rest_v3_spec.rb diff --git a/lib/puppet/provider/foreman_domain/rest_v3.rb b/lib/puppet/provider/foreman_domain/rest_v3.rb new file mode 100644 index 000000000..240bab707 --- /dev/null +++ b/lib/puppet/provider/foreman_domain/rest_v3.rb @@ -0,0 +1,56 @@ +Puppet::Type.type(:foreman_domain).provide(:rest_v3, :parent => Puppet::Type.type(:foreman_resource).provider(:rest_v3)) do + confine :feature => [:json, :oauth] + + def exists? + !id.nil? + end + + def create + path = "api/v2/domains" + payload = { + :domain => { + :name => resource[:name], + :fullname => resource[:fullname], + } + } + req = request(:post, path, {}, payload.to_json) + + unless success?(req) + error_string = "Error making POST request to Foreman at #{request_uri(path)}: #{error_message(req)}" + raise Puppet::Error.new(error_string) + end + end + + def destroy + req = request(:delete, destroy_path, {}) + + unless success?(req) + error_string = "Error making DELETE request to Foreman at #{request_uri(path)}: #{error_message(req)}" + raise Puppet::Error.new(error_string) + end + end + + def id + domain['id'] if domain + end + + def domain + @domain ||= begin + path = 'api/v2/domains' + req = request(:get, path, :search => %{name="#{resource[:name]}"}) + + unless success?(req) + error_string = "Error making GET request to Foreman at #{request_uri(path)}: #{error_message(req)}" + raise Puppet::Error.new(error_string) + end + + JSON.load(req.body)['results'].first + end + end + + private + + def destroy_path + "api/v2/domains/#{id}" + end +end diff --git a/lib/puppet/provider/foreman_subnet/rest_v3.rb b/lib/puppet/provider/foreman_subnet/rest_v3.rb new file mode 100644 index 000000000..171cbf28b --- /dev/null +++ b/lib/puppet/provider/foreman_subnet/rest_v3.rb @@ -0,0 +1,63 @@ +Puppet::Type.type(:foreman_subnet).provide(:rest_v3, :parent => Puppet::Type.type(:foreman_resource).provider(:rest_v3)) do + confine :feature => [:json, :oauth] + + def exists? + !id.nil? + end + + def create + path = "api/v2/subnets" + payload = { + :subnet => { + :name => resource[:name], + :description => resource[:description], + :network_type => resource[:network_type], + :network => resource[:network], + :cidr => resource[:cidr], + :mask => resource[:mask], + :from => resource[:from], + :to => resource[:to], + } + } + + req = request(:post, path, {}, payload.to_json) + + unless success?(req) + error_string = "Error making POST request to Foreman at #{request_uri(path)}: #{error_message(req)}" + raise Puppet::Error.new(error_string) + end + end + + def destroy + req = request(:delete, destroy_path, {}) + + unless success?(req) + error_string = "Error making DELETE request to Foreman at #{request_uri(path)}: #{error_message(req)}" + raise Puppet::Error.new(error_string) + end + end + + def id + subnet['id'] if subnet + end + + def subnet + @subnet ||= begin + path = 'api/v2/subnets' + req = request(:get, path, :search => %{name="#{resource[:name]}"}) + + unless success?(req) + error_string = "Error making GET request to Foreman at #{request_uri(path)}: #{error_message(req)}" + raise Puppet::Error.new(error_string) + end + + JSON.load(req.body)['results'].first + end + end + + private + + def destroy_path + "api/v2/subnets/#{id}" + end +end diff --git a/lib/puppet/type/foreman_domain.rb b/lib/puppet/type/foreman_domain.rb new file mode 100644 index 000000000..0add8709a --- /dev/null +++ b/lib/puppet/type/foreman_domain.rb @@ -0,0 +1,9 @@ +require_relative '../../puppet_x/foreman/common' + +Puppet::Type.newtype(:foreman_domain) do + desc 'foreman_domain creates a domain in foreman.' + + instance_eval(&PuppetX::Foreman::Common::REST_API_COMMON_PARAMS) + instance_eval(&PuppetX::Foreman::Common::FOREMAN_DOMAIN_PARAMS) + +end diff --git a/lib/puppet/type/foreman_subnet.rb b/lib/puppet/type/foreman_subnet.rb new file mode 100644 index 000000000..11cc3711e --- /dev/null +++ b/lib/puppet/type/foreman_subnet.rb @@ -0,0 +1,9 @@ +require_relative '../../puppet_x/foreman/common' + +Puppet::Type.newtype(:foreman_subnet) do + desc 'foreman_domain creates a subnet in foreman.' + + instance_eval(&PuppetX::Foreman::Common::REST_API_COMMON_PARAMS) + instance_eval(&PuppetX::Foreman::Common::FOREMAN_SUBNET_PARAMS) + +end diff --git a/lib/puppet_x/foreman/common.rb b/lib/puppet_x/foreman/common.rb index 63a7f4ebd..98dfca4f7 100644 --- a/lib/puppet_x/foreman/common.rb +++ b/lib/puppet_x/foreman/common.rb @@ -56,6 +56,50 @@ module Common desc 'The name of the host.' end end + + FOREMAN_DOMAIN_PARAMS = Proc.new do + newparam(:name, :namevar => true) do + desc 'The name of the domain resource.' + end + + newparam(:fullname) do + desc 'The name/description of the domain.' + end + end + + FOREMAN_SUBNET_PARAMS = Proc.new do + newparam(:name, :namevar => true) do + desc 'The name of the subnet resource.' + end + + newparam(:description) do + desc 'The subnet description.' + end + + newparam(:network_type) do + desc 'The subnet network type, either "IPv4" or "IPv6".' + end + + newparam(:network) do + desc 'The subnet network address.' + end + + newparam(:cidr) do + desc 'The subnet network CIDR.' + end + + newparam(:mask) do + desc 'The subnet network mask.' + end + + newparam(:from) do + desc 'Starting IP for auto suggestion.' + end + + newparam(:to) do + desc 'Ending IP for auto suggestion.' + end + end end end end diff --git a/spec/unit/foreman_domain_rest_v3_spec.rb b/spec/unit/foreman_domain_rest_v3_spec.rb new file mode 100644 index 000000000..cc723bf36 --- /dev/null +++ b/spec/unit/foreman_domain_rest_v3_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe Puppet::Type.type(:foreman_domain).provider(:rest_v3) do + let(:resource) do + Puppet::Type.type(:foreman_domain).new( + :name => 'example.com', + :fullname => 'domain entry for example.com', + :base_url => 'https://foreman.example.com', + :consumer_key => 'oauth_key', + :consumer_secret => 'oauth_secret', + :effective_user => 'admin' + ) + end + + let(:provider) do + provider = described_class.new + provider.resource = resource + provider + end + + describe '#create' do + it 'sends POST request' do + expect(provider).to receive(:request).with(:post, 'api/v2/domains', {}, kind_of(String)).and_return( + double(:code => '201', :body => {'id' => 1, 'name' => 'example.com', 'fullname' => 'domain entry for example.com'}) + ) + provider.create + end + end + + describe '#destroy' do + it 'sends DELETE request' do + expect(provider).to receive(:id).and_return(1) + expect(provider).to receive(:request).with(:delete, 'api/v2/domains/1', {}).and_return(double(:code => '204')) + provider.destroy + end + end + + describe '#exists?' do + it 'returns true when domain is marked as a foreman domain' do + expect(provider).to receive(:domain).twice.and_return({"id" => 1}) + expect(provider.exists?).to be true + end + + it 'returns nil when domain does not exist' do + expect(provider).to receive(:domain).and_return(nil) + expect(provider.exists?).to be false + end + end + + describe '#id' do + it 'returns ID from domain hash' do + expect(provider).to receive(:domain).twice.and_return({'id' => 1}) + expect(provider.id).to eq(1) + end + + it 'returns nil when domain is absent' do + expect(provider).to receive(:domain).and_return(nil) + expect(provider.id).to be_nil + end + end + + describe '#domain' do + it 'returns domain hash from API results' do + expect(provider).to receive(:request).with(:get, 'api/v2/domains', :search => 'name="example.com"').and_return( + double('response', :body => {:results => [{:id => 1, :name => 'example.com'}]}.to_json, :code => '200') + ) + expect(provider.domain['id']).to eq(1) + expect(provider.domain['name']).to eq('example.com') + end + end +end