Skip to content

Commit

Permalink
feat: add discovery rules resource (#2)
Browse files Browse the repository at this point in the history
feat: add discovery rules resource
  • Loading branch information
josi19 authored Nov 4, 2024
1 parent 1ffff62 commit 7172cd0
Show file tree
Hide file tree
Showing 13 changed files with 1,064 additions and 1 deletion.
53 changes: 53 additions & 0 deletions docs/resources/foreman_discovery_rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

# foreman_discovery_rules

Discovery rules in Foreman are used to automatically provision hosts based on predefined criteria.
These rules help streamline the process of adding new hosts to your infrastructure by automating the provisioning process based on specific conditions.

## Example Usage

```terraform
resource "foreman_discovery_rule" "example_rule_01" {
name = "Example Rule HPE servers"
search = "facts.bios_vendor = HPE"
hostgroup_ids = 3
hostname = "<%= @host.facts['nmprimary_dhcp4_option_host_name'] %>"
max_count = 0
priority = 100
enabled = true
location_ids = [2]
organization_ids = [1]
}
```

## Argument Reference

The following arguments are supported:

- `name` - (Required) The name of the discovery rule.
- `search` - (Required) The search criteria used to match hosts.
- `priority` - (Required) The priority of the rule.
- `hostgroup_id` - (Optional) The ID of the host group to which the discovered host will be assigned.
- `enabled` - (Optional) A boolean value indicating whether the discovery rule is enabled. When set to `true`, the rule is active and will be evaluated.
- `order` - (Optional) An integer specifying the order in which the rule is evaluated. Lower numbers are evaluated first.
- `parameters` - (Optional) A map of key-value pairs that will be saved as discovery rule parameters. These parameters can be used to pass additional information to the rule.
- `max_count` - (Optional) The maximum number of hosts that can be discovered by this rule. A value of `0` means unlimited.
- `hostname` - (Optional) The hostname pattern to be used for the discovered hosts.
- `location_ids` - (Optional) A list of location IDs where the discovered hosts will be assigned.
- `organization_ids` - (Optional) A list of organization IDs where the discovered hosts will be assigned.

## Attributes Reference

The following attributes are exported:

- `name` - The name of the discovery rule.
- `search` - The search criteria used to match hosts.
- `priority` - The priority of the rule.
- `hostgroup_id` - The ID of the host group to which the discovered host will be assigned.
- `enabled` - Whether the discovery rule is enabled.
- `order` - The order in which the rule is evaluated.
- `parameters` - A map of parameters that will be saved as discovery rule parameters.
- `max_count` - (Optional) The maximum number of hosts that can be discovered by this rule. A value of `0` means unlimited.
- `hostname` - (Optional) The hostname pattern to be used for the discovered hosts.
- `location_ids` - (Optional) A list of location IDs where the discovered hosts will be assigned.
- `organization_ids` - (Optional) A list of organization IDs where the discovered hosts will be assigned.
21 changes: 21 additions & 0 deletions examples/discovery_rule/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
provider "foreman" {
server_hostname = "192.168.1.118"
server_protocol = "https"

client_tls_insecure = true

client_username = "${var.client_username}"
client_password = "${var.client_password}"
}

resource "foreman_discovery_rule" "example_rule_01" {
name = "example-rule-01"
search = "facts.bios_vendor = HPE"
hostgroup_ids = 5
hostname = "<%= @host.facts['nmprimary_dhcp4_option_host_name'] %>"
max_count = 0
priority = 100
enabled = true
location_ids = [1]
organization_ids = [1]
}
214 changes: 214 additions & 0 deletions foreman/api/discovery_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package api

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"path"
"strconv"

"github.com/HanseMerkur/terraform-provider-utils/log"
)

const (
DiscoveryRuleEndpointPrefix = "/v2/discovery_rules/"
)

type ForemanDiscoveryRule struct {
ForemanObject
Name string `json:"name"`
Search string `json:"search,omitempty"`
HostGroupId int `json:"hostgroup_id,omitempty"`
Hostname string `json:"hostname,omitempty"`
HostsLimitMaxCount int `json:"max_count,omitempty"`
Priority int `json:"priority"`
Enabled bool `json:"enabled"`
LocationIds []int `json:"location_ids,omitempty"`
OrganizationIds []int `json:"organization_ids,omitempty"`
DefaultLocationId int `json:"location_id,omitempty"`
DefaultOrganizationId int `json:"organization_id,omitempty"`
}

