Skip to content

Commit

Permalink
Implement compute profiles (#134)
Browse files Browse the repository at this point in the history
* First draft of computeprofile resource

Implements the resource and refactors the data source.
The compute_attributes JSON is more tricky to handle, needs more testing
and validation.

* Extend compute_profiles with compute_attributes

Implements creation and deletion of compute profiles and their own
compute attributes. The most difficult part is the separate creation of
compute attributes, and the combined parsing to create a single struct
from both API sources.

Terraform understands multiple formats: strings (jsonencode..), lists,
maps etc. This implementation uses maps in the schema.

See examples/compute_profiles for reference.

* compute profiles: add resourceForemanComputeprofileRead; fix list init

* Compute profile: add custom JSON marshaller to ForemanComputeAttribute

Adds a custom marshalling func to ForemanComputeAttribute to handle the
JSONified attributes (e.g. boot_order and interfaces_attributes).

Also adds a more complex example, see examples/compute_profile/

* Comp Profile: remove "name" attr from compute attributes

Signed-off-by: Dominik Pataky <[email protected]>

* Compute profiles: fix parsing and marshalling of compute attributes

The compute attributes are more difficult to handle, since they are very
dynamically configurable in the Terraform manifests. This commit
improves JSON marshalling and unmarshalling and type handling.

Especially the correct setting of id and name of compute attributes was
reworked, and tested.

Signed-off-by: Dominik Pataky <[email protected]>

* Implement update func for compute profiles

Handles updates from Terraform for compute profiles and their respective
compute attributes.

Signed-off-by: Dominik Pataky <[email protected]>

---------

Signed-off-by: Dominik Pataky <[email protected]>
  • Loading branch information
bitkeks authored Aug 28, 2023
1 parent 51d1ace commit 3ffdb13
Show file tree
Hide file tree
Showing 9 changed files with 540 additions and 40 deletions.
3 changes: 2 additions & 1 deletion docs/data-sources/foreman_computeprofile.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# foreman_computeprofile



Foreman representation of a compute profile.


## Example Usage
Expand All @@ -26,5 +26,6 @@ The following arguments are supported:

The following attributes are exported:

- `compute_attributes` - List of compute attributes
- `name` - Compute profile name.

31 changes: 31 additions & 0 deletions docs/resources/foreman_computeprofile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

# foreman_computeprofile


Foreman representation of a compute profile.


## Example Usage

```
# Autogenerated example with required keys
resource "foreman_computeprofile" "example" {
}
```


## Argument Reference

The following arguments are supported:

- `compute_attributes` - (Required) List of compute attributes
- `name` - (Required) Name of the compute profile


## Attributes Reference

The following attributes are exported:

- `compute_attributes` - List of compute attributes
- `name` - Name of the compute profile

24 changes: 24 additions & 0 deletions examples/compute_profile/complex_vmware.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
resource "foreman_computeprofile" "vmware_webserver" {
name = "VMware Webserver"

compute_attributes {
name = "Webserver middle (2 CPUs and 16GB memory)"
compute_resource_id = data.foreman_computeresource.vmware.id

vm_attrs = {
cpus = 2
corespersocket = 1
memory_mb = 16384
firmware = "bios"
resource_pool = "pool1"
guest_id = "ubuntu64Guest"

boot_order = jsonencode([ "disk", "network" ])

interfaces_attributes = jsonencode({
0: { type: "VirtualE1000", network: "dmz-net" },
1: { type: "VirtualE1000", network: "webserver-net" } }
)
}
}
}
15 changes: 15 additions & 0 deletions examples/compute_profile/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
data "foreman_computeresource" "vmware" {
name = "VMware Cluster ABC"
}

resource "foreman_computeprofile" "Small VM" {
name = "Small VM"

compute_attributes {
compute_resource_id = data.foreman_computeresource.vmware.id
vm_attrs = {
cpus = 2
memory_mb = 4096
}
}
}
227 changes: 226 additions & 1 deletion foreman/api/computeprofile.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package api

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

"github.com/HanseMerkur/terraform-provider-utils/log"
)
Expand All @@ -18,8 +20,78 @@ const (
// -----------------------------------------------------------------------------

type ForemanComputeProfile struct {
// Inherits the base object's attributes
ForemanObject
ComputeAttributes []*ForemanComputeAttribute `json:"compute_attributes,omitempty"`
}

type ForemanComputeAttribute struct {
ForemanObject
ComputeResourceId int `json:"compute_resource_id"`
VMAttrs map[string]interface{} `json:"vm_attrs,omitempty"`
}

// Implement custom Marshal function for ForemanComputeAttribute to convert
// the internal vm_attrs map from all-string to their matching types.
func (ca *ForemanComputeAttribute) MarshalJSON() ([]byte, error) {
fca := map[string]interface{}{
"id": ca.Id,
"name": ca.Name,
"compute_resource_id": ca.ComputeResourceId,
"vm_attrs": nil,
}

attrs := map[string]interface{}{}

// Since we allow all types of input in the VMAttrs JSON,
// all types must be handled for conversion

for k, v := range ca.VMAttrs {
// log.Debugf("v %s %T: %+v", k, v, v)

switch v := v.(type) {

case int:
attrs[k] = strconv.Itoa(v)

case float32:
attrs[k] = strconv.FormatFloat(float64(v), 'f', -1, 32)

case float64:
attrs[k] = strconv.FormatFloat(v, 'f', -1, 64)

case bool:
attrs[k] = strconv.FormatBool(v)

case nil:
attrs[k] = nil

case string:
var res interface{}
umErr := json.Unmarshal([]byte(v), &res)
if umErr != nil {
// Most likely a "true" string, that cannot be unmarshalled
// Example err: "invalid character 'x' looking for beginning of value"
attrs[k] = v
} else {
// Conversion from JSON string to internal type worked, use it
attrs[k] = res
}

case map[string]interface{}, []interface{}:
// JSON array or object passed in, simply convert it to a string
by, err := json.Marshal(v)
if err != nil {
return nil, err
}
attrs[k] = string(by)

default:
log.Errorf("v had a type that was not handled: %T", v)
}
}

fca["vm_attrs"] = attrs
return json.Marshal(fca)
}

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -51,6 +123,10 @@ func (c *Client) ReadComputeProfile(ctx context.Context, id int) (*ForemanComput

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

for i := 0; i < len(readComputeProfile.ComputeAttributes); i++ {
log.Debugf("compute_attribute: [%+v]", readComputeProfile.ComputeAttributes[i])
}

return &readComputeProfile, nil
}

Expand Down Expand Up @@ -113,3 +189,152 @@ func (c *Client) QueryComputeProfile(ctx context.Context, t *ForemanComputeProfi

return queryResponse, nil
}

func (c *Client) CreateComputeprofile(ctx context.Context, d *ForemanComputeProfile) (*ForemanComputeProfile, error) {
log.Tracef("foreman/api/computeprofile.go#Create")

reqEndpoint := ComputeProfileEndpointPrefix

// Copy the original obj and then remove ComputeAttributes
compProfileClean := new(ForemanComputeProfile)
compProfileClean.ForemanObject = d.ForemanObject
compProfileClean.ComputeAttributes = nil

cprofJSONBytes, jsonEncErr := c.WrapJSONWithTaxonomy("compute_profile", compProfileClean)
if jsonEncErr != nil {
return nil, jsonEncErr
}

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

req, reqErr := c.NewRequestWithContext(
ctx,
http.MethodPost,
reqEndpoint,
bytes.NewBuffer(cprofJSONBytes),
)
if reqErr != nil {
return nil, reqErr
}

var createdComputeprofile ForemanComputeProfile
sendErr := c.SendAndParse(req, &createdComputeprofile)
if sendErr != nil {
return nil, sendErr
}

// Add the compute attributes as well
for i := 0; i < len(d.ComputeAttributes); i++ {
compattrsEndpoint := fmt.Sprintf("%s/%d/compute_resources/%d/compute_attributes",
ComputeProfileEndpointPrefix,
createdComputeprofile.Id,
d.ComputeAttributes[i].ComputeResourceId)

log.Debugf("d.ComputeAttributes[i]: %+v", d.ComputeAttributes[i])

by, err := c.WrapJSONWithTaxonomy("compute_attribute", d.ComputeAttributes[i])
if err != nil {
return nil, err
}
log.Debugf("%s", by)
req, reqErr = c.NewRequestWithContext(
ctx, http.MethodPost, compattrsEndpoint, bytes.NewBuffer(by),
)
if reqErr != nil {
return nil, reqErr
}
var createdComputeAttribute ForemanComputeAttribute
sendErr = c.SendAndParse(req, &createdComputeAttribute)
if sendErr != nil {
return nil, sendErr
}
createdComputeprofile.ComputeAttributes = append(createdComputeprofile.ComputeAttributes, &createdComputeAttribute)
}

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

return &createdComputeprofile, nil
}

func (c *Client) UpdateComputeProfile(ctx context.Context, d *ForemanComputeProfile) (*ForemanComputeProfile, error) {
log.Tracef("foreman/api/computeprofile.go#Update")

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

jsonBytes, jsonEncErr := c.WrapJSONWithTaxonomy("compute_profile", d)
if jsonEncErr != nil {
return nil, jsonEncErr
}

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

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

var updatedComputeProfile ForemanComputeProfile
sendErr := c.SendAndParse(req, &updatedComputeProfile)
if sendErr != nil {
return nil, sendErr
}

// Handle updates for the compute attributes of this compute profile
updatedComputeAttributes := []*ForemanComputeAttribute{}
for i := 0; i < len(d.ComputeAttributes); i++ {
elem := d.ComputeAttributes[i]
updateEndpoint := fmt.Sprintf("%s/%d/compute_resources/%d/compute_attributes/%d",
ComputeProfileEndpointPrefix,
updatedComputeProfile.Id,
elem.ComputeResourceId,
elem.Id)

log.Debugf("d.ComputeAttributes[i]: %+v", elem)

by, err := c.WrapJSONWithTaxonomy("compute_attribute", elem)
if err != nil {
return nil, err
}
log.Debugf("by: %s", by)

req, reqErr = c.NewRequestWithContext(
ctx,
http.MethodPut,
updateEndpoint,
bytes.NewBuffer(by),
)
if reqErr != nil {
return nil, reqErr
}

var updatedComputeAttribute ForemanComputeAttribute
sendErr = c.SendAndParse(req, &updatedComputeAttribute)
if sendErr != nil {
return nil, sendErr
}
updatedComputeAttributes = append(updatedComputeAttributes, &updatedComputeAttribute)
}

updatedComputeProfile.ComputeAttributes = updatedComputeAttributes

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

return &updatedComputeProfile, nil
}

func (c *Client) DeleteComputeProfile(ctx context.Context, id int) error {
log.Tracef("foreman/api/computeprofile.go#Delete")

reqEndpoint := fmt.Sprintf("/%s/%d", ComputeProfileEndpointPrefix, id)
req, reqErr := c.NewRequestWithContext(ctx, http.MethodDelete, reqEndpoint, nil)
if reqErr != nil {
return reqErr
}

return c.SendAndParse(req, nil)
}
Loading

0 comments on commit 3ffdb13

Please sign in to comment.