Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_logic_app_workflow - add supports for workflow_parameters #12314

Merged
merged 5 commits into from
Jul 30, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 190 additions & 19 deletions azurerm/internal/services/logic/logic_app_workflow_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"log"
"regexp"
"strconv"
"time"

"github.com/Azure/azure-sdk-for-go/services/logic/mgmt/2019-05-01/logic"
Expand Down Expand Up @@ -91,6 +92,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,
Expand Down Expand Up @@ -146,10 +155,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{
Expand All @@ -160,6 +177,7 @@ func resourceLogicAppWorkflowCreate(d *pluginsdk.ResourceData, meta interface{})
"contentVersion": workflowVersion,
"actions": make(map[string]interface{}),
"triggers": make(map[string]interface{}),
"parameters": workflowParameters,
},
Parameters: parameters,
},
Expand Down Expand Up @@ -227,13 +245,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),
Expand Down Expand Up @@ -282,11 +311,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 {
Expand All @@ -308,6 +332,25 @@ func resourceLogicAppWorkflowRead(d *pluginsdk.ResourceData, meta interface{}) e
if v, ok := definition.(map[string]interface{}); ok {
d.Set("workflow_schema", v["$schema"].(string))
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(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)
}
}
}
}

Expand Down Expand Up @@ -359,35 +402,163 @@ 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:
value = v == "true"
case logic.ParameterTypeFloat:
f, err := strconv.ParseFloat(v, 64)
if err != nil {
return nil, fmt.Errorf("converting %s to float64: %v", v, err)
}
value = f
case logic.ParameterTypeInt:
i, err := strconv.Atoi(v)
if err != nil {
return nil, fmt.Errorf("converting %s to int: %v", v, err)
}
value = i

case logic.ParameterTypeArray,
logic.ParameterTypeObject,
logic.ParameterTypeSecureObject:
obj, err := pluginsdk.ExpandJsonFromString(v)
if err != nil {
return nil, fmt.Errorf("converting %s to json: %v", v, err)
}
value = obj

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(input map[string]*logic.WorkflowParameter, paramDefs map[string]interface{}) (map[string]interface{}, error) {
output := make(map[string]interface{})

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 {
log.Printf("[WARN] 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:
tv, ok := v.Value.(float64)
if !ok {
log.Printf("[WARN] 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 {
log.Printf("[WARN] The value of parameter %s is expected to be float64, but got %T", k, v.Value)
}
value = strconv.Itoa(int(tv))

case logic.ParameterTypeArray,
logic.ParameterTypeObject:
tv, ok := v.Value.(map[string]interface{})
if !ok {
log.Printf("[WARN] The value of parameter %s is expected to be map[string]interface{}, but got %T", k, v.Value)
}
obj, err := pluginsdk.FlattenJsonToString(tv)
if err != nil {
return nil, fmt.Errorf("converting %+v from json: %v", tv, err)
}
value = obj

case logic.ParameterTypeString:
tv, ok := v.Value.(string)
if !ok {
log.Printf("[DEBUG] Skipping parameter %q since it's not a string", k)
log.Printf("[WARN] The value of parameter %s is expected to be string, but got %T", k, v.Value)
}
value = tv

case logic.ParameterTypeSecureString,
logic.ParameterTypeSecureObject:
// These are not expected to return from API
continue
}

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{} {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})
}

func (LogicAppWorkflowResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := azure.ParseAzureResourceID(state.ID)
if err != nil {
Expand Down Expand Up @@ -283,3 +298,66 @@ 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"
})
secstr = jsonencode({
type = "SecureString"
})
secobj = jsonencode({
type = "SecureObject"
})
}

parameters = {
b = "true"
str = "value"
int = "123"
float = "1.23"
obj = jsonencode({
foo = "bar"
})
secstr = "value"
secobj = jsonencode({
foo = "bar"
})
}

lifecycle {
ignore_changes = [
parameters["secstr"],
parameters["secobj"],
]
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how come we are ignoring changes heres?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are secure types, means they will not return from API on read.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so in that case we should be pulling the existing values from the config through into the read function so the user does not have to do this

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I've updated the code to read the security params from state, and ignore them in the import verification accordingly. Also I refined the test for the "Array", with some modification on the code.

Test Result

💤 TF_ACC=1 go test -timeout=3h -v ./azurerm/internal/services/logic -run='TestAccLogicAppWorkflow_parameters'
=== RUN   TestAccLogicAppWorkflow_parameters                                                             
=== PAUSE TestAccLogicAppWorkflow_parameters                                                             
=== CONT  TestAccLogicAppWorkflow_parameters                                                             
--- PASS: TestAccLogicAppWorkflow_parameters (92.06s)                  
PASS                                                                                                     
ok      github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/logic       92.080s

}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger)
}
4 changes: 3 additions & 1 deletion website/docs/r/logic_app_workflow.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down