type ForemanDiscoveryRuleResponse struct {
ForemanObject
Name string `json:"name"`
Search string `json:"search,omitempty"`
HostGroupId int `json:"hostgroup_id,omitempty"`
Hostname string `json:"hostname,omitempty"`
Priority int `json:"priority"`
Enabled bool `json:"enabled"`
HostsLimitMaxCount int `json:"hosts_limit,omitempty"`
Locations []EntityResponse `json:"locations,omitempty"`
Organizations []EntityResponse `json:"organizations,omitempty"`
}

type EntityResponse struct {
ID int `json:"id"`
Name string `json:"name"`
Title string `json:"title"`
Description any `json:"description"`
}

// CreateDiscoveryRule creates a new ForemanDiscoveryRule
func (c *Client) CreateDiscoveryRule(ctx context.Context, d *ForemanDiscoveryRule) (*ForemanDiscoveryRule, error) {
log.Tracef("foreman/api/discovery_rule.go#Create")

if d.DefaultLocationId == 0 {
d.DefaultLocationId = c.clientConfig.LocationID
}

if d.DefaultOrganizationId == 0 {
d.DefaultOrganizationId = c.clientConfig.OrganizationID
}

dJSONBytes, err := c.WrapJSON("discovery_rule", d)
if err != nil {
return nil, err
}

log.Debugf("discoveryruleJSONBytes: [%s]", dJSONBytes)

req, err := c.NewRequestWithContext(
ctx,
http.MethodPost,
DiscoveryRuleEndpointPrefix,
bytes.NewBuffer(dJSONBytes),
)
if err != nil {
return nil, err
}

var createdDiscoveryRule ForemanDiscoveryRule
if err := c.SendAndParse(req, &createdDiscoveryRule); err != nil {
return nil, err
}

log.Debugf("createdDiscoveryRule: [%+v]", createdDiscoveryRule)

return &createdDiscoveryRule, nil
}

// ReadDiscoveryRule reads the ForemanDiscoveryRule identified by the supplied ID
func (c *Client) ReadDiscoveryRule(ctx context.Context, id int) (*ForemanDiscoveryRuleResponse, error) {
log.Tracef("foreman/api/discovery_rule.go#Read")

reqEndpoint := path.Join(DiscoveryRuleEndpointPrefix, strconv.Itoa(id))
req, err := c.NewRequestWithContext(
ctx,
http.MethodGet,
reqEndpoint,
nil,
)
if err != nil {
return nil, err
}

var readDiscoveryRule ForemanDiscoveryRuleResponse
if err := c.SendAndParse(req, &readDiscoveryRule); err != nil {
return nil, err
}

log.Debugf("readDiscoveryRule: [%+v]", readDiscoveryRule)

return &readDiscoveryRule, nil
}

// UpdateDiscoveryRule updates the ForemanDiscoveryRule identified by the supplied ForemanDiscoveryRule
func (c *Client) UpdateDiscoveryRule(ctx context.Context, d *ForemanDiscoveryRule) (*ForemanDiscoveryRule, error) {
log.Tracef("foreman/api/discovery_rule.go#Update")

reqEndpoint := path.Join(DiscoveryRuleEndpointPrefix, strconv.Itoa(d.Id))

discoveryruleJSONBytes, err := c.WrapJSON("discovery_rule", d)
if err != nil {
return nil, err
}

log.Debugf("discoveryruleJSONBytes: [%s]", discoveryruleJSONBytes)

req, err := c.NewRequestWithContext(
ctx,
http.MethodPut,
reqEndpoint,
bytes.NewBuffer(discoveryruleJSONBytes),
)
if err != nil {
return nil, err
}

var updatedDiscoveryRule ForemanDiscoveryRule

if err := c.SendAndParse(req, &updatedDiscoveryRule); err != nil {
return nil, err
}

log.Debugf("updatedDiscoveryRule: [%+v]", updatedDiscoveryRule)

return &updatedDiscoveryRule, nil
}

// DeleteDiscoveryRule deletes the ForemanDiscoveryRule identified by the supplied ID
func (c *Client) DeleteDiscoveryRule(ctx context.Context, id int) error {
log.Tracef("foreman/api/discovery_rule.go#Delete")

reqEndpoint := path.Join(DiscoveryRuleEndpointPrefix, strconv.Itoa(id))

req, err := c.NewRequestWithContext(
ctx,
http.MethodDelete,
reqEndpoint,
nil,
)
if err != nil {
return err
}

return c.SendAndParse(req, nil)
}

