Skip to content

Commit

Permalink
Add Import functionality for object resource (#11)
Browse files Browse the repository at this point in the history
Signed-off-by: Viorel Dodin <[email protected]>
  • Loading branch information
DodinViorel committed Apr 22, 2024
1 parent 993bb41 commit f4db8c0
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 14 deletions.
1 change: 1 addition & 0 deletions examples/resources/cop_example/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ resource "observability_object" "conn" {
object_id = "just-terraform-testing"
layer_type = "TENANT"
layer_id = "0eb4e853-34fb-4f77-b3fc-b9cd3b462366"
import_id = "anzen:cloudConnection|just-terraform-testing|TENANT|0eb4e853-34fb-4f77-b3fc-b9cd3b462366"
data = jsonencode(
{
"cloudType": "AWS",
Expand Down
66 changes: 52 additions & 14 deletions internal/provider/object_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"encoding/json"
"fmt"
"strings"

"github.com/cisco-open/terraform-provider-observability/internal/api"

Expand Down Expand Up @@ -38,6 +39,7 @@ type ObjectResourceModel struct {
LayerID types.String `tfsdk:"layer_id"`
LayerType types.String `tfsdk:"layer_type"`
Data types.String `tfsdk:"data"`
ImportID types.String `tfsdk:"import_id"`
ID types.String `tfsdk:"id"`
}

Expand Down Expand Up @@ -74,6 +76,10 @@ func (r *ObjectResource) Schema(_ context.Context, _ resource.SchemaRequest, res
IsValidJSONString{},
},
},
"import_id": schema.StringAttribute{
MarkdownDescription: "ID used when doing import operation on an object",
Optional: true,
},
"id": schema.StringAttribute{
MarkdownDescription: "Used to provide compatibility for testing framework",
Computed: true,
Expand Down Expand Up @@ -156,6 +162,25 @@ func (r *ObjectResource) Read(ctx context.Context, req resource.ReadRequest, res
layerID := data.LayerID.ValueString()
layerType := data.LayerType.ValueString()
currentDataPayload := data.Data.ValueString()
importIdentifier := data.ImportID.ValueString()

// in case of import previous properties will be empty, only importIdentidier will get populated
// because of this, importIdentifier will be a composition of typeName|objID|layerID|layerType
// reason for this is that the api needs all for fields to properly identify an object
identityFields := strings.Split(importIdentifier, "|")
if len(identityFields) == 4 {
tflog.Debug(ctx, "Import scenario detected")
tflog.Debug(ctx, "Extracting required fields from import_id")
typeName = identityFields[0]
objID = identityFields[1]
layerType = identityFields[2]
layerID = identityFields[3]
}

tflog.Debug(ctx, fmt.Sprintf("Object id is %s", objID))
tflog.Debug(ctx, fmt.Sprintf("Layer id is %s", layerID))
tflog.Debug(ctx, fmt.Sprintf("Layer type is %s", layerType))
tflog.Debug(ctx, fmt.Sprintf("Data payload is %s", currentDataPayload))

result, err := r.client.GetObject(typeName, objID, layerID, layerType)
if err != nil {
Expand All @@ -175,27 +200,35 @@ func (r *ObjectResource) Read(ctx context.Context, req resource.ReadRequest, res
err = json.Unmarshal(result, &parsedResponse)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Unable to Unmarshal object of type %s with id %s", typeName, objID),
fmt.Sprintf("Unable to Unmarshal response object of type %s with id %s", typeName, objID),
err.Error(),
)
return
}

err = json.Unmarshal([]byte(currentDataPayload), &parsedCurrentDataPayload)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Unable to Unmarshal object of type %s with id %s", typeName, objID),
err.Error(),
)
return
if currentDataPayload != "" {
err = json.Unmarshal([]byte(currentDataPayload), &parsedCurrentDataPayload)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Unable to Unmarshal current object of type %s with id %s", typeName, objID),
err.Error(),
)
return
}
}

// update only the fields which we provided
// this is to avoid false updates due to the observability API generating new fields as response
dataPayload := parsedResponse["data"].(map[string]any)
for k, v := range dataPayload {
if _, ok := parsedCurrentDataPayload[k]; ok {
parsedCurrentDataPayload[k] = v
if parsedCurrentDataPayload == nil {
// data was not provided in this case, maybe import usecase
// populate all the fields with what the observability api provided
parsedCurrentDataPayload = dataPayload
} else {
// update only the fields which we provided
// this is to avoid false updates due to the observability API generating new fields as response
for k, v := range dataPayload {
if _, ok := parsedCurrentDataPayload[k]; ok {
parsedCurrentDataPayload[k] = v
}
}
}

Expand All @@ -216,6 +249,11 @@ func (r *ObjectResource) Read(ctx context.Context, req resource.ReadRequest, res

// set the placeholder value for testing purposses
data.ID = types.StringValue("placeholder")
// set the rest of the fields
data.TypeName = types.StringValue(typeName)
data.ObjectID = types.StringValue(objID)
data.LayerType = types.StringValue(layerType)
data.LayerID = types.StringValue(layerID)

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
Expand Down Expand Up @@ -285,5 +323,5 @@ func (r *ObjectResource) Delete(ctx context.Context, req resource.DeleteRequest,
}

func (r *ObjectResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
resource.ImportStatePassthroughID(ctx, path.Root("import_id"), req, resp)
}

0 comments on commit f4db8c0

Please sign in to comment.