Skip to content

Commit

Permalink
foreman_host: Replace the "name" attribute with "fqdn" (computed) and…
Browse files Browse the repository at this point in the history
… "shortname" (required) (#121)

* Add more tracef log entries; small formattings

* resourceForemanHost: Move resourceForemanHostCustomizeDiff in customdiff.All

* Add fqdn and shortname attributes to Foreman host, handle name

This commit introduces the new attributes "fqdn" and "shortname" to the
Terraform provider for foreman_host objects. Formerly, the "name"
attribute was used to configure and read the hostname of a host. But
this value can be inconsistent, because Foreman might expand a short
hostname to an FQDN, depending on a setting. (this is not a bug)

Because of this unexpected behaviour (results in "inconstistent plan" in
Terraform) the name attribute is now bypassed. Shortname is the new
required input variable.

Summary:
* name: was required, is now optional and computed
* fqdn: new, read-only, always returns the FQDN
* shortname: required and checked for dots (".") to issue warnings

Refs #116

* Add constructShortname func to Foreman host API wrapper

"Shortname" does not exist in the Foreman API, only "name" which is
either shortname or FQDN depending on a setting. For a more expected
behaviour the shortname and fqdn fields were introduced. This commit
fills the Shortname field in the ForemanHost API struct.

* Allow name to not have dots

* Fix shortname/fqdn tests; make name argument deprecated and read-only

The tests use an API mock response file to check against a mocked
Terraform schema. Since neither "shortname" nor "fqdn" host attributes
are in the Foreman API, we exclude them in the tests. They are
constructed from the "name" given by Foreman, or passed in in case of
the shortname.

To enforce the switch from "name" to "shortname", name is now a
deprecated argument. It is still accessible via the name attribute,
which is filled when the resource is read from the API.

* Move resourceForemanHostCustomizeDiffComputeAttributes into extra func again
  • Loading branch information
bitkeks committed Jul 19, 2023
1 parent 08f9fda commit d72e9c0
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 38 deletions.
15 changes: 6 additions & 9 deletions docs/resources/foreman_host.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ A host managed by Foreman.
```
# Autogenerated example with required keys
resource "foreman_host" "example" {
name = "compute01.dc1.company.com"
}
```

Expand All @@ -31,27 +30,24 @@ The following arguments are supported:
- `hostgroup_id` - (Optional, Force New) ID of the hostgroup to assign to the host.
- `image_id` - (Optional, Force New) ID of an image to be used as base for this host when cloning
- `interfaces_attributes` - (Optional) Host interface information.
- `manage_build` - (Optional) REMOVED, please use the new 'managed' key instead. Create host only, don't set build status or manage power states
- `manage_power_operations` - (Optional) Manage power operations, e.g. power on, if host's build flag will be enabled.
- `managed` - (Optional) Whether or not this host is managed by Foreman. Create host only, don't set build status or manage power states.
- `medium_id` - (Optional, Force New) ID of the medium mounted on the host.
- `method` - (Optional) REMOVED - use build argument instead to manage build flag of host.
- `model_id` - (Optional) ID of the hardware model if applicable
- `name` - (Required, Force New) Host fully qualified domain name.
- `operatingsystem_id` - (Optional, Force New) ID of the operating system to put on the host.
- `owner_id` - (Optional) ID of the user or usergroup that owns the host.
- `owner_type` - (Optional) Owner of the host, must be either User ot Usergroup
- `parameters` - (Optional) A map of parameters that will be saved as host parameters in the machine config.
- `provision_method` - (Optional) A string, "build" to deploy from the network, "image" to deploy from a disk image.
- `provision_method` - (Optional, Force New) Sets the provision method in Foreman for this host: either network-based ('build') or image-based ('image')
- `puppet_class_ids` - (Optional) IDs of the applied puppet classes.
- `retry_count` - (Optional) Number of times to retry on a failed attempt to register or delete a host in foreman.
- `shortname` - (Required, Force New) The short name of this host. Example: when the FQDN is 'host01.example.org', then 'host01' is the short name.


## Attributes Reference

The following attributes are exported:

- `build` - Whether or not this host's build flag will be enabled in Foreman. Default is true, which means host will be built at next boot.
- `comment` - Add additional information about this host.Note: Changes to this attribute will trigger a host rebuild.
- `compute_attributes` - Hypervisor specific VM options. Must be a JSON string, as every compute provider has different attributes schema
- `compute_profile_id` -
Expand All @@ -61,21 +57,22 @@ The following attributes are exported:
- `domain_name` - The domain name of the host.
- `enable_bmc` - Enables PMI/BMC functionality. On create and update calls, having this enabled will force a host to poweroff, set next boot to PXE and power on. Defaults to `false`.
- `environment_id` - ID of the environment to assign to the host.
- `fqdn` - Host fully qualified domain name. Read-only value to be used in variables.
- `hostgroup_id` - ID of the hostgroup to assign to the host.
- `image_id` - ID of an image to be used as base for this host when cloning
- `interfaces_attributes` - Host interface information.
- `manage_build` - REMOVED, please use the new 'managed' key instead. Create host only, don't set build status or manage power states
- `manage_power_operations` - Manage power operations, e.g. power on, if host's build flag will be enabled.
- `managed` - Whether or not this host is managed by Foreman. Create host only, don't set build status or manage power states.
- `medium_id` - ID of the medium mounted on the host.
- `method` - REMOVED - use build argument instead to manage build flag of host.
- `model_id` - ID of the hardware model if applicable
- `name` - Host fully qualified domain name.
- `name` - Name of this host as stored in Foreman. Can be short name or FQDN, depending on your Foreman settings (especially the setting 'append_domain_name_for_hosts').
- `operatingsystem_id` - ID of the operating system to put on the host.
- `owner_id` - ID of the user or usergroup that owns the host.
- `owner_type` - Owner of the host, must be either User ot Usergroup
- `parameters` - A map of parameters that will be saved as host parameters in the machine config.
- `provision_method` - Sets the provision method in Foreman for this host: either network-based ('build') or image-based ('image')
- `puppet_class_ids` - IDs of the applied puppet classes.
- `retry_count` - Number of times to retry on a failed attempt to register or delete a host in foreman.
- `shortname` - The short name of this host. Example: when the FQDN is 'host01.example.org', then 'host01' is the short name.
- `token` - Build token. Can be used to signal to Foreman that a host build is complete.

61 changes: 55 additions & 6 deletions foreman/api/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"strings"

"github.com/HanseMerkur/terraform-provider-utils/log"
)
Expand Down Expand Up @@ -48,6 +49,15 @@ type ForemanHost struct {
// Inherits the base object's attributes
ForemanObject

// Shortname, FQDN without DomainName.
// Not provided by the API, only available in templates with embedded Ruby
Shortname string

// The "fqdn" field in the Terraform schema exists only in the schema,
// not in this struct. Reason: name is a Foreman-managed field that can either hold
// the shortname or the FQDN, but the "fqdn" field is presented to the user
// and must be consistent.

// Whether or not to rebuild the host on reboot
Build bool `json:"build"`
// Current build status
Expand Down Expand Up @@ -105,7 +115,7 @@ type ForemanHost struct {
ComputeProfileId *int `json:"compute_profile_id,omitempty"`
// IDs of the puppet classes applied to the host
PuppetClassIds []int `json:"puppet_class_ids,omitempty"`
// Build token
// Build token, used by Foreman to provide a phone-home access token
Token string `json:"token,omitempty"`
// List of config groups to apply to the hostg
ConfigGroupIds []int `json:"config_group_ids"`
Expand Down Expand Up @@ -256,7 +266,7 @@ func (c *Client) SendPowerCommand(ctx context.Context, h *ForemanHost, cmd inter
// returned reference will have its ID and other API default values set by this
// function.
func (c *Client) CreateHost(ctx context.Context, h *ForemanHost, retryCount int) (*ForemanHost, error) {
log.Tracef("foreman/api/host.go#Create")
log.Tracef("foreman/api/host.go#CreateHost")

reqEndpoint := fmt.Sprintf("/%s", HostEndpointPrefix)

Expand Down Expand Up @@ -297,6 +307,10 @@ func (c *Client) CreateHost(ctx context.Context, h *ForemanHost, retryCount int)
return nil, sendErr
}

if err := constructShortname(&createdHost); err != nil {
return nil, err
}

createdHost.InterfacesAttributes = createdHost.InterfacesAttributesDecode
createdHost.PuppetClassIds = foremanObjectArrayToIdIntArray(createdHost.PuppetClassesDecode)
createdHost.ConfigGroupIds = foremanObjectArrayToIdIntArray(createdHost.ConfigGroupsDecode)
Expand All @@ -315,7 +329,7 @@ func (c *Client) CreateHost(ctx context.Context, h *ForemanHost, retryCount int)
// ReadHost reads the attributes of a ForemanHost identified by the supplied ID
// and returns a ForemanHost reference.
func (c *Client) ReadHost(ctx context.Context, id int) (*ForemanHost, error) {
log.Tracef("foreman/api/host.go#Read")
log.Tracef("foreman/api/host.go#ReadHost")

reqEndpoint := fmt.Sprintf("/%s/%d", HostEndpointPrefix, id)

Expand All @@ -335,6 +349,10 @@ func (c *Client) ReadHost(ctx context.Context, id int) (*ForemanHost, error) {
return nil, sendErr
}

if err := constructShortname(&readHost); err != nil {
return nil, err
}

computeAttributes, _ := c.readComputeAttributes(ctx, id)
if len(computeAttributes) > 0 {
readHost.ComputeAttributes = computeAttributes
Expand All @@ -351,7 +369,7 @@ func (c *Client) ReadHost(ctx context.Context, id int) (*ForemanHost, error) {
// supplied ForemanHost will be updated. A new ForemanHost reference is
// returned with the attributes from the result of the update operation.
func (c *Client) UpdateHost(ctx context.Context, h *ForemanHost, retryCount int) (*ForemanHost, error) {
log.Tracef("foreman/api/host.go#Update")
log.Tracef("foreman/api/host.go#UpdateHost")

reqEndpoint := fmt.Sprintf("/%s/%d", HostEndpointPrefix, h.Id)

Expand Down Expand Up @@ -391,6 +409,10 @@ func (c *Client) UpdateHost(ctx context.Context, h *ForemanHost, retryCount int)
return nil, sendErr
}

if err := constructShortname(&updatedHost); err != nil {
return nil, err
}

computeAttributes, _ := c.readComputeAttributes(ctx, h.Id)
if len(computeAttributes) > 0 {
updatedHost.ComputeAttributes = computeAttributes
Expand All @@ -406,7 +428,7 @@ func (c *Client) UpdateHost(ctx context.Context, h *ForemanHost, retryCount int)

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

reqEndpoint := fmt.Sprintf("/%s/%d", HostEndpointPrefix, id)

Expand All @@ -425,6 +447,7 @@ func (c *Client) DeleteHost(ctx context.Context, id int) error {

// Compute Attributes are only available via dedicated API endpoint. readComputeAttributes gets this endpoint.
func (c *Client) readComputeAttributes(ctx context.Context, id int) (map[string]interface{}, error) {
log.Tracef("foreman/api/host.go#readComputeAttributes")

reqEndpoint := fmt.Sprintf("/%s/%d/%s", HostEndpointPrefix, id, ComputeAttributesSuffix)

Expand All @@ -445,10 +468,36 @@ func (c *Client) readComputeAttributes(ctx context.Context, id int) (map[string]
}

readVmAttributesStr := make(map[string]interface{}, len(readVmAttributes))

for idx, val := range readVmAttributes {
readVmAttributesStr[idx] = val
}

return readVmAttributesStr, nil
}

func constructShortname(host *foremanHostDecode) error {
log.Tracef("foreman/api/host.go#constructShortname")

// Construct shortname from 'name'
if host.Shortname == "" {
before, after, found := strings.Cut(host.Name, ".")

// If no dot is found and shortname is not defined, Foreman probably does not expand hostnames with the domain name
if !found {
host.Shortname = host.Name
return nil
}

// Sanity check
if host.DomainName != "" && host.DomainName != after {
log.Errorf("After Cut of host.Name to find the shortname, the domain part did not match the rest of the 'name' string")
}

// If all went well, set the shortname to the first string from FQDN ('name' in Foreman)
log.Debugf("constructShortname: Shortname will be set to first element from FQDN: %s", before)
host.Shortname = before
} else {
log.Debugf("constructShortname: host.Shortname is not empty (is %s), so nothing is done", host.Shortname)
}
return nil
}
Loading

0 comments on commit d72e9c0

Please sign in to comment.