// QueryDiscoveryRule queries the ForemanDiscoveryRule identified by the supplied ForemanDiscoveryRule
func (c *Client) QueryDiscoveryRule(ctx context.Context, d *ForemanDiscoveryRule) (QueryResponse, error) {
log.Tracef("foreman/api/discovery_rule.go#Search")

queryResponse := QueryResponse{}

req, err := c.NewRequestWithContext(
ctx,
http.MethodGet,
DiscoveryRuleEndpointPrefix,
nil,
)
if err != nil {
return queryResponse, err
}

reqQuery := req.URL.Query()
reqQuery.Set("search", fmt.Sprintf("name=\"%s\"", d.Name))

req.URL.RawQuery = reqQuery.Encode()
if err := c.SendAndParse(req, &queryResponse); err != nil {
return queryResponse, err
}

log.Debugf("queryResponse: [%+v]", queryResponse)

results := []ForemanDiscoveryRule{}
resultsBytes, err := json.Marshal(queryResponse.Results)
if err != nil {
return queryResponse, err
}

if err := json.Unmarshal(resultsBytes, &results); err != nil {
return queryResponse, err
}

iArr := make([]interface{}, len(results))
for idx, val := range results {
iArr[idx] = val
}
queryResponse.Results = iArr

return queryResponse, nil
}
13 changes: 12 additions & 1 deletion foreman/foreman_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package foreman
import (
"context"
"encoding/json"
"github.com/HanseMerkur/terraform-provider-utils/log"
"io"
"math/rand"
"net/http"
Expand All @@ -14,6 +13,8 @@ import (
"strings"
"testing"

"github.com/HanseMerkur/terraform-provider-utils/log"

tfrand "github.com/HanseMerkur/terraform-provider-utils/rand"
"github.com/terraform-coop/terraform-provider-foreman/foreman/api"

Expand Down Expand Up @@ -256,6 +257,8 @@ func TestCRUDFunction_CorrectURLAndMethod(t *testing.T) {

testCases = append(testCases, DataSourceForemanTemplateKindCorrectURLAndMethodTestCases(t)...)

testCases = append(testCases, ResourceForemanDiscoveryRuleCorrectURLAndMethodTestCases(t)...)

cred := api.ClientCredentials{}
conf := api.ClientConfig{}

Expand Down Expand Up @@ -352,6 +355,8 @@ func TestCRUDFunction_RequestDataEmpty(t *testing.T) {

testCases = append(testCases, DataSourceForemanTemplateKindRequestDataEmptyTestCases(t)...)

testCases = append(testCases, ResourceForemanDiscoveryRuleRequestDataEmptyTestCases(t)...)

cred := api.ClientCredentials{}
conf := api.ClientConfig{}

Expand Down Expand Up @@ -503,6 +508,8 @@ func TestCRUDFunction_StatusCodeError(t *testing.T) {

testCases = append(testCases, DataSourceForemanTemplateKindStatusCodeTestCases(t)...)

testCases = append(testCases, ResourceForemanDiscoveryRuleStatusCodeTestCases(t)...)

cred := api.ClientCredentials{}
conf := api.ClientConfig{}

Expand Down Expand Up @@ -587,6 +594,8 @@ func TestCRUDFunction_EmptyResponseError(t *testing.T) {

testCases = append(testCases, DataSourceForemanTemplateKindEmptyResponseTestCases(t)...)

testCases = append(testCases, ResourceForemanDiscoveryRuleEmptyResponseTestCases(t)...)

cred := api.ClientCredentials{}
conf := api.ClientConfig{}

Expand Down Expand Up @@ -708,6 +717,8 @@ func TestCRUDFunction_MockResponse(t *testing.T) {

testCases = append(testCases, DataSourceForemanTemplateKindMockResponseTestCases(t)...)

testCases = append(testCases, ResourceForemanDiscoveryRuleMockResponseTestCases(t)...)

cred := api.ClientCredentials{}
conf := api.ClientConfig{}

Expand Down
1 change: 1 addition & 0 deletions foreman/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ func Provider() *schema.Provider {
"foreman_architecture": resourceForemanArchitecture(),
"foreman_host": resourceForemanHost(),
"foreman_hostgroup": resourceForemanHostgroup(),
"foreman_discovery_rule": resourceForemanDiscoveryRule(),
"foreman_media": resourceForemanMedia(),
"foreman_model": resourceForemanModel(),
"foreman_operatingsystem": resourceForemanOperatingSystem(),
Expand Down
Loading

0 comments on commit 7172cd0

Please sign in to comment.