From f9687a619c74869680684e9e8119610107936e31 Mon Sep 17 00:00:00 2001 From: magodo Date: Fri, 30 Jul 2021 09:51:07 +0800 Subject: [PATCH] `azurerm_logic_app_workflow` - add supports for `workflow_parameters` (#12314) Add supports for the workflow_parameters (the paramter definition), and also make the parameters (the paramter value) to support different kinds of types (previously it only supports String). Fixes: #7241, #11505 --- .../logic/logic_app_workflow_resource.go | 237 ++++++++++++++++-- .../logic/logic_app_workflow_resource_test.go | 81 ++++++ .../docs/r/logic_app_workflow.html.markdown | 4 +- 3 files changed, 302 insertions(+), 20 deletions(-) diff --git a/azurerm/internal/services/logic/logic_app_workflow_resource.go b/azurerm/internal/services/logic/logic_app_workflow_resource.go index dc131b449e0a..af9de154f542 100644 --- a/azurerm/internal/services/logic/logic_app_workflow_resource.go +++ b/azurerm/internal/services/logic/logic_app_workflow_resource.go @@ -1,9 +1,11 @@ package logic import ( + "encoding/json" "fmt" "log" "regexp" + "strconv" "time" "github.com/Azure/azure-sdk-for-go/services/logic/mgmt/2019-05-01/logic" @@ -91,6 +93,14 @@ func resourceLogicAppWorkflow() *pluginsdk.Resource { Default: "1.0.0.0", }, + "workflow_parameters": { + Type: pluginsdk.TypeMap, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + "access_endpoint": { Type: pluginsdk.TypeString, Computed: true, @@ -146,10 +156,18 @@ func resourceLogicAppWorkflowCreate(d *pluginsdk.ResourceData, meta interface{}) } location := azure.NormalizeLocation(d.Get("location").(string)) - parameters := expandLogicAppWorkflowParameters(d.Get("parameters").(map[string]interface{})) workflowSchema := d.Get("workflow_schema").(string) workflowVersion := d.Get("workflow_version").(string) + workflowParameters, err := expandLogicAppWorkflowWorkflowParameters(d.Get("workflow_parameters").(map[string]interface{})) + if err != nil { + return fmt.Errorf("expanding `workflow_parameters`: %+v", err) + } + + parameters, err := expandLogicAppWorkflowParameters(d.Get("parameters").(map[string]interface{}), workflowParameters) + if err != nil { + return err + } t := d.Get("tags").(map[string]interface{}) properties := logic.Workflow{ @@ -160,6 +178,7 @@ func resourceLogicAppWorkflowCreate(d *pluginsdk.ResourceData, meta interface{}) "contentVersion": workflowVersion, "actions": make(map[string]interface{}), "triggers": make(map[string]interface{}), + "parameters": workflowParameters, }, Parameters: parameters, }, @@ -227,13 +246,24 @@ func resourceLogicAppWorkflowUpdate(d *pluginsdk.ResourceData, meta interface{}) } location := azure.NormalizeLocation(d.Get("location").(string)) - parameters := expandLogicAppWorkflowParameters(d.Get("parameters").(map[string]interface{})) + workflowParameters, err := expandLogicAppWorkflowWorkflowParameters(d.Get("workflow_parameters").(map[string]interface{})) + if err != nil { + return fmt.Errorf("expanding `workflow_parameters`: %+v", err) + } + parameters, err := expandLogicAppWorkflowParameters(d.Get("parameters").(map[string]interface{}), workflowParameters) + if err != nil { + return err + } + t := d.Get("tags").(map[string]interface{}) + definition := read.WorkflowProperties.Definition.(map[string]interface{}) + definition["parameters"] = workflowParameters + properties := logic.Workflow{ Location: utils.String(location), WorkflowProperties: &logic.WorkflowProperties{ - Definition: read.WorkflowProperties.Definition, + Definition: definition, Parameters: parameters, }, Tags: tags.Expand(t), @@ -282,11 +312,6 @@ func resourceLogicAppWorkflowRead(d *pluginsdk.ResourceData, meta interface{}) e } if props := resp.WorkflowProperties; props != nil { - parameters := flattenLogicAppWorkflowParameters(props.Parameters) - if err := d.Set("parameters", parameters); err != nil { - return fmt.Errorf("Error setting `parameters`: %+v", err) - } - d.Set("access_endpoint", props.AccessEndpoint) if props.EndpointsConfiguration == nil || props.EndpointsConfiguration.Connector == nil { @@ -312,6 +337,25 @@ func resourceLogicAppWorkflowRead(d *pluginsdk.ResourceData, meta interface{}) e if v["contentVersion"] != nil { d.Set("workflow_version", v["contentVersion"].(string)) } + if p, ok := v["parameters"]; ok { + workflowParameters, err := flattenLogicAppWorkflowWorkflowParameters(p.(map[string]interface{})) + if err != nil { + return fmt.Errorf("flattening `workflow_parameters`: %+v", err) + } + if err := d.Set("workflow_parameters", workflowParameters); err != nil { + return fmt.Errorf("setting `workflow_parameters`: %+v", err) + } + + // The props.Parameters (the value of the param) is accompany with the "parameters" (the definition of the param) inside the props.Definition. + // We will need to make use of the definition of the parameters in order to properly flatten the value of the parameters being set (for kinds of types). + parameters, err := flattenLogicAppWorkflowParameters(d, props.Parameters, p.(map[string]interface{})) + if err != nil { + return fmt.Errorf("flattening `parameters`: %v", err) + } + if err := d.Set("parameters", parameters); err != nil { + return fmt.Errorf("Error setting `parameters`: %+v", err) + } + } } } @@ -363,35 +407,190 @@ func resourceLogicAppWorkflowDelete(d *pluginsdk.ResourceData, meta interface{}) return nil } -func expandLogicAppWorkflowParameters(input map[string]interface{}) map[string]*logic.WorkflowParameter { +func expandLogicAppWorkflowParameters(input map[string]interface{}, paramDefs map[string]interface{}) (map[string]*logic.WorkflowParameter, error) { output := make(map[string]*logic.WorkflowParameter) for k, v := range input { + defRaw, ok := paramDefs[k] + if !ok { + return nil, fmt.Errorf("no parameter definition for %s", k) + } + def := defRaw.(map[string]interface{}) + t := logic.ParameterType(def["type"].(string)) + + v := v.(string) + + var value interface{} + switch t { + case logic.ParameterTypeBool: + var uv bool + if err := json.Unmarshal([]byte(v), &uv); err != nil { + return nil, fmt.Errorf("unmarshalling %s to bool: %v", k, err) + } + value = uv + case logic.ParameterTypeFloat: + var uv float64 + if err := json.Unmarshal([]byte(v), &uv); err != nil { + return nil, fmt.Errorf("unmarshalling %s to float64: %v", k, err) + } + value = uv + case logic.ParameterTypeInt: + var uv int + if err := json.Unmarshal([]byte(v), &uv); err != nil { + return nil, fmt.Errorf("unmarshalling %s to int: %v", k, err) + } + value = uv + case logic.ParameterTypeArray: + var uv []interface{} + if err := json.Unmarshal([]byte(v), &uv); err != nil { + return nil, fmt.Errorf("unmarshalling %s to []interface{}: %v", k, err) + } + value = uv + case logic.ParameterTypeObject, + logic.ParameterTypeSecureObject: + var uv map[string]interface{} + if err := json.Unmarshal([]byte(v), &uv); err != nil { + return nil, fmt.Errorf("unmarshalling %s to map[string]interface{}: %v", k, err) + } + value = uv + case logic.ParameterTypeString, + logic.ParameterTypeSecureString: + value = v + } + output[k] = &logic.WorkflowParameter{ - Type: logic.ParameterTypeString, - Value: v.(string), + Type: t, + Value: value, } } - return output + return output, nil } -func flattenLogicAppWorkflowParameters(input map[string]*logic.WorkflowParameter) map[string]interface{} { +func flattenLogicAppWorkflowParameters(d *pluginsdk.ResourceData, input map[string]*logic.WorkflowParameter, paramDefs map[string]interface{}) (map[string]interface{}, error) { output := make(map[string]interface{}) + // Read the "parameters" from state, which is used to fill in the "sensitive" properties. + paramInState := make(map[string]interface{}) + paramsRaw := d.Get("parameters") + if params, ok := paramsRaw.(map[string]interface{}); ok { + paramInState = params + } + for k, v := range input { - if v != nil { - // we only support string parameters at this time - val, ok := v.Value.(string) + defRaw, ok := paramDefs[k] + if !ok { + // This should never happen. + log.Printf("[WARN] The parameter %s is not defined in the Logic App Workflow", k) + continue + } + + if v == nil { + log.Printf("[WARN] The value of parameter %s is nil", k) + continue + } + + def := defRaw.(map[string]interface{}) + t := logic.ParameterType(def["type"].(string)) + + var value string + switch t { + case logic.ParameterTypeBool: + tv, ok := v.Value.(bool) + if !ok { + return nil, fmt.Errorf("the value of parameter %s is expected to be bool, but got %T", k, v.Value) + } + value = "true" + if !tv { + value = "false" + } + case logic.ParameterTypeFloat: + // Note that the json unmarshalled response doesn't differ between float and int, as json has only type number. + tv, ok := v.Value.(float64) + if !ok { + return nil, fmt.Errorf("the value of parameter %s is expected to be float64, but got %T", k, v.Value) + } + value = strconv.FormatFloat(tv, 'f', -1, 64) + case logic.ParameterTypeInt: + // Note that the json unmarshalled response doesn't differ between float and int, as json has only type number. + tv, ok := v.Value.(float64) + if !ok { + return nil, fmt.Errorf("the value of parameter %s is expected to be float64, but got %T", k, v.Value) + } + value = strconv.Itoa(int(tv)) + + case logic.ParameterTypeArray: + tv, ok := v.Value.([]interface{}) if !ok { - log.Printf("[DEBUG] Skipping parameter %q since it's not a string", k) + return nil, fmt.Errorf("the value of parameter %s is expected to be []interface{}, but got %T", k, v.Value) + } + obj, err := json.Marshal(tv) + if err != nil { + return nil, fmt.Errorf("converting %+v from json: %v", tv, err) + } + value = string(obj) + + case logic.ParameterTypeObject: + tv, ok := v.Value.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("the value of parameter %s is expected to be map[string]interface{}, but got %T", k, v.Value) + } + obj, err := json.Marshal(tv) + if err != nil { + return nil, fmt.Errorf("converting %+v from json: %v", tv, err) + } + value = string(obj) + + case logic.ParameterTypeString: + tv, ok := v.Value.(string) + if !ok { + return nil, fmt.Errorf("the value of parameter %s is expected to be string, but got %T", k, v.Value) + } + value = tv + + case logic.ParameterTypeSecureString, + logic.ParameterTypeSecureObject: + // This is not returned from API, we will try to read them from the state instead. + if v, ok := paramInState[k]; ok { + value = v.(string) // The value in state here is guaranteed to be a string, so directly cast the type. } + } + + output[k] = value + } + + return output, nil +} + +func expandLogicAppWorkflowWorkflowParameters(input map[string]interface{}) (map[string]interface{}, error) { + if len(input) == 0 { + return nil, nil + } - output[k] = val + output := make(map[string]interface{}) + for k, v := range input { + obj, err := pluginsdk.ExpandJsonFromString(v.(string)) + if err != nil { + return nil, err } + output[k] = obj } + return output, nil +} - return output +func flattenLogicAppWorkflowWorkflowParameters(input map[string]interface{}) (map[string]interface{}, error) { + if input == nil { + return nil, nil + } + output := make(map[string]interface{}) + for k, v := range input { + objstr, err := pluginsdk.FlattenJsonToString(v.(map[string]interface{})) + if err != nil { + return nil, err + } + output[k] = objstr + } + return output, nil } func flattenIPAddresses(input *[]logic.IPAddress) []interface{} { diff --git a/azurerm/internal/services/logic/logic_app_workflow_resource_test.go b/azurerm/internal/services/logic/logic_app_workflow_resource_test.go index 81a8270b2c07..02e71e73adaf 100644 --- a/azurerm/internal/services/logic/logic_app_workflow_resource_test.go +++ b/azurerm/internal/services/logic/logic_app_workflow_resource_test.go @@ -133,6 +133,21 @@ func TestAccLogicAppWorkflow_integrationServiceEnvironment(t *testing.T) { }) } +func TestAccLogicAppWorkflow_parameters(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_logic_app_workflow", "test") + r := LogicAppWorkflowResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.parameters(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("parameters.secstr", "parameters.secobj"), + }) +} + func (LogicAppWorkflowResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := azure.ParseAzureResourceID(state.ID) if err != nil { @@ -283,3 +298,69 @@ resource "azurerm_logic_app_workflow" "test" { } `, IntegrationServiceEnvironmentResource{}.basic(data), data.RandomInteger) } + +func (LogicAppWorkflowResource) parameters(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-logic-%d" + location = "%s" +} + +resource "azurerm_logic_app_workflow" "test" { + name = "acctestlaw-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + workflow_parameters = { + b = jsonencode({ + type = "Bool" + }) + str = jsonencode({ + type = "String" + }) + int = jsonencode({ + type = "Int" + }) + float = jsonencode({ + type = "Float" + }) + obj = jsonencode({ + type = "Object" + }) + array = jsonencode({ + type = "Array" + }) + secstr = jsonencode({ + type = "SecureString" + }) + secobj = jsonencode({ + type = "SecureObject" + }) + } + + parameters = { + b = "true" + str = "value" + int = "123" + float = "1.23" + obj = jsonencode({ + s = "foo" + array = [1, 2, 3] + obj = { + i = 123 + } + }) + array = jsonencode([ + 1, "string", {}, [] + ]) + secstr = "value" + secobj = jsonencode({ + foo = "foo" + }) + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} diff --git a/website/docs/r/logic_app_workflow.html.markdown b/website/docs/r/logic_app_workflow.html.markdown index ba26a1b35fd6..4ddc14f18ada 100644 --- a/website/docs/r/logic_app_workflow.html.markdown +++ b/website/docs/r/logic_app_workflow.html.markdown @@ -39,13 +39,15 @@ The following arguments are supported: * `logic_app_integration_account_id` - (Optional) The ID of the integration account linked by this Logic App Workflow. +* `workflow_parameters` - (Optional) Specifies a map of Key-Value pairs of the Parameter Definitions to use for this Logic App Workflow. The key is the parameter name, and the value is a json encoded string of the parameter definition (see: https://docs.microsoft.com/en-us/azure/logic-apps/logic-apps-workflow-definition-language#parameters). + * `workflow_schema` - (Optional) Specifies the Schema to use for this Logic App Workflow. Defaults to `https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#`. Changing this forces a new resource to be created. * `workflow_version` - (Optional) Specifies the version of the Schema used for this Logic App Workflow. Defaults to `1.0.0.0`. Changing this forces a new resource to be created. * `parameters` - (Optional) A map of Key-Value pairs. --> **NOTE:** Any parameters specified must exist in the Schema defined in `workflow_schema`. +-> **NOTE:** Any parameters specified must exist in the Schema defined in `workflow_parameters`. * `tags` - (Optional) A mapping of tags to assign to the resource.