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

Add domains and subnets as puppet resources that interact with the Foreman API #1047

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
58 changes: 58 additions & 0 deletions lib/puppet/provider/foreman_domain/rest_v3.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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],
:dns_id => search('smart_proxies', resource[:dns_id]),
}
}

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
15 changes: 15 additions & 0 deletions lib/puppet/provider/foreman_resource/rest_v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,19 @@ def error_message(response)
JSON.parse(response.body)['error']['full_messages'].join(' ') rescue "Response: #{response.code} #{response.message}"
end
end

def search(id_name, value)
if !value.nil? and value.start_with?("search=") then
lookup_uri = "api/v2/" + id_name + "?" + value
lookup = request(:get, lookup_uri)
Comment on lines +136 to +138
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this creates some redundancy. We already have request which accepts a params hash so if anything this should pass search as a hash. That properly escapes it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekohl is there an example of using request I could have a look at to test with and make sure this works with it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless success?(lookup)
error_string = "Error making GET request to Foreman at #{lookup_uri}: #{error_message(lookup)}"
raise Puppet::Error.new(error_string)
end

JSON.load(lookup.body)['results'][0]['id']
else
value
end
end
end
75 changes: 75 additions & 0 deletions lib/puppet/provider/foreman_subnet/rest_v3.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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],
:gateway => resource[:gateway],
:dns_primary => resource[:dns_primary],
:dns_secondary => resource[:dns_secondary],
:ipam => !resource[:ipam].nil? ? resource[:ipam] : 'None',
:from => resource[:from],
:to => resource[:to],
:vlanid => resource[:vlanid],
:domain_ids => resource[:domain_ids].map {|s| search('domains', s)},
:dhcp_id => search('smart_proxies', resource[:dhcp_id]),
:tftp_id => search('smart_proxies', resource[:tftp_id]),
:httpboot_id => search('smart_proxies', resource[:httpboot_id]),
:dns_id => search('smart_proxies', resource[:dns_id]),
:template_id => search('smart_proxies', resource[:template_id]),
:bmc_id => search('smart_proxies', resource[:bmc_id]),
}
}

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
9 changes: 9 additions & 0 deletions lib/puppet/type/foreman_domain.rb
Original file line number Diff line number Diff line change
@@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're not reusing these (and it looks like you're not), it's better to inline them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you help me out here with what you're looking for? I'm more of a C, C++, Python programmer, very new to Ruby.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request is to move the methods being generated by PuppetX::Foreman::Common::FOREMAN_DOMAIN_PARAMS directly into this file on the foreman_domain type instead of the extra layer of meta-programming since there isn't another type which will reuse these methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stated another way, the methods in lib/puppet_x/foreman/common.rb should be moved into foreman_*/rest_v3.rb.


end
9 changes: 9 additions & 0 deletions lib/puppet/type/foreman_subnet.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require_relative '../../puppet_x/foreman/common'

Puppet::Type.newtype(:foreman_subnet) do
desc 'foreman_subnet creates a subnet in foreman.'

instance_eval(&PuppetX::Foreman::Common::REST_API_COMMON_PARAMS)
instance_eval(&PuppetX::Foreman::Common::FOREMAN_SUBNET_PARAMS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be inlined, like domain.


end
96 changes: 96 additions & 0 deletions lib/puppet_x/foreman/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,102 @@ 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

newparam(:dns_id) do
desc 'DNS Smart Proxy ID.'
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(:gateway) do
desc 'The subnet gateway.'
end

newparam(:dns_primary) do
desc 'Primary DNS address.'
end

newparam(:dns_secondary) do
desc 'Secondary DNS address.'
end

newparam(:ipam) do
desc 'IPAM Source type.'
end

newparam(:to) do
desc 'Static IPAM end address.'
end

newparam(:from) do
desc 'Static IPAM start address.'
end

newparam(:vlanid) do
desc 'VLAN ID for this subnet.'
end

newparam(:domain_ids) do
desc 'Domain IDs or searches to attribute to subnet.'
end

newparam(:dhcp_id) do
desc 'DHCP Smart Proxy ID.'
end

newparam(:tftp_id) do
desc 'TFTP Smart Proxy ID.'
end

newparam(:httpboot_id) do
desc 'HTTP Boot Smart Proxy ID.'
end

newparam(:dns_id) do
desc 'DNS Smart Proxy ID.'
end

newparam(:template_id) do
desc 'Template Smart Proxy ID.'
end

newparam(:bmc_id) do
desc 'BMC Smart Proxy ID.'
end
end
end
end
end
71 changes: 71 additions & 0 deletions spec/unit/foreman_domain_rest_v3_spec.rb
Original file line number Diff line number Diff line change
@@ -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