We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Change firewall bundle to read rules from resource properties
In order for the firewall to be updated by the API / other resources as part of pipelines, it must read the 'configurable' properties (parts of the firewall we want to allow a user/process to update) from the resource properties in cosmos. 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `resource_processor/resources/commands.py`
Content:
```
1 import asyncio
2 import json
3 import logging
4 from resources.helpers import get_installation_id
5 from shared.logging import shell_output_logger
6 
7 
8 def azure_login_command(config):
9     # Use a Service Principal when running locally
10     local_login = f"az login --service-principal --username {config['arm_client_id']} --password {config['arm_client_secret']} --tenant {config['arm_tenant_id']}"
11 
12     # Use the Managed Identity when in VMSS context
13     vmss_login = f"az login --identity -u {config['vmss_msi_id']}"
14 
15     command = vmss_login if config["vmss_msi_id"] else local_login
16     return command
17 
18 
19 def azure_acr_login_command(config):
20     return f"az acr login --name {config['registry_server'].replace('.azurecr.io','')}"
21 
22 
23 async def build_porter_command(config, logger, msg_body, custom_action=False):
24     porter_parameter_keys = await get_porter_parameter_keys(config, logger, msg_body)
25     porter_parameters = ""
26 
27     if porter_parameter_keys is None:
28         logger.warning("Unknown porter parameters - explain probably failed.")
29     else:
30         for parameter_name in porter_parameter_keys:
31             # try to find the param in order of priorities:
32             parameter_value = None
33 
34             # 1. msg parameters collection
35             if parameter_name in msg_body["parameters"]:
36                 parameter_value = msg_body["parameters"][parameter_name]
37 
38             # 2. config (e.g. terraform state env vars)
39             elif parameter_name in config:
40                 parameter_value = config[parameter_name]
41 
42             # 3. msg body root (e.g. id of the resource)
43             elif parameter_name in msg_body:
44                 parameter_value = msg_body[parameter_name]
45 
46             # if still not found, might be a special case
47             # (we give a chance to the method above to allow override of the special handeling done below)
48             else:
49                 parameter_value = get_special_porter_param_value(config, parameter_name, msg_body)
50 
51             # only append if we have a value, porter will complain anyway about missing parameters
52             if parameter_value is not None:
53                 porter_parameters = porter_parameters + f" --param {parameter_name}=\"{parameter_value}\""
54 
55     installation_id = get_installation_id(msg_body)
56 
57     command_line = [f"{azure_login_command(config)} && {azure_acr_login_command(config)} && porter "
58                     # If a custom action (i.e. not install, uninstall, upgrade) we need to use 'invoke'
59                     f"{'invoke --action ' if custom_action else ''}"
60                     f"{msg_body['action']} \"{installation_id}\" "
61                     f" --reference {config['registry_server']}/{msg_body['name']}:v{msg_body['version']}"
62                     f" {porter_parameters} --allow-docker-host-access --force"
63                     f" --cred ./vmss_porter/arm_auth_local_debugging.json"
64                     f" --cred ./vmss_porter/aad_auth.json"
65                     f" && porter show {installation_id}"]
66     return command_line
67 
68 
69 async def build_porter_command_for_outputs(msg_body):
70     installation_id = get_installation_id(msg_body)
71     # we only need "real" outputs and use jq to remove the logs which are big
72     command_line = [f"porter show {installation_id} --output json | jq -c '. | select(.Outputs!=null) | .Outputs | del (.[] | select(.Name==\"io.cnab.outputs.invocationImageLogs\"))'"]
73     return command_line
74 
75 
76 async def get_porter_parameter_keys(config, logger, msg_body):
77     command = [f"{azure_login_command(config)} >/dev/null && \
78         {azure_acr_login_command(config)} >/dev/null && \
79         porter explain --reference {config['registry_server']}/{msg_body['name']}:v{msg_body['version']} -ojson"]
80 
81     proc = await asyncio.create_subprocess_shell(
82         ''.join(command),
83         stdout=asyncio.subprocess.PIPE,
84         stderr=asyncio.subprocess.PIPE,
85         env=config["porter_env"])
86 
87     stdout, stderr = await proc.communicate()
88     logging.info(f'get_porter_parameter_keys exited with {proc.returncode}')
89     result_stdout = None
90     result_stderr = None
91 
92     if stdout:
93         result_stdout = stdout.decode()
94         porter_explain_parameters = json.loads(result_stdout)["parameters"]
95         porter_parameter_keys = [item["name"] for item in porter_explain_parameters]
96         return porter_parameter_keys
97     if stderr:
98         result_stderr = stderr.decode()
99         shell_output_logger(result_stderr, '[stderr]', logger, logging.WARN)
100 
101 
102 def get_special_porter_param_value(config, parameter_name: str, msg_body):
103     # some parameters might not have identical names and this comes to handle that
104     if parameter_name == "mgmt_acr_name":
105         return config["registry_server"].replace('.azurecr.io', '')
106     if parameter_name == "mgmt_resource_group_name":
107         return config["tfstate_resource_group_name"]
108     if parameter_name == "workspace_id":
109         return msg_body.get("workspaceId")  # not included in all messages
110     if parameter_name == "parent_service_id":
111         return msg_body.get("parentWorkspaceServiceId")  # not included in all messages
112
```

