diff --git a/HISTORY.rst b/HISTORY.rst index f2255b861..23bd223e8 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -8,15 +8,21 @@ Release History **IoT Central updates** - * add support to run root/interface level device commands. - * add support to get command history for root/interface level device commands. - * interface_id parameter for commands "device command run" , "device command history" run changed to optional. +* Adds support to run root/interface level device commands. +* Adds support to get command history for root/interface level device commands. +* The --interface-id parameter for commands "device command run" , "device command history" changed to optional. **IoT Hub updates** * Fix for "az iot hub c2d-message receive" - the command will use the "ContentEncoding" header value (which indicates the message body encoding) or fallback to utf-8 to decode the received message body. +* Changes to Edge validation for set-modules and edge deployment creation: + + By default only properties of system modules $edgeAgent and $edgeHub are validated against schemas installed with the IoT extension. + This can be disabled by using the --no-validation switch. + + **Azure Digital Twins updates** * Addition of the following commands diff --git a/azext_iot/_help.py b/azext_iot/_help.py index 715828e63..faf642a49 100644 --- a/azext_iot/_help.py +++ b/azext_iot/_help.py @@ -919,6 +919,9 @@ long-summary: | Modules content is json and in the form of {"modulesContent":{...}} or {"content":{"modulesContent":{...}}}. + By default properties of system modules $edgeAgent and $edgeHub are validated against schemas installed with the IoT extension. + This can be disabled by using the --no-validation switch. + Note: Upon execution the command will output the collection of modules applied to the device. examples: - name: Test edge modules while in development by setting modules on a target device. @@ -941,6 +944,9 @@ long-summary: | Deployment content is json and in the form of {"modulesContent":{...}} or {"content":{"modulesContent":{...}}}. + By default properties of system modules $edgeAgent and $edgeHub are validated against schemas installed with the IoT extension. + This can be disabled by using the --no-validation switch. + Edge deployments can be created with user defined metrics for on demand evaluation. User metrics are json and in the form of {"queries":{...}} or {"metrics":{"queries":{...}}}. examples: diff --git a/azext_iot/assets/azure-iot-edgeagent-deployment-1.0.json b/azext_iot/assets/azure-iot-edgeagent-deployment-1.0.json new file mode 100644 index 000000000..3df1f0d39 --- /dev/null +++ b/azext_iot/assets/azure-iot-edgeagent-deployment-1.0.json @@ -0,0 +1,239 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "JSON schema for Azure IoT EdgeAgent Deployment version 1.0", + "required": [ + "$edgeAgent" + ], + "properties": { + "$edgeAgent": { + "type": "object", + "title": "Configuration for the edgeAgent module", + "required": [ + "properties.desired" + ], + "properties": { + "properties.desired": { + "type": "object", + "required": [ + "schemaVersion", + "runtime", + "systemModules", + "modules" + ], + "properties": { + "schemaVersion": { + "type": "string", + "pattern": "1.0" + }, + "runtime": { + "type": "object", + "required": [ + "type", + "settings" + ], + "properties": { + "type": { + "$ref": "#/definitions/moduleType" + }, + "settings": { + "type": "object", + "properties": { + "minDockerVersion": { + "type": "string", + "examples": [ + "v1.25" + ] + }, + "loggingOptions": { + "type": "string" + }, + "registryCredentials": { + "type": "object", + "patternProperties": { + "^.+$": { + "type": "object", + "required": [ + "username", + "password", + "address" + ], + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "address": { + "type": "string", + "pattern": "^[^\\s]+$" + } + } + } + }, + "additionalProperties": false + } + } + } + } + }, + "systemModules": { + "type": "object", + "required": [ + "edgeAgent", + "edgeHub" + ], + "properties": { + "edgeAgent": { + "type": "object", + "required": [ + "type", + "settings" + ], + "properties": { + "type": { + "$ref": "#/definitions/moduleType" + }, + "settings": { + "$ref": "#/definitions/moduleSettings" + }, + "env": { + "$ref": "#/definitions/env" + } + } + }, + "edgeHub": { + "type": "object", + "title": "The Edgehub Schema", + "required": [ + "type", + "settings", + "status", + "restartPolicy" + ], + "properties": { + "type": { + "$ref": "#/definitions/moduleType" + }, + "settings": { + "$ref": "#/definitions/moduleSettings" + }, + "env": { + "$ref": "#/definitions/env" + }, + "status": { + "$ref": "#/definitions/status" + }, + "restartPolicy": { + "$ref": "#/definitions/restartPolicy" + } + } + } + }, + "additionalProperties": false + }, + "modules": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "type": "object", + "required": [ + "type", + "status", + "restartPolicy", + "settings" + ], + "properties": { + "version": { + "type": "string", + "examples": [ + "1.0" + ] + }, + "type": { + "$ref": "#/definitions/moduleType" + }, + "status": { + "$ref": "#/definitions/status" + }, + "restartPolicy": { + "$ref": "#/definitions/restartPolicy" + }, + "env": { + "$ref": "#/definitions/env" + }, + "settings": { + "$ref": "#/definitions/moduleSettings" + } + } + } + }, + "additionalProperties": false + } + } + } + } + } + }, + "additionalProperties": false, + "definitions": { + "moduleType": { + "enum": [ + "docker" + ] + }, + "status": { + "enum": [ + "running", + "stopped" + ] + }, + "restartPolicy": { + "enum": [ + "never", + "on-failure", + "on-unhealthy", + "always" + ] + }, + "moduleSettings": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string", + "examples": [ + "mcr.microsoft.com/azureiotedge-agent:1.0" + ] + }, + "createOptions": { + "$ref": "#/definitions/createOptions" + } + } + }, + "env": { + "type": "object", + "patternProperties": { + "^[^\\+#$\\s\\.]+$": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": ["number", "string", "boolean"] + } + } + } + }, + "additionalProperties": false + }, + "createOptions": { + "type": "string", + "contentMediaType": "application/json" + } + } +} \ No newline at end of file diff --git a/azext_iot/assets/azure-iot-edgeagent-deployment-1.1.json b/azext_iot/assets/azure-iot-edgeagent-deployment-1.1.json new file mode 100644 index 000000000..1a97c6994 --- /dev/null +++ b/azext_iot/assets/azure-iot-edgeagent-deployment-1.1.json @@ -0,0 +1,281 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "title": "JSON schema for Azure IoT EdgeAgent Deployment version 1.1", + "required": [ + "$edgeAgent" + ], + "properties": { + "$edgeAgent": { + "type": "object", + "title": "Configuration for the edgeAgent module", + "required": [ + "properties.desired" + ], + "properties": { + "properties.desired": { + "type": "object", + "required": [ + "schemaVersion", + "runtime", + "systemModules", + "modules" + ], + "properties": { + "schemaVersion": { + "type": "string", + "pattern": "1.1" + }, + "runtime": { + "type": "object", + "required": [ + "type", + "settings" + ], + "properties": { + "type": { + "$ref": "#/definitions/moduleType" + }, + "settings": { + "type": "object", + "properties": { + "minDockerVersion": { + "type": "string", + "examples": [ + "v1.25" + ] + }, + "loggingOptions": { + "type": "string" + }, + "registryCredentials": { + "type": "object", + "patternProperties": { + "^[^\\.\\$# ]+$": { + "type": "object", + "required": [ + "username", + "password", + "address" + ], + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "address": { + "type": "string", + "pattern": "^[^\\s]+$" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "systemModules": { + "type": "object", + "required": [ + "edgeAgent", + "edgeHub" + ], + "properties": { + "edgeAgent": { + "type": "object", + "required": [ + "type", + "settings" + ], + "properties": { + "type": { + "$ref": "#/definitions/moduleType" + }, + "settings": { + "$ref": "#/definitions/moduleSettings" + }, + "env": { + "$ref": "#/definitions/env" + }, + "imagePullPolicy": { + "$ref": "#/definitions/imagePullPolicy" + } + }, + "additionalProperties": false + }, + "edgeHub": { + "type": "object", + "title": "The Edgehub Schema", + "required": [ + "type", + "settings", + "status", + "restartPolicy" + ], + "properties": { + "type": { + "$ref": "#/definitions/moduleType" + }, + "settings": { + "$ref": "#/definitions/moduleSettings" + }, + "env": { + "$ref": "#/definitions/env" + }, + "status": { + "$ref": "#/definitions/status" + }, + "restartPolicy": { + "$ref": "#/definitions/restartPolicy" + }, + "imagePullPolicy": { + "$ref": "#/definitions/imagePullPolicy" + }, + "startupOrder": { + "$ref": "#/definitions/startupOrder" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "modules": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "type": "object", + "required": [ + "type", + "status", + "restartPolicy", + "settings" + ], + "properties": { + "version": { + "type": "string", + "examples": [ + "1.0" + ] + }, + "type": { + "$ref": "#/definitions/moduleType" + }, + "status": { + "$ref": "#/definitions/status" + }, + "restartPolicy": { + "$ref": "#/definitions/restartPolicy" + }, + "env": { + "$ref": "#/definitions/env" + }, + "settings": { + "$ref": "#/definitions/moduleSettings" + }, + "imagePullPolicy": { + "$ref": "#/definitions/imagePullPolicy" + }, + "startupOrder": { + "$ref": "#/definitions/startupOrder" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "definitions": { + "moduleType": { + "enum": [ + "docker" + ] + }, + "status": { + "enum": [ + "running", + "stopped" + ] + }, + "restartPolicy": { + "enum": [ + "never", + "on-failure", + "on-unhealthy", + "always" + ] + }, + "imagePullPolicy": { + "enum": [ + "never", + "on-create" + ] + }, + "startupOrder": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "moduleSettings": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string", + "examples": [ + "mcr.microsoft.com/azureiotedge-agent:1.0" + ] + } + }, + "patternProperties": { + "^(createoptions|createOptions)[0-9]*$": { + "$ref": "#/definitions/createOptions" + } + }, + "additionalProperties": false + }, + "env": { + "type": "object", + "patternProperties": { + "^[^\\+#$\\s\\.]+$": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": [ + "number", + "string", + "boolean" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "createOptions": { + "type": "string", + "contentMediaType": "application/json" + } + } +} \ No newline at end of file diff --git a/azext_iot/assets/azure-iot-edgehub-deployment-1.0.json b/azext_iot/assets/azure-iot-edgehub-deployment-1.0.json new file mode 100644 index 000000000..a33cc29c2 --- /dev/null +++ b/azext_iot/assets/azure-iot-edgehub-deployment-1.0.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "JSON schema for Azure IoT EdgeHub Deployment version 1.0", + "required": [ + "$edgeHub" + ], + "properties": { + "$edgeHub": { + "type": "object", + "title": "Configuration for the edgeHub module", + "required": [ + "properties.desired" + ], + "properties": { + "properties.desired": { + "type": "object", + "required": [ + "schemaVersion", + "routes" + ], + "properties": { + "schemaVersion": { + "type": "string", + "pattern": "1.0" + }, + "routes": { + "type": "object", + "patternProperties": { + "^.+$": { + "type": "string", + "examples": [ + "FROM /* INTO $upstream" + ], + "pattern": "^.+$" + } + } + }, + "storeAndForwardConfiguration": { + "type": "object", + "required": [ + "timeToLiveSecs" + ], + "properties": { + "timeToLiveSecs": { + "type": "integer", + "examples": [ + 7200 + ] + } + } + } + } + } + } + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/azext_iot/assets/azure-iot-edgehub-deployment-1.1.json b/azext_iot/assets/azure-iot-edgehub-deployment-1.1.json new file mode 100644 index 000000000..566357ce1 --- /dev/null +++ b/azext_iot/assets/azure-iot-edgehub-deployment-1.1.json @@ -0,0 +1,91 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "title": "JSON schema for Azure IoT EdgeHub Deployment version 1.1", + "required": [ + "$edgeHub" + ], + "properties": { + "$edgeHub": { + "type": "object", + "title": "Configuration for the edgeHub module", + "required": [ + "properties.desired" + ], + "properties": { + "properties.desired": { + "type": "object", + "required": [ + "schemaVersion", + "routes" + ], + "properties": { + "schemaVersion": { + "type": "string", + "pattern": "1.1" + }, + "routes": { + "type": "object", + "patternProperties": { + "^[^\\.\\$# ]+$": { + "anyOf": [{ + "type": "object", + "required": [ + "route" + ], + "properties": { + "route": { + "type": "string", + "examples": [ + "FROM /* INTO $upstream" + ], + "pattern": "^.+$" + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 9 + }, + "timeToLiveSecs": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + } + }, + "additionalProperties": false + }, { + "type": "string", + "examples": [ + "FROM /* INTO $upstream" + ], + "pattern": "^.+$" + } + ] + } + }, + "additionalProperties": false + }, + "storeAndForwardConfiguration": { + "type": "object", + "required": [ + "timeToLiveSecs" + ], + "properties": { + "timeToLiveSecs": { + "type": "integer", + "examples": [ + 7200 + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/azext_iot/assets/azure-iot-edgehub-deployment-1.2.json b/azext_iot/assets/azure-iot-edgehub-deployment-1.2.json new file mode 100644 index 000000000..9380f7c0c --- /dev/null +++ b/azext_iot/assets/azure-iot-edgehub-deployment-1.2.json @@ -0,0 +1,191 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "title": "JSON schema for Azure IoT EdgeHub Deployment version 1.2", + "required": [ + "$edgeHub" + ], + "properties": { + "$edgeHub": { + "type": "object", + "title": "Configuration for the edgeHub module", + "required": [ + "properties.desired" + ], + "properties": { + "properties.desired": { + "type": "object", + "required": [ + "schemaVersion", + "routes" + ], + "properties": { + "schemaVersion": { + "type": "string", + "pattern": "1.2" + }, + "routes": { + "type": "object", + "patternProperties": { + "^[^\\.\\$# ]+$": { + "anyOf": [{ + "type": "object", + "required": [ + "route" + ], + "properties": { + "route": { + "type": "string", + "examples": [ + "FROM /* INTO $upstream" + ], + "pattern": "^.+$" + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 9 + }, + "timeToLiveSecs": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + } + }, + "additionalProperties": false + }, { + "type": "string", + "examples": [ + "FROM /* INTO $upstream" + ], + "pattern": "^.+$" + } + ] + } + }, + "additionalProperties": false + }, + "storeAndForwardConfiguration": { + "type": "object", + "required": [ + "timeToLiveSecs" + ], + "properties": { + "timeToLiveSecs": { + "type": "integer", + "examples": [ + 7200 + ] + } + }, + "additionalProperties": false + }, + "mqttBroker": { + "type": "object", + "properties": { + "bridges": { + "type": "array", + "items": { + "type": "object", + "properties": { + "endpoint": { + "type": "string", + "pattern": "\\$upstream" + }, + "settings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "direction": { + "type": "string", + "pattern": "^in$|^out$" + }, + "topic": { + "type": "string", + "pattern": "^.*$" + }, + "inPrefix": { + "type": "string", + "pattern": "^.*$" + }, + "outPrefix": { + "type": "string", + "pattern": "^.*$" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "authorizations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "identities": { + "type": "array", + "items": { + "type": "string", + "pattern": "^.+$", + "examples": [ + "{{iot:identity}}", + "contoso.azure-devices.net/MyLeafDevice1", + "contoso.azure-devices.net/MyEdgeDevice/MyModule" + ] + } + }, + "allow": { + "$ref": "#/definitions/policy" + }, + "deny": { + "$ref": "#/definitions/policy" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "definitions": { + "policy": { + "type": "array", + "items": { + "type": "object", + "properties": { + "operations": { + "type": "array", + "items": { + "type": "string", + "pattern": "^mqtt:connect$|^mqtt:subscribe$|^mqtt:publish$" + } + }, + "resources": { + "type": "array", + "items": { + "type": "string", + "pattern": "^.+$", + "examples": [ + "$iothub/clients/+/twin/res/#", + "events/alerts" + ] + } + } + }, + "additionalProperties": false + } + } + } +} \ No newline at end of file diff --git a/azext_iot/assets/edge-deploy-2.0.schema.json b/azext_iot/assets/edge-deploy-2.0.schema.json deleted file mode 100644 index fb8794bfc..000000000 --- a/azext_iot/assets/edge-deploy-2.0.schema.json +++ /dev/null @@ -1,433 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "title": "JSON schema for Azure IoT Edge Deployment version 2.0", - "required": [ - "modulesContent" - ], - "properties": { - "modulesContent": { - "type": "object", - "title": "The configuration for all the modules.", - "required": [ - "$edgeAgent", - "$edgeHub" - ], - "properties": { - "$edgeAgent": { - "type": "object", - "title": "Configuration for the edgeAgent module", - "required": [ - "properties.desired" - ], - "properties": { - "properties.desired": { - "type": "object", - "required": [ - "schemaVersion", - "runtime", - "systemModules", - "modules" - ], - "properties": { - "schemaVersion": { - "type": "string", - "examples": [ - "1.0", - "1.1" - ] - }, - "runtime": { - "type": "object", - "required": [ - "type", - "settings" - ], - "properties": { - "type": { - "$ref": "#/definitions/moduleType" - }, - "settings": { - "type": "object", - "properties": { - "minDockerVersion": { - "type": "string", - "examples": [ - "v1.25" - ] - }, - "loggingOptions": { - "type": "string" - }, - "registryCredentials": { - "type": "object", - "patternProperties": { - "^[^\\.\\$# ]+$": { - "type": "object", - "required": [ - "username", - "password", - "address" - ], - "properties": { - "username": { - "type": "string" - }, - "password": { - "type": "string" - }, - "address": { - "type": "string", - "pattern": "^[^\\s]+$" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "patternProperties": { - "^[^\\.\\$# ]+$":{ - "type": ["array", "boolean", "integer", "null", "number", "object", "string"] - } - }, - "additionalProperties": false - } - }, - "patternProperties": { - "^[^\\.\\$# ]+$":{ - "type": ["array", "boolean", "integer", "null", "number", "object", "string"] - } - }, - "additionalProperties": false - }, - "systemModules": { - "type": "object", - "required": [ - "edgeAgent", - "edgeHub" - ], - "properties": { - "edgeAgent": { - "type": "object", - "required": [ - "type", - "settings" - ], - "properties": { - "type": { - "$ref": "#/definitions/moduleType" - }, - "settings": { - "$ref": "#/definitions/moduleSettings" - }, - "env": { - "$ref": "#/definitions/env" - }, - "imagePullPolicy": { - "$ref": "#/definitions/imagePullPolicy" - } - }, - "patternProperties": { - "^[^\\.\\$# ]+$":{ - "type": ["array", "boolean", "integer", "null", "number", "object", "string"] - } - }, - "additionalProperties": false - }, - "edgeHub": { - "type": "object", - "title": "The Edgehub Schema", - "required": [ - "type", - "settings", - "status", - "restartPolicy" - ], - "properties": { - "type": { - "$ref": "#/definitions/moduleType" - }, - "settings": { - "$ref": "#/definitions/moduleSettings" - }, - "env": { - "$ref": "#/definitions/env" - }, - "status": { - "$ref": "#/definitions/status" - }, - "restartPolicy": { - "$ref": "#/definitions/restartPolicy" - }, - "imagePullPolicy": { - "$ref": "#/definitions/imagePullPolicy" - }, - "startupOrder": { - "$ref": "#/definitions/startupOrder" - } - }, - "patternProperties": { - "^[^\\.\\$# ]+$":{ - "type": ["array", "boolean", "integer", "null", "number", "object", "string"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "modules": { - "type": "object", - "patternProperties": { - "^[a-zA-Z0-9_-]+$": { - "type": "object", - "required": [ - "type", - "status", - "restartPolicy", - "settings" - ], - "properties": { - "version": { - "type": "string", - "examples": [ - "1.0" - ] - }, - "type": { - "$ref": "#/definitions/moduleType" - }, - "status": { - "$ref": "#/definitions/status" - }, - "restartPolicy": { - "$ref": "#/definitions/restartPolicy" - }, - "env": { - "$ref": "#/definitions/env" - }, - "settings": { - "$ref": "#/definitions/moduleSettings" - }, - "imagePullPolicy": { - "$ref": "#/definitions/imagePullPolicy" - }, - "startupOrder": { - "$ref": "#/definitions/startupOrder" - } - }, - "patternProperties": { - "^[^\\.\\$# ]+$":{ - "type": ["array", "boolean", "integer", "null", "number", "object", "string"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "patternProperties": { - "^[^\\.\\$# ]+$":{ - "type": ["array", "boolean", "integer", "null", "number", "object", "string"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "$edgeHub": { - "type": "object", - "title": "Configuration for the edgeHub module", - "required": [ - "properties.desired" - ], - "properties": { - "properties.desired": { - "type": "object", - "required": [ - "schemaVersion", - "routes" - ], - "properties": { - "schemaVersion": { - "type": "string", - "examples": [ - "1.0", - "1.1" - ] - }, - "routes": { - "type": "object", - "patternProperties": { - "^[^\\.\\$# ]+$": { - "anyOf": [ - { - "type": "object", - "required": [ - "route" - ], - "properties": { - "route": { - "type": "string", - "examples": [ - "FROM /* INTO $upstream" - ], - "pattern": "^.+$" - }, - "priority": { - "type": "integer", - "minimum": 0, - "maximum": 9 - }, - "timeToLiveSecs": { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - } - }, - "additionalProperties": false - }, - { - "type": "string", - "examples": [ - "FROM /* INTO $upstream" - ], - "pattern": "^.+$" - } - ] - } - }, - "additionalProperties": false - }, - "storeAndForwardConfiguration": { - "type": "object", - "required": [ - "timeToLiveSecs" - ], - "properties": { - "timeToLiveSecs": { - "type": "integer", - "examples": [ - 7200 - ] - } - }, - "patternProperties": { - "^[^\\.\\$# ]+$":{ - "type": ["array", "boolean", "integer", "null", "number", "object", "string"] - } - }, - "additionalProperties": false - } - }, - "patternProperties": { - "^[^\\.\\$# ]+$":{ - "type": ["array", "boolean", "integer", "null", "number", "object", "string"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "patternProperties": { - "^[a-zA-Z0-9_-]+$": { - "type": "object", - "required": [ - "properties.desired" - ], - "properties": { - "properties.desired": { - "type": "object" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false, - "definitions": { - "moduleType": { - "enum": [ - "docker" - ] - }, - "status": { - "enum": [ - "running", - "stopped" - ] - }, - "restartPolicy": { - "enum": [ - "never", - "on-failure", - "on-unhealthy", - "always" - ] - }, - "imagePullPolicy": { - "enum": [ - "never", - "on-create" - ] - }, - "startupOrder": { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - "moduleSettings": { - "type": "object", - "required": [ - "image" - ], - "properties": { - "image": { - "type": "string", - "examples": [ - "mcr.microsoft.com/azureiotedge-agent:1.0" - ] - }, - "createOptions": { - "$ref": "#/definitions/createOptions" - } - }, - "patternProperties": { - "^[^\\.\\$# ]+$":{ - "type": ["array", "boolean", "integer", "null", "number", "object", "string"] - } - }, - "additionalProperties": false - }, - "env": { - "type": "object", - "patternProperties": { - "^[^\\+#$\\s\\.]+$": { - "type": "object", - "required": [ - "value" - ], - "properties": { - "value": { - "type": ["number", "string", "boolean"] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "createOptions": { - "type": "string", - "contentMediaType": "application/json" - } - } -} diff --git a/azext_iot/constants.py b/azext_iot/constants.py index 4f37a9004..b3e2dcf63 100644 --- a/azext_iot/constants.py +++ b/azext_iot/constants.py @@ -11,8 +11,8 @@ EXTENSION_NAME = "azure-iot" EXTENSION_ROOT = os.path.dirname(os.path.abspath(__file__)) EXTENSION_CONFIG_ROOT_KEY = "iotext" -EDGE_DEPLOYMENT_SCHEMA_2_PATH = os.path.join( - EXTENSION_ROOT, "assets", "edge-deploy-2.0.schema.json" +EDGE_DEPLOYMENT_ROOT_SCHEMAS_PATH = os.path.join( + EXTENSION_ROOT, "assets" ) BASE_MQTT_API_VERSION = "2018-06-30" MESSAGING_HTTP_C2D_SYSTEM_PROPERTIES = [ diff --git a/azext_iot/operations/hub.py b/azext_iot/operations/hub.py index 61ebaa58f..bbc56b261 100644 --- a/azext_iot/operations/hub.py +++ b/azext_iot/operations/hub.py @@ -32,7 +32,6 @@ ) from azext_iot.iothub.providers.discovery import IotHubDiscovery from azext_iot.common.utility import ( - shell_safe_json_parse, read_file_content, validate_key_value_pairs, url_encode_dict, @@ -260,11 +259,18 @@ def _assemble_auth(auth_method, pk, sk): ) auth = None - if auth_method in [DeviceAuthType.shared_private_key.name, DeviceAuthApiType.sas.value]: + if auth_method in [ + DeviceAuthType.shared_private_key.name, + DeviceAuthApiType.sas.value, + ]: auth = AuthenticationMechanism( - symmetric_key=SymmetricKey(primary_key=pk, secondary_key=sk), type=DeviceAuthApiType.sas.value + symmetric_key=SymmetricKey(primary_key=pk, secondary_key=sk), + type=DeviceAuthApiType.sas.value, ) - elif auth_method in [DeviceAuthType.x509_thumbprint.name, DeviceAuthApiType.selfSigned.value]: + elif auth_method in [ + DeviceAuthType.x509_thumbprint.name, + DeviceAuthApiType.selfSigned.value, + ]: if not pk: raise ValueError("primary thumbprint required with selfSigned auth") auth = AuthenticationMechanism( @@ -273,8 +279,13 @@ def _assemble_auth(auth_method, pk, sk): ), type=DeviceAuthApiType.selfSigned.value, ) - elif auth_method in [DeviceAuthType.x509_ca.name, DeviceAuthApiType.certificateAuthority.value]: - auth = AuthenticationMechanism(type=DeviceAuthApiType.certificateAuthority.value) + elif auth_method in [ + DeviceAuthType.x509_ca.name, + DeviceAuthApiType.certificateAuthority.value, + ]: + auth = AuthenticationMechanism( + type=DeviceAuthApiType.certificateAuthority.value + ) else: raise ValueError("Authorization method {} invalid.".format(auth_method)) return auth @@ -868,7 +879,11 @@ def _handle_module_update_params(parameters): def _parse_auth(parameters): - valid_auth = [DeviceAuthApiType.sas.value, DeviceAuthApiType.selfSigned.value, DeviceAuthApiType.certificateAuthority.value] + valid_auth = [ + DeviceAuthApiType.sas.value, + DeviceAuthApiType.selfSigned.value, + DeviceAuthApiType.certificateAuthority.value, + ] auth = parameters["authentication"].get("type") if auth not in valid_auth: raise CLIError("authentication.type must be one of {}".format(valid_auth)) @@ -1380,31 +1395,67 @@ def _process_config_content(content, config_type): def _validate_payload_schema(content): import json - from azext_iot.constants import EDGE_DEPLOYMENT_SCHEMA_2_PATH as schema_path + from os.path import join from azext_iot.models.validators import JsonSchemaType, JsonSchemaValidator + from azext_iot.constants import EDGE_DEPLOYMENT_ROOT_SCHEMAS_PATH as root_schema_path + from azext_iot.common.utility import shell_safe_json_parse + + EDGE_AGENT_SCHEMA_PATH = "azure-iot-edgeagent-deployment-{}.json" + EDGE_HUB_SCHEMA_PATH = "azure-iot-edgehub-deployment-{}.json" + EDGE_SCHEMA_PATH_DICT = { + "$edgeAgent": EDGE_AGENT_SCHEMA_PATH, + "$edgeHub": EDGE_HUB_SCHEMA_PATH, + } - if not exists(schema_path): - logger.info("Invalid schema path %s, skipping validation...", schema_path) - return + modules_content = content["modulesContent"] + system_modules_for_validation = ["$edgeAgent", "$edgeHub"] - logger.info("Validating deployment payload...") - schema_content = str(read_file_content(schema_path)) + for sys_module in system_modules_for_validation: + if sys_module in modules_content: + if ( + "properties.desired" in modules_content[sys_module] + and "schemaVersion" + in modules_content[sys_module]["properties.desired"] + ): + target_schema_ver = modules_content[sys_module][ + "properties.desired" + ]["schemaVersion"] + target_schema_def_path = join(root_schema_path, f"{EDGE_SCHEMA_PATH_DICT[sys_module].format(target_schema_ver)}") - try: - schema_content = shell_safe_json_parse(schema_content) - except CLIError: - logger.info("Issue parsing Edge deployment schema, skipping validation...") - return + logger.info("Attempting to fetch schema content from %s...", target_schema_def_path) + if not exists(target_schema_def_path): + logger.info("Invalid schema path %s, skipping validation...", target_schema_def_path) + continue - v = JsonSchemaValidator(schema_content, JsonSchemaType.draft4) - errors = v.validate(content) - if errors: - # Pretty printing schema validation errors - raise CLIError( - json.dumps({"validationErrors": errors}, separators=(",", ":"), indent=2) - ) - - return + try: + target_schema_def = str(read_file_content(target_schema_def_path)) + target_schema_def = shell_safe_json_parse(target_schema_def) + except Exception: + logger.info( + "Unable to fetch schema content from %s skipping validation...", + target_schema_def_path, + ) + continue + + logger.info(f"Validating {sys_module} of deployment payload against schema...") + to_validate_content = { + sys_module: modules_content[sys_module] + } + draft_version = JsonSchemaType.draft4 + if "$schema" in target_schema_def and "/draft-07/" in target_schema_def["$schema"]: + draft_version = JsonSchemaType.draft7 + + v = JsonSchemaValidator(target_schema_def, draft_version) + errors = v.validate(to_validate_content) + if errors: + # Pretty printing schema validation errors + raise CLIError( + json.dumps( + {"validationErrors": errors}, + separators=(",", ":"), + indent=2, + ) + ) def iot_hub_configuration_update( @@ -2029,7 +2080,10 @@ def _build_device_or_module_connection_string(entity, key_type="primary"): if key_type == "primary" else auth["symmetricKey"]["secondaryKey"] ) - elif auth_type in [DeviceAuthApiType.certificateAuthority.value.lower(), DeviceAuthApiType.selfSigned.value.lower()]: + elif auth_type in [ + DeviceAuthApiType.certificateAuthority.value.lower(), + DeviceAuthApiType.selfSigned.value.lower(), + ]: key = "x509=true" else: raise CLIError("Unable to form target connection string") @@ -2130,8 +2184,14 @@ def _iot_device_send_message( import os device = _iot_device_show(target, device_id) - if device and device.get("authentication", {}).get("type", "") != DeviceAuthApiType.sas.value: - raise CLIError('D2C send message command only supports symmetric key auth (SAS) based devices') + if ( + device + and device.get("authentication", {}).get("type", "") + != DeviceAuthApiType.sas.value + ): + raise CLIError( + "D2C send message command only supports symmetric key auth (SAS) based devices" + ) msgs = [] if properties: @@ -2521,8 +2581,14 @@ def http_wrap(target, device_id, generator): if protocol_type == ProtocolType.mqtt.name: device = _iot_device_show(target, device_id) - if device and device.get("authentication", {}).get("type", "") != DeviceAuthApiType.sas.value: - raise CLIError('MQTT simulation is only supported for symmetric key auth (SAS) based devices') + if ( + device + and device.get("authentication", {}).get("type", "") + != DeviceAuthApiType.sas.value + ): + raise CLIError( + "MQTT simulation is only supported for symmetric key auth (SAS) based devices" + ) wrap = mqtt_client_wrap( target=target, @@ -2614,6 +2680,7 @@ def iot_device_export( resource_group_name=None, ): from azext_iot._factory import iot_hub_service_factory + client = iot_hub_service_factory(cmd.cli_ctx) discovery = IotHubDiscovery(cmd) target = discovery.get_target( @@ -2639,21 +2706,31 @@ def iot_device_export( authentication_type=storage_authentication_type, ) - user_identity = identity not in [None, '[system]'] - if user_identity and storage_authentication_type != AuthenticationType.identityBased.name: + user_identity = identity not in [None, "[system]"] + if ( + user_identity + and storage_authentication_type != AuthenticationType.identityBased.name + ): raise CLIError( "Device export with user-assigned identities requires identity-based authentication [--storage-auth-type]" ) # Track 2 CLI SDKs provide support for user-assigned identity objects - if ensure_iothub_sdk_min_version(IOTHUB_TRACK_2_SDK_MIN_VERSION) and user_identity: - from azure.mgmt.iothub.models import ManagedIdentity # pylint: disable=no-name-in-module + if ( + ensure_iothub_sdk_min_version(IOTHUB_TRACK_2_SDK_MIN_VERSION) + and user_identity + ): + from azure.mgmt.iothub.models import ( + ManagedIdentity, + ) # pylint: disable=no-name-in-module + export_request.identity = ManagedIdentity(user_assigned_identity=identity) # if the user supplied a user-assigned identity, let them know they need a new CLI/SDK elif user_identity: raise CLIError( - "Device export with user-assigned identities requires a dependency of azure-mgmt-iothub>={}" - .format(IOTHUB_TRACK_2_SDK_MIN_VERSION) + "Device export with user-assigned identities requires a dependency of azure-mgmt-iothub>={}".format( + IOTHUB_TRACK_2_SDK_MIN_VERSION + ) ) return client.export_devices( @@ -2715,20 +2792,30 @@ def iot_device_import( authentication_type=storage_authentication_type, ) - user_identity = identity not in [None, '[system]'] - if user_identity and storage_authentication_type != AuthenticationType.identityBased.name: + user_identity = identity not in [None, "[system]"] + if ( + user_identity + and storage_authentication_type != AuthenticationType.identityBased.name + ): raise CLIError( "Device import with user-assigned identities requires identity-based authentication [--storage-auth-type]" ) # Track 2 CLI SDKs provide support for user-assigned identity objects - if ensure_iothub_sdk_min_version(IOTHUB_TRACK_2_SDK_MIN_VERSION) and user_identity: - from azure.mgmt.iothub.models import ManagedIdentity # pylint: disable=no-name-in-module + if ( + ensure_iothub_sdk_min_version(IOTHUB_TRACK_2_SDK_MIN_VERSION) + and user_identity + ): + from azure.mgmt.iothub.models import ( + ManagedIdentity, + ) # pylint: disable=no-name-in-module + import_request.identity = ManagedIdentity(user_assigned_identity=identity) # if the user supplied a user-assigned identity, let them know they need a new CLI/SDK elif user_identity: raise CLIError( - "Device import with user-assigned identities requires a dependency of azure-mgmt-iothub>={}" - .format(IOTHUB_TRACK_2_SDK_MIN_VERSION) + "Device import with user-assigned identities requires a dependency of azure-mgmt-iothub>={}".format( + IOTHUB_TRACK_2_SDK_MIN_VERSION + ) ) return client.import_devices( diff --git a/azext_iot/tests/iothub/configurations/test_edge_deployment_ea_v11_eh_v12.json b/azext_iot/tests/iothub/configurations/test_edge_deployment_ea_v11_eh_v12.json new file mode 100644 index 000000000..d460151e1 --- /dev/null +++ b/azext_iot/tests/iothub/configurations/test_edge_deployment_ea_v11_eh_v12.json @@ -0,0 +1,91 @@ +{ + "modulesContent": { + "$edgeAgent": { + "properties.desired": { + "schemaVersion": "1.1", + "runtime": { + "type": "docker", + "settings": { + "minDockerVersion": "v1.25", + "loggingOptions": "", + "registryCredentials": { + "ContosoRegistry": { + "username": "myacr", + "password": "", + "address": "myacr.azurecr.io" + } + } + } + }, + "systemModules": { + "edgeAgent": { + "type": "docker", + "settings": { + "image": "mcr.microsoft.com/azureiotedge-agent:1.0", + "createOptions": "" + } + }, + "edgeHub": { + "type": "docker", + "status": "running", + "restartPolicy": "always", + "startupOrder": 0, + "settings": { + "image": "mcr.microsoft.com/azureiotedge-hub:1.0", + "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"443/tcp\":[{\"HostPort\":\"443\"}],\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}]}}}" + } + } + }, + "modules": { + "SimulatedTemperatureSensor": { + "version": "1.0", + "type": "docker", + "status": "running", + "restartPolicy": "always", + "startupOrder": 2, + "settings": { + "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", + "createOptions": "{}" + } + }, + "filtermodule": { + "version": "1.0", + "type": "docker", + "status": "running", + "restartPolicy": "always", + "startupOrder": 1, + "env": { + "tempLimit": { + "value": "100" + } + }, + "settings": { + "image": "myacr.azurecr.io/filtermodule:latest", + "createOptions": "{}" + } + } + } + } + }, + "$edgeHub": { + "properties.desired": { + "schemaVersion": "1.2", + "routes": { + "sensorToFilter": { + "route": "FROM /messages/modules/SimulatedTemperatureSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/filtermodule/inputs/input1\")", + "priority": 0, + "timeToLiveSecs": 1800 + }, + "filterToIoTHub": { + "route": "FROM /messages/modules/filtermodule/outputs/output1 INTO $upstream", + "priority": 1, + "timeToLiveSecs": 1800 + } + }, + "storeAndForwardConfiguration": { + "timeToLiveSecs": 100 + } + } + } + } +} \ No newline at end of file diff --git a/azext_iot/tests/iothub/configurations/test_edge_deployment_ea_v90_eh_v91.json b/azext_iot/tests/iothub/configurations/test_edge_deployment_ea_v90_eh_v91.json new file mode 100644 index 000000000..e08e7bef2 --- /dev/null +++ b/azext_iot/tests/iothub/configurations/test_edge_deployment_ea_v90_eh_v91.json @@ -0,0 +1,91 @@ +{ + "modulesContent": { + "$edgeAgent": { + "properties.desired": { + "schemaVersion": "9.0", + "runtime": { + "type": "docker", + "settings": { + "minDockerVersion": "v1.25", + "loggingOptions": "", + "registryCredentials": { + "ContosoRegistry": { + "username": "myacr", + "password": "", + "address": "myacr.azurecr.io" + } + } + } + }, + "systemModules": { + "edgeAgent": { + "type": "docker", + "settings": { + "image": "mcr.microsoft.com/azureiotedge-agent:1.0", + "createOptions": "" + } + }, + "edgeHub": { + "type": "docker", + "status": "running", + "restartPolicy": "always", + "startupOrder": 0, + "settings": { + "image": "mcr.microsoft.com/azureiotedge-hub:1.0", + "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"443/tcp\":[{\"HostPort\":\"443\"}],\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}]}}}" + } + } + }, + "modules": { + "SimulatedTemperatureSensor": { + "version": "1.0", + "type": "docker", + "status": "running", + "restartPolicy": "always", + "startupOrder": 2, + "settings": { + "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", + "createOptions": "{}" + } + }, + "filtermodule": { + "version": "1.0", + "type": "docker", + "status": "running", + "restartPolicy": "always", + "startupOrder": 1, + "env": { + "tempLimit": { + "value": "100" + } + }, + "settings": { + "image": "myacr.azurecr.io/filtermodule:latest", + "createOptions": "{}" + } + } + } + } + }, + "$edgeHub": { + "properties.desired": { + "schemaVersion": "9.1", + "routes": { + "sensorToFilter": { + "route": "FROM /messages/modules/SimulatedTemperatureSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/filtermodule/inputs/input1\")", + "priority": 0, + "timeToLiveSecs": 1800 + }, + "filterToIoTHub": { + "route": "FROM /messages/modules/filtermodule/outputs/output1 INTO $upstream", + "priority": 1, + "timeToLiveSecs": 1800 + } + }, + "storeAndForwardConfiguration": { + "timeToLiveSecs": 100 + } + } + } + } +} \ No newline at end of file diff --git a/azext_iot/tests/iothub/configurations/test_iot_config_unit.py b/azext_iot/tests/iothub/configurations/test_iot_config_unit.py index cb54909c2..c41afa5b8 100644 --- a/azext_iot/tests/iothub/configurations/test_iot_config_unit.py +++ b/azext_iot/tests/iothub/configurations/test_iot_config_unit.py @@ -14,7 +14,12 @@ from knack.cli import CLIError from azext_iot.operations import hub as subject from azext_iot.common.utility import read_file_content, evaluate_literal -from azext_iot.tests.conftest import build_mock_response, path_service_client, mock_target, get_context_path +from azext_iot.tests.conftest import ( + build_mock_response, + path_service_client, + mock_target, + get_context_path, +) config_id = "myconfig-{}".format(str(uuid4()).replace("-", "")) @@ -33,12 +38,25 @@ def sample_config_edge_malformed(set_cwd): return result -@pytest.fixture(params=["file", "inlineA", "inlineB", "layered", "v1", "v11"]) +@pytest.fixture( + params=[ + "file", + "inlineA", + "inlineB", + "layered", + "v1", + "v11", + "ea_v11_eh_v12", + "ea_v90_eh_v91", + ] +) def sample_config_edge(set_cwd, request): path = "test_edge_deployment.json" layered_path = "test_edge_deployment_layered.json" v1_path = "test_edge_deployment_v1.json" v11_path = "test_edge_deployment_v11.json" + ea_v11_eh_v12_path = "test_edge_deployment_ea_v11_eh_v12.json" + ea_v90_eh_v91_path = "test_edge_deployment_ea_v90_eh_v91.json" payload = None if request.param == "inlineA": @@ -53,6 +71,10 @@ def sample_config_edge(set_cwd, request): payload = json.dumps(json.loads(read_file_content(v1_path))) elif request.param == "v11": payload = json.dumps(json.loads(read_file_content(v11_path))) + elif request.param == "ea_v11_eh_v12": + payload = json.dumps(json.loads(read_file_content(ea_v11_eh_v12_path))) + elif request.param == "ea_v90_eh_v91": + payload = json.dumps(json.loads(read_file_content(ea_v90_eh_v91_path))) return (request.param, payload) @@ -102,7 +124,7 @@ def service_client( headers={}, status=200, content_type="application/json", - match_querystring=False + match_querystring=False, ) mocked_response.add( @@ -112,7 +134,7 @@ def service_client( headers={"x-ms-continuation": ""}, status=200, content_type="application/json", - match_querystring=False + match_querystring=False, ) yield mocked_response @@ -181,6 +203,7 @@ def test_config_metric_show_invalid_args( self, fixture_cmd, service_client, metric_id, content_type, metric_type ): from functools import partial + service_client.assert_all_requests_are_fired = False with pytest.raises(CLIError): @@ -293,7 +316,12 @@ def test_config_create_edge( assert body.get("priority") == priority assert body.get("labels") == evaluate_literal(labels, dict) - if sample_config_edge[0] == "inlineB" or sample_config_edge[0] == "v11": + if ( + sample_config_edge[0] == "inlineB" + or sample_config_edge[0] == "v11" + or sample_config_edge[0] == "ea_v11_eh_v12" + or sample_config_edge[0] == "ea_v90_eh_v91" + ): assert ( body["content"]["modulesContent"] == json.loads(sample_config_edge[1])["modulesContent"] @@ -574,7 +602,8 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): return service_client @pytest.mark.parametrize( - "etag", [generate_generic_id(), None], + "etag", + [generate_generic_id(), None], ) def test_config_delete(self, serviceclient, fixture_cmd, etag): subject.iot_hub_configuration_delete( @@ -604,7 +633,8 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): return service_client @pytest.mark.parametrize( - "etag", [generate_generic_id(), None], + "etag", + [generate_generic_id(), None], ) def test_config_update(self, fixture_cmd, serviceclient, sample_config_show, etag): subject.iot_hub_configuration_update( @@ -612,7 +642,7 @@ def test_config_update(self, fixture_cmd, serviceclient, sample_config_show, eta config_id=config_id, hub_name=mock_target["entity"], parameters=sample_config_show, - etag=etag + etag=etag, ) args = serviceclient.call_args url = args[0][0].url @@ -649,9 +679,13 @@ def test_config_update_invalid_args( ) type_name = "class" if "class" in str(type) else "type" - assert str(exc_label.value) == ("The property \"labels\" must be of <{0} 'dict'> but is <{0} 'str'>. " - "Input: not a dictionary. Review inline JSON examples here --> " - "https://github.com/Azure/azure-iot-cli-extension/wiki/Tips".format(type_name)) + assert str(exc_label.value) == ( + "The property \"labels\" must be of <{0} 'dict'> but is <{0} 'str'>. " + "Input: not a dictionary. Review inline JSON examples here --> " + "https://github.com/Azure/azure-iot-cli-extension/wiki/Tips".format( + type_name + ) + ) def test_config_update_error(self, fixture_cmd, serviceclient_generic_error): with pytest.raises(CLIError): @@ -671,18 +705,24 @@ def service_client(self, mocked_response, fixture_ghcs, request): # Create mock edge deployments and ADM device and module configurations for i in range(size): - result.append({ - "id": "edgeDeployment{}".format(i), - "content": {"modulesContent": {"key": {}}}, - }) - result.append({ - "id": "moduleConfiguration{}".format(i), - "content": {"moduleContent": {"key": {}}}, - }) - result.append({ - "id": "deviceConfiguration{}".format(i), - "content": {"deviceContent": {"key": {}}}, - }) + result.append( + { + "id": "edgeDeployment{}".format(i), + "content": {"modulesContent": {"key": {}}}, + } + ) + result.append( + { + "id": "moduleConfiguration{}".format(i), + "content": {"moduleContent": {"key": {}}}, + } + ) + result.append( + { + "id": "deviceConfiguration{}".format(i), + "content": {"deviceContent": {"key": {}}}, + } + ) mocked_response.add( method=responses.GET, @@ -691,7 +731,7 @@ def service_client(self, mocked_response, fixture_ghcs, request): headers={"x-ms-continuation": ""}, status=200, content_type="application/json", - match_querystring=False + match_querystring=False, ) mocked_response.expected_size = size @@ -778,7 +818,12 @@ def test_config_apply_edge( in url ) - if sample_config_edge[0] == "inlineB" or sample_config_edge[0] == "v11": + if ( + sample_config_edge[0] == "inlineB" + or sample_config_edge[0] == "v11" + or sample_config_edge[0] == "ea_v11_eh_v12" + or sample_config_edge[0] == "ea_v90_eh_v91" + ): assert ( body["modulesContent"] == json.loads(sample_config_edge[1])["modulesContent"] diff --git a/setup.py b/setup.py index 23833446c..3dfc06209 100644 --- a/setup.py +++ b/setup.py @@ -81,7 +81,7 @@ EXTENSION_REF_NAME: [ "azext_metadata.json", "digicert.pem", - "assets/edge-deploy-2.0.schema.json", + "assets/*", ] }, install_requires=DEPENDENCIES,