diff --git a/client/calm/calm_service.go b/client/calm/calm_service.go index 0ad222733..c3ce7f6bf 100644 --- a/client/calm/calm_service.go +++ b/client/calm/calm_service.go @@ -22,6 +22,7 @@ type Service interface { AppRunlogs(ctx context.Context, appUUID, runlogUUID string) (*AppRunlogsResponse, error) ListBlueprint(ctx context.Context, filter *BlueprintListInput) (*BlueprintListResponse, error) GetRuntimeEditables(ctx context.Context, bpUUID string) (*RuntimeEditablesResponse, error) + PatchApp(ctx context.Context, appUUID string, patchUUID string, input *PatchInput) (*AppPatchResponse, error) } func (op Operations) ProvisionBlueprint(ctx context.Context, bpUUID string, input *BlueprintProvisionInput) (*AppProvisionTaskOutput, error) { @@ -139,3 +140,17 @@ func (op Operations) GetRuntimeEditables(ctx context.Context, bpUUID string) (*R return appResponse, op.client.Do(ctx, req, appResponse) } + +func (op Operations) PatchApp(ctx context.Context, appUUID string, patchUUID string, input *PatchInput) (*AppPatchResponse, error) { + path := fmt.Sprintf("/apps/%s/patch/%s/run", appUUID, patchUUID) + + req, err := op.client.NewRequest(ctx, http.MethodPost, path, input) + + appResponse := new(AppPatchResponse) + + if err != nil { + return nil, err + } + + return appResponse, op.client.Do(ctx, req, appResponse) +} diff --git a/client/calm/calm_structs.go b/client/calm/calm_structs.go index 97a9887e8..4b4d980a4 100644 --- a/client/calm/calm_structs.go +++ b/client/calm/calm_structs.go @@ -139,3 +139,36 @@ type RuntimeSpec struct { Type *string `json:"type,omitempty"` UUID *string `json:"uuid,omitempty"` } +type PatchInput struct { + Spec PatchSpec `json:"spec"` + APIVersion string `json:"api_version"` + Metadata map[string]interface{} `json:"metadata"` +} + +type PatchSpec struct { + Args ArgsSpec `json:"args"` + TargetUUID string `json:"target_uuid"` + TargetKind string `json:"target_kind"` +} + +type ArgsSpec struct { + Variables []*VariableList `json:"variables"` + Patch map[string]interface{} `json:"patch"` +} + +type VariableList struct { + TaskUUID string `json:"task_uuid,omitempty"` + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} + +type AppPatchResponse struct { + Status ActionRunStatus `json:"status"` + Spec json.RawMessage `json:"spec"` + APIVersion string `json:"api_version"` + Metadata json.RawMessage `json:"metadata"` +} + +type ActionRunStatus struct { + RunlogUUID string `json:"runlog_uuid"` +} diff --git a/nutanix/provider.go b/nutanix/provider.go index 82d319810..653e23f04 100644 --- a/nutanix/provider.go +++ b/nutanix/provider.go @@ -267,6 +267,7 @@ func Provider() *schema.Provider { "nutanix_ndb_cluster": resourceNutanixNDBCluster(), "nutanix_karbon_worker_nodepool": resourceNutanixKarbonWorkerNodePool(), "nutanix_calm_app_provision": resourceNutanixCalmAppProvision(), + "nutanix_calm_app_patch": resourceNutanixCalmAppPatch(), }, ConfigureContextFunc: providerConfigure, } diff --git a/nutanix/resource_nutanix_calm_app_patch.go b/nutanix/resource_nutanix_calm_app_patch.go new file mode 100644 index 000000000..70e28af22 --- /dev/null +++ b/nutanix/resource_nutanix_calm_app_patch.go @@ -0,0 +1,346 @@ +package nutanix + +import ( + "context" + "encoding/json" + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-nutanix/client/calm" +) + +func resourceNutanixCalmAppPatch() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceNutanixCalmAppPatchCreate, + ReadContext: resourceNutanixCalmAppPatchRead, + UpdateContext: resourceNutanixCalmAppPatchUpdate, + DeleteContext: resourceNutanixCalmAppPatchDelete, + Schema: map[string]*schema.Schema{ + "app_uuid": { + Type: schema.TypeString, + Required: true, + }, + "patch_name": { + Type: schema.TypeString, + Required: true, + }, + "run_action": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "config_name": { + Type: schema.TypeString, + Required: true, + }, + "vm_config": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "memory_size_mib": { + Type: schema.TypeInt, + Optional: true, + }, + "num_sockets": { + Type: schema.TypeInt, + Optional: true, + }, + "num_vcpus_per_socket": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "nics": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "index": { + Type: schema.TypeInt, + Required: true, + }, + "operation": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "categories": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Optional: true, + }, + "operation": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourceNutanixCalmAppPatchCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*Client).Calm + + appUUID := d.Get("app_uuid").(string) + patchName := d.Get("patch_name").(string) + + // fetch app for spec + + appResp, err := conn.Service.GetApp(ctx, appUUID) + if err != nil { + return diag.FromErr(err) + } + + var objSpec map[string]interface{} + if err := json.Unmarshal(appResp.Spec, &objSpec); err != nil { + fmt.Println("Error unmarshalling Spec:", err) + } + + var objMetadata map[string]interface{} + if err := json.Unmarshal(appResp.Metadata, &objMetadata); err != nil { + fmt.Println("Error unmarshalling Spec:", err) + } + + var objStatus map[string]interface{} + if err := json.Unmarshal(appResp.Status, &objStatus); err != nil { + fmt.Println("Error unmarshalling Spec:", err) + } + + //fetch input + + fetchInput := &calm.PatchInput{} + fetchInput.APIVersion = appResp.APIVersion + fetchInput.Metadata = objMetadata + + var patchUUID string + // fetch patch for spec + fetchSpec := &calm.PatchSpec{} + fetchSpec.TargetUUID = appUUID + fetchSpec.TargetKind = "Application" + fetchSpec.Args.Variables = []*calm.VariableList{} + fetchSpec.Args.Patch, patchUUID = expandPatchSpec(objSpec, patchName) + // fetchSpec.Args.Variables = [] + + if runtimeEditables, ok := d.GetOk("run_action"); ok { + // get the path list from objSpec + + // runtimeVMConfigMap := runtime.([]interface{})[0].(map[string]interface{})["vm_config"].(map[string]interface{}) + // num_sockets := runtimeVMConfigMap["num_sockets"].(int) + + // print("NUM SOCKETS::::", num_sockets) + + // func to return attrs_list from patch_list + + attsDataMap := getAttrsListFromPatchList(objSpec, patchName) + log.Println("ATTRS LIST::::", attsDataMap) + + runtimeEditablesList := runtimeEditables.([]interface{}) + + for _, runtimeEditable := range runtimeEditablesList { + runtimeEditableMap := runtimeEditable.(map[string]interface{}) + + if vmconfig, ok := runtimeEditableMap["vm_config"].([]interface{}); ok { + for _, vmConfig := range vmconfig { + vmConfigMap := vmConfig.(map[string]interface{}) + // log.Println("VM CONFIG MAP::::", vmConfigMap) + if numSockets, ok := vmConfigMap["num_sockets"].(int); ok { + log.Println("NUM SOCKETS::::", numSockets) + fetchSpec.Args.Patch["attrs_list"].([]interface{})[0].(map[string]interface{})["data"].(map[string]interface{})["num_sockets_ruleset"].(map[string]interface{})["value"] = numSockets + } + if memorySizeMib, ok := vmConfigMap["memory_size_mib"].(int); ok { + log.Println("MEMORY SIZE::::", memorySizeMib) + fetchSpec.Args.Patch["attrs_list"].([]interface{})[0].(map[string]interface{})["data"].(map[string]interface{})["memory_size_mib_ruleset"].(map[string]interface{})["value"] = memorySizeMib + } + if numVcpusPerSocket, ok := vmConfigMap["num_vcpus_per_socket"].(int); ok { + log.Println("NUM VCPUS PER SOCKET::::", numVcpusPerSocket) + fetchSpec.Args.Patch["attrs_list"].([]interface{})[0].(map[string]interface{})["data"].(map[string]interface{})["num_vcpus_per_socket_ruleset"].(map[string]interface{})["value"] = numVcpusPerSocket + } + } + } + if categories, ok := runtimeEditableMap["categories"].([]interface{}); ok { + for _, category := range categories { + categoryMap := category.(map[string]interface{}) + log.Println("CATEGORY MAP::::", categoryMap) + categoryList := fetchSpec.Args.Patch["attrs_list"].([]interface{})[0].(map[string]interface{})["data"].(map[string]interface{})["pre_defined_categories"].([]interface{}) + if operation, ok := categoryMap["operation"].(string); ok { + if operation == "add" { + categoryList = append(categoryList, map[string]interface{}{ + "value": categoryMap["value"], + "operation": "add", + }) + } else { + categoryList = append(categoryList, map[string]interface{}{ + "value": categoryMap["value"], + "operation": "delete", + }) + } + } + fetchSpec.Args.Patch["attrs_list"].([]interface{})[0].(map[string]interface{})["data"].(map[string]interface{})["pre_defined_categories"] = categoryList + } + } + + // // fetch the current nic present in app + // getNicList := fetchSpec.Args.Patch["attrs_list"].([]interface{})[0].(map[string]interface{})["data"].(map[string]interface{})["pre_defined_nic_list"].([]interface{}) + // for _, getNicMap := range getNicList { + // // now get the nic in config spec + // fmt.Println("Length of NIC LIST::::", len(getNicList)) + // if nics, ok := runtimeEditableMap["nics"].([]interface{}); ok { + // for _, nic := range nics { + // nicMap := nic.(map[string]interface{}) + // idx := nicMap["index"].(int) + // ops := nicMap["operation"].(string) + // fmt.Println("IDX::::", idx) + // fmt.Println("OPS::::", ops) + + // getNicList = append(getNicList, map[string]interface{}{ + // "identifier": idx, + // "operation": ops, + // }) + // // if getNicMap.(map[string]interface{})["identifier"].(string) == string(idx) { + // // getNicMap.(map[string]interface{})["operation"] = ops + // // fmt.Println("INSIDE NIC MAP") + // // } + // } + // } + // } + + // if resource, ok := objStatus["resources"].(map[string]interface{}); ok { + // fmt.Println("INSIDE RESOURCE") + // // Access the list "app_profile" + // if deployList, ok := resource["deployment_list"].([]interface{}); ok { + // fmt.Println("INSIDE DEPLOYMENT") + // for _, deploy := range deployList { + // deployMap := deploy.(map[string]interface{}) + // log.Println("DEPLOYYYYY MAPPPPPPPP") + // if subs, ok := deployMap["substrate_configuration"].(map[string]interface{}); ok { + // fmt.Println("INSIDE SUBSTRATE") + // if element, ok := subs["element_list"].([]interface{}); ok { + // for _, elem := range element { + // fmt.Println("INSIDE ELEMENT") + // if nics, ok := elem.(map[string]interface{})["create_spec"].(map[string]interface{}); ok { + // fmt.Println("create_spec") + // if resources, ok := nics["resources"].(map[string]interface{}); ok { + // if nicList, ok := resources["nic_list"].([]interface{}); ok { + // fmt.Println("INSIDE NICS LIST") + // for _, nic := range nicList { + // nicMap := nic.(map[string]interface{}) + // identifier := nicMap["nic_type"].(string) + // fmt.Println("NIC TYPE::::", identifier) + // // if nics, ok := runtimeEditableMap["nics"].([]interface{}); ok { + // // for _, nic := range nics { + // // fmt.Println("NIC IDENTIFIER::::", nic.(map[string]interface{})) + // // } + // // } + // } + // } + // } + // } + // } + // } + // } + // } + // } + // } + } + + } + fetchInput.Spec = *fetchSpec + + // log.Println("HELLLLLOOOOOO2222") + // aJSON, _ := json.Marshal(fetchSpec) + // fmt.Printf("JSON Print - \n%s\n", string(aJSON)) + + // return nil + + fetchResp, err := conn.Service.PatchApp(ctx, appUUID, patchUUID, fetchInput) + if err != nil { + return diag.FromErr(err) + } + + runlogUUID := fetchResp.Status.RunlogUUID + + fmt.Println("Response:", runlogUUID) + + // poll till action is completed + appStateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING"}, + Target: []string{"POLICY_EXEC"}, + Refresh: RunlogStateRefreshFunc(ctx, conn, appUUID, runlogUUID), + Timeout: d.Timeout(schema.TimeoutUpdate), + Delay: 5 * time.Second, + } + + if _, errWaitTask := appStateConf.WaitForStateContext(ctx); errWaitTask != nil { + return diag.Errorf("error waiting for app to perform Patch(%s): %s", errWaitTask) + } + + d.SetId(resource.UniqueId()) + return nil +} + +func resourceNutanixCalmAppPatchRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return nil +} +func resourceNutanixCalmAppPatchUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return resourceNutanixCalmAppPatchCreate(ctx, d, meta) +} +func resourceNutanixCalmAppPatchDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return nil +} + +func expandPatchSpec(pr map[string]interface{}, patchName string) (map[string]interface{}, string) { + if resource, ok := pr["resources"].(map[string]interface{}); ok { + // fmt.Println("RESOURCESSSSS") + if patchList, ok := resource["patch_list"].([]interface{}); ok { + for _, patch := range patchList { + if dep, ok := patch.(map[string]interface{}); ok { + fmt.Println("DEP UUID::::", dep["uuid"]) + if dep["name"] == patchName { + fmt.Println("DEP UUID::::", dep["uuid"]) + return patch.(map[string]interface{}), dep["uuid"].(string) + } + } + } + } + } + return nil, "" +} + +func getAttrsListFromPatchList(pr map[string]interface{}, patchName string) map[string]interface{} { + if resource, ok := pr["resources"].(map[string]interface{}); ok { + if patchList, ok := resource["patch_list"].([]interface{}); ok { + for _, patch := range patchList { + if dep, ok := patch.(map[string]interface{}); ok { + if dep["name"] == patchName { + if attrs, ok := dep["attrs_list"].([]interface{}); ok { + for _, attr := range attrs { + if data, ok := attr.(map[string]interface{}); ok { + return data + } + } + } + } + } + } + } + } + return nil +}