Path: `api_app/models/domain/resource_template.py`
Content:
```
1 from typing import Dict, Any, List, Optional
2 
3 from pydantic import Field
4 
5 from models.domain.azuretremodel import AzureTREModel
6 from models.domain.resource import ResourceType
7 
8 
9 class Property(AzureTREModel):
10     type: str = Field(title="Property type")
11     title: str = Field("", title="Property description")
12     description: str = Field("", title="Property description")
13     default: Any = Field(None, title="Default value for the property")
14     enum: Optional[List[str]] = Field(None, title="Enum values")
15     const: Optional[Any] = Field(None, title="Constant value")
16     multipleOf: Optional[float] = Field(None, title="Multiple of")
17     maximum: Optional[float] = Field(None, title="Maximum value")
18     exclusiveMaximum: Optional[float] = Field(None, title="Exclusive maximum value")
19     minimum: Optional[float] = Field(None, title="Minimum value")
20     exclusiveMinimum: Optional[float] = Field(None, title="Exclusive minimum value")
21     maxLength: Optional[int] = Field(None, title="Maximum length")
22     minLength: Optional[int] = Field(None, title="Minimum length")
23     pattern: Optional[str] = Field(None, title="Pattern")
24     updateable: Optional[bool] = Field(None, title="Indicates that the field can be updated")
25     readOnly: Optional[bool] = Field(None, title="Indicates the field is read-only")
26 
27 
28 class CustomAction(AzureTREModel):
29     name: str = Field(None, title="Custom action name")
30     description: str = Field("", title="Action description")
31 
32 
33 class PipelineStepProperty(AzureTREModel):
34     name: str = Field(title="name", description="name of the property to update")
35     type: str = Field(title="type", description="data type of the property to update")
36     value: str = Field(title="value", description="value to use in substitution for the property to update")
37 
38 
39 class PipelineStep(AzureTREModel):
40     stepId: Optional[str] = Field(title="stepId", description="Unique id identifying the step")
41     stepTitle: Optional[str] = Field(title="stepTitle", description="Human readable title of what the step is for")
42     resourceTemplateName: Optional[str] = Field(title="resourceTemplateName", description="Name of the template for the resource under change")
43     resourceType: Optional[ResourceType] = Field(title="resourceType", description="Type of resource under change")
44     resourceAction: Optional[str] = Field(title="resourceAction", description="Action - install / upgrade / uninstall etc")
45     properties: Optional[List[PipelineStepProperty]]
46 
47 
48 class Pipeline(AzureTREModel):
49     install: Optional[List[PipelineStep]]
50     upgrade: Optional[List[PipelineStep]]
51     uninstall: Optional[List[PipelineStep]]
52 
53 
54 class ResourceTemplate(AzureTREModel):
55     id: str
56     name: str = Field(title="Unique template name")
57     title: str = Field("", title="Template title or friendly name")
58     description: str = Field(title="Template description")
59     version: str = Field(title="Template version")
60     resourceType: ResourceType = Field(title="Type of resource this template is for (workspace/service)")
61     current: bool = Field(title="Is this the current version of this template")
62     type: str = "object"
63     required: List[str] = Field(title="List of properties which must be provided")
64     properties: Dict[str, Property] = Field(title="Template properties")
65     actions: List[CustomAction] = Field(default=[], title="Template actions")
66     customActions: List[CustomAction] = Field(default=[], title="Template custom actions")
67     pipeline: Optional[Pipeline] = Field(default=None, title="Template pipeline to define updates to other resources")
68 
69     # setting this to false means if extra, unexpected fields are supplied, the request is invalidated
70     additionalProperties: bool = Field(default=False, title="Prevent unspecified properties being applied")
71
```

Path: `api_app/_version.py`
Content:
```
1 __version__ = "0.2.23"
2
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/api_app/_version.py b/api_app/_version.py
--- a/api_app/_version.py
+++ b/api_app/_version.py
@@ -1 +1 @@
-__version__ = "0.2.23"
+__version__ = "0.2.24"
diff --git a/api_app/models/domain/resource_template.py b/api_app/models/domain/resource_template.py
--- a/api_app/models/domain/resource_template.py
+++ b/api_app/models/domain/resource_template.py
@@ -23,6 +23,7 @@
     pattern: Optional[str] = Field(None, title="Pattern")
     updateable: Optional[bool] = Field(None, title="Indicates that the field can be updated")
     readOnly: Optional[bool] = Field(None, title="Indicates the field is read-only")
+    items: Optional[dict] = None  # items can contain sub-properties
 
 
 class CustomAction(AzureTREModel):
diff --git a/resource_processor/resources/commands.py b/resource_processor/resources/commands.py
--- a/resource_processor/resources/commands.py
+++ b/resource_processor/resources/commands.py
@@ -1,6 +1,8 @@
 import asyncio
 import json
 import logging
+import base64
+
 from resources.helpers import get_installation_id
 from shared.logging import shell_output_logger
 
@@ -50,6 +52,13 @@
 
             # only append if we have a value, porter will complain anyway about missing parameters
             if parameter_value is not None:
+                if isinstance(parameter_value, dict) or isinstance(parameter_value, list):
+                    # base64 encode complex types to pass in safely
+                    val = json.dumps(parameter_value)
+                    val_bytes = val.encode("ascii")
+                    val_base64_bytes = base64.b64encode(val_bytes)
+                    parameter_value = val_base64_bytes.decode("ascii")
+
                 porter_parameters = porter_parameters + f" --param {parameter_name}=\"{parameter_value}\""
 
     installation_id = get_installation_id(msg_body)

{"golden_diff": "diff --git a/api_app/_version.py b/api_app/_version.py\n--- a/api_app/_version.py\n+++ b/api_app/_version.py\n@@ -1 +1 @@\n-__version__ = \"0.2.23\"\n+__version__ = \"0.2.24\"\ndiff --git a/api_app/models/domain/resource_template.py b/api_app/models/domain/resource_template.py\n--- a/api_app/models/domain/resource_template.py\n+++ b/api_app/models/domain/resource_template.py\n@@ -23,6 +23,7 @@\n     pattern: Optional[str] = Field(None, title=\"Pattern\")\n     updateable: Optional[bool] = Field(None, title=\"Indicates that the field can be updated\")\n     readOnly: Optional[bool] = Field(None, title=\"Indicates the field is read-only\")\n+    items: Optional[dict] = None  # items can contain sub-properties\n \n \n class CustomAction(AzureTREModel):\ndiff --git a/resource_processor/resources/commands.py b/resource_processor/resources/commands.py\n--- a/resource_processor/resources/commands.py\n+++ b/resource_processor/resources/commands.py\n@@ -1,6 +1,8 @@\n import asyncio\n import json\n import logging\n+import base64\n+\n from resources.helpers import get_installation_id\n from shared.logging import shell_output_logger\n \n@@ -50,6 +52,13 @@\n \n             # only append if we have a value, porter will complain anyway about missing parameters\n             if parameter_value is not None:\n+                if isinstance(parameter_value, dict) or isinstance(parameter_value, list):\n+                    # base64 encode complex types to pass in safely\n+                    val = json.dumps(parameter_value)\n+                    val_bytes = val.encode(\"ascii\")\n+                    val_base64_bytes = base64.b64encode(val_bytes)\n+                    parameter_value = val_base64_bytes.decode(\"ascii\")\n+\n                 porter_parameters = porter_parameters + f\" --param {parameter_name}=\\\"{parameter_value}\\\"\"\n \n     installation_id = get_installation_id(msg_body)\n", "issue": "Change firewall bundle to read rules from resource properties\nIn order for the firewall to be updated by the API / other resources as part of pipelines, it must read the 'configurable' properties (parts of the firewall we want to allow a user/process to update) from the resource properties in cosmos. \n", "before_files": [{"content": "import asyncio\nimport json\nimport logging\nfrom resources.helpers import get_installation_id\nfrom shared.logging import shell_output_logger\n\n\ndef azure_login_command(config):\n    # Use a Service Principal when running locally\n    local_login = f\"az login --service-principal --username {config['arm_client_id']} --password {config['arm_client_secret']} --tenant {config['arm_tenant_id']}\"\n\n    # Use the Managed Identity when in VMSS context\n    vmss_login = f\"az login --identity -u {config['vmss_msi_id']}\"\n\n    command = vmss_login if config[\"vmss_msi_id\"] else local_login\n    return command\n\n\ndef azure_acr_login_command(config):\n    return f\"az acr login --name {config['registry_server'].replace('.azurecr.io','')}\"\n\n\nasync def build_porter_command(config, logger, msg_body, custom_action=False):\n    porter_parameter_keys = await get_porter_parameter_keys(config, logger, msg_body)\n    porter_parameters = \"\"\n\n    if porter_parameter_keys is None:\n        logger.warning(\"Unknown porter parameters - explain probably failed.\")\n    else:\n        for parameter_name in porter_parameter_keys:\n            # try to find the param in order of priorities:\n            parameter_value = None\n\n            # 1. msg parameters collection\n            if parameter_name in msg_body[\"parameters\"]:\n                parameter_value = msg_body[\"parameters\"][parameter_name]\n\n            # 2. config (e.g. terraform state env vars)\n            elif parameter_name in config:\n                parameter_value = config[parameter_name]\n\n            # 3. msg body root (e.g. id of the resource)\n            elif parameter_name in msg_body:\n                parameter_value = msg_body[parameter_name]\n\n            # if still not found, might be a special case\n            # (we give a chance to the method above to allow override of the special handeling done below)\n            else:\n                parameter_value = get_special_porter_param_value(config, parameter_name, msg_body)\n\n            # only append if we have a value, porter will complain anyway about missing parameters\n            if parameter_value is not None:\n                porter_parameters = porter_parameters + f\" --param {parameter_name}=\\\"{parameter_value}\\\"\"\n\n    installation_id = get_installation_id(msg_body)\n\n    command_line = [f\"{azure_login_command(config)} && {azure_acr_login_command(config)} && porter \"\n                    # If a custom action (i.e. not install, uninstall, upgrade) we need to use 'invoke'\n                    f\"{'invoke --action ' if custom_action else ''}\"\n                    f\"{msg_body['action']} \\\"{installation_id}\\\" \"\n                    f\" --reference {config['registry_server']}/{msg_body['name']}:v{msg_body['version']}\"\n                    f\" {porter_parameters} --allow-docker-host-access --force\"\n                    f\" --cred ./vmss_porter/arm_auth_local_debugging.json\"\n                    f\" --cred ./vmss_porter/aad_auth.json\"\n                    f\" && porter show {installation_id}\"]\n    return command_line\n\n\nasync def build_porter_command_for_outputs(msg_body):\n    installation_id = get_installation_id(msg_body)\n    # we only need \"real\" outputs and use jq to remove the logs which are big\n    command_line = [f\"porter show {installation_id} --output json | jq -c '. | select(.Outputs!=null) | .Outputs | del (.[] | select(.Name==\\\"io.cnab.outputs.invocationImageLogs\\\"))'\"]\n    return command_line\n\n\nasync def get_porter_parameter_keys(config, logger, msg_body):\n    command = [f\"{azure_login_command(config)} >/dev/null && \\\n        {azure_acr_login_command(config)} >/dev/null && \\\n        porter explain --reference {config['registry_server']}/{msg_body['name']}:v{msg_body['version']} -ojson\"]\n\n    proc = await asyncio.create_subprocess_shell(\n        ''.join(command),\n        stdout=asyncio.subprocess.PIPE,\n        stderr=asyncio.subprocess.PIPE,\n        env=config[\"porter_env\"])\n\n    stdout, stderr = await proc.communicate()\n    logging.info(f'get_porter_parameter_keys exited with {proc.returncode}')\n    result_stdout = None\n    result_stderr = None\n\n    if stdout:\n        result_stdout = stdout.decode()\n        porter_explain_parameters = json.loads(result_stdout)[\"parameters\"]\n        porter_parameter_keys = [item[\"name\"] for item in porter_explain_parameters]\n        return porter_parameter_keys\n    if stderr:\n        result_stderr = stderr.decode()\n        shell_output_logger(result_stderr, '[stderr]', logger, logging.WARN)\n\n\ndef get_special_porter_param_value(config, parameter_name: str, msg_body):\n    # some parameters might not have identical names and this comes to handle that\n    if parameter_name == \"mgmt_acr_name\":\n        return config[\"registry_server\"].replace('.azurecr.io', '')\n    if parameter_name == \"mgmt_resource_group_name\":\n        return config[\"tfstate_resource_group_name\"]\n    if parameter_name == \"workspace_id\":\n        return msg_body.get(\"workspaceId\")  # not included in all messages\n    if parameter_name == \"parent_service_id\":\n        return msg_body.get(\"parentWorkspaceServiceId\")  # not included in all messages\n", "path": "resource_processor/resources/commands.py"}, {"content": "from typing import Dict, Any, List, Optional\n\nfrom pydantic import Field\n\nfrom models.domain.azuretremodel import AzureTREModel\nfrom models.domain.resource import ResourceType\n\n\nclass Property(AzureTREModel):\n    type: str = Field(title=\"Property type\")\n    title: str = Field(\"\", title=\"Property description\")\n    description: str = Field(\"\", title=\"Property description\")\n    default: Any = Field(None, title=\"Default value for the property\")\n    enum: Optional[List[str]] = Field(None, title=\"Enum values\")\n    const: Optional[Any] = Field(None, title=\"Constant value\")\n    multipleOf: Optional[float] = Field(None, title=\"Multiple of\")\n    maximum: Optional[float] = Field(None, title=\"Maximum value\")\n    exclusiveMaximum: Optional[float] = Field(None, title=\"Exclusive maximum value\")\n    minimum: Optional[float] = Field(None, title=\"Minimum value\")\n    exclusiveMinimum: Optional[float] = Field(None, title=\"Exclusive minimum value\")\n    maxLength: Optional[int] = Field(None, title=\"Maximum length\")\n    minLength: Optional[int] = Field(None, title=\"Minimum length\")\n    pattern: Optional[str] = Field(None, title=\"Pattern\")\n    updateable: Optional[bool] = Field(None, title=\"Indicates that the field can be updated\")\n    readOnly: Optional[bool] = Field(None, title=\"Indicates the field is read-only\")\n\n\nclass CustomAction(AzureTREModel):\n    name: str = Field(None, title=\"Custom action name\")\n    description: str = Field(\"\", title=\"Action description\")\n\n\nclass PipelineStepProperty(AzureTREModel):\n    name: str = Field(title=\"name\", description=\"name of the property to update\")\n    type: str = Field(title=\"type\", description=\"data type of the property to update\")\n    value: str = Field(title=\"value\", description=\"value to use in substitution for the property to update\")\n\n\nclass PipelineStep(AzureTREModel):\n    stepId: Optional[str] = Field(title=\"stepId\", description=\"Unique id identifying the step\")\n    stepTitle: Optional[str] = Field(title=\"stepTitle\", description=\"Human readable title of what the step is for\")\n    resourceTemplateName: Optional[str] = Field(title=\"resourceTemplateName\", description=\"Name of the template for the resource under change\")\n    resourceType: Optional[ResourceType] = Field(title=\"resourceType\", description=\"Type of resource under change\")\n    resourceAction: Optional[str] = Field(title=\"resourceAction\", description=\"Action - install / upgrade / uninstall etc\")\n    properties: Optional[List[PipelineStepProperty]]\n\n\nclass Pipeline(AzureTREModel):\n    install: Optional[List[PipelineStep]]\n    upgrade: Optional[List[PipelineStep]]\n    uninstall: Optional[List[PipelineStep]]\n\n\nclass ResourceTemplate(AzureTREModel):\n    id: str\n    name: str = Field(title=\"Unique template name\")\n    title: str = Field(\"\", title=\"Template title or friendly name\")\n    description: str = Field(title=\"Template description\")\n    version: str = Field(title=\"Template version\")\n    resourceType: ResourceType = Field(title=\"Type of resource this template is for (workspace/service)\")\n    current: bool = Field(title=\"Is this the current version of this template\")\n    type: str = \"object\"\n    required: List[str] = Field(title=\"List of properties which must be provided\")\n    properties: Dict[str, Property] = Field(title=\"Template properties\")\n    actions: List[CustomAction] = Field(default=[], title=\"Template actions\")\n    customActions: List[CustomAction] = Field(default=[], title=\"Template custom actions\")\n    pipeline: Optional[Pipeline] = Field(default=None, title=\"Template pipeline to define updates to other resources\")\n\n    # setting this to false means if extra, unexpected fields are supplied, the request is invalidated\n    additionalProperties: bool = Field(default=False, title=\"Prevent unspecified properties being applied\")\n", "path": "api_app/models/domain/resource_template.py"}, {"content": "__version__ = \"0.2.23\"\n", "path": "api_app/_version.py"}], "after_files": [{"content": "import asyncio\nimport json\nimport logging\nimport base64\n\nfrom resources.helpers import get_installation_id\nfrom shared.logging import shell_output_logger\n\n\ndef azure_login_command(config):\n    # Use a Service Principal when running locally\n    local_login = f\"az login --service-principal --username {config['arm_client_id']} --password {config['arm_client_secret']} --tenant {config['arm_tenant_id']}\"\n\n    # Use the Managed Identity when in VMSS context\n    vmss_login = f\"az login --identity -u {config['vmss_msi_id']}\"\n\n    command = vmss_login if config[\"vmss_msi_id\"] else local_login\n    return command\n\n\ndef azure_acr_login_command(config):\n    return f\"az acr login --name {config['registry_server'].replace('.azurecr.io','')}\"\n\n\nasync def build_porter_command(config, logger, msg_body, custom_action=False):\n    porter_parameter_keys = await get_porter_parameter_keys(config, logger, msg_body)\n    porter_parameters = \"\"\n\n    if porter_parameter_keys is None:\n        logger.warning(\"Unknown porter parameters - explain probably failed.\")\n    else:\n        for parameter_name in porter_parameter_keys:\n            # try to find the param in order of priorities:\n            parameter_value = None\n\n            # 1. msg parameters collection\n            if parameter_name in msg_body[\"parameters\"]:\n                parameter_value = msg_body[\"parameters\"][parameter_name]\n\n            # 2. config (e.g. terraform state env vars)\n            elif parameter_name in config:\n                parameter_value = config[parameter_name]\n\n            # 3. msg body root (e.g. id of the resource)\n            elif parameter_name in msg_body:\n                parameter_value = msg_body[parameter_name]\n\n            # if still not found, might be a special case\n            # (we give a chance to the method above to allow override of the special handeling done below)\n            else:\n                parameter_value = get_special_porter_param_value(config, parameter_name, msg_body)\n\n            # only append if we have a value, porter will complain anyway about missing parameters\n            if parameter_value is not None:\n                if isinstance(parameter_value, dict) or isinstance(parameter_value, list):\n                    # base64 encode complex types to pass in safely\n                    val = json.dumps(parameter_value)\n                    val_bytes = val.encode(\"ascii\")\n                    val_base64_bytes = base64.b64encode(val_bytes)\n                    parameter_value = val_base64_bytes.decode(\"ascii\")\n\n                porter_parameters = porter_parameters + f\" --param {parameter_name}=\\\"{parameter_value}\\\"\"\n\n    installation_id = get_installation_id(msg_body)\n\n    command_line = [f\"{azure_login_command(config)} && {azure_acr_login_command(config)} && porter \"\n                    # If a custom action (i.e. not install, uninstall, upgrade) we need to use 'invoke'\n                    f\"{'invoke --action ' if custom_action else ''}\"\n                    f\"{msg_body['action']} \\\"{installation_id}\\\" \"\n                    f\" --reference {config['registry_server']}/{msg_body['name']}:v{msg_body['version']}\"\n                    f\" {porter_parameters} --allow-docker-host-access --force\"\n                    f\" --cred ./vmss_porter/arm_auth_local_debugging.json\"\n                    f\" --cred ./vmss_porter/aad_auth.json\"\n                    f\" && porter show {installation_id}\"]\n    return command_line\n\n\nasync def build_porter_command_for_outputs(msg_body):\n    installation_id = get_installation_id(msg_body)\n    # we only need \"real\" outputs and use jq to remove the logs which are big\n    command_line = [f\"porter show {installation_id} --output json | jq -c '. | select(.Outputs!=null) | .Outputs | del (.[] | select(.Name==\\\"io.cnab.outputs.invocationImageLogs\\\"))'\"]\n    return command_line\n\n\nasync def get_porter_parameter_keys(config, logger, msg_body):\n    command = [f\"{azure_login_command(config)} >/dev/null && \\\n        {azure_acr_login_command(config)} >/dev/null && \\\n        porter explain --reference {config['registry_server']}/{msg_body['name']}:v{msg_body['version']} -ojson\"]\n\n    proc = await asyncio.create_subprocess_shell(\n        ''.join(command),\n        stdout=asyncio.subprocess.PIPE,\n        stderr=asyncio.subprocess.PIPE,\n        env=config[\"porter_env\"])\n\n    stdout, stderr = await proc.communicate()\n    logging.info(f'get_porter_parameter_keys exited with {proc.returncode}')\n    result_stdout = None\n    result_stderr = None\n\n    if stdout:\n        result_stdout = stdout.decode()\n        porter_explain_parameters = json.loads(result_stdout)[\"parameters\"]\n        porter_parameter_keys = [item[\"name\"] for item in porter_explain_parameters]\n        return porter_parameter_keys\n    if stderr:\n        result_stderr = stderr.decode()\n        shell_output_logger(result_stderr, '[stderr]', logger, logging.WARN)\n\n\ndef get_special_porter_param_value(config, parameter_name: str, msg_body):\n    # some parameters might not have identical names and this comes to handle that\n    if parameter_name == \"mgmt_acr_name\":\n        return config[\"registry_server\"].replace('.azurecr.io', '')\n    if parameter_name == \"mgmt_resource_group_name\":\n        return config[\"tfstate_resource_group_name\"]\n    if parameter_name == \"workspace_id\":\n        return msg_body.get(\"workspaceId\")  # not included in all messages\n    if parameter_name == \"parent_service_id\":\n        return msg_body.get(\"parentWorkspaceServiceId\")  # not included in all messages\n", "path": "resource_processor/resources/commands.py"}, {"content": "from typing import Dict, Any, List, Optional\n\nfrom pydantic import Field\n\nfrom models.domain.azuretremodel import AzureTREModel\nfrom models.domain.resource import ResourceType\n\n\nclass Property(AzureTREModel):\n    type: str = Field(title=\"Property type\")\n    title: str = Field(\"\", title=\"Property description\")\n    description: str = Field(\"\", title=\"Property description\")\n    default: Any = Field(None, title=\"Default value for the property\")\n    enum: Optional[List[str]] = Field(None, title=\"Enum values\")\n    const: Optional[Any] = Field(None, title=\"Constant value\")\n    multipleOf: Optional[float] = Field(None, title=\"Multiple of\")\n    maximum: Optional[float] = Field(None, title=\"Maximum value\")\n    exclusiveMaximum: Optional[float] = Field(None, title=\"Exclusive maximum value\")\n    minimum: Optional[float] = Field(None, title=\"Minimum value\")\n    exclusiveMinimum: Optional[float] = Field(None, title=\"Exclusive minimum value\")\n    maxLength: Optional[int] = Field(None, title=\"Maximum length\")\n    minLength: Optional[int] = Field(None, title=\"Minimum length\")\n    pattern: Optional[str] = Field(None, title=\"Pattern\")\n    updateable: Optional[bool] = Field(None, title=\"Indicates that the field can be updated\")\n    readOnly: Optional[bool] = Field(None, title=\"Indicates the field is read-only\")\n    items: Optional[dict] = None  # items can contain sub-properties\n\n\nclass CustomAction(AzureTREModel):\n    name: str = Field(None, title=\"Custom action name\")\n    description: str = Field(\"\", title=\"Action description\")\n\n\nclass PipelineStepProperty(AzureTREModel):\n    name: str = Field(title=\"name\", description=\"name of the property to update\")\n    type: str = Field(title=\"type\", description=\"data type of the property to update\")\n    value: str = Field(title=\"value\", description=\"value to use in substitution for the property to update\")\n\n\nclass PipelineStep(AzureTREModel):\n    stepId: Optional[str] = Field(title=\"stepId\", description=\"Unique id identifying the step\")\n    stepTitle: Optional[str] = Field(title=\"stepTitle\", description=\"Human readable title of what the step is for\")\n    resourceTemplateName: Optional[str] = Field(title=\"resourceTemplateName\", description=\"Name of the template for the resource under change\")\n    resourceType: Optional[ResourceType] = Field(title=\"resourceType\", description=\"Type of resource under change\")\n    resourceAction: Optional[str] = Field(title=\"resourceAction\", description=\"Action - install / upgrade / uninstall etc\")\n    properties: Optional[List[PipelineStepProperty]]\n\n\nclass Pipeline(AzureTREModel):\n    install: Optional[List[PipelineStep]]\n    upgrade: Optional[List[PipelineStep]]\n    uninstall: Optional[List[PipelineStep]]\n\n\nclass ResourceTemplate(AzureTREModel):\n    id: str\n    name: str = Field(title=\"Unique template name\")\n    title: str = Field(\"\", title=\"Template title or friendly name\")\n    description: str = Field(title=\"Template description\")\n    version: str = Field(title=\"Template version\")\n    resourceType: ResourceType = Field(title=\"Type of resource this template is for (workspace/service)\")\n    current: bool = Field(title=\"Is this the current version of this template\")\n    type: str = \"object\"\n    required: List[str] = Field(title=\"List of properties which must be provided\")\n    properties: Dict[str, Property] = Field(title=\"Template properties\")\n    actions: List[CustomAction] = Field(default=[], title=\"Template actions\")\n    customActions: List[CustomAction] = Field(default=[], title=\"Template custom actions\")\n    pipeline: Optional[Pipeline] = Field(default=None, title=\"Template pipeline to define updates to other resources\")\n\n    # setting this to false means if extra, unexpected fields are supplied, the request is invalidated\n    additionalProperties: bool = Field(default=False, title=\"Prevent unspecified properties being applied\")\n", "path": "api_app/models/domain/resource_template.py"}, {"content": "__version__ = \"0.2.24\"\n", "path": "api_app/_version.py"}]}