diff --git a/.azure-devops/create-release.yml b/.azure-devops/create-release.yml index 0ae28e069..45d869c3b 100644 --- a/.azure-devops/create-release.yml +++ b/.azure-devops/create-release.yml @@ -2,103 +2,161 @@ pr: none trigger: none -jobs: - -- job: 'Build_Publish_Azure_IoT_CLI_Extension' - pool: - vmImage: 'Ubuntu-16.04' - - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '3.7.x' - architecture: 'x64' - - - template: templates/setup-ci-machine.yml - - - template: templates/build-publish-azure-iot-cli-extension.yml - -- job: 'Build_Publish_Azure_CLI_Test_SDK' - pool: - vmImage: 'Ubuntu-16.04' - - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '3.7.x' - architecture: 'x64' - - - template: templates/setup-ci-machine.yml - - - template: templates/build-publish-azure-cli-test-sdk.yml - -- job: 'Run_Tests' - dependsOn : Build_Publish_Azure_CLI_Test_SDK - pool: - vmImage: 'Ubuntu-16.04' - - steps: - - template: templates/run-tests.yml - parameters: - pythonVersion: '3.7.x' - - - script: 'pip install .' - displayName: 'Install the whl locally' - workingDirectory: '.' - - - task: PythonScript@0 - displayName : 'setupVersion' - name: 'setupVersion' - inputs: - scriptSource: 'inline' - script: | - from azext_iot.constants import VERSION - print("Version is " + VERSION) - print("##vso[task.setvariable variable=CLIVersion;isOutput=true]"+VERSION) - print("##vso[task.setvariable variable=ReleaseTitle;isOutput=true]"+"azure-iot "+VERSION) - -- job: 'Calculate_Sha_And_Create_Release' - dependsOn : Run_Tests - pool: - vmImage: 'vs2017-win2016' - variables: - CLIVersion: $[dependencies.Run_Tests.outputs['setupVersion.CLIVersion']] - ReleaseTitle: $[dependencies.Run_Tests.outputs['setupVersion.ReleaseTitle']] - - steps: - - task: DownloadBuildArtifacts@0 - displayName : 'Download Extension wheel from Build Artifacts' - inputs: - buildType: 'current' - downloadType: 'single' - artifactName: 'azure-iot' - downloadPath: '$(System.ArtifactsDirectory)/extension' - - - task: PowerShell@2 - displayName: 'Calculate sha for downloaded extension' - inputs: - targetType: 'inline' - script: | - $extensions = Get-ChildItem -Filter "*.whl" -Recurse | Select-Object FullName - Foreach ($extension in $extensions) - { - Write-Host "calculating sha256 for " $extension.FullName - (Get-Filehash -Path $extension.Fullname -Algorithm SHA256).Hash.ToLower() - } - Write-Host "done" - workingDirectory: '$(System.ArtifactsDirectory)/extension' - - - task: GitHubRelease@0 - inputs: - gitHubConnection: GitHub - repositoryName: $(Build.Repository.Name) - action: 'create' - target: '$(Build.SourceVersion)' - tagSource: manual - tag: 'v$(CLIVersion)' - title: $(ReleaseTitle) - assets: '$(System.ArtifactsDirectory)/extension/**/*.whl' - assetUploadMode: 'replace' - isDraft: true - isPreRelease: false - addChangeLog: false +variables: + pythonVersion: '3.6.x' + architecture: 'x64' + +stages: + - stage: 'build' + displayName: 'Build and Publish Artifacts' + jobs: + + - job: 'Build_Publish_Azure_IoT_CLI_Extension' + pool: + vmImage: 'Ubuntu-16.04' + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: $(pythonVersion) + architecture: $(architecture) + + - template: templates/setup-ci-machine.yml + + - template: templates/build-publish-azure-iot-cli-extension.yml + + - job: 'Build_Publish_Azure_CLI_Test_SDK' + pool: + vmImage: 'Ubuntu-16.04' + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: $(pythonVersion) + architecture: $(architecture) + + - template: templates/setup-ci-machine.yml + + - template: templates/build-publish-azure-cli-test-sdk.yml + + - job: 'recordVersion' + displayName: 'Install and verify version' + dependsOn: [Build_Publish_Azure_IoT_CLI_Extension, Build_Publish_Azure_CLI_Test_SDK] + steps: + - template: templates/setup-dev-test-env.yml + parameters: + pythonVersion: $(pythonVersion) + architecture: $(architecture) + + - template: templates/install-and-record-version.yml + + - stage: 'test' + displayName: 'Run tests' + pool: + vmImage: 'Ubuntu-16.04' + dependsOn: build + jobs: + - job: 'testCentral' + displayName: 'Test IoT Central' + steps: + - template: templates/run-tests.yml + parameters: + path: 'azext_iot/tests/central' + name: 'iot-central' + + - job: 'testADT' + displayName: 'Test Azure DigitalTwins' + steps: + - template: templates/run-tests.yml + parameters: + path: 'azext_iot/tests/digitaltwins' + name: 'azure-digitaltwins' + + - job: 'testDPS' + displayName: 'Test DPS' + steps: + - template: templates/run-tests.yml + parameters: + path: 'azext_iot/tests/dps' + name: 'device-provisioning-service' + + - job: 'testHub' + displayName: 'Test IoT Hub' + steps: + - template: templates/run-tests.yml + parameters: + path: 'azext_iot/tests/iothub' + name: 'iot-hub' + + - job: 'unitTests' + displayName: 'Unit tests and code coverage' + steps: + - template: templates/run-tests.yml + parameters: + runIntTests: 'false' + runUnitTests: 'true' + + - stage: 'kpi' + displayName: 'Build KPIs' + dependsOn: [build, test] + jobs: + - job: 'calculateCodeCoverage' + displayName: 'Calculate distributed code coverage' + steps: + - template: templates/calculate-code-coverage.yml + parameters: + pythonVersion: $(pythonVersion) + architecture: $(architecture) + + - stage: 'release' + displayName: 'Stage GitHub release' + dependsOn: [build, test] + jobs: + - deployment: 'StageGitHub' + displayName: 'Stage CLI extension on GitHub' + environment: 'production' + + - job: 'Calculate_Sha_And_Create_Release' + pool: + vmImage: 'vs2017-win2016' + variables: + CLIVersion: $[ stageDependencies.build.recordVersion.outputs['setupVersion.CLIVersion'] ] + ReleaseTitle: $[ stageDependencies.build.recordVersion.outputs['setupVersion.ReleaseTitle'] ] + + steps: + - task: DownloadBuildArtifacts@0 + displayName : 'Download Extension wheel from Build Artifacts' + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: 'azure-iot' + downloadPath: '$(System.ArtifactsDirectory)/extension' + + - task: PowerShell@2 + displayName: 'Calculate sha for downloaded extension' + inputs: + targetType: 'inline' + script: | + $extensions = Get-ChildItem -Filter "*.whl" -Recurse | Select-Object FullName + Foreach ($extension in $extensions) + { + Write-Host "calculating sha256 for " $extension.FullName + (Get-Filehash -Path $extension.Fullname -Algorithm SHA256).Hash.ToLower() + } + Write-Host "done" + workingDirectory: '$(System.ArtifactsDirectory)/extension' + + - task: GitHubRelease@0 + inputs: + gitHubConnection: AzIoTCLIGitHub + repositoryName: $(Build.Repository.Name) + action: 'create' + target: '$(Build.SourceVersion)' + tagSource: manual + tag: 'v$(CLIVersion)' + title: $(ReleaseTitle) + assets: '$(System.ArtifactsDirectory)/extension/**/*.whl' + assetUploadMode: 'replace' + isDraft: true + isPreRelease: false + addChangeLog: false diff --git a/.azure-devops/merge.yml b/.azure-devops/merge.yml index d1edf8a03..762950505 100644 --- a/.azure-devops/merge.yml +++ b/.azure-devops/merge.yml @@ -8,76 +8,49 @@ pr: variables: iot_ext_package: azure-iot + iot_ext_venv: venv jobs: -- job: 'Build_Publish_Azure_IoT_CLI_Extension' +- job: 'build_and_publish_azure_iot_cli_ext' pool: vmImage: 'Ubuntu-16.04' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.7.x' + versionSpec: '3.6.x' architecture: 'x64' - template: templates/setup-ci-machine.yml - template: templates/build-publish-azure-iot-cli-extension.yml -- job: 'Build_Publish_Azure_CLI_Test_SDK' +- job: 'build_and_publish_azure_cli_test_sdk' pool: vmImage: 'Ubuntu-16.04' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.7.x' + versionSpec: '3.6.x' architecture: 'x64' - template: templates/setup-ci-machine.yml - template: templates/build-publish-azure-cli-test-sdk.yml -- job: 'Run_Tests_Windows' - dependsOn : [ 'Build_Publish_Azure_CLI_Test_SDK', 'Build_Publish_Azure_IoT_CLI_Extension'] - pool: - vmImage: 'vs2017-win2016' - - steps: - - task: PowerShell@2 - inputs: - targetType: 'inline' - script : 'ren "C:\Program Files\Common Files\AzureCliExtensionDirectory" "C:\Program Files\Common Files\AzureCliExtensionDirectory1"' - - - template: templates/run-tests.yml - parameters: - pythonVersion: '3.x' - -- job: 'Run_Tests_Windows_Azure_CLI_Released_Version' - dependsOn : [Build_Publish_Azure_CLI_Test_SDK, 'Build_Publish_Azure_IoT_CLI_Extension'] - pool: - vmImage: 'vs2017-win2016' - - steps: - - template: templates/run-tests.yml - parameters: - pythonVersion: '3.x' - runWithAzureCliReleased: 'false' - -- job: 'Run_Tests_Ubuntu' - dependsOn: ['Build_Publish_Azure_CLI_Test_SDK','Build_Publish_Azure_IoT_CLI_Extension'] +- job: 'run_unit_tests_ubuntu' + dependsOn: [ 'build_and_publish_azure_iot_cli_ext', 'build_and_publish_azure_cli_test_sdk'] pool: vmImage: 'Ubuntu-16.04' strategy: matrix: - Python27: - python.version: '2.x' Python36: python.version: '3.6.x' Python37: python.version: '3.7.x' Python38: python.version: '3.8.x' - maxParallel: 4 + maxParallel: 3 steps: - bash: sudo rm -R -f /usr/local/lib/azureExtensionDir @@ -85,26 +58,49 @@ jobs: - template: templates/run-tests.yml parameters: pythonVersion: '$(python.version)' + runUnitTests: 'true' + runIntTests: 'false' -- job: 'Run_Tests_Mac' - dependsOn: ['Build_Publish_Azure_CLI_Test_SDK','Build_Publish_Azure_IoT_CLI_Extension'] +- job: 'run_unit_tests_macOs' + dependsOn: ['build_and_publish_azure_iot_cli_ext', 'build_and_publish_azure_cli_test_sdk'] pool: vmImage: 'macOS-10.14' steps: - template: templates/run-tests.yml parameters: - pythonVersion: '3.x' + pythonVersion: '3.8.x' + runUnitTests: 'true' + runIntTests: 'false' -- job: 'Run_Style_Check' - dependsOn: ['Build_Publish_Azure_CLI_Test_SDK','Build_Publish_Azure_IoT_CLI_Extension'] + - template: templates/calculate-code-coverage.yml + +- job: 'run_unit_tests_windows' + dependsOn : [ 'build_and_publish_azure_iot_cli_ext', 'build_and_publish_azure_cli_test_sdk'] + pool: + vmImage: 'vs2017-win2016' + + steps: + - task: PowerShell@2 + inputs: + targetType: 'inline' + script : 'ren "C:\Program Files\Common Files\AzureCliExtensionDirectory" "C:\Program Files\Common Files\AzureCliExtensionDirectory1"' + + - template: templates/run-tests.yml + parameters: + pythonVersion: '3.8.x' + runUnitTests: 'true' + runIntTests: 'false' + +- job: 'run_style_check' + dependsOn: ['build_and_publish_azure_iot_cli_ext', 'build_and_publish_azure_cli_test_sdk'] pool: vmImage: 'Ubuntu-16.04' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.x' + versionSpec: '3.8.x' architecture: 'x64' - template: templates/install-azure-cli-released.yml @@ -119,21 +115,46 @@ jobs: displayName: 'Evaluate with flake8' workingDirectory: '.' -- job: 'Run_Core_Linter_and_HelpText_Check' - dependsOn: ['Build_Publish_Azure_CLI_Test_SDK','Build_Publish_Azure_IoT_CLI_Extension'] +# TODO: Evaluate this style or similar alternative for setting up CLI env +- job: 'run_azdev_linter_on_command_table' + dependsOn: ['build_and_publish_azure_iot_cli_ext'] + displayName: 'Evaluate IoT extension command table' pool: vmImage: 'Ubuntu-16.04' steps: - task: UsePythonVersion@0 inputs: - versionSpec: '3.x' + versionSpec: '3.6.x' architecture: 'x64' - - template: templates/install-azure-cli-released.yml - - template: templates/install-azdev.yml - - template: templates/setup-ci-machine.yml # Likely temporary fix due to pkg_resources.ContextualVersionConflict from six ver 1.13.0 vs pinned 1.12.0 - - template: templates/download-install-local-azure-iot-cli-extension.yml + - template: templates/install-configure-azure-cli.yml + +- job: CredScan + displayName: 'Credential Scan' + pool: + vmImage: 'vs2017-win2016' - - script: 'azdev cli-lint --ci --extensions $(iot_ext_package)' - displayName: 'Evaluate with CLI core linter' + steps: + - task: CredScan@3 + inputs: + outputFormat: 'pre' + scanFolder: '$(Build.SourcesDirectory)' + suppressionsFile: '$(Build.SourcesDirectory)/CredScanSuppressions.json' + + - task: PostAnalysis@1 + inputs: + AllTools: false + APIScan: false + BinSkim: false + CodesignValidation: false + CredScan: true + FortifySCA: false + FxCop: false + ModernCop: false + PoliCheck: false + RoslynAnalyzers: false + SDLNativeRules: false + Semmle: false + TSLint: false + ToolLogsNotFoundAction: 'Standard' diff --git a/.azure-devops/templates/build-publish-azure-cli-test-sdk.yml b/.azure-devops/templates/build-publish-azure-cli-test-sdk.yml index f797cdf21..263108a86 100644 --- a/.azure-devops/templates/build-publish-azure-cli-test-sdk.yml +++ b/.azure-devops/templates/build-publish-azure-cli-test-sdk.yml @@ -1,21 +1,21 @@ steps: - - script: 'rm -rf azure-cli' + - script: 'rm -rf ../azure-cli' displayName: 'delete azure cli directory' - - script: 'git clone https://github.com/Azure/azure-cli.git' + - script: 'git clone -q --single-branch -b master https://github.com/Azure/azure-cli.git ../azure-cli' displayName: 'Clone Azure CLI repository' - script: 'pip install --upgrade .' displayName: 'Install Azure CLI test SDK' - workingDirectory: 'azure-cli/src/azure-cli-testsdk/' + workingDirectory: '../azure-cli/src/azure-cli-testsdk/' - script: 'python setup.py sdist bdist_wheel' displayName: 'Build wheel for Azure CLI test SDK' - workingDirectory: 'azure-cli/src/azure-cli-testsdk/' + workingDirectory: '../azure-cli/src/azure-cli-testsdk/' - task: PublishBuildArtifacts@1 displayName: 'Publish Azure CLI test SDK as artifact' inputs: - pathtoPublish: 'azure-cli/src/azure-cli-testsdk/dist' + pathtoPublish: '../azure-cli/src/azure-cli-testsdk/dist' artifactName: 'azure-cli-test-sdk' publishLocation: 'Container' diff --git a/.azure-devops/templates/build-publish-azure-iot-cli-extension.yml b/.azure-devops/templates/build-publish-azure-iot-cli-extension.yml index 4a41bde21..b9af22ad6 100644 --- a/.azure-devops/templates/build-publish-azure-iot-cli-extension.yml +++ b/.azure-devops/templates/build-publish-azure-iot-cli-extension.yml @@ -1,8 +1,4 @@ steps: - - script: 'pip install --upgrade .' - displayName: 'Install Azure IoT CLI extension' - workingDirectory: '.' - - script: 'python setup.py sdist bdist_wheel' displayName: 'Build wheel for Azure IoT CLI extension' workingDirectory: '.' diff --git a/.azure-devops/templates/calculate-code-coverage.yml b/.azure-devops/templates/calculate-code-coverage.yml new file mode 100644 index 000000000..eb14c272a --- /dev/null +++ b/.azure-devops/templates/calculate-code-coverage.yml @@ -0,0 +1,34 @@ +parameters: + pythonVersion: '3.6.x' + architecture: 'x64' + +steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: ${{ parameters.pythonVersion }} + architecture: ${{ parameters.architecture }} + + - template: setup-ci-machine.yml + + - task: DownloadBuildArtifacts@0 + displayName : 'Download code coverage KPIs' + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: 'coverage' + downloadPath: '$(System.ArtifactsDirectory)/aziotext_kpi/' + + - bash: | + ls -l ./coverage + export COVERAGE_FILE=.coverage.combined + for i in ./coverage/.coverage.*; do + coverage combine -a $i + done + coverage xml --rcfile="$(System.DefaultWorkingDirectory)/.coveragerc" + workingDirectory: '$(System.ArtifactsDirectory)/aziotext_kpi/' + displayName: 'Merging code coverage data' + + - task: PublishCodeCoverageResults@1 + inputs: + codeCoverageTool: 'cobertura' + summaryFileLocation: '$(System.ArtifactsDirectory)/aziotext_kpi/coverage.xml' diff --git a/.azure-devops/templates/install-and-record-version.yml b/.azure-devops/templates/install-and-record-version.yml new file mode 100644 index 000000000..7c26af21b --- /dev/null +++ b/.azure-devops/templates/install-and-record-version.yml @@ -0,0 +1,15 @@ +steps: +- script: 'pip install .' + displayName: 'Install the whl locally' + workingDirectory: '.' + +- task: PythonScript@0 + displayName : 'setupVersion' + name: 'setupVersion' + inputs: + scriptSource: 'inline' + script: | + from azext_iot.constants import VERSION + print("Version is " + VERSION) + print("##vso[task.setvariable variable=CLIVersion;isOutput=true]"+VERSION) + print("##vso[task.setvariable variable=ReleaseTitle;isOutput=true]"+"azure-iot "+VERSION) diff --git a/.azure-devops/templates/install-azdev.yml b/.azure-devops/templates/install-azdev.yml index c58c5a447..b3efc8160 100644 --- a/.azure-devops/templates/install-azdev.yml +++ b/.azure-devops/templates/install-azdev.yml @@ -1,3 +1,3 @@ steps: - - script: 'pip install -e "git+https://github.com/Azure/azure-cli@dev#egg=azure-cli-dev-tools&subdirectory=tools"' + - script: 'pip install azdev' displayName: 'Install azdev tool' diff --git a/.azure-devops/templates/install-configure-azure-cli.yml b/.azure-devops/templates/install-configure-azure-cli.yml new file mode 100644 index 000000000..34a5f5d9a --- /dev/null +++ b/.azure-devops/templates/install-configure-azure-cli.yml @@ -0,0 +1,29 @@ +steps: + - task: DownloadBuildArtifacts@0 + displayName : 'Download Extension wheel from Build Artifacts' + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: 'azure-iot' + downloadPath: '$(System.ArtifactsDirectory)/extension' + - task: Bash@3 + inputs: + targetType: 'inline' + script: | + set -ev + pip install virtualenv + python -m virtualenv venv/ + source ./venv/bin/activate + git clone --single-branch -b dev https://github.com/Azure/azure-cli.git ../azure-cli + pip install azdev + azdev --version + azdev setup -c ../azure-cli -r ./ + AZURE_EXTENSION_DIR=~/.azure/cliextensions + ARTIFACTS_DIR=$(System.ArtifactsDirectory)/extension + WHEELS=$(ls $ARTIFACTS_DIR/azure-iot/*.whl) + az --version + for i in $WHEELS; do + az extension add --source $i -y --debug + done + cp ./linter_exclusions.yml $AZURE_EXTENSION_DIR/azure-iot/ + azdev linter --include-whl-extensions azure-iot --min-severity medium diff --git a/.azure-devops/templates/run-tests.yml b/.azure-devops/templates/run-tests.yml index 876d7963f..91db4af4f 100644 --- a/.azure-devops/templates/run-tests.yml +++ b/.azure-devops/templates/run-tests.yml @@ -1,49 +1,45 @@ parameters: - pythonVersion: '' - runUnitTestsOnly: 'true' - runWithAzureCliReleased: 'false' + pythonVersion: '3.6.x' + architecture: 'x64' + runUnitTests: 'false' + runIntTests: 'true' + runWithAzureCliReleased: 'true' + path: 'azext_iot/tests' + name: 'all' steps: - - task: UsePythonVersion@0 + - template: setup-dev-test-env.yml + parameters: + architecture: ${{ parameters.architecture }} + pythonVersion: ${{ parameters.pythonVersion }} + runWithAzureCliReleased: ${{ parameters.runWithAzureCliReleased }} + + - template: set-testenv-sentinel.yml + + - ${{ if eq(parameters.runIntTests, 'true') }}: + - task: AzureCLI@2 + continueOnError: true + displayName: '${{ parameters.name }} integration tests' + inputs: + azureSubscription: AzIoTCLIService + scriptType: bash + scriptLocation: inlineScript + inlineScript: | + export COVERAGE_FILE=.coverage.${{ parameters.name }} + pytest -vv ${{ parameters.path }} -k "_int" --cov=azext_iot --cov-config .coveragerc --junitxml=junit/test-iotext-int-${{ parameters.name }}.xml + + - ${{ if eq(parameters.runUnitTests, 'true') }}: + - script: | + pytest -vv ${{ parameters.path }} -k "_unit" --cov=azext_iot --cov-config .coveragerc --junitxml=junit/test-iotext-unit-${{ parameters.name }}.xml + displayName: '${{ parameters.name }} unit tests' + env: + COVERAGE_FILE: .coverage.${{ parameters.name }} + + - task: PublishBuildArtifacts@1 inputs: - versionSpec: ${{ parameters.pythonVersion }} - architecture: 'x64' - - - ${{ if eq(parameters.runWithAzureCliReleased, 'false') }}: - - template: install-azure-cli-edge.yml - - - ${{ if eq(parameters.runWithAzureCliReleased, 'true') }}: - - template: install-azure-cli-released.yml - - - template: download-install-local-azure-test-sdk.yml - - - template: setup-ci-machine.yml - - - template: download-install-local-azure-iot-cli-extension.yml - - - template: set-pythonpath.yml - - - ${{ if eq(parameters.runUnitTestsOnly, 'false') }}: - - script: pytest --junitxml "TEST-results.xml" - displayName: 'Execute all Tests' - - - ${{ if eq(parameters.runUnitTestsOnly, 'true') }}: - - script: pytest -v azext_iot/tests/test_iot_ext_unit.py --junitxml=junit/test-iothub-unit-results.xml - displayName: 'Execute IoT Hub unit tests' - - script: pytest -v azext_iot/tests/test_iot_dps_unit.py --junitxml=junit/test-dps-unit-results.xml - displayName: 'Execute DPS unit tests' - - script: pytest -v azext_iot/tests/test_iot_utility_unit.py --junitxml=junit/test-utility-unit-results.xml - displayName: 'Execute Utility unit tests' - - script: pytest -v azext_iot/tests/test_iot_central_unit.py --junitxml=junit/test-central-unit-results.xml - displayName: 'Execute IoT Central unit tests' - - script: pytest -v azext_iot/tests/test_iot_pnp_unit.py --junitxml=junit/test-pnp-unit-results.xml - displayName: 'Execute IoT PnP unit tests' - - script: pytest -v azext_iot/tests/test_iot_digitaltwin_unit.py --junitxml=junit/test-dt-unit-results.xml - displayName: 'Execute IoT DigitalTwin unit tests' - - script: pytest -v azext_iot/tests/iothub/configurations/test_iot_config_unit.py --junitxml=junit/test-config-unit-results.xml - displayName: 'Execute IoT Configuration unit tests' - - script: pytest -v azext_iot/tests/iothub/jobs/test_iothub_jobs_unit.py --junitxml=junit/test-jobs-unit-results.xml - displayName: 'Execute IoT Hub job unit tests' + pathToPublish: .coverage.${{ parameters.name }} + publishLocation: 'Container' + artifactName: 'coverage' - task: PublishTestResults@2 condition: succeededOrFailed() @@ -51,5 +47,5 @@ steps: inputs: testResultsFormat: 'JUnit' testResultsFiles: '**/test-*.xml' - testRunTitle: 'Publish test results for Python ${{ parameters.pythonVersion }} on OS $(Agent.OS)' + testRunTitle: 'Publish ${{ parameters.name }} test results for Python ${{ parameters.pythonVersion }} on OS $(Agent.OS)' searchFolder: '$(System.DefaultWorkingDirectory)' diff --git a/.azure-devops/templates/set-pythonpath.yml b/.azure-devops/templates/set-pythonpath.yml index c30f4ba05..a9ae4302d 100644 --- a/.azure-devops/templates/set-pythonpath.yml +++ b/.azure-devops/templates/set-pythonpath.yml @@ -1,12 +1,11 @@ steps: - task: PythonScript@0 - displayName : 'Extract extension path' - name: 'extractExtensionPath' + displayName : 'Set Extension Path' + name: 'setExtensionPath' inputs: scriptSource: 'inline' script: | from azure.cli.core.extension import get_extension_path - from six import print_ extension_path = get_extension_path("azure-iot") - print_("Extension path is " + extension_path) - print_("##vso[task.setvariable variable=PYTHONPATH;]"+extension_path) + print("Extension path is " + extension_path) + print("##vso[task.setvariable variable=PYTHONPATH;]"+extension_path) diff --git a/.azure-devops/templates/set-testenv-sentinel.yml b/.azure-devops/templates/set-testenv-sentinel.yml new file mode 100644 index 000000000..94950611e --- /dev/null +++ b/.azure-devops/templates/set-testenv-sentinel.yml @@ -0,0 +1,50 @@ +steps: + - task: PythonScript@0 + displayName : 'Set test envs with sentinel values' + name: 'setTestEnvSentinel' + inputs: + scriptSource: 'inline' + script: | + # This task is in place to get around DevOps pipelines env var naming rules which would require + # application code changes. + import os + sentinel_value = "sentinel" + envvars = { + "AZURE_TEST_RUN_LIVE":True, + "azext_iot_testrg": os.environ.get("AZEXT_IOT_TESTRG", sentinel_value), + "azext_iot_testhub": os.environ.get("AZEXT_IOT_TESTHUB", sentinel_value), + "azext_iot_testdps": os.environ.get("AZEXT_IOT_TESTDPS", sentinel_value), + "azext_iot_teststorageuri": os.environ.get("AZEXT_IOT_TESTSTORAGEURI", sentinel_value), + "azext_iot_central_app_id": os.environ.get("AZEXT_IOT_CENTRAL_APP_ID", sentinel_value), + "azext_dt_ep_eventgrid_topic": os.environ.get("AZEXT_DT_EP_EVENTGRID_TOPIC", sentinel_value), + "azext_dt_ep_servicebus_namespace": os.environ.get("AZEXT_DT_EP_SERVICEBUS_NAMESPACE", sentinel_value), + "azext_dt_ep_servicebus_policy": os.environ.get("AZEXT_DT_EP_SERVICEBUS_POLICY", sentinel_value), + "azext_dt_ep_servicebus_topic": os.environ.get("AZEXT_DT_EP_SERVICEBUS_TOPIC", sentinel_value), + "azext_dt_ep_eventhub_namespace": os.environ.get("AZEXT_DT_EP_EVENTHUB_NAMESPACE", sentinel_value), + "azext_dt_ep_eventhub_policy": os.environ.get("AZEXT_DT_EP_EVENTHUB_POLICY", sentinel_value), + "azext_dt_ep_eventhub_topic": os.environ.get("AZEXT_DT_EP_EVENTHUB_TOPIC", sentinel_value), + "azext_dt_ep_rg": os.environ.get("AZEXT_DT_EP_RG", sentinel_value), + } + f = open("./pytest.ini", "w+") + f.write("[pytest]\n") + f.write("junit_family = xunit1\n") + f.write("env = \n") + envvars_sentinel = [" {}={}\n".format(key, val) for key, val in envvars.items()] + f.writelines(envvars_sentinel) + print(envvars_sentinel) + f.close() + + env: + AZEXT_IOT_TESTRG: $(azext_iot_testrg) + AZEXT_IOT_TESTHUB: $(azext_iot_testhub) + AZEXT_IOT_TESTDPS: $(azext_iot_testdps) + AZEXT_IOT_TESTSTORAGEURI: $(azext_iot_teststorageuri) + AZEXT_IOT_CENTRAL_APP_ID: $(azext_iot_central_app_id) + AZEXT_DT_EP_EVENTGRID_TOPIC: $(azext_dt_ep_eventgrid_topic) + AZEXT_DT_EP_SERVICEBUS_NAMESPACE: $(azext_dt_ep_servicebus_namespace) + AZEXT_DT_EP_SERVICEBUS_POLICY: $(azext_dt_ep_servicebus_policy) + AZEXT_DT_EP_SERVICEBUS_TOPIC: $(azext_dt_ep_servicebus_topic) + AZEXT_DT_EP_EVENTHUB_NAMESPACE: $(azext_dt_ep_eventhub_namespace) + AZEXT_DT_EP_EVENTHUB_POLICY: $(azext_dt_ep_eventhub_policy) + AZEXT_DT_EP_EVENTHUB_TOPIC: $(azext_dt_ep_eventhub_topic) + AZEXT_DT_EP_RG: $(azext_dt_ep_rg) diff --git a/.azure-devops/templates/setup-dev-test-env.yml b/.azure-devops/templates/setup-dev-test-env.yml new file mode 100644 index 000000000..cc6c1a875 --- /dev/null +++ b/.azure-devops/templates/setup-dev-test-env.yml @@ -0,0 +1,24 @@ +parameters: + pythonVersion: '' + architecture: '' + runWithAzureCliReleased: 'true' + +steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: ${{ parameters.pythonVersion }} + architecture: ${{ parameters.architecture }} + + - ${{ if eq(parameters.runWithAzureCliReleased, 'false') }}: + - template: install-azure-cli-edge.yml + + - ${{ if eq(parameters.runWithAzureCliReleased, 'true') }}: + - template: install-azure-cli-released.yml + + - template: download-install-local-azure-test-sdk.yml + + - template: setup-ci-machine.yml + + - template: download-install-local-azure-iot-cli-extension.yml + + - template: set-pythonpath.yml \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f491da409..78122037e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,26 @@ # This is a comment. # Each line is a file pattern followed by one or more owners. -* @digimaun \ No newline at end of file +# Global Code Owner(s) +* @digimaun + +# Central Code Owner(s) +azext_iot/central/ @prbans +azext_iot/tests/central/ @prbans +azext_iot/tests/test_iot_central_int.py @prbans +azext_iot/tests/test_iot_central_unit.py @prbans + +# Monitor Code Owner(s) +azext_iot/monitor/ @prbans @digimaun + +# AICS Code Owner(s) +azext_iot/product/ @montgomp @c-ryan-k +azext_iot/tests/product/ @montgomp @c-ryan-k + +# PnP Repository Code Owners(s) +azext_iot/pnp/ @c-ryan-k +azext_iot/tests/pnp/ @c-ryan-k + +# Test Code Owners +azext_iot/tests/test_monitor_parsers_unit.py @prbans @digimaun +azext_iot/tests/test_uamqp_import.py @prbans @digimaun diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 1c6b96da8..000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - - -Thank you for submitting an issue! - -We want you to have an overall smooth dev experience. In order to best help resolve the issue, please provide as much detail as possible. - -Details like Python version, OS version, Shell Type (e.g. bash, cmd.exe, Bash on Windows), CLI core (`az --version`) and IoT Extension (`az extension list`) versions are key in building context around the issue. - ---- diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..7e15e96e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,36 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[bug] " +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment (please complete the following information):** + +- OS: [e.g. Windows 10, Ubuntu 16.04, MacOS X] +- Shell: [e.g. bash, wsl bash, powershell, cmd] +- Az CLI version: [e.g. 2.0.80] +- IoT extension version: [e.g. 0.9.1] +- Python version (if pip installed): [e.g. 3.8.1] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c066459cf..b064e1fcf 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,6 @@ --- This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - Thank you for contributing to the IoT extension! This checklist is used to make sure that common guidelines for a pull request are followed. @@ -10,5 +9,6 @@ This checklist is used to make sure that common guidelines for a pull request ar - [ ] If introducing new functionality or modified behavior, are they backed by unit and integration tests? - [ ] In the same context as above are command names and their parameter definitions accurate? Do help docs have sufficient content? -- [ ] Have **all** unit **and** integration tests passed locally? i.e. `pytest -s /azext_iot/tests` +- [ ] Have **all** unit **and** integration tests passed locally? i.e. `pytest -vv` - [ ] Have static checks passed using the .pylintrc and .flake8 rules? Look at the CI scripts for example usage. +- [ ] Have you made an entry in HISTORY.rst which concisely explains your feature or change? diff --git a/.gitignore b/.gitignore index 27c48d6c7..adc940887 100644 --- a/.gitignore +++ b/.gitignore @@ -187,7 +187,8 @@ _pkginfo.txt # Ignore Visual Studio and Pytest cache *.[Cc]ache *.pytest_cache -pytest.ini +*.ini +!pytest.ini.example # Others ClientBin/ @@ -312,10 +313,7 @@ src/build /doc/_build /doc/sphinx/_build /.vs/config/applicationhost.config -.vscode/settings.json -.vscode/.ropeproject/ -.vscode/tags -.vscode/cSpell.json +.vscode/ .project .pydevproject diff --git a/.pylintrc b/.pylintrc index b7bf67326..62bb79bf5 100644 --- a/.pylintrc +++ b/.pylintrc @@ -11,7 +11,7 @@ ignore=CVS # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. -ignore-patterns= +ignore-patterns=sdk # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). @@ -278,7 +278,10 @@ disable=import-outside-toplevel, xreadlines-attribute, deprecated-sys-function, exception-escape, - comprehension-escape + comprehension-escape, + not-callable, + raise-missing-from, + super-with-arguments # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -337,7 +340,6 @@ enable=syntax-error, bad-super-call, missing-super-argument, no-member, - not-callable, assignment-from-no-return, no-value-for-parameter, too-many-function-args, diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..4a40b52a3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at iotupx@microsoft.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 940afb227..1e2322253 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,97 +1,101 @@ # Contributing -## Development Machine Setup +## Dev Setup -1. Setup Azure CLI Development Environment +1. Get Python 3: https://www.python.org/downloads/ - - Follow the [Azure CLI: Setting up your development environment](https://github.com/Azure/azure-cli/blob/master/doc/configuring_your_machine.md) steps to setup your machine for general Azure CLI development. - - Move on to the next step when you can successfully run `azdev` and see the help message. +### Required Repositories - > Make sure you keep the virtual environment you created above activated while completing following steps. +You must fork and clone the repositories below. Follow the videos and instructions found [here](https://github.com/Azure/azure-cli-dev-tools#setting-up-your-development-environment). -1. Update AZURE_EXTENSION_DIR and PYTHONPATH Environment Variables +1. https://github.com/Azure/azure-cli - By default, CLI extensions are installed to the `~/.azure/cliextensions` directory. For extension development, you'll want to update the AZURE_EXTENSION_DIR environment variable to `~/.azure/devcliextensions`, so your development extensions don't collide with your production extensions. +2. https://github.com/Azure/azure-iot-cli-extension - You will also need to add both the project root and and devcliextensions directory to PYTHONPATH. +> IMPORTANT: When cloning the repositories and environments, ensure they are all siblings to each other. This makes things much easier down the line. - 1. Navigate to the root of this repo on your local machine: +``` +source-directory/ +|-- azure-cli/ +|-- azure-iot-cli-extension/ +|-- .env3/ +``` - ``` - cd << clone root >> - ``` +> IMPORTANT: Ensure you keep the Python virtual environment you created above. It is required for development. - 1. Run the following script to set the environment variables. +After following the videos, ensure you have: - **Windows:** +1. Python virtual environment - ``` - set EXTENSION_PATH=%USERPROFILE%\.azure\devcliextensions\ - mkdir %EXTENSION_PATH% - set AZURE_EXTENSION_DIR=%EXTENSION_PATH% - set PYTHONPATH=%PYTHONPATH%;%EXTENSION_PATH%azure-iot;%CD% - ``` - **Linux:** +2. Functional development az cli - ``` - export EXTENSION_PATH=~/.azure/devcliextensions/ - mkdir -p $EXTENSION_PATH - echo $"export AZURE_EXTENSION_DIR=$EXTENSION_PATH" >> ~/.bash_profile - echo $"export PYTHONPATH=$PYTHONPATH:${EXTENSION_PATH}azure-iot:$(pwd)" >> ~/.bash_profile - ``` +#### Environment Variables -1. Install Extension +It is recommended that you set the following environment variables in a way such that they are persisted through machine restarts. - 1. Navigate to the root of this repo on your local machine: +You can run this setup in `bash` or `cmd` environments, this documentation just show the `powershell` flavor. - ``` - cd << clone root >> +1. Create a directory for your development extensions to live in + + ```powershell + mkdir path/to/source/extensions/azure-iot ``` - 1. Install the Extension +2. Set `AZURE_EXTENSION_DIR` to the following - **Windows:** - ``` - pip install -U --target %AZURE_EXTENSION_DIR%/azure-iot . + ```powershell + $env:AZURE_EXTENSION_DIR="path/to/source/extensions" ``` - **Linux:** - ``` - pip install -U --target $AZURE_EXTENSION_DIR/azure-iot . +3. Set `PYTHONPATH` to the following. Order matters here so be careful. + + ```powershell + $env:PYTHONPATH="path/to/source/azure-iot-cli-extension;path/to/source/extensions/azure-iot" ``` -1. Verify Setup +Restart any PowerShell windows you may have open and reactivate your python environment. Check that the environment variables created above have persisted. - Run the following command to view installed extensions: +#### azdev Steps - `az --debug` +Similar to the video, just execute the following command. - That will output which directory is being used to load extensions and it will show that the `azure-iot` extension has been loaded. +```powershell +azdev setup -c path/to/source/azure-cli +``` - ``` - Extensions directory: '...\.azure\devcliextensions\' - Found 1 extensions: ['azure-iot'] +#### Install dev extension + +1. Change directories + + ```powershell + cd path/to/source/azure-iot-cli-extension ``` -Please use `az --debug` if you run into any issues, or file an issue in this GitHub repo. +2. Install the extension (should only be needed once) -Please refer to the [Azure CLI Extension Guide](https://github.com/Azure/azure-cli/tree/master/doc/extensions) for further information and help developing extensions. + ```powershell + pip install -U --target path/to/source/extensions/azure-iot . + ``` -### Running Tests +#### Verify environment is setup correctly -1. Install Dependencies +Run a command that is present in the iot extension space - This project utilizes the following: [pytest](https://docs.pytest.org/en/latest/), [unittest](https://docs.python.org/3.6/library/unittest.html), `pytest-mock`, and `pytest-cov`. +```powershell +az iot central app -h +``` - Run the following to install them: +If this works, then you should now be able to make changes to the extension and have them reflected immediately in your az cli. - `pip install -r dev_requirements` +## Unit and Integration Testing -1. Activate Virtual Environment +### Unit Tests - Ensure that the virtual environment you created while setting up your machine for general Azure CLI development is activated and the dev_setup.py script has been run. +You may need to install the dev_requirements for this -#### Unit Tests +```powershell +pip install -r path/to/source/dev_requirements +``` _Hub:_ `pytest azext_iot/tests/test_iot_ext_unit.py` @@ -99,19 +103,24 @@ _Hub:_ _DPS:_ `pytest azext_iot/tests/test_iot_dps_unit.py` -#### Integration Tests +### Integration Tests Integration tests are run against Azure resources and depend on environment variables. -##### Azure Resource Setup +#### Azure Resource Setup 1. Create IoT Hub -> IMPORTANT: Your IoT Hub must be created specifically for integration tests and must not contain any devices when the tests are run. -1. Create Files Storage - In IoT Hub, click Files, create a new Storage Account and link to an empty Container. -1. Create IoT Hub Device Provisioning Service (DPS) -1. Link IoT Hub to DPS - From DPS, click "Linked IoT Hub" and link the IoT Hub you just created. -##### Environment Variables + > IMPORTANT: Your IoT Hub must be created specifically for integration tests and must not contain any devices when the tests are run. + +2. Create Files Storage - In IoT Hub, click Files, create a new Storage Account and link to an empty Container. + +3. Create IoT Hub Device Provisioning Service (DPS) + +4. Link IoT Hub to DPS - From DPS, click "Linked IoT Hub" and link the IoT Hub you just created. + +#### Integration Test Environment Variables + You can either manually set the environment variables or use the `pytest.ini.example` file in the root of the extension repo. To use that file, rename it to `pytest.ini`, open it and set the variables as indicated below. ``` @@ -121,10 +130,12 @@ You can either manually set the environment variables or use the `pytest.ini.exa azext_iot_testhub_cs="IoT Hub Connection String" azext_iot_testdps="IoT Hub DPS Name" azext_iot_teststorageuri="Blob Container SAS Uri" + azext_iot_identity_teststorageid="Storage Account ID" ``` `azext_iot_teststorageuri` is optional and only required when you want to test device export and file upload functionality. You can generate a SAS Uri for your Blob container using the [Azure Storage Explorer](https://azure.microsoft.com/en-us/features/storage-explorer/). You must also configure your IoT Hub's File Upload storage container via the Azure Portal for this test to pass. +`azext_iot_identity_teststorageid` is optional and only required when you want to test Identity-Based device export and file upload functionality. During this test, your hub will be assigned a System-Assigned AAD identity, and will be granted the role of "Storage Blob Data Contributor" on the storage account you provide. Both the hub's identity and the RBAC role will be removed once the test completes. ##### IoT Hub @@ -132,7 +143,6 @@ Execute the following command to run the IoT Hub integration tests: `pytest azext_iot/tests/test_iot_ext_int.py` - ##### Device Provisioning Service Execute the following command to run the IoT Hub DPS integration tests: @@ -145,7 +155,120 @@ Execute the following command to run both Unit and Integration tests and output `pytest -v . --cov=azext_iot --cov-config .coveragerc` -#### Microsoft CLA +#### Formatting and Linting + +The repo uses the linter in `azdev`. + +To install the required version of azdev, run this command: + +```powershell +pip install -e "git+https://github.com/Azure/azure-cli@dev#egg=azure-cli-dev-tools&subdirectory=tools" +``` + +To run the linter, run this command: + +```powershell +azdev cli-lint --ci --extensions azure-iot +``` + +We use our flake8 and pylint rules. We recommend you set up your IDE as per the VSCode setup below for best compliance. + +We are also starting to use `python black`. To set this up on VSCode, see the following blog post. + +https://medium.com/@marcobelo/setting-up-python-black-on-visual-studio-code-5318eba4cd00 + +## Optional + +### VSCode setup + +1. Install VSCode + +2. Install the required extensions + * ([ms-python.python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) is recommended) + +3. Set up `settings.json` + + ```json + { + "python.pythonPath": "path/to/source/env3/Scripts/python.exe", + "python.venvPath": "path/to/source/", + "python.linting.pylintEnabled": true, + "python.autoComplete.extraPaths": [ + "path/to/source/env3/Lib/site-packages" + ], + "python.linting.flake8Enabled": true, + "python.linting.flake8Args": [ + "--config=setup.cfg" + ], + "files.associations": { + "*/.azure-devops/.yml": "azure-pipelines" + } + } + ``` + +4. Set up `launch.json` + + ```json + { + "version": "0.2.0", + "configurations": [ + { + "name": "Azure CLI Debug (Integrated Console)", + "type": "python", + "request": "launch", + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceRoot}/../azure-cli/src/azure-cli/azure/cli/__main__.py", + "cwd": "${workspaceRoot}", + "args": [ + "--help" + ], + "console": "integratedTerminal", + "debugOptions": [ + "WaitOnAbnormalExit", + "WaitOnNormalExit", + "RedirectOutput" + ], + "justMyCode": false + } + ] + } + ``` + + * launch.json was derived from [this](https://raw.githubusercontent.com/Azure/azure-cli/dev/.vscode/launch.json) file + + * Note: your "program" path might be different if you did not set up the folder structure as siblings as recommended above + + * Note: when passing args, ensure they are all comma separated. + + Correct: + + ```json + "args": [ + "--a", "value", "--b", "value" + ], + ``` + + Incorrect: + + ```json + "args": [ + "--a value --b value" + ], + ``` + +5. Set up python black. + +6. You should now be able to place breakpoints in VSCode and see execution halt as the code hits them. + +### Python debugging + +https://docs.python.org/3/library/pdb.html + +1. `pip install pdb` +2. If you need a breakpoint, put `import pdb; pdb.set_trace()` in your code +3. Run your command, it should break execution wherever you put the breakpoint. + +## Microsoft CLA This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us @@ -153,4 +276,4 @@ the rights to use your contribution. For details, visit https://cla.microsoft.co When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. \ No newline at end of file +provided by the bot. You will only need to do this once across all repos using our CLA. diff --git a/CredScanSuppressions.json b/CredScanSuppressions.json new file mode 100644 index 000000000..20e6d7c60 --- /dev/null +++ b/CredScanSuppressions.json @@ -0,0 +1,37 @@ +{ + "tool": "Credential Scanner", + "suppressions": [ + { + "file": "azext_iot\\operations\\_mqtt.py", + "placeholder": "password=sas", + "_justification": "Client device simulation requires a SAS key extracted from IoT Hub to be passed in MQTT lib." + }, + { + "file": "azext_iot\\tests\\iothub\\test_iothub_discovery_unit.py", + "placeholder": "SharedAccessKey=AB+c/+5nm2XpDXcffhnGhnxz/TVF4m5ag7AuVIGwchj=", + "_justification": "Completely made up IoT Hub policy key for unit test." + }, + { + "file": "azext_iot\\tests\\iothub\\configurations\\test_edge_deployment.json", + "_justification": "Completely made up deployment with fake container registry creds." + }, + { + "file": "azext_iot\\tests\\conftest.py", + "_justification": "Completely made up keys for unit tests." + }, + { + "file": "azext_iot\\tests\\dps\\test_iot_dps_int.py", + "placeholder": "cT/EXZvsplPEpT//p98Pc6sKh8mY3kYgSxavHwMkl7w=", + "_justification": "Ensure made up endorsement key evaluates to the expected device key." + }, + { + "file": "azext_iot\\tests\\dps\\test_iot_dps_unit.py", + "_justification": "Completely made up keys for unit tests." + }, + { + "file": "azext_iot\\tests\\iothub\\test_iot_ext_unit.py", + "placeholder": "+XLy+MVZ+aTeOnVzN2kLeB16O+kSxmz6g3rS6fAf6rw=", + "_justification": "Ensure made up policy key generates the proper SAS token." + } + ] +} \ No newline at end of file diff --git a/HISTORY.rst b/HISTORY.rst index 176e1f046..0b3aa6a3c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,396 @@ Release History =============== +0.10.11 ++++++++++++++++ + +**IoT Hub updates** + +* Fixed an issue where an explicit json null could not be sent for the following commands: + + * az iot hub invoke-device-method + * az iot hub invoke-module-method + +**Azure Digital Twins updates** + +* Fixed an issue in the following update commands where malformed json patch content would not raise an error + causing the process to call the respective service endpoint with a request payload containing an empty array. + + * az dt twin update + * az dt twin relationship update + * az dt twin component update + +**IoT Central updates** + +Placeholder + + +0.10.10 ++++++++++++++++ + +**Azure Digital Twins updates** + +* Addition of the optional '--etag' argument for the following commands: + + * az dt twin [update | delete] + * az dt twin relationship [update | delete] + +* Addition of the optional '--if-not-match' switch for the following commands: + + * az dt twin create + * az dt twin relationship create + + +0.10.9 ++++++++++++++++ + +**Azure IoT Product Certification service updates** + +* Fix bug for `az iot product test create` sending a byte string instead of "regular" base64 string. + +**Azure Digital Twins updates** + +* Addition of Digital Twins Identity support focused around Managed Service Identity (MSI) and Identity based endpoint integration. +* Addition of Digital Twins networking functionality around private-links and private-endpoint connections. See "az dt network". + +**IoT Hub updates** + +* Improve http debug logging. +* Fix bug related to issue #296. Adds a clause to device-identity update that allows user to update primary-key / secondary-key + and primary-thumbprint / secondary-thumbprint values (respectively, per auth method) without needing to specify the auth_method in the update command. + + +0.10.8 ++++++++++++++++ + +**IoT Central updates** + +* az iot central device|device-template|api-token|diagnostic help strings updated with improved language. +* update parsing template logic to support DTDLV2 models. +* remove deprecated commands 1) iot central app device-twin 2) iot central app monitor-events + + +**IoT Hub updates** + +The following commands support an explicit etag parameter. If no etag arg is passed the value "*" is used. + +* az iot hub device-identity update +* az iot hub device-identity delete +* az iot hub device-identity renew-key +* az iot hub device-twin update +* az iot hub device-twin delete +* az iot hub module-identity update +* az iot hub module-identity delete +* az iot hub module-twin update +* az iot hub module-twin delete +* az iot hub configuration update +* az iot hub configuration delete +* az iot edge deployment update +* az iot edge deployment update + +Re-introduce prior in-preview IoT Hub device digital twin/pnp runtime commands under the "az iot hub digital-twin" root command group. + +* az iot hub digital-twin show +* az iot hub digital-twin update +* az iot hub digital-twin invoke-command + + +0.10.7 ++++++++++++++++ + +**IoT Hub updates** + +* Change command name from az iot hub device-identity `regenerate-key` to `renew-key` to better align with az cli core verbs. + + +0.10.6 ++++++++++++++++ + +**Azure IoT Product Certification service** + +* Fix bug for `az iot product test create` not specifying query parameter "GenerateProvisioningConfiguration" appropriately. + + +**IoT Hub updates** + +* SDK refresh. IoT Hub service calls point to api-version 2020-09-30. + +* Updated nested edge (edge offline) commands to support parentScopes. + + Set of changes + + * 'az iot hub device-identity get-parent' is deprecated use 'az iot hub device-identity parent show' instead. Deprecated command group is planned to be removed by December 2021 + * 'az iot hub device-identity set-parent' is deprecated use 'az iot hub device-identity parent set' instead. Deprecated command is planned to be removed by December 2021 + * 'az iot hub device-identity add-children' is deprecated use 'az iot hub device-identity children add' instead. Deprecated command group is planned to be removed by December 2021 + * 'az iot hub device-identity remove-children' is deprecated use 'az iot hub device-identity children remove' instead. Deprecated command is planned to be removed by December 2021 + * 'az iot hub device-identity list-children' is deprecated use 'az iot hub device-identity children list' instead. Deprecated command group is planned to be removed by December 2021 + + +0.10.5 ++++++++++++++++ + +**Azure Digital Twins updates** + +* Breaking change on the `--tags` parameter for `az dt create`. The prior input format of --tags "a=b;c=d" has been + changed to --tags a=b c=d to be more consistent with other Az CLI tag formats. + + +0.10.4 ++++++++++++++++ + +**General updates** + +* IoT extension installation constrained to Python 3.6 or greater. + +**Azure Digital Twins updates** + +* ADT GA updates and release. + +**IoT Edge** + +* Validation schema updated with $edgeHub 1.1 route option. +* Introduces `--no-validation` to skip client side schema based validation for edge deployment creation. + + +0.10.3 ++++++++++++++++ + +**General updates** + +* Python 3.5 support will soon be dropped corresponding with the official end of life date. +* Formal python requires constraint added to constrain installs to Py 3.5+. + +**IoT Plug-and-Play updates** + +* The in preview `az iot pnp` command group has been removed. PnP CLI functionality will be re-imagined at a future point in time. + + +0.10.2 ++++++++++++++++ + +**IoT Hub updates** + +* Adds `az iot hub device-identity regenerate-key`. + + +0.10.1 ++++++++++++++++ + +**IoT Plug-and-Play updates** + +* Regenerated PnP runtime SDK to API version 2020-09-30 +* All `az iot pnp` commands still remain under preview and are subject to change or deletion. + +**IoT Hub updates** + +* All configuration/edge deployment list operations no longer have a default top. By default all configuration entities will be returned. + Existing --top input should not be affected. + + +0.10.0 ++++++++++++++++ + +**IoT Hub updates** + +* Add convenience arguments for device update. + +**IoT DPS updates** + +* Added --show-keys argument to `dps enrollment show` and `dps enrollment-group show` to include full attestation information for symmetric key enrollments and enrollment groups +* Regenerated 2019-03-31 DPS Service SDK + +**Breaking Changes** + +* `az iot dps enrollment show` and `az iot dps enrollment-group show` now return raw service results instead of deserialized models. + This means that some properties that were previously returned as `null` for these commands will no longer be returned, possibly causing a breaking change. + + +0.9.9 ++++++++++++++++ + +**IoT DPS updates** + +* Introduces 'az iot dps compute-device-key' preview command to generate derived device SAS key + +**IoT Central updates** + +* Introduces 'az iot central diagnostics' preview command group to perform application and device level diagnostics +* Introduces 'az iot central device compute-device-key' preview command to generate derived device SAS key + +* This release involves a re-grouping of IoT Central commands. + + Set of changes for GA commands + + * 'az iot central app device-twin' is deprecated use 'az iot central device twin' instead. Deprecated command group is planned to be removed by December 2020 + * 'az iot central app monitor-events' is deprecated use 'az iot central diagnostics monitor-events' instead. Deprecated command is planned to be removed by December 2020 + + Set of changes for preview commands + + * 'az iot central app device registration-summary' moved to 'az iot central diagnostics registration-summary' + * 'az iot central app monitor-properties' moved to 'az iot central diagnostics monitor-properties' + * 'az iot central app validate-messages' moved to 'az iot central diagnostics validate-messages' + * 'az iot central app validate-properties' moved to 'az iot central diagnostics validate-properties' + * 'az iot central diagnostics monitor-events' added to support deprecation of 'az iot central app monitor-events' + * 'az iot central app device run-command' moved to 'az iot central device command run' + * 'az iot central app device show-command-history' moved to 'az iot central device command history' + * 'az iot central device twin' added to support deprecation of 'az iot central app device-twin' command group + +**IoT Hub updates** + +Cloud-to-Device message enhancements + +* Introduced new `az iot device c2d-message purge` command to purge the message queue for a device. +* Added message ack arguments to `az iot c2d-message receive` to ack the message after it is received: + + * Options are `--complete`, `--abandon`, and `--reject`, and only one can be used per command. + * `az iot device c2d-message receive` with no ack arguments remains unchanged and will not ack the message. + +Edge device creation enhancements + +* Enabled x509 certificate authentication types (`x509_thumbprint` and `x509_ca`) for edge device creation with `az iot hub device-identity create --ee` + +Bug fixes + +* Fixes issue #243 where providing a connection string via --login still required "az login". + +**Digital Twins updates** + +The following command groups support passing in a DT instance hostname directly. + + * az dt route + * az dt model + * az dt twin + +* Like before, if an instance name is provided, the user subscription is first queried for the target instance to retrieve the hostname. +* If a hostname is provided, the subscription query is skipped and the provided value is used for subsequent interaction. + + +0.9.8 ++++++++++++++++ +General changes + +* Starting with v0.9.8 of the IoT extension, the minCliCoreVersion has been bumped to 2.3.1. This sets a comfortable minimum desired experience we want for our users. + +Introducing preview commands for the Azure IoT Product Certification service + +* A new IoT root command group 'az iot product' has been added + + * Use 'az iot product requirement' to manage product certification requirements + * Use 'az iot product test' to manage device tests for certification + + * The product test command group encompasses test cases, runs and tasks + +IoT Central updates + +* Introduces the 'az iot central app user' preview command group for managing application users and service principals +* Introduces the 'az iot central app api-token' preview command group for managing application api tokens +* Removal of deprecated command groups and commands + +IoT Hub updates + +* All "... show-connection-string" based commands are deprecated in favor of "... connection-string show" canonical Az CLI style. + + * The show connection string command for a target IoT Hub has moved to the IoT extension. + * 'az iot hub connection-string show' supports a --default-eventhub flag which indicates the operation will construct a connection string for the default eventhub endpoint of the target IoT Hub. +* Export/Import device identity commands support reading blob container SAS URI's via file + +Azure Digital Twins updates + +* The 'location' argument for 'az dt create' is now optional. If no location is provided, the location of the target resource group is used. + + +0.9.7 ++++++++++++++++ +Refreshes commands for the Azure IoT Plug & Play summer refresh + +* The existing Plug & Play preview commands across Azure CLI and the IoT extension have been removed and replaced with a completely new commands. If you still need the legacy preview experience, then you can leverage older versions of the CLI and extension. +* The new commands exist entirely in the extension with the following command groups: + + * az iot pnp repo ## For tenant repository configuration + * az iot pnp model ## For managing repository models and related content + * az iot pnp role-assignment ## For managing role assignments for model repo assets + * az iot pnp twin ## For interacting with the digital twin of a Plug & Play device + +Introduces new preview Azure IoT Central commands + +* az iot central app monitor-properties +* az iot central app validate-properties +* az iot central app device run-command +* az iot central app device show-command-history +* az iot central app device show-credentials + +Device Provisioning Service update + +* DPS enrollments now support the custom allocation policy resolving issue #200 + +0.9.6 ++++++++++++++++ +* Fixes event monitor initialization issue. + +0.9.5 ++++++++++++++++ +* IoT Hub commands now support dynamic privileged policy discovery. `iothubhowner` is no longer relied on. Instead any policy that has `RegistryWrite`, `ServiceConnect` and `DeviceConnect` permissions will be used. +* Monitoring commands (such as for `central` or `hub`) support module Id filter. Also it is more clear that an event comes from a module. +* Improved validation of central telemetry. +* Digital Twin endpoint create commands now support custom subscription options. + +0.9.4 ++++++++++++++++ +Azure Digital Twins Public Preview - CLI release + +Introducing 35 new commands in the following command groups: + +* az dt +* az dt endpoint +* az dt model +* az dt role-assignment +* az dt route +* az dt twin +* az dt twin relationship +* az dt twin telemety + +0.9.3 ++++++++++++++++ +* IoT Hub device identity import/export commands support usage via managed service identity using the --auth-type argument. + +* Adds preview command group "az iot central app device" + + * Adds preview command "az iot central app device create" + * Adds preview command "az iot central app device show" + * Adds preview command "az iot central app device list" + * Adds preview command "az iot central app device delete" + * Adds preview command "az iot central app device registration-info" + * Adds preview command "az iot central app device registration-summary" + +* Adds preview command group "az iot central app device-template" + + * Adds preview command "az iot central app device-template create" + * Adds preview command "az iot central app device-template show" + * Adds preview command "az iot central app device-template list" + * Adds preview command "az iot central app device-template delete" + * Adds preview command "az iot central app device-template map" + +* Changed how results are displayed in "az iot central app validate-messages" + +Known issues + +* The following preview commands will retrieve at most 25 results + + * az iot central app device list + * az iot central app device-template list + * az iot central app device-template map + +0.9.2 ++++++++++++++++ +* Device and module twin update operations provide explicit patch arguments (--desired, --tags). +* Adds command "az iot central app validate-messages" +* Remove Py 2.7 support and remnants from setup manifest. +* Remove Py 3.4 support and remnants from setup manifest. + +0.9.1 ++++++++++++++++ +* Adds edge configuration argument for creating or updating enrollment[groups] + 0.9.0 +++++++++++++++ * Breaking change: Evaluating an edge deployment/hub configuration SYSTEM metric (via show-metric) will return non-manipulated query output. @@ -14,7 +404,6 @@ Release History * Extension package name has been changed to 'azure-iot'. * Help text for ADM module configurations has been updated with proper target condition syntax for module criteria. - 0.8.9 +++++++++++++++ * Updated uamqp version to ~1.2. @@ -200,27 +589,27 @@ Release History * Significant restructing of CLI, prioritizes pure Python solutions where possible * Provides IoT Edge capabilities * Adds following new commands: -* iot query -* iot device show -* iot device list -* iot device create -* iot device update -* iot device delete -* iot device twin show -* iot device twin update -* iot device module show -* iot device module list -* iot device module create -* iot device module update -* iot device module delete -* iot device module twin show -* iot device module twin update -* iot device module twin replace -* iot configuration apply -* iot configuration create -* iot configuration update -* iot configuration delete -* iot configuration show +* iot query +* iot device show +* iot device list +* iot device create +* iot device update +* iot device delete +* iot device twin show +* iot device twin update +* iot device module show +* iot device module list +* iot device module create +* iot device module update +* iot device module delete +* iot device module twin show +* iot device module twin update +* iot device module twin replace +* iot configuration apply +* iot configuration create +* iot configuration update +* iot configuration delete +* iot configuration show * iot configuration list * Bug fixes @@ -235,6 +624,6 @@ Release History * Show and update device twin * Invoke device method * Device simulation -* Hub message send (Cloud-to-device) +* Hub message send (Cloud-to-device) * New device message send (Device-to-cloud) supports http, amqp, mqtt * Get SAS token diff --git a/README.md b/README.md index cbe5ea0c1..8378708a6 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,36 @@ # Microsoft Azure IoT extension for Azure CLI ![Python](https://img.shields.io/pypi/pyversions/azure-cli.svg?maxAge=2592000) -[![Build Status](https://dev.azure.com/azure/azure-iot-cli-extension/_apis/build/status/Merge%20-%20Azure.azure-iot-cli-extension?branchName=dev)](https://dev.azure.com/azure/azure-iot-cli-extension/_build/latest?definitionId=49&branchName=dev) +![Build Status](https://dev.azure.com/azureiotdevxp/aziotcli/_apis/build/status/Merge%20-%20Azure.azure-iot-cli-extension?branchName=dev) The **Azure IoT extension for Azure CLI** aims to accelerate the development, management and automation of Azure IoT solutions. It does this via addition of rich features and functionality to the official [Azure CLI](https://docs.microsoft.com/en-us/cli/azure). +## News + +The legacy IoT extension Id `azure-cli-iot-ext` is deprecated in favor of the new modern Id `azure-iot`. `azure-iot` is a superset of `azure-cli-iot-ext` and any new features or fixes will apply to `azure-iot` only. Also the legacy and modern IoT extension should **never** co-exist in the same CLI environment. + +Uninstall the legacy extension with the following command: `az extension remove --name azure-cli-iot-ext`. + +Related - if you see an error with a stacktrace similar to: +``` +... +azure-cli-iot-ext/azext_iot/common/_azure.py, ln 90, in get_iot_hub_connection_string + client = iot_hub_service_factory(cmd.cli_ctx) +cliextensions/azure-cli-iot-ext/azext_iot/_factory.py, ln 29, in iot_hub_service_factory + from azure.mgmt.iothub.iot_hub_client import IotHubClient +ModuleNotFoundError: No module named 'azure.mgmt.iothub.iot_hub_client' +``` + +The resolution is to remove the deprecated `azure-cli-iot-ext` and install any version of the `azure-iot` extension. + ## Commands -Please refer to the official `az iot` page on [Microsoft Docs](https://docs.microsoft.com/en-us/cli/azure/ext/azure-cli-iot-ext) for a complete list of supported commands. You can also find IoT CLI usage tips on the [wiki](https://github.com/Azure/azure-iot-cli-extension/wiki/Tips). +Please refer to the official `az iot` reference on [Microsoft Docs](https://docs.microsoft.com/en-us/cli/azure/ext/azure-iot/iot) for a complete list of supported commands. You can also find IoT CLI usage tips on the [wiki](https://github.com/Azure/azure-iot-cli-extension/wiki/Tips). ## Installation 1. Install the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) - - You must have at least `v2.0.70`, which you can verify with `az --version` + - You must have at least `v2.3.1`, which you can verify with `az --version` 1. Add, Update or Remove the IoT extension with the following commands: - Add: `az extension add --name azure-iot` - Update: `az extension update --name azure-iot` @@ -22,9 +40,106 @@ Please refer to the [Installation Troubleshooting Guide](docs/install-help.md) i ## Usage -After installing the Azure IoT extension your CLI environment is augmented with the addition of `central`, `device`, `dps`, `dt`, `edge`, `hub` and `pnp` commands. +After installing the Azure IoT extension your CLI environment is augmented with the addition of `hub`, `central`, `dps`, `dt`, `edge` and `device` commands. + +For usage and help content of any command or command group, pass in the `-h` parameter. Root command group details are shown for the following IoT services. + +> **Click** a section to expand or collapse + +
+ Digital Twins + +``` +$ az dt -h +Group + az dt : Manage Azure Digital Twins solutions & infrastructure. + This command group is in preview. It may be changed/removed in a future release. +Subgroups: + endpoint : Manage and configure Digital Twins instance endpoints. + model : Manage DTDL models and definitions on a Digital Twins instance. + role-assignment : Manage RBAC role assignments for a Digital Twins instance. + route : Manage and configure event routes. + twin : Manage and configure the digital twins of a Digital Twins instance. + +Commands: + create : Create a new Digital Twins instance. + delete : Delete an existing Digital Twins instance. + list : List the collection of Digital Twins instances by subscription or resource + group. + show : Show an existing Digital Twins instance. +``` +
+ +
+ IoT Central + +``` +$ az iot central -h +Group + az iot central : Manage IoT Central resources. + IoT Central is an IoT application platform that reduces the burden and cost of developing, + managing, and maintaining enterprise-grade IoT solutions. Choosing to build with IoT Central + gives you the opportunity to focus time, money, and energy on transforming your business + with IoT data, rather than just maintaining and updating a complex and continually evolving + IoT infrastructure. + IoT Central documentation is available at https://aka.ms/iotcentral-documentation. + +Subgroups: + api-token [Preview] : Create and Manage API tokens. + app : Manage IoT Central applications. + device [Preview] : Manage and configure IoT Central devices. + device-template [Preview] : Manage and configure IoT Central device templates. + diagnostics [Preview] : Perform application and device level diagnostics. + user [Preview] : Manage and configure IoT Central users. + +For more specific examples, use: az find "az iot central" +``` +
+ +
+ IoT Device Provisioning + +``` +$ az iot dps -h +Group + az iot dps : Manage entities in an Azure IoT Hub Device Provisioning Service. Augmented with the + IoT extension. + +Subgroups: + access-policy : Manage Azure IoT Hub Device Provisioning Service access policies. + certificate : Manage Azure IoT Hub Device Provisioning Service certificates. + enrollment : Manage enrollments in an Azure IoT Hub Device Provisioning Service. + enrollment-group : Manage Azure IoT Hub Device Provisioning Service. + linked-hub : Manage Azure IoT Hub Device Provisioning Service linked IoT hubs. + registration : Manage Azure IoT Hub Device Provisioning Service registrations. + +Commands: + create : Create an Azure IoT Hub device provisioning service. + delete : Delete an Azure IoT Hub device provisioning service. + list : List Azure IoT Hub device provisioning services. + show : Get the details of an Azure IoT Hub device provisioning service. + update : Update an Azure IoT Hub device provisioning service. +``` +
+ +
+ IoT Edge + +``` +$ az iot edge -h +Group + az iot edge : Manage IoT solutions on the Edge. + +Subgroups: + deployment : Manage IoT Edge deployments at scale. + +Commands: + set-modules : Set edge modules on a single device. +``` +
-For usage and help content for any command or command group, pass in the `-h` parameter, for example: +
+ IoT Hub ``` $ az iot hub -h @@ -33,13 +148,14 @@ Group Subgroups: certificate : Manage IoT Hub certificates. - configuration : Manage IoT device configurations at scale. + configuration : Manage IoT automatic device management configuration at scale. + connection-string : Manage IoT Hub connection strings. consumer-group : Manage the event hub consumer groups of an IoT hub. device-identity : Manage IoT devices. device-twin : Manage IoT device twin configuration. devicestream : Manage device streams of an IoT hub. distributed-tracing [Preview] : Manage distributed settings per-device. - job : Manage jobs in an IoT hub. + job : Manage IoT Hub jobs (v2). message-enrichment : Manage message enrichments for endpoints of an IoT Hub. module-identity : Manage IoT device modules. module-twin : Manage IoT device module twin configuration. @@ -67,6 +183,7 @@ Commands: show-stats : Get the statistics for an IoT hub. update : Update metadata for an IoT hub. ``` +
## Scenario Automation diff --git a/azext_iot/__init__.py b/azext_iot/__init__.py index 4d2cc95d4..f724bb485 100644 --- a/azext_iot/__init__.py +++ b/azext_iot/__init__.py @@ -9,53 +9,46 @@ from azext_iot._factory import iot_service_provisioning_factory from azext_iot.constants import VERSION import azext_iot._help # noqa: F401 +from azext_iot.product.command_map import load_product_commands -iothub_ops = CliCommandType( - operations_tmpl='azext_iot.operations.hub#{}' -) - -iothub_ops_job = CliCommandType( - operations_tmpl='azext_iot.iothub.job_commands#{}' -) - -iothub_ops_device = CliCommandType( - operations_tmpl='azext_iot.iothub.device_commands#{}' -) - +iothub_ops = CliCommandType(operations_tmpl="azext_iot.operations.hub#{}") iotdps_ops = CliCommandType( - operations_tmpl='azext_iot.operations.dps#{}', - client_factory=iot_service_provisioning_factory -) - -iotcentral_ops = CliCommandType( - operations_tmpl='azext_iot.operations.central#{}' -) - -iotdigitaltwin_ops = CliCommandType( - operations_tmpl='azext_iot.operations.digitaltwin#{}' -) - -iotpnp_ops = CliCommandType( - operations_tmpl='azext_iot.operations.pnp#{}' + operations_tmpl="azext_iot.operations.dps#{}", + client_factory=iot_service_provisioning_factory, ) class IoTExtCommandsLoader(AzCommandsLoader): - def __init__(self, cli_ctx=None): super(IoTExtCommandsLoader, self).__init__(cli_ctx=cli_ctx) def load_command_table(self, args): from azext_iot.commands import load_command_table + from azext_iot.iothub.command_map import load_iothub_commands + from azext_iot.central.command_map import load_central_commands + from azext_iot.digitaltwins.command_map import load_digitaltwins_commands + load_command_table(self, args) - from azext_iot.iothub.command_bindings import load_iothub_commands load_iothub_commands(self, args) + load_central_commands(self, args) + load_digitaltwins_commands(self, args) + load_product_commands(self, args) + return self.command_table def load_arguments(self, command): from azext_iot._params import load_arguments + from azext_iot.iothub.params import load_iothub_arguments + from azext_iot.central.params import load_central_arguments + from azext_iot.digitaltwins.params import load_digitaltwins_arguments + from azext_iot.product.params import load_product_params + load_arguments(self, command) + load_iothub_arguments(self, command) + load_central_arguments(self, command) + load_digitaltwins_arguments(self, command) + load_product_params(self, command) COMMAND_LOADER_CLS = IoTExtCommandsLoader diff --git a/azext_iot/_factory.py b/azext_iot/_factory.py index d302f973c..98fcbbf44 100644 --- a/azext_iot/_factory.py +++ b/azext_iot/_factory.py @@ -10,15 +10,14 @@ from azext_iot.common.sas_token_auth import SasTokenAuthentication from azext_iot.common.shared import SdkType +from azext_iot.constants import USER_AGENT from msrestazure.azure_exceptions import CloudError __all__ = [ + "SdkResolver", "CloudError", "iot_hub_service_factory", "iot_service_provisioning_factory", - "SdkResolver", - "_bind_sdk", - "_get_sdk_exception_type" ] @@ -35,14 +34,9 @@ def iot_hub_service_factory(cli_ctx, *_): working with IoT Hub. """ from azure.cli.core.commands.client_factory import get_mgmt_service_client + from azure.cli.core.profiles import ResourceType - # To support newer and older IotHubClient. 0.9.0+ has breaking changes. - try: - from azure.mgmt.iothub import IotHubClient - except: - # For <0.9.0 - from azure.mgmt.iothub.iot_hub_client import IotHubClient - return get_mgmt_service_client(cli_ctx, IotHubClient).iot_hub_resource + return get_mgmt_service_client(cli_ctx, ResourceType.MGMT_IOTHUB).iot_hub_resource def iot_service_provisioning_factory(cli_ctx, *_): @@ -59,13 +53,15 @@ def iot_service_provisioning_factory(cli_ctx, *_): """ from azure.cli.core.commands.client_factory import get_mgmt_service_client from azure.mgmt.iothubprovisioningservices.iot_dps_client import IotDpsClient + return get_mgmt_service_client(cli_ctx, IotDpsClient) class SdkResolver(object): - def __init__(self, target, device_id=None): + def __init__(self, target, device_id=None, auth_override=None): self.target = target self.device_id = device_id + self.auth_override = auth_override # This initialization will likely need to change to support more variation of SDK self.sas_uri = self.target["entity"] @@ -75,75 +71,53 @@ def __init__(self, target, device_id=None): def get_sdk(self, sdk_type): sdk_map = self._construct_sdk_map() - return sdk_map[sdk_type]() + sdk_client = sdk_map[sdk_type]() + sdk_client.config.enable_http_logger = True + sdk_client.config.add_user_agent(USER_AGENT) + return sdk_client def _construct_sdk_map(self): return { SdkType.service_sdk: self._get_iothub_service_sdk, # Don't need to call here - SdkType.device_sdk: self._get_iothub_device_sdk + SdkType.device_sdk: self._get_iothub_device_sdk, + SdkType.dps_sdk: self._get_dps_service_sdk, } def _get_iothub_device_sdk(self): from azext_iot.sdk.iothub.device import IotHubGatewayDeviceAPIs + credentials = SasTokenAuthentication( uri=self.sas_uri, - shared_access_policy_name=self.target['policy'], - shared_access_key=self.target['primarykey']) + shared_access_policy_name=self.target["policy"], + shared_access_key=self.target["primarykey"], + ) return IotHubGatewayDeviceAPIs(credentials=credentials, base_url=self.endpoint) def _get_iothub_service_sdk(self): from azext_iot.sdk.iothub.service import IotHubGatewayServiceAPIs - credentials = SasTokenAuthentication( - uri=self.sas_uri, - shared_access_policy_name=self.target['policy'], - shared_access_key=self.target['primarykey']) - - return IotHubGatewayServiceAPIs(credentials=credentials, base_url=self.endpoint) - -# TODO: Deprecated. To be removed asap. -def _bind_sdk(target, sdk_type, device_id=None, auth=None): - from azext_iot.sdk.service.iot_hub_gateway_service_apis import IotHubGatewayServiceAPIs - from azext_iot.sdk.dps import ProvisioningServiceClient - from azext_iot.sdk.pnp.digital_twin_repository_service import DigitalTwinRepositoryService - - sas_uri = target['entity'] - endpoint = "https://{}".format(sas_uri) - if device_id: - sas_uri = '{}/devices/{}'.format(sas_uri, device_id) - - if sdk_type is SdkType.pnp_sdk: - return ( - DigitalTwinRepositoryService(endpoint), - _get_sdk_exception_type(sdk_type) + credentials = ( + self.auth_override + if self.auth_override + else SasTokenAuthentication( + uri=self.sas_uri, + shared_access_policy_name=self.target["policy"], + shared_access_key=self.target["primarykey"], + ) ) - if not auth: - auth = SasTokenAuthentication(sas_uri, target['policy'], target['primarykey']) + return IotHubGatewayServiceAPIs(credentials=credentials, base_url=self.endpoint) - if sdk_type is SdkType.service_sdk: - return ( - IotHubGatewayServiceAPIs(auth, endpoint), - _get_sdk_exception_type(sdk_type) - ) + def _get_dps_service_sdk(self): + from azext_iot.sdk.dps.service import ProvisioningServiceClient - if sdk_type is SdkType.dps_sdk: - return ( - ProvisioningServiceClient(auth, endpoint), - _get_sdk_exception_type(sdk_type) + credentials = SasTokenAuthentication( + uri=self.sas_uri, + shared_access_policy_name=self.target["policy"], + shared_access_key=self.target["primarykey"], ) - return None - - -# TODO: Dependency for _bind_sdk. Will be removed asap. -def _get_sdk_exception_type(sdk_type): - from importlib import import_module - - exception_library = { - SdkType.service_sdk: import_module('msrestazure.azure_exceptions'), - SdkType.dps_sdk: import_module('azext_iot.sdk.dps.models.provisioning_service_error_details'), - SdkType.pnp_sdk: import_module('msrest.exceptions') - } - return exception_library.get(sdk_type, None) + return ProvisioningServiceClient( + credentials=credentials, base_url=self.endpoint + ) diff --git a/azext_iot/_help.py b/azext_iot/_help.py index 9ed667eb7..8667e4cda 100644 --- a/azext_iot/_help.py +++ b/azext_iot/_help.py @@ -10,7 +10,9 @@ from knack.help_files import helps -helps['iot'] = """ +helps[ + "iot" +] = """ type: group short-summary: Manage Internet of Things (IoT) assets. Augmented with the IoT extension. @@ -19,12 +21,16 @@ https://github.com/Azure/azure-iot-cli-extension/wiki/Tips """ -helps['iot hub'] = """ +helps[ + "iot hub" +] = """ type: group short-summary: Manage entities in an Azure IoT Hub. """ -helps['iot hub monitor-events'] = """ +helps[ + "iot hub monitor-events" +] = """ type: command short-summary: Monitor device telemetry & messages sent to an IoT Hub. long-summary: | @@ -65,7 +71,9 @@ az iot hub monitor-events -n {iothub_name} --content-type application/json """ -helps['iot hub monitor-feedback'] = """ +helps[ + "iot hub monitor-feedback" +] = """ type: command short-summary: Monitor feedback sent by devices to acknowledge cloud-to-device (C2D) messages. long-summary: | @@ -88,12 +96,49 @@ az iot hub monitor-feedback -n {iothub_name} -d {device_id} -w {message_id} """ -helps['iot hub device-identity'] = """ +helps[ + "iot hub connection-string" +] = """ + type: group + short-summary: Manage IoT Hub connection strings. +""" + +helps[ + "iot hub connection-string show" +] = """ + type: command + short-summary: Show the connection strings for the specified IoT Hubs using the given policy name and key. + examples: + - name: Show the connection strings for all active state IoT Hubs in a subscription using the default policy and primary key. + text: > + az iot hub connection-string show + - name: Show the connection strings for all active state IoT Hubs in a resource group using the default policy and primary key. + text: > + az iot hub connection-string show --resource-group MyResourceGroup + - name: Show all connection strings of the given IoT Hub using primary key. + text: > + az iot hub connection-string show -n MyIotHub --all + - name: Show the connection string of the given IoT Hub using the default policy and primary key. + text: > + az iot hub connection-string show -n MyIotHub + - name: Show the connection string of the given IoT Hub using policy 'service' and secondary key. + text: > + az iot hub connection-string show -n MyIotHub --policy-name service --key-type secondary + - name: Show the eventhub compatible connection string of the given IoT Hub\'s default eventhub. + text: > + az iot hub connection-string show -n MyIotHub --default-eventhub +""" + +helps[ + "iot hub device-identity" +] = """ type: group short-summary: Manage IoT devices. """ -helps['iot hub device-identity create'] = """ +helps[ + "iot hub device-identity create" +] = """ type: command short-summary: Create a device in an IoT Hub. examples: @@ -129,17 +174,23 @@ --status disabled --status-reason 'for reasons' """ -helps['iot hub device-identity show'] = """ +helps[ + "iot hub device-identity show" +] = """ type: command short-summary: Get the details of an IoT Hub device. """ -helps['iot hub device-identity list'] = """ +helps[ + "iot hub device-identity list" +] = """ type: command short-summary: List devices in an IoT Hub. """ -helps['iot hub device-identity update'] = """ +helps[ + "iot hub device-identity update" +] = """ type: command short-summary: Update an IoT Hub device. long-summary: Use --set followed by property assignments for updating a device. @@ -149,125 +200,290 @@ text: > az iot hub device-identity update -d {device_id} -n {iothub_name} --set capabilities.iotEdge=true + - name: Turn on edge capabilities for device using convenience argument. + text: > + az iot hub device-identity update -d {device_id} -n {iothub_name} --ee - name: Disable device status text: > az iot hub device-identity update -d {device_id} -n {iothub_name} --set status=disabled + - name: Disable device status using convenience argument. + text: > + az iot hub device-identity update -d {device_id} -n {iothub_name} --status disabled - name: In one command text: > az iot hub device-identity update -d {device_id} -n {iothub_name} --set status=disabled capabilities.iotEdge=true """ -helps['iot hub device-identity delete'] = """ +helps[ + "iot hub device-identity renew-key" +] = """ + type: command + short-summary: Renew target keys of an IoT Hub device with sas authentication. + examples: + - name: Renew the primary key. + text: az iot hub device-identity renew-key -d {device_id} -n {iothub_name} --kt primary + - name: Swap the primary and secondary keys. + text: az iot hub device-identity renew-key -d {device_id} -n {iothub_name} --kt swap +""" + +helps[ + "iot hub device-identity delete" +] = """ type: command short-summary: Delete an IoT Hub device. """ -helps['iot hub device-identity show-connection-string'] = """ +helps[ + "iot hub device-identity show-connection-string" +] = """ type: command short-summary: Show a given IoT Hub device connection string. """ -helps['iot hub device-identity export'] = """ +helps[ + "iot hub device-identity connection-string" +] = """ + type: group + short-summary: Manage IoT device\'s connection string. +""" + +helps[ + "iot hub device-identity connection-string show" +] = """ + type: command + short-summary: Show a given IoT Hub device connection string. +""" + +helps[ + "iot hub device-identity export" +] = """ type: command - short-summary: Export all device identities from an IoT Hub to an Azure Storage blob container. + short-summary: Export all device identities from an IoT Hub to an Azure Storage blob container. For inline + blob container SAS uri input, please review the input rules of your environment. long-summary: For more information, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities + examples: + - name: Export all device identities to a configured blob container and include device keys. Uses an inline SAS uri example. + text: > + az iot hub device-identity export -n {iothub_name} --ik --bcu + 'https://mystorageaccount.blob.core.windows.net/devices?sv=2019-02-02&st=2020-08-23T22%3A35%3A00Z&se=2020-08-24T22%3A35%3A00Z&sr=c&sp=rwd&sig=VrmJ5sQtW3kLzYg10VqmALGCp4vtYKSLNjZDDJBSh9s%3D' + - name: Export all device identities to a configured blob container using a file path which contains the SAS uri. + text: > + az iot hub device-identity export -n {iothub_name} --bcu {sas_uri_filepath} """ -helps['iot hub device-identity import'] = """ +helps[ + "iot hub device-identity import" +] = """ type: command - short-summary: Import device identities to an IoT Hub from a blob. + short-summary: Import device identities to an IoT Hub from a blob. For inline + blob container SAS uri input, please review the input rules of your environment. long-summary: For more information, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities + examples: + - name: Import all device identities from a blob using an inline SAS uri. + text: > + az iot hub device-identity import -n {iothub_name} --ibcu {input_sas_uri} --obcu {output_sas_uri} + - name: Import all device identities from a blob using a file path which contains SAS uri. + text: > + az iot hub device-identity import -n {iothub_name} --ibcu {input_sas_uri_filepath} --obcu {output_sas_uri_filepath} """ -helps['iot hub device-identity get-parent'] = """ +helps[ + "iot hub device-identity get-parent" +] = """ type: command short-summary: Get the parent device of the specified device. examples: - name: Get the parent device of the specified device. text: > - az iot hub device-identity get-parent -d {non_edge_device_id} -n {iothub_name} + az iot hub device-identity get-parent -d {device_id} -n {iothub_name} """ -helps['iot hub device-identity set-parent'] = """ +helps[ + "iot hub device-identity set-parent" +] = """ type: command - short-summary: Set the parent device of the specified non-edge device. + short-summary: Set the parent device of the specified device. examples: - - name: Set the parent device of the specified non-edge device. + - name: Set the parent device of the specified device. text: > - az iot hub device-identity set-parent -d {non_edge_device_id} --pd {edge_device_id} -n {iothub_name} - - name: Set the parent device of the specified non-edge device irrespectively the non-edge device is + az iot hub device-identity set-parent -d {device_id} --pd {edge_device_id} -n {iothub_name} + - name: Set the parent device of the specified device irrespectively the device is already a child of other edge device. text: > - az iot hub device-identity set-parent -d {non_edge_device_id} --pd {edge_device_id} --force -n {iothub_name} + az iot hub device-identity set-parent -d {device_id} --pd {edge_device_id} --force -n {iothub_name} """ -helps['iot hub device-identity add-children'] = """ +helps[ + "iot hub device-identity parent" +] = """ + type: group + short-summary: Manage IoT device\'s parent device. +""" + +helps[ + "iot hub device-identity parent show" +] = """ type: command - short-summary: Add specified comma-separated list of non edge device ids as children of specified edge device. + short-summary: Get the parent device of the specified device. examples: - - name: Add non-edge devices as a children to the edge device. + - name: Get the parent device of the specified device. text: > - az iot hub device-identity add-children -d {edge_device_id} --child-list {comma_separated_non_edge_device_id} + az iot hub device-identity parent show -d {device_id} -n {iothub_name} +""" + +helps[ + "iot hub device-identity parent set" +] = """ + type: command + short-summary: Set the parent device of the specified device. + examples: + - name: Set the parent device of the specified device. + text: > + az iot hub device-identity parent set -d {device_id} --pd {edge_device_id} -n {iothub_name} + - name: Set the parent device of the specified device and overwrites its original parent. + text: > + az iot hub device-identity parent set -d {device_id} --pd {edge_device_id} --force -n {iothub_name} +""" + +helps[ + "iot hub device-identity add-children" +] = """ + type: command + short-summary: Add specified comma-separated list of device ids as children of specified edge device. + examples: + - name: Add devices as a children to the edge device. + text: > + az iot hub device-identity add-children -d {edge_device_id} --child-list {comma_separated_device_id} -n {iothub_name} - - name: Add non-edge devices as a children to the edge device irrespectively the non-edge device is + - name: Add devices as a children to the edge device irrespectively the device is already a child of other edge device. text: > - az iot hub device-identity add-children -d {edge_device_id} --child-list {comma_separated_non_edge_device_id} + az iot hub device-identity add-children -d {edge_device_id} --child-list {comma_separated_device_id} -n {iothub_name} -f """ -helps['iot hub device-identity list-children'] = """ +helps[ + "iot hub device-identity list-children" +] = """ type: command - short-summary: Print comma-separated list of assigned child devices. + short-summary: Outputs comma-separated list of assigned child devices. examples: - - name: Show all assigned non-edge devices as comma-separated list. + - name: Show all assigned devices as comma-separated list. text: > az iot hub device-identity list-children -d {edge_device_id} -n {iothub_name} """ -helps['iot hub device-identity remove-children'] = """ +helps[ + "iot hub device-identity remove-children" +] = """ type: command - short-summary: Remove non edge devices as children from specified edge device. + short-summary: Remove devices as children from specified edge device. examples: - name: Remove all mentioned devices as children of specified device. text: > - az iot hub device-identity remove-children -d {edge_device_id} --child-list {comma_separated_non_edge_device_id} + az iot hub device-identity remove-children -d {edge_device_id} --child-list {comma_separated_device_id} -n {iothub_name} - - name: Remove all non-edge devices as children specified edge device. + - name: Remove all devices as children specified edge device. text: > az iot hub device-identity remove-children -d {edge_device_id} --remove-all """ -helps['iot hub device-twin'] = """ +helps[ + "iot hub device-identity children" +] = """ + type: group + short-summary: Manage IoT device\'s children device. +""" + +helps[ + "iot hub device-identity children add" +] = """ + type: command + short-summary: Add specified space-separated list of device ids as children of specified edge device. + examples: + - name: Add devices as a children to the edge device. + text: > + az iot hub device-identity children add -d {edge_device_id} --child-list {space_separated_device_id} + -n {iothub_name} + - name: Add devices as children to the edge device and overwrites children devices' + original parent. + text: > + az iot hub device-identity children add -d {edge_device_id} --child-list {space_separated_device_id} + -n {iothub_name} -f +""" + +helps[ + "iot hub device-identity children list" +] = """ + type: command + short-summary: Outputs list of assigned child devices. + examples: + - name: Show all assigned children devices as list. + text: > + az iot hub device-identity children list -d {edge_device_id} -n {iothub_name} + - name: Show all assigned children devices as list whose device ID contains substring of 'test'. + text: > + az iot hub device-identity children list -d {edge_device_id} -n {iothub_name} --query "[?contains(@,'test')]" +""" + +helps[ + "iot hub device-identity children remove" +] = """ + type: command + short-summary: Remove devices as children from specified edge device. + examples: + - name: Remove all mentioned devices as children of specified device. + text: > + az iot hub device-identity children remove -d {edge_device_id} --child-list {space_separated_device_id} + -n {iothub_name} + - name: Remove all devices as children specified edge device. + text: > + az iot hub device-identity children remove -d {edge_device_id} --remove-all +""" + +helps[ + "iot hub device-twin" +] = """ type: group short-summary: Manage IoT device twin configuration. """ -helps['iot hub device-twin show'] = """ +helps[ + "iot hub device-twin show" +] = """ type: command short-summary: Get a device twin definition. """ -helps['iot hub device-twin update'] = """ +helps[ + "iot hub device-twin update" +] = """ type: command - short-summary: Update device twin definition. - long-summary: Use --set followed by property assignments for updating a device twin. - Leverage properties returned from 'iot hub device-twin show'. + short-summary: Update device twin desired properties and tags. + long-summary: Provide --desired or --tags arguments for PATCH behavior. + Usage of generic update args (i.e. --set) will reflect PUT behavior + and are deprecated. examples: - - name: Add nested tags to device twin. + - name: Patch device twin desired properties. text: > - az iot hub device-twin update --device-id {device_id} --hub-name {iothub_name} - --set tags='{"location":{"region":"US"}}' - - name: Remove the 'region' property from parent 'location' property + az iot hub device-twin update -n {iothub_name} -d {device_id} + --desired '{"conditions":{"temperature":{"warning":70, "critical":100}}}' + - name: Patch device twin tags. text: > - az iot hub device-twin update --device-id {device_id} --hub-name {iothub_name} - --set tags.location.region='null' + az iot hub device-twin update -n {iothub_name} -d {device_id} + --tags '{"country": "USA"}' + - name: Patch removal of 'critical' desired property from parent 'temperature' + text: > + az iot hub device-twin update -n {iothub_name} -d {device_id} + --desired '{"condition":{"temperature":{"critical": null}}}' """ -helps['iot hub device-twin replace'] = """ +helps[ + "iot hub device-twin replace" +] = """ type: command short-summary: Replace device twin definition with target json. long-summary: Input json directly or use a file path. @@ -277,32 +493,44 @@ az iot hub device-twin replace -d {device_id} -n {iothub_name} -j ../mydevicetwin.json """ -helps['iot hub module-identity'] = """ +helps[ + "iot hub module-identity" +] = """ type: group short-summary: Manage IoT device modules. """ -helps['iot hub module-identity show-connection-string'] = """ +helps[ + "iot hub module-identity show-connection-string" +] = """ type: command short-summary: Show a target IoT device module connection string. """ -helps['iot hub module-identity create'] = """ +helps[ + "iot hub module-identity create" +] = """ type: command short-summary: Create a module on a target IoT device in an IoT Hub. """ -helps['iot hub module-identity show'] = """ +helps[ + "iot hub module-identity show" +] = """ type: command short-summary: Get the details of an IoT device module in an IoT Hub. """ -helps['iot hub module-identity list'] = """ +helps[ + "iot hub module-identity list" +] = """ type: command short-summary: List modules located on an IoT device in an IoT Hub. """ -helps['iot hub module-identity update'] = """ +helps[ + "iot hub module-identity update" +] = """ type: command short-summary: Update an IoT Hub device module. long-summary: Use --set followed by property assignments for updating a module. @@ -315,38 +543,67 @@ authentication.symmetricKey.secondaryKey="" """ -helps['iot hub module-identity delete'] = """ +helps[ + "iot hub module-identity delete" +] = """ type: command short-summary: Delete a device in an IoT Hub. """ -helps['iot hub module-twin'] = """ +helps[ + "iot hub module-identity connection-string" +] = """ + type: group + short-summary: Manage IoT device module\'s connection string. +""" + +helps[ + "iot hub module-identity connection-string show" +] = """ + type: command + short-summary: Show a target IoT device module connection string. +""" + +helps[ + "iot hub module-twin" +] = """ type: group short-summary: Manage IoT device module twin configuration. """ -helps['iot hub module-twin show'] = """ +helps[ + "iot hub module-twin show" +] = """ type: command short-summary: Show a module twin definition. """ -helps['iot hub module-twin update'] = """ +helps[ + "iot hub module-twin update" +] = """ type: command - short-summary: Update module twin definition. - long-summary: Use --set followed by property assignments for updating a module. - Leverage properties returned from 'iot hub module-twin show'. + short-summary: Update module twin desired properties and tags. + long-summary: Provide --desired or --tags arguments for PATCH behavior. + Usage of generic update args (i.e. --set) will reflect PUT behavior + and are deprecated. examples: - - name: Add desired properties to module twin. + - name: Patch module twin desired properties. + text: > + az iot hub module-twin update -n {iothub_name} -d {device_id} -m {module_id} + --desired '{"conditions":{"temperature":{"warning":70, "critical":100}}}' + - name: Patch module twin tags. text: > - az iot hub module-twin update -d {device_id} -n {iothub_name} -m {module_name} --set - properties.desired='{"conditions":{"temperature":{"warning":70, "critical":100}}}' - - name: Remove 'critical' property from parent 'temperature' + az iot hub module-twin update -n {iothub_name} -d {device_id} -m {module_id} + --tags '{"country": "USA"}' + - name: Patch removal of 'critical' desired property from parent 'temperature' text: > - az iot hub module-twin update -d mydevice -n myhub -m mymod --set - properties.desired.condition.temperature.critical='null' + az iot hub module-twin update -n {iothub_name} -d {device_id} -m {module_id} + --desired '{"condition":{"temperature":{"critical": null}}}' """ -helps['iot hub module-twin replace'] = """ +helps[ + "iot hub module-twin replace" +] = """ type: command short-summary: Replace a module twin definition with target json. long-summary: Input json directly or use a file path. @@ -357,7 +614,9 @@ -m {module_name} -j ../mymodtwin.json """ -helps['iot hub generate-sas-token'] = """ +helps[ + "iot hub generate-sas-token" +] = """ type: command short-summary: Generate a SAS token for a target IoT Hub, device or module. long-summary: For device SAS tokens, the policy parameter is used to @@ -379,17 +638,33 @@ --login 'HostName=myhub.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=12345' """ -helps['iot hub invoke-module-method'] = """ +helps[ + "iot hub invoke-module-method" +] = """ type: command short-summary: Invoke an Edge module method. + examples: + - name: Invoke a direct method on edge device using a module from the cloud. + text: > + az iot hub invoke-module-method -n {iothub_name} -d {device_id} + -m '$edgeAgent' --method-name 'RestartModule' --method-payload '{"schemaVersion": "1.0"}' """ -helps['iot hub invoke-device-method'] = """ +helps[ + "iot hub invoke-device-method" +] = """ type: command short-summary: Invoke a device method. + examples: + - name: Invoke a direct method on device from the cloud. + text: > + az iot hub invoke-device-method --hub-name {iothub_name} --device-id {device_id} + --method-name Reboot --method-payload '{"version":"1.0"}' """ -helps['iot hub query'] = """ +helps[ + "iot hub query" +] = """ type: command short-summary: Query an IoT Hub using a powerful SQL-like language. long-summary: Query an IoT Hub using a powerful SQL-like language to retrieve information @@ -405,12 +680,16 @@ az iot hub query -n {iothub_name} -q "select * from devices.modules where devices.deviceId = '{device_id}'" """ -helps['iot hub configuration'] = """ +helps[ + "iot hub configuration" +] = """ type: group short-summary: Manage IoT automatic device management configuration at scale. """ -helps['iot hub configuration create'] = """ +helps[ + "iot hub configuration create" +] = """ type: command short-summary: Create an IoT automatic device management configuration in a target IoT Hub. long-summary: | @@ -448,17 +727,23 @@ --metrics '{\\"metrics\\": {\\"queries\\": {\\"mymetric\\":\\"select moduleId from devices.modules where tags.location=''US''\\"}}}' """ -helps['iot hub configuration show'] = """ +helps[ + "iot hub configuration show" +] = """ type: command short-summary: Get the details of an IoT automatic device management configuration. """ -helps['iot hub configuration list'] = """ +helps[ + "iot hub configuration list" +] = """ type: command short-summary: List IoT automatic device management configurations in an IoT Hub. """ -helps['iot hub configuration update'] = """ +helps[ + "iot hub configuration update" +] = """ type: command short-summary: | Update specified properties of an IoT automatic device management configuration. @@ -474,12 +759,16 @@ targetCondition="tags.building=43 and tags.environment='dev'" """ -helps['iot hub configuration delete'] = """ +helps[ + "iot hub configuration delete" +] = """ type: command short-summary: Delete an IoT device configuration. """ -helps['iot hub configuration show-metric'] = """ +helps[ + "iot hub configuration show-metric" +] = """ type: command short-summary: Evaluate a target user or system metric defined in an IoT device configuration examples: @@ -492,12 +781,16 @@ --metric-type system """ -helps['iot hub distributed-tracing'] = """ +helps[ + "iot hub distributed-tracing" +] = """ type: group short-summary: Manage distributed settings per-device. """ -helps['iot hub distributed-tracing show'] = """ +helps[ + "iot hub distributed-tracing show" +] = """ type: command short-summary: Get the distributed tracing settings for a device. examples: @@ -506,7 +799,9 @@ az iot hub distributed-tracing show -d {device_id} -n {iothub_name} """ -helps['iot hub distributed-tracing update'] = """ +helps[ + "iot hub distributed-tracing update" +] = """ type: command short-summary: Update the distributed tracing options for a device. examples: @@ -515,37 +810,73 @@ az iot hub distributed-tracing update -d {device_id} --sm on --sr 50 -n {iothub_name} """ -helps['iot device'] = """ +helps[ + "iot device" +] = """ type: group short-summary: Leverage device-to-cloud and cloud-to-device messaging capabilities. """ -helps['iot device c2d-message'] = """ +helps[ + "iot device c2d-message" +] = """ type: group short-summary: Cloud-to-device messaging commands. """ -helps['iot device c2d-message abandon'] = """ +helps[ + "iot device c2d-message abandon" +] = """ type: command short-summary: Abandon a cloud-to-device message. """ -helps['iot device c2d-message complete'] = """ +helps[ + "iot device c2d-message complete" +] = """ type: command short-summary: Complete a cloud-to-device message. """ -helps['iot device c2d-message receive'] = """ +helps[ + "iot device c2d-message receive" +] = """ type: command short-summary: Receive a cloud-to-device message. + long-summary: | + Note: Only one message ack argument [--complete, --reject, --abandon] will be accepted. + examples: + - name: Basic usage + text: > + az iot device c2d-message receive -d {device_id} -n {hub_name} -g {resource_group} + - name: Receive a message and set a lock timeout of 30 seconds for that message + text: > + az iot device c2d-message receive -d {device_id} -n {hub_name} -g {resource_group} --lt {30} + - name: Receive a message and ack it as 'complete' after it is received + text: > + az iot device c2d-message receive -d {device_id} -n {hub_name} -g {resource_group} --complete + - name: Receive a message and reject it after it is received + text: > + az iot device c2d-message receive -d {device_id} -n {hub_name} -g {resource_group} --reject """ -helps['iot device c2d-message reject'] = """ +helps[ + "iot device c2d-message reject" +] = """ type: command short-summary: Reject or deadletter a cloud-to-device message. """ -helps['iot device c2d-message send'] = """ +helps[ + "iot device c2d-message purge" +] = """ + type: command + short-summary: Purge cloud-to-device message queue for a target device. +""" + +helps[ + "iot device c2d-message send" +] = """ type: command short-summary: Send a cloud-to-device message. long-summary: | @@ -564,7 +895,9 @@ az iot device c2d-message send -d {device_id} -n {iothub_name} --ack full --wait """ -helps['iot device send-d2c-message'] = """ +helps[ + "iot device send-d2c-message" +] = """ type: command short-summary: Send an mqtt device-to-cloud message. The command supports sending messages with application and system properties. @@ -579,7 +912,9 @@ text: az iot device send-d2c-message -n {iothub_name} -d {device_id} --props '$.mid=;$.cid=' """ -helps['iot device simulate'] = """ +helps[ + "iot device simulate" +] = """ type: command short-summary: | Simulate a device in an Azure IoT Hub. @@ -609,12 +944,16 @@ text: az iot device simulate -n {iothub_name} -d {device_id} --rs abandon --protocol http """ -helps['iot device upload-file'] = """ +helps[ + "iot device upload-file" +] = """ type: command short-summary: Upload a local file as a device to a pre-configured blob storage container. """ -helps['iot edge'] = """ +helps[ + "iot edge" +] = """ type: group short-summary: Manage IoT solutions on the Edge. long-summmary: | @@ -627,7 +966,9 @@ https://docs.microsoft.com/en-us/azure/iot-edge/ """ -helps['iot edge set-modules'] = """ +helps[ + "iot edge set-modules" +] = """ type: command short-summary: Set edge modules on a single device. long-summary: | @@ -640,12 +981,16 @@ az iot edge set-modules --hub-name {iothub_name} --device-id {device_id} --content ../modules_content.json """ -helps['iot edge deployment'] = """ +helps[ + "iot edge deployment" +] = """ type: group short-summary: Manage IoT Edge deployments at scale. """ -helps['iot edge deployment create'] = """ +helps[ + "iot edge deployment create" +] = """ type: command short-summary: Create an IoT Edge deployment in a target IoT Hub. long-summary: | @@ -657,41 +1002,61 @@ - name: Create a deployment with labels (bash syntax example) that applies for devices in 'building 9' and the environment is 'test'. text: > - az iot edge deployment create -d {deployment_name} -n {iothub_name} --content modules_content.json + az iot edge deployment create -d {deployment_name} -n {iothub_name} + --content modules_content.json --labels '{"key0":"value0", "key1":"value1"}' - --target-condition "tags.building=9 and tags.environment='test'" --priority 3 + --target-condition "tags.building=9 and tags.environment='test'" + --priority 3 - name: Create a deployment with labels (powershell syntax example) that applies for devices tagged with environment 'dev'. text: > - az iot edge deployment create -d {deployment_name} -n {iothub_name} --content modules_content.json - --labels '{\\"key\\":\\"value\\"}' + az iot edge deployment create -d {deployment_name} -n {iothub_name} + --content modules_content.json + --labels "{'key':'value'}" --target-condition "tags.environment='dev'" - name: Create a layered deployment that applies for devices tagged with environment 'dev'. - Both user metrics and modules content defined inline (cmd syntax example). + Both user metrics and modules content defined inline (powershell syntax example). text: > az iot edge deployment create -d {deployment_name} -n {iothub_name} - --content "{\\"modulesContent\\":{\\"$edgeAgent\\":{\\"properties.desired.modules.mymodule0\\":{ }},\\"$edgeHub\\":{\\"properties.desired.routes.myroute0\\":\\"FROM /messages/* INTO $upstream\\"}}}" - --target-condition "tags.environment='dev'" --priority 10 - --metrics "{\\"queries\\":{\\"mymetrik\\":\\"SELECT deviceId from devices where properties.reported.lastDesiredStatus.code = 200\\"}}" + --content "{'modulesContent':{'`$edgeAgent':{'properties.desired.modules.mymodule0':{ }},'`$edgeHub':{'properties.desired.routes.myroute0':'FROM /messages/* INTO `$upstream'}}}" + --target-condition "tags.environment='dev'" + --priority 10 + --metrics "{'queries':{'mymetrik':'SELECT deviceId from devices where properties.reported.lastDesiredStatus.code = 200'}}" --layered - - name: Create a layered deployment that applies for devices in 'building 9' and the environment is 'test'. + - name: Create a layered deployment that applies for devices in 'building 9' and environment 'test'. + Both user metrics and modules content defined inline (bash syntax example). text: > - az iot edge deployment create -d {deployment_name} -n {iothub_name} --content layered_modules_content.json + az iot edge deployment create -d {deployment_name} -n {iothub_name} + --content '{"modulesContent":{"$edgeAgent":{"properties.desired.modules.mymodule0":{ }},"$edgeHub":{"properties.desired.routes.myroute0":"FROM /messages/* INTO $upstream"}}}' + --target-condition "tags.building=9 and tags.environment='test'" + --metrics '{"queries":{"mymetrik":"SELECT deviceId from devices where properties.reported.lastDesiredStatus.code = 200"}}' + --layered + - name: Create a layered deployment that applies for devices in 'building 9' and environment 'test'. + Both user metrics and modules content defined from file. + text: > + az iot edge deployment create -d {deployment_name} -n {iothub_name} + --content layered_modules_content.json --target-condition "tags.building=9 and tags.environment='test'" --metrics metrics_content.json --layered """ -helps['iot edge deployment show'] = """ +helps[ + "iot edge deployment show" +] = """ type: command short-summary: Get the details of an IoT Edge deployment. """ -helps['iot edge deployment list'] = """ +helps[ + "iot edge deployment list" +] = """ type: command short-summary: List IoT Edge deployments in an IoT Hub. """ -helps['iot edge deployment update'] = """ +helps[ + "iot edge deployment update" +] = """ type: command short-summary: | Update specified properties of an IoT Edge deployment. @@ -707,42 +1072,66 @@ --set labels='{"purpose":"dev", "owners":"IoTEngineering"}' targetCondition='tags.building=9' """ -helps['iot edge deployment delete'] = """ +helps[ + "iot edge deployment delete" +] = """ type: command short-summary: Delete an IoT Edge deployment. """ -helps['iot edge deployment show-metric'] = """ +helps[ + "iot edge deployment show-metric" +] = """ type: command short-summary: Evaluate a target system metric defined in an IoT Edge deployment. examples: - name: Evaluate the 'appliedCount' system metric text: > - az iot edge deployment show-metric -m appliedCount -d {deployment_name} -n {iothub_name} + az iot edge deployment show-metric -m appliedCount -d {deployment_name} -n {iothub_name} --mt system + - name: Evaluate the 'myCustomMetric' user metric + text: > + az iot edge deployment show-metric -m myCustomMetric -d {deployment_name} -n {iothub_name} """ -helps['iot dps'] = """ +helps[ + "iot dps" +] = """ type: group short-summary: Manage entities in an Azure IoT Hub Device Provisioning Service. Augmented with the IoT extension. """ -helps['iot dps enrollment'] = """ +helps[ + "iot dps enrollment" +] = """ type: group short-summary: Manage enrollments in an Azure IoT Hub Device Provisioning Service. """ -helps['iot dps enrollment list'] = """ +helps[ + "iot dps enrollment list" +] = """ type: command short-summary: List device enrollments in an Azure IoT Hub Device Provisioning Service. """ -helps['iot dps enrollment show'] = """ +helps[ + "iot dps enrollment show" +] = """ type: command short-summary: Get device enrollment details in an Azure IoT Hub Device Provisioning Service. + examples: + - name: Basic usage + text: > + az iot dps enrollment show --dps-name {dps_name} -g {resource_group} --enrollment-id {enrollment_id} + - name: Include full attestation information in results for a symmetric key enrollment + text: > + az iot dps enrollment show --dps-name {dps_name} -g {resource_group} --enrollment-id {symmetric_key_enrollment_id} --show-keys """ -helps['iot dps enrollment create'] = """ +helps[ + "iot dps enrollment create" +] = """ type: command short-summary: Create a device enrollment in an Azure IoT Hub Device Provisioning Service. examples: @@ -755,14 +1144,15 @@ - name: Create an enrollment '{enrollment_id}' with attestation type 'x509' in the Azure IoT Device Provisioning Service '{dps_name}' in the resource group '{resource_group_name}' with provisioning status 'disabled', target IoT Hub - '{iothub_host_name}', device id '{device_id}' and initial twin - properties '{"location":{"region":"US"}}'. + '{iothub_host_name}', device id '{device_id}', initial twin properties + '{"location":{"region":"US"}}' and initial twin tags '{"version":"1"}'. text: > az iot dps enrollment create -g {resource_group_name} --dps-name {dps_name} --enrollment-id {enrollment_id} --attestation-type x509 --certificate-path /certificates/Certificate.pem --provisioning-status disabled --iot-hub-host-name {iothub_host_name} - --initial-twin-properties "{'location':{'region':'US'}}" --device-id {device_id} + --initial-twin-properties "{'location':{'region':'US'}}" + --initial-twin-tags "{'version':'1'}" --device-id {device_id} - name: Create an enrollment 'MyEnrollment' with attestation type 'tpm' in the Azure IoT Device Provisioning Service '{dps_name}' in the resource group '{resource_group_name}'. text: > @@ -793,9 +1183,16 @@ az iot dps enrollment create -g {resource_group_name} --dps-name {dps_name} --enrollment-id {enrollment_id} --attestation-type tpm --allocation-policy hashed --endorsement-key 14963E8F3BA5B3984110B3C1CA8E8B89 --iot-hubs "{iot_hub_host_name1} {iot_hub_host_name2}" + - name: Create an enrollment 'MyEnrollment' with custom allocation policy, + text: > + az iot dps enrollment create -g {resource_group_name} --dps-name {dps_name} + --enrollment-id {enrollment_id} --attestation-type symmetrickey --allocation-policy custom + --webhook-url {webhook_url} --api-version {api_version} """ -helps['iot dps enrollment update'] = """ +helps[ + "iot dps enrollment update" +] = """ type: command short-summary: Update a device enrollment in an Azure IoT Hub Device Provisioning Service. examples: @@ -829,29 +1226,53 @@ az iot dps enrollment update -g {resource_group_name} --dps-name {dps_name} --enrollment-id {enrollment_id} --allocation-policy geolatency --etag AAAAAAAAAAA= --iot-hubs "{iot_hub_host_name1} {iot_hub_host_name2} {iot_hub_host_name3}" + - name: Update enrollment '{enrollment_id}' in the Azure IoT Device Provisioning Service '{dps_name}' + in the resource group '{resource_group_name}' with + initial twin properties '{"location":{"region":"USA"}}' and initial twin tags '{"version":"2"}'. + text: > + az iot dps enrollment update -g {resource_group_name} --dps-name {dps_name} + --enrollment-id {enrollment_id} --initial-twin-properties "{'location':{'region':'USA'}}" + --initial-twin-tags "{'version1':'2'}" """ -helps['iot dps enrollment delete'] = """ +helps[ + "iot dps enrollment delete" +] = """ type: command short-summary: Delete a device enrollment in an Azure IoT Hub Device Provisioning Service. """ -helps['iot dps enrollment-group'] = """ +helps[ + "iot dps enrollment-group" +] = """ type: group short-summary: Manage Azure IoT Hub Device Provisioning Service. """ -helps['iot dps enrollment-group list'] = """ +helps[ + "iot dps enrollment-group list" +] = """ type: command short-summary: List enrollments groups in an Azure IoT Hub Device Provisioning Service. """ -helps['iot dps enrollment-group show'] = """ +helps[ + "iot dps enrollment-group show" +] = """ type: command short-summary: Get the details of an enrollment group in an Azure IoT Hub Device Provisioning Service. + examples: + - name: Basic usage + text: > + az iot dps enrollment-group show --dps-name {dps_name} -g {resource_group} --enrollment-id {enrollment_id} + - name: Include full attestation information in results for a symmetric key enrollment-group + text: > + az iot dps enrollment-group show --dps-name {dps_name} -g {resource_group} --enrollment-id {symmetric_key_enrollment_id} --show-keys """ -helps['iot dps enrollment-group create'] = """ +helps[ + "iot dps enrollment-group create" +] = """ type: command short-summary: Create an enrollment group in an Azure IoT Hub Device Provisioning Service. examples: @@ -868,30 +1289,40 @@ --enrollment-id {enrollment_id} --secondary-ca-name {certificate_name} - name: Create an enrollment group '{enrollment_id}' in the Azure IoT provisioning service 'MyDps' in the resource group '{resource_group_name}' with provisioning status - 'enabled', target IoT Hub '{iothub_host_name}' and initial twin - tags '{"location":{"region":"US"}} using an intermediate certificate as primary certificate'. + 'enabled', target IoT Hub '{iothub_host_name}', initial twin properties + '{"location":{"region":"US"}}' and initial twin tags '{"version_dps":"1"}' + using an intermediate certificate as primary certificate'. text: > az iot dps enrollment-group create -g {resource_group_name} --dps-name {dps_name} --enrollment-id {enrollment_id} --certificate-path /certificates/Certificate.pem --provisioning-status enabled --iot-hub-host-name {iothub_host_name} - --initial-twin-tags "{'location':{'region':'US'}}" + --initial-twin-properties "{'location':{'region':'US'}}" + --initial-twin-tags "{'version_dps':'1'}" - name: Create an enrollment group '{enrollment_id}' in the Azure IoT provisioning service '{dps_name}' in the resource group '{resource_group_name} with attestation type 'symmetrickey'. text: > az iot dps enrollment-group create -g {resource_group_name} --dps-name {dps_name} --enrollment-id {enrollment_id} --primary-key {primary_key} --secondary-key {secondary_key} + - name: Create an enrollment group '{enrollment_id}' with custom allocation policy, + text: > + az iot dps enrollment-group create -g {resource_group_name} --dps-name {dps_name} + --enrollment-id {enrollment_id} --allocation-policy custom --webhook-url {webhook_url} + --api-version {api_version} """ -helps['iot dps enrollment-group update'] = """ +helps[ + "iot dps enrollment-group update" +] = """ type: command short-summary: Update an enrollment group in an Azure IoT Hub Device Provisioning Service. examples: - name: Update enrollment group '{enrollment_id}' in the Azure IoT provisioning service '{dps_name}' - in the resource group '{resource_group_name}' with new initial twin tags. + in the resource group '{resource_group_name}' with initial twin properties and initial twin tags. text: > az iot dps enrollment-group update -g {resource_group_name} --dps-name {dps_name} - --enrollment-id {enrollment_id} --initial-twin-tags "{'location':{'region':'US2'}}" --etag AAAAAAAAAAA= + --enrollment-id {enrollment_id} --initial-twin-properties "{'location':{'region':'USA'}}" + --initial-twin-tags "{'version_dps':'2'}" --etag AAAAAAAAAAA= - name: Update enrollment group '{enrollment_id}' in the Azure IoT provisioning service '{dps_name}' in the resource group '{resource_group_name}' with new primary intermediate certificate and remove existing secondary intermediate certificate. @@ -913,384 +1344,50 @@ --enrollment-id {enrollment_id} --primary-key {new_primary_key} --etag AAAAAAAAAAA= """ -helps['iot dps enrollment-group delete'] = """ +helps[ + "iot dps enrollment-group delete" +] = """ type: command short-summary: Delete an enrollment group in an Azure IoT Hub Device Provisioning Service. """ -helps['iot dps registration'] = """ +helps[ + "iot dps registration" +] = """ type: group short-summary: Manage Azure IoT Hub Device Provisioning Service registrations. """ -helps['iot dps registration list'] = """ +helps[ + "iot dps registration list" +] = """ type: command short-summary: List device registration state in an Azure IoT Hub Device Provisioning Service enrollment group. """ -helps['iot dps registration show'] = """ +helps[ + "iot dps registration show" +] = """ type: command short-summary: Get the device registration state in an Azure IoT Hub Device Provisioning Service. """ -helps['iot dps registration delete'] = """ +helps[ + "iot dps registration delete" +] = """ type: command short-summary: Delete a device registration in an Azure IoT Hub Device Provisioning Service. """ -helps['iotcentral app monitor-events'] = """ +helps[ + "iot dps compute-device-key" +] = """ type: command - short-summary: Monitor device telemetry & messages sent to the IoT Hub for an IoT Central app. - long-summary: | - EXPERIMENTAL requires Python 3.5+ - This command relies on and may install dependent Cython package (uamqp) upon first execution. - https://github.com/Azure/azure-uamqp-python - - DEPRECATED. Use 'az iot central app monitor-events' instead. + short-summary: Generate a derived device SAS key. + long-summary: Generate a derived device key from a DPS enrollment group symmetric key. examples: - name: Basic usage text: > - az iotcentral app monitor-events --app-id {app_id} - - name: Basic usage when filtering on target device - text: > - az iotcentral app monitor-events --app-id {app_id} -d {device_id} - - name: Basic usage when filtering targeted devices with a wildcard in the ID - text: > - az iotcentral app monitor-events --app-id {app_id} -d Device* - - name: Filter device and specify an Event Hub consumer group to bind to. - text: > - az iotcentral app monitor-events --app-id {app_id} -d {device_id} --cg {consumer_group_name} - - name: Receive message annotations (message headers) - text: > - az iotcentral app monitor-events --app-id {app_id} -d {device_id} --properties anno - - name: Receive message annotations + system properties. Never time out. - text: > - az iotcentral app monitor-events --app-id {app_id} -d {device_id} --properties anno sys --timeout 0 - - name: Receive all message attributes from all device messages - text: > - az iotcentral app monitor-events --app-id {app_id} --props all - - name: Receive all messages and parse message payload as JSON - text: > - az iotcentral app monitor-events --app-id {app_id} --output json - """ - -helps['iotcentral device-twin'] = """ - type: group - short-summary: Manage IoT Central device twins. - long-summary: DEPRECATED. Use 'az iot central device-twin' instead. -""" - -helps['iotcentral device-twin show'] = """ - type: command - short-summary: Get the device twin from IoT Hub. - long-summary: DEPRECATED. Use 'az iot central device-twin show' instead. -""" - -helps['iot central'] = """ - type: group - short-summary: Manage Azure IoT Central assets. -""" - -helps['iot central app'] = """ - type: group - short-summary: Manage Azure IoT Central applications. -""" - -helps['iot central app monitor-events'] = """ - type: command - short-summary: Monitor device telemetry & messages sent to the IoT Hub for an IoT Central app. - long-summary: | - EXPERIMENTAL requires Python 3.5+ - This command relies on and may install dependent Cython package (uamqp) upon first execution. - https://github.com/Azure/azure-uamqp-python - examples: - - name: Basic usage - text: > - az iot central app monitor-events --app-id {app_id} - - name: Basic usage when filtering on target device - text: > - az iot central app monitor-events --app-id {app_id} -d {device_id} - - name: Basic usage when filtering targeted devices with a wildcard in the ID - text: > - az iot central app monitor-events --app-id {app_id} -d Device* - - name: Filter device and specify an Event Hub consumer group to bind to. - text: > - az iot central app monitor-events --app-id {app_id} -d {device_id} --cg {consumer_group_name} - - name: Receive message annotations (message headers) - text: > - az iot central app monitor-events --app-id {app_id} -d {device_id} --properties anno - - name: Receive message annotations + system properties. Never time out. - text: > - az iot central app monitor-events --app-id {app_id} -d {device_id} --properties anno sys --timeout 0 - - name: Receive all message attributes from all device messages - text: > - az iot central app monitor-events --app-id {app_id} --props all - - name: Receive all messages and parse message payload as JSON - text: > - az iot central app monitor-events --app-id {app_id} --output json - """ - -helps['iot central device-twin'] = """ - type: group - short-summary: Manage IoT Central device twins. -""" - -helps['iot central device-twin show'] = """ - type: command - short-summary: Get the device twin from IoT Hub. -""" - -helps['iot dt'] = """ - type: group - short-summary: Manage digital twin of an IoT Plug and Play device. -""" - -helps['iot dt invoke-command'] = """ - type: command - short-summary: Executes a command on an IoT Plug and Play device. - long-summary: You can leverage az login and provide --hub-name instead of --login for every command. - examples: - - name: Execute a command on device . - text: > - az iot dt invoke-command --login {iothub_cs} - --interface {plug_and_play_interface} --device-id {device_id} - --command-name {command_name} --command-payload {payload} - - name: Execute a command on device within current session. - text: > - az iot dt invoke-command --hub-name {iothub_name} - --interface {plug_and_play_interface} --device-id {device_id} - --command-name {command_name} --command-payload {payload} -""" - -helps['iot dt list-interfaces'] = """ - type: command - short-summary: List interfaces of a target IoT Plug and Play device. - long-summary: You can leverage az login and provide --hub-name instead of --login for every command. - examples: - - name: List all IoT Plug and Play interfaces on a device. - text: > - az iot dt list-interfaces --login {iothub_cs} - --device-id {device_id} - - name: List all IoT Plug and Play interfaces on a device within current session. - text: > - az iot dt list-interfaces --hub-name {iothub_name} --device-id {device_id} -""" - -helps['iot dt list-properties'] = """ - type: command - short-summary: List properties of a target IoT Plug and Play device interface(s). - long-summary: You can leverage az login and provide --hub-name instead of --login for every command. - examples: - - name: List all properties of all device's interfaces on an IoT Plug and Play device. - text: > - az iot dt list-properties --login {iothub_cs} --source device - --device-id {device_id} - - name: List all properties of all public interfaces on an IoT Plug and Play device within current session. - text: > - az iot dt list-properties --hub-name {iothub_name} --device-id {device_id} --source public - - name: List all properties of device's interface on an IoT Plug and Play device. - text: > - az iot dt list-properties --login {iothub_cs} --source device - --device-id {device_id} --interface {plug_and_play_interface} -""" - -helps['iot dt list-commands'] = """ - type: command - short-summary: List commands of an IoT Plug and Play devices interface(s). - long-summary: You can leverage az login and provide --hub-name instead of --login for every command. - examples: - - name: List all commands of all private interfaces on an IoT Plug and Play device. - text: > - az iot dt list-commands --login {iothub_cs} --source private - --device-id {device_id} --repo-id {plug_and_play_model_repository_id} - - name: List all commands of a private interface on an IoT Plug and Play device. - text: > - az iot dt list-commands --login {iothub_cs} --source private - --device-id {device_id} --repo-id {plug_and_play_model_repository_id} - --interface {plug_and_play_interface} - - name: List all commands of all public interfaces on an IoT Plug and Play device. - text: > - az iot dt list-commands --login {iothub_cs} --source public - --device-id {device_id} - - name: List all commands of device's interface on an IoT Plug and Play device. - text: > - az iot dt list-commands --login {iothub_cs} --source device - --device-id {device_id} --interface {plug_and_play_interface} -""" - -helps['iot dt monitor-events'] = """ - type: command - short-summary: Monitor Digital Twin events. - long-summary: You can leverage az login and provide --hub-name instead of --login for every command. - examples: - - name: Basic usage monitoring events of all devices and all interfaces using the logged in session. - text: > - az iot dt monitor-events -n {iothub_name} - - name: Basic usage monitoring events of all devices and all interfaces using an IotHub connection string. - text: > - az iot dt monitor-events --login {iothub_cs} - - name: Basic usage when filtering on specific interface events while targeting devices with a wildcard in the ID. - text: > - az iot dt monitor-events -n {iothub_name} -d Device* -i {plug_and_play_interface} - - name: Filter Digital Twin events of a subset of devices using IoT Hub query language. - text: > - az iot dt monitor-events -n {iothub_name} -q "select * from devices where tags.location.region = 'US'" - - name: Filter events on a device with a particular interface. Use a custom consumer group when binding and - see all message properties. - text: > - az iot dt monitor-events --login {iothub_cs} --device-id {device_id} - --interface {plug_and_play_interface} --consumer-group {consumer_group_name} --properties all -""" - -helps['iot dt update-property'] = """ - type: command - short-summary: Update an IoT Plug and Play device interfaces writable property. - examples: - - name: Update an IoT Plug and Play device interfaces read-write property. - text: > - az iot dt update-property --login {iothub_cs} --device-id {device_id} - --interface-payload {payload} - - name: Update an IoT Plug and Play device interfaces read-write property within current session. - text: > - az iot dt update-property --hub-name {iothub_name} --device-id {device_id} - --interface-payload {payload} -""" - -helps['iot pnp'] = """ - type: group - short-summary: Manage entities of an IoT Plug and Play model repository. -""" - -helps['iot pnp interface'] = """ - type: group - short-summary: Manage interfaces in an IoT Plug and Play model repository. -""" - -helps['iot pnp interface publish'] = """ - type: command - short-summary: Publish an interface to public repository. - examples: - - name: Publish an interface to public repository. - text: > - az iot pnp interface publish -r {pnp_repository} --interface {plug_and_play_interface_id} -""" - -helps['iot pnp interface create'] = """ - type: command - short-summary: Create an interface in the company repository. - examples: - - name: Create an interface in the company repository. - text: > - az iot pnp interface create --def {plug_and_play_interface_file_path} -r {pnp_repository} -""" - -helps['iot pnp interface update'] = """ - type: command - short-summary: Update an interface in the company repository. - examples: - - name: Update an interface in the company repository. - text: > - az iot pnp interface update --def {updated_plug_and_play_interface_file_path} -r {pnp_repository} -""" - -helps['iot pnp interface list'] = """ - type: command - short-summary: List all interfaces. - examples: - - name: List all company repository's interfaces. - text: > - az iot pnp interface list -r {pnp_repository} - - name: List all public interfaces. - text: > - az iot pnp interface list -""" - -helps['iot pnp interface show'] = """ - type: command - short-summary: Get the details of an interface. - examples: - - name: Get the details of a company repository interface. - text: > - az iot pnp interface show -r {pnp_repository} --interface {plug_and_play_interface_id} - - name: Get the details of public interface. - text: > - az iot pnp interface show --interface {plug_and_play_interface_id} -""" - -helps['iot pnp interface delete'] = """ - type: command - short-summary: Delete an interface in the company repository. - examples: - - name: Delete an interface in the company repository. - text: > - az iot pnp interface delete -r {pnp_repository} --interface {plug_and_play_interface_id} -""" - -helps['iot pnp capability-model'] = """ - type: group - short-summary: Manage device capability models in an IoT Plug and Play model repository. -""" - -helps['iot pnp capability-model list'] = """ - type: command - short-summary: List all capability-model. - examples: - - name: List all company repository's capability-model. - text: > - az iot pnp capability-model list -r {pnp_repository} - - name: List all public capability-model. - text: > - az iot pnp capability-model list -""" - -helps['iot pnp capability-model show'] = """ - type: command - short-summary: Get the details of a capability-model. - examples: - - name: Get the details of a company repository capability-model. - text: > - az iot pnp capability-model show -r {pnp_repository} --model {plug_and_play_capability_model_id} - - name: Get the details of public capability-model. - text: > - az iot pnp capability-model show --model {plug_and_play_capability_model_id} -""" - -helps['iot pnp capability-model create'] = """ - type: command - short-summary: Create a capability-model in the company repository. - examples: - - name: Create a capability-model in the company repository. - text: > - az iot pnp capability-model create --def {plug_and_play_capability_model_file_path} -r {pnp_repository} -""" - -helps['iot pnp capability-model publish'] = """ - type: command - short-summary: Publish the capability-model to public repository. - examples: - - name: Publish the capability-model to public repository. - text: > - az iot pnp capability-model publish -r {pnp_repository} - --model {plug_and_play_capability_model_id} -""" - -helps['iot pnp capability-model delete'] = """ - type: command - short-summary: Delete the capability-model in the company repository. - examples: - - name: Delete the capability-model in the company repository. - text: > - az iot pnp capability-model delete -r {pnp_repository} - --model {plug_and_play_capability_model_id} -""" - -helps['iot pnp capability-model update'] = """ - type: command - short-summary: Update the capability-model in the company repository. - examples: - - name: Update the capability-model in the company repository. - text: > - az iot pnp capability-model update --def {updated_plug_and_play_capability_model_file_path} - -r {pnp_repository} + az iot dps compute-device-key --key {enrollement_group_symmetric_key} --registration-id {registration_id} """ diff --git a/azext_iot/_params.py b/azext_iot/_params.py index 10ca49be1..91cfb4314 100644 --- a/azext_iot/_params.py +++ b/azext_iot/_params.py @@ -13,7 +13,7 @@ resource_group_name_type, get_enum_type, get_resource_name_completion_list, - get_three_state_flag + get_three_state_flag, ) from azext_iot.common.shared import ( EntityStatusType, @@ -27,41 +27,49 @@ ReprovisionType, AllocationType, DistributedTracingSamplingModeType, - ModelSourceType, JobType, JobCreateType, - JobStatusType + JobStatusType, + AuthenticationType, + RenewKeyType, ) from azext_iot._validators import mode2_iot_login_handler from azext_iot.assets.user_messages import info_param_properties_device hub_name_type = CLIArgumentType( - completer=get_resource_name_completion_list('Microsoft.Devices/IotHubs'), - help='IoT Hub name.') + completer=get_resource_name_completion_list("Microsoft.Devices/IotHubs"), + help="IoT Hub name.", +) event_msg_prop_type = CLIArgumentType( - options_list=['--properties', '--props', '-p'], - nargs='*', - choices=CaseInsensitiveList(['sys', 'app', 'anno', 'all']), - help='Indicate key message properties to output. ' - 'sys = system properties, app = application properties, anno = annotations' + options_list=["--properties", "--props", "-p"], + nargs="*", + choices=CaseInsensitiveList(["sys", "app", "anno", "all"]), + help="Indicate key message properties to output. " + "sys = system properties, app = application properties, anno = annotations", +) + +children_list_prop_type = CLIArgumentType( + options_list=["--child-list", "--cl"], + nargs="*", + help="Child device list (space separated).", ) # There is a bug in CLI core preventing treating --qos as an integer. # Until its resolved, ensure casting of value to integer # TODO: azure.cli.core.parser line 180 difflib.get_close_matches qos_type = CLIArgumentType( - options_list=['--qos'], + options_list=["--qos"], type=str, nargs="?", choices=["0", "1"], - help='Quality of Service. 0 = At most once, 1 = At least once. 2 (Exactly once) is not supported.' + help="Quality of Service. 0 = At most once, 1 = At least once. 2 (Exactly once) is not supported.", ) event_timeout_type = CLIArgumentType( - options_list=['--timeout', '--to', '-t'], + options_list=["--timeout", "--to", "-t"], type=int, - help='Maximum seconds to maintain connection without receiving message. Use 0 for infinity. ' + help="Maximum seconds to maintain connection without receiving message. Use 0 for infinity. ", ) @@ -69,492 +77,918 @@ def load_arguments(self, _): """ Load CLI Args for Knack parser """ - with self.argument_context('iot') as context: - context.argument('login', options_list=['--login', '-l'], - validator=mode2_iot_login_handler, - help='This command supports an entity connection string with rights to perform action. ' - 'Use to avoid session login via "az login". ' - 'If both an entity connection string and name are provided the connection string takes priority.') - context.argument('resource_group_name', arg_type=resource_group_name_type) - context.argument('hub_name', options_list=['--hub-name', '-n'], arg_type=hub_name_type) - context.argument('device_id', options_list=['--device-id', '-d'], help='Target Device.') - context.argument('module_id', options_list=['--module-id', '-m'], help='Target Module.') - context.argument('key_type', options_list=['--key-type', '--kt'], - arg_type=get_enum_type(KeyType), - help='Shared access policy key type for auth.') - context.argument('policy_name', options_list=['--policy-name', '--pn'], - help='Shared access policy to use for auth.') - context.argument('duration', options_list=['--duration', '--du'], type=int, - help='Valid token duration in seconds.') - context.argument('etag', options_list=['--etag', '-e'], help='Entity tag value.') - context.argument('top', type=int, options_list=['--top'], - help='Maximum number of elements to return. Use -1 for unlimited.') - context.argument('method_name', options_list=['--method-name', '--mn'], - help='Target method for invocation.') - context.argument('method_payload', options_list=['--method-payload', '--mp'], - help='Json payload to be passed to method. Must be file path or raw json.') - context.argument('timeout', options_list=['--timeout', '--to'], type=int, - help='Maximum number of seconds to wait for device method result.') - context.argument('method_connect_timeout', options_list=['--method-connect-timeout', '--mct'], type=int, - help='Maximum number of seconds to wait on device connection.') - context.argument('method_response_timeout', options_list=['--method-response-timeout', '--mrt'], type=int, - help='Maximum number of seconds to wait for device method result.') - context.argument('auth_method', options_list=['--auth-method', '--am'], - arg_type=get_enum_type(DeviceAuthType), - help='The authorization type an entity is to be created with.') - context.argument('metric_type', options_list=['--metric-type', '--mt'], arg_type=get_enum_type(MetricType), - help='Indicates which metric collection should be used to lookup a metric.') - context.argument('metric_id', options_list=['--metric-id', '-m'], - help='Target metric for evaluation.') - context.argument('yes', options_list=['--yes', '-y'], - arg_type=get_three_state_flag(), - help='Skip user prompts. Indicates acceptance of dependency installation (if required). ' - 'Used primarily for automation scenarios. Default: false') - context.argument('repair', options_list=['--repair', '-r'], - arg_type=get_three_state_flag(), - help='Reinstall uamqp dependency compatible with extension version. Default: false') - context.argument('repo_endpoint', options_list=['--endpoint', '-e'], help='IoT Plug and Play endpoint.') - context.argument('repo_id', options_list=['--repo-id', '-r'], help='IoT Plug and Play repository Id.') - context.argument('consumer_group', options_list=['--consumer-group', '--cg', '-c'], - help='Specify the consumer group to use when connecting to event hub endpoint.') - context.argument('enqueued_time', options_list=['--enqueued-time', '--et', '-e'], type=int, - help='Indicates the time that should be used as a starting point to read messages from the partitions. ' - 'Units are milliseconds since unix epoch. ' - 'If no time is indicated "now" is used.') - context.argument('content_type', options_list=['--content-type', '--ct'], - help='Specify the Content-Type of the message payload to automatically format the output to that type.') - context.argument('device_query', options_list=['--device-query', '-q'], help='Specify a custom query to filter devices.') - context.argument('edge_enabled', options_list=['--edge-enabled', '--ee'], - arg_type=get_three_state_flag(), - help='Flag indicating edge enablement.') - - with self.argument_context('iot hub') as context: - context.argument('target_json', options_list=['--json', '-j'], - help='Json to replace existing twin with. Provide file path or raw json.') - context.argument('policy_name', options_list=['--policy-name', '--pn'], - help='Shared access policy with operation permissions for target IoT Hub entity.') - context.argument('primary_thumbprint', arg_group='X.509', - options_list=['--primary-thumbprint', '--ptp'], - help='Explicit self-signed certificate thumbprint to use for primary key.') - context.argument('secondary_thumbprint', arg_group='X.509', - options_list=['--secondary-thumbprint', '--stp'], - help='Explicit self-signed certificate thumbprint to ' - 'use for secondary key.') - context.argument('valid_days', arg_group='X.509', options_list=['--valid-days', '--vd'], - type=int, - help='Generate self-signed cert and use its thumbprint. Valid ' - 'for specified number of days. Default: 365.') - context.argument('output_dir', arg_group='X.509', options_list=['--output-dir', '--od'], - help='Generate self-signed cert and use its thumbprint. ' - 'Output to specified target directory') - - with self.argument_context('iot hub job') as context: - context.argument('job_id', options_list=['--job-id'], - help='IoT Hub job Id.') - context.argument('job_status', options_list=['--job-status', '--js'], - help='The status of a scheduled job.', - arg_type=get_enum_type(JobStatusType)) - context.argument('job_type', options_list=['--job-type', '--jt'], - help='The type of scheduled job.', - arg_type=get_enum_type(JobType)) - context.argument('query_condition', options_list=['--query-condition', '-q'], - help='Condition for device query to get devices to execute the job on. ' - 'Required if job type is scheduleDeviceMethod or scheduleUpdateTwin. ' - 'Note: The service will prefix "SELECT * FROM devices WHERE " to the input') - context.argument('start_time', options_list=['--start-time', '--start'], - help='The scheduled start of the job in ISO 8601 date time format. ' - 'If no start time is provided, the job is queued for asap execution.') - context.argument('ttl', options_list=['--ttl'], type=int, - help='Max execution time in seconds, before job is terminated.') - context.argument('twin_patch', options_list=['--twin-patch', '--patch'], - help='The desired twin patch. Provide file path or raw json.') - context.argument('wait', options_list=['--wait', '-w'], - arg_type=get_three_state_flag(), - help='Block until the created job is in a completed, failed or cancelled state. ' - 'Will regularly poll on interval specified by --poll-interval.') - context.argument('poll_interval', options_list=['--poll-interval', '--interval'], type=int, - help='Interval in seconds that job status will be checked if --wait flag is passed in.') - context.argument('poll_duration', options_list=['--poll-duration', '--duration'], type=int, - help='Total duration in seconds where job status will be checked if --wait flag is passed in.') - - with self.argument_context('iot hub job create') as context: - context.argument('job_type', options_list=['--job-type', '--jt'], - help='The type of scheduled job.', - arg_type=get_enum_type(JobCreateType)) - - with self.argument_context('iot hub monitor-events') as context: - context.argument('timeout', arg_type=event_timeout_type) - context.argument('properties', arg_type=event_msg_prop_type) - - with self.argument_context('iot hub monitor-feedback') as context: - context.argument('wait_on_id', options_list=['--wait-on-msg', '-w'], - help='Feedback monitor will block until a message with specific id (uuid) is received.') - - with self.argument_context('iot hub device-identity') as context: - context.argument('status', options_list=['--status', '--sta'], - arg_type=get_enum_type(EntityStatusType), - help='Set device status upon creation.') - context.argument('status_reason', options_list=['--status-reason', '--star'], - help='Description for device status.') - - with self.argument_context('iot hub device-identity create') as context: - context.argument('force', options_list=['--force', '-f'], - help='Overwrites the non-edge device\'s parent device.') - context.argument('set_parent_id', options_list=['--set-parent', '--pd'], help='Id of edge device.') - context.argument('add_children', options_list=['--add-children', '--cl'], - help='Child device list (comma separated) includes only non-edge devices.') - - with self.argument_context('iot hub device-identity export') as context: - context.argument('blob_container_uri', - options_list=['--blob-container-uri', '--bcu'], - help='Blob Shared Access Signature URI with write access to ' - 'a blob container. This is used to output the status of the ' - 'job and the results.') - context.argument('include_keys', - options_list=['--include-keys', '--ik'], - arg_type=get_three_state_flag(), - help='If set, keys are exported normally. Otherwise, keys are ' - 'set to null in export output.') - - with self.argument_context('iot hub device-identity import') as context: - context.argument('input_blob_container_uri', - options_list=['--input-blob-container-uri', '--ibcu'], - help='Blob Shared Access Signature URI with read access to a blob ' - 'container. This blob contains the operations to be performed on ' - 'the identity registry ') - context.argument('output_blob_container_uri', - options_list=['--output-blob-container-uri', '--obcu'], - help='Blob Shared Access Signature URI with write access ' - 'to a blob container. This is used to output the status of ' - 'the job and the results.') - - with self.argument_context('iot hub device-identity get-parent') as context: - context.argument('device_id', help='Id of non-edge device.') - - with self.argument_context('iot hub device-identity set-parent') as context: - context.argument('device_id', help='Id of non-edge device.') - context.argument('parent_id', options_list=['--parent-device-id', '--pd'], help='Id of edge device.') - context.argument('force', options_list=['--force', '-f'], - help='Overwrites the non-edge device\'s parent device.') - - with self.argument_context('iot hub device-identity add-children') as context: - context.argument('device_id', help='Id of edge device.') - context.argument('child_list', options_list=['--child-list', '--cl'], - help='Child device list (comma separated) includes only non-edge devices.') - context.argument('force', options_list=['--force', '-f'], - help='Overwrites the non-edge device\'s parent device.') - - with self.argument_context('iot hub device-identity remove-children') as context: - context.argument('device_id', help='Id of edge device.') - context.argument('child_list', options_list=['--child-list', '--cl'], - help='Child device list (comma separated) includes only non-edge devices.') - context.argument('remove_all', options_list=['--remove-all', '-a'], help='To remove all children.') - - with self.argument_context('iot hub distributed-tracing update') as context: - context.argument('sampling_mode', options_list=['--sampling-mode', '--sm'], - help='Turns sampling for distributed tracing on and off. 1 is On and, 2 is Off.', - arg_type=get_enum_type(DistributedTracingSamplingModeType)) - context.argument('sampling_rate', options_list=['--sampling-rate', '--sr'], - help='Controls the amount of messages sampled for adding trace context. This value is' - 'a percentage. Only values from 0 to 100 (inclusive) are permitted.') - - with self.argument_context('iot hub device-identity list-children') as context: - context.argument('device_id', help='Id of edge device.') - - with self.argument_context('iot hub query') as context: - context.argument('query_command', options_list=['--query-command', '-q'], - help='User query to be executed.') - context.argument('top', options_list=['--top'], type=int, - help='Maximum number of elements to return. By default query has no cap.') - - with self.argument_context('iot device') as context: - context.argument('data', options_list=['--data', '--da'], help='Message body.') - context.argument('properties', options_list=['--properties', '--props', '-p'], - help=info_param_properties_device()) - context.argument('msg_count', options_list=['--msg-count', '--mc'], type=int, - help='Number of device messages to send to IoT Hub.') - context.argument('msg_interval', options_list=['--msg-interval', '--mi'], type=int, - help='Delay in seconds between device-to-cloud messages.') - context.argument('receive_settle', options_list=['--receive-settle', '--rs'], - arg_type=get_enum_type(SettleType), - help='Indicates how to settle received cloud-to-device messages. ' - 'Supported with HTTP only.') - context.argument('protocol_type', options_list=['--protocol', '--proto'], - arg_type=get_enum_type(ProtocolType), - help='Indicates device-to-cloud message protocol') - context.argument('qos', arg_type=qos_type) - - with self.argument_context('iot device simulate') as context: - context.argument('properties', options_list=['--properties', '--props', '-p'], - help=info_param_properties_device(include_http=True)) - - with self.argument_context('iot device c2d-message') as context: - context.argument('ack', options_list=['--ack'], arg_type=get_enum_type(AckType), - help='Request the delivery of per-message feedback regarding the final state of that message. ' - 'The description of ack values is as follows. ' - 'Positive: If the c2d message reaches the Completed state, IoT Hub generates a feedback message. ' - 'Negative: If the c2d message reaches the Dead lettered state, IoT Hub generates a feedback message. ' - 'Full: IoT Hub generates a feedback message in either case. ' - 'By default, no ack is requested.') - context.argument('correlation_id', options_list=['--correlation-id', '--cid'], - help='The correlation Id associated with the C2D message.') - context.argument('properties', options_list=['--properties', '--props', '-p'], - help=info_param_properties_device(include_mqtt=False)) - context.argument('expiry_time_utc', options_list=['--expiry-time-utc', '--expiry'], type=int, - help='Units are milliseconds since unix epoch. ' - 'If no time is indicated the default IoT Hub C2D message TTL is used.') - context.argument('message_id', options_list=['--message-id', '--mid'], - help='The C2D message Id. If no message Id is provided a UUID will be generated.') - context.argument('user_id', options_list=['--user-id', '--uid'], - help='The C2D message, user Id property.') - context.argument('lock_timeout', options_list=['--lock-timeout', '--lt'], type=int, - help='Specifies the amount of time a message will be invisible to other receive calls.') - context.argument('content_type', options_list=['--content-type', '--ct'], - help='The content type associated with the C2D message.') - context.argument('content_encoding', options_list=['--content-encoding', '--ce'], - help='The content encoding associated with the C2D message.') - - with self.argument_context('iot device c2d-message send') as context: - context.argument('wait_on_feedback', options_list=['--wait', '-w'], - arg_type=get_three_state_flag(), - help='If set the c2d send operation will block until device feedback has been received.') - - with self.argument_context('iot device upload-file') as context: - context.argument('file_path', options_list=['--file-path', '--fp'], - help='Path to file for upload.') - context.argument('content_type', options_list=['--content-type', '--ct'], - help='MIME Type of file.') - - with self.argument_context('iot hub configuration') as context: - context.argument('config_id', options_list=['--config-id', '-c'], - help='Target device configuration name.') - context.argument('target_condition', options_list=['--target-condition', '--tc', '-t'], - help='Target condition in which a device configuration applies to.') - context.argument('priority', options_list=['--priority', '--pri'], - help='Weight of the device configuration in case of competing rules (highest wins).') - context.argument('content', options_list=['--content', '-k'], - help='Device configuration content. Provide file path or raw json.') - context.argument('metrics', options_list=['--metrics', '-m'], - help='Device configuration metric definitions. Provide file path or raw json.') - context.argument('labels', options_list=['--labels', '--lab'], - help="Map of labels to be applied to target configuration. " - "Format example: {\"key0\":\"value0\", \"key1\":\"value1\"}") - context.argument('top', options_list=['--top'], type=int, - help='Maximum number of configurations to return.') - - with self.argument_context('iot edge') as context: - context.argument('config_id', options_list=['--deployment-id', '-d'], - help='Target deployment name.') - context.argument('target_condition', options_list=['--target-condition', '--tc', '-t'], - help='Target condition in which an Edge deployment applies to.') - context.argument('priority', options_list=['--priority', '--pri'], - help='Weight of deployment in case of competing rules (highest wins).') - context.argument('content', options_list=['--content', '-k'], - help='IoT Edge deployment content. Provide file path or raw json.') - context.argument('metrics', options_list=['--metrics', '-m'], - help='IoT Edge deployment metric definitions. Provide file path or raw json.') - context.argument('labels', options_list=['--labels', '--lab'], - help="Map of labels to be applied to target deployment. " - "Use the following format: '{\"key0\":\"value0\", \"key1\":\"value1\"}'") - context.argument('top', options_list=['--top'], type=int, - help='Maximum number of deployments to return.') - context.argument('layered', options_list=['--layered'], - arg_type=get_three_state_flag(), - help='Layered deployments allow you to define desired properties in $edgeAgent, $edgeHub and user ' - 'modules that will layer on top of a base deployment. For example the routes specified in a layered ' - 'deployment will merge with routes of the base deployment. Routes with the same name will be ' - 'overwritten based on deployment priority.' - ) - - with self.argument_context('iot dps') as context: - context.argument('dps_name', help='Name of the Azure IoT Hub device provisioning service') - context.argument('initial_twin_properties', - options_list=['--initial-twin-properties', '--props'], - help='Initial twin properties') - context.argument('initial_twin_tags', options_list=['--initial-twin-tags', '--tags'], - help='Initial twin tags') - context.argument('iot_hub_host_name', options_list=['--iot-hub-host-name', '--hn'], - help='Host name of target IoT Hub') - context.argument('provisioning_status', options_list=['--provisioning-status', '--ps'], - arg_type=get_enum_type(EntityStatusType), - help='Enable or disable enrollment entry') - context.argument('certificate_path', - options_list=['--certificate-path', '--cp'], - help='The path to the file containing the primary certificate.') - context.argument('secondary_certificate_path', - options_list=['--secondary-certificate-path', '--scp'], - help='The path to the file containing the secondary certificate') - context.argument('remove_certificate', - options_list=['--remove-certificate', '--rc'], - help='Remove current primary certificate', - arg_type=get_three_state_flag()) - context.argument('remove_secondary_certificate', - options_list=['--remove-secondary-certificate', '--rsc'], - help='Remove current secondary certificate', - arg_type=get_three_state_flag()) - context.argument('reprovision_policy', options_list=['--reprovision-policy', '--rp'], - arg_type=get_enum_type(ReprovisionType), - help='Device data to be handled on re-provision to different Iot Hub.') - context.argument('allocation_policy', options_list=['--allocation-policy', '--ap'], - arg_type=get_enum_type(AllocationType), - help='Type of allocation for device assigned to the Hub.') - context.argument('iot_hubs', options_list=['--iot-hubs', '--ih'], - help='Host name of target IoT Hub. Use space-separated list for multiple IoT Hubs.') - - with self.argument_context('iot dps enrollment') as context: - context.argument('enrollment_id', help='ID of device enrollment record') - context.argument('device_id', help='IoT Hub Device ID') - context.argument('primary_key', options_list=['--primary-key', '--pk'], - help='The primary symmetric shared access key stored in base64 format. ') - context.argument('secondary_key', options_list=['--secondary-key', '--sk'], - help='The secondary symmetric shared access key stored in base64 format. ') - - with self.argument_context('iot dps enrollment create') as context: - context.argument('attestation_type', options_list=['--attestation-type', '--at'], - arg_type=get_enum_type(AttestationType), help='Attestation Mechanism') - context.argument('certificate_path', - options_list=['--certificate-path', '--cp'], - help='The path to the file containing the primary certificate. ' - 'When choosing x509 as attestation type, ' - 'one of the certificate path is required.') - context.argument('secondary_certificate_path', - options_list=['--secondary-certificate-path', '--scp'], - help='The path to the file containing the secondary certificate. ' - 'When choosing x509 as attestation type, ' - 'one of the certificate path is required.') - context.argument('endorsement_key', options_list=['--endorsement-key', '--ek'], - help='TPM endorsement key for a TPM device. ' - 'When choosing tpm as attestation type, endorsement key is required.') - - with self.argument_context('iot dps enrollment update') as context: - context.argument('endorsement_key', options_list=['--endorsement-key', '--ek'], - help='TPM endorsement key for a TPM device.') - - with self.argument_context('iot dps enrollment-group') as context: - context.argument('enrollment_id', help='ID of enrollment group') - context.argument('primary_key', options_list=['--primary-key', '--pk'], - help='The primary symmetric shared access key stored in base64 format. ') - context.argument('secondary_key', options_list=['--secondary-key', '--sk'], - help='The secondary symmetric shared access key stored in base64 format. ') - context.argument('certificate_path', - options_list=['--certificate-path', '--cp'], - help='The path to the file containing the primary certificate. ' - 'If attestation with an intermediate certificate is desired then a certificate path must be provided.') - context.argument('secondary_certificate_path', - options_list=['--secondary-certificate-path', '--scp'], - help='The path to the file containing the secondary certificate. ' - 'If attestation with an intermediate certificate is desired then a certificate path must be provided.') - context.argument('root_ca_name', - options_list=['--root-ca-name', '--ca-name', '--cn'], - help='The name of the primary root CA certificate. ' - 'If attestation with a root CA certificate is desired then a root ca name must be provided.') - context.argument('secondary_root_ca_name', - options_list=['--secondary-root-ca-name', '--secondary-ca-name', '--scn'], - help='The name of the secondary root CA certificate. ' - 'If attestation with a root CA certificate is desired then a root ca name must be provided.') - - with self.argument_context('iot dps registration') as context: - context.argument('registration_id', help='ID of device registration') - - with self.argument_context('iot dps registration list') as context: - context.argument('enrollment_id', help='ID of enrollment group') - - # TODO: Remove 'iotcentral', non-conventional and no reuse of arguments. 'iot central' is the go forward. - with self.argument_context('iotcentral app monitor-events') as context: - context.argument('device_id', options_list=['--device-id', '-d'], help='Target Device.') - context.argument('app_id', options_list=['--app-id'], help='Target App.') - context.argument('timeout', options_list=['--timeout', '--to', '-t'], type=int, - help='Maximum seconds to maintain connection without receiving message. Use 0 for infinity. ') - context.argument('consumer_group', options_list=['--consumer-group', '--cg', '-c'], - help='Specify the consumer group to use when connecting to event hub endpoint.') - context.argument('enqueued_time', options_list=['--enqueued-time', '--et', '-e'], type=int, - help='Indicates the time that should be used as a starting point to read messages from the partitions. ' - 'Units are milliseconds since unix epoch. ' - 'If no time is indicated "now" is used.') - context.argument('properties', arg_type=event_msg_prop_type) - context.argument('content_type', options_list=['--content-type', '--ct'], - help='Specify the Content-Type of the message payload to automatically format the output to that type.') - context.argument('repair', options_list=['--repair', '-r'], - arg_type=get_three_state_flag(), - help='Reinstall uamqp dependency compatible with extension version. Default: false') - context.argument('central_api_uri', options_list=['--central-api-uri'], - help='IoT Central API override. For use with environments other than production.') - context.argument('yes', options_list=['--yes', '-y'], - arg_type=get_three_state_flag(), - help='Skip user prompts. Indicates acceptance of dependency installation (if required). ' - 'Used primarily for automation scenarios. Default: false') - - with self.argument_context('iotcentral device-twin show') as context: - context.argument('device_id', options_list=['--device-id', '-d'], help='Target Device.') - context.argument('app_id', options_list=['--app-id'], help='Target App.') - context.argument('central_api_uri', options_list=['--central-api-uri'], - help='IoT Central API override. For use with environments other than production.') - - with self.argument_context('iot central') as context: - context.argument('app_id', options_list=['--app-id'], help='Target App.') - context.argument('central_api_uri', options_list=['--central-api-uri'], - help='IoT Central API override. For use with environments other than production.') - - with self.argument_context('iot central app monitor-events') as context: - context.argument('timeout', arg_type=event_timeout_type) - context.argument('properties', arg_type=event_msg_prop_type) - - with self.argument_context('iot dt') as context: - context.argument('repo_login', options_list=['--repo-login', '--rl'], - help='This command supports an entity connection string with rights to perform action. ' - 'Use to avoid PnP endpoint and repository name if repository is private. ' - 'If both an entity connection string and name are provided the connection string takes priority.') - context.argument('interface', options_list=['--interface', '-i'], - help='Target interface name. This should be the name of the interface not the urn-id.') - context.argument('command_name', options_list=['--command-name', '--cn'], - help='IoT Plug and Play interface command name.') - context.argument('command_payload', options_list=['--command-payload', '--cp', '--cv'], - help='IoT Plug and Play interface command payload. ' - 'Content can be directly input or extracted from a file path.') - context.argument('interface_payload', options_list=['--interface-payload', '--ip', '--iv'], - help='IoT Plug and Play interface payload. ' - 'Content can be directly input or extracted from a file path.') - context.argument('source_model', options_list=['--source', '-s'], - help='Choose your option to get model definition from specified source. ', - arg_type=get_enum_type(ModelSourceType)) - context.argument('schema', options_list=['--schema'], - help='Show interface with entity schema.') - - with self.argument_context('iot dt monitor-events') as context: - context.argument('timeout', arg_type=event_timeout_type) - context.argument('properties', arg_type=event_msg_prop_type) - - with self.argument_context('iot pnp') as context: - context.argument('model', options_list=['--model', '-m'], - help='Target capability-model urn-id. Example: urn:example:capabilityModels:Mxchip:1') - context.argument('interface', options_list=['--interface', '-i'], - help='Target interface urn-id. Example: urn:example:interfaces:MXChip:1') - - with self.argument_context('iot pnp interface') as context: - context.argument('interface_definition', options_list=['--definition', '--def'], - help='IoT Plug and Play interface definition written in PPDL (JSON-LD). ' - 'Can be directly input or a file path where the content is extracted.') - - with self.argument_context('iot pnp interface list') as context: - context.argument('search_string', options_list=['--search', '--ss'], - help='Searches IoT Plug and Play interfaces for given string in the' - ' \"Description, DisplayName, comment and Id\".') - context.argument('top', type=int, options_list=['--top'], - help='Maximum number of interface to return.') - - with self.argument_context('iot pnp capability-model') as context: - context.argument('model_definition', options_list=['--definition', '--def'], - help='IoT Plug and Play capability-model definition written in PPDL (JSON-LD). ' - 'Can be directly input or a file path where the content is extracted.') - - with self.argument_context('iot pnp capability-model show') as context: - context.argument('expand', options_list=['--expand'], - help='Indicates whether to expand the device capability model\'s' - ' interface definitions or not.') - - with self.argument_context('iot pnp capability-model list') as context: - context.argument('search_string', options_list=['--search', '--ss'], - help='Searches IoT Plug and Play models for given string in the' - ' \"Description, DisplayName, comment and Id\".') - context.argument('top', type=int, options_list=['--top'], - help='Maximum number of capability-model to return.') + with self.argument_context("iot") as context: + context.argument( + "login", + options_list=["--login", "-l"], + validator=mode2_iot_login_handler, + help="This command supports an entity connection string with rights to perform action. " + 'Use to avoid session login via "az login". ' + "If both an entity connection string and name are provided the connection string takes priority.", + ) + context.argument("resource_group_name", arg_type=resource_group_name_type) + context.argument( + "hub_name", options_list=["--hub-name", "-n"], arg_type=hub_name_type + ) + context.argument( + "device_id", options_list=["--device-id", "-d"], help="Target Device." + ) + context.argument( + "module_id", options_list=["--module-id", "-m"], help="Target Module." + ) + context.argument( + "key_type", + options_list=["--key-type", "--kt"], + arg_type=get_enum_type(KeyType), + help="Shared access policy key type for authentication.", + ) + context.argument( + "policy_name", + options_list=["--policy-name", "--pn"], + help="Shared access policy to use for authentication.", + ) + context.argument( + "duration", + options_list=["--duration", "--du"], + type=int, + help="Valid token duration in seconds.", + ) + context.argument( + "etag", + options_list=["--etag", "-e"], + help="Etag or entity tag corresponding to the last state of the resource. " + "If no etag is provided the value '*' is used." + ) + context.argument( + "top", + type=int, + options_list=["--top"], + help="Maximum number of elements to return. Use -1 for unlimited.", + ) + context.argument( + "method_name", + options_list=["--method-name", "--mn"], + help="Target method for invocation.", + ) + context.argument( + "method_payload", + options_list=["--method-payload", "--mp"], + help="Json payload to be passed to method. Must be file path or raw json.", + ) + context.argument( + "timeout", + options_list=["--timeout", "--to"], + type=int, + help="Maximum number of seconds to wait for device method result.", + ) + context.argument( + "method_connect_timeout", + options_list=["--method-connect-timeout", "--mct"], + type=int, + help="Maximum number of seconds to wait on device connection.", + ) + context.argument( + "method_response_timeout", + options_list=["--method-response-timeout", "--mrt"], + type=int, + help="Maximum number of seconds to wait for device method result.", + ) + context.argument( + "auth_method", + options_list=["--auth-method", "--am"], + arg_type=get_enum_type(DeviceAuthType), + help="The authorization type an entity is to be created with.", + ) + context.argument( + "metric_type", + options_list=["--metric-type", "--mt"], + arg_type=get_enum_type(MetricType), + help="Indicates which metric collection should be used to lookup a metric.", + ) + context.argument( + "metric_id", + options_list=["--metric-id", "-m"], + help="Target metric for evaluation.", + ) + context.argument( + "yes", + options_list=["--yes", "-y"], + arg_type=get_three_state_flag(), + help="Skip user prompts. Indicates acceptance of dependency installation (if required). " + "Used primarily for automation scenarios. Default: false", + ) + context.argument( + "repair", + options_list=["--repair", "-r"], + arg_type=get_three_state_flag(), + help="Reinstall uamqp dependency compatible with extension version. Default: false", + ) + context.argument( + "consumer_group", + options_list=["--consumer-group", "--cg", "-c"], + help="Specify the consumer group to use when connecting to event hub endpoint.", + ) + context.argument( + "enqueued_time", + options_list=["--enqueued-time", "--et", "-e"], + type=int, + help="Indicates the time that should be used as a starting point to read messages from the partitions. " + "Units are milliseconds since unix epoch. " + 'If no time is indicated "now" is used.', + ) + context.argument( + "content_type", + options_list=["--content-type", "--ct"], + help="Specify the Content-Type of the message payload to automatically format the output to that type.", + ) + context.argument( + "device_query", + options_list=["--device-query", "-q"], + help="Specify a custom query to filter devices.", + ) + context.argument( + "edge_enabled", + options_list=["--edge-enabled", "--ee"], + arg_type=get_three_state_flag(), + help="Flag indicating edge enablement.", + ) + + with self.argument_context("iot hub") as context: + context.argument( + "target_json", + options_list=["--json", "-j"], + help="Json to replace existing twin with. Provide file path or raw json.", + ) + context.argument( + "policy_name", + options_list=["--policy-name", "--pn"], + help="Shared access policy with operation permissions for target IoT Hub entity.", + ) + context.argument( + "primary_thumbprint", + arg_group="X.509", + options_list=["--primary-thumbprint", "--ptp"], + help="Explicit self-signed certificate thumbprint to use for primary key.", + ) + context.argument( + "secondary_thumbprint", + arg_group="X.509", + options_list=["--secondary-thumbprint", "--stp"], + help="Explicit self-signed certificate thumbprint to " + "use for secondary key.", + ) + context.argument( + "valid_days", + arg_group="X.509", + options_list=["--valid-days", "--vd"], + type=int, + help="Generate self-signed cert and use its thumbprint. Valid " + "for specified number of days. Default: 365.", + ) + context.argument( + "output_dir", + arg_group="X.509", + options_list=["--output-dir", "--od"], + help="Generate self-signed cert and use its thumbprint. " + "Output to specified target directory", + ) + context.argument( + "tags", arg_group="Twin Patch", options_list=["--tags"], help="Twin tags." + ) + context.argument( + "desired", + arg_group="Twin Patch", + options_list=["--desired"], + help="Twin desired properties.", + ) + + with self.argument_context("iot hub connection-string") as context: + context.argument( + "show_all", + options_list=["--show-all", "--all"], + help="Show all shared access policies for the respective IoT Hub.", + ) + context.argument( + "default_eventhub", + arg_type=get_three_state_flag(), + options_list=["--default-eventhub", "--eh"], + help="Flag indicating the connection string returned is for the default EventHub endpoint. Default: false.", + ) + + with self.argument_context("iot hub job") as context: + context.argument("job_id", options_list=["--job-id"], help="IoT Hub job Id.") + context.argument( + "job_status", + options_list=["--job-status", "--js"], + help="The status of a scheduled job.", + arg_type=get_enum_type(JobStatusType), + ) + context.argument( + "job_type", + options_list=["--job-type", "--jt"], + help="The type of scheduled job.", + arg_type=get_enum_type(JobType), + ) + context.argument( + "query_condition", + options_list=["--query-condition", "-q"], + help="Condition for device query to get devices to execute the job on. " + "Required if job type is scheduleDeviceMethod or scheduleUpdateTwin. " + 'Note: The service will prefix "SELECT * FROM devices WHERE " to the input', + ) + context.argument( + "start_time", + options_list=["--start-time", "--start"], + help="The scheduled start of the job in ISO 8601 date time format. " + "If no start time is provided, the job is queued for asap execution.", + ) + context.argument( + "ttl", + options_list=["--ttl"], + type=int, + help="Max execution time in seconds, before job is terminated.", + ) + context.argument( + "twin_patch", + options_list=["--twin-patch", "--patch"], + help="The desired twin patch. Provide file path or raw json.", + ) + context.argument( + "wait", + options_list=["--wait", "-w"], + arg_type=get_three_state_flag(), + help="Block until the created job is in a completed, failed or cancelled state. " + "Will regularly poll on interval specified by --poll-interval.", + ) + context.argument( + "poll_interval", + options_list=["--poll-interval", "--interval"], + type=int, + help="Interval in seconds that job status will be checked if --wait flag is passed in.", + ) + context.argument( + "poll_duration", + options_list=["--poll-duration", "--duration"], + type=int, + help="Total duration in seconds where job status will be checked if --wait flag is passed in.", + ) + + with self.argument_context("iot hub job create") as context: + context.argument( + "job_type", + options_list=["--job-type", "--jt"], + help="The type of scheduled job.", + arg_type=get_enum_type(JobCreateType), + ) + + with self.argument_context("iot hub monitor-events") as context: + context.argument("timeout", arg_type=event_timeout_type) + context.argument("properties", arg_type=event_msg_prop_type) + context.argument( + "interface", + options_list=["--interface", "-i"], + help="Target interface identifier to filter on. For example: dtmi:com:example:TemperatureController;1", + ) + + with self.argument_context("iot hub monitor-feedback") as context: + context.argument( + "wait_on_id", + options_list=["--wait-on-msg", "-w"], + help="Feedback monitor will block until a message with specific id (uuid) is received.", + ) + + with self.argument_context("iot hub device-identity") as context: + context.argument( + "status", + options_list=["--status", "--sta"], + arg_type=get_enum_type(EntityStatusType), + help="Set device status upon creation.", + ) + context.argument( + "status_reason", + options_list=["--status-reason", "--star"], + help="Description for device status.", + ) + + with self.argument_context('iot hub device-identity update') as context: + context.argument( + "primary_key", + options_list=["--primary-key", "--pk"], + help="The primary symmetric shared access key stored in base64 format.", + ) + context.argument( + "secondary_key", + options_list=["--secondary-key", "--sk"], + help="The secondary symmetric shared access key stored in base64 format.", + ) + + with self.argument_context("iot hub device-identity create") as context: + context.argument( + "force", + options_list=["--force", "-f"], + arg_type=get_three_state_flag(), + help="Overwrites the device's parent device. " + "This command parameter has been deprecated and will be removed " + "in a future release. Use 'az iot hub device-identity parent set' instead.", + deprecate_info=context.deprecate() + ) + context.argument( + "set_parent_id", + options_list=["--set-parent", "--pd"], + help="Id of edge device. " + "This command parameter has been deprecated and will be removed " + "in a future release. Use 'az iot hub device-identity parent set' instead.", + deprecate_info=context.deprecate() + ) + context.argument( + "add_children", + options_list=["--add-children", "--cl"], + help="Child device list (comma separated). " + "This command parameter has been deprecated and will be removed " + "in a future release. Use 'az iot hub device-identity children add' instead.", + deprecate_info=context.deprecate() + ) + + with self.argument_context('iot hub device-identity renew-key') as context: + context.argument( + "renew_key_type", + options_list=["--key-type", "--kt"], + arg_type=get_enum_type(RenewKeyType), + help="Target key type to regenerate." + ) + + with self.argument_context("iot hub device-identity export") as context: + context.argument( + "blob_container_uri", + options_list=["--blob-container-uri", "--bcu"], + help="Blob Shared Access Signature URI with write, read, and delete access to " + "a blob container. This is used to output the status of the " + "job and the results. Note: when using Identity-based authentication an " + "https:// URI is still required. Input for this argument can be inline or from a file path.", + ) + context.argument( + "include_keys", + options_list=["--include-keys", "--ik"], + arg_type=get_three_state_flag(), + help="If set, keys are exported normally. Otherwise, keys are " + "set to null in export output.", + ) + context.argument( + "storage_authentication_type", + options_list=["--auth-type", "--storage-authentication-type"], + arg_type=get_enum_type(AuthenticationType), + help="Authentication type for communicating with the storage container.", + ) + + with self.argument_context("iot hub device-identity import") as context: + context.argument( + "input_blob_container_uri", + options_list=["--input-blob-container-uri", "--ibcu"], + help="Blob Shared Access Signature URI with read access to a blob " + "container. This blob contains the operations to be performed on " + "the identity registry. Note: when using Identity-based authentication " + "an https:// URI is still required. Input for this argument can be inline " + "or from a file path.", + ) + context.argument( + "output_blob_container_uri", + options_list=["--output-blob-container-uri", "--obcu"], + help="Blob Shared Access Signature URI with write access " + "to a blob container. This is used to output the status of " + "the job and the results. Note: when using Identity-based " + "authentication an https:// URI is still required. Input for " + "this argument can be inline or from a file path.", + ) + context.argument( + "storage_authentication_type", + options_list=["--auth-type", "--storage-authentication-type"], + arg_type=get_enum_type(AuthenticationType), + help="Authentication type for communicating with the storage container.", + ) + + with self.argument_context("iot hub device-identity get-parent") as context: + context.argument("device_id", help="Id of device.") + + with self.argument_context("iot hub device-identity set-parent") as context: + context.argument("device_id", help="Id of device.") + context.argument( + "parent_id", + options_list=["--parent-device-id", "--pd"], + help="Id of edge device.", + ) + context.argument( + "force", + options_list=["--force", "-f"], + help="Overwrites the device's parent device.", + ) + + with self.argument_context("iot hub device-identity add-children") as context: + context.argument("device_id", help="Id of edge device.") + context.argument( + "child_list", + options_list=["--child-list", "--cl"], + help="Child device list (comma separated).", + ) + context.argument( + "force", + options_list=["--force", "-f"], + help="Overwrites the child device's parent device.", + ) + + with self.argument_context("iot hub device-identity remove-children") as context: + context.argument("device_id", help="Id of edge device.") + context.argument( + "child_list", + options_list=["--child-list", "--cl"], + help="Child device list (comma separated).", + ) + context.argument( + "remove_all", + options_list=["--remove-all", "-a"], + help="To remove all children.", + ) + + with self.argument_context("iot hub device-identity list-children") as context: + context.argument("device_id", help="Id of edge device.") + + with self.argument_context("iot hub device-identity parent set") as context: + context.argument( + "parent_id", + options_list=["--parent-device-id", "--pd"], + help="Id of edge device.", + ) + context.argument( + "force", + options_list=["--force", "-f"], + help="Overwrites the device's parent device.", + ) + + with self.argument_context("iot hub device-identity children") as context: + context.argument("device_id", help="Id of edge device.") + context.argument("child_list", arg_type=children_list_prop_type) + + with self.argument_context("iot hub device-identity children add") as context: + context.argument( + "force", + options_list=["--force", "-f"], + help="Overwrites the child device's parent device.", + ) + + with self.argument_context("iot hub device-identity children remove") as context: + context.argument( + "remove_all", + options_list=["--remove-all", "-a"], + help="To remove all children.", + ) + + with self.argument_context("iot hub distributed-tracing update") as context: + context.argument( + "sampling_mode", + options_list=["--sampling-mode", "--sm"], + help="Turns sampling for distributed tracing on and off. 1 is On and, 2 is Off.", + arg_type=get_enum_type(DistributedTracingSamplingModeType), + ) + context.argument( + "sampling_rate", + options_list=["--sampling-rate", "--sr"], + help="Controls the amount of messages sampled for adding trace context. This value is" + "a percentage. Only values from 0 to 100 (inclusive) are permitted.", + ) + + with self.argument_context("iot hub query") as context: + context.argument( + "query_command", + options_list=["--query-command", "-q"], + help="User query to be executed.", + ) + context.argument( + "top", + options_list=["--top"], + type=int, + help="Maximum number of elements to return. By default query has no cap.", + ) + + with self.argument_context("iot device") as context: + context.argument("data", options_list=["--data", "--da"], help="Message body.") + context.argument( + "properties", + options_list=["--properties", "--props", "-p"], + help=info_param_properties_device(), + ) + context.argument( + "msg_count", + options_list=["--msg-count", "--mc"], + type=int, + help="Number of device messages to send to IoT Hub.", + ) + context.argument( + "msg_interval", + options_list=["--msg-interval", "--mi"], + type=int, + help="Delay in seconds between device-to-cloud messages.", + ) + context.argument( + "receive_settle", + options_list=["--receive-settle", "--rs"], + arg_type=get_enum_type(SettleType), + help="Indicates how to settle received cloud-to-device messages. " + "Supported with HTTP only.", + ) + context.argument( + "protocol_type", + options_list=["--protocol", "--proto"], + arg_type=get_enum_type(ProtocolType), + help="Indicates device-to-cloud message protocol", + ) + context.argument("qos", arg_type=qos_type) + + with self.argument_context("iot device simulate") as context: + context.argument( + "properties", + options_list=["--properties", "--props", "-p"], + help=info_param_properties_device(include_http=True), + ) + + with self.argument_context("iot device c2d-message") as context: + context.argument( + "correlation_id", + options_list=["--correlation-id", "--cid"], + help="The correlation Id associated with the C2D message.", + ) + context.argument( + "properties", + options_list=["--properties", "--props", "-p"], + help=info_param_properties_device(include_mqtt=False), + ) + context.argument( + "expiry_time_utc", + options_list=["--expiry-time-utc", "--expiry"], + type=int, + help="Units are milliseconds since unix epoch. " + "If no time is indicated the default IoT Hub C2D message TTL is used.", + ) + context.argument( + "message_id", + options_list=["--message-id", "--mid"], + help="The C2D message Id. If no message Id is provided a UUID will be generated.", + ) + context.argument( + "user_id", + options_list=["--user-id", "--uid"], + help="The C2D message, user Id property.", + ) + context.argument( + "lock_timeout", + options_list=["--lock-timeout", "--lt"], + type=int, + help="Specifies the amount of time a message will be invisible to other receive calls.", + ) + context.argument( + "content_type", + options_list=["--content-type", "--ct"], + help="The content type associated with the C2D message.", + ) + context.argument( + "content_encoding", + options_list=["--content-encoding", "--ce"], + help="The content encoding associated with the C2D message.", + ) + + with self.argument_context("iot device c2d-message send") as context: + context.argument( + "ack", + options_list=["--ack"], + arg_type=get_enum_type(AckType), + help="Request the delivery of per-message feedback regarding the final state of that message. " + "The description of ack values is as follows. " + "Positive: If the c2d message reaches the Completed state, IoT Hub generates a feedback message. " + "Negative: If the c2d message reaches the Dead lettered state, IoT Hub generates a feedback message. " + "Full: IoT Hub generates a feedback message in either case. " + "By default, no ack is requested.", + ) + context.argument( + "wait_on_feedback", + options_list=["--wait", "-w"], + arg_type=get_three_state_flag(), + help="If set the c2d send operation will block until device feedback has been received.", + ) + + with self.argument_context("iot device c2d-message receive") as context: + context.argument( + "abandon", + arg_group="Message Ack", + options_list=["--abandon"], + arg_type=get_three_state_flag(), + help="Abandon the cloud-to-device message after receipt.", + ) + context.argument( + "complete", + arg_group="Message Ack", + options_list=["--complete"], + arg_type=get_three_state_flag(), + help="Complete the cloud-to-device message after receipt.", + ) + context.argument( + "reject", + arg_group="Message Ack", + options_list=["--reject"], + arg_type=get_three_state_flag(), + help="Reject the cloud-to-device message after receipt.", + ) + + with self.argument_context("iot device upload-file") as context: + context.argument( + "file_path", + options_list=["--file-path", "--fp"], + help="Path to file for upload.", + ) + context.argument( + "content_type", + options_list=["--content-type", "--ct"], + help="MIME Type of file.", + ) + + with self.argument_context("iot hub configuration") as context: + context.argument( + "config_id", + options_list=["--config-id", "-c"], + help="Target device configuration name.", + ) + context.argument( + "target_condition", + options_list=["--target-condition", "--tc", "-t"], + help="Target condition in which a device configuration applies to.", + ) + context.argument( + "priority", + options_list=["--priority", "--pri"], + help="Weight of the device configuration in case of competing rules (highest wins).", + ) + context.argument( + "content", + options_list=["--content", "-k"], + help="Device configuration content. Provide file path or raw json.", + ) + context.argument( + "metrics", + options_list=["--metrics", "-m"], + help="Device configuration metric definitions. Provide file path or raw json.", + ) + context.argument( + "labels", + options_list=["--labels", "--lab"], + help="Map of labels to be applied to target configuration. " + 'Format example: {"key0":"value0", "key1":"value1"}', + ) + context.argument( + "top", + options_list=["--top"], + type=int, + help="Maximum number of configurations to return. By default all configurations are returned.", + ) + + with self.argument_context("iot edge") as context: + context.argument( + "config_id", + options_list=["--deployment-id", "-d"], + help="Target deployment name.", + ) + context.argument( + "target_condition", + options_list=["--target-condition", "--tc", "-t"], + help="Target condition in which an Edge deployment applies to.", + ) + context.argument( + "priority", + options_list=["--priority", "--pri"], + help="Weight of deployment in case of competing rules (highest wins).", + ) + context.argument( + "content", + options_list=["--content", "-k"], + help="IoT Edge deployment content. Provide file path or raw json.", + ) + context.argument( + "metrics", + options_list=["--metrics", "-m"], + help="IoT Edge deployment metric definitions. Provide file path or raw json.", + ) + context.argument( + "labels", + options_list=["--labels", "--lab"], + help="Map of labels to be applied to target deployment. " + 'Use the following format: \'{"key0":"value0", "key1":"value1"}\'', + ) + context.argument( + "top", + options_list=["--top"], + type=int, + help="Maximum number of deployments to return. By default all deployments are returned.", + ) + context.argument( + "layered", + options_list=["--layered"], + arg_type=get_three_state_flag(), + help="Layered deployments allow you to define desired properties in $edgeAgent, $edgeHub and user " + "modules that will layer on top of a base deployment. For example the routes specified in a layered " + "deployment will merge with routes of the base deployment. Routes with the same name will be " + "overwritten based on deployment priority.", + ) + context.argument( + "no_validation", + options_list=["--no-validation"], + arg_type=get_three_state_flag(), + help="Disables client side schema validation for edge deployment creation.", + ) + + with self.argument_context("iot dps") as context: + context.argument( + "dps_name", help="Name of the Azure IoT Hub device provisioning service" + ) + context.argument( + "initial_twin_properties", + options_list=["--initial-twin-properties", "--props"], + help="Initial twin properties", + ) + context.argument( + "initial_twin_tags", + options_list=["--initial-twin-tags", "--tags"], + help="Initial twin tags", + ) + context.argument( + "iot_hub_host_name", + options_list=["--iot-hub-host-name", "--hn"], + help="Host name of target IoT Hub", + ) + context.argument( + "provisioning_status", + options_list=["--provisioning-status", "--ps"], + arg_type=get_enum_type(EntityStatusType), + help="Enable or disable enrollment entry", + ) + context.argument( + "certificate_path", + options_list=["--certificate-path", "--cp"], + help="The path to the file containing the primary certificate.", + ) + context.argument( + "secondary_certificate_path", + options_list=["--secondary-certificate-path", "--scp"], + help="The path to the file containing the secondary certificate", + ) + context.argument( + "remove_certificate", + options_list=["--remove-certificate", "--rc"], + help="Remove current primary certificate", + arg_type=get_three_state_flag(), + ) + context.argument( + "remove_secondary_certificate", + options_list=["--remove-secondary-certificate", "--rsc"], + help="Remove current secondary certificate", + arg_type=get_three_state_flag(), + ) + context.argument( + "reprovision_policy", + options_list=["--reprovision-policy", "--rp"], + arg_type=get_enum_type(ReprovisionType), + help="Device data to be handled on re-provision to different Iot Hub.", + ) + context.argument( + "allocation_policy", + options_list=["--allocation-policy", "--ap"], + arg_type=get_enum_type(AllocationType), + help="Type of allocation for device assigned to the Hub.", + ) + context.argument( + "iot_hubs", + options_list=["--iot-hubs", "--ih"], + help="Host name of target IoT Hub. Use space-separated list for multiple IoT Hubs.", + ) + context.argument( + "webhook_url", + options_list=["--webhook-url", "--wh"], + help="The webhook URL used for custom allocation requests.", + ) + context.argument( + "api_version", + options_list=["--api-version", "--av"], + help="The API version of the provisioning service types sent in the custom allocation" + " request. Minimum supported version: 2018-09-01-preview.", + ) + + with self.argument_context("iot dps compute-device-key") as context: + context.argument( + "symmetric_key", + options_list=["--symmetric-key", "--key"], + help="The symmetric shared access key for the enrollment group. ", + ) + context.argument("registration_id", help="ID of device registration. ") + + with self.argument_context("iot dps enrollment") as context: + context.argument("enrollment_id", help="ID of device enrollment record") + context.argument("device_id", help="IoT Hub Device ID") + context.argument( + "primary_key", + options_list=["--primary-key", "--pk"], + help="The primary symmetric shared access key stored in base64 format. ", + ) + context.argument( + "secondary_key", + options_list=["--secondary-key", "--sk"], + help="The secondary symmetric shared access key stored in base64 format. ", + ) + + with self.argument_context("iot dps enrollment create") as context: + context.argument( + "attestation_type", + options_list=["--attestation-type", "--at"], + arg_type=get_enum_type(AttestationType), + help="Attestation Mechanism", + ) + context.argument( + "certificate_path", + options_list=["--certificate-path", "--cp"], + help="The path to the file containing the primary certificate. " + "When choosing x509 as attestation type, " + "one of the certificate path is required.", + ) + context.argument( + "secondary_certificate_path", + options_list=["--secondary-certificate-path", "--scp"], + help="The path to the file containing the secondary certificate. " + "When choosing x509 as attestation type, " + "one of the certificate path is required.", + ) + context.argument( + "endorsement_key", + options_list=["--endorsement-key", "--ek"], + help="TPM endorsement key for a TPM device. " + "When choosing tpm as attestation type, endorsement key is required.", + ) + + with self.argument_context("iot dps enrollment show") as context: + context.argument( + "show_keys", + options_list=["--show-keys", "--keys"], + arg_type=get_three_state_flag(), + help="Include attestation keys and information in enrollment results" + ) + + with self.argument_context("iot dps enrollment update") as context: + context.argument( + "endorsement_key", + options_list=["--endorsement-key", "--ek"], + help="TPM endorsement key for a TPM device.", + ) + + with self.argument_context("iot dps enrollment-group") as context: + context.argument("enrollment_id", help="ID of enrollment group") + context.argument( + "primary_key", + options_list=["--primary-key", "--pk"], + help="The primary symmetric shared access key stored in base64 format. ", + ) + context.argument( + "secondary_key", + options_list=["--secondary-key", "--sk"], + help="The secondary symmetric shared access key stored in base64 format. ", + ) + context.argument( + "certificate_path", + options_list=["--certificate-path", "--cp"], + help="The path to the file containing the primary certificate. " + "If attestation with an intermediate certificate is desired then a certificate path must be provided.", + ) + context.argument( + "secondary_certificate_path", + options_list=["--secondary-certificate-path", "--scp"], + help="The path to the file containing the secondary certificate. " + "If attestation with an intermediate certificate is desired then a certificate path must be provided.", + ) + context.argument( + "root_ca_name", + options_list=["--root-ca-name", "--ca-name", "--cn"], + help="The name of the primary root CA certificate. " + "If attestation with a root CA certificate is desired then a root ca name must be provided.", + ) + context.argument( + "secondary_root_ca_name", + options_list=["--secondary-root-ca-name", "--secondary-ca-name", "--scn"], + help="The name of the secondary root CA certificate. " + "If attestation with a root CA certificate is desired then a root ca name must be provided.", + ) + + with self.argument_context("iot dps enrollment-group show") as context: + context.argument( + "show_keys", + options_list=["--show-keys", "--keys"], + arg_type=get_three_state_flag(), + help="Include attestation keys and information in enrollment group results" + ) + + with self.argument_context("iot dps registration") as context: + context.argument("registration_id", help="ID of device registration") + + with self.argument_context("iot dps registration list") as context: + context.argument("enrollment_id", help="ID of enrollment group") diff --git a/azext_iot/_validators.py b/azext_iot/_validators.py index 8538c5538..822726a3c 100644 --- a/azext_iot/_validators.py +++ b/azext_iot/_validators.py @@ -23,9 +23,6 @@ def mode2_iot_login_handler(cmd, namespace): elif 'dps_name' in args: iot_cmd_type = 'DPS' entity_value = args['dps_name'] - elif 'repo_endpoint' in args: - iot_cmd_type = 'PnP' - entity_value = args['repo_endpoint'] if not any([login_value, entity_value]): raise CLIError(error_no_hub_or_login_on_input(iot_cmd_type)) diff --git a/azext_iot/assets/edge-deploy-2.0.schema.json b/azext_iot/assets/edge-deploy-2.0.schema.json index 70332856f..fb8794bfc 100644 --- a/azext_iot/assets/edge-deploy-2.0.schema.json +++ b/azext_iot/assets/edge-deploy-2.0.schema.json @@ -33,7 +33,8 @@ "schemaVersion": { "type": "string", "examples": [ - "1.0" + "1.0", + "1.1" ] }, "runtime": { @@ -162,6 +163,9 @@ }, "imagePullPolicy": { "$ref": "#/definitions/imagePullPolicy" + }, + "startupOrder": { + "$ref": "#/definitions/startupOrder" } }, "patternProperties": { @@ -209,6 +213,9 @@ }, "imagePullPolicy": { "$ref": "#/definitions/imagePullPolicy" + }, + "startupOrder": { + "$ref": "#/definitions/startupOrder" } }, "patternProperties": { @@ -249,18 +256,49 @@ "schemaVersion": { "type": "string", "examples": [ - "1.0" + "1.0", + "1.1" ] }, "routes": { "type": "object", "patternProperties": { "^[^\\.\\$# ]+$": { - "type": "string", - "examples": [ - "FROM /* INTO $upstream" - ], - "pattern": "^.+$" + "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 @@ -341,6 +379,11 @@ "on-create" ] }, + "startupOrder": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, "moduleSettings": { "type": "object", "required": [ @@ -387,4 +430,4 @@ "contentMediaType": "application/json" } } -} \ No newline at end of file +} diff --git a/azext_iot/azext_metadata.json b/azext_iot/azext_metadata.json index 9d4d4e147..5bed66c74 100644 --- a/azext_iot/azext_metadata.json +++ b/azext_iot/azext_metadata.json @@ -1,3 +1,3 @@ { - "azext.minCliCoreVersion": "2.0.70" -} \ No newline at end of file + "azext.minCliCoreVersion": "2.3.1" +} diff --git a/azext_iot/central/__init__.py b/azext_iot/central/__init__.py new file mode 100644 index 000000000..73178b8ad --- /dev/null +++ b/azext_iot/central/__init__.py @@ -0,0 +1,9 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.central._help import load_central_help + +load_central_help() diff --git a/azext_iot/central/_help.py b/azext_iot/central/_help.py new file mode 100644 index 000000000..05ecf8159 --- /dev/null +++ b/azext_iot/central/_help.py @@ -0,0 +1,541 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.help_files import helps + + +def load_central_help(): + helps[ + "iot central" + ] = """ + type: group + short-summary: Manage IoT Central resources. + long-summary: | + IoT Central is an IoT application platform that reduces the burden and cost of developing, + managing, and maintaining enterprise-grade IoT solutions. Choosing to build with IoT Central + gives you the opportunity to focus time, money, and energy on transforming your business + with IoT data, rather than just maintaining and updating a complex and continually evolving + IoT infrastructure. + + IoT Central documentation is available at https://aka.ms/iotcentral-documentation + Additional information on CLI commands is available at https://aka.ms/azure-cli-iot-ext + """ + + helps[ + "iot central app" + ] = """ + type: group + short-summary: Manage IoT Central applications. + long-summary: Create, delete, view, and update your IoT Central apps. + """ + + _load_central_devices_help() + _load_central_users_help() + _load_central_api_token_help() + _load_central_device_templates_help() + _load_central_monitors_help() + _load_central_command_help() + _load_central_compute_device_key() + + +def _load_central_devices_help(): + helps[ + "iot central device" + ] = """ + type: group + short-summary: Manage and configure IoT Central devices + """ + + helps[ + "iot central device create" + ] = """ + type: command + short-summary: Create a device in IoT Central. + + examples: + - name: Create a device + text: > + az iot central device create + --app-id {appid} + --device-id {deviceid} + + - name: Create a simulated device + text: > + az iot central device create + --app-id {appid} + --device-id {deviceid} + --instance-of {devicetemplateid} + --simulated + """ + + helps[ + "iot central device show" + ] = """ + type: command + short-summary: Get a device from IoT Central. + + examples: + - name: Get a device + text: > + az iot central device show + --app-id {appid} + --device-id {deviceid} + """ + + helps[ + "iot central device delete" + ] = """ + type: command + short-summary: Delete a device from IoT Central. + + examples: + - name: Delete a device + text: > + az iot central device delete + --app-id {appid} + --device-id {deviceid} + """ + + helps[ + "iot central device show-credentials" + ] = """ + type: command + short-summary: Get device credentials from IoT Central. + + examples: + - name: Get device credentials for a device + text: > + az iot central device show-credentials + --app-id {appid} + --device-id {deviceid} + """ + + helps[ + "iot central device registration-info" + ] = """ + type: command + short-summary: Get registration info on device(s) from IoT Central. + long-summary: | + Note: This command can take a significant amount of time to return if no device id is specified and your app contains a lot of devices. + + examples: + - name: Get registration info on specified device + text: > + az iot central device registration-info --app-id {appid} --device-id {deviceid} + """ + + +def _load_central_compute_device_key(): + helps[ + "iot central device compute-device-key" + ] = """ + type: command + short-summary: Generate a derived device SAS key. + long-summary: Generate a derived device key from a group-level SAS key. + examples: + - name: Basic usage + text: > + az iot central device compute-device-key --pk {primaryKey} --device-id {deviceid} + """ + + +def _load_central_command_help(): + helps[ + "iot central device command" + ] = """ + type: group + short-summary: Run device commands. + """ + + helps[ + "iot central device command history" + ] = """ + type: command + short-summary: Get the details for the latest command request and response sent to the device. + long-summary: | + Lists the most recent command request and response that was sent to the device from IoT Central. + Any update that the device performs to the device properties as a result of the command execution are not included in the response. + examples: + - name: Show command response + text: > + az iot central device command history + --app-id {appid} + --device-id {deviceid} + --interface-id {interfaceid} + --command-name {commandname} + """ + + helps[ + "iot central device command run" + ] = """ + type: command + short-summary: Run a command on a device and view associated response. Does NOT monitor property updates that the command may perform. + long-summary: | + Note: payload should be nested under "request". + i.e. if your device expects the payload in a shape {"key": "value"} + payload should be {"request": {"key": "value"}}. + --content can also be pointed at a filepath like this (.../path/to/payload.json) + examples: + - name: Run command response + text: > + az iot central device command run + --app-id {appid} + --device-id {deviceid} + --interface-id {interfaceid} + --command-name {commandname} + --content {payload} + + - name: Short Run command response + text: > + az iot central device command run + -n {appid} + -d {deviceid} + -i {interfaceid} + --cn {commandname} + -k {payload} + """ + + +def _load_central_users_help(): + helps[ + "iot central user" + ] = """ + type: group + short-summary: Manage and configure IoT Central users + """ + + helps[ + "iot central user create" + ] = """ + type: command + short-summary: Add a user to the application + examples: + - name: Add a user by email to the application + text: > + az iot central user create + --user-id {userId} + --app-id {appId} + --email {emailAddress} + --role admin + + - name: Add a service-principal to the application + text: > + az iot central user create + --user-id {userId} + --app-id {appId} + --tenant-id {tenantId} + --object-id {objectId} + --role operator + """ + helps[ + "iot central user show" + ] = """ + type: command + short-summary: Get the details of a user by ID + examples: + - name: Get details of user + text: > + az iot central user show + --app-id {appid} + --user-id {userId} + """ + + helps[ + "iot central user delete" + ] = """ + type: command + short-summary: Delete a user from the application + examples: + - name: Delete a user + text: > + az iot central user delete + --app-id {appid} + --user-id {userId} + + """ + + helps[ + "iot central user list" + ] = """ + type: command + short-summary: Get list of users in an application + examples: + - name: List of users + text: > + az iot central user list + --app-id {appid} + + """ + + +def _load_central_api_token_help(): + helps[ + "iot central api-token" + ] = """ + type: group + short-summary: Manage API tokens for your IoT Central application. + long-summary: IoT Central allows you to generate and manage API tokens to be used to access the IoT Central API. More information about APIs can be found at https://aka.ms/iotcentraldocsapi. + """ + + helps[ + "iot central api-token create" + ] = """ + type: command + short-summary: Generate an API token associated with your IoT Central application. + long-summary: | + Note: Write down your token once it's been generated as you won't be able to retrieve it again. + examples: + - name: Add new API token + text: > + az iot central api-token create + --token-id {tokenId} + --app-id {appId} + --role admin + """ + helps[ + "iot central api-token show" + ] = """ + type: command + short-summary: Get details for an API token associated with your IoT Central application. + long-summary: List details, like its associated role, for an API token in your IoT Central app. + examples: + - name: Get API token + text: > + az iot central api-token show + --app-id {appid} + --token-id {tokenId} + """ + + helps[ + "iot central api-token delete" + ] = """ + type: command + short-summary: Delete an API token associated with your IoT Central application. + examples: + - name: Delete an API token + text: > + az iot central api-token delete + --app-id {appid} + --token-id {tokenId} + """ + + helps[ + "iot central api-token list" + ] = """ + type: command + short-summary: List all API tokens associated with your IoT Central application. + long-summary: Information in the list contains basic information about the tokens in the application and does not include token values. + examples: + - name: List of API tokens + text: > + az iot central api-token list + --app-id {appid} + + """ + + +def _load_central_device_templates_help(): + helps[ + "iot central device-template" + ] = """ + type: group + short-summary: Manage and configure IoT Central device templates + """ + + helps[ + "iot central device-template create" + ] = """ + type: command + short-summary: Create a device template in IoT Central. + + examples: + - name: Create a device template with payload read from a file + text: > + az iot central device-template create + --app-id {appid} + --content {pathtofile} + --device-template-id {devicetemplateid} + + - name: Create a device template with payload read from raw json + text: > + az iot central device-template create + --app-id {appid} + --content {json} + --device-template-id {devicetemplateid} + """ + + helps[ + "iot central device-template show" + ] = """ + type: command + short-summary: Get a device template from IoT Central. + + examples: + - name: Get a device template + text: > + az iot central device-template show + --app-id {appid} + --device-template-id {devicetemplateid} + """ + + helps[ + "iot central device-template delete" + ] = """ + type: command + short-summary: Delete a device template from IoT Central. + long-summary: | + Note: this is expected to fail if any devices are still associated to this template. + + examples: + - name: Delete a device template from IoT Central + text: > + az iot central device-template delete + --app-id {appid} + --device-template-id {devicetemplateid} + """ + + +def _load_central_monitors_help(): + + helps[ + "iot central diagnostics" + ] = """ + type: group + short-summary: Perform application and device level diagnostics. + """ + + helps[ + "iot central diagnostics monitor-events" + ] = """ + type: command + short-summary: View device telemetry messages sent to the IoT Central app. + long-summary: | + Shows the telemetry data sent to IoT Central application. By default, + it shows all the data sent by all devices. Use the --device-id parameter + to filter to a specific device. + + examples: + - name: Basic usage + text: > + az iot central diagnostics monitor-events --app-id {app_id} + - name: Basic usage when filtering on target device + text: > + az iot central diagnostics monitor-events --app-id {app_id} -d {device_id} + - name: Basic usage when filtering targeted devices with a wildcard in the ID + text: > + az iot central diagnostics monitor-events --app-id {app_id} -d Device*d + - name: Basic usage when filtering on module. + text: > + az iot central diagnostics monitor-events --app-id {app_id} -m {module_id} + - name: Basic usage when filtering targeted modules with a wildcard in the ID + text: > + az iot central diagnostics monitor-events --app-id {app_id} -m Module* + - name: Filter device and specify an Event Hub consumer group to bind to. + text: > + az iot central diagnostics monitor-events --app-id {app_id} -d {device_id} --cg {consumer_group_name} + - name: Receive message annotations (message headers) + text: > + az iot central diagnostics monitor-events --app-id {app_id} -d {device_id} --properties anno + - name: Receive message annotations + system properties. Never time out. + text: > + az iot central diagnostics monitor-events --app-id {app_id} -d {device_id} --properties anno sys --timeout 0 + - name: Receive all message attributes from all device messages + text: > + az iot central diagnostics monitor-events --app-id {app_id} --props all + - name: Receive all messages and parse message payload as JSON + text: > + az iot central diagnostics monitor-events --app-id {app_id} --output json + """ + + helps[ + "iot central diagnostics validate-messages" + ] = """ + type: command + short-summary: Validate messages sent to the IoT Hub for an IoT Central app. + long-summary: | + Performs validations on the telemetry messages and reports back data that is not modeled in the device template or data where the data type doesn’t match what is defined in the device template. + examples: + - name: Basic usage + text: > + az iot central diagnostics validate-messages --app-id {app_id} + - name: Output errors as they are detected + text: > + az iot central diagnostics validate-messages --app-id {app_id} --style scroll + - name: Basic usage when filtering on target device + text: > + az iot central diagnostics validate-messages --app-id {app_id} -d {device_id} + - name: Basic usage when filtering targeted devices with a wildcard in the ID + text: > + az iot central diagnostics validate-messages --app-id {app_id} -d Device* + - name: Basic usage when filtering on module. + text: > + az iot central diagnostics validate-messages --app-id {app_id} -m {module_id} + - name: Basic usage when filtering targeted modules with a wildcard in the ID + text: > + az iot central diagnostics validate-messages --app-id {app_id} -m Module* + - name: Filter device and specify an Event Hub consumer group to bind to. + text: > + az iot central diagnostics validate-messages --app-id {app_id} -d {device_id} --cg {consumer_group_name} + """ + + helps[ + "iot central diagnostics monitor-properties" + ] = """ + type: command + short-summary: View desired and reported properties sent to/from the IoT Central app. + long-summary: | + Polls device-twin from central and compares it to the last device-twin + Parses out properties from device-twin, and detects if changes were made + Prints subset of properties that were changed within the polling interval + examples: + - name: Basic usage + text: > + az iot central diagnostics monitor-properties --app-id {app_id} -d {device_id} + """ + + helps[ + "iot central diagnostics validate-properties" + ] = """ + type: command + short-summary: Validate reported properties sent to the IoT Central application. + long-summary: | + Performs validations on reported property updates: + 1) Warning - Properties sent by device that are not modeled in central. + 2) Warning - Properties with same name declared in multiple interfaces + should have interface name included as part of the property update. + examples: + - name: Basic usage + text: > + az iot central diagnostics validate-properties --app-id {app_id} -d {device_id} + """ + + helps[ + "iot central diagnostics registration-summary" + ] = """ + type: command + short-summary: View the registration summary of all the devices in an app. + long-summary: | + Note: This command can take a significant amount of time to return + if your app contains a lot of devices + examples: + - name: Registration summary + text: > + az iot central diagnostics registration-summary --app-id {appid} + """ + + helps[ + "iot central device twin" + ] = """ + type: group + short-summary: Manage IoT Central device twins. + """ + + helps[ + "iot central device twin show" + ] = """ + type: command + short-summary: Get the device twin from IoT Hub. + """ diff --git a/azext_iot/central/command_map.py b/azext_iot/central/command_map.py new file mode 100644 index 000000000..42c092bd1 --- /dev/null +++ b/azext_iot/central/command_map.py @@ -0,0 +1,111 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Load CLI commands +""" +from azure.cli.core.commands import CliCommandType + +central_device_ops = CliCommandType( + operations_tmpl="azext_iot.central.commands_device#{}" +) + +central_device_templates_ops = CliCommandType( + operations_tmpl="azext_iot.central.commands_device_template#{}" +) + +central_device_twin_ops = CliCommandType( + operations_tmpl="azext_iot.central.commands_device_twin#{}" +) + +central_monitor_ops = CliCommandType( + operations_tmpl="azext_iot.central.commands_monitor#{}" +) + +central_user_ops = CliCommandType(operations_tmpl="azext_iot.central.commands_user#{}") + +central_api_token_ops = CliCommandType( + operations_tmpl="azext_iot.central.commands_api_token#{}" +) + + +# Dev note - think of this as the "router" and all self.command_group as the controllers +def load_central_commands(self, _): + """ + Load CLI commands + """ + + with self.command_group( + "iot central diagnostics", command_type=central_monitor_ops, is_preview=True + ) as cmd_group: + cmd_group.command("monitor-events", "monitor_events") + cmd_group.command( + "validate-messages", "validate_messages", + ) + cmd_group.command( + "monitor-properties", "monitor_properties", + ) + cmd_group.command( + "validate-properties", "validate_properties", + ) + + with self.command_group( + "iot central diagnostics", command_type=central_device_ops, is_preview=True + ) as cmd_group: + cmd_group.command( + "registration-summary", "registration_summary", + ) + + with self.command_group( + "iot central user", command_type=central_user_ops, is_preview=True, + ) as cmd_group: + cmd_group.command("create", "add_user") + cmd_group.command("list", "list_users") + cmd_group.show_command("show", "get_user") + cmd_group.command("delete", "delete_user") + + with self.command_group( + "iot central api-token", command_type=central_api_token_ops, is_preview=True, + ) as cmd_group: + cmd_group.command("create", "add_api_token") + cmd_group.command("list", "list_api_tokens") + cmd_group.show_command("show", "get_api_token") + cmd_group.command("delete", "delete_api_token") + + with self.command_group( + "iot central device", command_type=central_device_ops, is_preview=True, + ) as cmd_group: + # cmd_group.command("list", "list_devices") + cmd_group.show_command("show", "get_device") + cmd_group.command("create", "create_device") + cmd_group.command("delete", "delete_device") + cmd_group.command("registration-info", "registration_info") + cmd_group.command("show-credentials", "get_credentials") + cmd_group.command("compute-device-key", "compute_device_key") + + with self.command_group( + "iot central device command", command_type=central_device_ops, is_preview=True, + ) as cmd_group: + cmd_group.command("run", "run_command") + cmd_group.command("history", "get_command_history") + + with self.command_group( + "iot central device-template", + command_type=central_device_templates_ops, + is_preview=True, + ) as cmd_group: + # cmd_group.command("list", "list_device_templates") + # cmd_group.command("map", "map_device_templates") + cmd_group.show_command("show", "get_device_template") + cmd_group.command("create", "create_device_template") + cmd_group.command("delete", "delete_device_template") + + with self.command_group( + "iot central device twin", command_type=central_device_twin_ops, is_preview=True + ) as cmd_group: + cmd_group.show_command( + "show", "device_twin_show", + ) diff --git a/azext_iot/central/commands_api_token.py b/azext_iot/central/commands_api_token.py new file mode 100644 index 000000000..e8111f1a6 --- /dev/null +++ b/azext_iot/central/commands_api_token.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# Dev note - think of this as a controller + + +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.providers import CentralApiTokenProvider +from azext_iot.central.models.enum import Role + + +def add_api_token( + cmd, + app_id: str, + token_id: str, + role: str, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralApiTokenProvider(cmd=cmd, app_id=app_id, token=token) + return provider.add_api_token( + token_id=token_id, role=Role[role], central_dns_suffix=central_dns_suffix, + ) + + +def list_api_tokens( + cmd, app_id: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralApiTokenProvider(cmd=cmd, app_id=app_id, token=token) + + return provider.get_api_token_list(central_dns_suffix=central_dns_suffix,) + + +def get_api_token( + cmd, app_id: str, token_id: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralApiTokenProvider(cmd=cmd, app_id=app_id, token=token) + + return provider.get_api_token( + token_id=token_id, central_dns_suffix=central_dns_suffix + ) + + +def delete_api_token( + cmd, app_id: str, token_id: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralApiTokenProvider(cmd=cmd, app_id=app_id, token=token) + + return provider.delete_api_token( + token_id=token_id, central_dns_suffix=central_dns_suffix + ) diff --git a/azext_iot/central/commands_device.py b/azext_iot/central/commands_device.py new file mode 100644 index 000000000..69e6026c5 --- /dev/null +++ b/azext_iot/central/commands_device.py @@ -0,0 +1,128 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# Dev note - think of this as a controller + +from knack.util import CLIError + +from azext_iot.common import utility +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.providers import CentralDeviceProvider + + +def list_devices(cmd, app_id: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT): + provider = CentralDeviceProvider(cmd=cmd, app_id=app_id, token=token) + return provider.list_devices() + + +def get_device( + cmd, app_id: str, device_id: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralDeviceProvider(cmd=cmd, app_id=app_id, token=token) + return provider.get_device(device_id) + + +def create_device( + cmd, + app_id: str, + device_id: str, + device_name=None, + instance_of=None, + simulated=False, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, +): + if simulated and not instance_of: + raise CLIError( + "Error: if you supply --simulated you must also specify --instance-of" + ) + provider = CentralDeviceProvider(cmd=cmd, app_id=app_id, token=token) + return provider.create_device( + device_id=device_id, + device_name=device_name, + instance_of=instance_of, + simulated=simulated, + central_dns_suffix=central_dns_suffix, + ) + + +def delete_device( + cmd, app_id: str, device_id: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralDeviceProvider(cmd=cmd, app_id=app_id, token=token) + return provider.delete_device(device_id) + + +def registration_info( + cmd, app_id: str, device_id, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralDeviceProvider(cmd=cmd, app_id=app_id, token=token,) + + return provider.get_device_registration_info( + device_id=device_id, central_dns_suffix=central_dns_suffix, device_status=None, + ) + + +def run_command( + cmd, + app_id: str, + device_id: str, + interface_id: str, + command_name: str, + content: str, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, +): + if not isinstance(content, str): + raise CLIError("content must be a string: {}".format(content)) + + payload = utility.process_json_arg(content, argument_name="content") + + provider = CentralDeviceProvider(cmd=cmd, app_id=app_id, token=token) + return provider.run_component_command( + device_id=device_id, + interface_id=interface_id, + command_name=command_name, + payload=payload, + ) + + +def get_command_history( + cmd, + app_id: str, + device_id: str, + interface_id: str, + command_name: str, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralDeviceProvider(cmd=cmd, app_id=app_id, token=token) + return provider.get_component_command_history( + device_id=device_id, interface_id=interface_id, command_name=command_name, + ) + + +def registration_summary( + cmd, app_id: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralDeviceProvider(cmd=cmd, app_id=app_id, token=token,) + return provider.get_device_registration_summary( + central_dns_suffix=central_dns_suffix, + ) + + +def get_credentials( + cmd, app_id: str, device_id, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralDeviceProvider(cmd=cmd, app_id=app_id, token=token,) + return provider.get_device_credentials( + device_id=device_id, central_dns_suffix=central_dns_suffix, + ) + + +def compute_device_key(cmd, primary_key, device_id): + return utility.compute_device_key( + primary_key=primary_key, registration_id=device_id + ) diff --git a/azext_iot/central/commands_device_template.py b/azext_iot/central/commands_device_template.py new file mode 100644 index 000000000..9cbcdfab9 --- /dev/null +++ b/azext_iot/central/commands_device_template.py @@ -0,0 +1,76 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# Dev note - think of this as a controller + +from knack.util import CLIError + +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.common import utility +from azext_iot.central.providers import CentralDeviceTemplateProvider + + +def get_device_template( + cmd, + app_id: str, + device_template_id: str, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralDeviceTemplateProvider(cmd=cmd, app_id=app_id, token=token) + template = provider.get_device_template( + device_template_id=device_template_id, central_dns_suffix=central_dns_suffix + ) + return template.raw_template + + +def list_device_templates( + cmd, app_id: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT +): + provider = CentralDeviceTemplateProvider(cmd=cmd, app_id=app_id, token=token) + templates = provider.list_device_templates(central_dns_suffix=central_dns_suffix) + return {template.id: template.raw_template for template in templates.values()} + + +def map_device_templates( + cmd, app_id: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT +): + provider = CentralDeviceTemplateProvider(cmd=cmd, app_id=app_id, token=token) + return provider.map_device_templates(central_dns_suffix=central_dns_suffix) + + +def create_device_template( + cmd, + app_id: str, + device_template_id: str, + content: str, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, +): + if not isinstance(content, str): + raise CLIError("content must be a string: {}".format(content)) + + payload = utility.process_json_arg(content, argument_name="content") + + provider = CentralDeviceTemplateProvider(cmd=cmd, app_id=app_id, token=token) + template = provider.create_device_template( + device_template_id=device_template_id, + payload=payload, + central_dns_suffix=central_dns_suffix, + ) + return template.raw_template + + +def delete_device_template( + cmd, + app_id: str, + device_template_id: str, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralDeviceTemplateProvider(cmd=cmd, app_id=app_id, token=token) + return provider.delete_device_template( + device_template_id=device_template_id, central_dns_suffix=central_dns_suffix + ) diff --git a/azext_iot/central/commands_device_twin.py b/azext_iot/central/commands_device_twin.py new file mode 100644 index 000000000..923ac11e7 --- /dev/null +++ b/azext_iot/central/commands_device_twin.py @@ -0,0 +1,18 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.providers.devicetwin_provider import CentralDeviceTwinProvider + + +def device_twin_show( + cmd, device_id, app_id, token=None, central_dns_suffix=CENTRAL_ENDPOINT +): + device_twin_provider = CentralDeviceTwinProvider( + cmd=cmd, app_id=app_id, token=token, device_id=device_id + ) + return device_twin_provider.get_device_twin(central_dns_suffix=central_dns_suffix) diff --git a/azext_iot/central/commands_monitor.py b/azext_iot/central/commands_monitor.py new file mode 100644 index 000000000..d3e297998 --- /dev/null +++ b/azext_iot/central/commands_monitor.py @@ -0,0 +1,151 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +from azure.cli.core.commands import AzCliCommand +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.providers.monitor_provider import MonitorProvider +from azext_iot.monitor.models.enum import Severity +from azext_iot.monitor.models.arguments import ( + CommonParserArguments, + CommonHandlerArguments, + CentralHandlerArguments, + TelemetryArguments, +) +from azext_iot.monitor.property import PropertyMonitor + + +def validate_messages( + cmd: AzCliCommand, + app_id, + device_id=None, + module_id=None, + consumer_group="$Default", + timeout=300, + enqueued_time=None, + repair=False, + properties=None, + yes=False, + max_messages=10, + duration=300, + style="scroll", + minimum_severity=Severity.warning.name, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, +): + telemetry_args = TelemetryArguments( + cmd, + timeout=timeout, + properties=properties, + enqueued_time=enqueued_time, + repair=repair, + yes=yes, + ) + common_parser_args = CommonParserArguments( + properties=telemetry_args.properties, content_type="application/json" + ) + common_handler_args = CommonHandlerArguments( + output=telemetry_args.output, + common_parser_args=common_parser_args, + device_id=device_id, + module_id=module_id, + ) + central_handler_args = CentralHandlerArguments( + duration=duration, + max_messages=max_messages, + style=style, + minimum_severity=Severity[minimum_severity], + common_handler_args=common_handler_args, + ) + provider = MonitorProvider( + cmd=cmd, + app_id=app_id, + token=token, + consumer_group=consumer_group, + central_dns_suffix=central_dns_suffix, + central_handler_args=central_handler_args, + ) + provider.start_validate_messages(telemetry_args) + + +def monitor_events( + cmd: AzCliCommand, + app_id, + device_id=None, + module_id=None, + consumer_group="$Default", + timeout=300, + enqueued_time=None, + repair=False, + properties=None, + yes=False, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, +): + telemetry_args = TelemetryArguments( + cmd, + timeout=timeout, + properties=properties, + enqueued_time=enqueued_time, + repair=repair, + yes=yes, + ) + common_parser_args = CommonParserArguments( + properties=telemetry_args.properties, content_type="application/json" + ) + common_handler_args = CommonHandlerArguments( + output=telemetry_args.output, + common_parser_args=common_parser_args, + device_id=device_id, + module_id=module_id, + ) + central_handler_args = CentralHandlerArguments( + duration=0, + max_messages=0, + style="scroll", + minimum_severity=Severity.warning, + common_handler_args=common_handler_args, + ) + provider = MonitorProvider( + cmd=cmd, + app_id=app_id, + token=token, + consumer_group=consumer_group, + central_dns_suffix=central_dns_suffix, + central_handler_args=central_handler_args, + ) + provider.start_monitor_events(telemetry_args) + + +def monitor_properties( + cmd, device_id: str, app_id: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + monitor = PropertyMonitor( + cmd=cmd, + app_id=app_id, + device_id=device_id, + token=token, + central_dns_suffix=central_dns_suffix, + ) + monitor.start_property_monitor() + + +def validate_properties( + cmd, + device_id: str, + app_id: str, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, + minimum_severity=Severity.warning.name, +): + monitor = PropertyMonitor( + cmd=cmd, + app_id=app_id, + device_id=device_id, + token=token, + central_dns_suffix=central_dns_suffix, + ) + monitor.start_validate_property_monitor(Severity[minimum_severity]) diff --git a/azext_iot/central/commands_user.py b/azext_iot/central/commands_user.py new file mode 100644 index 000000000..63841f35a --- /dev/null +++ b/azext_iot/central/commands_user.py @@ -0,0 +1,67 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# Dev note - think of this as a controller + + +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.providers import CentralUserProvider +from azext_iot.central.models.enum import Role + + +def add_user( + cmd, + app_id: str, + assignee: str, + role: str, + email=None, + tenant_id=None, + object_id=None, + token=None, + central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralUserProvider(cmd=cmd, app_id=app_id, token=token) + + if email: + return provider.add_email( + assignee=assignee, + email=email, + role=Role[role], + central_dns_suffix=central_dns_suffix, + ) + + return provider.add_service_principal( + assignee=assignee, + tenant_id=tenant_id, + object_id=object_id, + role=Role[role], + central_dns_suffix=central_dns_suffix, + ) + + +def list_users( + cmd, app_id: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralUserProvider(cmd=cmd, app_id=app_id, token=token) + + return provider.get_user_list(central_dns_suffix=central_dns_suffix,) + + +def get_user( + cmd, app_id: str, assignee: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralUserProvider(cmd=cmd, app_id=app_id, token=token) + + return provider.get_user(assignee=assignee, central_dns_suffix=central_dns_suffix) + + +def delete_user( + cmd, app_id: str, assignee: str, token=None, central_dns_suffix=CENTRAL_ENDPOINT, +): + provider = CentralUserProvider(cmd=cmd, app_id=app_id, token=token) + + return provider.delete_user( + assignee=assignee, central_dns_suffix=central_dns_suffix + ) diff --git a/azext_iot/operations/events3/__init__.py b/azext_iot/central/models/__init__.py similarity index 100% rename from azext_iot/operations/events3/__init__.py rename to azext_iot/central/models/__init__.py diff --git a/azext_iot/central/models/device.py b/azext_iot/central/models/device.py new file mode 100644 index 000000000..7bc02bd78 --- /dev/null +++ b/azext_iot/central/models/device.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.central.models.enum import DeviceStatus + + +class Device: + def __init__(self, device: dict): + self.approved = device.get("approved") + self.description = device.get("description") + self.display_name = device.get("displayName") + self.etag = device.get("etag") + self.id = device.get("id") + self.instance_of = device.get("instanceOf") + self.provisioned = device.get("provisioned") + self.simulated = device.get("simulated") + self.device_status = self._parse_device_status() + pass + + def _parse_device_status(self) -> DeviceStatus: + if not self.approved: + return DeviceStatus.blocked + + if not self.instance_of: + return DeviceStatus.unassociated + + if not self.provisioned: + return DeviceStatus.registered + + return DeviceStatus.provisioned + + def get_registration_info(self): + registration_info = { + "device_status": self.device_status.value, + "display_name": self.display_name, + "id": self.id, + "simulated": self.simulated, + "instance_of": self.instance_of, + } + + return registration_info diff --git a/azext_iot/central/models/devicetwin.py b/azext_iot/central/models/devicetwin.py new file mode 100644 index 000000000..3ff74676a --- /dev/null +++ b/azext_iot/central/models/devicetwin.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +class DeviceTwin: + def __init__( + self, device_twin: dict, + ): + self.device_twin = device_twin + self.device_id = device_twin.get("deviceId") + self.desired_property = Property( + "desired property", + device_twin.get("properties", {}).get("desired"), + self.device_id, + ) + self.reported_property = Property( + "reported property", + device_twin.get("properties", {}).get("reported"), + self.device_id, + ) + + +class Property: + def __init__( + self, name: str, props: dict, device_id, + ): + self.name = name + self.props = props + self.metadata = props.get("$metadata") + self.version = props.get("$version") + self.device_id = device_id diff --git a/azext_iot/central/models/enum.py b/azext_iot/central/models/enum.py new file mode 100644 index 000000000..1e4e976c5 --- /dev/null +++ b/azext_iot/central/models/enum.py @@ -0,0 +1,43 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Enum definitions for central + +""" + +from enum import Enum + + +class DeviceStatus(Enum): + """ + Type of Device status. + """ + + provisioned = "provisioned" + registered = "registered" + blocked = "blocked" + unassociated = "unassociated" + + +class Role(Enum): + """ + Types of roles a user can have in Central (admin, builder, etc) + """ + + admin = "ca310b8d-2f4a-44e0-a36e-957c202cd8d4" + builder = "344138e9-8de4-4497-8c54-5237e96d6aaf" + operator = "ae2c9854-393b-4f97-8c42-479d70ce626e" + + +class UserType(Enum): + """ + Types of users that can be added to use/manage a Central app + (service principal, email, etc) + """ + + service_principal = "ServicePrincipalUser" + email = "EmailUser" diff --git a/azext_iot/central/models/template.py b/azext_iot/central/models/template.py new file mode 100644 index 000000000..cfb767a84 --- /dev/null +++ b/azext_iot/central/models/template.py @@ -0,0 +1,114 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.util import CLIError + + +class Template: + def __init__(self, template: dict): + self.raw_template = template + try: + self.id = template.get("id") + self.name = template.get("displayName") + self.interfaces = self._extract_interfaces(template) + self.schema_names = self._extract_schema_names(self.interfaces) + self.components = self._extract_components(template) + if self.components: + self.component_schema_names = self._extract_schema_names( + self.components + ) + + except: + raise CLIError("Could not parse iot central device template.") + + def get_schema(self, name, is_component=False, identifier="") -> dict: + entities = self.components if is_component else self.interfaces + if identifier: + # identifier specified, do a pointed lookup + entry = entities.get(identifier, {}) + return entry.get(name) + + # find first matching name in any component + for entry in entities.values(): + schema = entry.get(name) + if schema: + return schema + + # not found + return None + + def _extract_components(self, template: dict) -> dict: + try: + dcm = template.get("capabilityModel", {}) + if dcm.get("contents"): + rootContents = dcm.get("contents", {}) + components = [ + entity + for entity in rootContents + if entity.get("@type") == "Component" + ] + + if components: + return { + component["name"]: self._extract_schemas(component) + for component in components + } + return {} + return {} + except Exception: + details = "Unable to extract schema for component from template '{}'.".format( + self.id + ) + raise CLIError(details) + + def _extract_root_interface_contents(self, dcm: dict): + rootContents = dcm.get("contents", {}) + contents = [ + entity for entity in rootContents if entity.get("@type") != "Component" + ] + + return {"@id": dcm.get("@id", {}), "schema": {"contents": contents}} + + def _extract_interfaces(self, template: dict) -> dict: + try: + + interfaces = [] + dcm = template.get("capabilityModel", {}) + + if dcm.get("contents"): + interfaces.append(self._extract_root_interface_contents(dcm)) + + if dcm.get("@type") == "CapabilityModel": + interfaces.extend(dcm.get("implements")) + else: + interfaces.extend(dcm.get("extends")) + + return { + interface["@id"]: self._extract_schemas(interface) + for interface in interfaces + } + except Exception: + details = "Unable to extract device schema from template '{}'.".format( + self.id + ) + raise CLIError(details) + + def _extract_schemas(self, entity: dict) -> dict: + return {schema["name"]: schema for schema in entity["schema"]["contents"]} + + def _extract_schema_names(self, entity: dict) -> dict: + return { + entity_name: list(entity_schemas.keys()) + for entity_name, entity_schemas in entity.items() + } + + def _get_interface_list_property(self, property_name): + # returns the list of interfaces where property with property_name is defined + return [ + interface + for interface, schema in self.schema_names.items() + if property_name in schema + ] diff --git a/azext_iot/central/params.py b/azext_iot/central/params.py new file mode 100644 index 000000000..b800ccfa1 --- /dev/null +++ b/azext_iot/central/params.py @@ -0,0 +1,198 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +CLI parameter definitions. +""" + +from knack.arguments import CLIArgumentType, CaseInsensitiveList +from azure.cli.core.commands.parameters import get_three_state_flag +from azext_iot.monitor.models.enum import Severity +from azext_iot.central.models.enum import Role +from azext_iot._params import event_msg_prop_type, event_timeout_type + +severity_type = CLIArgumentType( + options_list=["--minimum-severity"], + choices=CaseInsensitiveList([sev.name for sev in Severity]), + help="Minimum severity of issue required for reporting.", +) + +role_type = CLIArgumentType( + options_list=["--role", "-r"], + choices=CaseInsensitiveList([role.name for role in Role]), + help="The role that will be associated with this token." + " You can specify one of the built-in roles, or specify the role ID of a custom role." + " See more at https://aka.ms/iotcentral-customrolesdocs", +) + +style_type = CLIArgumentType( + options_list=["--style"], + choices=CaseInsensitiveList(["scroll", "json", "csv"]), + help="Indicate output style" + "scroll = deliver errors as they arrive, json = summarize results as json, csv = summarize results as csv", +) + + +def load_central_arguments(self, _): + """ + Load CLI Args for Knack parser + """ + with self.argument_context("iot central") as context: + context.argument( + "app_id", + options_list=["--app-id", "-n"], + help="The App ID of the IoT Central app you want to manage." + " You can find the App ID in the \"About\" page for your application under the help menu." + ) + context.argument( + "token", + options_list=["--token"], + help="If you'd prefer to submit your request without authenticating against the Azure CLI, you can specify a valid" + " user token to authenticate your request. You must specify the type of key as part of the request." + " Learn more at https://aka.ms/iotcentraldocsapi", + ) + context.argument( + "central_dns_suffix", + options_list=["--central-dns-suffix", "--central-api-uri"], + help="The IoT Central DNS suffix associated with your application. Default value is: azureiotcentral.com", + ) + context.argument( + "device_id", + options_list=["--device-id", "-d"], + help="The ID of the target device, " + "You can find the Device Id by clicking on the Connect button on the Device Details page.", + ) + + with self.argument_context("iot central device-template") as context: + context.argument( + "device_template_id", + options_list=["--device-template-id", "--dtid"], + help="The ID of the target device template. Example: somedevicetemplate", + ) + context.argument( + "content", + options_list=["--content", "-k"], + help="The device template definition. Provide path to JSON file or raw stringified JSON." + " [File Path Example: ./path/to/file.json] [Example of stringified JSON: {}]." + " The request body must contain CapabilityModel.", + ) + + with self.argument_context("iot central device-template create") as context: + context.argument( + "device_template_id", + options_list=["--device-template-id", "--dtid"], + help="Unique ID for the Device template.", + ) + + with self.argument_context("iot central api-token") as context: + context.argument( + "token_id", + options_list=["--token-id", "--tkid"], + help="The IoT Central ID associated with this token, [0-9a-zA-Z\\-] allowed, max length limit to 40." + " Specify an ID that you'll then use when modifying or deleting this token later via the CLI or API.", + ) + context.argument("role", arg_type=role_type) + + with self.argument_context("iot central device compute-device-key") as context: + context.argument( + "primary_key", + options_list=["--primary-key", "--pk"], + help="The primary symmetric shared access key stored in base64 format. ", + ) + + with self.argument_context("iot central device") as context: + context.argument( + "instance_of", + options_list=["--instance-of"], + help="Central template id. Example: urn:ojpkindbz:modelDefinition:iild3tm_uo", + ) + context.argument( + "simulated", + options_list=["--simulated"], + arg_type=get_three_state_flag(), + help="Add this flag if you would like IoT Central to set this up as a simulated device. " + "--instance-of is required if this is true", + ) + context.argument( + "device_name", + options_list=["--device-name"], + help="Human readable device name. Example: Fridge", + ) + context.argument( + "interface_id", + options_list=["--interface-id", "-i"], + help="The name of the interface as specified in the device template. You can find it by navigating to Device" + " Template and view the interface identity under the corresponding device capability.", + ) + context.argument( + "command_name", + options_list=["--command-name", "--cn"], + help="The command name as specified in the device template. Command name could be different from the Display" + " Name of the command.", + ) + context.argument( + "content", + options_list=["--content", "-k"], + help="Configuration for request. " + "Provide path to JSON file or raw stringified JSON. " + "[File Path Example: ./path/to/file.json] " + "[Stringified JSON Example: {'a': 'b'}] ", + ) + + with self.argument_context("iot central device create") as context: + context.argument( + "device_id", + options_list=["--device-id", "-d"], + help="Provide a unique identifier for the device." + " A case-sensitive string (up to 128 characters long) of ASCII 7-bit alphanumeric characters plus" + " certain special characters: - . + % _ # * ? ! ( ) , : = @ $ '", + ) + + with self.argument_context("iot central user") as context: + context.argument( + "tenant_id", + options_list=["--tenant-id", "--tnid"], + help="Tenant ID for service principal to be added to the app. Object ID must also be specified. ", + ) + context.argument( + "object_id", + options_list=["--object-id", "--oid"], + help="Object ID for service principal to be added to the app. Tenant ID must also be specified. ", + ) + context.argument( + "email", + options_list=["--email"], + help="Email address of user to be added to the app. ", + ) + context.argument( + "assignee", + options_list=["--user-id", "--assignee"], + help="ID associated with the user. ", + ) + context.argument("role", arg_type=role_type) + + with self.argument_context("iot central diagnostics") as context: + context.argument("timeout", arg_type=event_timeout_type) + context.argument("properties", arg_type=event_msg_prop_type) + context.argument("minimum_severity", arg_type=severity_type) + context.argument("style", arg_type=style_type) + context.argument( + "duration", + options_list=["--duration", "--dr"], + type=int, + help="Maximum duration to receive messages from target device before terminating connection." + "Use 0 for infinity.", + ) + context.argument( + "max_messages", + options_list=["--max-messages", "--mm"], + type=int, + help="Maximum number of messages to recieve from target device before terminating connection." + "Use 0 for infinity.", + ) + context.argument( + "module_id", options_list=["--module-id", "-m"], help="Provide IoT Edge Module ID if the device type is IoT Edge.", + ) diff --git a/azext_iot/central/providers/__init__.py b/azext_iot/central/providers/__init__.py new file mode 100644 index 000000000..451120fe6 --- /dev/null +++ b/azext_iot/central/providers/__init__.py @@ -0,0 +1,21 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.central.providers.device_provider import CentralDeviceProvider +from azext_iot.central.providers.device_template_provider import ( + CentralDeviceTemplateProvider, +) +from azext_iot.central.providers.devicetwin_provider import CentralDeviceTwinProvider +from azext_iot.central.providers.user_provider import CentralUserProvider +from azext_iot.central.providers.api_token_provider import CentralApiTokenProvider + +__all__ = [ + "CentralDeviceProvider", + "CentralDeviceTemplateProvider", + "CentralDeviceTwinProvider", + "CentralUserProvider", + "CentralApiTokenProvider", +] diff --git a/azext_iot/central/providers/api_token_provider.py b/azext_iot/central/providers/api_token_provider.py new file mode 100644 index 000000000..b76574513 --- /dev/null +++ b/azext_iot/central/providers/api_token_provider.py @@ -0,0 +1,78 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.log import get_logger + +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central import services as central_services +from azext_iot.central.models.enum import Role + + +logger = get_logger(__name__) + + +class CentralApiTokenProvider: + def __init__(self, cmd, app_id: str, token=None): + """ + Provider for API token APIs + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch API token details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + Useful in scenarios where user doesn't own the app + therefore AAD token won't work, but a SAS token generated by owner will + """ + self._cmd = cmd + self._app_id = app_id + self._token = token + + def add_api_token( + self, token_id: str, role: Role, central_dns_suffix=CENTRAL_ENDPOINT, + ): + + return central_services.api_token.add_api_token( + cmd=self._cmd, + app_id=self._app_id, + token_id=token_id, + role=role, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + def get_api_token_list( + self, central_dns_suffix=CENTRAL_ENDPOINT, + ): + + return central_services.api_token.get_api_token_list( + cmd=self._cmd, + app_id=self._app_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + def get_api_token( + self, token_id, central_dns_suffix=CENTRAL_ENDPOINT, + ): + return central_services.api_token.get_api_token( + cmd=self._cmd, + app_id=self._app_id, + token_id=token_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + def delete_api_token( + self, token_id, central_dns_suffix=CENTRAL_ENDPOINT, + ): + return central_services.api_token.delete_api_token( + cmd=self._cmd, + app_id=self._app_id, + token_id=token_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) diff --git a/azext_iot/central/providers/device_provider.py b/azext_iot/central/providers/device_provider.py new file mode 100644 index 000000000..cfa1eadca --- /dev/null +++ b/azext_iot/central/providers/device_provider.py @@ -0,0 +1,242 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.util import CLIError +from knack.log import get_logger +from typing import List +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central import services as central_services +from azext_iot.central.models.enum import DeviceStatus +from azext_iot.central.models.device import Device +from azext_iot.dps.services import global_service as dps_global_service + + +logger = get_logger(__name__) + + +class CentralDeviceProvider: + def __init__(self, cmd, app_id: str, token=None): + """ + Provider for device APIs + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + Useful in scenarios where user doesn't own the app + therefore AAD token won't work, but a SAS token generated by owner will + """ + self._cmd = cmd + self._app_id = app_id + self._token = token + self._devices = {} + self._device_templates = {} + self._device_credentials = {} + self._device_registration_info = {} + + def get_device(self, device_id, central_dns_suffix=CENTRAL_ENDPOINT) -> Device: + if not device_id: + raise CLIError("Device id must be specified.") + # get or add to cache + device = self._devices.get(device_id) + if not device: + device = central_services.device.get_device( + cmd=self._cmd, + app_id=self._app_id, + device_id=device_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + self._devices[device_id] = device + + if not device: + raise CLIError("No device found with id: '{}'.".format(device_id)) + + return device + + def list_devices(self, central_dns_suffix=CENTRAL_ENDPOINT) -> List[Device]: + devices = central_services.device.list_devices( + cmd=self._cmd, + app_id=self._app_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + # add to cache + self._devices.update({device.id: device for device in devices}) + + return self._devices + + def create_device( + self, + device_id, + device_name=None, + instance_of=None, + simulated=False, + central_dns_suffix=CENTRAL_ENDPOINT, + ) -> Device: + if not device_id: + raise CLIError("Device id must be specified.") + + if device_id in self._devices: + raise CLIError("Device already exists.") + + device = central_services.device.create_device( + cmd=self._cmd, + app_id=self._app_id, + device_id=device_id, + device_name=device_name, + instance_of=instance_of, + simulated=simulated, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + if not device: + raise CLIError("No device found with id: '{}'.".format(device_id)) + + # add to cache + self._devices[device.id] = device + + return device + + def delete_device(self, device_id, central_dns_suffix=CENTRAL_ENDPOINT,) -> dict: + if not device_id: + raise CLIError("Device id must be specified.") + + # get or add to cache + result = central_services.device.delete_device( + cmd=self._cmd, + app_id=self._app_id, + device_id=device_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + # remove from cache + # pop "miss" raises a KeyError if None is not provided + self._devices.pop(device_id, None) + self._device_credentials.pop(device_id, None) + + return result + + def get_device_credentials( + self, device_id, central_dns_suffix=CENTRAL_ENDPOINT, + ) -> dict: + credentials = self._device_credentials.get(device_id) + + if not credentials: + credentials = central_services.device.get_device_credentials( + cmd=self._cmd, + app_id=self._app_id, + device_id=device_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + if not credentials: + raise CLIError( + "Could not find device credentials for device '{}'.".format(device_id) + ) + + # add to cache + self._device_credentials[device_id] = credentials + + return credentials + + def get_device_registration_info( + self, + device_id, + device_status: DeviceStatus, + central_dns_suffix=CENTRAL_ENDPOINT, + ) -> dict: + dps_state = {} + info = self._device_registration_info.get(device_id) + + if info: + return info + + device = self.get_device(device_id, central_dns_suffix) + if device.device_status == DeviceStatus.provisioned: + credentials = self.get_device_credentials( + device_id=device_id, central_dns_suffix=central_dns_suffix + ) + id_scope = credentials["idScope"] + key = credentials["symmetricKey"]["primaryKey"] + dps_state = dps_global_service.get_registration_state( + id_scope=id_scope, key=key, device_id=device_id + ) + dps_state = self._dps_populate_essential_info(dps_state, device.device_status) + + info = { + "@device_id": device_id, + "dps_state": dps_state, + "device_registration_info": device.get_registration_info(), + } + + self._device_registration_info[device_id] = info + + return info + + def get_device_registration_summary(self, central_dns_suffix=CENTRAL_ENDPOINT): + return central_services.device.get_device_registration_summary( + cmd=self._cmd, + app_id=self._app_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + def run_component_command( + self, + device_id: str, + interface_id: str, + command_name: str, + payload: dict, + central_dns_suffix=CENTRAL_ENDPOINT, + ): + return central_services.device.run_component_command( + cmd=self._cmd, + app_id=self._app_id, + token=self._token, + device_id=device_id, + interface_id=interface_id, + command_name=command_name, + payload=payload, + central_dns_suffix=central_dns_suffix, + ) + + def get_component_command_history( + self, + device_id: str, + interface_id: str, + command_name: str, + central_dns_suffix=CENTRAL_ENDPOINT, + ): + return central_services.device.get_component_command_history( + cmd=self._cmd, + app_id=self._app_id, + token=self._token, + device_id=device_id, + interface_id=interface_id, + command_name=command_name, + central_dns_suffix=central_dns_suffix, + ) + + def _dps_populate_essential_info(self, dps_info, device_status: DeviceStatus): + error = { + DeviceStatus.provisioned: "None.", + DeviceStatus.registered: "Device is not yet provisioned.", + DeviceStatus.blocked: "Device is blocked from connecting to IoT Central application." + " Unblock the device in IoT Central and retry. Learn more: https://aka.ms/iotcentral-docs-dps-SAS", + DeviceStatus.unassociated: "Device does not have a valid template associated with it.", + } + + filtered_dps_info = { + "status": dps_info.get("status"), + "error": error.get(device_status), + } + return filtered_dps_info diff --git a/azext_iot/central/providers/device_template_provider.py b/azext_iot/central/providers/device_template_provider.py new file mode 100644 index 000000000..2a8f72597 --- /dev/null +++ b/azext_iot/central/providers/device_template_provider.py @@ -0,0 +1,113 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.util import CLIError +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central import services as central_services + + +class CentralDeviceTemplateProvider: + def __init__(self, cmd, app_id, token=None): + """ + Provider for device_template APIs + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + Useful in scenarios where user doesn't own the app + therefore AAD token won't work, but a SAS token generated by owner will + """ + self._cmd = cmd + self._app_id = app_id + self._token = token + self._device_templates = {} + + def get_device_template( + self, device_template_id, central_dns_suffix=CENTRAL_ENDPOINT, + ): + # get or add to cache + device_template = self._device_templates.get(device_template_id) + if not device_template: + device_template = central_services.device_template.get_device_template( + cmd=self._cmd, + app_id=self._app_id, + device_template_id=device_template_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + self._device_templates[device_template_id] = device_template + + if not device_template: + raise CLIError( + "No device template for device template with id: '{}'.".format( + device_template_id + ) + ) + + return device_template + + def list_device_templates( + self, central_dns_suffix=CENTRAL_ENDPOINT, + ): + templates = central_services.device_template.list_device_templates( + cmd=self._cmd, app_id=self._app_id, token=self._token + ) + + self._device_templates.update({template.id: template for template in templates}) + + return self._device_templates + + def map_device_templates( + self, central_dns_suffix=CENTRAL_ENDPOINT, + ): + """ + Maps each template name to the corresponding template id + """ + templates = central_services.device_template.list_device_templates( + cmd=self._cmd, app_id=self._app_id, token=self._token + ) + return {template.name: template.id for template in templates} + + def create_device_template( + self, + device_template_id: str, + payload: str, + central_dns_suffix=CENTRAL_ENDPOINT, + ): + template = central_services.device_template.create_device_template( + cmd=self._cmd, + app_id=self._app_id, + device_template_id=device_template_id, + payload=payload, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + self._device_templates[template.id] = template + + return template + + def delete_device_template( + self, device_template_id, central_dns_suffix=CENTRAL_ENDPOINT, + ): + if not device_template_id: + raise CLIError("Device template id must be specified.") + + result = central_services.device_template.delete_device_template( + cmd=self._cmd, + token=self._token, + app_id=self._app_id, + device_template_id=device_template_id, + central_dns_suffix=central_dns_suffix, + ) + + # remove from cache + # pop "miss" raises a KeyError if None is not provided + self._device_templates.pop(device_template_id, None) + + return result diff --git a/azext_iot/central/providers/devicetwin_provider.py b/azext_iot/central/providers/devicetwin_provider.py new file mode 100644 index 000000000..836676742 --- /dev/null +++ b/azext_iot/central/providers/devicetwin_provider.py @@ -0,0 +1,60 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.util import CLIError +from knack.log import get_logger +from azext_iot.common.utility import find_between +from azext_iot._factory import SdkResolver, CloudError +from azext_iot.common.shared import SdkType +from azext_iot.common.utility import unpack_msrest_error +from azext_iot.common.sas_token_auth import BasicSasTokenAuthentication + + +logger = get_logger(__name__) + + +class CentralDeviceTwinProvider: + def __init__(self, cmd, app_id: str, token: str, device_id: str): + """ + Provider for devicetwin APIs + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + Useful in scenarios where user doesn't own the app + therefore AAD token won't work, but a SAS token generated by owner will + """ + self._cmd = cmd + self._app_id = app_id + self._token = token + self._device_id = device_id + + def get_device_twin(self, central_dns_suffix): + from azext_iot.common._azure import get_iot_central_tokens + + tokens = get_iot_central_tokens( + self._cmd, self._app_id, self._token, central_dns_suffix + ) + + exception = None + + # The device could be in any hub associated with the given app. + # We must search through each IoT Hub until device is found. + for token_group in tokens.values(): + sas_token = token_group["iothubTenantSasToken"]["sasToken"] + endpoint = find_between(sas_token, "SharedAccessSignature sr=", "&sig=") + target = {"entity": endpoint} + auth = BasicSasTokenAuthentication(sas_token=sas_token) + service_sdk = SdkResolver(target=target, auth_override=auth).get_sdk(SdkType.service_sdk) + try: + return service_sdk.devices.get_twin(id=self._device_id, raw=True).response.json() + except CloudError as e: + if exception is None: + exception = CLIError(unpack_msrest_error(e)) + + raise CLIError("Could not get device twin") diff --git a/azext_iot/central/providers/monitor_provider.py b/azext_iot/central/providers/monitor_provider.py new file mode 100644 index 000000000..555db570c --- /dev/null +++ b/azext_iot/central/providers/monitor_provider.py @@ -0,0 +1,104 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +from azure.cli.core.commands import AzCliCommand +from azext_iot.central.providers import ( + CentralDeviceProvider, + CentralDeviceTemplateProvider, +) + +from azext_iot.monitor.models.arguments import ( + CentralHandlerArguments, + TelemetryArguments, +) + + +class MonitorProvider: + """ + Provider for monitor events and validating messages + """ + + def __init__( + self, + cmd: AzCliCommand, + app_id: str, + token: str, + consumer_group: str, + central_handler_args: CentralHandlerArguments, + central_dns_suffix: str, + ): + central_device_provider = CentralDeviceProvider( + cmd=cmd, app_id=app_id, token=token + ) + central_template_provider = CentralDeviceTemplateProvider( + cmd=cmd, app_id=app_id, token=token + ) + self._targets = self._build_targets( + cmd=cmd, + app_id=app_id, + token=token, + consumer_group=consumer_group, + central_dns_suffix=central_dns_suffix, + ) + self._handler = self._build_handler( + central_device_provider=central_device_provider, + central_template_provider=central_template_provider, + central_handler_args=central_handler_args, + ) + + def start_monitor_events(self, telemetry_args: TelemetryArguments): + from azext_iot.monitor import telemetry + + telemetry.start_multiple_monitors( + targets=self._targets, + enqueued_time_utc=telemetry_args.enqueued_time, + on_start_string=self._handler.generate_startup_string("Monitoring"), + on_message_received=self._handler.parse_message, + timeout=telemetry_args.timeout, + ) + + def start_validate_messages(self, telemetry_args: TelemetryArguments): + from azext_iot.monitor import telemetry + + telemetry.start_multiple_monitors( + targets=self._targets, + enqueued_time_utc=telemetry_args.enqueued_time, + on_start_string=self._handler.generate_startup_string("Validating"), + on_message_received=self._handler.validate_message, + timeout=telemetry_args.timeout, + ) + + def _build_targets( + self, + cmd: AzCliCommand, + app_id: str, + token: str, + consumer_group: str, + central_dns_suffix: str, + ): + from azext_iot.monitor.builders import central_target_builder + + targets = central_target_builder.build_central_event_hub_targets( + cmd, app_id, token, central_dns_suffix + ) + [target.add_consumer_group(consumer_group) for target in targets] + + return targets + + def _build_handler( + self, + central_device_provider: CentralDeviceProvider, + central_template_provider: CentralDeviceTemplateProvider, + central_handler_args: CentralHandlerArguments, + ): + from azext_iot.monitor.handlers import CentralHandler + + return CentralHandler( + central_device_provider=central_device_provider, + central_template_provider=central_template_provider, + central_handler_args=central_handler_args, + ) diff --git a/azext_iot/central/providers/user_provider.py b/azext_iot/central/providers/user_provider.py new file mode 100644 index 000000000..f69ef1903 --- /dev/null +++ b/azext_iot/central/providers/user_provider.py @@ -0,0 +1,110 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.log import get_logger +from knack.util import CLIError + +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central import services as central_services +from azext_iot.central.models.enum import Role + + +logger = get_logger(__name__) + + +class CentralUserProvider: + def __init__(self, cmd, app_id: str, token=None): + """ + Provider for device APIs + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + Useful in scenarios where user doesn't own the app + therefore AAD token won't work, but a SAS token generated by owner will + """ + self._cmd = cmd + self._app_id = app_id + self._token = token + + def add_service_principal( + self, + assignee: str, + tenant_id: str, + object_id: str, + role: Role, + central_dns_suffix=CENTRAL_ENDPOINT, + ): + if not tenant_id: + raise CLIError("Must specify --tenant-id when adding a service principal") + + if not object_id: + raise CLIError("Must specify --object-id when adding a service principal") + + return central_services.user.add_service_principal( + cmd=self._cmd, + app_id=self._app_id, + assignee=assignee, + tenant_id=tenant_id, + object_id=object_id, + role=role, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + def get_user_list( + self, central_dns_suffix=CENTRAL_ENDPOINT, + ): + return central_services.user.get_user_list( + cmd=self._cmd, + app_id=self._app_id, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + def get_user( + self, assignee, central_dns_suffix=CENTRAL_ENDPOINT, + ): + return central_services.user.get_user( + cmd=self._cmd, + app_id=self._app_id, + assignee=assignee, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + def delete_user( + self, assignee, central_dns_suffix=CENTRAL_ENDPOINT, + ): + return central_services.user.delete_user( + cmd=self._cmd, + app_id=self._app_id, + assignee=assignee, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + def add_email( + self, + assignee: str, + email: str, + role: Role, + central_dns_suffix=CENTRAL_ENDPOINT, + ): + if not email: + raise CLIError("Must specify --email when adding a user by email") + + return central_services.user.add_email( + cmd=self._cmd, + app_id=self._app_id, + assignee=assignee, + email=email, + role=role, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) diff --git a/azext_iot/central/services/__init__.py b/azext_iot/central/services/__init__.py new file mode 100644 index 000000000..6463834e2 --- /dev/null +++ b/azext_iot/central/services/__init__.py @@ -0,0 +1,10 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.central.services import device, device_template, user, api_token + + +__all__ = ["device", "device_template", "user", "api_token"] diff --git a/azext_iot/central/services/_utility.py b/azext_iot/central/services/_utility.py new file mode 100644 index 000000000..f3afed651 --- /dev/null +++ b/azext_iot/central/services/_utility.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# Nothing in this file should be used outside of service/central + +from knack.util import CLIError +from requests import Response + +from azext_iot import constants +from azext_iot.common import auth + + +def get_headers(token, cmd, has_json_payload=False): + if not token: + aad_token = auth.get_aad_token(cmd, resource="https://apps.azureiotcentral.com") + token = "Bearer {}".format(aad_token["accessToken"]) + + if has_json_payload: + return { + "Authorization": token, + "User-Agent": constants.USER_AGENT, + "Content-Type": "application/json", + } + + return {"Authorization": token, "User-Agent": constants.USER_AGENT} + + +def try_extract_result(response: Response): + # 201 and 204 response codes indicate success + # with no content, hence attempting to retrieve content will fail + if response.status_code in [201, 204]: + return {"result": "success"} + + try: + body = response.json() + except: + raise CLIError("Error parsing response body") + + if "error" in body: + raise CLIError(body["error"]) + + return body diff --git a/azext_iot/central/services/api_token.py b/azext_iot/central/services/api_token.py new file mode 100644 index 000000000..fd1a0123d --- /dev/null +++ b/azext_iot/central/services/api_token.py @@ -0,0 +1,125 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import requests + +from knack.log import get_logger +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.services import _utility +from azext_iot.central.models.enum import Role + +logger = get_logger(__name__) + +BASE_PATH = "api/preview/apiTokens" + + +def add_api_token( + cmd, + app_id: str, + token_id: str, + role: Role, + token: str, + central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + Add an API token to a Central app + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token_id: Unique ID for the API token. + role : permission level to access the application + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + token: dict + """ + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, token_id) + + payload = { + "roles": [{"role": role.value}], + } + + headers = _utility.get_headers(token, cmd, has_json_payload=True) + + response = requests.put(url, headers=headers, json=payload) + return _utility.try_extract_result(response) + + +def get_api_token_list( + cmd, app_id: str, token: str, central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + Get the list of API tokens for a central app. + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + tokens: dict + """ + url = "https://{}.{}/{}".format(app_id, central_dns_suffix, BASE_PATH) + + headers = _utility.get_headers(token, cmd) + + response = requests.get(url, headers=headers) + return _utility.try_extract_result(response) + + +def get_api_token( + cmd, app_id: str, token: str, token_id: str, central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + Get information about a specified API token. + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + token_id: Unique ID for the API token. + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + token: dict + """ + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, token_id) + + headers = _utility.get_headers(token, cmd) + + response = requests.get(url, headers=headers) + return _utility.try_extract_result(response) + + +def delete_api_token( + cmd, app_id: str, token: str, token_id: str, central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + delete API token from the app. + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + token_id:Unique ID for the API token. + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + result (currently a 201) + """ + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, token_id) + + headers = _utility.get_headers(token, cmd) + + response = requests.delete(url, headers=headers) + return _utility.try_extract_result(response) diff --git a/azext_iot/central/services/device.py b/azext_iot/central/services/device.py new file mode 100644 index 000000000..82ac30da7 --- /dev/null +++ b/azext_iot/central/services/device.py @@ -0,0 +1,298 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# This is largely derived from https://docs.microsoft.com/en-us/rest/api/iotcentral/devices + +import requests + +from knack.util import CLIError +from knack.log import get_logger +from typing import List + +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.services import _utility +from azext_iot.central.models.device import Device +from azext_iot.central.models.enum import DeviceStatus + +logger = get_logger(__name__) + +BASE_PATH = "api/preview/devices" + + +def get_device( + cmd, app_id: str, device_id: str, token: str, central_dns_suffix=CENTRAL_ENDPOINT, +) -> Device: + """ + Get device info given a device id + + Args: + cmd: command passed into az + device_id: unique case-sensitive device id, + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + device: dict + """ + + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_id) + headers = _utility.get_headers(token, cmd) + + response = requests.get(url, headers=headers) + result = _utility.try_extract_result(response) + return Device(result) + + +def list_devices( + cmd, app_id: str, token: str, max_pages=1, central_dns_suffix=CENTRAL_ENDPOINT, +) -> List[Device]: + """ + Get a list of all devices in IoTC app + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + list of devices + """ + + devices = [] + + url = "https://{}.{}/{}".format(app_id, central_dns_suffix, BASE_PATH) + headers = _utility.get_headers(token, cmd) + + pages_processed = 0 + while (pages_processed <= max_pages) and url: + response = requests.get(url, headers=headers) + result = _utility.try_extract_result(response) + + if "value" not in result: + raise CLIError("Value is not present in body: {}".format(result)) + + devices = devices + [Device(device) for device in result["value"]] + + url = result.get("nextLink") + pages_processed = pages_processed + 1 + + return devices + + +def get_device_registration_summary( + cmd, app_id: str, token: str, central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + Get device registration summary for a given app + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + registration summary + """ + + registration_summary = {status.value: 0 for status in DeviceStatus} + + url = "https://{}.{}/{}".format(app_id, central_dns_suffix, BASE_PATH) + headers = _utility.get_headers(token, cmd) + logger.warning( + "This command may take a long time to complete if your app contains a lot of devices" + ) + while url: + response = requests.get(url, headers=headers) + result = _utility.try_extract_result(response) + + if "value" not in result: + raise CLIError("Value is not present in body: {}".format(result)) + + for device in result["value"]: + registration_summary[Device(device).device_status.value] += 1 + + print("Processed {} devices...".format(sum(registration_summary.values()))) + url = result.get("nextLink") + return registration_summary + + +def create_device( + cmd, + app_id: str, + device_id: str, + device_name: str, + instance_of: str, + simulated: bool, + token: str, + central_dns_suffix=CENTRAL_ENDPOINT, +) -> Device: + """ + Create a device in IoTC + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + device_id: unique case-sensitive device id + device_name: (non-unique) human readable name for the device + instance_of: (optional) string that maps to the device_template_id + of the device template that this device is to be an instance of + simulated: if IoTC is to simulate data for this device + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + device: dict + """ + + if not device_name: + device_name = device_id + + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_id) + headers = _utility.get_headers(token, cmd, has_json_payload=True) + payload = { + "displayName": device_name, + "simulated": simulated, + "approved": True, + } + if instance_of: + payload["instanceOf"] = instance_of + + response = requests.put(url, headers=headers, json=payload) + result = _utility.try_extract_result(response) + return Device(result) + + +def delete_device( + cmd, app_id: str, device_id: str, token: str, central_dns_suffix=CENTRAL_ENDPOINT, +) -> dict: + """ + Delete a device from IoTC + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + device_id: unique case-sensitive device id, + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + {"result": "success"} on success + Raises error on failure + """ + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_id) + headers = _utility.get_headers(token, cmd) + + response = requests.delete(url, headers=headers) + return _utility.try_extract_result(response) + + +def get_device_credentials( + cmd, app_id: str, device_id: str, token: str, central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + Get device credentials from IoTC + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + device_id: unique case-sensitive device id, + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + device_credentials: dict + """ + url = "https://{}.{}/{}/{}/credentials".format( + app_id, central_dns_suffix, BASE_PATH, device_id + ) + headers = _utility.get_headers(token, cmd) + + response = requests.get(url, headers=headers) + return _utility.try_extract_result(response) + + +def run_component_command( + cmd, + app_id: str, + token: str, + device_id: str, + interface_id: str, + command_name: str, + payload: dict, + central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + Execute a direct method on a device + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + device_id: unique case-sensitive device id + interface_id: interface id where command exists + command_name: name of command to execute + payload: params for command + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + result (currently a 201) + """ + url = "https://{}.{}/{}/{}/components/{}/commands/{}".format( + app_id, central_dns_suffix, BASE_PATH, device_id, interface_id, command_name + ) + headers = _utility.get_headers(token, cmd) + + response = requests.post(url, headers=headers, json=payload) + + # execute command response has caveats in it due to Async/Sync device methods + # return the response if we get 201, otherwise try to apply generic logic + if response.status_code == 201: + return response.json() + + return _utility.try_extract_result(response) + + +def get_component_command_history( + cmd, + app_id: str, + token: str, + device_id: str, + interface_id: str, + command_name: str, + central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + Get component command history + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + device_id: unique case-sensitive device id + interface_id: interface id where command exists + command_name: name of command to view execution history + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + Command history (List) - currently limited to 1 item + """ + url = "https://{}.{}/{}/{}/components/{}/commands/{}".format( + app_id, central_dns_suffix, BASE_PATH, device_id, interface_id, command_name + ) + headers = _utility.get_headers(token, cmd) + + response = requests.get(url, headers=headers) + return _utility.try_extract_result(response) diff --git a/azext_iot/central/services/device_template.py b/azext_iot/central/services/device_template.py new file mode 100644 index 000000000..44d3a9590 --- /dev/null +++ b/azext_iot/central/services/device_template.py @@ -0,0 +1,146 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# This is largely derived from https://docs.microsoft.com/en-us/rest/api/iotcentral/devicetemplates + +import requests + +from typing import List + +from knack.util import CLIError +from knack.log import get_logger +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.services import _utility +from azext_iot.central.models.template import Template + +logger = get_logger(__name__) + +BASE_PATH = "api/preview/deviceTemplates" + + +def get_device_template( + cmd, + app_id: str, + device_template_id: str, + token: str, + central_dns_suffix=CENTRAL_ENDPOINT, +) -> Template: + """ + Get a specific device template from IoTC + + Args: + cmd: command passed into az + device_template_id: case sensitive device template id, + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + device: dict + """ + url = "https://{}.{}/{}/{}".format( + app_id, central_dns_suffix, BASE_PATH, device_template_id + ) + headers = _utility.get_headers(token, cmd) + + response = requests.get(url, headers=headers) + return Template(_utility.try_extract_result(response)) + + +def list_device_templates( + cmd, app_id: str, token: str, central_dns_suffix=CENTRAL_ENDPOINT, +) -> List[Template]: + """ + Get a list of all device templates in IoTC + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + device: dict + """ + + url = "https://{}.{}/{}".format(app_id, central_dns_suffix, BASE_PATH) + headers = _utility.get_headers(token, cmd) + + response = requests.get(url, headers=headers) + + result = _utility.try_extract_result(response) + + if "value" not in result: + raise CLIError("Value is not present in body: {}".format(result)) + + return [Template(item) for item in result["value"]] + + +def create_device_template( + cmd, + app_id: str, + device_template_id: str, + payload: dict, + token: str, + central_dns_suffix=CENTRAL_ENDPOINT, +) -> Template: + """ + Create a device template in IoTC + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + device_template_id: case sensitive device template id, + payload: see example payload available in + /azext_iot/tests/central/json/device_template_int_test.json + or check here for more information + https://docs.microsoft.com/en-us/rest/api/iotcentral/devicetemplates + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + device: dict + """ + + url = "https://{}.{}/{}/{}".format( + app_id, central_dns_suffix, BASE_PATH, device_template_id + ) + headers = _utility.get_headers(token, cmd, has_json_payload=True) + + response = requests.put(url, headers=headers, json=payload) + return Template(_utility.try_extract_result(response)) + + +def delete_device_template( + cmd, + app_id: str, + device_template_id: str, + token: str, + central_dns_suffix=CENTRAL_ENDPOINT, +) -> dict: + """ + Delete a device template from IoTC + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + device_template_id: case sensitive device template id, + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + device: dict + """ + url = "https://{}.{}/{}/{}".format( + app_id, central_dns_suffix, BASE_PATH, device_template_id + ) + headers = _utility.get_headers(token, cmd) + + response = requests.delete(url, headers=headers) + return _utility.try_extract_result(response) diff --git a/azext_iot/central/services/user.py b/azext_iot/central/services/user.py new file mode 100644 index 000000000..40b84987f --- /dev/null +++ b/azext_iot/central/services/user.py @@ -0,0 +1,167 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import requests + +from knack.log import get_logger +from azext_iot.constants import CENTRAL_ENDPOINT +from azext_iot.central.services import _utility +from azext_iot.central.models.enum import Role, UserType + +logger = get_logger(__name__) + +BASE_PATH = "api/preview/users" + + +def add_service_principal( + cmd, + app_id: str, + assignee: str, + tenant_id: str, + object_id: str, + role: Role, + token: str, + central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + Add a user to a Central app + + Args: + cmd: command passed into az + tenant_id: tenant id of service principal to be added + object_id: object id of service principal to be added + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + user: dict + """ + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, assignee) + + payload = { + "tenantId": tenant_id, + "objectId": object_id, + "type": UserType.service_principal.value, + "roles": [{"role": role.value}], + } + + headers = _utility.get_headers(token, cmd, has_json_payload=True) + + response = requests.put(url, headers=headers, json=payload) + return _utility.try_extract_result(response) + + +def add_email( + cmd, + app_id: str, + assignee: str, + email: str, + role: Role, + token: str, + central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + Add a user to a Central app + + Args: + cmd: command passed into az + email: email of user to be added + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + user: dict + """ + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, assignee) + + payload = { + "email": email, + "type": UserType.email.value, + "roles": [{"role": role.value}], + } + + headers = _utility.get_headers(token, cmd, has_json_payload=True) + + response = requests.put(url, headers=headers, json=payload) + return _utility.try_extract_result(response) + + +def get_user_list( + cmd, app_id: str, token: str, central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + Get the list of users for central app. + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + users: dict + """ + url = "https://{}.{}/{}".format(app_id, central_dns_suffix, BASE_PATH) + + headers = _utility.get_headers(token, cmd) + + response = requests.get(url, headers=headers) + return _utility.try_extract_result(response) + + +def get_user( + cmd, app_id: str, token: str, assignee: str, central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + Get information for the specified user. + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + assignee: unique ID of the user + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + users: dict + """ + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, assignee) + + headers = _utility.get_headers(token, cmd) + + response = requests.get(url, headers=headers) + return _utility.try_extract_result(response) + + +def delete_user( + cmd, app_id: str, token: str, assignee: str, central_dns_suffix=CENTRAL_ENDPOINT, +): + """ + delete user from theapp. + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + assignee: unique ID of the user + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + users: dict + """ + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, assignee) + + headers = _utility.get_headers(token, cmd) + + response = requests.delete(url, headers=headers) + return _utility.try_extract_result(response) diff --git a/azext_iot/commands.py b/azext_iot/commands.py index a03f99080..68a1e021b 100644 --- a/azext_iot/commands.py +++ b/azext_iot/commands.py @@ -8,152 +8,235 @@ Load CLI commands """ -from azext_iot import iothub_ops, iotdps_ops, iotdigitaltwin_ops, iotpnp_ops, iotcentral_ops +from azext_iot import ( + iothub_ops, + iotdps_ops, +) def load_command_table(self, _): """ Load CLI commands """ - with self.command_group('iot hub', command_type=iothub_ops) as cmd_group: - cmd_group.command('query', 'iot_query') - cmd_group.command('invoke-device-method', 'iot_device_method') - cmd_group.command('invoke-module-method', 'iot_device_module_method') - cmd_group.command('generate-sas-token', 'iot_get_sas_token') - cmd_group.command('monitor-events', 'iot_hub_monitor_events') - cmd_group.command('monitor-feedback', 'iot_hub_monitor_feedback') - - with self.command_group('iot hub device-identity', command_type=iothub_ops) as cmd_group: - cmd_group.command('create', 'iot_device_create') - cmd_group.command('show', 'iot_device_show') - cmd_group.command('list', 'iot_device_list') - cmd_group.command('delete', 'iot_device_delete') - cmd_group.generic_update_command('update', getter_name='iot_device_show', - setter_name='iot_device_update') - - cmd_group.command('show-connection-string', 'iot_get_device_connection_string') - cmd_group.command('import', 'iot_device_import') - cmd_group.command('export', 'iot_device_export') - cmd_group.command('add-children', 'iot_device_children_add') - cmd_group.command('remove-children', 'iot_device_children_remove') - cmd_group.command('list-children', 'iot_device_children_list') - cmd_group.command('get-parent', 'iot_device_get_parent') - cmd_group.command('set-parent', 'iot_device_set_parent') - - with self.command_group('iot hub module-identity', command_type=iothub_ops) as cmd_group: - cmd_group.command('create', 'iot_device_module_create') - cmd_group.command('show', 'iot_device_module_show') - cmd_group.command('list', 'iot_device_module_list') - cmd_group.command('delete', 'iot_device_module_delete') - cmd_group.generic_update_command('update', getter_name='iot_device_module_show', - setter_name='iot_device_module_update') - - cmd_group.command('show-connection-string', 'iot_get_module_connection_string') - - with self.command_group('iot hub module-twin', command_type=iothub_ops) as cmd_group: - cmd_group.command('show', 'iot_device_module_twin_show') - cmd_group.command('replace', 'iot_device_module_twin_replace') - cmd_group.generic_update_command('update', getter_name='iot_device_module_twin_show', - setter_name='iot_device_module_twin_update') - - with self.command_group('iot hub device-twin', command_type=iothub_ops) as cmd_group: - cmd_group.command('show', 'iot_device_twin_show') - cmd_group.command('replace', 'iot_device_twin_replace') - cmd_group.generic_update_command('update', getter_name='iot_device_twin_show', - setter_name='iot_device_twin_update') - - with self.command_group('iot hub configuration', command_type=iothub_ops) as cmd_group: - cmd_group.command('show-metric', 'iot_hub_configuration_metric_show') - cmd_group.command('create', 'iot_hub_configuration_create') - cmd_group.command('show', 'iot_hub_configuration_show') - cmd_group.command('list', 'iot_hub_configuration_list') - cmd_group.command('delete', 'iot_hub_configuration_delete') - cmd_group.generic_update_command('update', getter_name='iot_hub_configuration_show', - setter_name='iot_hub_configuration_update') - - with self.command_group('iot hub distributed-tracing', command_type=iothub_ops, is_preview=True) as cmd_group: - cmd_group.command('show', 'iot_hub_distributed_tracing_show') - cmd_group.command('update', 'iot_hub_distributed_tracing_update') - - with self.command_group('iot edge', command_type=iothub_ops) as cmd_group: - cmd_group.command('set-modules', 'iot_edge_set_modules') - - with self.command_group('iot edge deployment', command_type=iothub_ops) as cmd_group: - cmd_group.command('show-metric', 'iot_edge_deployment_metric_show') - cmd_group.command('create', 'iot_edge_deployment_create') - cmd_group.command('show', 'iot_hub_configuration_show') - cmd_group.command('list', 'iot_edge_deployment_list') - cmd_group.command('delete', 'iot_hub_configuration_delete') - cmd_group.generic_update_command('update', getter_name='iot_hub_configuration_show', - setter_name='iot_hub_configuration_update') - - with self.command_group('iot device', command_type=iothub_ops) as cmd_group: - cmd_group.command('send-d2c-message', 'iot_device_send_message') - cmd_group.command('simulate', 'iot_simulate_device') - cmd_group.command('upload-file', 'iot_device_upload_file') - - with self.command_group('iot device c2d-message', command_type=iothub_ops) as cmd_group: - cmd_group.command('complete', 'iot_c2d_message_complete') - cmd_group.command('abandon', 'iot_c2d_message_abandon') - cmd_group.command('reject', 'iot_c2d_message_reject') - cmd_group.command('receive', 'iot_c2d_message_receive') - cmd_group.command('send', 'iot_c2d_message_send') - - with self.command_group('iot dps enrollment', command_type=iotdps_ops) as cmd_group: - cmd_group.command('create', 'iot_dps_device_enrollment_create') - cmd_group.command('list', 'iot_dps_device_enrollment_list') - cmd_group.command('show', 'iot_dps_device_enrollment_get') - cmd_group.command('update', 'iot_dps_device_enrollment_update') - cmd_group.command('delete', 'iot_dps_device_enrollment_delete') - - with self.command_group('iot dps enrollment-group', command_type=iotdps_ops) as cmd_group: - cmd_group.command('create', 'iot_dps_device_enrollment_group_create') - cmd_group.command('list', 'iot_dps_device_enrollment_group_list') - cmd_group.command('show', 'iot_dps_device_enrollment_group_get') - cmd_group.command('update', 'iot_dps_device_enrollment_group_update') - cmd_group.command('delete', 'iot_dps_device_enrollment_group_delete') - - with self.command_group('iot dps registration', command_type=iotdps_ops) as cmd_group: - cmd_group.command('list', 'iot_dps_registration_list') - cmd_group.command('show', 'iot_dps_registration_get') - cmd_group.command('delete', 'iot_dps_registration_delete') - - with self.command_group('iotcentral', command_type=iotcentral_ops, - deprecate_info=self.deprecate(redirect='iot central', hide=True)) as cmd_group: - pass - - with self.command_group('iotcentral app', command_type=iotcentral_ops) as cmd_group: - cmd_group.command('monitor-events', 'iot_central_monitor_events') - - with self.command_group('iotcentral device-twin', command_type=iotcentral_ops) as cmd_group: - cmd_group.command('show', 'iot_central_device_show') - - with self.command_group('iot central app', command_type=iotcentral_ops) as cmd_group: - cmd_group.command('monitor-events', 'iot_central_monitor_events') - - with self.command_group('iot central device-twin', command_type=iotcentral_ops) as cmd_group: - cmd_group.command('show', 'iot_central_device_show') - - with self.command_group('iot dt', command_type=iotdigitaltwin_ops, is_preview=True) as cmd_group: - cmd_group.command('list-interfaces', 'iot_digitaltwin_interface_list') - cmd_group.command('list-properties', 'iot_digitaltwin_properties_list') - cmd_group.command('update-property', 'iot_digitaltwin_property_update') - cmd_group.command('invoke-command', 'iot_digitaltwin_invoke_command') - cmd_group.command('monitor-events', 'iot_digitaltwin_monitor_events') - cmd_group.command('list-commands', 'iot_digitaltwin_command_list') - - with self.command_group('iot pnp interface', command_type=iotpnp_ops, is_preview=True) as cmd_group: - cmd_group.command('show', 'iot_pnp_interface_show') - cmd_group.command('list', 'iot_pnp_interface_list') - cmd_group.command('create', 'iot_pnp_interface_create') - cmd_group.command('publish', 'iot_pnp_interface_publish') - cmd_group.command('delete', 'iot_pnp_interface_delete') - cmd_group.command('update', 'iot_pnp_interface_update') - - with self.command_group('iot pnp capability-model', command_type=iotpnp_ops, is_preview=True) as cmd_group: - cmd_group.command('show', 'iot_pnp_model_show') - cmd_group.command('list', 'iot_pnp_model_list') - cmd_group.command('create', 'iot_pnp_model_create') - cmd_group.command('publish', 'iot_pnp_model_publish') - cmd_group.command('delete', 'iot_pnp_model_delete') - cmd_group.command('update', 'iot_pnp_model_update') + with self.command_group("iot hub", command_type=iothub_ops) as cmd_group: + cmd_group.command("query", "iot_query") + cmd_group.command("invoke-device-method", "iot_device_method") + cmd_group.command("invoke-module-method", "iot_device_module_method") + cmd_group.command("generate-sas-token", "iot_get_sas_token") + cmd_group.command("monitor-events", "iot_hub_monitor_events") + cmd_group.command("monitor-feedback", "iot_hub_monitor_feedback") + + with self.command_group( + "iot hub device-identity", command_type=iothub_ops + ) as cmd_group: + cmd_group.command("create", "iot_device_create") + cmd_group.show_command("show", "iot_device_show") + cmd_group.command("list", "iot_device_list") + cmd_group.command("delete", "iot_device_delete") + cmd_group.generic_update_command( + "update", + getter_name="iot_device_show", + custom_func_type=iothub_ops, + setter_name="iot_device_update", + custom_func_name="update_iot_device_custom" + ) + cmd_group.command("renew-key", 'iot_device_key_regenerate') + cmd_group.command( + "show-connection-string", + "iot_get_device_connection_string", + deprecate_info=self.deprecate( + redirect="az iot hub device-identity connection-string show" + ), + ) + cmd_group.command("import", "iot_device_import") + cmd_group.command("export", "iot_device_export") + cmd_group.command( + "add-children", + "iot_device_children_add", + deprecate_info=self.deprecate( + redirect="az iot hub device-identity children add" + ), + ) + cmd_group.command( + "remove-children", + "iot_device_children_remove", + deprecate_info=self.deprecate( + redirect="az iot hub device-identity children remove" + ), + ) + cmd_group.command( + "list-children", + "iot_device_children_list_comma_separated", + deprecate_info=self.deprecate( + redirect="az iot hub device-identity children list" + ), + ) + cmd_group.command( + "get-parent", + "iot_device_get_parent", + deprecate_info=self.deprecate( + redirect="az iot hub device-identity parent show" + ), + ) + cmd_group.command( + "set-parent", + "iot_device_set_parent", + deprecate_info=self.deprecate( + redirect="az iot hub device-identity parent set" + ), + ) + + with self.command_group( + "iot hub device-identity children", command_type=iothub_ops + ) as cmd_group: + cmd_group.show_command("add", "iot_device_children_add") + cmd_group.show_command("remove", "iot_device_children_remove") + cmd_group.show_command("list", "iot_device_children_list") + + with self.command_group( + "iot hub device-identity parent", command_type=iothub_ops + ) as cmd_group: + cmd_group.show_command("show", "iot_device_get_parent") + cmd_group.show_command("set", "iot_device_set_parent") + + with self.command_group( + "iot hub device-identity connection-string", command_type=iothub_ops + ) as cmd_group: + cmd_group.show_command("show", "iot_get_device_connection_string") + + with self.command_group( + "iot hub module-identity", command_type=iothub_ops + ) as cmd_group: + cmd_group.command("create", "iot_device_module_create") + cmd_group.show_command("show", "iot_device_module_show") + cmd_group.command("list", "iot_device_module_list") + cmd_group.command("delete", "iot_device_module_delete") + cmd_group.generic_update_command( + "update", + getter_name="iot_device_module_show", + setter_name="iot_device_module_update", + ) + + cmd_group.show_command( + "show-connection-string", + "iot_get_module_connection_string", + deprecate_info=self.deprecate( + redirect="az iot hub module-identity connection-string show" + ), + ) + + with self.command_group( + "iot hub module-identity connection-string", command_type=iothub_ops + ) as cmd_group: + cmd_group.show_command("show", "iot_get_module_connection_string") + + with self.command_group( + "iot hub module-twin", command_type=iothub_ops + ) as cmd_group: + cmd_group.show_command("show", "iot_device_module_twin_show") + cmd_group.command("replace", "iot_device_module_twin_replace") + cmd_group.generic_update_command( + "update", + getter_name="iot_device_module_twin_show", + setter_name="iot_device_module_twin_update", + custom_func_name="iot_twin_update_custom", + custom_func_type=iothub_ops, + ) + + with self.command_group( + "iot hub device-twin", command_type=iothub_ops + ) as cmd_group: + cmd_group.show_command("show", "iot_device_twin_show") + cmd_group.command("replace", "iot_device_twin_replace") + cmd_group.generic_update_command( + "update", + getter_name="iot_device_twin_show", + setter_name="iot_device_twin_update", + custom_func_name="iot_twin_update_custom", + custom_func_type=iothub_ops, + ) + + with self.command_group( + "iot hub configuration", command_type=iothub_ops + ) as cmd_group: + cmd_group.command("show-metric", "iot_hub_configuration_metric_show") + cmd_group.command("create", "iot_hub_configuration_create") + cmd_group.show_command("show", "iot_hub_configuration_show") + cmd_group.command("list", "iot_hub_configuration_list") + cmd_group.command("delete", "iot_hub_configuration_delete") + cmd_group.generic_update_command( + "update", + getter_name="iot_hub_configuration_show", + setter_name="iot_hub_configuration_update", + ) + + with self.command_group( + "iot hub distributed-tracing", command_type=iothub_ops, is_preview=True + ) as cmd_group: + cmd_group.show_command("show", "iot_hub_distributed_tracing_show") + cmd_group.command("update", "iot_hub_distributed_tracing_update") + + with self.command_group( + "iot hub connection-string", command_type=iothub_ops + ) as cmd_group: + cmd_group.show_command("show", "iot_hub_connection_string_show") + + with self.command_group("iot edge", command_type=iothub_ops) as cmd_group: + cmd_group.command("set-modules", "iot_edge_set_modules") + + with self.command_group( + "iot edge deployment", command_type=iothub_ops + ) as cmd_group: + cmd_group.command("show-metric", "iot_edge_deployment_metric_show") + cmd_group.command("create", "iot_edge_deployment_create") + cmd_group.show_command("show", "iot_hub_configuration_show") + cmd_group.command("list", "iot_edge_deployment_list") + cmd_group.command("delete", "iot_hub_configuration_delete") + cmd_group.generic_update_command( + "update", + getter_name="iot_hub_configuration_show", + setter_name="iot_hub_configuration_update", + ) + + with self.command_group("iot device", command_type=iothub_ops) as cmd_group: + cmd_group.command("send-d2c-message", "iot_device_send_message") + cmd_group.command("simulate", "iot_simulate_device") + cmd_group.command("upload-file", "iot_device_upload_file") + + with self.command_group( + "iot device c2d-message", command_type=iothub_ops + ) as cmd_group: + cmd_group.command("complete", "iot_c2d_message_complete") + cmd_group.command("abandon", "iot_c2d_message_abandon") + cmd_group.command("reject", "iot_c2d_message_reject") + cmd_group.command("receive", "iot_c2d_message_receive") + cmd_group.command("send", "iot_c2d_message_send") + cmd_group.command("purge", "iot_c2d_message_purge") + + with self.command_group("iot dps", command_type=iotdps_ops) as cmd_group: + cmd_group.command( + "compute-device-key", "iot_dps_compute_device_key", is_preview=True + ) + + with self.command_group("iot dps enrollment", command_type=iotdps_ops) as cmd_group: + cmd_group.command("create", "iot_dps_device_enrollment_create") + cmd_group.command("list", "iot_dps_device_enrollment_list") + cmd_group.show_command("show", "iot_dps_device_enrollment_get") + cmd_group.command("update", "iot_dps_device_enrollment_update") + cmd_group.command("delete", "iot_dps_device_enrollment_delete") + + with self.command_group( + "iot dps enrollment-group", command_type=iotdps_ops + ) as cmd_group: + cmd_group.command("create", "iot_dps_device_enrollment_group_create") + cmd_group.command("list", "iot_dps_device_enrollment_group_list") + cmd_group.show_command("show", "iot_dps_device_enrollment_group_get") + cmd_group.command("update", "iot_dps_device_enrollment_group_update") + cmd_group.command("delete", "iot_dps_device_enrollment_group_delete") + + with self.command_group( + "iot dps registration", command_type=iotdps_ops + ) as cmd_group: + cmd_group.command("list", "iot_dps_registration_list") + cmd_group.show_command("show", "iot_dps_registration_get") + cmd_group.command("delete", "iot_dps_registration_delete") diff --git a/azext_iot/common/_azure.py b/azext_iot/common/_azure.py index bc7c8d1d2..ffa76293c 100644 --- a/azext_iot/common/_azure.py +++ b/azext_iot/common/_azure.py @@ -6,177 +6,53 @@ from knack.util import CLIError from azext_iot.common.utility import validate_key_value_pairs -from azext_iot.common.utility import trim_from_start -from azext_iot._factory import iot_hub_service_factory -from azure.cli.core._profile import Profile - - -def _get_aad_token(cmd, resource=None): - ''' - get AAD token to access to a specified resource - :param resource: Azure resource endpoints. Default to Azure Resource Manager - Use 'az cloud show' command for other Azure resources - ''' - resource = (resource or cmd.cli_ctx.cloud.endpoints.active_directory_resource_id) - profile = Profile(cli_ctx=cmd.cli_ctx) - creds, subscription, tenant = profile.get_raw_token(subscription=None, resource=resource) - return { - 'tokenType': creds[0], - 'accessToken': creds[1], - 'expiresOn': creds[2].get('expiresOn', 'N/A'), - 'subscription': subscription, - 'tenant': tenant - } - - -def _parse_connection_string(cs, validate=None, cstring_type='entity'): +from azext_iot.common.auth import get_aad_token + + +def _parse_connection_string(cs, validate=None, cstring_type="entity"): decomposed = validate_key_value_pairs(cs) decomposed_lower = dict((k.lower(), v) for k, v in decomposed.items()) if validate: for k in validate: if not any([decomposed.get(k), decomposed_lower.get(k.lower())]): - raise ValueError('{} connection string has missing property: {}'.format(cstring_type, k)) + raise ValueError( + "{} connection string has missing property: {}".format( + cstring_type, k + ) + ) return decomposed def parse_pnp_connection_string(cs): - validate = ['HostName', 'RepositoryId', 'SharedAccessKeyName', 'SharedAccessKey'] - return _parse_connection_string(cs, validate, 'PnP Model Repository') + validate = ["HostName", "RepositoryId", "SharedAccessKeyName", "SharedAccessKey"] + return _parse_connection_string(cs, validate, "PnP Model Repository") def parse_iot_hub_connection_string(cs): - validate = ['HostName', 'SharedAccessKeyName', 'SharedAccessKey'] - return _parse_connection_string(cs, validate, 'IoT Hub') + validate = ["HostName", "SharedAccessKeyName", "SharedAccessKey"] + return _parse_connection_string(cs, validate, "IoT Hub") def parse_iot_device_connection_string(cs): - validate = ['HostName', 'DeviceId', 'SharedAccessKey'] - return _parse_connection_string(cs, validate, 'Device') + validate = ["HostName", "DeviceId", "SharedAccessKey"] + return _parse_connection_string(cs, validate, "Device") def parse_iot_device_module_connection_string(cs): - validate = ['HostName', 'DeviceId', 'ModuleId', 'SharedAccessKey'] - return _parse_connection_string(cs, validate, 'Module') - - -CONN_STR_TEMPLATE = 'HostName={};SharedAccessKeyName={};SharedAccessKey={}' - - -def get_iot_hub_connection_string( - cmd, - hub_name, - resource_group_name, - policy_name='iothubowner', - key_type='primary', - include_events=False, - login=None): - """ - Function used to build up dictionary of IoT Hub connection string parts - - Args: - cmd (object): Knack cmd - hub_name (str): IoT Hub name - resource_group_name (str): name of Resource Group contianing IoT Hub - policy_name (str): Security policy name for shared key; e.g. 'iothubowner'(default) - key_type (str): Shared key; either 'primary'(default) or 'secondary' - include_events (bool): Include key event hub properties - - Returns: - (dict): of connection string elements. - - Raises: - CLIError: on input validation failure. + validate = ["HostName", "DeviceId", "ModuleId", "SharedAccessKey"] + return _parse_connection_string(cs, validate, "Module") - """ - - result = {} - target_hub = None - policy = None - - if login: - try: - decomposed = parse_iot_hub_connection_string(login) - decomposed_lower = dict((k.lower(), v) for k, v in decomposed.items()) - except ValueError as e: - raise CLIError(e) - - result = {} - result['cs'] = login - result['policy'] = decomposed_lower['sharedaccesskeyname'] - result['primarykey'] = decomposed_lower['sharedaccesskey'] - result['entity'] = decomposed_lower['hostname'] - return result - - client = None - if getattr(cmd, 'cli_ctx', None): - client = iot_hub_service_factory(cmd.cli_ctx) - else: - client = cmd - - def _find_iot_hub_from_list(hubs, hub_name): - if hubs: - return next((hub for hub in hubs if hub_name.lower() == hub.name.lower()), None) - return None - - if resource_group_name is None: - hubs = client.list_by_subscription() - if not hubs: - raise CLIError('No IoT Hub found in current subscription.') - target_hub = _find_iot_hub_from_list(hubs, hub_name) - else: - try: - target_hub = client.get(resource_group_name, hub_name) - except Exception: - pass - - if target_hub is None: - raise CLIError( - 'No IoT Hub found with name {} in current subscription.'.format(hub_name)) - - try: - addprops = getattr(target_hub, 'additional_properties', None) - resource_group_name = addprops.get('resourcegroup') if addprops else getattr( - target_hub, 'resourcegroup', None) - policy = client.get_keys_for_key_name(resource_group_name, target_hub.name, policy_name) - except Exception: - pass - - if policy is None: - raise CLIError( - 'No keys found for policy {} of IoT Hub {}.'.format(policy_name, hub_name) - ) - - result['cs'] = CONN_STR_TEMPLATE.format( - target_hub.properties.host_name, - policy.key_name, - policy.primary_key if key_type == 'primary' else policy.secondary_key) - result['entity'] = target_hub.properties.host_name - result['policy'] = policy_name - result['primarykey'] = policy.primary_key - result['secondarykey'] = policy.secondary_key - result['subscription'] = client.config.subscription_id - result['resourcegroup'] = resource_group_name - result['location'] = target_hub.location - result['sku_tier'] = target_hub.sku.tier.value - - if include_events: - events = {} - events['endpoint'] = trim_from_start(target_hub.properties.event_hub_endpoints['events'].endpoint, 'sb://').strip('/') - events['partition_count'] = target_hub.properties.event_hub_endpoints['events'].partition_count - events['path'] = target_hub.properties.event_hub_endpoints['events'].path - events['partition_ids'] = target_hub.properties.event_hub_endpoints['events'].partition_ids - result['events'] = events - - return result +CONN_STR_TEMPLATE = "HostName={};SharedAccessKeyName={};SharedAccessKey={}" def get_iot_dps_connection_string( - client, - dps_name, - resource_group_name, - policy_name='provisioningserviceowner', - key_type='primary'): + client, + dps_name, + resource_group_name, + policy_name="provisioningserviceowner", + key_type="primary", +): """ Function used to build up dictionary of IoT Hub Device Provisioning Service connection string parts @@ -199,7 +75,9 @@ def get_iot_dps_connection_string( def _find_iot_dps_from_list(all_dps, dps_name): if all_dps: - return next((dps for dps in all_dps if dps_name.lower() == dps.name.lower()), None) + return next( + (dps for dps in all_dps if dps_name.lower() == dps.name.lower()), None + ) return None try: @@ -209,136 +87,70 @@ def _find_iot_dps_from_list(all_dps, dps_name): if target_dps is None: raise CLIError( - 'No IoT Provisioning Service found ' - 'with name {} in current subscription.'.format(dps_name)) + "No IoT Provisioning Service found " + "with name {} in current subscription.".format(dps_name) + ) try: policy = client.iot_dps_resource.list_keys_for_key_name( - dps_name, - policy_name, - resource_group_name) + dps_name, policy_name, resource_group_name + ) except Exception: pass if policy is None: raise CLIError( - 'No keys found for policy {} of ' - 'IoT Provisioning Service {}.'.format(policy_name, dps_name) + "No keys found for policy {} of " + "IoT Provisioning Service {}.".format(policy_name, dps_name) ) result = {} - result['cs'] = CONN_STR_TEMPLATE.format( + result["cs"] = CONN_STR_TEMPLATE.format( target_dps.properties.service_operations_host_name, policy.key_name, - policy.primary_key if key_type == 'primary' else policy.secondary_key) - result['entity'] = target_dps.properties.service_operations_host_name - result['policy'] = policy_name - result['primarykey'] = policy.primary_key - result['secondarykey'] = policy.secondary_key - result['subscription'] = client.config.subscription_id + policy.primary_key if key_type == "primary" else policy.secondary_key, + ) + result["entity"] = target_dps.properties.service_operations_host_name + result["policy"] = policy_name + result["primarykey"] = policy.primary_key + result["secondarykey"] = policy.secondary_key + result["subscription"] = client.config.subscription_id return result -def get_iot_central_tokens(cmd, app_id, central_api_uri): - def get_event_hub_token(app_id, iotcAccessToken): - import requests - url = "https://{}/v1-beta/applications/{}/diagnostics/sasTokens".format(central_api_uri, app_id) - response = requests.post(url, headers={'Authorization': 'Bearer {}'.format(iotcAccessToken)}) - return response.json() - - aad_token = _get_aad_token(cmd, resource="https://apps.azureiotcentral.com")['accessToken'] - - tokens = get_event_hub_token(app_id, aad_token) - - if tokens.get('error'): - raise CLIError( - 'Error {} getting tokens. {}'.format(tokens['error']['code'], tokens['error']['message']) - ) - - return tokens +def get_iot_central_tokens(cmd, app_id, token, central_dns_suffix): + import requests + if not token: + aad_token = get_aad_token(cmd, resource="https://apps.azureiotcentral.com")[ + "accessToken" + ] + token = "Bearer {}".format(aad_token) -def get_iot_hub_token_from_central_app_id(cmd, app_id, central_api_uri): - return get_iot_central_tokens(cmd, app_id, central_api_uri)['iothubTenantSasToken']['sasToken'] + url = "https://{}.{}/system/iothubs/generateSasTokens".format( + app_id, central_dns_suffix + ) + response = requests.post(url, headers={"Authorization": token}) + tokens = response.json() + additional_help = ( + "Please ensure that the user is logged through the `az login` command, " + "has the correct tenant set (the users home tenant) and " + "has access to the application through http://apps.azureiotcentral.com" + ) -def get_iot_pnp_connection_string( - cmd, - endpoint, - repo_id, - user_role='Admin', - login=None): - """ - Function used to build up dictionary of IoT PnP connection string parts - - Args: - cmd (object): Knack cmd - endpoint (str): PnP endpoint - repository_id (str): PnP repository Id. - user_role (str): User role of the access key for the given PnP repository. - - Returns: - (dict): of connection string elements. + if tokens.get("error"): + error_message = tokens["error"]["message"] + if tokens["error"]["code"].startswith("403.043.004."): + error_message = "{} {}".format(error_message, additional_help) - Raises: - CLIError: on input validation failure. - - """ - - from azure.cli.command_modules.iot.digitaltwinrepositoryprovisioningservice import DigitalTwinRepositoryProvisioningService - from azure.cli.command_modules.iot._utils import get_auth_header - from azext_iot.constants import PNP_REPO_ENDPOINT - - result = {} - client = None - headers = None - - if login: - - try: - decomposed = parse_pnp_connection_string(login) - except ValueError as e: - raise CLIError(e) - - result = {} - result['cs'] = login - result['policy'] = decomposed['SharedAccessKeyName'] - result['primarykey'] = decomposed['SharedAccessKey'] - result['repository_id'] = decomposed['RepositoryId'] - result['entity'] = decomposed['HostName'] - result['entity'] = result['entity'].replace('https://', '') - result['entity'] = result['entity'].replace('http://', '') - return result - - def _find_key_from_list(keys, user_role): - if keys: - return next((key for key in keys if key.user_role.lower() == user_role.lower()), None) - return None - - if repo_id: - client = DigitalTwinRepositoryProvisioningService(endpoint) - headers = get_auth_header(cmd) - keys = client.get_keys_async(repository_id=repo_id, api_version=client.api_version, custom_headers=headers) - - if keys is None: - raise CLIError('Auth key required for repository "{}"'.format(repo_id)) - - policy = _find_key_from_list(keys, user_role) - - if policy is None: - raise CLIError( - 'No auth key found for repository "{}" with user_role "{}".'.format(repo_id, user_role) - ) + raise CLIError( + "Error {} getting tokens. {}".format(tokens["error"]["code"], error_message) + ) - result['cs'] = policy.connection_string - result['entity'] = policy.service_endpoint - result['policy'] = policy.id - result['primarykey'] = policy.secret - result['repository_id'] = policy.repository_id - else: - result['entity'] = PNP_REPO_ENDPOINT + if tokens.get("message"): + error_message = "{} {}".format(tokens["message"], additional_help) + raise CLIError(error_message) - result['entity'] = result['entity'].replace('https://', '') - result['entity'] = result['entity'].replace('http://', '') - return result + return tokens diff --git a/azext_iot/common/auth.py b/azext_iot/common/auth.py new file mode 100644 index 000000000..a952b81f9 --- /dev/null +++ b/azext_iot/common/auth.py @@ -0,0 +1,27 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azure.cli.core._profile import Profile + + +def get_aad_token(cmd, resource=None): + """ + get AAD token to access to a specified resource + :param resource: Azure resource endpoints. Default to Azure Resource Manager + Use 'az cloud show' command for other Azure resources + """ + resource = resource or cmd.cli_ctx.cloud.endpoints.active_directory_resource_id + profile = Profile(cli_ctx=cmd.cli_ctx) + creds, subscription, tenant = profile.get_raw_token( + subscription=None, resource=resource + ) + return { + "tokenType": creds[0], + "accessToken": creds[1], + "expiresOn": creds[2].get("expiresOn", "N/A"), + "subscription": subscription, + "tenant": tenant, + } diff --git a/azext_iot/common/digitaltwin_sas_token_auth.py b/azext_iot/common/digitaltwin_sas_token_auth.py deleted file mode 100644 index c2abbe108..000000000 --- a/azext_iot/common/digitaltwin_sas_token_auth.py +++ /dev/null @@ -1,65 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- -""" -digitaltwin_sas_token_auth: Module containing DigitalTwin Model Shared Access Signature token class. - -""" - -from base64 import b64encode, b64decode -from hashlib import sha256 -from hmac import HMAC -from time import time -try: - from urllib import (urlencode, quote_plus) -except ImportError: - from urllib.parse import (urlencode, quote_plus) -from msrest.authentication import Authentication - - -class DigitalTwinSasTokenAuthentication(Authentication): - """ - Shared Access Signature authorization for DigitalTwin Repository. - - Args: - uri (str): Uri of target resource. - shared_access_policy_name (str): Name of shared access policy. - shared_access_key (str): Shared access key. - expiry (int): Expiry of the token to be generated. Input should - be seconds since the epoch, in UTC. Default is an hour later from now. - """ - def __init__(self, repositoryId, endpoint, shared_access_key_name, shared_access_key, expiry=None): - self.repositoryId = repositoryId - self.policy = shared_access_key_name - self.key = shared_access_key - self.endpoint = endpoint - if expiry is None: - self.expiry = time() + 3600 # Default expiry is an hour later - else: - self.expiry = expiry - - def generate_sas_token(self): - """ - Create a shared access signiture token as a string literal. - - Returns: - result (str): SAS token as string literal. - """ - encoded_uri = quote_plus(self.endpoint) - encoded_repo_id = quote_plus(self.repositoryId) - ttl = int(self.expiry) - sign_key = '%s\n%s\n%d' % (encoded_repo_id, encoded_uri, ttl) - signature = b64encode(HMAC(b64decode(self.key), sign_key.encode('utf-8'), sha256).digest()) - result = { - 'sr': self.endpoint, - 'sig': signature, - 'se': str(ttl) - } - - if self.policy: - result['skn'] = self.policy - result['rid'] = self.repositoryId - - return 'SharedAccessSignature ' + urlencode(result) diff --git a/azext_iot/common/embedded_cli.py b/azext_iot/common/embedded_cli.py new file mode 100644 index 000000000..5e5ab471a --- /dev/null +++ b/azext_iot/common/embedded_cli.py @@ -0,0 +1,70 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +import shlex +from azure.cli.core import get_default_cli +from knack.log import get_logger +from knack.util import CLIError +from io import StringIO + +logger = get_logger(__name__) + + +class EmbeddedCLI(object): + def __init__(self): + super(EmbeddedCLI, self).__init__() + self.output = "" + self.error_code = 0 + self.az_cli = get_default_cli() + + def invoke(self, command: str, subscription: str = None): + output_file = StringIO() + + command = self._ensure_json_output(command=command) + if subscription: + command = self._ensure_subscription( + command=command, subscription=subscription + ) + + # TODO: Capture stderr? + try: + self.error_code = ( + self.az_cli.invoke(shlex.split(command), out_file=output_file) or 0 + ) + except SystemExit as se: + # Support caller error handling + self.error_code = se.code + + self.output = output_file.getvalue() + logger.debug( + "Embedded CLI received error code: %s, output: '%s'", + self.error_code, + self.output, + ) + output_file.close() + + return self + + def as_json(self): + try: + return json.loads(self.output) + except: + raise CLIError( + "Issue parsing received payload '{}' as json. Please try again or check resource status.".format( + self.output + ) + ) + + def success(self): + logger.debug("Operation error code: %s", self.error_code) + return self.error_code == 0 + + def _ensure_json_output(self, command: str): + return "{} -o json".format(command) + + def _ensure_subscription(self, command: str, subscription: str): + return "{} --subscription '{}'".format(command, subscription) diff --git a/azext_iot/common/shared.py b/azext_iot/common/shared.py index cb28783ee..2fd07e353 100644 --- a/azext_iot/common/shared.py +++ b/azext_iot/common/shared.py @@ -25,7 +25,6 @@ class SdkType(Enum): dps_sdk = 5 device_sdk = 6 service_sdk = 7 - pnp_sdk = 8 class EntityStatusType(Enum): @@ -131,6 +130,7 @@ class AllocationType(Enum): hashed = "hashed" geolatency = "geolatency" static = "static" + custom = "custom" class DistributedTracingSamplingModeType(Enum): @@ -152,16 +152,6 @@ class PnPModelType(Enum): capabilityModel = "capabilityModel" -class ModelSourceType(Enum): - """ - Type of source to get model definition. - """ - - public = "public" - private = "private" - device = "device" - - class ConfigType(Enum): """ Type of configuration deployment. @@ -214,3 +204,47 @@ class JobStatusType(Enum): cancelled = "cancelled" scheduled = "scheduled" queued = "queued" + + +class AuthenticationType(Enum): + """ + Route or endpoint authentication mechanism. + """ + + keyBased = "key" + identityBased = "identity" + + +class RenewKeyType(Enum): + """ + Target key type for regeneration. + """ + + primary = KeyType.primary.value + secondary = KeyType.secondary.value + swap = "swap" + + +class IoTHubStateType(Enum): + """ + IoT Hub State Property + """ + + Activating = "Activating" + Active = "Active" + Deleting = "Deleting" + Deleted = "Deleted" + ActivationFailed = "ActivationFailed" + DeletionFailed = "DeletionFailed" + Transitioning = "Transitioning" + Suspending = "Suspending" + Suspended = "Suspended" + Resuming = "Resuming" + FailingOver = "FailingOver" + FailoverFailed = "FailoverFailed" + TenantCommitted = "TenantCommitted" + Restoring = "Restoring" + IdentityCreated = "IdentityCreated" + KeyEncryptionKeyRevoking = "KeyEncryptionKeyRevoking" + KeyEncryptionKeyRevoked = "KeyEncryptionKeyRevoked" + ReActivating = "ReActivating" diff --git a/azext_iot/common/utility.py b/azext_iot/common/utility.py index b60d58725..e116f1d6d 100644 --- a/azext_iot/common/utility.py +++ b/azext_iot/common/utility.py @@ -5,15 +5,19 @@ # -------------------------------------------------------------------------------------------- """ -utility: Define helper functions for 'common' scripts. +utility: Defines common utility functions and components. """ import ast import base64 +import isodate import json import os import sys +import re +import hmac +import hashlib from threading import Event, Thread from datetime import datetime @@ -34,13 +38,13 @@ def parse_entity(entity, filter_none=False): result (dict): a dictionary of attributes from the function input. """ result = {} - attributes = [attr for attr in dir(entity) if not attr.startswith('_')] + attributes = [attr for attr in dir(entity) if not attr.startswith("_")] for attribute in attributes: value = getattr(entity, attribute, None) if filter_none and not value: continue value_behavior = dir(value) - if '__call__' not in value_behavior: + if "__call__" not in value_behavior: result[attribute] = value return result @@ -73,19 +77,25 @@ def verify_transform(subject, mapping): verifies that subject[k] is of type mapping[k] """ import jmespath + for k in mapping.keys(): result = jmespath.search(k, subject) if result is None: raise AttributeError('The property "{}" is required'.format(k)) if not isinstance(result, mapping[k]): - supplemental_info = '' + supplemental_info = "" if mapping[k] == dict: - wiki_link = 'https://github.com/Azure/azure-iot-cli-extension/wiki/Tips' - supplemental_info = 'Review inline JSON examples here --> {}'.format(wiki_link) - - raise TypeError('The property "{}" must be of {} but is {}. Input: {}. {}'.format( - k, str(mapping[k]), str(type(result)), result, supplemental_info)) + wiki_link = "https://github.com/Azure/azure-iot-cli-extension/wiki/Tips" + supplemental_info = "Review inline JSON examples here --> {}".format( + wiki_link + ) + + raise TypeError( + 'The property "{}" must be of {} but is {}. Input: {}. {}'.format( + k, str(mapping[k]), str(type(result)), result, supplemental_info + ) + ) def validate_key_value_pairs(string): @@ -99,8 +109,8 @@ def validate_key_value_pairs(string): """ result = None if string: - kv_list = [x for x in string.split(';') if '=' in x] # key-value pairs - result = dict(x.split('=', 1) for x in kv_list) + kv_list = [x for x in string.split(";") if "=" in x] # key-value pairs + result = dict(x.split("=", 1) for x in kv_list) return result @@ -139,6 +149,7 @@ def shell_safe_json_parse(json_or_dict_string, preserve_order=False): if not preserve_order: return json.loads(json_or_dict_string) from collections import OrderedDict + return json.loads(json_or_dict_string, object_pairs_hook=OrderedDict) except ValueError as json_ex: try: @@ -146,36 +157,41 @@ def shell_safe_json_parse(json_or_dict_string, preserve_order=False): except SyntaxError: raise CLIError(json_ex) except ValueError as ex: - logger.debug(ex) # log the exception which could be a python dict parsing error. - raise CLIError(json_ex) # raise json_ex error which is more readable and likely. + logger.debug( + ex + ) # log the exception which could be a python dict parsing error. + raise CLIError( + json_ex + ) # raise json_ex error which is more readable and likely. def read_file_content(file_path, allow_binary=False): from codecs import open as codecs_open + # Note, always put 'utf-8-sig' first, so that BOM in WinOS won't cause trouble. - for encoding in ['utf-8-sig', 'utf-8', 'utf-16', 'utf-16le', 'utf-16be']: + for encoding in ["utf-8-sig", "utf-8", "utf-16", "utf-16le", "utf-16be"]: try: with codecs_open(file_path, encoding=encoding) as f: - logger.debug("attempting to read file %s as %s", file_path, encoding) + logger.debug("Attempting to read file %s as %s", file_path, encoding) return f.read() except (UnicodeError, UnicodeDecodeError): pass if allow_binary: try: - with open(file_path, 'rb') as input_file: - logger.debug("attempting to read file %s as binary", file_path) + with open(file_path, "rb") as input_file: + logger.debug("Attempting to read file %s as binary", file_path) return base64.b64encode(input_file.read()).decode("utf-8") except Exception: # pylint: disable=broad-except pass - raise CLIError('Failed to decode file {} - unknown decoding'.format(file_path)) + raise CLIError("Failed to decode file {} - unknown decoding".format(file_path)) def trim_from_start(s, substring): """ Trims a substring from the target string (if it exists) returning the trimmed string. Otherwise returns original target string. """ if s.startswith(substring): - s = s[len(substring):] + s = s[len(substring) :] return s @@ -186,12 +202,17 @@ def validate_min_python_version(major, minor, error_msg=None, exit_on_fail=True) if version.major > major: return True if major == version.major: - result = (version.minor >= minor) + result = version.minor >= minor if not result: if exit_on_fail: - msg = error_msg if error_msg else 'Python version {}.{} or higher required for this functionality.'.format( - major, minor) + msg = ( + error_msg + if error_msg + else "Python version {}.{} or higher required for this functionality.".format( + major, minor + ) + ) sys.exit(msg) return result @@ -205,10 +226,10 @@ def unicode_binary_map(target): for k in target: key = k if isinstance(k, bytes): - key = str(k, 'utf8') + key = str(k, "utf8") if isinstance(target[k], bytes): - result[key] = str(target[k], 'utf8') + result[key] = str(target[k], "utf8") else: result[key] = target[k] @@ -232,11 +253,11 @@ def execute_onthread(**kwargs): Event(), Thread(): Event object to set the cancellation flag, Executing Thread object """ - interval = kwargs.get('interval') - method = kwargs.get('method') - method_args = kwargs.get('args') - max_runs = kwargs.get('max_runs') - handle = kwargs.get('return_handle') + interval = kwargs.get("interval") + method = kwargs.get("method") + method_args = kwargs.get("args") + max_runs = kwargs.get("max_runs") + handle = kwargs.get("return_handle") if not interval: interval = 2 @@ -292,6 +313,7 @@ def url_encode_str(s, plus=False): def test_import(package): """ Used to determine if a dependency is loading correctly """ import importlib + try: importlib.import_module(package) except ImportError: @@ -302,10 +324,10 @@ def test_import(package): def unpack_pnp_http_error(e): error = unpack_msrest_error(e) if isinstance(error, dict): - if error.get('error'): - error = error['error'] - if error.get('stackTrace'): - error.pop('stackTrace') + if error.get("error"): + error = error["error"] + if error.get("stackTrace"): + error.pop("stackTrace") return error @@ -340,13 +362,13 @@ def init_monitoring(cmd, timeout, properties, enqueued_time, repair, yes): validate_min_python_version(3, 5) if timeout < 0: - raise CLIError('Monitoring timeout must be 0 (inf) or greater.') - timeout = (timeout * 1000) + raise CLIError("Monitoring timeout must be 0 (inf) or greater.") + timeout = timeout * 1000 config = cmd.cli_ctx.config output = cmd.cli_ctx.invocation.data.get("output", None) if not output: - output = 'json' + output = "json" ensure_uamqp(config, yes, repair) if not properties: @@ -358,17 +380,6 @@ def init_monitoring(cmd, timeout, properties, enqueued_time, repair, yes): return (enqueued_time, properties, timeout, output) -def get_sas_token(target): - from azext_iot.common.digitaltwin_sas_token_auth import DigitalTwinSasTokenAuthentication - token = '' - if target.get('repository_id'): - token = DigitalTwinSasTokenAuthentication(target["repository_id"], - target["entity"], - target["policy"], - target["primarykey"]).generate_sas_token() - return {'Authorization': '{}'.format(token)} - - def dict_clean(d): """ Remove None from dictionary """ if not isinstance(d, dict): @@ -396,19 +407,96 @@ def looks_like_file(element): ".java", ".ts", ".js", - ".cs" + ".cs", ) ) -def ensure_pkg_resources_entries(): - import pkg_resources +class ISO8601Validator: + def is_iso8601_date(self, to_validate) -> bool: + try: + return bool(isodate.parse_date(to_validate)) + except Exception: + return False + + def is_iso8601_datetime(self, to_validate: str) -> bool: + try: + return bool(isodate.parse_datetime(to_validate)) + except Exception: + return False + + def is_iso8601_duration(self, to_validate: str) -> bool: + try: + return bool(isodate.parse_duration(to_validate)) + except Exception: + return False + + def is_iso8601_time(self, to_validate: str) -> bool: + try: + return bool(isodate.parse_time(to_validate)) + except Exception: + return False + + +def ensure_iothub_sdk_min_version(min_ver): + from packaging import version + try: + from azure.mgmt.iothub import __version__ as iot_sdk_version + except ImportError: + from azure.mgmt.iothub._configuration import VERSION as iot_sdk_version + + return version.parse(iot_sdk_version) >= version.parse(min_ver) + + +def scantree(path): + for entry in os.scandir(path): + if entry.is_dir(follow_symlinks=False): + yield from scantree(entry.path) + else: + yield entry + + +def find_between(s, start, end): + return (s.split(start))[1].split(end)[0] + + +def valid_hostname(host_name): + """ + Approximate validation + Reference: https://en.wikipedia.org/wiki/Hostname + """ + + if len(host_name) > 253: + return False - from azure.cli.core.extension import get_extension_path - from azext_iot.constants import EXTENSION_NAME + valid_label = re.compile(r"(?!-)[A-Z\d-]{1,63}(? + az dt create -n {instance_name} -g {resouce_group} + + - name: Create instance in target resource group with specified location and tags. + text: > + az dt create -n {instance_name} -g {resouce_group} -l westus --tags a=b c=d + + - name: Create instance in the target resource group with a system managed identity. + text: > + az dt create -n {instance_name} -g {resouce_group} --assign-identity + + - name: Create instance in the target resource group with a system managed identity then + assign the identity to one or more scopes (space-separated) with the role of Contributor. + text: > + az dt create -n {instance_name} -g {resouce_group} --assign-identity + --scopes + "/subscriptions/a12345ea-bb21-994d-2263-c716348e32a1/resourceGroups/ProResourceGroup/providers/Microsoft.EventHub/namespaces/myEventHubNamespace/eventhubs/myEventHub" + "/subscriptions/a12345ea-bb21-994d-2263-c716348e32a1/resourceGroups/ProResourceGroup/providers/Microsoft.ServiceBus/namespaces/myServiceBusNamespace/topics/myTopic" + + - name: Create instance in the target resource group with a system managed identity then + assign the identity to one or more scopes with a custom specified role. + text: > + az dt create -n {instance_name} -g {resouce_group} --assign-identity + --scopes + "/subscriptions/a12345ea-bb21-994d-2263-c716348e32a1/resourceGroups/ProResourceGroup/providers/Microsoft.EventHub/namespaces/myEventHubNamespace/eventhubs/myEventHub" + "/subscriptions/a12345ea-bb21-994d-2263-c716348e32a1/resourceGroups/ProResourceGroup/providers/Microsoft.ServiceBus/namespaces/myServiceBusNamespace/topics/myTopic" + --role MyCustomRole + + - name: Update an instance in the target resource group to enable system managed identity. + text: > + az dt create -n {instance_name} -g {resouce_group} --assign-identity + + - name: Update an instance in the target resource group to disable system managed identity. + text: > + az dt create -n {instance_name} -g {resouce_group} --assign-identity false + + - name: Update an instance in the target resource group with new tag values and disable public network access. + text: > + az dt create -n {instance_name} -g {resouce_group} --tags env=prod --public-network-access Disabled + """ + + helps["dt show"] = """ + type: command + short-summary: Show an existing Digital Twins instance. + + examples: + - name: Show an instance. + text: > + az dt show -n {instance_name} + + - name: Show an instance and project certain properties. + text: > + az dt show -n {instance_name} --query "{Endpoint:hostName, Location:location}" + """ + + helps["dt list"] = """ + type: command + short-summary: List the collection of Digital Twins instances by subscription or resource group. + + examples: + - name: List all instances in the current subscription. + text: > + az dt list + + - name: List all instances in target resource group and output in table format. + text: > + az dt list -g {resource_group} --output table + + - name: List all instances in subscription that meet a condition. + text: > + az dt list --query "[?contains(name, 'Production')]" + + - name: Count instances that meet condition. + text: > + az dt list --query "length([?contains(name, 'Production')])" + """ + + helps["dt delete"] = """ + type: command + short-summary: Delete an existing Digital Twins instance. + + examples: + - name: Delete an arbitrary instance in blocking fashion with a confirmation prompt. + text: > + az dt delete -n {instance_name} + - name: Delete an arbitrary instance with no blocking or prompt. + text: > + az dt delete -n {instance_name} -y --no-wait + """ + + helps["dt endpoint"] = """ + type: group + short-summary: Manage and configure Digital Twins instance endpoints. + """ + + helps["dt endpoint create"] = """ + type: group + short-summary: Add egress endpoints to a Digital Twins instance. + """ + + helps["dt endpoint create eventgrid"] = """ + type: command + short-summary: Adds an EventGrid Topic endpoint to a Digital Twins instance. + Requires pre-created resource. + + examples: + - name: Adds an EventGrid Topic endpoint to a target instance. + text: > + az dt endpoint create eventgrid --endpoint-name {endpoint_name} + --eventgrid-resource-group {eventgrid_resource_group} + --eventgrid-topic {eventgrid_topic_name} + -n {instance_name} + """ + + helps["dt endpoint create eventhub"] = """ + type: command + short-summary: Adds an EventHub endpoint to a Digital Twins instance. + Requires pre-created resource. The instance must be created + with a managed identity to support identity based endpoint integration + + examples: + - name: Adds an EventHub endpoint to a target instance using Key based auth. + text: > + az dt endpoint create eventhub --endpoint-name {endpoint_name} + --eventhub-resource-group {eventhub_resource_group} + --eventhub-namespace {eventhub_namespace} + --eventhub {eventhub_name} + --eventhub-policy {eventhub_policy} + -n {instance_name} + + - name: Adds an EventHub endpoint to a target instance using Identity based auth. + text: > + az dt endpoint create eventhub --endpoint-name {endpoint_name} + --eventhub-resource-group {eventhub_resource_group} + --eventhub-namespace {eventhub_namespace} + --eventhub {eventhub_name} + --auth-type IdentityBased + -n {instance_name} + """ + + helps["dt endpoint create servicebus"] = """ + type: command + short-summary: Adds a ServiceBus Topic endpoint to a Digital Twins instance. + Requires pre-created resource. The instance must be created + with a managed identity to support identity based endpoint integration + + examples: + - name: Adds a ServiceBus Topic endpoint to a target instance using Key based auth. + text: > + az dt endpoint create servicebus --endpoint-name {endpoint_name} + --servicebus-resource-group {servicebus_resource_group} + --servicebus-namespace {servicebus_namespace} + --servicebus-topic {servicebus_topic_name} + --servicebus-policy {servicebus_policy} + -n {instance_name} + + - name: Adds a ServiceBus Topic endpoint to a target instance using Identity based auth. + text: > + az dt endpoint create servicebus --endpoint-name {endpoint_name} + --servicebus-resource-group {servicebus_resource_group} + --servicebus-namespace {servicebus_namespace} + --servicebus-topic {servicebus_topic_name} + -n {instance_name} + """ + + helps["dt endpoint list"] = """ + type: command + short-summary: List all egress endpoints configured on a Digital Twins instance. + + examples: + - name: List all egress endpoints configured on an instance. + text: > + az dt endpoint list -n {instance_name} + """ + + helps["dt endpoint show"] = """ + type: command + short-summary: Show details of an endpoint configured on a Digital Twins instance. + + examples: + - name: Show a desired endpoint by name on an instance. + text: > + az dt endpoint show -n {instance_name} --endpoint-name {endpoint_name} + """ + + helps["dt endpoint delete"] = """ + type: command + short-summary: Remove an endpoint from a Digital Twins instance. + + examples: + - name: Remove an endpoint from an instance and block until the operation is complete. + text: > + az dt endpoint delete -n {instance_name} --endpoint-name {endpoint_name} + - name: Remove an endpoint from an instance without confirmation or blocking. + text: > + az dt endpoint delete -n {instance_name} --endpoint-name {endpoint_name} -y --no-wait + """ + + helps["dt network"] = """ + type: group + short-summary: Manage Digital Twins network configuration including private links and endpoint connections. + """ + + helps["dt network private-link"] = """ + type: group + short-summary: Manage Digital Twins instance private-link operations. + """ + + helps["dt network private-link show"] = """ + type: command + short-summary: Show a private-link associated with the instance. + + examples: + - name: Show the private-link named "API" associated with the instance. + text: > + az dt network private-link show -n {instance_name} --link-name API + """ + + helps["dt network private-link list"] = """ + type: command + short-summary: List private-links associated with the Digital Twins instance. + + examples: + - name: List all private-links associated with the instance. + text: > + az dt network private-link list -n {instance_name} + """ + + helps["dt network private-endpoint"] = """ + type: group + short-summary: Manage Digital Twins instance private-endpoints. + long-summary: Use 'az network private-endpoint create' to create a private-endpoint and link to a Digital Twins resource. + """ + + helps["dt network private-endpoint connection"] = """ + type: group + short-summary: Manage Digital Twins instance private-endpoint connections. + """ + + helps["dt network private-endpoint connection list"] = """ + type: command + short-summary: List private-endpoint connections associated with the Digital Twins instance. + + examples: + - name: List all private-endpoint connections associated with the instance. + text: > + az dt network private-endpoint connection list -n {instance_name} + """ + + helps["dt network private-endpoint connection show"] = """ + type: command + short-summary: Show a private-endpoint connection associated with the Digital Twins instance. + + examples: + - name: Show details of the private-endpoint connection named ba8408b6-1372-41b2-aef8-af43afc4729f. + text: > + az dt network private-endpoint connection show -n {instance_name} --cn ba8408b6-1372-41b2-aef8-af43afc4729f + """ + + helps["dt network private-endpoint connection set"] = """ + type: command + short-summary: Set the state of a private-endpoint connection associated with the Digital Twins instance. + + examples: + - name: Approve a pending private-endpoint connection associated with the instance and add a description. + text: > + az dt network private-endpoint connection set -n {instance_name} --cn {connection_name} --status Approved --desc "A description." + + - name: Reject a private-endpoint connection associated with the instance and add a description. + text: > + az dt network private-endpoint connection set -n {instance_name} --cn {connection_name} --status Rejected --desc "Does not comply." + """ + + helps["dt network private-endpoint connection delete"] = """ + type: command + short-summary: Delete a private-endpoint connection associated with the Digital Twins instance. + + examples: + - name: Delete the private-endpoint connection named ba8408b6-1372-41b2-aef8-af43afc4729f with confirmation. Block until finished. + text: > + az dt network private-endpoint connection delete -n {instance_name} --cn ba8408b6-1372-41b2-aef8-af43afc4729f + + - name: Delete the private-endpoint connection named ba8408b6-1372-41b2-aef8-af43afc4729f no confirmation. Return immediately. + text: > + az dt network private-endpoint connection delete -n {instance_name} --cn ba8408b6-1372-41b2-aef8-af43afc4729f -y --no-wait + """ + + helps["dt role-assignment"] = """ + type: group + short-summary: Manage RBAC role assignments for a Digital Twins instance. + long-summary: | + Note that in order to perform role assignments, the logged in principal needs permissions + such as Owner or User Access Administrator at the assigned scope. + + This command group is provided for convenience. For more complex role assignment scenarios + use the 'az role assignment' command group. + """ + + helps["dt role-assignment create"] = """ + type: command + short-summary: Assign a user, group or service principal to a role against a Digital Twins instance. + long-summary: + Note that in order to perform role assignments, the logged in principal needs permissions + such as Owner or User Access Administrator at the assigned scope. + + examples: + - name: Assign a user (by email) the built-in Digital Twins Owner role against a target instance. + text: > + az dt role-assignment create -n {instance_name} --assignee "owneruser@microsoft.com" --role "Azure Digital Twins Data Owner" + + - name: Assign a user (by object Id) the built-in Digital Twins Reader role against a target instance. + text: > + az dt role-assignment create -n {instance_name} --assignee "97a89267-0966-4054-a156-b7d86ef8e216" --role "Azure Digital Twins Data Reader" + + - name: Assign a service principal a custom role against a target instance. + text: > + az dt role-assignment create -n {instance_name} --assignee {service_principal_name_or_id} --role {role_name_or_id} + """ + + helps["dt role-assignment delete"] = """ + type: command + short-summary: Remove a user, group or service principal role assignment from a Digital Twins instance. + long-summary: + Note that in order to perform role assignments, the logged in principal needs permissions + such as Owner or User Access Administrator at the assigned scope. + + examples: + - name: Remove a user from a specific role assignment of a Digital Twins instance. + text: > + az dt role-assignment delete -n {instance_name} --assignee "removeuser@microsoft.com" --role "Azure Digital Twins Data Reader" + + - name: Remove a user from all assigned roles of a Digital Twins instance. + text: > + az dt role-assignment delete -n {instance_name} --assignee "removeuser@microsoft.com" + """ + + helps["dt role-assignment list"] = """ + type: command + short-summary: List the existing role assignments of a Digital Twins instance. + + examples: + - name: List the role assignments on a target instance. + text: > + az dt role-assignment list -n {instance_name} + + - name: List the role assignments on a target instance and filter by role. + text: > + az dt role-assignment list -n {instance_name} --role {role_name_or_id} + """ + + helps["dt route"] = """ + type: group + short-summary: Manage and configure event routes. + long-summary: + Note that an endpoint must first be configred before adding an event route. + """ + + helps["dt route create"] = """ + type: command + short-summary: Add an event route to a Digital Twins instance. + + examples: + - name: Adds an event route for an existing endpoint on target instance with default filter of "true". + text: > + az dt route create -n {instance_or_hostname} --endpoint-name {endpoint_name} --route-name {route_name} + - name: Adds an event route for an existing endpoint on target instance with custom filter. + text: > + az dt route create -n {instance_or_hostname} --endpoint-name {endpoint_name} --route-name {route_name} + --filter "type = 'Microsoft.DigitalTwins.Twin.Create'" + """ + + helps["dt route list"] = """ + type: command + short-summary: List the configured event routes of a Digital Twins instance. + + examples: + - name: List configured event routes of a target instance. + text: > + az dt route list -n {instance_or_hostname} + """ + + helps["dt route delete"] = """ + type: command + short-summary: Remove an event route from a Digital Twins instance. + + examples: + - name: Remove an event route from a target instance. + text: > + az dt route delete -n {instance_or_hostname} --route-name {route_name} + """ + + helps["dt route show"] = """ + type: command + short-summary: Show details of an event route configured on a Digital Twins instance. + + examples: + - name: Show an event route on a target instance. + text: > + az dt route show -n {instance_or_hostname} --route-name {route_name} + """ + + helps["dt twin"] = """ + type: group + short-summary: Manage and configure the digital twins of a Digital Twins instance. + """ + + helps["dt twin create"] = """ + type: command + short-summary: Create a digital twin on an instance. + long-summary: | + --properties can be inline JSON or file path. + Note: --properties are required for twins that contain components. + + examples: + - name: Create a digital twin from an existing (prior-created) model. + text: > + az dt twin create -n {instance_or_hostname} --dtmi "dtmi:com:example:Room;1" + --twin-id {twin_id} + + - name: Create a digital twin from an existing (prior-created) model with if-none-match tag. + text: > + az dt twin create -n {instance_or_hostname} --dtmi "dtmi:com:example:Room;1" + --twin-id {twin_id} --if-none-match + + - name: Create a digital twin from an existing (prior-created) model. Instantiate with property values. + text: > + az dt twin create -n {instance_or_hostname} --dtmi "dtmi:com:example:DeviceInformation;1" + --twin-id {twin_id} --properties '{"manufacturer": "Microsoft"}' + + - name: Create a digital twin with component from existing (prior-created) models. Instantiate component with minimum properties. + text: > + az dt twin create -n {instance_or_hostname} --dtmi "dtmi:com:example:TemperatureController;1" --twin-id {twin_id} --properties '{ + "Thermostat": { + "$metadata": {}, + } + }' + + - name: Create a digital twin with component from existing (prior-created) models. Instantiate with property values. + text: > + az dt twin create -n {instance_or_hostname} --dtmi "dtmi:com:example:TemperatureController;1" --twin-id {twin_id} --properties '{ + "Temperature": 10.2, + "Thermostat": { + "$metadata": {}, + "setPointTemp": 23.12 + } + }' + """ + + helps["dt twin update"] = """ + type: command + short-summary: Update an instance digital twin via JSON patch specification. + long-summary: Updates to property values and $model elements may happen + in the same request. Operations are limited to add, replace and remove. + + examples: + - name: Update a digital twin via JSON patch specification. + text: > + az dt twin update -n {instance_or_hostname} --twin-id {twin_id} + --json-patch '{"op":"replace", "path":"/Temperature", "value": 20.5}' + + - name: Update a digital twin via JSON patch specification and using etag. + text: > + az dt twin update -n {instance_or_hostname} --twin-id {twin_id} --etag {etag} + --json-patch '{"op":"replace", "path":"/Temperature", "value": 20.5}' + + - name: Update a digital twin via JSON patch specification. + text: > + az dt twin update -n {instance_or_hostname} --twin-id {twin_id} + --json-patch '[ + {"op":"replace", "path":"/Temperature", "value": 20.5}, + {"op":"add", "path":"/Areas", "value": ["ControlSystem"]} + ]' + + - name: Update a digital twin via JSON patch specification defined in a file. + text: > + az dt twin update -n {instance_or_hostname} --twin-id {twin_id} + --json-patch ./my/patch/document.json + """ + + helps["dt twin show"] = """ + type: command + short-summary: Show the details of a digital twin. + + examples: + - name: Show the details of a digital twin. + text: > + az dt twin show -n {instance_or_hostname} --twin-id {twin_id} + """ + + helps["dt twin query"] = """ + type: command + short-summary: Query the digital twins of an instance. Allows traversing relationships and filtering by property values. + + examples: + - name: Query all digital twins in target instance and project all attributes. Also show cost in query units. + text: > + az dt twin query -n {instance_or_hostname} -q "select * from digitaltwins" --show-cost + + - name: Query by model and project all attributes. + text: > + az dt twin query -n {instance_or_hostname} -q "select * from digitaltwins T where IS_OF_MODEL(T, 'dtmi:com:example:Room;2')" + """ + + helps["dt twin delete"] = """ + type: command + short-summary: Remove a digital twin. All relationships referencing this twin must already be deleted. + + examples: + - name: Remove a digital twin by Id. + text: > + az dt twin delete -n {instance_or_hostname} --twin-id {twin_id} + + - name: Remove a digital twin by Id using the etag. + text: > + az dt twin delete -n {instance_or_hostname} --twin-id {twin_id} --etag {etag} + """ + + helps["dt twin delete-all"] = """ + type: command + short-summary: Deletes all digital twins within a Digital Twins instance, including all relationships for those twins. + + examples: + - name: Delete all digital twins. Any relationships referencing the twins will also be deleted. + text: > + az dt twin delete-all -n {instance_or_hostname} + """ + + helps["dt twin relationship"] = """ + type: group + short-summary: Manage and configure the digital twin relationships of a Digital Twins instance. + """ + + helps["dt twin relationship create"] = """ + type: command + short-summary: Create a relationship between source and target digital twins. + long-summary: --properties can be inline JSON or file path. + + examples: + - name: Create a relationship between two digital twins. + text: > + az dt twin relationship create -n {instance_or_hostname} --relationship-id {relationship_id} --relationship contains + --twin-id {source_twin_id} --target {target_twin_id} + + - name: Create a relationship between two digital twins with if-none-match tag + text: > + az dt twin relationship create -n {instance_or_hostname} --relationship-id {relationship_id} --relationship contains + --twin-id {source_twin_id} --target {target_twin_id} --if-none-match + + - name: Create a relationship with initialized properties between two digital twins. + text: > + az dt twin relationship create -n {instance_or_hostname} --relationship-id {relationship_id} --relationship contains + --twin-id {source_twin_id} --target {target_twin_id} + --properties '{"ownershipUser": "me", "ownershipDepartment": "Computer Science"}' + """ + + helps["dt twin relationship show"] = """ + type: command + short-summary: Show details of a digital twin relationship. + + examples: + - name: Show details of a digital twin relationship. + text: > + az dt twin relationship show -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id} + """ + + helps["dt twin relationship list"] = """ + type: command + short-summary: List the relationships of a digital twin. + + examples: + - name: List outgoing relationships of a digital twin. + text: > + az dt twin relationship list -n {instance_or_hostname} --twin-id {twin_id} + + - name: List outgoing relationships of a digital twin and filter on relationship 'contains' + text: > + az dt twin relationship list -n {instance_or_hostname} --twin-id {twin_id} --relationship contains + + - name: List incoming relationships of a digital twin. + text: > + az dt twin relationship list -n {instance_or_hostname} --twin-id {twin_id} --incoming + + - name: List incoming relationships of a digital twin and filter on relationship 'contains'. + text: > + az dt twin relationship list -n {instance_or_hostname} --twin-id {twin_id} --relationship contains --incoming + """ + + helps["dt twin relationship update"] = """ + type: command + short-summary: Updates the properties of a relationship between two + digital twins via JSON patch specification. + long-summary: Operations are limited to add, replace and remove. + + examples: + - name: Update a digital twin relationship via JSON patch specification. + text: > + az dt twin relationship update -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id} + --relationship contains --json-patch '{"op":"replace", "path":"/Temperature", "value": 20.5}' + + - name: Update a digital twin relationship via JSON patch specification and using etag. + text: > + az dt twin relationship update -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id} + --relationship contains --json-patch '{"op":"replace", "path":"/Temperature", "value": 20.5}' --etag {etag} + + - name: Update a digital twin relationship via JSON patch specification. + text: > + az dt twin relationship update -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id} + --relationship contains --json-patch '[ + {"op":"replace", "path":"/Temperature", "value": 20.5}, + {"op":"add", "path":"/Areas", "value": ["ControlSystem"]} + ]' + + - name: Update a digital twin relationship via JSON patch specification defined in a file. + text: > + az dt twin relationship update -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id} + --relationship contains --json-patch ./my/patch/document.json + """ + + helps["dt twin relationship delete"] = """ + type: command + short-summary: Delete a digital twin relationship on a Digital Twins instance. + + examples: + - name: Delete a digital twin relationship. + text: > + az dt twin relationship delete -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id} + + - name: Delete a digital twin relationship using the etag. + text: > + az dt twin relationship delete -n {instance_or_hostname} --twin-id {twin_id} --relationship-id {relationship_id} --etag {etag} + """ + + helps["dt twin relationship delete-all"] = """ + type: command + short-summary: Deletes all digital twin relationships within a Digital Twins instance, including incoming relationships. + + examples: + - name: Delete all digital twin relationships associated with the twin. + text: > + az dt twin relationship delete-all -n {instance_or_hostname} --twin-id {twin_id} + + - name: Delete all digital twin relationships within the Digital Twins instace. + text: > + az dt twin relationship delete-all -n {instance_or_hostname} + """ + + helps["dt twin telemetry"] = """ + type: group + short-summary: Test and validate the event routes and endpoints of a Digital Twins instance. + """ + + helps["dt twin telemetry send"] = """ + type: command + short-summary: Sends telemetry on behalf of a digital twin. If component path is provided the + emitted telemetry is on behalf of the component. + + examples: + - name: Send twin telemetry + text: > + az dt twin telemetry send -n {instance_or_hostname} --twin-id {twin_id} + """ + + helps["dt twin component"] = """ + type: group + short-summary: Show and update the digital twin components of a Digital Twins instance. + """ + + helps["dt twin component show"] = """ + type: command + short-summary: Show details of a digital twin component. + + examples: + - name: Show details of a digital twin component + text: > + az dt twin component show -n {instance_or_hostname} --twin-id {twin_id} --component Thermostat + """ + + helps["dt twin component update"] = """ + type: command + short-summary: Update a digital twin component via JSON patch specification. + long-summary: Updates to property values and $model elements may happen + in the same request. Operations are limited to add, replace and remove. + + examples: + - name: Update a digital twin component via JSON patch specification. + text: > + az dt twin component update -n {instance_or_hostname} --twin-id {twin_id} --component {component_path} + --json-patch '{"op":"replace", "path":"/Temperature", "value": 20.5}' + + - name: Update a digital twin component via JSON patch specification. + text: > + az dt twin component update -n {instance_or_hostname} --twin-id {twin_id} --component {component_path} + --json-patch '[ + {"op":"replace", "path":"/Temperature", "value": 20.5}, + {"op":"add", "path":"/Areas", "value": ["ControlSystem"]} + ]' + + - name: Update a digital twin component via JSON patch specification defined in a file. + text: > + az dt twin component update -n {instance_or_hostname} --twin-id {twin_id} --component {component_path} + --json-patch ./my/patch/document.json + """ + + helps["dt model"] = """ + type: group + short-summary: Manage DTDL models and definitions on a Digital Twins instance. + """ + + helps["dt model create"] = """ + type: command + short-summary: Uploads one or more models. When any error occurs, no models are uploaded. + long-summary: --models can be inline json or file path. + + examples: + - name: Bulk upload all .json or .dtdl model files from a target directory. Model processing is recursive. + text: > + az dt model create -n {instance_or_hostname} --from-directory {directory_path} + + - name: Upload model json inline or from file path. + text: > + az dt model create -n {instance_or_hostname} --models {file_path_or_inline_json} + """ + + helps["dt model show"] = """ + type: command + short-summary: Retrieve a target model or model definition. + + examples: + - name: Show model meta data + text: > + az dt model show -n {instance_or_hostname} --dtmi "dtmi:com:example:Floor;1" + + - name: Show model meta data and definition + text: > + az dt model show -n {instance_or_hostname} --dtmi "dtmi:com:example:Floor;1" --definition + """ + + helps["dt model list"] = """ + type: command + short-summary: List model metadata, definitions and dependencies. + + examples: + - name: List model metadata + text: > + az dt model list -n {instance_or_hostname} + + - name: List model definitions + text: > + az dt model list -n {instance_or_hostname} --definition + + - name: List dependencies of particular pre-existing model(s). Space seperate dtmi values. + text: > + az dt model list -n {instance_or_hostname} --dependencies-for {model_id0} {model_id1} + """ + + helps["dt model update"] = """ + type: command + short-summary: Updates the metadata for a model. Currently a model can only be decommisioned. + + examples: + - name: Decommision a target model + text: > + az dt model update -n {instance_or_hostname} --dtmi "dtmi:com:example:Floor;1" --decommission + """ + + helps["dt model delete"] = """ + type: command + short-summary: Delete a model. A model can only be deleted if no other models reference it. + + examples: + - name: Delete a target model. + text: > + az dt model delete -n {instance_or_hostname} --dtmi "dtmi:com:example:Floor;1" + """ diff --git a/azext_iot/digitaltwins/command_map.py b/azext_iot/digitaltwins/command_map.py new file mode 100644 index 000000000..22924cb4b --- /dev/null +++ b/azext_iot/digitaltwins/command_map.py @@ -0,0 +1,169 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Load CLI commands +""" +from azure.cli.core.commands import CliCommandType + +digitaltwins_resource_ops = CliCommandType( + operations_tmpl="azext_iot.digitaltwins.commands_resource#{}" +) + +digitaltwins_route_ops = CliCommandType( + operations_tmpl="azext_iot.digitaltwins.commands_routes#{}" +) + +digitaltwins_model_ops = CliCommandType( + operations_tmpl="azext_iot.digitaltwins.commands_models#{}" +) + +digitaltwins_twin_ops = CliCommandType( + operations_tmpl="azext_iot.digitaltwins.commands_twins#{}" +) + +digitaltwins_rbac_ops = CliCommandType( + operations_tmpl="azext_iot.digitaltwins.commands_rbac#{}" +) + + +def load_digitaltwins_commands(self, _): + """ + Load CLI commands + """ + with self.command_group( + "dt", + command_type=digitaltwins_resource_ops, + ) as cmd_group: + cmd_group.command("create", "create_instance") + cmd_group.show_command("show", "show_instance") + cmd_group.command("list", "list_instances") + cmd_group.command("delete", "delete_instance", confirmation=True, supports_no_wait=True) + + with self.command_group( + "dt endpoint", command_type=digitaltwins_resource_ops + ) as cmd_group: + cmd_group.show_command( + "show", + "show_endpoint", + table_transformer=( + "{EndpointName:name, EndpointType:properties.endpointType," + "ProvisioningState:properties.provisioningState,CreatedTime:properties.createdTime}" + ), + ) + cmd_group.command( + "list", + "list_endpoints", + table_transformer=( + "[*].{EndpointName:name, EndpointType:properties.endpointType," + "ProvisioningState:properties.provisioningState,CreatedTime:properties.createdTime}" + ), + ) + cmd_group.command("delete", "delete_endpoint", confirmation=True, supports_no_wait=True) + + with self.command_group( + "dt endpoint create", command_type=digitaltwins_resource_ops + ) as cmd_group: + cmd_group.command("eventgrid", "add_endpoint_eventgrid") + cmd_group.command("servicebus", "add_endpoint_servicebus") + cmd_group.command("eventhub", "add_endpoint_eventhub") + + with self.command_group( + "dt route", command_type=digitaltwins_route_ops + ) as cmd_group: + cmd_group.show_command( + "show", + "show_route", + table_transformer="{RouteName:id,EndpointName:endpointName,Filter:filter}", + ) + cmd_group.command( + "list", + "list_routes", + table_transformer="[*].{RouteName:id,EndpointName:endpointName,Filter:filter}", + ) + cmd_group.command("delete", "delete_route") + cmd_group.command("create", "create_route") + + with self.command_group( + "dt role-assignment", command_type=digitaltwins_rbac_ops + ) as cmd_group: + cmd_group.command("create", "assign_role") + cmd_group.command("delete", "remove_role") + cmd_group.command("list", "list_assignments") + + with self.command_group("dt twin", command_type=digitaltwins_twin_ops) as cmd_group: + cmd_group.command("query", "query_twins") + cmd_group.command("create", "create_twin") + cmd_group.show_command("show", "show_twin") + cmd_group.command("update", "update_twin") + cmd_group.command("delete", "delete_twin") + cmd_group.command("delete-all", "delete_all_twin", confirmation=True) + + with self.command_group( + "dt twin component", command_type=digitaltwins_twin_ops + ) as cmd_group: + cmd_group.show_command("show", "show_component") + cmd_group.command("update", "update_component") + + with self.command_group( + "dt twin relationship", command_type=digitaltwins_twin_ops + ) as cmd_group: + cmd_group.command("create", "create_relationship") + cmd_group.show_command("show", "show_relationship") + cmd_group.command("list", "list_relationships") + cmd_group.command("update", "update_relationship") + cmd_group.command("delete", "delete_relationship") + cmd_group.command("delete-all", "delete_all_relationship", confirmation=True) + + with self.command_group( + "dt twin telemetry", command_type=digitaltwins_twin_ops + ) as cmd_group: + cmd_group.command("send", "send_telemetry") + + with self.command_group( + "dt model", command_type=digitaltwins_model_ops + ) as cmd_group: + cmd_group.command("create", "add_models") + cmd_group.show_command( + "show", + "show_model", + table_transformer="{ModelId:id,UploadTime:uploadTime,Decommissioned:decommissioned}", + ) + cmd_group.command( + "list", + "list_models", + table_transformer="[*].{ModelId:id,UploadTime:uploadTime,Decommissioned:decommissioned}", + ) + cmd_group.command("update", "update_model") + cmd_group.command("delete", "delete_model") + + with self.command_group( + "dt network", + command_type=digitaltwins_resource_ops, + ) as cmd_group: + pass + + with self.command_group( + "dt network private-link", + command_type=digitaltwins_resource_ops, + ) as cmd_group: + cmd_group.show_command("show", "show_private_link") + cmd_group.command("list", "list_private_links") + + with self.command_group( + "dt network private-endpoint", + command_type=digitaltwins_resource_ops, + ) as cmd_group: + pass + + with self.command_group( + "dt network private-endpoint connection", + command_type=digitaltwins_resource_ops, + ) as cmd_group: + cmd_group.command("set", "set_private_endpoint_conn") + cmd_group.show_command("show", "show_private_endpoint_conn") + cmd_group.command("list", "list_private_endpoint_conns") + cmd_group.command("delete", "delete_private_endpoint_conn", confirmation=True, supports_no_wait=True) diff --git a/azext_iot/digitaltwins/commands_models.py b/azext_iot/digitaltwins/commands_models.py new file mode 100644 index 000000000..bdf6782e0 --- /dev/null +++ b/azext_iot/digitaltwins/commands_models.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.digitaltwins.providers.model import ModelProvider +from knack.log import get_logger + +logger = get_logger(__name__) + + +def add_models(cmd, name_or_hostname, models=None, from_directory=None, resource_group_name=None): + model_provider = ModelProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + logger.debug("Received models input: %s", models) + return model_provider.add(models=models, from_directory=from_directory) + + +def show_model(cmd, name_or_hostname, model_id, definition=False, resource_group_name=None): + model_provider = ModelProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return model_provider.get(id=model_id, get_definition=definition) + + +def list_models( + cmd, name_or_hostname, definition=False, dependencies_for=None, resource_group_name=None +): + model_provider = ModelProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return model_provider.list( + get_definition=definition, dependencies_for=dependencies_for + ) + + +def update_model(cmd, name_or_hostname, model_id, decommission=None, resource_group_name=None): + if decommission is None: + logger.info("No update arguments provided. Nothing to update.") + return + + model_provider = ModelProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return model_provider.update(id=model_id, decommission=decommission,) + + +def delete_model(cmd, name_or_hostname, model_id, resource_group_name=None): + model_provider = ModelProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return model_provider.delete(id=model_id) diff --git a/azext_iot/digitaltwins/commands_rbac.py b/azext_iot/digitaltwins/commands_rbac.py new file mode 100644 index 000000000..bf08b4356 --- /dev/null +++ b/azext_iot/digitaltwins/commands_rbac.py @@ -0,0 +1,42 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.digitaltwins.providers.resource import ResourceProvider +from knack.log import get_logger + +logger = get_logger(__name__) + + +def assign_role(cmd, name, role_type, assignee, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.assign_role( + name=name, + role_type=role_type, + assignee=assignee, + resource_group_name=resource_group_name, + ) + + +def remove_role(cmd, name, assignee=None, role_type=None, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.remove_role( + name=name, + assignee=assignee, + role_type=role_type, + resource_group_name=resource_group_name, + ) + + +def list_assignments( + cmd, name, include_inherited=False, role_type=None, resource_group_name=None +): + rp = ResourceProvider(cmd) + return rp.get_role_assignments( + name=name, + include_inherited=include_inherited, + resource_group_name=resource_group_name, + role_type=role_type, + ) diff --git a/azext_iot/digitaltwins/commands_resource.py b/azext_iot/digitaltwins/commands_resource.py new file mode 100644 index 000000000..96d176bc0 --- /dev/null +++ b/azext_iot/digitaltwins/commands_resource.py @@ -0,0 +1,220 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.digitaltwins.providers.resource import ResourceProvider +from azext_iot.digitaltwins.common import ( + ADTEndpointType, + ADTEndpointAuthType, + ADTPublicNetworkAccessType, +) +from knack.log import get_logger + +logger = get_logger(__name__) + + +def create_instance( + cmd, + name, + resource_group_name, + location=None, + tags=None, + assign_identity=None, + scopes=None, + role_type="Contributor", + public_network_access=ADTPublicNetworkAccessType.enabled.value, +): + rp = ResourceProvider(cmd) + return rp.create( + name=name, + resource_group_name=resource_group_name, + location=location, + tags=tags, + assign_identity=assign_identity, + scopes=scopes, + role_type=role_type, + public_network_access=public_network_access, + ) + + +def list_instances(cmd, resource_group_name=None): + rp = ResourceProvider(cmd) + + if not resource_group_name: + return rp.list() + return rp.list_by_resouce_group(resource_group_name) + + +def show_instance(cmd, name, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.find_instance(name=name, resource_group_name=resource_group_name) + + +def delete_instance(cmd, name, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.delete(name=name, resource_group_name=resource_group_name) + + +def list_endpoints(cmd, name, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.list_endpoints(name=name, resource_group_name=resource_group_name) + + +def show_endpoint(cmd, name, endpoint_name, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.get_endpoint( + name=name, endpoint_name=endpoint_name, resource_group_name=resource_group_name + ) + + +def delete_endpoint(cmd, name, endpoint_name, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.delete_endpoint( + name=name, endpoint_name=endpoint_name, resource_group_name=resource_group_name + ) + + +def add_endpoint_eventgrid( + cmd, + name, + endpoint_name, + eventgrid_topic_name, + eventgrid_resource_group, + resource_group_name=None, + endpoint_subscription=None, + dead_letter_uri=None, + dead_letter_secret=None, + auth_type=ADTEndpointAuthType.keybased.value, +): + rp = ResourceProvider(cmd) + return rp.add_endpoint( + name=name, + resource_group_name=resource_group_name, + endpoint_name=endpoint_name, + endpoint_resource_type=ADTEndpointType.eventgridtopic.value, + endpoint_resource_name=eventgrid_topic_name, + endpoint_resource_group=eventgrid_resource_group, + endpoint_subscription=endpoint_subscription, + dead_letter_uri=dead_letter_uri, + dead_letter_secret=dead_letter_secret, + auth_type=auth_type, + ) + + +def add_endpoint_servicebus( + cmd, + name, + endpoint_name, + servicebus_topic_name, + servicebus_resource_group, + servicebus_namespace, + servicebus_policy=None, + resource_group_name=None, + endpoint_subscription=None, + dead_letter_uri=None, + dead_letter_secret=None, + auth_type=ADTEndpointAuthType.keybased.value, +): + rp = ResourceProvider(cmd) + return rp.add_endpoint( + name=name, + resource_group_name=resource_group_name, + endpoint_name=endpoint_name, + endpoint_resource_type=ADTEndpointType.servicebus.value, + endpoint_resource_name=servicebus_topic_name, + endpoint_resource_group=servicebus_resource_group, + endpoint_resource_namespace=servicebus_namespace, + endpoint_resource_policy=servicebus_policy, + endpoint_subscription=endpoint_subscription, + dead_letter_uri=dead_letter_uri, + dead_letter_secret=dead_letter_secret, + auth_type=auth_type, + ) + + +def add_endpoint_eventhub( + cmd, + name, + endpoint_name, + eventhub_name, + eventhub_resource_group, + eventhub_namespace, + eventhub_policy=None, + resource_group_name=None, + endpoint_subscription=None, + dead_letter_uri=None, + dead_letter_secret=None, + auth_type=ADTEndpointAuthType.keybased.value, +): + rp = ResourceProvider(cmd) + return rp.add_endpoint( + name=name, + resource_group_name=resource_group_name, + endpoint_name=endpoint_name, + endpoint_resource_type=ADTEndpointType.eventhub.value, + endpoint_resource_name=eventhub_name, + endpoint_resource_group=eventhub_resource_group, + endpoint_resource_namespace=eventhub_namespace, + endpoint_resource_policy=eventhub_policy, + endpoint_subscription=endpoint_subscription, + dead_letter_uri=dead_letter_uri, + dead_letter_secret=dead_letter_secret, + auth_type=auth_type, + ) + + +def show_private_link(cmd, name, link_name, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.get_private_link( + name=name, resource_group_name=resource_group_name, link_name=link_name + ) + + +def list_private_links(cmd, name, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.list_private_links(name=name, resource_group_name=resource_group_name) + + +def set_private_endpoint_conn( + cmd, + name, + conn_name, + status, + description=None, + group_ids=None, + actions_required=None, + resource_group_name=None, +): + rp = ResourceProvider(cmd) + return rp.set_private_endpoint_conn( + name=name, + resource_group_name=resource_group_name, + conn_name=conn_name, + status=status, + description=description, + group_ids=group_ids, + actions_required=actions_required, + ) + + +def show_private_endpoint_conn(cmd, name, conn_name, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.get_private_endpoint_conn( + name=name, resource_group_name=resource_group_name, conn_name=conn_name + ) + + +def list_private_endpoint_conns(cmd, name, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.list_private_endpoint_conns( + name=name, resource_group_name=resource_group_name + ) + + +def delete_private_endpoint_conn(cmd, name, conn_name, resource_group_name=None): + rp = ResourceProvider(cmd) + return rp.delete_private_endpoint_conn( + name=name, resource_group_name=resource_group_name, conn_name=conn_name + ) diff --git a/azext_iot/digitaltwins/commands_routes.py b/azext_iot/digitaltwins/commands_routes.py new file mode 100644 index 000000000..923ffd57d --- /dev/null +++ b/azext_iot/digitaltwins/commands_routes.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.digitaltwins.providers.route import RouteProvider +from knack.log import get_logger + +logger = get_logger(__name__) + + +def create_route( + cmd, name_or_hostname, route_name, endpoint_name, filter="true", resource_group_name=None +): + route_provider = RouteProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return route_provider.create( + route_name=route_name, endpoint_name=endpoint_name, filter=filter + ) + + +def show_route(cmd, name_or_hostname, route_name, resource_group_name=None): + route_provider = RouteProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return route_provider.get(route_name=route_name) + + +def list_routes(cmd, name_or_hostname, resource_group_name=None): + route_provider = RouteProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return route_provider.list() + + +def delete_route(cmd, name_or_hostname, route_name, resource_group_name=None): + route_provider = RouteProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return route_provider.delete(route_name=route_name) diff --git a/azext_iot/digitaltwins/commands_twins.py b/azext_iot/digitaltwins/commands_twins.py new file mode 100644 index 000000000..8eea79b4d --- /dev/null +++ b/azext_iot/digitaltwins/commands_twins.py @@ -0,0 +1,164 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.digitaltwins.providers.twin import TwinProvider +from knack.log import get_logger + +logger = get_logger(__name__) + + +def query_twins( + cmd, name_or_hostname, query_command, show_cost=False, resource_group_name=None +): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.invoke_query(query=query_command, show_cost=show_cost) + + +def create_twin( + cmd, + name_or_hostname, + twin_id, + model_id, + if_none_match=False, + properties=None, + resource_group_name=None +): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.create( + twin_id=twin_id, model_id=model_id, if_none_match=if_none_match, properties=properties + ) + + +def show_twin(cmd, name_or_hostname, twin_id, resource_group_name=None): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.get(twin_id) + + +def update_twin(cmd, name_or_hostname, twin_id, json_patch, resource_group_name=None, etag=None): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.update(twin_id=twin_id, json_patch=json_patch, etag=etag) + + +def delete_twin(cmd, name_or_hostname, twin_id, resource_group_name=None, etag=None): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.delete(twin_id=twin_id, etag=etag) + + +def delete_all_twin(cmd, name_or_hostname, resource_group_name=None): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.delete_all() + + +def create_relationship( + cmd, + name_or_hostname, + twin_id, + target_twin_id, + relationship_id, + relationship, + if_none_match=False, + properties=None, + resource_group_name=None, +): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.add_relationship( + twin_id=twin_id, + target_twin_id=target_twin_id, + relationship_id=relationship_id, + relationship=relationship, + if_none_match=if_none_match, + properties=properties, + ) + + +def show_relationship( + cmd, name_or_hostname, twin_id, relationship_id, resource_group_name=None, +): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.get_relationship( + twin_id=twin_id, relationship_id=relationship_id + ) + + +def update_relationship( + cmd, + name_or_hostname, + twin_id, + relationship_id, + json_patch, + resource_group_name=None, + etag=None +): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.update_relationship( + twin_id=twin_id, relationship_id=relationship_id, json_patch=json_patch, etag=etag + ) + + +def list_relationships( + cmd, + name_or_hostname, + twin_id, + incoming_relationships=False, + relationship=None, + resource_group_name=None, +): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.list_relationships( + twin_id=twin_id, + incoming_relationships=incoming_relationships, + relationship=relationship, + ) + + +def delete_relationship( + cmd, name_or_hostname, twin_id, relationship_id, resource_group_name=None, etag=None +): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.delete_relationship( + twin_id=twin_id, relationship_id=relationship_id, etag=etag + ) + + +def delete_all_relationship( + cmd, name_or_hostname, twin_id=None, resource_group_name=None +): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + if twin_id: + return twin_provider.delete_all_relationship(twin_id=twin_id) + else: + return twin_provider.delete_all(only_relationships=True) + + +def send_telemetry( + cmd, + name_or_hostname, + twin_id, + dt_id=None, + component_path=None, + telemetry=None, + resource_group_name=None, +): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.send_telemetry( + twin_id=twin_id, dt_id=dt_id, component_path=component_path, telemetry=telemetry + ) + + +def show_component( + cmd, name_or_hostname, twin_id, component_path, resource_group_name=None +): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.get_component(twin_id=twin_id, component_path=component_path) + + +def update_component( + cmd, name_or_hostname, twin_id, component_path, json_patch, resource_group_name=None, etag=None +): + twin_provider = TwinProvider(cmd=cmd, name=name_or_hostname, rg=resource_group_name) + return twin_provider.update_component( + twin_id=twin_id, component_path=component_path, json_patch=json_patch, etag=etag + ) diff --git a/azext_iot/digitaltwins/common.py b/azext_iot/digitaltwins/common.py new file mode 100644 index 000000000..b0a382a70 --- /dev/null +++ b/azext_iot/digitaltwins/common.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +shared: Define shared data types(enums) + +""" + +from enum import Enum + + +class ADTEndpointType(Enum): + """ + ADT endpoint type. + """ + + eventgridtopic = "eventgridtopic" + servicebus = "servicebus" + eventhub = "eventhub" + + +class ADTEndpointAuthType(Enum): + """ + ADT endpoint auth type. + """ + + identitybased = "IdentityBased" + keybased = "KeyBased" + + +class ADTPrivateConnectionStatusType(Enum): + """ + ADT private endpoint connection status type. + """ + + pending = "Pending" + approved = "Approved" + rejected = "Rejected" + disconnected = "Disconnected" + + +class ADTPublicNetworkAccessType(Enum): + """ + ADT private endpoint connection status type. + """ + + enabled = "Enabled" + disabled = "Disabled" diff --git a/azext_iot/digitaltwins/params.py b/azext_iot/digitaltwins/params.py new file mode 100644 index 000000000..5214425b5 --- /dev/null +++ b/azext_iot/digitaltwins/params.py @@ -0,0 +1,425 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +CLI parameter definitions. +""" + +from knack.arguments import CLIArgumentType +from azure.cli.core.commands.parameters import ( + resource_group_name_type, + get_three_state_flag, + get_enum_type, + tags_type, +) +from azext_iot.digitaltwins.common import ( + ADTEndpointAuthType, + ADTPrivateConnectionStatusType, + ADTPublicNetworkAccessType, +) + +depfor_type = CLIArgumentType( + options_list=["--dependencies-for"], + type=str, + nargs="+", + help="The set of models which will have their dependencies retrieved. " + "If omitted, all models are retrieved. Format is a whitespace separated list.", +) + + +def load_digitaltwins_arguments(self, _): + """ + Load CLI Args for Knack parser + """ + with self.argument_context("dt") as context: + context.argument( + "resource_group_name", + arg_type=resource_group_name_type, + help="Digital Twins instance resource group. " + "You can configure the default group using `az configure --defaults group=`.", + ) + context.argument( + "name", + options_list=["-n", "--dtn", "--dt-name"], + help="Digital Twins instance name.", + ) + context.argument( + "name_or_hostname", + options_list=["-n", "--dtn", "--dt-name"], + help="Digital Twins instance name or hostname. If an instance name is provided, the user subscription is " + "first queried for the target instance to retrieve the hostname. If a hostname is provided, the " + "subscription query is skipped and the provided value is used for subsequent interaction.", + ) + context.argument( + "location", + options_list=["--location", "-l"], + help="Digital Twins instance location. If no location is provided the resource group location is used." + "You can configure the default location using `az configure --defaults location=`.", + ), + context.argument( + "tags", + options_list=["--tags"], + arg_type=tags_type, + help="Digital Twins instance tags. Property bag in key-value pairs with the following format: a=b c=d", + ) + context.argument( + "endpoint_name", + options_list=["--endpoint-name", "--en"], + help="Endpoint name.", + ) + context.argument( + "route_name", + options_list=["--route-name", "--rn"], + help="Event route name.", + ) + context.argument( + "filter", + options_list=["--filter"], + help="Event route filter.", + ) + context.argument( + "role_type", + options_list=["--role"], + help="Role name or Id.", + ) + context.argument( + "assignee", + options_list=["--assignee"], + help="Represent a user, group, or service principal. supported format: " + "object id, user sign-in name, or service principal name.", + ) + context.argument( + "model_id", + options_list=["--model-id", "--dtmi", "-m"], + help="Digital Twins model Id. Example: dtmi:com:example:Room;2", + ) + context.argument( + "twin_id", + options_list=["--twin-id", "-t"], + help="The digital twin Id.", + ) + context.argument( + "include_inherited", + options_list=["--include-inherited"], + help="Include assignments applied on parent scopes.", + arg_type=get_three_state_flag(), + ) + context.argument( + "top", + type=int, + options_list=["--top"], + help="Maximum number of elements to return.", + ) + context.argument( + "public_network_access", + options_list=["--public-network-access", "--pna"], + help="Determines if the Digital Twins instance can be accessed from a public network.", + arg_group="Networking", + arg_type=get_enum_type(ADTPublicNetworkAccessType), + ) + + with self.argument_context("dt create") as context: + context.argument( + "assign_identity", + arg_group="Managed Service Identity", + help="Assign a system generated identity to the Digital Twins instance.", + arg_type=get_three_state_flag(), + ) + context.argument( + "scopes", + arg_group="Managed Service Identity", + nargs="+", + options_list=["--scopes"], + help="Space-seperated scopes the system assigned identity can access.", + ) + context.argument( + "role_type", + arg_group="Managed Service Identity", + options_list=["--role"], + help="Role name or Id the system assigned identity will have.", + ) + + with self.argument_context("dt endpoint create") as context: + context.argument( + "dead_letter_secret", + options_list=["--deadletter-sas-uri", "--dsu"], + help="Dead-letter storage container URL with SAS token for Key based authentication.", + arg_group="Dead-letter Endpoint", + ) + context.argument( + "dead_letter_uri", + options_list=["--deadletter-uri", "--du"], + help="Dead-letter storage container URL for Identity based authentication.", + arg_group="Dead-letter Endpoint", + ) + context.argument( + "auth_type", + options_list=["--auth-type"], + help="Endpoint authentication type.", + arg_type=get_enum_type(ADTEndpointAuthType), + ) + + with self.argument_context("dt endpoint create eventgrid") as context: + context.argument( + "eventgrid_topic_name", + options_list=["--eventgrid-topic", "--egt"], + help="Name of EventGrid Topic to integrate with.", + arg_group="Event Grid Topic", + ) + context.argument( + "eventgrid_resource_group", + options_list=["--eventgrid-resource-group", "--egg"], + help="Name of EventGrid Topic resource group.", + arg_group="Event Grid Topic", + ) + context.argument( + "endpoint_subscription", + options_list=["--eventgrid-subscription", "--egs"], + help="Name or ID of subscription where the endpoint resource exists. " + "If no subscription is provided the default subscription is used.", + arg_group="Event Grid Topic", + ) + + with self.argument_context("dt endpoint create eventhub") as context: + context.argument( + "eventhub_name", + options_list=["--eventhub", "--eh"], + help="Name of EventHub to integrate with.", + arg_group="Event Hub", + ) + context.argument( + "eventhub_policy", + options_list=["--eventhub-policy", "--ehp"], + help="EventHub policy to use for endpoint configuration. Required when --auth-type is KeyBased.", + arg_group="Event Hub", + ) + context.argument( + "eventhub_namespace", + options_list=["--eventhub-namespace", "--ehn"], + help="EventHub Namespace identifier.", + arg_group="Event Hub", + ) + context.argument( + "eventhub_resource_group", + options_list=["--eventhub-resource-group", "--ehg"], + help="Name of EventHub resource group.", + arg_group="Event Hub", + ) + context.argument( + "endpoint_subscription", + options_list=["--eventhub-subscription", "--ehs"], + help="Name or ID of subscription where the endpoint resource exists. " + "If no subscription is provided the default subscription is used.", + arg_group="Event Hub", + ) + + with self.argument_context("dt endpoint create servicebus") as context: + context.argument( + "servicebus_topic_name", + options_list=["--servicebus-topic", "--sbt"], + help="Name of ServiceBus Topic to integrate with.", + arg_group="Service Bus Topic", + ) + context.argument( + "servicebus_policy", + options_list=["--servicebus-policy", "--sbp"], + help="ServiceBus Topic policy to use for endpoint configuration. Required when --auth-type is KeyBased.", + arg_group="Service Bus Topic", + ) + context.argument( + "servicebus_namespace", + options_list=["--servicebus-namespace", "--sbn"], + help="ServiceBus Namespace identifier.", + arg_group="Service Bus Topic", + ) + context.argument( + "servicebus_resource_group", + options_list=["--servicebus-resource-group", "--sbg"], + help="Name of ServiceBus resource group.", + arg_group="Service Bus Topic", + ) + context.argument( + "endpoint_subscription", + options_list=["--servicebus-subscription", "--sbs"], + help="Name or ID of subscription where the endpoint resource exists. " + "If no subscription is provided the default subscription is used.", + arg_group="Service Bus Topic", + ) + + with self.argument_context("dt twin") as context: + context.argument( + "query_command", + options_list=["--query-command", "-q"], + help="User query to be executed.", + ) + context.argument( + "show_cost", + options_list=["--show-cost", "--cost"], + help="Calculates and shows the query charge.", + arg_type=get_three_state_flag(), + ) + context.argument( + "relationship_id", + options_list=["--relationship-id", "-r"], + help="Relationship Id.", + ) + context.argument( + "relationship", + options_list=["--relationship", "--kind"], + help="Relationship name or kind. For example: 'contains'", + ) + context.argument( + "json_patch", + options_list=["--json-patch", "--patch"], + help="An update specification described by JSON-patch. " + "Updates to property values and $model elements may happen in the same request. " + "Operations are limited to add, replace and remove. Provide file path or inline JSON.", + ) + context.argument( + "etag", options_list=["--etag", "-e"], help="Entity tag value. The command will succeed if " + "the etag matches the current etag for the resource." + ) + context.argument( + "component_path", + options_list=["--component"], + help="The path to the DTDL component.", + ) + context.argument( + "if_none_match", + options_list=["--if-none-match"], + help="Indicates the create operation should fail if an existing twin with the same id exists." + ) + + with self.argument_context("dt twin create") as context: + context.argument( + "properties", + options_list=["--properties", "-p"], + help="Initial property values for instantiating a digital twin or related components. " + "Provide file path or inline JSON. Properties are required for twins that contain components, " + "at the minimum you must provide an empty $metadata object for each component.", + ) + + with self.argument_context("dt twin telemetry") as context: + context.argument( + "telemetry", + options_list=["--telemetry"], + help="Inline telemetry JSON or file path to telemetry JSON. Default payload is an empty object: {}", + ) + context.argument( + "dt_id", + options_list=["--dt-id"], + help="A unique message identifier (in the scope of the digital twin id) that is commonly used " + "for de-duplicating messages. If no value is provided a GUID is automatically generated.", + ) + context.argument( + "component_path", + options_list=["--component"], + help="The path to the DTDL component. If set, telemetry will be emitted on behalf of the component.", + ) + + with self.argument_context("dt twin relationship") as context: + context.argument( + "twin_id", + options_list=["--twin-id", "-t", "--source"], + help="The source twin Id for a relationship.", + ) + context.argument( + "target_twin_id", + options_list=["--target-twin-id", "--target"], + help="The target twin Id for a relationship.", + ) + + with self.argument_context("dt twin relationship create") as context: + context.argument( + "properties", + options_list=["--properties", "-p"], + help="Initial property values for instantiating a digital twin relationship. Provide file path or inline JSON.", + ) + + with self.argument_context("dt twin relationship list") as context: + context.argument( + "incoming_relationships", + options_list=["--incoming"], + help="Retrieves all incoming relationships for a digital twin.", + arg_type=get_three_state_flag(), + ) + context.argument( + "relationship", + options_list=["--relationship", "--kind"], + help="Filter result by the kind of relationship.", + ) + + with self.argument_context("dt model") as context: + context.argument( + "from_directory", + options_list=["--from-directory", "--fd"], + help="The directory JSON model files will be parsed from.", + arg_group="Models Input", + ) + context.argument( + "models", + options_list=["--models"], + help="Inline model JSON or file path to model JSON.", + arg_group="Models Input", + ) + context.argument( + "definition", + options_list=["--definition", "--def"], + arg_type=get_three_state_flag(), + help="The operation will retrieve the model definition.", + ) + context.argument( + "decommission", + options_list=["--decommission"], + arg_type=get_three_state_flag(), + help="Indicates intent to decommission a target model.", + ) + context.argument( + "dependencies_for", + arg_type=depfor_type, + ) + + with self.argument_context("dt network private-link") as context: + context.argument( + "link_name", + options_list=["--link-name", "--ln"], + help="Private link name.", + arg_group="Private Connection", + ) + + with self.argument_context("dt network private-endpoint") as context: + context.argument( + "conn_name", + options_list=["--conn-name", "--cn"], + help="Private endpoint connection name.", + arg_group="Private-Endpoint", + ) + context.argument( + "group_ids", + options_list=["--group-ids"], + help="Space seperated list of group ids that the private endpoint should connect to.", + arg_group="Private-Endpoint", + nargs="+", + ) + context.argument( + "status", + options_list=["--status"], + help="The status of a private endpoint connection.", + arg_type=get_enum_type(ADTPrivateConnectionStatusType), + arg_group="Private-Endpoint", + ) + context.argument( + "description", + options_list=["--description", "--desc"], + help="Description for the private endpoint connection.", + arg_group="Private-Endpoint", + ) + context.argument( + "actions_required", + options_list=["--actions-required", "--ar"], + help="A message indicating if changes on the service provider require any updates on the consumer.", + arg_group="Private-Endpoint", + ) diff --git a/azext_iot/digitaltwins/providers/__init__.py b/azext_iot/digitaltwins/providers/__init__.py new file mode 100644 index 000000000..c8e0929b6 --- /dev/null +++ b/azext_iot/digitaltwins/providers/__init__.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.sdk.digitaltwins.controlplane import AzureDigitalTwinsManagementClient +from azext_iot.sdk.digitaltwins.controlplane.models import ErrorResponseException +from msrestazure.azure_exceptions import CloudError +from azext_iot.constants import USER_AGENT + +__all__ = [ + "digitaltwins_service_factory", + "DigitalTwinsResourceManager", + "CloudError", + "ErrorResponseException", +] + + +def digitaltwins_service_factory(cli_ctx, *_) -> AzureDigitalTwinsManagementClient: + """ + Factory for importing deps and getting service client resources. + + Args: + cli_ctx (knack.cli.CLI): CLI context. + *_ : all other args ignored. + + Returns: + AzureDigitalTwinsManagementClient: Top level client instance. + """ + from azure.cli.core.commands.client_factory import get_mgmt_service_client + + return get_mgmt_service_client(cli_ctx, AzureDigitalTwinsManagementClient) + + +class DigitalTwinsResourceManager(object): + def __init__(self, cmd): + assert cmd + self.cmd = cmd + + def get_mgmt_sdk(self): + client = digitaltwins_service_factory(self.cmd.cli_ctx) + client.config.add_user_agent(USER_AGENT) + return client diff --git a/azext_iot/digitaltwins/providers/auth.py b/azext_iot/digitaltwins/providers/auth.py new file mode 100644 index 000000000..bb4cc528d --- /dev/null +++ b/azext_iot/digitaltwins/providers/auth.py @@ -0,0 +1,62 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from msrest.authentication import Authentication + + +class DigitalTwinAuthentication(Authentication): + """ + Shared Access Signature authorization for Azure IoT Hub. + + """ + + def __init__(self, cmd, resource_id): + self.resource_id = resource_id + self.cmd = cmd + + def signed_session(self, session=None): + """ + Create requests session with SAS auth headers. + + If a session object is provided, configure it directly. Otherwise, + create a new session and return it. + + Returns: + session (): requests.Session. + """ + + return self.refresh_session(session) + + def refresh_session( + self, session=None, + ): + """ + Refresh requests session with SAS auth headers. + + If a session object is provided, configure it directly. Otherwise, + create a new session and return it. + + Returns: + session (): requests.Session. + """ + + session = session or super(DigitalTwinAuthentication, self).signed_session() + session.headers["Authorization"] = self.generate_token() + return session + + def generate_token(self): + from azure.cli.core._profile import Profile + + profile = Profile(cli_ctx=self.cmd.cli_ctx) + creds, subscription, tenant = profile.get_raw_token(resource=self.resource_id) + parsed_token = { + "tokenType": creds[0], + "accessToken": creds[1], + "expiresOn": creds[2].get("expiresOn", "N/A"), + "subscription": subscription, + "tenant": tenant, + } + return "{} {}".format(parsed_token["tokenType"], parsed_token["accessToken"]) diff --git a/azext_iot/digitaltwins/providers/base.py b/azext_iot/digitaltwins/providers/base.py new file mode 100644 index 000000000..4670b92cb --- /dev/null +++ b/azext_iot/digitaltwins/providers/base.py @@ -0,0 +1,63 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.digitaltwins.providers.resource import ResourceProvider +from azext_iot.sdk.digitaltwins.dataplane import AzureDigitalTwinsAPI +from azext_iot.sdk.digitaltwins.dataplane.models import ErrorResponseException +from azext_iot.constants import DIGITALTWINS_RESOURCE_ID, USER_AGENT +from azext_iot.common.utility import valid_hostname +from knack.cli import CLIError + +__all__ = ["DigitalTwinsProvider", "ErrorResponseException"] + + +class DigitalTwinsProvider(object): + def __init__(self, cmd, name, rg=None): + assert cmd + assert name + + self.cmd = cmd + self.name = name + self.rg = rg + self.resource_id = DIGITALTWINS_RESOURCE_ID + self.rp = ResourceProvider(self.cmd) + + def _get_endpoint(self): + host_name = None + https_prefix = "https://" + http_prefix = "http://" + + if self.name.lower().startswith(https_prefix): + self.name = self.name[len(https_prefix) :] + elif self.name.lower().startswith(http_prefix): + self.name = self.name[len(http_prefix) :] + + if not all([valid_hostname(self.name), "." in self.name]): + instance = self.rp.find_instance( + name=self.name, resource_group_name=self.rg + ) + host_name = instance.host_name + if not host_name: + raise CLIError("Instance has invalid hostName. Aborting operation...") + else: + host_name = self.name + + return "https://{}".format(host_name) + + def get_sdk(self): + from azure.cli.core.commands.client_factory import get_mgmt_service_client + + client = get_mgmt_service_client( + cli_ctx=self.cmd.cli_ctx, + client_or_resource_type=AzureDigitalTwinsAPI, + base_url=self._get_endpoint(), + resource=self.resource_id, + subscription_bound=False, + base_url_bound=False, + ) + + client.config.add_user_agent(USER_AGENT) + return client diff --git a/azext_iot/tests/iothub/device_identity/__init__.py b/azext_iot/digitaltwins/providers/endpoint/__init__.py similarity index 100% rename from azext_iot/tests/iothub/device_identity/__init__.py rename to azext_iot/digitaltwins/providers/endpoint/__init__.py diff --git a/azext_iot/digitaltwins/providers/endpoint/builders.py b/azext_iot/digitaltwins/providers/endpoint/builders.py new file mode 100644 index 000000000..73e05c581 --- /dev/null +++ b/azext_iot/digitaltwins/providers/endpoint/builders.py @@ -0,0 +1,330 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.common.embedded_cli import EmbeddedCLI +from azext_iot.digitaltwins.common import ADTEndpointAuthType +from abc import ABC, abstractmethod +from knack.util import CLIError +from knack.log import get_logger + +from azext_iot.sdk.digitaltwins.controlplane.models import ( + EventGrid as EventGridEndpointProperties, + EventHub as EventHubEndpointProperties, + ServiceBus as ServiceBusEndpointProperties, +) + +logger = get_logger(__name__) + + +class BaseEndpointBuilder(ABC): + def __init__( + self, + endpoint_resource_name: str, + endpoint_resource_group: str, + auth_type: str = ADTEndpointAuthType.keybased.value, + dead_letter_secret: str = None, + dead_letter_uri: str = None, + endpoint_subscription: str = None, + ): + self.cli = EmbeddedCLI() + self.error_prefix = "Could not create ADT instance endpoint. Unable to retrieve" + self.endpoint_resource_name = endpoint_resource_name + self.endpoint_resource_group = endpoint_resource_group + self.endpoint_subscription = endpoint_subscription + self.auth_type = auth_type + self.dead_letter_secret = dead_letter_secret + self.dead_letter_uri = dead_letter_uri + + def build_endpoint(self): + endpoint_properties = ( + self.build_key_based() + if self.auth_type == ADTEndpointAuthType.keybased.value + else self.build_identity_based() + ) + endpoint_properties.authentication_type = self.auth_type + return endpoint_properties + + @abstractmethod + def build_key_based(self): + pass + + @abstractmethod + def build_identity_based(self): + pass + + +class EventGridEndpointBuilder(BaseEndpointBuilder): + def __init__( + self, + endpoint_resource_name, + endpoint_resource_group, + auth_type=ADTEndpointAuthType.keybased.value, + dead_letter_secret=None, + dead_letter_uri=None, + endpoint_subscription=None, + ): + super().__init__( + endpoint_resource_name=endpoint_resource_name, + endpoint_resource_group=endpoint_resource_group, + auth_type=auth_type, + dead_letter_secret=dead_letter_secret, + dead_letter_uri=dead_letter_uri, + endpoint_subscription=endpoint_subscription, + ) + + def build_key_based(self): + eg_topic_keys_op = self.cli.invoke( + "eventgrid topic key list -n {} -g {}".format( + self.endpoint_resource_name, self.endpoint_resource_group + ), + subscription=self.endpoint_subscription, + ) + if not eg_topic_keys_op.success(): + raise CLIError("{} Event Grid topic keys.".format(self.error_prefix)) + eg_topic_keys = eg_topic_keys_op.as_json() + + eg_topic_endpoint_op = self.cli.invoke( + "eventgrid topic show -n {} -g {}".format( + self.endpoint_resource_name, self.endpoint_resource_group + ), + subscription=self.endpoint_subscription, + ) + if not eg_topic_endpoint_op.success(): + raise CLIError("{} Event Grid topic endpoint.".format(self.error_prefix)) + eg_topic_endpoint = eg_topic_endpoint_op.as_json() + + # TODO: Potentionally have shared attributes handled by build_endpoint() + return EventGridEndpointProperties( + access_key1=eg_topic_keys["key1"], + access_key2=eg_topic_keys["key2"], + dead_letter_secret=self.dead_letter_secret, + dead_letter_uri=self.dead_letter_uri, + topic_endpoint=eg_topic_endpoint["endpoint"], + ) + + def build_identity_based(self): + raise CLIError( + "Identity based EventGrid endpoint creation is not yet supported. " + ) + + +class ServiceBusEndpointBuilder(BaseEndpointBuilder): + def __init__( + self, + endpoint_resource_name, + endpoint_resource_group, + endpoint_resource_namespace, + endpoint_resource_policy, + auth_type=ADTEndpointAuthType.keybased.value, + dead_letter_secret=None, + dead_letter_uri=None, + endpoint_subscription=None, + ): + super().__init__( + endpoint_resource_name=endpoint_resource_name, + endpoint_resource_group=endpoint_resource_group, + auth_type=auth_type, + dead_letter_secret=dead_letter_secret, + dead_letter_uri=dead_letter_uri, + endpoint_subscription=endpoint_subscription, + ) + self.endpoint_resource_namespace = endpoint_resource_namespace + self.endpoint_resource_policy = endpoint_resource_policy + + def build_key_based(self): + sb_topic_keys_op = self.cli.invoke( + "servicebus topic authorization-rule keys list -n {} " + "--namespace-name {} -g {} --topic-name {}".format( + self.endpoint_resource_policy, + self.endpoint_resource_namespace, + self.endpoint_resource_group, + self.endpoint_resource_name, + ), + subscription=self.endpoint_subscription, + ) + if not sb_topic_keys_op.success(): + raise CLIError("{} Service Bus topic keys.".format(self.error_prefix)) + sb_topic_keys = sb_topic_keys_op.as_json() + + return ServiceBusEndpointProperties( + primary_connection_string=sb_topic_keys["primaryConnectionString"], + secondary_connection_string=sb_topic_keys["secondaryConnectionString"], + dead_letter_secret=self.dead_letter_secret, + dead_letter_uri=self.dead_letter_uri, + ) + + def build_identity_based(self): + sb_namespace_op = self.cli.invoke( + "servicebus namespace show --name {} -g {}".format( + self.endpoint_resource_namespace, + self.endpoint_resource_group, + ), + subscription=self.endpoint_subscription, + ) + if not sb_namespace_op.success(): + raise CLIError("{} Service Bus Namespace.".format(self.error_prefix)) + sb_namespace_meta = sb_namespace_op.as_json() + sb_endpoint = sb_namespace_meta["serviceBusEndpoint"] + + sb_topic_op = self.cli.invoke( + "servicebus topic show --name {} --namespace {} -g {}".format( + self.endpoint_resource_name, + self.endpoint_resource_namespace, + self.endpoint_resource_group, + ), + subscription=self.endpoint_subscription, + ) + + if not sb_topic_op.success(): + raise CLIError("{} Service Bus Topic.".format(self.error_prefix)) + + return ServiceBusEndpointProperties( + endpoint_uri=transform_sb_hostname_to_schemauri(sb_endpoint), + entity_path=self.endpoint_resource_name, + dead_letter_secret=self.dead_letter_secret, + dead_letter_uri=self.dead_letter_uri, + ) + + +class EventHubEndpointBuilder(BaseEndpointBuilder): + def __init__( + self, + endpoint_resource_name, + endpoint_resource_group, + endpoint_resource_namespace, + endpoint_resource_policy, + auth_type=ADTEndpointAuthType.keybased.value, + dead_letter_secret=None, + dead_letter_uri=None, + endpoint_subscription=None, + ): + super().__init__( + endpoint_resource_name=endpoint_resource_name, + endpoint_resource_group=endpoint_resource_group, + auth_type=auth_type, + dead_letter_secret=dead_letter_secret, + dead_letter_uri=dead_letter_uri, + endpoint_subscription=endpoint_subscription, + ) + self.endpoint_resource_namespace = endpoint_resource_namespace + self.endpoint_resource_policy = endpoint_resource_policy + + def build_key_based(self): + eventhub_topic_keys_op = self.cli.invoke( + "eventhubs eventhub authorization-rule keys list -n {} " + "--namespace-name {} -g {} --eventhub-name {}".format( + self.endpoint_resource_policy, + self.endpoint_resource_namespace, + self.endpoint_resource_group, + self.endpoint_resource_name, + ), + subscription=self.endpoint_subscription, + ) + if not eventhub_topic_keys_op.success(): + raise CLIError("{} Event Hub keys.".format(self.error_prefix)) + eventhub_topic_keys = eventhub_topic_keys_op.as_json() + + return EventHubEndpointProperties( + connection_string_primary_key=eventhub_topic_keys[ + "primaryConnectionString" + ], + connection_string_secondary_key=eventhub_topic_keys[ + "secondaryConnectionString" + ], + dead_letter_secret=self.dead_letter_secret, + dead_letter_uri=self.dead_letter_uri, + ) + + def build_identity_based(self): + sb_namespace_op = self.cli.invoke( + "eventhubs namespace show --name {} -g {}".format( + self.endpoint_resource_namespace, + self.endpoint_resource_group, + ), + subscription=self.endpoint_subscription, + ) + if not sb_namespace_op.success(): + raise CLIError("{} EventHub Namespace.".format(self.error_prefix)) + sb_namespace_meta = sb_namespace_op.as_json() + sb_endpoint = sb_namespace_meta["serviceBusEndpoint"] + + sb_topic_op = self.cli.invoke( + "eventhubs eventhub show --name {} --namespace {} -g {}".format( + self.endpoint_resource_name, + self.endpoint_resource_namespace, + self.endpoint_resource_group, + ), + subscription=self.endpoint_subscription, + ) + + if not sb_topic_op.success(): + raise CLIError("{} EventHub.".format(self.error_prefix)) + + return EventHubEndpointProperties( + endpoint_uri=transform_sb_hostname_to_schemauri(sb_endpoint), + entity_path=self.endpoint_resource_name, + dead_letter_secret=self.dead_letter_secret, + dead_letter_uri=self.dead_letter_uri, + ) + + +def transform_sb_hostname_to_schemauri(endpoint): + from urllib.parse import urlparse + + sb_endpoint_parts = urlparse(endpoint) + sb_hostname = sb_endpoint_parts.hostname + sb_schema_uri = "sb://{}/".format(sb_hostname) + return sb_schema_uri + + +def build_endpoint( + endpoint_resource_type: str, + endpoint_resource_name: str, + endpoint_resource_group: str, + auth_type: str = ADTEndpointAuthType.keybased.value, + endpoint_resource_namespace: str = None, + endpoint_resource_policy: str = None, + dead_letter_secret: str = None, + dead_letter_uri: str = None, + endpoint_subscription: str = None, +): + from azext_iot.digitaltwins.common import ADTEndpointType + + if endpoint_resource_type == ADTEndpointType.eventgridtopic.value: + return EventGridEndpointBuilder( + endpoint_resource_name=endpoint_resource_name, + endpoint_resource_group=endpoint_resource_group, + auth_type=auth_type, + dead_letter_secret=dead_letter_secret, + dead_letter_uri=dead_letter_uri, + endpoint_subscription=endpoint_subscription, + ).build_endpoint() + + if endpoint_resource_type == ADTEndpointType.servicebus.value: + return ServiceBusEndpointBuilder( + endpoint_resource_name=endpoint_resource_name, + endpoint_resource_group=endpoint_resource_group, + auth_type=auth_type, + dead_letter_secret=dead_letter_secret, + dead_letter_uri=dead_letter_uri, + endpoint_subscription=endpoint_subscription, + endpoint_resource_namespace=endpoint_resource_namespace, + endpoint_resource_policy=endpoint_resource_policy, + ).build_endpoint() + + if endpoint_resource_type == ADTEndpointType.eventhub.value: + return EventHubEndpointBuilder( + endpoint_resource_name=endpoint_resource_name, + endpoint_resource_group=endpoint_resource_group, + auth_type=auth_type, + dead_letter_secret=dead_letter_secret, + dead_letter_uri=dead_letter_uri, + endpoint_subscription=endpoint_subscription, + endpoint_resource_namespace=endpoint_resource_namespace, + endpoint_resource_policy=endpoint_resource_policy, + ).build_endpoint() + + raise ValueError("{} not supported.".format(endpoint_resource_type)) diff --git a/azext_iot/digitaltwins/providers/generic.py b/azext_iot/digitaltwins/providers/generic.py new file mode 100644 index 000000000..7147e32ed --- /dev/null +++ b/azext_iot/digitaltwins/providers/generic.py @@ -0,0 +1,49 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +# Experimental - depends on consistency of APIs +def accumulate_result( + method, + token_name="continuationToken", + token_arg_name="continuation_token", + values_name="items", + **kwargs +): + result_accumulator = [] + + nextlink = None + token_keyword = {token_arg_name: nextlink} + + # TODO: Genericize + query_cost_sum = 0 + + while True: + response = method(raw=True, **token_keyword, **kwargs).response + headers = response.headers + if headers: + query_charge = headers.get("query-charge") + if query_charge: + query_cost_sum = query_cost_sum + float(query_charge) + + result = response.json() + if result and result.get(values_name): + result_values = result.get(values_name) + result_accumulator.extend(result_values) + nextlink = result.get(token_name) + if not nextlink: + break + token_keyword[token_arg_name] = nextlink + else: + break + + return result_accumulator, query_cost_sum + + +def remove_prefix(text, prefix): + if text.startswith(prefix): + return text[len(prefix) :] + return text diff --git a/azext_iot/digitaltwins/providers/model.py b/azext_iot/digitaltwins/providers/model.py new file mode 100644 index 000000000..2adbb6fc3 --- /dev/null +++ b/azext_iot/digitaltwins/providers/model.py @@ -0,0 +1,110 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +from azext_iot.common.utility import process_json_arg, scantree, unpack_msrest_error +from azext_iot.digitaltwins.providers.base import DigitalTwinsProvider +from azext_iot.digitaltwins.providers import ErrorResponseException +from knack.log import get_logger +from knack.util import CLIError + +logger = get_logger(__name__) + + +class ModelProvider(DigitalTwinsProvider): + def __init__(self, cmd, name, rg=None): + super(ModelProvider, self).__init__( + cmd=cmd, name=name, rg=rg, + ) + self.model_sdk = self.get_sdk().digital_twin_models + + def add(self, models=None, from_directory=None): + if not any([models, from_directory]): + raise CLIError("Provide either --models or --from-directory.") + + # If both arguments are provided. --models wins. + payload = [] + if models: + models_result = process_json_arg(content=models, argument_name="models") + + # TODO: + if isinstance(models_result, list): + payload.extend(models_result) + elif isinstance(models_result, dict): + payload.append(models_result) + + elif from_directory: + payload = self._process_directory(from_directory=from_directory) + + logger.info("Models payload %s", json.dumps(payload)) + + # TODO: Not standard - have to revisit. + response = self.model_sdk.add(payload) + if response.status_code not in [200, 201]: + error_text = response.text + if response.status_code == 403 and not error_text: + error_text = "Current principal access is forbidden. Please validate rbac role assignments." + else: + try: + error_text = response.json() + except Exception: + pass + raise CLIError(error_text) + + return response.json() + + def _process_directory(self, from_directory): + logger.debug( + "Documents contained in directory: {}, processing...".format(from_directory) + ) + payload = [] + for entry in scantree(from_directory): + if all( + [not entry.name.endswith(".json"), not entry.name.endswith(".dtdl")] + ): + logger.debug( + "Skipping {} - model file must end with .json or .dtdl".format( + entry.path + ) + ) + continue + entry_json = process_json_arg(content=entry.path, argument_name=entry.name) + payload.append(entry_json) + + return payload + + def get(self, id, get_definition=False): + return self.model_sdk.get_by_id( + id=id, include_model_definition=get_definition, raw=True + ).response.json() + + def list( + self, get_definition=False, dependencies_for=None, top=None + ): # top is guarded for int() in arg def + from azext_iot.sdk.digitaltwins.dataplane.models import DigitalTwinModelsListOptions + + list_options = DigitalTwinModelsListOptions(max_item_count=top) + + return self.model_sdk.list( + dependencies_for=dependencies_for, + include_model_definition=get_definition, + digital_twin_models_list_options=list_options, + ) + + def update(self, id, decommission: bool): + patched_model = [ + {"op": "replace", "path": "/decommissioned", "value": decommission} + ] + + # Does not return model object upon updating + self.model_sdk.update(id=id, update_model=patched_model) + return self.get(id=id) + + def delete(self, id: str): + try: + return self.model_sdk.delete(id=id) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) diff --git a/azext_iot/digitaltwins/providers/rbac.py b/azext_iot/digitaltwins/providers/rbac.py new file mode 100644 index 000000000..a449a54f5 --- /dev/null +++ b/azext_iot/digitaltwins/providers/rbac.py @@ -0,0 +1,70 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.common.embedded_cli import EmbeddedCLI +from knack.util import CLIError + + +class RbacProvider(object): + def __init__(self): + self.cli = EmbeddedCLI() + + def list_assignments(self, dt_scope, include_inherited=False, role_type=None): + include_inherited_flag = "" + filter_role_type = "" + + if include_inherited: + include_inherited_flag = "--include-inherited" + + if role_type: + filter_role_type = "--role '{}'".format(role_type) + + list_op = self.cli.invoke( + "role assignment list --scope '{}' {} {}".format( + dt_scope, filter_role_type, include_inherited_flag + ) + ) + + if not list_op.success(): + raise CLIError("Unable to determine assignments.") + + return list_op.as_json() + + def assign_role(self, dt_scope, assignee, role_type): + assign_op = self.cli.invoke( + "role assignment create --scope '{}' --role '{}' --assignee '{}'".format( + dt_scope, role_type, assignee + ) + ) + if not assign_op.success(): + raise CLIError("Unable to assign role.") + + return assign_op.as_json() + + def remove_role(self, dt_scope, assignee, role_type=None): + filter_role_type = "" + if role_type: + filter_role_type = "--role '{}'".format(role_type) + + delete_op = self.cli.invoke( + "role assignment delete --scope '{}' --assignee '{}' {}".format( + dt_scope, assignee, filter_role_type + ) + ) + if not delete_op.success(): + raise CLIError("Unable to remove role assignment.") + return + + def assign_role_flex(self, principal_id, scope, principal_type="ServicePrincipal", role_type="Contributor"): + assign_op = self.cli.invoke( + "role assignment create --scope '{}' --role '{}' --assignee-object-id '{}' --assignee-principal-type '{}' ".format( + scope, role_type, principal_id, principal_type + ) + ) + if not assign_op.success(): + raise CLIError("Unable to assign role.") + + return assign_op.as_json() diff --git a/azext_iot/digitaltwins/providers/resource.py b/azext_iot/digitaltwins/providers/resource.py new file mode 100644 index 000000000..f643764e7 --- /dev/null +++ b/azext_iot/digitaltwins/providers/resource.py @@ -0,0 +1,467 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from azext_iot.digitaltwins.common import ( + ADTEndpointAuthType, + ADTPublicNetworkAccessType, +) +from azext_iot.digitaltwins.providers import ( + DigitalTwinsResourceManager, + CloudError, + ErrorResponseException, +) +from azext_iot.digitaltwins.providers.rbac import RbacProvider +from azext_iot.sdk.digitaltwins.controlplane.models import ( + DigitalTwinsDescription, +) +from azext_iot.common.utility import unpack_msrest_error +from knack.util import CLIError +from knack.log import get_logger + +logger = get_logger(__name__) + + +class ResourceProvider(DigitalTwinsResourceManager): + def __init__(self, cmd): + super(ResourceProvider, self).__init__(cmd=cmd) + self.mgmt_sdk = self.get_mgmt_sdk() + self.rbac = RbacProvider() + + def create( + self, + name, + resource_group_name, + location=None, + tags=None, + timeout=60, + assign_identity=None, + scopes=None, + role_type="Contributor", + public_network_access=ADTPublicNetworkAccessType.enabled.value, + ): + if not location: + from azext_iot.common.embedded_cli import EmbeddedCLI + + resource_group_meta = ( + EmbeddedCLI() + .invoke("group show --name {}".format(resource_group_name)) + .as_json() + ) + location = resource_group_meta["location"] + + try: + if assign_identity: + if scopes and not role_type: + raise CLIError( + "Both --scopes and --role values are required when assigning the instance identity." + ) + + digital_twins_create = DigitalTwinsDescription( + location=location, + tags=tags, + identity={"type": "SystemAssigned" if assign_identity else "None"}, + public_network_access=public_network_access, + ) + create_or_update = self.mgmt_sdk.digital_twins.create_or_update( + resource_name=name, + resource_group_name=resource_group_name, + digital_twins_create=digital_twins_create, + long_running_operation_timeout=timeout, + ) + + def rbac_handler(lro): + instance = lro.resource().as_dict() + identity = instance.get("identity") + if identity: + identity_type = identity.get("type") + principal_id = identity.get("principal_id") + + if ( + principal_id + and scopes + and identity_type + and identity_type.lower() == "systemassigned" + ): + for scope in scopes: + logger.info( + "Applying rbac assignment: Principal Id: {}, Scope: {}, Role: {}".format( + principal_id, scope, role_type + ) + ) + self.rbac.assign_role_flex( + principal_id=principal_id, + scope=scope, + role_type=role_type, + ) + + create_or_update.add_done_callback(rbac_handler) + return create_or_update + except CloudError as e: + raise e + except ErrorResponseException as err: + raise CLIError(unpack_msrest_error(err)) + + def list(self): + try: + return self.mgmt_sdk.digital_twins.list() + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def list_by_resouce_group(self, resource_group_name): + try: + return self.mgmt_sdk.digital_twins.list_by_resource_group( + resource_group_name=resource_group_name + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def get(self, name, resource_group_name): + try: + return self.mgmt_sdk.digital_twins.get( + resource_name=name, resource_group_name=resource_group_name + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def find_instance(self, name, resource_group_name=None): + if resource_group_name: + try: + return self.get(name=name, resource_group_name=resource_group_name) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + dt_collection_pager = self.list() + dt_collection = [] + try: + while True: + dt_collection.extend(dt_collection_pager.advance_page()) + except StopIteration: + pass + + compare_name = name.lower() + filter_result = [ + instance + for instance in dt_collection + if instance.name.lower() == compare_name + ] + + if filter_result: + if len(filter_result) > 1: + raise CLIError( + "Ambiguous DT instance name. Please include the DT instance resource group." + ) + return filter_result[0] + + raise CLIError( + "DT instance: '{}' not found by auto-discovery. " + "Provide resource group via -g for direct lookup.".format(name) + ) + + def get_rg(self, dt_instance): + dt_scope = dt_instance.id + split_decomp = dt_scope.split("/") + res_g = split_decomp[4] + return res_g + + def delete(self, name, resource_group_name=None): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + try: + return self.mgmt_sdk.digital_twins.delete( + resource_name=name, + resource_group_name=resource_group_name, + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + # RBAC + + def get_role_assignments( + self, name, include_inherited=False, role_type=None, resource_group_name=None + ): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + return self.rbac.list_assignments( + dt_scope=target_instance.id, + include_inherited=include_inherited, + role_type=role_type, + ) + + def assign_role(self, name, role_type, assignee, resource_group_name=None): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + return self.rbac.assign_role( + dt_scope=target_instance.id, assignee=assignee, role_type=role_type + ) + + def remove_role(self, name, assignee, role_type=None, resource_group_name=None): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + return self.rbac.remove_role( + dt_scope=target_instance.id, assignee=assignee, role_type=role_type + ) + + # Endpoints + + def get_endpoint(self, name, endpoint_name, resource_group_name=None): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + try: + return self.mgmt_sdk.digital_twins_endpoint.get( + resource_name=target_instance.name, + endpoint_name=endpoint_name, + resource_group_name=resource_group_name, + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def list_endpoints(self, name, resource_group_name=None): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + try: + return self.mgmt_sdk.digital_twins_endpoint.list( + resource_name=target_instance.name, + resource_group_name=resource_group_name, + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + # TODO: Polling issue related to mismatched status codes. + def delete_endpoint(self, name, endpoint_name, resource_group_name=None): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + try: + return self.mgmt_sdk.digital_twins_endpoint.delete( + resource_name=target_instance.name, + endpoint_name=endpoint_name, + resource_group_name=resource_group_name, + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def add_endpoint( + self, + name, + endpoint_name, + endpoint_resource_type, + endpoint_resource_name, + endpoint_resource_group, + endpoint_resource_policy=None, + endpoint_resource_namespace=None, + endpoint_subscription=None, + dead_letter_uri=None, + dead_letter_secret=None, + resource_group_name=None, + timeout=20, + auth_type=None, + ): + from azext_iot.digitaltwins.common import ADTEndpointType + + requires_namespace = [ + ADTEndpointType.eventhub.value, + ADTEndpointType.servicebus.value, + ] + if endpoint_resource_type in requires_namespace: + if not endpoint_resource_namespace: + raise CLIError( + "Endpoint resources of type {} require a namespace.".format( + " or ".join(map(str, requires_namespace)) + ) + ) + + if ( + auth_type == ADTEndpointAuthType.keybased.value + and not endpoint_resource_policy + ): + raise CLIError( + "Endpoint resources of type {} require a policy name when using Key based integration.".format( + " or ".join(map(str, requires_namespace)) + ) + ) + + if dead_letter_uri and auth_type == ADTEndpointAuthType.keybased.value: + raise CLIError( + "Use --deadletter-sas-uri to support deadletter for a Key based endpoint." + ) + + if dead_letter_secret and auth_type == ADTEndpointAuthType.identitybased.value: + raise CLIError( + "Use --deadletter-uri to support deadletter for an Identity based endpoint." + ) + + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + from azext_iot.digitaltwins.providers.endpoint.builders import build_endpoint + + properties = build_endpoint( + endpoint_resource_type=endpoint_resource_type, + endpoint_resource_name=endpoint_resource_name, + endpoint_resource_group=endpoint_resource_group, + endpoint_subscription=endpoint_subscription, + endpoint_resource_namespace=endpoint_resource_namespace, + endpoint_resource_policy=endpoint_resource_policy, + auth_type=auth_type, + dead_letter_secret=dead_letter_secret, + dead_letter_uri=dead_letter_uri, + ) + + try: + return self.mgmt_sdk.digital_twins_endpoint.create_or_update( + resource_name=target_instance.name, + resource_group_name=resource_group_name, + endpoint_name=endpoint_name, + properties=properties, + long_running_operation_timeout=timeout, + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def get_private_link(self, name, link_name, resource_group_name=None): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + try: + return self.mgmt_sdk.private_link_resources.get( + resource_group_name=resource_group_name, + resource_name=name, + resource_id=link_name, + raw=True, + ).response.json() + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def list_private_links(self, name, resource_group_name=None): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + try: + # This resource is not paged though it may have been the intent. + link_collection = self.mgmt_sdk.private_link_resources.list( + resource_group_name=resource_group_name, resource_name=name, raw=True + ).response.json() + return link_collection.get("value", []) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def set_private_endpoint_conn( + self, + name, + conn_name, + status, + description, + actions_required=None, + group_ids=None, + resource_group_name=None, + ): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + try: + return self.mgmt_sdk.private_endpoint_connections.create_or_update( + resource_group_name=resource_group_name, + resource_name=name, + private_endpoint_connection_name=conn_name, + properties={ + "privateLinkServiceConnectionState": { + "status": status, + "description": description, + "actions_required": actions_required, + }, + "groupIds": group_ids, + }, + ) + + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def get_private_endpoint_conn(self, name, conn_name, resource_group_name=None): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + try: + return self.mgmt_sdk.private_endpoint_connections.get( + resource_group_name=resource_group_name, + resource_name=name, + private_endpoint_connection_name=conn_name, + raw=True, + ).response.json() + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def list_private_endpoint_conns(self, name, resource_group_name=None): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + try: + # This resource is not paged though it may have been the intent. + endpoint_collection = self.mgmt_sdk.private_endpoint_connections.list( + resource_group_name=resource_group_name, resource_name=name, raw=True + ).response.json() + return endpoint_collection.get("value", []) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def delete_private_endpoint_conn(self, name, conn_name, resource_group_name=None): + target_instance = self.find_instance( + name=name, resource_group_name=resource_group_name + ) + if not resource_group_name: + resource_group_name = self.get_rg(target_instance) + + try: + return self.mgmt_sdk.private_endpoint_connections.delete( + resource_group_name=resource_group_name, + resource_name=name, + private_endpoint_connection_name=conn_name + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) diff --git a/azext_iot/digitaltwins/providers/route.py b/azext_iot/digitaltwins/providers/route.py new file mode 100644 index 000000000..131c98e37 --- /dev/null +++ b/azext_iot/digitaltwins/providers/route.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.common.utility import unpack_msrest_error +from azext_iot.digitaltwins.providers.base import DigitalTwinsProvider +from azext_iot.digitaltwins.providers import ErrorResponseException +from knack.log import get_logger +from knack.util import CLIError + +logger = get_logger(__name__) + + +class RouteProvider(DigitalTwinsProvider): + def __init__(self, cmd, name, rg=None): + super(RouteProvider, self).__init__(cmd=cmd, name=name, rg=rg) + self.sdk = self.get_sdk().event_routes + + def get(self, route_name): + try: + return self.sdk.get_by_id(route_name) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def list(self, top=None): # top is guarded for int() in arg def + from azext_iot.sdk.digitaltwins.dataplane.models import EventRoutesListOptions + + list_options = EventRoutesListOptions(max_item_count=top) + + try: + return self.sdk.list(event_routes_list_options=list_options,) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def create(self, route_name, endpoint_name, filter=None): + if not filter: + filter = "true" + + # TODO: Adding routes does not return an object + try: + self.sdk.add(id=route_name, endpoint_name=endpoint_name, filter=filter) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + return self.get(route_name=route_name) + + def delete(self, route_name): + try: + return self.sdk.delete(id=route_name) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) diff --git a/azext_iot/digitaltwins/providers/twin.py b/azext_iot/digitaltwins/providers/twin.py new file mode 100644 index 000000000..e465a355d --- /dev/null +++ b/azext_iot/digitaltwins/providers/twin.py @@ -0,0 +1,335 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +from azext_iot.digitaltwins.providers.base import ( + DigitalTwinsProvider, + ErrorResponseException, +) +from azext_iot.digitaltwins.providers.model import ModelProvider +from azext_iot.common.utility import process_json_arg, unpack_msrest_error +from knack.log import get_logger +from knack.util import CLIError + +logger = get_logger(__name__) + + +class TwinOptions(): + def __init__(self, if_match=None, if_none_match=None): + self.if_match = if_match + self.if_none_match = if_none_match + self.traceparent = None + self.tracestate = None + + +class TwinProvider(DigitalTwinsProvider): + def __init__(self, cmd, name, rg=None): + super(TwinProvider, self).__init__( + cmd=cmd, + name=name, + rg=rg, + ) + self.model_provider = ModelProvider(cmd=cmd, name=name, rg=rg) + self.query_sdk = self.get_sdk().query + self.twins_sdk = self.get_sdk().digital_twins + + def invoke_query(self, query, show_cost): + from azext_iot.digitaltwins.providers.generic import accumulate_result + + try: + accumulated_result, cost = accumulate_result( + self.query_sdk.query_twins, + values_name="value", + token_name="continuationToken", + token_arg_name="continuation_token", + query=query, + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + query_result = {} + query_result["result"] = accumulated_result + if show_cost: + query_result["cost"] = cost + + return query_result + + def create(self, twin_id, model_id, if_none_match=False, properties=None): + twin_request = { + "$dtId": twin_id, + "$metadata": {"$model": model_id}, + } + + if properties: + properties = process_json_arg( + content=properties, argument_name="properties" + ) + twin_request.update(properties) + + logger.info("Twin payload %s", json.dumps(twin_request)) + + try: + options = TwinOptions(if_none_match=("*" if if_none_match else None)) + return self.twins_sdk.add(id=twin_id, twin=twin_request, digital_twins_add_options=options) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def get(self, twin_id): + try: + return self.twins_sdk.get_by_id(id=twin_id, raw=True).response.json() + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def update(self, twin_id, json_patch, etag=None): + json_patch = process_json_arg(content=json_patch, argument_name="json-patch") + + json_patch_collection = [] + if isinstance(json_patch, dict): + json_patch_collection.append(json_patch) + elif isinstance(json_patch, list): + json_patch_collection.extend(json_patch) + else: + raise CLIError(f"--json-patch content must be an object or array. Actual type was: {type(json_patch).__name__}") + + logger.info("Patch payload %s", json.dumps(json_patch_collection)) + + try: + options = TwinOptions(if_match=(etag if etag else "*")) + self.twins_sdk.update( + id=twin_id, patch_document=json_patch_collection, digital_twins_update_options=options, raw=True + ) + return self.get(twin_id=twin_id) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def delete(self, twin_id, etag=None): + try: + options = TwinOptions(if_match=(etag if etag else "*")) + self.twins_sdk.delete(id=twin_id, digital_twins_delete_options=options, raw=True) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def delete_all(self, only_relationships=False): + # need to get all twins + query = "select * from digitaltwins" + twins = self.invoke_query(query=query, show_cost=False)["result"] + print(f"Found {len(twins)} twin(s).") + + # go through and delete all + for twin in twins: + try: + self.delete_all_relationship( + twin_id=twin["$dtId"] + ) + if not only_relationships: + self.delete(twin_id=twin["$dtId"]) + except CLIError as e: + logger.warn(f"Could not delete twin {twin['$dtId']}. The error is {e}") + + def add_relationship( + self, + twin_id, + target_twin_id, + relationship_id, + relationship, + if_none_match=False, + properties=None, + ): + relationship_request = { + "$targetId": target_twin_id, + "$relationshipName": relationship, + } + + if properties: + properties = process_json_arg( + content=properties, argument_name="properties" + ) + relationship_request.update(properties) + + logger.info("Relationship payload %s", json.dumps(relationship_request)) + try: + options = TwinOptions(if_none_match=("*" if if_none_match else None)) + return self.twins_sdk.add_relationship( + id=twin_id, + relationship_id=relationship_id, + relationship=relationship_request, + digital_twins_add_relationship_options=options, + raw=True, + ).response.json() + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def get_relationship(self, twin_id, relationship_id): + try: + return self.twins_sdk.get_relationship_by_id( + id=twin_id, relationship_id=relationship_id, raw=True + ).response.json() + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def list_relationships( + self, twin_id, incoming_relationships=False, relationship=None + ): + if not incoming_relationships: + return self.twins_sdk.list_relationships( + id=twin_id, relationship_name=relationship + ) + + incoming_pager = self.twins_sdk.list_incoming_relationships(id=twin_id) + + incoming_result = [] + try: + while True: + incoming_result.extend(incoming_pager.advance_page()) + except StopIteration: + pass + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + if relationship: + incoming_result = [ + edge + for edge in incoming_result + if edge.relationship_name and edge.relationship_name == relationship + ] + + return incoming_result + + def update_relationship(self, twin_id, relationship_id, json_patch, etag=None): + json_patch = process_json_arg(content=json_patch, argument_name="json-patch") + + json_patch_collection = [] + if isinstance(json_patch, dict): + json_patch_collection.append(json_patch) + elif isinstance(json_patch, list): + json_patch_collection.extend(json_patch) + else: + raise CLIError(f"--json-patch content must be an object or array. Actual type was: {type(json_patch).__name__}") + + logger.info("Patch payload %s", json.dumps(json_patch_collection)) + + try: + options = TwinOptions(if_match=(etag if etag else "*")) + self.twins_sdk.update_relationship( + id=twin_id, + relationship_id=relationship_id, + patch_document=json_patch_collection, + digital_twins_update_relationship_options=options, + ) + return self.get_relationship( + twin_id=twin_id, relationship_id=relationship_id + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def delete_relationship(self, twin_id, relationship_id, etag=None): + try: + options = TwinOptions(if_match=(etag if etag else "*")) + self.twins_sdk.delete_relationship( + id=twin_id, relationship_id=relationship_id, digital_twins_delete_relationship_options=options + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def delete_all_relationship(self, twin_id): + relationships = self.list_relationships(twin_id, incoming_relationships=True) + incoming_pager = self.list_relationships(twin_id) + + # relationships pager needs to be advanced to get relationships + try: + while True: + relationships.extend(incoming_pager.advance_page()) + except StopIteration: + pass + + print(f"Found {len(relationships)} relationship(s) associated with twin {twin_id}.") + + for relationship in relationships: + try: + if isinstance(relationship, dict): + self.delete_relationship( + twin_id=twin_id, + relationship_id=relationship['$relationshipId'] + ) + else: + self.delete_relationship( + twin_id=relationship.source_id, + relationship_id=relationship.relationship_id + ) + except CLIError as e: + logger.warn(f"Could not delete relationship {relationship}. The error is {e}.") + + def get_component(self, twin_id, component_path): + try: + return self.twins_sdk.get_component( + id=twin_id, component_path=component_path, raw=True + ).response.json() + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def update_component(self, twin_id, component_path, json_patch, etag=None): + json_patch = process_json_arg(content=json_patch, argument_name="json-patch") + + json_patch_collection = [] + if isinstance(json_patch, dict): + json_patch_collection.append(json_patch) + elif isinstance(json_patch, list): + json_patch_collection.extend(json_patch) + else: + raise CLIError(f"--json-patch content must be an object or array. Actual type was: {type(json_patch).__name__}") + + logger.info("Patch payload %s", json.dumps(json_patch_collection)) + + try: + options = TwinOptions(if_match=(etag if etag else "*")) + self.twins_sdk.update_component( + id=twin_id, + component_path=component_path, + patch_document=json_patch_collection, + digital_twins_update_component_options=options, + ) + return self.get_component(twin_id=twin_id, component_path=component_path) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) + + def send_telemetry(self, twin_id, telemetry=None, dt_id=None, component_path=None): + from uuid import uuid4 + from datetime import datetime, timezone + + local_time = datetime.now(timezone.utc).astimezone() + dt_timestamp = local_time.isoformat() + + telemetry_request = {} + + if telemetry: + telemetry = process_json_arg(content=telemetry, argument_name="telemetry") + else: + telemetry = {} + + telemetry_request.update(telemetry) + + logger.info("Telemetry payload: {}".format(json.dumps(telemetry_request))) + if not dt_id: + dt_id = str(uuid4()) + + try: + if component_path: + self.twins_sdk.send_component_telemetry( + id=twin_id, + message_id=dt_id, + dt_timestamp=dt_timestamp, + component_path=component_path, + telemetry=telemetry_request, + ) + + self.twins_sdk.send_telemetry( + id=twin_id, + message_id=dt_id, + dt_timestamp=dt_timestamp, + telemetry=telemetry_request, + ) + except ErrorResponseException as e: + raise CLIError(unpack_msrest_error(e)) diff --git a/azext_iot/dps/__init__.py b/azext_iot/dps/__init__.py new file mode 100644 index 000000000..55614acbf --- /dev/null +++ b/azext_iot/dps/__init__.py @@ -0,0 +1,5 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/azext_iot/dps/services/__init__.py b/azext_iot/dps/services/__init__.py new file mode 100644 index 000000000..55614acbf --- /dev/null +++ b/azext_iot/dps/services/__init__.py @@ -0,0 +1,5 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/azext_iot/dps/services/auth.py b/azext_iot/dps/services/auth.py new file mode 100644 index 000000000..8ddc1f468 --- /dev/null +++ b/azext_iot/dps/services/auth.py @@ -0,0 +1,28 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import time +import base64 +import hmac +import hashlib +import urllib + + +def get_dps_sas_auth_header( + scope_id, device_id, key, +): + sr = "{}%2Fregistrations%2F{}".format(scope_id, device_id) + expires = int(time.time() + 21600) + registration_id = f"{sr}\n{str(expires)}" + secret = base64.b64decode(key) + signature = base64.b64encode( + hmac.new( + secret, msg=registration_id.encode("utf8"), digestmod=hashlib.sha256 + ).digest() + ) + quote_signature = urllib.parse.quote(signature, "~()*!.'") + token = f"SharedAccessSignature sr={sr}&sig={quote_signature}&se={str(expires)}&skn=registration" + return token diff --git a/azext_iot/dps/services/global_service.py b/azext_iot/dps/services/global_service.py new file mode 100644 index 000000000..6f1dd0904 --- /dev/null +++ b/azext_iot/dps/services/global_service.py @@ -0,0 +1,49 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# This is for calls that route to the global DPS +# Useful for when you don't know what the dps name is ahead of time +# E.g. most IoT Central scenarios + +import requests + +from azext_iot import constants +from azext_iot.dps.services import auth + + +def get_registration_state(id_scope: str, key: str, device_id: str): + """ + Gets device registration state from global dps endpoint + Usefule for when dps name is unknown + + https://docs.microsoft.com/en-us/rest/api/iot-dps/getdeviceregistrationstate/getdeviceregistrationstate + + Params: + id_scope: dps id_scope + key: either primary or secondary symmetric key + device_id: device id that uniquely identifies the device + + Returns: + DeviceRegistrationState: dict + ProvisioningServiceErrorDetails: dict + """ + authToken = auth.get_dps_sas_auth_header(id_scope, device_id, key) + + url = "https://global.azure-devices-provisioning.net/{}/registrations/{}?api-version=2019-03-31".format( + id_scope, device_id + ) + header_parameters = { + "Content-Type": "application/json", + "User-Agent": constants.USER_AGENT, + "Authorization": authToken, + } + body = {"registrationId": "{}".format(device_id)} + response = requests.post(url, headers=header_parameters, json=body) + + try: + response.raise_for_status() + return response.json() + except Exception as e: + return {"error": str(e), "device_id": device_id} diff --git a/azext_iot/iothub/_help.py b/azext_iot/iothub/_help.py index c47362dbb..4ee5c1e7c 100644 --- a/azext_iot/iothub/_help.py +++ b/azext_iot/iothub/_help.py @@ -17,7 +17,7 @@ def load_iothub_help(): short-summary: Manage IoT Hub jobs (v2). """ - helps['iot hub job create'] = """ + helps["iot hub job create"] = """ type: command short-summary: Create and schedule an IoT Hub job for execution. long-summary: | @@ -43,9 +43,14 @@ def load_iothub_help(): text: > az iot hub job create --job-id {job_name} --job-type scheduleDeviceMethod -n {iothub_name} --method-name setSyncIntervalSec --method-payload 30 --query-condition "properties.reported.settings.syncIntervalSec != 30" + + - name: Create and schedule a job to invoke a device method for all devices. + text: > + az iot hub job create --job-id {job_name} --job-type scheduleDeviceMethod -q "*" -n {iothub_name} + --method-name setSyncIntervalSec --method-payload '{"version":"1.0"}' """ - helps['iot hub job show'] = """ + helps["iot hub job show"] = """ type: command short-summary: Show details of an existing IoT Hub job. @@ -55,7 +60,7 @@ def load_iothub_help(): az iot hub job show --hub-name {iothub_name} --job-id {job_id} """ - helps['iot hub job list'] = """ + helps["iot hub job list"] = """ type: command short-summary: List the historical jobs of an IoT Hub. @@ -77,7 +82,7 @@ def load_iothub_help(): az iot hub job list --hub-name {iothub_name} --job-type export --job-status completed """ - helps['iot hub job cancel'] = """ + helps["iot hub job cancel"] = """ type: command short-summary: Cancel an IoT Hub job. @@ -86,3 +91,57 @@ def load_iothub_help(): text: > az iot hub job cancel --hub-name {iothub_name} --job-id {job_id} """ + + helps["iot hub digital-twin"] = """ + type: group + short-summary: Manipulate and interact with the digital twin of an IoT Hub device. + """ + + helps["iot hub digital-twin invoke-command"] = """ + type: command + short-summary: Invoke a root or component level command of a digital twin device. + + examples: + - name: Invoke root level command "reboot" which takes a payload that includes the "delay" property. + text: > + az iot hub digital-twin invoke-command --command-name reboot -n {iothub_name} -d {device_id} --payload '{"delay":5}' + + - name: Invoke command "getMaxMinReport" on component "thermostat1" that takes no input. + text: > + az iot hub digital-twin invoke-command --cn getMaxMinReport -n {iothub_name} -d {device_id} --component-path thermostat1 + """ + + helps["iot hub digital-twin show"] = """ + type: command + short-summary: Show the digital twin of an IoT Hub device. + + examples: + - name: Show the target device digital twin. + text: > + az iot hub digital-twin show -n {iothub_name} -d {device_id} + """ + + helps["iot hub digital-twin update"] = """ + type: command + short-summary: Update the read-write properties of a digital twin device via JSON patch specification. + long-summary: Currently operations are limited to add, replace and remove. + + examples: + - name: Update a digital twin via JSON patch specification. + text: > + az iot hub digital-twin update --hub-name {iothub_name} --device-id {device_id} + --json-patch '{"op":"add", "path":"/thermostat1/targetTemperature", "value": 54}' + + - name: Update a digital twin via JSON patch specification. + text: > + az iot hub digital-twin update -n {iothub_name} -d {device_id} + --json-patch '[ + {"op":"remove", "path":"/thermostat1/targetTemperature"}, + {"op":"add", "path":"/thermostat2/targetTemperature", "value": 22} + ]' + + - name: Update a digital twin property via JSON patch specification defined in a file. + text: > + az iot hub digital-twin update -n {iothub_name} -d {device_id} + --json-patch ./my/patch/document.json + """ diff --git a/azext_iot/iothub/command_bindings.py b/azext_iot/iothub/command_bindings.py deleted file mode 100644 index aeaba9e2a..000000000 --- a/azext_iot/iothub/command_bindings.py +++ /dev/null @@ -1,25 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -""" -Load CLI commands -""" - -from azext_iot import iothub_ops_job, iothub_ops_device - - -def load_iothub_commands(self, _): - """ - Load CLI commands - """ - with self.command_group("iot hub job", command_type=iothub_ops_job) as cmd_group: - cmd_group.command("create", "job_create") - cmd_group.command("show", "job_show") - cmd_group.command("list", "job_list") - cmd_group.command("cancel", "job_cancel") - - with self.command_group("iot hub device-identity", command_type=iothub_ops_device) as cmd_group: - pass diff --git a/azext_iot/iothub/command_map.py b/azext_iot/iothub/command_map.py new file mode 100644 index 000000000..c74a85fe2 --- /dev/null +++ b/azext_iot/iothub/command_map.py @@ -0,0 +1,31 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Load CLI commands +""" +from azure.cli.core.commands import CliCommandType + +pnp_runtime_ops = CliCommandType( + operations_tmpl="azext_iot.iothub.commands_pnp_runtime#{}" +) +iothub_job_ops = CliCommandType(operations_tmpl="azext_iot.iothub.commands_job#{}") + + +def load_iothub_commands(self, _): + """ + Load CLI commands + """ + with self.command_group("iot hub job", command_type=iothub_job_ops) as cmd_group: + cmd_group.command("create", "job_create") + cmd_group.show_command("show", "job_show") + cmd_group.command("list", "job_list") + cmd_group.command("cancel", "job_cancel") + + with self.command_group("iot hub digital-twin", command_type=pnp_runtime_ops) as cmd_group: + cmd_group.command("invoke-command", "invoke_device_command") + cmd_group.show_command("show", "get_digital_twin") + cmd_group.command("update", "patch_digital_twin") diff --git a/azext_iot/iothub/job_commands.py b/azext_iot/iothub/commands_job.py similarity index 100% rename from azext_iot/iothub/job_commands.py rename to azext_iot/iothub/commands_job.py diff --git a/azext_iot/iothub/commands_pnp_runtime.py b/azext_iot/iothub/commands_pnp_runtime.py new file mode 100644 index 000000000..83a608f2f --- /dev/null +++ b/azext_iot/iothub/commands_pnp_runtime.py @@ -0,0 +1,67 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.iothub.providers.pnp_runtime import PnPRuntimeProvider +from knack.log import get_logger + +logger = get_logger(__name__) + + +def invoke_device_command( + cmd, + device_id, + command_name, + component_path=None, + payload="{}", + connect_timeout=None, + response_timeout=None, + hub_name=None, + resource_group_name=None, + login=None, +): + runtime_provider = PnPRuntimeProvider( + cmd=cmd, hub_name=hub_name, rg=resource_group_name, login=login + ) + return runtime_provider.invoke_device_command( + device_id=device_id, + command_name=command_name, + payload=payload, + component_path=component_path, + connect_timeout=connect_timeout, + response_timeout=response_timeout + ) + + +def get_digital_twin( + cmd, + device_id, + hub_name=None, + resource_group_name=None, + login=None, +): + runtime_provider = PnPRuntimeProvider( + cmd=cmd, hub_name=hub_name, rg=resource_group_name, login=login + ) + return runtime_provider.get_digital_twin( + device_id=device_id, + ) + + +def patch_digital_twin( + cmd, + device_id, + json_patch, + hub_name=None, + resource_group_name=None, + login=None, + etag=None +): + runtime_provider = PnPRuntimeProvider( + cmd=cmd, hub_name=hub_name, rg=resource_group_name, login=login + ) + return runtime_provider.patch_digital_twin( + device_id=device_id, json_patch=json_patch, etag=etag + ) diff --git a/azext_iot/iothub/device_commands.py b/azext_iot/iothub/device_commands.py deleted file mode 100644 index 68a53aa8d..000000000 --- a/azext_iot/iothub/device_commands.py +++ /dev/null @@ -1,16 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -from knack.log import get_logger -from azext_iot.iothub.providers.device_identity import DeviceIdentityProvider - - -logger = get_logger(__name__) - - -def get_device_metrics(cmd, hub_name=None, resource_group_name=None, login=None): - device_provider = DeviceIdentityProvider(cmd=cmd, hub_name=hub_name, rg=resource_group_name, login=login) - return device_provider.get_device_stats() diff --git a/azext_iot/iothub/mgmt_helpers.py b/azext_iot/iothub/mgmt_helpers.py deleted file mode 100644 index a9a6bf4c9..000000000 --- a/azext_iot/iothub/mgmt_helpers.py +++ /dev/null @@ -1,28 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -from azext_iot._factory import iot_hub_service_factory -from knack.util import CLIError - - -def get_mgmt_iothub_exception(): - """ Py 2/3 compatible management plane exception import""" - - try: - from azure.mgmt.iothub.models.error_details_py3 import ErrorDetailsException - except ImportError: - from azure.mgmt.iothub.models.error_details import ErrorDetailsException - - return ErrorDetailsException - - -def get_mgmt_iothub_client(cmd, raise_if_error=False): - try: - return iot_hub_service_factory(cmd.cli_ctx) - except CLIError as e: - if raise_if_error: - raise e - return None diff --git a/azext_iot/iothub/models/__init__.py b/azext_iot/iothub/models/__init__.py new file mode 100644 index 000000000..55614acbf --- /dev/null +++ b/azext_iot/iothub/models/__init__.py @@ -0,0 +1,5 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/azext_iot/iothub/models/iothub_target.py b/azext_iot/iothub/models/iothub_target.py new file mode 100644 index 000000000..798342203 --- /dev/null +++ b/azext_iot/iothub/models/iothub_target.py @@ -0,0 +1,33 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.common._azure import parse_iot_hub_connection_string + + +# TODO: Align with vNext for IoT Hub +class IotHubTarget: + def __init__(self, decomposed: dict): + # Revisit + decomposed_lower = dict((k.lower(), v) for k, v in decomposed.items()) + + self.cs = decomposed_lower.get("cs") + self.policy = decomposed_lower.get("sharedaccesskeyname") + self.shared_access_key = decomposed_lower.get("sharedaccesskey") + self.entity = decomposed_lower.get("hostname") + + @classmethod + def from_connection_string(cls, cstring): + decomposed = parse_iot_hub_connection_string(cs=cstring) + decomposed["cs"] = cstring + return cls(decomposed) + + def as_dict(self): + return { + "cs": self.cs, + "policy": self.policy, + "primarykey": self.shared_access_key, + "entity": self.entity, + } diff --git a/azext_iot/iothub/params.py b/azext_iot/iothub/params.py new file mode 100644 index 000000000..1265ae7b5 --- /dev/null +++ b/azext_iot/iothub/params.py @@ -0,0 +1,47 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +def load_iothub_arguments(self, _): + """ + Load CLI Args for Knack parser + """ + with self.argument_context("iot hub digital-twin") as context: + context.argument( + "command_name", + options_list=["--command-name", "--cn"], + help="Digital twin command name.", + ) + context.argument( + "component_path", + options_list=["--component-path"], + help="Digital twin component path. For example: thermostat1.", + ) + context.argument( + "json_patch", + options_list=["--json-patch", "--patch"], + help="An update specification described by JSON-patch. " + "Operations are limited to add, replace and remove. Provide file path or inline JSON.", + ) + context.argument( + "payload", + options_list=["--payload"], + help="JSON payload input for command. Provide file path or inline JSON.", + ) + context.argument( + "connect_timeout", + type=int, + options_list=["--connect-timeout", "--cto"], + help="Maximum interval of time, in seconds, that IoT Hub will attempt to connect to the device.", + arg_group="Timeout" + ) + context.argument( + "response_timeout", + type=int, + options_list=["--response-timeout", "--rto"], + help="Maximum interval of time, in seconds, that the digital twin command will wait for the result.", + arg_group="Timeout" + ) diff --git a/azext_iot/iothub/providers/base.py b/azext_iot/iothub/providers/base.py index 408d9e9aa..e668264dc 100644 --- a/azext_iot/iothub/providers/base.py +++ b/azext_iot/iothub/providers/base.py @@ -4,7 +4,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from azext_iot.common._azure import get_iot_hub_connection_string +from azext_iot.iothub.providers.discovery import IotHubDiscovery from azext_iot._factory import SdkResolver from msrest.exceptions import SerializationError from msrestazure.azure_exceptions import CloudError @@ -18,8 +18,8 @@ def __init__(self, cmd, hub_name, rg, login=None): self.cmd = cmd self.hub_name = hub_name self.rg = rg - self.target = get_iot_hub_connection_string( - cmd=self.cmd, + self.discovery = IotHubDiscovery(cmd) + self.target = self.discovery.get_target( hub_name=self.hub_name, resource_group_name=self.rg, login=login, diff --git a/azext_iot/iothub/providers/device_identity.py b/azext_iot/iothub/providers/device_identity.py deleted file mode 100644 index 1c982151a..000000000 --- a/azext_iot/iothub/providers/device_identity.py +++ /dev/null @@ -1,23 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -from knack.log import get_logger -from knack.util import CLIError -from azext_iot.common.shared import SdkType -from azext_iot.common.utility import unpack_msrest_error -from azext_iot.iothub.providers.base import IoTHubProvider, CloudError - - -logger = get_logger(__name__) - - -class DeviceIdentityProvider(IoTHubProvider): - def get_device_stats(self): - service_sdk = self.get_sdk(SdkType.service_sdk) - try: - return service_sdk.registry_manager.get_device_statistics() - except CloudError as e: - raise CLIError(unpack_msrest_error(e)) diff --git a/azext_iot/iothub/providers/discovery.py b/azext_iot/iothub/providers/discovery.py new file mode 100644 index 000000000..4d9043d4a --- /dev/null +++ b/azext_iot/iothub/providers/discovery.py @@ -0,0 +1,214 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.util import CLIError +from knack.log import get_logger +from azure.cli.core.commands.client_factory import get_subscription_id +from azext_iot.common.utility import trim_from_start, ensure_iothub_sdk_min_version +from azext_iot.iothub.models.iothub_target import IotHubTarget +from azext_iot._factory import iot_hub_service_factory +from azext_iot.constants import IOTHUB_TRACK_2_SDK_MIN_VERSION +from typing import Dict, List +from enum import Enum, EnumMeta + +PRIVILEDGED_ACCESS_RIGHTS_SET = set( + ["RegistryWrite", "ServiceConnect", "DeviceConnect"] +) +CONN_STR_TEMPLATE = "HostName={};SharedAccessKeyName={};SharedAccessKey={}" + +logger = get_logger(__name__) + + +# TODO: Consider abstract base class +class IotHubDiscovery(object): + def __init__(self, cmd): + self.cmd = cmd + self.client = None + self.sub_id = "unknown" + + def _initialize_client(self): + if not self.client: + if getattr(self.cmd, "cli_ctx", None): + self.client = iot_hub_service_factory(self.cmd.cli_ctx) + self.sub_id = get_subscription_id(self.cmd.cli_ctx) + else: + self.client = self.cmd + + def get_iothubs(self, rg: str = None) -> List: + self._initialize_client() + + hubs_list = [] + + if not rg: + hubs_pager = self.client.list_by_subscription() + else: + hubs_pager = self.client.list_by_resource_group(resource_group_name=rg) + + if ensure_iothub_sdk_min_version(IOTHUB_TRACK_2_SDK_MIN_VERSION): + for hubs in hubs_pager.by_page(): + hubs_list.extend(hubs) + else: + try: + while True: + hubs_list.extend(hubs_pager.advance_page()) + except StopIteration: + pass + + return hubs_list + + def get_policies(self, hub_name: str, rg: str) -> List: + self._initialize_client() + + policy_pager = self.client.list_keys( + resource_group_name=rg, resource_name=hub_name + ) + policy_list = [] + + if ensure_iothub_sdk_min_version(IOTHUB_TRACK_2_SDK_MIN_VERSION): + for policy in policy_pager.by_page(): + policy_list.extend(policy) + else: + try: + while True: + policy_list.extend(policy_pager.advance_page()) + except StopIteration: + pass + + return policy_list + + def find_iothub(self, hub_name: str, rg: str = None): + self._initialize_client() + + if rg: + try: + return self.client.get(resource_group_name=rg, resource_name=hub_name) + except: # pylint: disable=broad-except + raise CLIError( + "Unable to find IoT Hub: {} in resource group: {}".format( + hub_name, rg + ) + ) + + hubs_list = self.get_iothubs() + + if hubs_list: + target_hub = next( + (hub for hub in hubs_list if hub_name.lower() == hub.name.lower()), None + ) + if target_hub: + return target_hub + + raise CLIError( + "Unable to find IoT Hub: {} in current subscription {}.".format( + hub_name, self.sub_id + ) + ) + + def find_policy(self, hub_name: str, rg: str, policy_name: str = "auto"): + self._initialize_client() + + if policy_name.lower() != "auto": + return self.client.get_keys_for_key_name( + resource_group_name=rg, resource_name=hub_name, key_name=policy_name + ) + + policy_list = self.get_policies(hub_name=hub_name, rg=rg) + + for policy in policy_list: + rights_set = set(policy.rights.split(", ")) + if PRIVILEDGED_ACCESS_RIGHTS_SET == rights_set: + logger.info( + "Using policy '%s' for IoT Hub interaction.", policy.key_name + ) + return policy + + raise CLIError( + "Unable to discover a priviledged policy for IoT Hub: {}, in subscription {}. " + "When interfacing with an IoT Hub, the IoT extension requires any single policy with " + "'RegistryWrite', 'ServiceConnect' and 'DeviceConnect' rights.".format( + hub_name, self.sub_id + ) + ) + + @classmethod + def get_target_by_cstring(cls, connection_string: str) -> IotHubTarget: + return IotHubTarget.from_connection_string(cstring=connection_string).as_dict() + + def get_target(self, hub_name: str, resource_group_name: str = None, **kwargs) -> Dict[str, str]: + cstring = kwargs.get("login") + if cstring: + return self.get_target_by_cstring(connection_string=cstring) + + target_iothub = self.find_iothub(hub_name=hub_name, rg=resource_group_name) + + policy_name = kwargs.get("policy_name", "auto") + rg = target_iothub.additional_properties.get("resourcegroup") + + target_policy = self.find_policy( + hub_name=target_iothub.name, rg=rg, policy_name=policy_name, + ) + + key_type = kwargs.get("key_type", "primary") + include_events = kwargs.get("include_events", False) + return self._build_target( + iothub=target_iothub, + policy=target_policy, + key_type=key_type, + include_events=include_events, + ) + + def get_targets(self, resource_group_name: str = None, **kwargs) -> List[Dict[str, str]]: + targets = [] + hubs = self.get_iothubs(rg=resource_group_name) + if hubs: + for hub in hubs: + targets.append( + self.get_target(hub_name=hub.name, resource_group_name=self._get_rg(hub), **kwargs) + ) + + return targets + + def _get_rg(self, hub): + return hub.additional_properties.get("resourcegroup") + + def _build_target( + self, iothub, policy, key_type: str = None, include_events=False + ) -> Dict[str, str]: + # This is more or less a compatibility function which produces the + # same result as _azure.get_iot_hub_connection_string() + # In future iteration we will return a 'Target' object rather than dict + # but that will be better served aligning with vNext pattern for Iot Hub + + target = {} + target["cs"] = CONN_STR_TEMPLATE.format( + iothub.properties.host_name, + policy.key_name, + policy.primary_key if key_type == "primary" else policy.secondary_key, + ) + target["entity"] = iothub.properties.host_name + target["policy"] = policy.key_name + target["primarykey"] = policy.primary_key + target["secondarykey"] = policy.secondary_key + target["subscription"] = self.sub_id + target["resourcegroup"] = iothub.additional_properties.get("resourcegroup") + target["location"] = iothub.location + target["sku_tier"] = iothub.sku.tier.value if isinstance(iothub.sku.tier, (Enum, EnumMeta)) else iothub.sku.tier + + if include_events: + events = {} + events["endpoint"] = trim_from_start( + iothub.properties.event_hub_endpoints["events"].endpoint, "sb://" + ).strip("/") + events["partition_count"] = iothub.properties.event_hub_endpoints[ + "events" + ].partition_count + events["path"] = iothub.properties.event_hub_endpoints["events"].path + events["partition_ids"] = iothub.properties.event_hub_endpoints[ + "events" + ].partition_ids + target["events"] = events + + return target diff --git a/azext_iot/iothub/providers/job.py b/azext_iot/iothub/providers/job.py index 06e7a5f61..f5d21377a 100644 --- a/azext_iot/iothub/providers/job.py +++ b/azext_iot/iothub/providers/job.py @@ -31,8 +31,8 @@ def _get(self, job_id, job_version=JobVersionType.v2): try: if job_version == JobVersionType.v2: - return service_sdk.job_client.get_job(id=job_id, raw=True).response.json() - return self._convert_v1_to_v2(service_sdk.job_client.get_import_export_job(id=job_id)) + return service_sdk.jobs.get_scheduled_job(id=job_id, raw=True).response.json() + return self._convert_v1_to_v2(service_sdk.jobs.get_import_export_job(id=job_id)) except CloudError as e: raise CLIError(unpack_msrest_error(e)) @@ -50,8 +50,8 @@ def _cancel(self, job_id, job_version=JobVersionType.v2): try: if job_version == JobVersionType.v2: - return service_sdk.job_client.cancel_job(id=job_id, raw=True).response.json() - return service_sdk.job_client.cancel_import_export_job(id=job_id) + return service_sdk.jobs.cancel_scheduled_job(id=job_id, raw=True).response.json() + return service_sdk.jobs.cancel_import_export_job(id=job_id) except CloudError as e: raise CLIError(unpack_msrest_error(e)) @@ -92,10 +92,10 @@ def _list(self, job_type=None, job_status=None, top=None, job_version=JobVersion try: if job_version == JobVersionType.v2: query = [job_type, job_status] - query_method = service_sdk.job_client.query_jobs + query_method = service_sdk.jobs.query_scheduled_jobs jobs_collection.extend(_execute_query(query, query_method, top)) elif job_version == JobVersionType.v1: - jobs_collection.extend(service_sdk.job_client.get_import_export_jobs()) + jobs_collection.extend(service_sdk.jobs.get_import_export_jobs()) jobs_collection = [self._convert_v1_to_v2(job) for job in jobs_collection] return jobs_collection @@ -191,7 +191,7 @@ def create( service_sdk = self.get_sdk(SdkType.service_sdk) try: - job_result = service_sdk.job_client.create_job(id=job_id, job_request=job_request, raw=True).response.json() + job_result = service_sdk.jobs.create_scheduled_job(id=job_id, job_request=job_request, raw=True).response.json() if wait: logger.info("Waiting for job finished state...") current_datetime = datetime.now() diff --git a/azext_iot/iothub/providers/pnp_runtime.py b/azext_iot/iothub/providers/pnp_runtime.py new file mode 100644 index 000000000..20c827d2b --- /dev/null +++ b/azext_iot/iothub/providers/pnp_runtime.py @@ -0,0 +1,105 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +from knack.log import get_logger +from knack.util import CLIError +from azext_iot.common.shared import SdkType +from azext_iot.common.utility import unpack_msrest_error, process_json_arg +from azext_iot.iothub.providers.base import ( + IoTHubProvider, + CloudError, +) +from azext_iot.sdk.iothub.service.operations.digital_twin_operations import DigitalTwinOperations + + +logger = get_logger(__name__) + + +class PnPRuntimeProvider(IoTHubProvider): + def __init__(self, cmd, hub_name=None, rg=None, login=None): + super(PnPRuntimeProvider, self).__init__( + cmd=cmd, hub_name=hub_name, rg=rg, login=login + ) + self.runtime_sdk: DigitalTwinOperations = self.get_sdk( + SdkType.service_sdk + ).digital_twin + + def invoke_device_command( + self, + device_id, + command_name, + payload="{}", + component_path=None, + connect_timeout=None, + response_timeout=None, + ): + # Prevent msrest locking up shell + self.runtime_sdk.config.retry_policy.retries = 1 + + try: + if payload: + payload = process_json_arg(payload, argument_name="payload") + + api_timeout_kwargs = { + "connect_timeout_in_seconds": connect_timeout, + "response_timeout_in_seconds": response_timeout, + } + response = ( + self.runtime_sdk.invoke_component_command( + id=device_id, + command_name=command_name, + payload=payload, + timeout=connect_timeout, + component_path=component_path, + raw=True, + **api_timeout_kwargs, + ).response + if component_path + else self.runtime_sdk.invoke_root_level_command( + id=device_id, + command_name=command_name, + payload=payload, + timeout=connect_timeout, + raw=True, + **api_timeout_kwargs, + ).response + ) + + return { + "payload": response.json(), + "status": response.headers.get("x-ms-command-statuscode"), + } + + except CloudError as e: + raise CLIError(unpack_msrest_error(e)) + + def get_digital_twin(self, device_id): + return self.runtime_sdk.get_digital_twin(id=device_id, raw=True).response.json() + + def patch_digital_twin(self, device_id, json_patch, etag=None): + json_patch = process_json_arg(content=json_patch, argument_name="json-patch") + + json_patch_collection = [] + if isinstance(json_patch, dict): + json_patch_collection.append(json_patch) + if isinstance(json_patch, list): + json_patch_collection.extend(json_patch) + + logger.info("Patch payload %s", json.dumps(json_patch_collection)) + + try: + # Currently no response text is returned from the update + self.runtime_sdk.update_digital_twin( + id=device_id, + digital_twin_patch=json_patch_collection, + if_match=etag if etag else "*", + raw=True, + ).response + + return self.get_digital_twin(device_id=device_id) + except CloudError as e: + raise CLIError(unpack_msrest_error(e)) diff --git a/azext_iot/models/__init__.py b/azext_iot/models/__init__.py index 6b79613f9..55614acbf 100644 --- a/azext_iot/models/__init__.py +++ b/azext_iot/models/__init__.py @@ -3,8 +3,3 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- - - -from azext_iot.common.utility import ensure_pkg_resources_entries - -ensure_pkg_resources_entries() diff --git a/azext_iot/monitor/__init__.py b/azext_iot/monitor/__init__.py new file mode 100644 index 000000000..55614acbf --- /dev/null +++ b/azext_iot/monitor/__init__.py @@ -0,0 +1,5 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/azext_iot/monitor/base_classes.py b/azext_iot/monitor/base_classes.py new file mode 100644 index 000000000..76182b56b --- /dev/null +++ b/azext_iot/monitor/base_classes.py @@ -0,0 +1,25 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from abc import ABC, abstractmethod + + +class AbstractBaseParser(ABC): + def __init__(self): + super().__init__() + + @abstractmethod + def parse_message(self, message) -> dict: + raise NotImplementedError() + + +class AbstractBaseEventsHandler(ABC): + def __init__(self): + super().__init__() + + @abstractmethod + def parse_message(self, message): + raise NotImplementedError() diff --git a/azext_iot/monitor/builders/__init__.py b/azext_iot/monitor/builders/__init__.py new file mode 100644 index 000000000..55614acbf --- /dev/null +++ b/azext_iot/monitor/builders/__init__.py @@ -0,0 +1,5 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/azext_iot/monitor/builders/_common.py b/azext_iot/monitor/builders/_common.py new file mode 100644 index 000000000..7ac3e0ddc --- /dev/null +++ b/azext_iot/monitor/builders/_common.py @@ -0,0 +1,69 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import uamqp +import urllib + +from azext_iot.monitor.models.target import Target + +DEBUG = False + + +async def convert_token_to_target(tokens) -> Target: + token_expiry = tokens["expiry"] + event_hub_token = tokens["eventhubSasToken"] + + sas_token = event_hub_token["sasToken"] + path = event_hub_token["entityPath"] + raw_url = event_hub_token["hostname"] + + url = urllib.parse.urlparse(raw_url) + hostname = url.hostname + + meta_data = await _query_meta_data_internal(hostname, path, sas_token, token_expiry) + + partition_count = meta_data[b"partition_count"] + partitions = [str(i) for i in range(int(partition_count))] + + auth = _build_auth_container_from_token(hostname, path, sas_token, token_expiry) + + return Target(hostname=hostname, path=path, partitions=partitions, auth=auth) + + +async def query_meta_data(address, path, auth): + source = uamqp.address.Source(address) + receive_client = uamqp.ReceiveClientAsync( + source, auth=auth, timeout=30000, debug=DEBUG + ) + try: + await receive_client.open_async() + message = uamqp.Message(application_properties={"name": path}) + + response = await receive_client.mgmt_request_async( + message, + b"READ", + op_type=b"com.microsoft:eventhub", + status_code_field=b"status-code", + description_fields=b"status-description", + timeout=30000, + ) + test = response.get_data() + return test + finally: + await receive_client.close_async() + + +async def _query_meta_data_internal(hostname, path, sas_token, token_expiry): + address = "amqps://{}/{}/$management".format(hostname, path) + auth = _build_auth_container_from_token(hostname, path, sas_token, token_expiry) + return await query_meta_data(address=address, path=path, auth=auth) + + +def _build_auth_container_from_token(hostname, path, token, expiry): + sas_uri = "sb://{}/{}".format(hostname, path) + return uamqp.authentication.SASTokenAsync( + audience=sas_uri, uri=sas_uri, expires_at=expiry, token=token + ) diff --git a/azext_iot/monitor/builders/central_target_builder.py b/azext_iot/monitor/builders/central_target_builder.py new file mode 100644 index 000000000..84c8ca644 --- /dev/null +++ b/azext_iot/monitor/builders/central_target_builder.py @@ -0,0 +1,32 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import asyncio + +from typing import List + +from azext_iot.common._azure import get_iot_central_tokens +from azext_iot.monitor.models.target import Target +from azext_iot.monitor.builders._common import convert_token_to_target + + +def build_central_event_hub_targets( + cmd, app_id, aad_token, central_dns_suffix +) -> List[Target]: + event_loop = asyncio.get_event_loop() + return event_loop.run_until_complete( + _build_central_event_hub_targets_async( + cmd, app_id, aad_token, central_dns_suffix + ) + ) + + +async def _build_central_event_hub_targets_async( + cmd, app_id, aad_token, central_dns_suffix +): + all_tokens = get_iot_central_tokens(cmd, app_id, aad_token, central_dns_suffix) + targets = [await convert_token_to_target(token) for token in all_tokens.values()] + + return targets diff --git a/azext_iot/monitor/builders/hub_target_builder.py b/azext_iot/monitor/builders/hub_target_builder.py new file mode 100644 index 000000000..bc61bc3fd --- /dev/null +++ b/azext_iot/monitor/builders/hub_target_builder.py @@ -0,0 +1,98 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import asyncio +import uamqp + +from azext_iot.common.sas_token_auth import SasTokenAuthentication +from azext_iot.common.utility import parse_entity, unicode_binary_map, url_encode_str +from azext_iot.monitor.builders._common import query_meta_data +from azext_iot.monitor.models.target import Target + +# To provide amqp frame trace +DEBUG = False + + +class AmqpBuilder: + @classmethod + def build_iothub_amqp_endpoint_from_target(cls, target, duration=360): + hub_name = target["entity"].split(".")[0] + user = "{}@sas.root.{}".format(target["policy"], hub_name) + sas_token = SasTokenAuthentication( + target["entity"], target["policy"], target["primarykey"], duration + ).generate_sas_token() + return url_encode_str(user) + ":{}@{}".format( + url_encode_str(sas_token), target["entity"] + ) + + +class EventTargetBuilder: + def __init__(self): + self.eventLoop = asyncio.new_event_loop() + asyncio.set_event_loop(self.eventLoop) + + def build_iot_hub_target(self, target): + return self.eventLoop.run_until_complete( + self._build_iot_hub_target_async(target) + ) + + def _build_auth_container(self, target): + sas_uri = "sb://{}/{}".format( + target["events"]["endpoint"], target["events"]["path"] + ) + return uamqp.authentication.SASTokenAsync.from_shared_access_key( + sas_uri, target["policy"], target["primarykey"] + ) + + async def _evaluate_redirect(self, endpoint): + source = uamqp.address.Source( + "amqps://{}/messages/events/$management".format(endpoint) + ) + receive_client = uamqp.ReceiveClientAsync( + source, timeout=30000, prefetch=1, debug=DEBUG + ) + + try: + await receive_client.open_async() + await receive_client.receive_message_batch_async(max_batch_size=1) + except uamqp.errors.LinkRedirect as redirect: + redirect = unicode_binary_map(parse_entity(redirect)) + result = {} + result["events"] = {} + result["events"]["endpoint"] = redirect["hostname"] + result["events"]["path"] = ( + redirect["address"].replace("amqps://", "").split("/")[1] + ) + result["events"]["address"] = redirect["address"] + return redirect, result + finally: + await receive_client.close_async() + + async def _build_iot_hub_target_async(self, target): + if "events" not in target: + endpoint = AmqpBuilder.build_iothub_amqp_endpoint_from_target(target) + _, update = await self._evaluate_redirect(endpoint) + target["events"] = update["events"] + endpoint = target["events"]["endpoint"] + path = target["events"]["path"] + auth = self._build_auth_container(target) + meta_data = await query_meta_data( + address=target["events"]["address"], + path=target["events"]["path"], + auth=auth, + ) + partition_count = meta_data[b"partition_count"] + partition_ids = [] + for i in range(int(partition_count)): + partition_ids.append(str(i)) + target["events"]["partition_ids"] = partition_ids + else: + endpoint = target["events"]["endpoint"] + path = target["events"]["path"] + partitions = target["events"]["partition_ids"] + auth = self._build_auth_container(target) + + return Target(hostname=endpoint, path=path, partitions=partitions, auth=auth) diff --git a/azext_iot/monitor/central_validator/__init__.py b/azext_iot/monitor/central_validator/__init__.py new file mode 100644 index 000000000..ec5fc16b0 --- /dev/null +++ b/azext_iot/monitor/central_validator/__init__.py @@ -0,0 +1,10 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.monitor.central_validator.validate_schema import validate +from azext_iot.monitor.central_validator.utils import extract_schema_type + +__all__ = ["validate", "extract_schema_type"] diff --git a/azext_iot/monitor/central_validator/utils.py b/azext_iot/monitor/central_validator/utils.py new file mode 100644 index 000000000..c00780ebd --- /dev/null +++ b/azext_iot/monitor/central_validator/utils.py @@ -0,0 +1,28 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +def extract_schema_type(schema: dict): + # some error with parsing schema + if not isinstance(schema, dict): + return + + schema_type = schema.get("schema") + # some error with parsing schema + if not schema_type: + return + + # Custom defined complex types store schema as dict + if not isinstance(schema_type, str): + schema_type = schema_type["@type"] + + # If template is retrieved through API, the type info is in a list + # Extract the first item + # TODO: update this work around once IoTC has consistency between API and UX + if isinstance(schema_type, list): + schema_type = schema_type[0] + + return schema_type diff --git a/azext_iot/monitor/central_validator/validate_schema.py b/azext_iot/monitor/central_validator/validate_schema.py new file mode 100644 index 000000000..cc97da387 --- /dev/null +++ b/azext_iot/monitor/central_validator/validate_schema.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from azext_iot.common.utility import ISO8601Validator +from azext_iot.monitor.central_validator import utils +from azext_iot.monitor.central_validator.validators import enum, geopoint, obj, vector + +iso_validator = ISO8601Validator() + +validation_function_factory = { + # primitive + "boolean": lambda schema, value: isinstance(value, bool), + "double": lambda schema, value: isinstance(value, (float, int)), + "float": lambda schema, value: isinstance(value, (float, int)), + "integer": lambda schema, value: isinstance(value, int), + "long": lambda schema, value: isinstance(value, (float, int)), + "string": lambda schema, value: isinstance(value, str), + # primitive - time + "date": lambda schema, value: iso_validator.is_iso8601_date(value), + "dateTime": lambda schema, value: iso_validator.is_iso8601_datetime(value), + "duration": lambda schema, value: iso_validator.is_iso8601_duration(value), + "time": lambda schema, value: iso_validator.is_iso8601_time(value), + # pre-defined complex + "geopoint": geopoint.validate, + "vector": vector.validate, + # complex + "Enum": enum.validate, + "Object": obj.validate, + # TODO: yet to be enabled in prod "Map": lambda val: False, + # TODO: yet to be implemented in prod "Array": lambda val: False, +} + + +def validate(schema, value): + # if theres nothing to validate, then its valid + if value is None: + return True + + schema_type = utils.extract_schema_type(schema) + if not schema_type: + return False + + validate_function = validation_function_factory.get(schema_type) + + # invalid schema type detected + if not validate_function: + return False + + return validate_function(schema, value) diff --git a/azext_iot/monitor/central_validator/validators/__init__.py b/azext_iot/monitor/central_validator/validators/__init__.py new file mode 100644 index 000000000..55614acbf --- /dev/null +++ b/azext_iot/monitor/central_validator/validators/__init__.py @@ -0,0 +1,5 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/azext_iot/monitor/central_validator/validators/enum.py b/azext_iot/monitor/central_validator/validators/enum.py new file mode 100644 index 000000000..8dd0179c3 --- /dev/null +++ b/azext_iot/monitor/central_validator/validators/enum.py @@ -0,0 +1,17 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +def validate(schema, value): + if not isinstance(schema, dict): + return False + + # schema.schema.enumValues, but done safely + enum_values = schema.get("schema", {}).get("enumValues", []) + + allowed_values = [item["enumValue"] for item in enum_values if "enumValue" in item] + + return value in allowed_values diff --git a/azext_iot/monitor/central_validator/validators/geopoint.py b/azext_iot/monitor/central_validator/validators/geopoint.py new file mode 100644 index 000000000..4b4661c69 --- /dev/null +++ b/azext_iot/monitor/central_validator/validators/geopoint.py @@ -0,0 +1,27 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +def validate(schema, value: dict): + required_keys = set(["lat", "lon"]) + all_keys = set(["alt", "lat", "lon"]) + if not isinstance(value, dict): + return False + + # check that value has all required keys + if not set(required_keys).issubset(set(value.keys())): + return False + + # check that value does not have more than expected keys + if not set(value.keys()).issubset(all_keys): + return False + + # check that all values are double + for val in value.values(): + if not isinstance(val, (float, int)): + return False + + return True diff --git a/azext_iot/monitor/central_validator/validators/obj.py b/azext_iot/monitor/central_validator/validators/obj.py new file mode 100644 index 000000000..47e1620eb --- /dev/null +++ b/azext_iot/monitor/central_validator/validators/obj.py @@ -0,0 +1,27 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.monitor.central_validator import validate_schema + + +def validate(schema: dict, value: dict): + if not isinstance(schema, dict): + return False + + if not isinstance(value, dict): + return False + + fields = schema.get("schema", {}).get("fields", []) + schema_fields = {field["name"]: field for field in fields} + + for key, val in value.items(): + if key not in schema_fields: + return False + + if not validate_schema.validate(schema_fields[key], val): + return False + + return True diff --git a/azext_iot/monitor/central_validator/validators/vector.py b/azext_iot/monitor/central_validator/validators/vector.py new file mode 100644 index 000000000..e19b4baec --- /dev/null +++ b/azext_iot/monitor/central_validator/validators/vector.py @@ -0,0 +1,22 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +def validate(schema, value: dict): + required_keys = set(["x", "y", "z"]) + if not isinstance(value, dict): + return False + + # check that value has all required keys + if set(value.keys()) != required_keys: + return False + + # check that all values are double + for val in value.values(): + if not isinstance(val, (float, int)): + return False + + return True diff --git a/azext_iot/monitor/event.py b/azext_iot/monitor/event.py new file mode 100644 index 000000000..faa7afa85 --- /dev/null +++ b/azext_iot/monitor/event.py @@ -0,0 +1,143 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +import uamqp +import yaml + +from uuid import uuid4 +from knack.log import get_logger +from azext_iot.constants import USER_AGENT +from azext_iot.common.utility import process_json_arg +from azext_iot.monitor.builders.hub_target_builder import AmqpBuilder + +# To provide amqp frame trace +DEBUG = False +logger = get_logger(__name__) + + +def send_c2d_message( + target, + device_id, + data, + message_id=None, + correlation_id=None, + ack=None, + content_type=None, + user_id=None, + content_encoding="utf-8", + expiry_time_utc=None, + properties=None, +): + + app_props = {} + if properties: + app_props.update(properties) + + app_props["iothub-ack"] = ack if ack else "none" + + msg_props = uamqp.message.MessageProperties() + msg_props.to = "/devices/{}/messages/devicebound".format(device_id) + + target_msg_id = message_id if message_id else str(uuid4()) + msg_props.message_id = target_msg_id + + if correlation_id: + msg_props.correlation_id = correlation_id + + if user_id: + msg_props.user_id = user_id + + if content_type: + msg_props.content_type = content_type + + # Ensures valid json when content_type is application/json + content_type = content_type.lower() + if content_type == "application/json": + data = json.dumps(process_json_arg(data, "data")) + + if content_encoding: + msg_props.content_encoding = content_encoding + + if expiry_time_utc: + msg_props.absolute_expiry_time = int(expiry_time_utc) + + msg_body = str.encode(data) + + message = uamqp.Message( + body=msg_body, properties=msg_props, application_properties=app_props + ) + + operation = "/messages/devicebound" + endpoint = AmqpBuilder.build_iothub_amqp_endpoint_from_target(target) + endpoint_with_op = endpoint + operation + client = uamqp.SendClient( + target="amqps://" + endpoint_with_op, + client_name=_get_container_id(), + debug=DEBUG, + ) + client.queue_message(message) + result = client.send_all_messages() + errors = [m for m in result if m == uamqp.constants.MessageState.SendFailed] + return target_msg_id, errors + + +def monitor_feedback(target, device_id, wait_on_id=None, token_duration=3600): + def handle_msg(msg): + payload = next(msg.get_data()) + if isinstance(payload, bytes): + payload = str(payload, "utf8") + # assume json [] based on spec + payload = json.loads(payload) + for p in payload: + if ( + device_id + and p.get("deviceId") + and p["deviceId"].lower() != device_id.lower() + ): + return None + print(yaml.dump({"feedback": p}, default_flow_style=False), flush=True) + if wait_on_id: + msg_id = p["originalMessageId"] + if msg_id == wait_on_id: + return msg_id + return None + + operation = "/messages/servicebound/feedback" + endpoint = AmqpBuilder.build_iothub_amqp_endpoint_from_target( + target, duration=token_duration + ) + endpoint = endpoint + operation + + device_filter_txt = None + if device_id: + device_filter_txt = " filtering on device: {},".format(device_id) + + print( + "Starting C2D feedback monitor,{} use ctrl-c to stop...".format( + device_filter_txt if device_filter_txt else "" + ) + ) + + try: + client = uamqp.ReceiveClient( + "amqps://" + endpoint, client_name=_get_container_id(), debug=DEBUG + ) + message_generator = client.receive_messages_iter() + for msg in message_generator: + match = handle_msg(msg) + if match: + logger.info("Requested message Id has been matched...") + msg.accept() + return match + except uamqp.errors.AMQPConnectionError: + logger.debug("AMQPS connection has expired...") + finally: + client.close() + + +def _get_container_id(): + return "{}/{}".format(USER_AGENT, str(uuid4())) diff --git a/azext_iot/monitor/handlers/__init__.py b/azext_iot/monitor/handlers/__init__.py new file mode 100644 index 000000000..0b2bd36c5 --- /dev/null +++ b/azext_iot/monitor/handlers/__init__.py @@ -0,0 +1,10 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.monitor.handlers.common_handler import CommonHandler +from azext_iot.monitor.handlers.central_handler import CentralHandler + +__all__ = ["CommonHandler", "CentralHandler"] diff --git a/azext_iot/monitor/handlers/central_handler.py b/azext_iot/monitor/handlers/central_handler.py new file mode 100644 index 000000000..54311ff1c --- /dev/null +++ b/azext_iot/monitor/handlers/central_handler.py @@ -0,0 +1,169 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import csv +import sys + +from typing import List +from knack.log import get_logger + +from azext_iot.monitor.utility import stop_monitor, get_loop +from azext_iot.central.providers import ( + CentralDeviceProvider, + CentralDeviceTemplateProvider, +) +from azext_iot.monitor.handlers import CommonHandler +from azext_iot.monitor.models.arguments import CentralHandlerArguments +from azext_iot.monitor.parsers.central_parser import CentralParser +from azext_iot.monitor.parsers.issue import Issue + +logger = get_logger(__name__) + + +class CentralHandler(CommonHandler): + def __init__( + self, + central_device_provider: CentralDeviceProvider, + central_template_provider: CentralDeviceTemplateProvider, + central_handler_args: CentralHandlerArguments, + ): + super(CentralHandler, self).__init__( + common_handler_args=central_handler_args.common_handler_args + ) + + self._central_device_provider = central_device_provider + self._central_template_provider = central_template_provider + + self._central_handler_args = central_handler_args + + self._messages = [] + self._issues: List[Issue] = [] + + if self._central_handler_args.duration: + loop = get_loop() + # the monitor takes a few seconds to start + loop.call_later( + self._central_handler_args.duration + 5, self._quit_duration_exceeded + ) + + def validate_message(self, message): + parser = CentralParser( + message=message, + common_parser_args=self._common_handler_args.common_parser_args, + central_device_provider=self._central_device_provider, + central_template_provider=self._central_template_provider, + ) + + if not self._should_process_device(parser.device_id): + return + + if not self._should_process_module(parser.module_id): + return + + parsed_message = parser.parse_message() + + self._messages.append(parsed_message) + n_messages = len(self._messages) + + issues = parser.issues_handler.get_issues_with_minimum_severity( + self._central_handler_args.minimum_severity + ) + + self._issues.extend(issues) + + self._print_progress_update(n_messages) + + if self._central_handler_args.style == "scroll" and issues: + [issue.log() for issue in issues] + + if ( + self._central_handler_args.max_messages + and n_messages >= self._central_handler_args.max_messages + ): + self._quit_messages_exceeded() + + def generate_startup_string(self, name: str): + device_id = self._central_handler_args.common_handler_args.device_id + duration = self._central_handler_args.duration + max_messages = self._central_handler_args.max_messages + module_id = self._central_handler_args.common_handler_args.module_id + + filter_text = "" + if device_id: + filter_text = ".\nFiltering on device: {}".format(device_id) + + if module_id: + logger.warning("Module filtering is applicable only for edge devices.") + filter_text += ".\nFiltering on module: {}".format(module_id) + + exit_text = "" + if duration and max_messages: + exit_text = ".\nExiting after {} second(s), or {} message(s) have been parsed (whichever happens first).".format( + duration, max_messages, + ) + elif duration: + exit_text = ".\nExiting after {} second(s).".format(duration) + elif max_messages: + exit_text = ".\nExiting after parsing {} message(s).".format(max_messages) + + result = "{} telemetry{}{}".format(name, filter_text, exit_text) + + return result + + def _print_progress_update(self, n_messages: int): + if (n_messages % self._central_handler_args.progress_interval) == 0: + print("Processed {} messages...".format(n_messages), flush=True) + + def _print_results(self): + n_messages = len(self._messages) + + if not self._issues: + print("No errors detected after parsing {} message(s).".format(n_messages)) + return + + if self._central_handler_args.style.lower() == "scroll": + return + + print("Processing and displaying results.") + + issues = [issue.json_repr() for issue in self._issues] + + if self._central_handler_args.style.lower() == "json": + self._handle_json_summary(issues) + return + + if self._central_handler_args.style.lower() == "csv": + self._handle_csv_summary(issues) + return + + def _handle_json_summary(self, issues: List[Issue]): + import json + + output = json.dumps(issues, indent=4) + print(output) + + def _handle_csv_summary(self, issues: List[Issue]): + fieldnames = ["severity", "details", "message", "device_id", "template_id"] + writer = csv.DictWriter(sys.stdout, fieldnames=fieldnames) + writer.writeheader() + for issue in issues: + writer.writerow(issue) + + def _quit_messages_exceeded(self): + message = "Successfully parsed {} message(s).".format( + self._central_handler_args.max_messages + ) + print(message, flush=True) + self._print_results() + stop_monitor() + + def _quit_duration_exceeded(self): + message = "{} second(s) have elapsed.".format( + self._central_handler_args.duration + ) + print(message, flush=True) + self._print_results() + stop_monitor() diff --git a/azext_iot/monitor/handlers/common_handler.py b/azext_iot/monitor/handlers/common_handler.py new file mode 100644 index 000000000..b779f7d43 --- /dev/null +++ b/azext_iot/monitor/handlers/common_handler.py @@ -0,0 +1,81 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +import re +import yaml + +from azext_iot.monitor.base_classes import AbstractBaseEventsHandler +from azext_iot.monitor.parsers.common_parser import CommonParser +from azext_iot.monitor.models.arguments import CommonHandlerArguments + + +class CommonHandler(AbstractBaseEventsHandler): + def __init__(self, common_handler_args: CommonHandlerArguments): + super(CommonHandler, self).__init__() + self._common_handler_args = common_handler_args + + def parse_message(self, message): + parser = CommonParser( + message=message, + common_parser_args=self._common_handler_args.common_parser_args, + ) + + if not self._should_process_device(parser.device_id): + return + + if not self._should_process_interface(parser.interface_name): + return + + if not self._should_process_module(parser.module_id): + return + + result = parser.parse_message() + + if self._common_handler_args.output.lower() == "json": + dump = json.dumps(result, indent=4) + else: + dump = yaml.safe_dump(result, default_flow_style=False) + + print(dump, flush=True) + + def _should_process_device(self, device_id): + expected_device_id = self._common_handler_args.device_id + expected_devices = self._common_handler_args.devices + + process_device = self._perform_id_match(expected_device_id, device_id) + + if expected_devices and device_id not in expected_devices: + return False + + return process_device + + def _perform_id_match(self, expected_id, actual_id): + if expected_id and expected_id != actual_id: + if "*" in expected_id or "?" in expected_id: + regex = ( + re.escape(expected_id).replace("\\*", ".*").replace("\\?", ".") + + "$" + ) + if not re.match(regex, actual_id): + return False + else: + return False + return True + + def _should_process_interface(self, interface_name): + expected_interface_name = self._common_handler_args.interface_name + + # if no filter is specified, then process all interfaces + if not expected_interface_name: + return True + + # only process if the expected and actual interface name match + return expected_interface_name == interface_name + + def _should_process_module(self, module_id): + expected_module_id = self._common_handler_args.module_id + return self._perform_id_match(expected_module_id, module_id) diff --git a/azext_iot/monitor/models/__init__.py b/azext_iot/monitor/models/__init__.py new file mode 100644 index 000000000..55614acbf --- /dev/null +++ b/azext_iot/monitor/models/__init__.py @@ -0,0 +1,5 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/azext_iot/monitor/models/arguments.py b/azext_iot/monitor/models/arguments.py new file mode 100644 index 000000000..cbe0ef19a --- /dev/null +++ b/azext_iot/monitor/models/arguments.py @@ -0,0 +1,77 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azure.cli.core.commands import AzCliCommand +from azext_iot.common.utility import init_monitoring +from azext_iot.monitor.models.enum import Severity + + +class TelemetryArguments: + def __init__( + self, + cmd: AzCliCommand, + timeout: int, + properties: list, + enqueued_time: int, + repair: bool, + yes: bool, + ): + (enqueued_time, unique_properties, timeout_ms, output) = init_monitoring( + cmd=cmd, + timeout=timeout, + properties=properties, + enqueued_time=enqueued_time, + repair=repair, + yes=yes, + ) + self.output = output + self.timeout = timeout_ms + self.properties = unique_properties + self.enqueued_time = enqueued_time + + +class CommonParserArguments: + def __init__( + self, properties: list = None, content_type="", + ): + self.properties = properties or [] + self.content_type = content_type or "" + + +class CommonHandlerArguments: + def __init__( + self, + output: str, + common_parser_args: CommonParserArguments, + devices: list = None, + device_id="", + interface_name="", + module_id="", + ): + self.output = output + self.devices = devices or [] + self.device_id = device_id or "" + self.interface_name = interface_name or "" + self.module_id = module_id or "" + self.common_parser_args = common_parser_args + + +class CentralHandlerArguments: + def __init__( + self, + duration: int, + max_messages: int, + common_handler_args: CommonHandlerArguments, + style="json", + minimum_severity=Severity.warning, + progress_interval=5, + ): + self.duration = duration + self.max_messages = max_messages + self.minimum_severity = minimum_severity + self.progress_interval = progress_interval + self.style = style + self.common_handler_args = common_handler_args diff --git a/azext_iot/tests/iothub/jobs/conftest.py b/azext_iot/monitor/models/enum.py similarity index 58% rename from azext_iot/tests/iothub/jobs/conftest.py rename to azext_iot/monitor/models/enum.py index f7effeffe..691b916e6 100644 --- a/azext_iot/tests/iothub/jobs/conftest.py +++ b/azext_iot/monitor/models/enum.py @@ -5,15 +5,10 @@ # -------------------------------------------------------------------------------------------- -import pytest +from enum import IntEnum -from ...conftest import mock_target -path_ghcs = "azext_iot.iothub.providers.base.get_iot_hub_connection_string" - - -@pytest.fixture() -def fixture_ghcs(mocker): - ghcs = mocker.patch(path_ghcs) - ghcs.return_value = mock_target - return ghcs +class Severity(IntEnum): + info = 1 + warning = 2 + error = 3 diff --git a/azext_iot/monitor/models/target.py b/azext_iot/monitor/models/target.py new file mode 100644 index 000000000..bb9746ac2 --- /dev/null +++ b/azext_iot/monitor/models/target.py @@ -0,0 +1,23 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +class Target: + def __init__( + self, + hostname: str, + path: str, + partitions: list, + auth, # : uamqp.authentication.SASTokenAsync, + ): + self.hostname = hostname + self.path = path + self.auth = auth + self.partitions = partitions + self.consumer_group = None + + def add_consumer_group(self, consumer_group: str): + self.consumer_group = consumer_group diff --git a/azext_iot/monitor/parsers/__init__.py b/azext_iot/monitor/parsers/__init__.py new file mode 100644 index 000000000..55614acbf --- /dev/null +++ b/azext_iot/monitor/parsers/__init__.py @@ -0,0 +1,5 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/azext_iot/monitor/parsers/central_parser.py b/azext_iot/monitor/parsers/central_parser.py new file mode 100644 index 000000000..f5a8c4686 --- /dev/null +++ b/azext_iot/monitor/parsers/central_parser.py @@ -0,0 +1,165 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import re + +from uamqp.message import Message + +from azext_iot.central.providers import ( + CentralDeviceProvider, + CentralDeviceTemplateProvider, +) +from azext_iot.central.models.template import Template +from azext_iot.monitor.parsers import strings +from azext_iot.monitor.central_validator import validate, extract_schema_type +from azext_iot.monitor.models.arguments import CommonParserArguments +from azext_iot.monitor.models.enum import Severity +from azext_iot.monitor.parsers.common_parser import CommonParser + + +class CentralParser(CommonParser): + def __init__( + self, + message: Message, + common_parser_args: CommonParserArguments, + central_device_provider: CentralDeviceProvider, + central_template_provider: CentralDeviceTemplateProvider, + ): + super(CentralParser, self).__init__( + message=message, common_parser_args=common_parser_args + ) + self._central_device_provider = central_device_provider + self._central_template_provider = central_template_provider + self._template_id = None + + def _add_central_issue(self, severity: Severity, details: str): + self.issues_handler.add_central_issue( + severity=severity, + details=details, + message=self._message, + device_id=self.device_id, + template_id=self._template_id, + ) + + def parse_message(self) -> dict: + parsed_message = super(CentralParser, self).parse_message() + + payload = parsed_message["event"]["payload"] + + self._perform_static_validations(payload=payload) + + # disable dynamic validations until Microservices work is figured out + self._perform_dynamic_validations(payload=payload) + + return parsed_message + + # Static validations should only need information present in the payload + # i.e. there should be no need for network calls + def _perform_static_validations(self, payload: dict): + # if its not a dictionary, something else went wrong with parsing + if not isinstance(payload, dict): + return + + self._validate_field_names(payload=payload) + + def _validate_field_names(self, payload: dict): + # source: + # https://github.com/Azure/IoTPlugandPlay/tree/master/DTDL + regex = "^[a-zA-Z_][a-zA-Z0-9_]*$" + + # if a field name does not match the above regex, it is an invalid field name + invalid_field_names = [ + field_name + for field_name in payload.keys() + if not re.search(regex, field_name) + ] + if invalid_field_names: + details = strings.invalid_field_name(invalid_field_names) + self._add_issue(severity=Severity.error, details=details) + + # Dynamic validations should need data external to the payload + # e.g. device template + def _perform_dynamic_validations(self, payload: dict): + # if the payload is not a dictionary some other parsing error occurred + if not isinstance(payload, dict): + return + + template = self._get_template() + + if not isinstance(template, Template): + return + + # if component name is not defined then data should be mapped to root/inherited interfaces + if not self.component_name: + self._validate_payload( + payload=payload, template=template, is_component=False + ) + return + + if not template.components: + # template does not have any valid components + details = strings.invalid_component_name(self.component_name, list()) + self._add_central_issue(severity=Severity.warning, details=details) + return + + # if component name is defined check to see if its a valid name + if self.component_name not in template.components: + details = strings.invalid_component_name( + self.component_name, list(template.components.keys()) + ) + self._add_central_issue(severity=Severity.warning, details=details) + return + + # if component name is valid check to see if payload is valid + self._validate_payload(payload=payload, template=template, is_component=True) + + def _get_template(self): + try: + device = self._central_device_provider.get_device(self.device_id) + template = self._central_template_provider.get_device_template( + device.instance_of + ) + self._template_id = template.id + return template + except Exception as e: + details = strings.device_template_not_found(e) + self._add_central_issue(severity=Severity.error, details=details) + + # currently validates: + # 1) primitive types match (e.g. boolean is indeed bool etc) + # 2) names match (i.e. Humidity vs humidity etc) + def _validate_payload(self, payload: dict, template: Template, is_component: bool): + name_miss = [] + for telemetry_name, telemetry in payload.items(): + schema = template.get_schema( + name=telemetry_name, + identifier=self.component_name, + is_component=is_component, + ) + if not schema: + name_miss.append(telemetry_name) + else: + self._process_telemetry(telemetry_name, schema, telemetry) + + if name_miss: + if is_component: + details = strings.invalid_field_name_component_mismatch_template( + name_miss, template.component_schema_names + ) + else: + details = strings.invalid_field_name_mismatch_template( + name_miss, template.schema_names, + ) + self._add_central_issue(severity=Severity.warning, details=details) + + def _process_telemetry(self, telemetry_name: str, schema, telemetry): + expected_type = extract_schema_type(schema) + is_payload_valid = validate(schema, telemetry) + if expected_type and not is_payload_valid: + details = strings.invalid_primitive_schema_mismatch_template( + telemetry_name, expected_type, telemetry + ) + self._add_central_issue(severity=Severity.error, details=details) diff --git a/azext_iot/monitor/parsers/common_parser.py b/azext_iot/monitor/parsers/common_parser.py new file mode 100644 index 000000000..1ddc22e9c --- /dev/null +++ b/azext_iot/monitor/parsers/common_parser.py @@ -0,0 +1,214 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +import re + +from uamqp.message import Message + +from azext_iot.common.utility import parse_entity, unicode_binary_map +from azext_iot.monitor.base_classes import AbstractBaseParser +from azext_iot.monitor.parsers import strings +from azext_iot.monitor.models.arguments import CommonParserArguments +from azext_iot.monitor.models.enum import Severity +from azext_iot.monitor.parsers.issue import IssueHandler + +DEVICE_ID_IDENTIFIER = b"iothub-connection-device-id" +MODULE_ID_IDENTIFIER = b"iothub-connection-module-id" +INTERFACE_NAME_IDENTIFIER_V1 = b"iothub-interface-name" +INTERFACE_NAME_IDENTIFIER_V2 = b"dt-dataschema" +COMPONENT_NAME_IDENTIFIER = b"dt-subject" + + +class CommonParser(AbstractBaseParser): + def __init__(self, message: Message, common_parser_args: CommonParserArguments): + self.issues_handler = IssueHandler() + self._common_parser_args = common_parser_args + self._message = message + self.device_id = "" # need to default + self.device_id = self._parse_device_id(message) + self.module_id = self._parse_module_id(message) + self.interface_name = self._parse_interface_name(message) + self.component_name = self._parse_component_name(message) + + def parse_message(self) -> dict: + """ + Parses an AMQP based IoT Hub telemetry event. + + """ + + message = self._message + properties = self._common_parser_args.properties + content_type = self._common_parser_args.content_type + + event = {} + event["origin"] = self.device_id + event["module"] = self.module_id + event["interface"] = self.interface_name + event["component"] = self.component_name + + if not properties: + properties = [] # guard against None being passed in + + system_properties = self._parse_system_properties(message) + + self._parse_content_encoding(message, system_properties) + + content_type = self._parse_content_type(content_type, system_properties) + + if properties: + event["properties"] = {} + + if "anno" in properties or "all" in properties: + annotations = self._parse_annotations(message) + event["annotations"] = annotations + + if system_properties and ("sys" in properties or "all" in properties): + event["properties"]["system"] = system_properties + + if "app" in properties or "all" in properties: + application_properties = self._parse_application_properties(message) + event["properties"]["application"] = application_properties + + payload = self._parse_payload(message, content_type) + + event["payload"] = payload + + event_source = {"event": event} + + return event_source + + def _add_issue(self, severity: Severity, details: str): + self.issues_handler.add_issue( + severity=severity, + details=details, + message=self._message, + device_id=self.device_id, + ) + + def _parse_device_id(self, message: Message) -> str: + try: + return str(message.annotations.get(DEVICE_ID_IDENTIFIER), "utf8") + except Exception: + details = strings.unknown_device_id() + self._add_issue(severity=Severity.error, details=details) + return "" + + def _parse_module_id(self, message: Message) -> str: + try: + return str(message.annotations.get(MODULE_ID_IDENTIFIER), "utf8") + except Exception: + # a message not containing an module name is expected for non-edge devices + # so there's no "issue" to log here + return "" + + def _parse_interface_name(self, message: Message) -> str: + try: + # Grab either the DTDL v1 or v2 amqp interface identifier. + # It's highly unlikely both will be present at the same time + # as they reflect different versions of a Plug & Play device. + target_interface = message.annotations.get( + INTERFACE_NAME_IDENTIFIER_V1 + ) or message.annotations.get(INTERFACE_NAME_IDENTIFIER_V2) + return str(target_interface, "utf8") + except Exception: + # a message not containing an interface name is expected for non-pnp devices + # so there's no "issue" to log here + return "" + + def _parse_component_name(self, message: Message) -> str: + try: + return str(message.annotations.get(COMPONENT_NAME_IDENTIFIER), "utf8") + except Exception: + return "" + + def _parse_system_properties(self, message: Message): + try: + return unicode_binary_map(parse_entity(message.properties, True)) + except Exception: + details = strings.invalid_system_properties() + self._add_issue(severity=Severity.warning, details=details) + return {} + + def _parse_content_encoding(self, message: Message, system_properties) -> str: + content_encoding = "" + + if "content_encoding" in system_properties: + content_encoding = system_properties["content_encoding"] + + if not content_encoding: + details = strings.invalid_encoding_none_found() + self._add_issue(severity=Severity.warning, details=details) + return None + + if "utf-8" not in content_encoding.lower(): + details = strings.invalid_encoding(content_encoding.lower()) + self._add_issue(severity=Severity.warning, details=details) + return None + + return content_encoding + + def _parse_content_type( + self, expected_content_type: str, system_properties: dict + ) -> str: + actual_content_type = system_properties.get("content_type", "") + + # Device data is not expected to be of a certain type + # Continue parsing per rules that the device is sending + if not expected_content_type: + return actual_content_type.lower() + + # Device is expected to send data in a certain format. + # Data from device implies the data is in an incorrect format. + # Log the issue, and continue parsing as if device is in expected format. + if actual_content_type.lower() != expected_content_type.lower(): + details = strings.content_type_mismatch( + actual_content_type, expected_content_type + ) + self._add_issue(severity=Severity.warning, details=details) + return expected_content_type.lower() + + return actual_content_type + + def _parse_annotations(self, message: Message): + try: + return unicode_binary_map(message.annotations) + except Exception: + details = strings.invalid_annotations() + self._add_issue(severity=Severity.warning, details=details) + return {} + + def _parse_application_properties(self, message: Message): + try: + return unicode_binary_map(message.application_properties) + except Exception: + details = strings.invalid_application_properties() + self._add_issue(severity=Severity.warning, details=details) + return {} + + def _parse_payload(self, message: Message, content_type): + payload = "" + data = message.get_data() + + if data: + payload = str(next(data), "utf8") + + if "application/json" in content_type.lower(): + return self._try_parse_json(payload) + + return payload + + def _try_parse_json(self, payload): + result = payload + try: + regex = r"(\\r\\n)+|\\r+|\\n+" + payload_no_white_space = re.compile(regex).sub("", payload) + result = json.loads(payload_no_white_space) + except Exception: + details = strings.invalid_json() + self._add_issue(severity=Severity.error, details=details) + + return result diff --git a/azext_iot/monitor/parsers/issue.py b/azext_iot/monitor/parsers/issue.py new file mode 100644 index 000000000..266b4203c --- /dev/null +++ b/azext_iot/monitor/parsers/issue.py @@ -0,0 +1,130 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from typing import List +from knack.log import get_logger + +from azext_iot.monitor.models.enum import Severity + +logger = get_logger(__name__) + + +class Issue: + def __init__(self, severity: Severity, details: str, message, device_id=""): + self.severity = severity + self.details = details + self.device_id = device_id + self.message = str(message) + + if not self.device_id: + self.device_id = "Unknown" + + def log(self): + to_log = "[{}] [DeviceId: {}] {}\n".format( + self.severity.name.upper(), self.device_id, self.details + ) + + self._log(to_log) + + def _log(self, to_log: str): + if self.severity == Severity.info: + logger.info(to_log) + + if self.severity == Severity.warning: + logger.warning(to_log) + + if self.severity == Severity.error: + logger.error(to_log) + + def json_repr(self): + json_repr = vars(self) + json_repr["severity"] = self.severity.name + return json_repr + + +class CentralIssue(Issue): + def __init__( + self, severity: Severity, details: str, message, device_id="", template_id="" + ): + super(CentralIssue, self).__init__(severity, details, message, device_id) + self.template_id = template_id + + if not self.template_id: + self.template_id = "Unknown" + + def log(self): + to_log = "[{}] [DeviceId: {}] [TemplateId: {}] {}\n".format( + self.severity.name.upper(), self.device_id, self.template_id, self.details + ) + + self._log(to_log) + + +class IssueHandler: + def __init__(self): + self._issues = [] + + def add_issue(self, severity: Severity, details: str, message, device_id=""): + issue = Issue( + severity=severity, details=details, message=message, device_id=device_id + ) + self._issues.append(issue) + + def add_central_issue( + self, severity: Severity, details: str, message, device_id="", template_id="" + ): + issue = CentralIssue( + severity=severity, + details=details, + message=message, + device_id=device_id, + template_id=template_id, + ) + self._issues.append(issue) + + def get_all_issues(self) -> List[Issue]: + return self._issues + + def get_issues_with_severity(self, severity: Severity) -> List[Issue]: + """ + arguments: + severity: Severity + returns: + all issues where severity equal specified severity + + example: + issue_handler.get_issues_with_severity(Severity.info) + returns all issues classified as "info" + """ + return [issue for issue in self._issues if issue.severity == severity] + + def get_issues_with_minimum_severity(self, severity: Severity) -> List[Issue]: + """ + arguments: + severity: Severity + returns: + all issues where severity >= specified severity + + example: + issue_handler.get_issues_with_minimum_severity(Severity.warning) + returns all issues classified as "warning" and "error" + "info" will not be included + """ + return [issue for issue in self._issues if issue.severity >= severity] + + def get_issues_with_maximum_severity(self, severity: Severity) -> List[Issue]: + """ + arguments: + severity: Severity + returns: + all issues where severity <= specified severity + + example: + issue_handler.get_issues_with_maximum_severity(Severity.warning) + returns all issues classified as "warning" and "info" + "error" will not be included + """ + return [issue for issue in self._issues if issue.severity <= severity] diff --git a/azext_iot/monitor/parsers/strings.py b/azext_iot/monitor/parsers/strings.py new file mode 100644 index 000000000..48123456b --- /dev/null +++ b/azext_iot/monitor/parsers/strings.py @@ -0,0 +1,151 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +SUPPORTED_ENCODING = ["utf-8"] +SUPPORTED_FIELD_NAME_CHARS = ["a-z", "A-Z", "0-9", "_"] +SUPPORTED_CONTENT_TYPE = ["application/json"] +SUPPORTED_MESSAGE_HEADERS = [] + + +def unknown_device_id(): # error + return "Device ID not found in message" + + +def invalid_json(): # error + return "Invalid JSON format." + + +def invalid_encoding(encoding: str): # warning + return "Encoding type '{}' is not supported. Expected encoding '{}'.".format( + encoding, SUPPORTED_ENCODING + ) + + +def invalid_field_name(invalid_field_names: list): # error + return ( + "Invalid characters detected. Invalid field names: '{}'. " + "Allowed characters: {}." + ).format(invalid_field_names, SUPPORTED_FIELD_NAME_CHARS) + + +def invalid_pnp_property_not_value_wrapped(): + raise NotImplementedError() + + +def invalid_non_pnp_field_name_duplicate(): + raise NotImplementedError() + + +def content_type_mismatch( + actual_content_type: str, expected_content_type: str +): # warning + return "Content type '{}' is not supported. Expected Content type is '{}'.".format( + actual_content_type, expected_content_type + ) + + +def invalid_custom_headers(): + return ( + "Custom message headers are not supported in IoT Central. " + "All the custom message headers will be dropped. " + "Supported message headers: '{}'." + ).format(SUPPORTED_MESSAGE_HEADERS) + + +# warning +def invalid_component_name(component_name: str, allowed_components: list): + return ( + "Device is specifying a component that is unknown. Device specified component: '{}'. Allowed components: '{}'." + ).format(component_name, allowed_components) + + +# warning +def invalid_field_name_mismatch_template( + unmodeled_capabilities: list, modeled_capabilities: list +): + return ( + "Device is sending data that has not been defined in the device template. " + "Following capabilities have NOT been defined in the device template '{}'. " + "Following capabilities have been defined in the device template (grouped by interface) '{}'. " + ).format(unmodeled_capabilities, modeled_capabilities) + + +# warning +def invalid_field_name_component_mismatch_template( + unmodeled_capabilities: list, modeled_capabilities: list +): + return ( + "Device is sending data that has not been defined in the device template. " + "Following capabilities have NOT been defined in the device template '{}'. " + "Following capabilities have been defined in the device template (grouped by components) '{}'. " + ).format(unmodeled_capabilities, modeled_capabilities) + + +# warning +def duplicate_property_name(duplicate_prop_name, interfaces: list): + return ( + "Duplicate property: '{}' found under following interfaces {} in the device model. " + "Either provide the interface name as part of the device payload or make the propery name unique in the device model" + ).format(duplicate_prop_name, interfaces) + + +# error +def invalid_primitive_schema_mismatch_template(field_name: str, data_type: str, data): + return ( + "Datatype of telemetry field '{}' does not match the datatype {}. Data sent by the device : {}. " + "For more information, see: https://aka.ms/iotcentral-payloads" + ).format(field_name, data_type, data,) + + +# to remove +def invalid_interface_name_not_found(): + return "Interface name not found." + + +# to remove +def invalid_interface_name_mismatch( + expected_interface_name: str, actual_interface_name: str +): + return "Inteface name mismatch. Expected: {}, Actual: {}.".format( + expected_interface_name, actual_interface_name + ) + + +# warning; downgrade to info if needed +def invalid_system_properties(): + return "Failed to parse system properties." + + +# warning +def invalid_encoding_none_found(): + return ( + "No encoding found. Expected encoding 'utf-8' to be present in message header." + ) + + +# warning +def invalid_encoding_missing(): + return "Content type not found in system properties." + + +# warning +def invalid_annotations(): + return "Unable to decode message.annotations." + + +# warning +def invalid_application_properties(): + return "Unable to decode message.application_properties." + + +# error +def device_template_not_found(error: Exception): + return "Error retrieving template '{}'. Please try again later.".format(error) + + +# error +def invalid_template_extract_schema_failed(template: dict): + return "Unable to extract device schema from template '{}'.".format(template) diff --git a/azext_iot/monitor/property.py b/azext_iot/monitor/property.py new file mode 100644 index 000000000..b91a7e889 --- /dev/null +++ b/azext_iot/monitor/property.py @@ -0,0 +1,220 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import datetime +import isodate +import time +from azext_iot.monitor.parsers import strings +from azext_iot.monitor.models.enum import Severity +from azext_iot.constants import ( + CENTRAL_ENDPOINT, + DEVICETWIN_POLLING_INTERVAL_SEC, + DEVICETWIN_MONITOR_TIME_SEC, + PNP_DTDLV2_COMPONENT_MARKER, +) + +from azext_iot.central.models.devicetwin import DeviceTwin, Property +from azext_iot.central.providers import ( + CentralDeviceProvider, + CentralDeviceTemplateProvider, + CentralDeviceTwinProvider, +) +from azext_iot.monitor.parsers.issue import IssueHandler + + +class PropertyMonitor: + def __init__( + self, + cmd, + app_id: str, + device_id: str, + token: str, + central_dns_suffix=CENTRAL_ENDPOINT, + ): + self._cmd = cmd + self._app_id = app_id + self._device_id = device_id + self._token = token + self._central_dns_suffix = central_dns_suffix + self._device_twin_provider = CentralDeviceTwinProvider( + cmd=self._cmd, + app_id=self._app_id, + token=self._token, + device_id=self._device_id, + ) + self._central_device_provider = CentralDeviceProvider( + cmd=self._cmd, app_id=self._app_id, token=self._token + ) + self._central_template_provider = CentralDeviceTemplateProvider( + cmd=self._cmd, app_id=self._app_id, token=self._token + ) + self._template = self._get_device_template() + + def _compare_properties(self, prev_prop: Property, prop: Property): + if prev_prop.version == prop.version: + return + + changes = { + key: self._changed_props(prop.props[key], prop.metadata[key], key,) + for key, val in prop.metadata.items() + if self._is_relevant(key, val) + } + + return changes + + def _is_relevant(self, key, val): + if key in {"$lastUpdated", "$lastUpdatedVersion"}: + return False + + updated_within = datetime.datetime.now() - datetime.timedelta( + seconds=DEVICETWIN_MONITOR_TIME_SEC + ) + + last_updated = isodate.parse_datetime(val["$lastUpdated"]) + return last_updated.timestamp() >= updated_within.timestamp() + + def _changed_props(self, prop, metadata, property_name): + + # not an interface - whole thing is change log + if not self._is_component(prop): + return prop + + # iterate over properties in the component + # if the property is not an exact match for what is present in the previous set of properties + # track it as a change + diff = { + key: prop[key] + for key, val in metadata.items() + if self._is_relevant(key, val) + } + return diff + + def _is_component(self, prop): + return type(prop) == dict and prop.get(PNP_DTDLV2_COMPONENT_MARKER) == "c" + + def _validate_payload(self, changes, minimum_severity): + for value in changes: + issues = self._validate_payload_against_entities( + changes[value], value, minimum_severity + ) + for issue in issues: + issue.log() + + def _validate_payload_against_entities(self, payload: dict, name, minimum_severity): + name_miss = [] + issues_handler = IssueHandler() + + if not self._is_component(payload): + # update is not part of a component check under interfaces + schema = self._template.get_schema(name=name) + if not schema: + name_miss.append(name) + details = strings.invalid_field_name_mismatch_template( + name_miss, self._template.schema_names + ) + + interfaces_with_specified_property = self._template._get_interface_list_property( + name + ) + + if len(interfaces_with_specified_property) > 1: + details = strings.duplicate_property_name( + name, interfaces_with_specified_property + ) + issues_handler.add_central_issue( + severity=Severity.warning, + details=details, + message=None, + device_id=self._device_id, + template_id=self._template.id, + ) + else: + # Property update is part of a component perform additional validations under component list. + component_property_updates = [ + property_name + for property_name in payload + if property_name != PNP_DTDLV2_COMPONENT_MARKER + ] + for property_name in component_property_updates: + schema = self._template.get_schema( + name=property_name, identifier=name, is_component=True + ) + if not schema: + name_miss.append(property_name) + details = strings.invalid_field_name_component_mismatch_template( + name_miss, self._template.component_schema_names + ) + + if name_miss: + issues_handler.add_central_issue( + severity=Severity.warning, + details=details, + message=None, + device_id=self._device_id, + template_id=self._template.id, + ) + + return issues_handler.get_issues_with_minimum_severity(minimum_severity) + + def _get_device_template(self): + device = self._central_device_provider.get_device(self._device_id) + template = self._central_template_provider.get_device_template( + device_template_id=device.instance_of, + central_dns_suffix=self._central_dns_suffix, + ) + return template + + def start_property_monitor(self,): + prev_twin = None + + while True: + raw_twin = self._device_twin_provider.get_device_twin( + central_dns_suffix=self._central_dns_suffix + ) + + twin = DeviceTwin(raw_twin) + if prev_twin: + change_d = self._compare_properties( + prev_twin.desired_property, twin.desired_property, + ) + change_r = self._compare_properties( + prev_twin.reported_property, twin.reported_property + ) + + if change_d: + print("Changes in desired properties:") + print("version :", twin.desired_property.version) + print(change_d) + + if change_r: + print("Changes in reported properties:") + print("version :", twin.reported_property.version) + print(change_r) + + time.sleep(DEVICETWIN_POLLING_INTERVAL_SEC) + + prev_twin = twin + + def start_validate_property_monitor(self, minimum_severity): + prev_twin = None + + while True: + + raw_twin = self._device_twin_provider.get_device_twin( + central_dns_suffix=self._central_dns_suffix + ) + + twin = DeviceTwin(raw_twin) + if prev_twin: + change_r = self._compare_properties( + prev_twin.reported_property, twin.reported_property + ) + if change_r: + self._validate_payload(change_r, minimum_severity) + + time.sleep(DEVICETWIN_POLLING_INTERVAL_SEC) + + prev_twin = twin diff --git a/azext_iot/monitor/telemetry.py b/azext_iot/monitor/telemetry.py new file mode 100644 index 000000000..c83f494a8 --- /dev/null +++ b/azext_iot/monitor/telemetry.py @@ -0,0 +1,188 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import asyncio +import sys +import uamqp + +from uuid import uuid4 +from knack.log import get_logger +from typing import List +from azext_iot.constants import VERSION, USER_AGENT +from azext_iot.monitor.models.target import Target +from azext_iot.monitor.utility import get_loop + +logger = get_logger(__name__) +DEBUG = False + + +def start_single_monitor( + target: Target, + enqueued_time_utc, + on_start_string: str, + on_message_received, + timeout=0, +): + """ + :param on_message_received: + A callback to process messages as they arrive from the service. + It takes a single argument, a ~uamqp.message.Message object. + """ + return start_multiple_monitors( + targets=[target], + enqueued_time_utc=enqueued_time_utc, + on_start_string=on_start_string, + on_message_received=on_message_received, + timeout=timeout, + ) + + +def start_multiple_monitors( + targets: List[Target], + on_start_string: str, + enqueued_time_utc, + on_message_received, + timeout=0, +): + """ + :param on_message_received: + A callback to process messages as they arrive from the service. + It takes a single argument, a ~uamqp.message.Message object. + """ + coroutines = [ + _initiate_event_monitor( + target=target, + enqueued_time_utc=enqueued_time_utc, + on_message_received=on_message_received, + timeout=timeout, + ) + for target in targets + ] + + loop = get_loop() + + future = asyncio.gather(*coroutines, loop=loop, return_exceptions=True) + result = None + + try: + print(on_start_string, flush=True) + future.add_done_callback(lambda future: _stop_and_suppress_eloop(loop)) + result = loop.run_until_complete(future) + except KeyboardInterrupt: + print("Stopping event monitor...", flush=True) + for t in asyncio.Task.all_tasks(): # pylint: disable=no-member + t.cancel() + loop.run_forever() + finally: + if result: + errors = result[0] + if errors and errors[0]: + logger.debug(errors) + raise RuntimeError(errors[0]) + + +async def _initiate_event_monitor( + target: Target, enqueued_time_utc, on_message_received, timeout=0 +): + if not target.partitions: + logger.debug("No Event Hub partitions found to listen on.") + return + + coroutines = [] + + async with uamqp.ConnectionAsync( + target.hostname, + sasl=target.auth, + debug=DEBUG, + container_id=_get_container_id(), + properties=_get_conn_props(), + ) as conn: + for p in target.partitions: + coroutines.append( + _monitor_events( + target=target, + connection=conn, + partition=p, + enqueued_time_utc=enqueued_time_utc, + on_message_received=on_message_received, + timeout=timeout, + ) + ) + return await asyncio.gather(*coroutines, return_exceptions=True) + + +async def _monitor_events( + target: Target, + connection, + partition, + enqueued_time_utc, + on_message_received, + timeout=0, +): + source = uamqp.address.Source( + "amqps://{}/{}/ConsumerGroups/{}/Partitions/{}".format( + target.hostname, target.path, target.consumer_group, partition + ) + ) + source.set_filter( + bytes( + "amqp.annotation.x-opt-enqueuedtimeutc > " + str(enqueued_time_utc), "utf8" + ) + ) + + exp_cancelled = False + receive_client = uamqp.ReceiveClientAsync( + source, + auth=target.auth, + timeout=timeout, + prefetch=0, + client_name=_get_container_id(), + debug=DEBUG, + ) + + try: + if connection: + await receive_client.open_async(connection=connection) + + async for msg in receive_client.receive_messages_iter_async(): + on_message_received(msg) + + except asyncio.CancelledError: + exp_cancelled = True + await receive_client.close_async() + except uamqp.errors.LinkDetach as ld: + if isinstance(ld.description, bytes): + ld.description = str(ld.description, "utf8") + raise RuntimeError(ld.description) + except KeyboardInterrupt: + logger.info("Keyboard interrupt, closing monitor on partition %s", partition) + exp_cancelled = True + await receive_client.close_async() + raise + finally: + if not exp_cancelled: + await receive_client.close_async() + logger.info("Closed monitor on partition %s", partition) + + +def _stop_and_suppress_eloop(loop): + try: + loop.stop() + except Exception: + pass + + +def _get_conn_props(): + return { + "product": USER_AGENT, + "version": VERSION, + "framework": "Python {}.{}.{}".format(*sys.version_info[0:3]), + "platform": sys.platform, + } + + +def _get_container_id(): + return "{}/{}".format(USER_AGENT, str(uuid4())) diff --git a/azext_iot/monitor/utility.py b/azext_iot/monitor/utility.py new file mode 100644 index 000000000..ada95186a --- /dev/null +++ b/azext_iot/monitor/utility.py @@ -0,0 +1,31 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import asyncio +from asyncio import AbstractEventLoop + + +def generate_on_start_string(device_id=None): + device_filter_txt = None + if device_id: + device_filter_txt = " filtering on device: {},".format(device_id) + + return "Starting event monitor,{} use ctrl-c to stop...".format( + device_filter_txt if device_filter_txt else "", + ) + + +def stop_monitor(): + raise KeyboardInterrupt() + + +def get_loop() -> AbstractEventLoop: + loop = asyncio.get_event_loop() + if loop.is_closed(): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + return loop diff --git a/azext_iot/operations/central.py b/azext_iot/operations/central.py deleted file mode 100644 index 2e0daa5be..000000000 --- a/azext_iot/operations/central.py +++ /dev/null @@ -1,48 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -from knack.util import CLIError -from azext_iot._factory import _bind_sdk -from azext_iot.common._azure import get_iot_hub_token_from_central_app_id -from azext_iot.common.shared import SdkType -from azext_iot.common.utility import (unpack_msrest_error, init_monitoring) -from azext_iot.common.sas_token_auth import BasicSasTokenAuthentication - - -def find_between(s, start, end): - return (s.split(start))[1].split(end)[0] - - -def iot_central_device_show(cmd, device_id, app_id, central_api_uri='api.azureiotcentral.com'): - sasToken = get_iot_hub_token_from_central_app_id(cmd, app_id, central_api_uri) - endpoint = find_between(sasToken, 'SharedAccessSignature sr=', '&sig=') - target = {'entity': endpoint} - auth = BasicSasTokenAuthentication(sas_token=sasToken) - service_sdk, errors = _bind_sdk(target, SdkType.service_sdk, auth=auth) - try: - return service_sdk.get_twin(device_id) - except errors.CloudError as e: - raise CLIError(unpack_msrest_error(e)) - - -def iot_central_monitor_events(cmd, app_id, device_id=None, consumer_group='$Default', timeout=300, enqueued_time=None, - repair=False, properties=None, yes=False, central_api_uri='api.azureiotcentral.com'): - - (enqueued_time, properties, timeout, output) = init_monitoring(cmd, timeout, properties, enqueued_time, repair, yes) - - import importlib - - events3 = importlib.import_module('azext_iot.operations.events3._events') - builders = importlib.import_module('azext_iot.operations.events3._builders') - - eventHubTarget = builders.EventTargetBuilder().build_central_event_hub_target(cmd, app_id, central_api_uri) - events3.executor(eventHubTarget, - consumer_group=consumer_group, - enqueued_time=enqueued_time, - properties=properties, - timeout=timeout, - device_id=device_id, - output=output) diff --git a/azext_iot/operations/digitaltwin.py b/azext_iot/operations/digitaltwin.py deleted file mode 100644 index 2946cb103..000000000 --- a/azext_iot/operations/digitaltwin.py +++ /dev/null @@ -1,253 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -from os.path import exists -from knack.util import CLIError -from azext_iot.constants import PNP_ENDPOINT -from azext_iot._factory import _bind_sdk -from azext_iot.common.shared import SdkType, ModelSourceType -from azext_iot.common._azure import get_iot_hub_connection_string -from azext_iot.common.utility import (shell_safe_json_parse, - read_file_content, - unpack_msrest_error) -from azext_iot.operations.pnp import (iot_pnp_interface_show, - iot_pnp_interface_list, - _validate_repository) -from azext_iot.operations.hub import _iot_hub_monitor_events - - -INTERFACE_KEY_NAME = 'urn_azureiot_ModelDiscovery_DigitalTwin' -INTERFACE_COMMAND = 'Command' -INTERFACE_PROPERTY = 'Property' -INTERFACE_TELEMETRY = 'Telemetry' -INTERFACE_MODELDEFINITION = 'urn_azureiot_ModelDiscovery_ModelDefinition' -INTERFACE_COMMANDNAME = 'getModelDefinition' - - -def iot_digitaltwin_interface_list(cmd, device_id, hub_name=None, resource_group_name=None, login=None): - device_default_interface = _iot_digitaltwin_interface_show(cmd, device_id, INTERFACE_KEY_NAME, - hub_name, resource_group_name, login) - result = _get_device_default_interface_dict(device_default_interface) - return {'interfaces': result} - - -def iot_digitaltwin_command_list(cmd, device_id, source_model, interface=None, schema=False, - repo_endpoint=PNP_ENDPOINT, repo_id=None, repo_login=None, - hub_name=None, resource_group_name=None, login=None): - result = [] - target_interfaces = [] - source_model = source_model.lower() - device_interfaces = _iot_digitaltwin_interface_list(cmd, device_id, hub_name, resource_group_name, login) - interface_list = _get_device_default_interface_dict(device_interfaces) - target_interface = next((item for item in interface_list if item['name'] == interface), None) - if interface and not target_interface: - raise CLIError('Target interface is not implemented by the device!') - - if interface: - target_interfaces.append(target_interface) - else: - target_interfaces = interface_list - - for entity in target_interfaces: - interface_result = {'name': entity['name'], 'urn_id': entity['urn_id'], 'commands': {}} - interface_commands = [] - found_commands = [] - if source_model == ModelSourceType.device.value.lower(): - found_commands = _device_interface_elements(cmd, device_id, entity['urn_id'], INTERFACE_COMMAND, - hub_name, resource_group_name, login) - else: - if source_model == ModelSourceType.private.value.lower(): - _validate_repository(repo_id, repo_login) - found_commands = _pnp_interface_elements(cmd, entity['urn_id'], INTERFACE_COMMAND, - repo_endpoint, repo_id, repo_login) - for command in found_commands: - command.pop('@type', None) - if schema: - interface_commands.append(command) - else: - interface_commands.append(command.get('name')) - interface_result['commands'] = interface_commands - result.append(interface_result) - return {'interfaces': result} - - -def iot_digitaltwin_properties_list(cmd, device_id, source_model, interface=None, schema=False, - repo_endpoint=PNP_ENDPOINT, repo_id=None, repo_login=None, - hub_name=None, resource_group_name=None, login=None): - result = [] - target_interfaces = [] - source_model = source_model.lower() - device_interfaces = _iot_digitaltwin_interface_list(cmd, device_id, hub_name, resource_group_name, login) - interface_list = _get_device_default_interface_dict(device_interfaces) - target_interface = next((item for item in interface_list if item['name'] == interface), None) - if interface and not target_interface: - raise CLIError('Target interface is not implemented by the device!') - - if interface: - target_interfaces.append(target_interface) - else: - target_interfaces = interface_list - - for entity in target_interfaces: - interface_result = {'name': entity['name'], 'urn_id': entity['urn_id'], 'properties': {}} - interface_properties = [] - found_properties = [] - if source_model == ModelSourceType.device.value.lower(): - found_properties = _device_interface_elements(cmd, device_id, entity['urn_id'], INTERFACE_PROPERTY, - hub_name, resource_group_name, login) - else: - if source_model == ModelSourceType.private.value.lower(): - _validate_repository(repo_id, repo_login) - found_properties = _pnp_interface_elements(cmd, entity['urn_id'], INTERFACE_PROPERTY, - repo_endpoint, repo_id, repo_login) - for prop in found_properties: - prop.pop('@type', None) - if schema: - interface_properties.append(prop) - else: - interface_properties.append(prop.get('name')) - interface_result['properties'] = interface_properties - result.append(interface_result) - return {'interfaces': result} - - -def iot_digitaltwin_invoke_command(cmd, interface, device_id, command_name, command_payload=None, - timeout=10, hub_name=None, resource_group_name=None, login=None): - device_interfaces = _iot_digitaltwin_interface_list(cmd, device_id, hub_name, resource_group_name, login) - interface_list = _get_device_default_interface_dict(device_interfaces) - - target_interface = next((item for item in interface_list if item['name'] == interface), None) - - if not target_interface: - raise CLIError('Target interface is not implemented by the device!') - - if command_payload: - if exists(command_payload): - command_payload = str(read_file_content(command_payload)) - - target_json = None - try: - target_json = shell_safe_json_parse(command_payload) - except ValueError: - pass - - if target_json or isinstance(target_json, bool): - command_payload = target_json - - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) - service_sdk, errors = _bind_sdk(target, SdkType.service_sdk) - try: - result = service_sdk.invoke_interface_command(device_id, - interface, - command_name, - command_payload, - connect_timeout_in_seconds=timeout, - response_timeout_in_seconds=timeout) - return result - except errors.CloudError as e: - raise CLIError(unpack_msrest_error(e)) - - -def iot_digitaltwin_property_update(cmd, interface_payload, device_id, - hub_name=None, resource_group_name=None, login=None): - if exists(interface_payload): - interface_payload = str(read_file_content(interface_payload)) - - target_json = None - try: - target_json = shell_safe_json_parse(interface_payload) - except ValueError: - pass - - if target_json: - interface_payload = target_json - - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) - service_sdk, errors = _bind_sdk(target, SdkType.service_sdk) - try: - result = service_sdk.update_interfaces(device_id, interfaces=interface_payload) - return result - except errors.CloudError as e: - raise CLIError(unpack_msrest_error(e)) - - -def iot_digitaltwin_monitor_events(cmd, hub_name=None, device_id=None, consumer_group='$Default', timeout=300, - enqueued_time=None, resource_group_name=None, yes=False, properties=None, repair=False, - login=None, content_type=None, device_query=None, interface=None): - _iot_hub_monitor_events(cmd=cmd, hub_name=hub_name, device_id=device_id, - consumer_group=consumer_group, timeout=timeout, enqueued_time=enqueued_time, - resource_group_name=resource_group_name, yes=yes, properties=properties, - repair=repair, login=login, content_type=content_type, device_query=device_query, - interface_name=interface, pnp_context=True) - - -def _iot_digitaltwin_interface_show(cmd, device_id, interface, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) - service_sdk, errors = _bind_sdk(target, SdkType.service_sdk) - try: - device_interface = service_sdk.get_interface(device_id, interface) - return device_interface - except errors.CloudError as e: - raise CLIError(unpack_msrest_error(e)) - - -def _iot_digitaltwin_interface_list(cmd, device_id, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) - service_sdk, errors = _bind_sdk(target, SdkType.service_sdk) - try: - device_interfaces = service_sdk.get_interfaces(device_id) - return device_interfaces - except errors.CloudError as e: - raise CLIError(unpack_msrest_error(e)) - - -def _get_device_default_interface_dict(device_default_interface): - interface = device_default_interface['interfaces'][INTERFACE_KEY_NAME] - result = [] - for k, v in interface['properties']['modelInformation']['reported']['value']['interfaces'].items(): - result.append({'name': k, "urn_id": v}) - return result - - -def _pnp_interface_elements(cmd, interface, target_type, repo_endpoint, repo_id, login): - interface_elements = [] - results = iot_pnp_interface_list(cmd, repo_endpoint, repo_id, interface, login=login) - if results: - interface_def = iot_pnp_interface_show(cmd, interface, repo_endpoint, repo_id, login) - interface_contents = interface_def.get('contents') - for content in interface_contents: - if isinstance(content.get('@type'), list) and target_type in content.get('@type'): - interface_elements.append(content) - elif content.get('@type') == target_type: - interface_elements.append(content) - return interface_elements - - -def _device_interface_elements(cmd, device_id, interface, target_type, hub_name, resource_group_name, login): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) - service_sdk, errors = _bind_sdk(target, SdkType.service_sdk) - interface_elements = [] - try: - payload = {'id': {}} - payload['id'] = interface - target_payload = shell_safe_json_parse(str(payload)) - interface_def = service_sdk.invoke_interface_command(device_id, - INTERFACE_MODELDEFINITION, - INTERFACE_COMMANDNAME, - target_payload) - if interface_def and interface_def.get('contents'): - interface_contents = interface_def.get('contents') - for content in interface_contents: - if isinstance(content.get('@type'), list) and target_type in content.get('@type'): - interface_elements.append(content) - elif content.get('@type') == target_type: - interface_elements.append(content) - return interface_elements - except errors.CloudError as e: - raise CLIError(unpack_msrest_error(e)) - except Exception: - # returning an empty collection to continue - return [] diff --git a/azext_iot/operations/dps.py b/azext_iot/operations/dps.py index dd98994f4..91fe91e89 100644 --- a/azext_iot/operations/dps.py +++ b/azext_iot/operations/dps.py @@ -6,170 +6,232 @@ from knack.log import get_logger from knack.util import CLIError -from azext_iot.common.shared import (SdkType, - AttestationType, - ReprovisionType, - AllocationType) +from azext_iot.common.shared import ( + SdkType, + AttestationType, + ReprovisionType, + AllocationType, +) from azext_iot.common._azure import get_iot_dps_connection_string from azext_iot.common.utility import shell_safe_json_parse from azext_iot.common.certops import open_certificate +from azext_iot.common.utility import compute_device_key from azext_iot.operations.generic import _execute_query -from azext_iot._factory import _bind_sdk -from azext_iot.sdk.dps.models.individual_enrollment import IndividualEnrollment -from azext_iot.sdk.dps.models.attestation_mechanism import AttestationMechanism -from azext_iot.sdk.dps.models.tpm_attestation import TpmAttestation -from azext_iot.sdk.dps.models.symmetric_key_attestation import SymmetricKeyAttestation -from azext_iot.sdk.dps.models.x509_attestation import X509Attestation -from azext_iot.sdk.dps.models.x509_certificates import X509Certificates -from azext_iot.sdk.dps.models.x509_certificate_with_info import X509CertificateWithInfo -from azext_iot.sdk.dps.models.initial_twin import InitialTwin -from azext_iot.sdk.dps.models.twin_collection import TwinCollection -from azext_iot.sdk.dps.models.initial_twin_properties import InitialTwinProperties -from azext_iot.sdk.dps.models.enrollment_group import EnrollmentGroup -from azext_iot.sdk.dps.models.x509_ca_references import X509CAReferences -from azext_iot.sdk.dps.models.reprovision_policy import ReprovisionPolicy -from azext_iot.sdk.dps.models import DeviceCapabilities +from azext_iot._factory import SdkResolver +from azext_iot.sdk.dps.service.models import ( + IndividualEnrollment, + CustomAllocationDefinition, + AttestationMechanism, + TpmAttestation, + SymmetricKeyAttestation, + X509Attestation, + X509Certificates, + X509CertificateWithInfo, + InitialTwin, + TwinCollection, + InitialTwinProperties, + EnrollmentGroup, + X509CAReferences, + ReprovisionPolicy, + DeviceCapabilities, + ProvisioningServiceErrorDetailsException, +) logger = get_logger(__name__) # DPS Enrollments + def iot_dps_device_enrollment_list(client, dps_name, resource_group_name, top=None): - from azext_iot.sdk.dps.models.query_specification import QuerySpecification + from azext_iot.sdk.dps.service.models.query_specification import QuerySpecification + target = get_iot_dps_connection_string(client, dps_name, resource_group_name) + try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) query_command = "SELECT *" - query = [QuerySpecification(query_command)] - return _execute_query(query, m_sdk.device_enrollment.query, top) - except errors.ProvisioningServiceErrorDetailsException as e: + query = [QuerySpecification(query=query_command)] + return _execute_query(query, sdk.query_individual_enrollments, top) + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) -def iot_dps_device_enrollment_get(client, enrollment_id, dps_name, resource_group_name): +def iot_dps_device_enrollment_get( + client, enrollment_id, dps_name, resource_group_name, show_keys=None +): target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) - return m_sdk.device_enrollment.get(enrollment_id) - except errors.ProvisioningServiceErrorDetailsException as e: + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) + + enrollment = sdk.get_individual_enrollment( + enrollment_id, raw=True + ).response.json() + if show_keys: + enrollment_type = enrollment["attestation"]["type"] + if enrollment_type == AttestationType.symmetricKey.value: + attestation = sdk.get_individual_enrollment_attestation_mechanism( + enrollment_id, raw=True + ).response.json() + enrollment["attestation"] = attestation + else: + logger.warn( + "--show-keys argument was provided, but requested enrollment has an attestation type of '{}'." + " Currently, --show-keys is only supported for symmetric key enrollments".format( + enrollment_type + ) + ) + return enrollment + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) -def iot_dps_device_enrollment_create(client, - enrollment_id, - attestation_type, - dps_name, - resource_group_name, - endorsement_key=None, - certificate_path=None, - secondary_certificate_path=None, - primary_key=None, - secondary_key=None, - device_id=None, - iot_hub_host_name=None, - initial_twin_tags=None, - initial_twin_properties=None, - provisioning_status=None, - reprovision_policy=None, - allocation_policy=None, - iot_hubs=None, - edge_enabled=False): +def iot_dps_device_enrollment_create( + client, + enrollment_id, + attestation_type, + dps_name, + resource_group_name, + endorsement_key=None, + certificate_path=None, + secondary_certificate_path=None, + primary_key=None, + secondary_key=None, + device_id=None, + iot_hub_host_name=None, + initial_twin_tags=None, + initial_twin_properties=None, + provisioning_status=None, + reprovision_policy=None, + allocation_policy=None, + iot_hubs=None, + edge_enabled=False, + webhook_url=None, + api_version=None +): target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) + if attestation_type == AttestationType.tpm.value: if not endorsement_key: - raise CLIError('Endorsement key is requried') - attestation = AttestationMechanism(AttestationType.tpm.value, TpmAttestation(endorsement_key)) + raise CLIError("Endorsement key is requried") + attestation = AttestationMechanism( + type=AttestationType.tpm.value, + tpm=TpmAttestation(endorsement_key=endorsement_key), + ) if attestation_type == AttestationType.x509.value: - attestation = _get_attestation_with_x509_client_cert(certificate_path, secondary_certificate_path) + attestation = _get_attestation_with_x509_client_cert( + certificate_path, secondary_certificate_path + ) if attestation_type == AttestationType.symmetricKey.value: - attestation = AttestationMechanism(AttestationType.symmetricKey.value, - None, - None, - SymmetricKeyAttestation(primary_key, secondary_key)) + attestation = AttestationMechanism( + type=AttestationType.symmetricKey.value, + symmetric_key=SymmetricKeyAttestation( + primary_key=primary_key, secondary_key=secondary_key + ), + ) reprovision = _get_reprovision_policy(reprovision_policy) initial_twin = _get_initial_twin(initial_twin_tags, initial_twin_properties) iot_hub_list = iot_hubs.split() if iot_hubs else iot_hubs - _validate_allocation_policy_for_enrollment(allocation_policy, iot_hub_host_name, iot_hub_list) + _validate_allocation_policy_for_enrollment( + allocation_policy, iot_hub_host_name, iot_hub_list, webhook_url, api_version + ) if iot_hub_host_name and allocation_policy is None: allocation_policy = AllocationType.static.value iot_hub_list = iot_hub_host_name.split() + custom_allocation_definition = ( + CustomAllocationDefinition(webhook_url=webhook_url, api_version=api_version) + if allocation_policy == AllocationType.custom.value + else None + ) capabilities = DeviceCapabilities(iot_edge=edge_enabled) - enrollment = IndividualEnrollment(enrollment_id, - attestation, - capabilities, - device_id, - None, - initial_twin, - None, - provisioning_status, - reprovision, - allocation_policy, - iot_hub_list) - return m_sdk.device_enrollment.create_or_update(enrollment_id, enrollment) - except errors.ProvisioningServiceErrorDetailsException as e: + enrollment = IndividualEnrollment( + registration_id=enrollment_id, + attestation=attestation, + capabilities=capabilities, + device_id=device_id, + initial_twin=initial_twin, + provisioning_status=provisioning_status, + reprovision_policy=reprovision, + allocation_policy=allocation_policy, + iot_hubs=iot_hub_list, + custom_allocation_definition=custom_allocation_definition, + ) + return sdk.create_or_update_individual_enrollment(enrollment_id, enrollment) + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) -def iot_dps_device_enrollment_update(client, - enrollment_id, - dps_name, - resource_group_name, - etag=None, - endorsement_key=None, - certificate_path=None, - secondary_certificate_path=None, - remove_certificate=None, - remove_secondary_certificate=None, - primary_key=None, - secondary_key=None, - device_id=None, - iot_hub_host_name=None, - initial_twin_tags=None, - initial_twin_properties=None, - provisioning_status=None, - reprovision_policy=None, - allocation_policy=None, - iot_hubs=None, - edge_enabled=None): +def iot_dps_device_enrollment_update( + client, + enrollment_id, + dps_name, + resource_group_name, + etag=None, + endorsement_key=None, + certificate_path=None, + secondary_certificate_path=None, + remove_certificate=None, + remove_secondary_certificate=None, + primary_key=None, + secondary_key=None, + device_id=None, + iot_hub_host_name=None, + initial_twin_tags=None, + initial_twin_properties=None, + provisioning_status=None, + reprovision_policy=None, + allocation_policy=None, + iot_hubs=None, + edge_enabled=None, + webhook_url=None, + api_version=None, +): target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) - enrollment_record = m_sdk.device_enrollment.get(enrollment_id) - # Verify etag - if etag and hasattr(enrollment_record, 'etag') and etag != enrollment_record.etag.replace('"', ''): - raise LookupError("enrollment etag doesn't match.") - if not etag: - etag = enrollment_record.etag.replace('"', '') + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) + enrollment_record = sdk.get_individual_enrollment(enrollment_id) + # Verify and update attestation information attestation_type = enrollment_record.attestation.type - _validate_arguments_for_attestation_mechanism(attestation_type, - endorsement_key, - certificate_path, - secondary_certificate_path, - remove_certificate, - remove_secondary_certificate, - primary_key, - secondary_key) + _validate_arguments_for_attestation_mechanism( + attestation_type, + endorsement_key, + certificate_path, + secondary_certificate_path, + remove_certificate, + remove_secondary_certificate, + primary_key, + secondary_key, + ) if attestation_type == AttestationType.tpm.value: if endorsement_key: enrollment_record.attestation.tpm.endorsement_key = endorsement_key elif attestation_type == AttestationType.x509.value: - enrollment_record.attestation = _get_updated_attestation_with_x509_client_cert(enrollment_record.attestation, - certificate_path, - secondary_certificate_path, - remove_certificate, - remove_secondary_certificate) + enrollment_record.attestation = _get_updated_attestation_with_x509_client_cert( + enrollment_record.attestation, + certificate_path, + secondary_certificate_path, + remove_certificate, + remove_secondary_certificate, + ) else: - enrollment_record.attestation = m_sdk.device_enrollment.attestation_mechanism_method(enrollment_id) + enrollment_record.attestation = sdk.get_individual_enrollment_attestation_mechanism( + enrollment_id + ) if primary_key: enrollment_record.attestation.symmetric_key.primary_key = primary_key if secondary_key: - enrollment_record.attestation.symmetric_key.secondary_key = secondary_key + enrollment_record.attestation.symmetric_key.secondary_key = ( + secondary_key + ) # Update enrollment information if iot_hub_host_name: enrollment_record.allocation_policy = AllocationType.static.value @@ -181,184 +243,269 @@ def iot_dps_device_enrollment_update(client, enrollment_record.provisioning_status = provisioning_status enrollment_record.registrationState = None if reprovision_policy: - enrollment_record.reprovision_policy = _get_reprovision_policy(reprovision_policy) - enrollment_record.initial_twin = _get_updated_inital_twin(enrollment_record, - initial_twin_tags, - initial_twin_properties) + enrollment_record.reprovision_policy = _get_reprovision_policy( + reprovision_policy + ) + enrollment_record.initial_twin = _get_updated_inital_twin( + enrollment_record, initial_twin_tags, initial_twin_properties + ) iot_hub_list = iot_hubs.split() if iot_hubs else iot_hubs - _validate_allocation_policy_for_enrollment(allocation_policy, iot_hub_host_name, iot_hub_list) + _validate_allocation_policy_for_enrollment( + allocation_policy, iot_hub_host_name, iot_hub_list, webhook_url, api_version + ) if allocation_policy: enrollment_record.allocation_policy = allocation_policy enrollment_record.iot_hubs = iot_hub_list enrollment_record.iot_hub_host_name = None - + if allocation_policy == AllocationType.custom.value: + enrollment_record.custom_allocation_definition = CustomAllocationDefinition( + webhook_url=webhook_url, api_version=api_version + ) if edge_enabled is not None: enrollment_record.capabilities = DeviceCapabilities(iot_edge=edge_enabled) - return m_sdk.device_enrollment.create_or_update(enrollment_id, enrollment_record, etag) - except errors.ProvisioningServiceErrorDetailsException as e: + return sdk.create_or_update_individual_enrollment( + enrollment_id, enrollment_record, if_match=(etag if etag else "*") + ) + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) -def iot_dps_device_enrollment_delete(client, enrollment_id, dps_name, resource_group_name): +def iot_dps_device_enrollment_delete( + client, enrollment_id, dps_name, resource_group_name, etag=None +): target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) - return m_sdk.device_enrollment.delete(enrollment_id) - except errors.ProvisioningServiceErrorDetailsException as e: + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) + + return sdk.delete_individual_enrollment(enrollment_id, if_match=(etag if etag else "*")) + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) # DPS Enrollments Group -def iot_dps_device_enrollment_group_list(client, dps_name, resource_group_name, top=None): - from azext_iot.sdk.dps.models.query_specification import QuerySpecification + +def iot_dps_device_enrollment_group_list( + client, dps_name, resource_group_name, top=None +): + from azext_iot.sdk.dps.service.models.query_specification import QuerySpecification + target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) query_command = "SELECT *" - query1 = [QuerySpecification(query_command)] - return _execute_query(query1, m_sdk.device_enrollment_group.query, top) - except errors.ProvisioningServiceErrorDetailsException as e: + query1 = [QuerySpecification(query=query_command)] + return _execute_query(query1, sdk.query_enrollment_groups, top) + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) -def iot_dps_device_enrollment_group_get(client, enrollment_id, dps_name, resource_group_name): +def iot_dps_device_enrollment_group_get( + client, enrollment_id, dps_name, resource_group_name, show_keys=None +): target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) - return m_sdk.device_enrollment_group.get(enrollment_id) - except errors.ProvisioningServiceErrorDetailsException as e: + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) + + enrollment_group = sdk.get_enrollment_group( + enrollment_id, raw=True + ).response.json() + if show_keys: + enrollment_type = enrollment_group["attestation"]["type"] + if enrollment_type == AttestationType.symmetricKey.value: + attestation = sdk.get_enrollment_group_attestation_mechanism( + enrollment_id, raw=True + ).response.json() + enrollment_group["attestation"] = attestation + else: + logger.warn( + "--show-keys argument was provided, but requested enrollment group has an attestation type of '{}'." + " Currently, --show-keys is only supported for symmetric key enrollment groups".format( + enrollment_type + ) + ) + return enrollment_group + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) -def iot_dps_device_enrollment_group_create(client, - enrollment_id, - dps_name, - resource_group_name, - certificate_path=None, - secondary_certificate_path=None, - root_ca_name=None, - secondary_root_ca_name=None, - primary_key=None, - secondary_key=None, - iot_hub_host_name=None, - initial_twin_tags=None, - initial_twin_properties=None, - provisioning_status=None, - reprovision_policy=None, - allocation_policy=None, - iot_hubs=None, - edge_enabled=False): +def iot_dps_device_enrollment_group_create( + client, + enrollment_id, + dps_name, + resource_group_name, + certificate_path=None, + secondary_certificate_path=None, + root_ca_name=None, + secondary_root_ca_name=None, + primary_key=None, + secondary_key=None, + iot_hub_host_name=None, + initial_twin_tags=None, + initial_twin_properties=None, + provisioning_status=None, + reprovision_policy=None, + allocation_policy=None, + iot_hubs=None, + edge_enabled=False, + webhook_url=None, + api_version=None, +): target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) + if not certificate_path and not secondary_certificate_path: if not root_ca_name and not secondary_root_ca_name: - attestation = AttestationMechanism(AttestationType.symmetricKey.value, - None, - None, - SymmetricKeyAttestation(primary_key, secondary_key)) + attestation = AttestationMechanism( + type=AttestationType.symmetricKey.value, + symmetric_key=SymmetricKeyAttestation( + primary_key=primary_key, secondary_key=secondary_key + ), + ) if certificate_path or secondary_certificate_path: if root_ca_name or secondary_root_ca_name: - raise CLIError('Please provide either certificate path or certficate name') - attestation = _get_attestation_with_x509_signing_cert(certificate_path, secondary_certificate_path) + raise CLIError( + "Please provide either certificate path or certficate name" + ) + attestation = _get_attestation_with_x509_signing_cert( + certificate_path, secondary_certificate_path + ) if root_ca_name or secondary_root_ca_name: if certificate_path or secondary_certificate_path: - raise CLIError('Please provide either certificate path or certficate name') - attestation = _get_attestation_with_x509_ca_cert(root_ca_name, secondary_root_ca_name) + raise CLIError( + "Please provide either certificate path or certficate name" + ) + attestation = _get_attestation_with_x509_ca_cert( + root_ca_name, secondary_root_ca_name + ) reprovision = _get_reprovision_policy(reprovision_policy) initial_twin = _get_initial_twin(initial_twin_tags, initial_twin_properties) iot_hub_list = iot_hubs.split() if iot_hubs else iot_hubs - _validate_allocation_policy_for_enrollment(allocation_policy, iot_hub_host_name, iot_hub_list) + _validate_allocation_policy_for_enrollment( + allocation_policy, iot_hub_host_name, iot_hub_list, webhook_url, api_version + ) if iot_hub_host_name and allocation_policy is None: allocation_policy = AllocationType.static.value iot_hub_list = iot_hub_host_name.split() + custom_allocation_definition = ( + CustomAllocationDefinition(webhook_url=webhook_url, api_version=api_version) + if allocation_policy == AllocationType.custom.value + else None + ) + capabilities = DeviceCapabilities(iot_edge=edge_enabled) - group_enrollment = EnrollmentGroup(enrollment_id, - attestation, - capabilities, - None, - initial_twin, - None, - provisioning_status, - reprovision, - allocation_policy, - iot_hub_list) - return m_sdk.device_enrollment_group.create_or_update(enrollment_id, group_enrollment) - except errors.ProvisioningServiceErrorDetailsException as e: + group_enrollment = EnrollmentGroup( + enrollment_group_id=enrollment_id, + attestation=attestation, + capabilities=capabilities, + initial_twin=initial_twin, + provisioning_status=provisioning_status, + reprovision_policy=reprovision, + allocation_policy=allocation_policy, + iot_hubs=iot_hub_list, + custom_allocation_definition=custom_allocation_definition, + ) + return sdk.create_or_update_enrollment_group(enrollment_id, group_enrollment) + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) -def iot_dps_device_enrollment_group_update(client, - enrollment_id, - dps_name, - resource_group_name, - etag=None, - certificate_path=None, - secondary_certificate_path=None, - root_ca_name=None, - secondary_root_ca_name=None, - remove_certificate=None, - remove_secondary_certificate=None, - primary_key=None, - secondary_key=None, - iot_hub_host_name=None, - initial_twin_tags=None, - initial_twin_properties=None, - provisioning_status=None, - reprovision_policy=None, - allocation_policy=None, - iot_hubs=None, - edge_enabled=None): +def iot_dps_device_enrollment_group_update( + client, + enrollment_id, + dps_name, + resource_group_name, + etag=None, + certificate_path=None, + secondary_certificate_path=None, + root_ca_name=None, + secondary_root_ca_name=None, + remove_certificate=None, + remove_secondary_certificate=None, + primary_key=None, + secondary_key=None, + iot_hub_host_name=None, + initial_twin_tags=None, + initial_twin_properties=None, + provisioning_status=None, + reprovision_policy=None, + allocation_policy=None, + iot_hubs=None, + edge_enabled=None, + webhook_url=None, + api_version=None, +): target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) - enrollment_record = m_sdk.device_enrollment_group.get(enrollment_id) - # Verify etag - if etag and hasattr(enrollment_record, 'etag') and etag != enrollment_record.etag.replace('"', ''): - raise LookupError("enrollment etag doesn't match.") - if not etag: - etag = enrollment_record.etag.replace('"', '') + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) + + enrollment_record = sdk.get_enrollment_group(enrollment_id) # Update enrollment information if enrollment_record.attestation.type == AttestationType.symmetricKey.value: - enrollment_record.attestation = m_sdk.device_enrollment_group.attestation_mechanism_method(enrollment_id) + enrollment_record.attestation = sdk.get_enrollment_group_attestation_mechanism( + enrollment_id + ) if primary_key: enrollment_record.attestation.symmetric_key.primary_key = primary_key if secondary_key: - enrollment_record.attestation.symmetric_key.secondary_key = secondary_key + enrollment_record.attestation.symmetric_key.secondary_key = ( + secondary_key + ) if enrollment_record.attestation.type == AttestationType.x509.value: if not certificate_path and not secondary_certificate_path: if not root_ca_name and not secondary_root_ca_name: # Check if certificate can be safely removed while no new certificate has been provided if remove_certificate and remove_secondary_certificate: - raise CLIError('Please provide at least one certificate') - - if not _can_remove_primary_certificate(remove_certificate, enrollment_record.attestation): - raise CLIError('Please provide at least one certificate while removing the only primary certificate') - - if not _can_remove_secondary_certificate(remove_secondary_certificate, enrollment_record.attestation): - raise CLIError('Please provide at least one certificate while removing the only secondary certificate') + raise CLIError("Please provide at least one certificate") + + if not _can_remove_primary_certificate( + remove_certificate, enrollment_record.attestation + ): + raise CLIError( + "Please provide at least one certificate while removing the only primary certificate" + ) + + if not _can_remove_secondary_certificate( + remove_secondary_certificate, enrollment_record.attestation + ): + raise CLIError( + "Please provide at least one certificate while removing the only secondary certificate" + ) if certificate_path or secondary_certificate_path: if root_ca_name or secondary_root_ca_name: - raise CLIError('Please provide either certificate path or certficate name') - enrollment_record.attestation = _get_updated_attestation_with_x509_signing_cert(enrollment_record.attestation, - certificate_path, - secondary_certificate_path, - remove_certificate, - remove_secondary_certificate) + raise CLIError( + "Please provide either certificate path or certficate name" + ) + enrollment_record.attestation = _get_updated_attestation_with_x509_signing_cert( + enrollment_record.attestation, + certificate_path, + secondary_certificate_path, + remove_certificate, + remove_secondary_certificate, + ) if root_ca_name or secondary_root_ca_name: if certificate_path or secondary_certificate_path: - raise CLIError('Please provide either certificate path or certficate name') - enrollment_record.attestation = _get_updated_attestation_with_x509_ca_cert(enrollment_record.attestation, - root_ca_name, - secondary_root_ca_name, - remove_certificate, - remove_secondary_certificate) + raise CLIError( + "Please provide either certificate path or certficate name" + ) + enrollment_record.attestation = _get_updated_attestation_with_x509_ca_cert( + enrollment_record.attestation, + root_ca_name, + secondary_root_ca_name, + remove_certificate, + remove_secondary_certificate, + ) if iot_hub_host_name: enrollment_record.allocation_policy = AllocationType.static.value enrollment_record.iot_hubs = iot_hub_host_name.split() @@ -366,63 +513,97 @@ def iot_dps_device_enrollment_group_update(client, if provisioning_status: enrollment_record.provisioning_status = provisioning_status if reprovision_policy: - enrollment_record.reprovision_policy = _get_reprovision_policy(reprovision_policy) - enrollment_record.initial_twin = _get_updated_inital_twin(enrollment_record, - initial_twin_tags, - initial_twin_properties) + enrollment_record.reprovision_policy = _get_reprovision_policy( + reprovision_policy + ) + enrollment_record.initial_twin = _get_updated_inital_twin( + enrollment_record, initial_twin_tags, initial_twin_properties + ) iot_hub_list = iot_hubs.split() if iot_hubs else iot_hubs - _validate_allocation_policy_for_enrollment(allocation_policy, iot_hub_host_name, iot_hub_list) + _validate_allocation_policy_for_enrollment( + allocation_policy, iot_hub_host_name, iot_hub_list, webhook_url, api_version + ) if allocation_policy: enrollment_record.allocation_policy = allocation_policy enrollment_record.iot_hubs = iot_hub_list enrollment_record.iot_hub_host_name = None + if allocation_policy == AllocationType.custom.value: + enrollment_record.custom_allocation_definition = CustomAllocationDefinition( + webhook_url=webhook_url, api_version=api_version + ) if edge_enabled is not None: enrollment_record.capabilities = DeviceCapabilities(iot_edge=edge_enabled) - return m_sdk.device_enrollment_group.create_or_update(enrollment_id, enrollment_record, etag) - except errors.ProvisioningServiceErrorDetailsException as e: + return sdk.create_or_update_enrollment_group( + enrollment_id, enrollment_record, if_match=(etag if etag else "*") + ) + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) -def iot_dps_device_enrollment_group_delete(client, enrollment_id, dps_name, resource_group_name): +def iot_dps_device_enrollment_group_delete( + client, enrollment_id, dps_name, resource_group_name, etag=None +): target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) - return m_sdk.device_enrollment_group.delete(enrollment_id) - except errors.ProvisioningServiceErrorDetailsException as e: + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) + + return sdk.delete_enrollment_group(enrollment_id, if_match=(etag if etag else "*")) + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) +def iot_dps_compute_device_key( + cmd, symmetric_key, registration_id, +): + return compute_device_key( + primary_key=symmetric_key, registration_id=registration_id + ) + + # DPS Registration + def iot_dps_registration_list(client, dps_name, resource_group_name, enrollment_id): target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) - return m_sdk.registration_state.query_registration_state(enrollment_id) - except errors.ProvisioningServiceErrorDetailsException as e: + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) + + return sdk.query_device_registration_states( + enrollment_id, raw=True + ).response.json() + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) def iot_dps_registration_get(client, dps_name, resource_group_name, registration_id): target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) - return m_sdk.registration_state.get_registration_state(registration_id) - except errors.ProvisioningServiceErrorDetailsException as e: + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) + + return sdk.get_device_registration_state( + registration_id, raw=True + ).response.json() + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) -def iot_dps_registration_delete(client, dps_name, resource_group_name, registration_id): +def iot_dps_registration_delete(client, dps_name, resource_group_name, registration_id, etag=None): target = get_iot_dps_connection_string(client, dps_name, resource_group_name) try: - m_sdk, errors = _bind_sdk(target, SdkType.dps_sdk) - return m_sdk.registration_state.delete_registration_state(registration_id) - except errors.ProvisioningServiceErrorDetailsException as e: + resolver = SdkResolver(target=target) + sdk = resolver.get_sdk(SdkType.dps_sdk) + + return sdk.delete_device_registration_state(registration_id, if_match=(etag if etag else "*")) + except ProvisioningServiceErrorDetailsException as e: raise CLIError(e) def _get_initial_twin(initial_twin_tags=None, initial_twin_properties=None): from azext_iot.common.utility import dict_clean + if initial_twin_tags == "": initial_twin_tags = None elif initial_twin_tags: @@ -431,27 +612,40 @@ def _get_initial_twin(initial_twin_tags=None, initial_twin_properties=None): if initial_twin_properties == "": initial_twin_properties = None elif initial_twin_properties: - initial_twin_properties = dict_clean(shell_safe_json_parse(str(initial_twin_properties))) - return InitialTwin(TwinCollection(initial_twin_tags), - InitialTwinProperties(TwinCollection(initial_twin_properties))) + initial_twin_properties = dict_clean( + shell_safe_json_parse(str(initial_twin_properties)) + ) + return InitialTwin( + tags=TwinCollection(additional_properties=initial_twin_tags), + properties=InitialTwinProperties( + desired=TwinCollection(additional_properties=initial_twin_properties) + ), + ) -def _get_updated_inital_twin(enrollment_record, initial_twin_tags=None, initial_twin_properties=None): + +def _get_updated_inital_twin( + enrollment_record, initial_twin_tags=None, initial_twin_properties=None +): if initial_twin_properties != "" and not initial_twin_tags: - if hasattr(enrollment_record, 'initial_twin'): - if hasattr(enrollment_record.initial_twin, 'tags'): + if hasattr(enrollment_record, "initial_twin"): + if hasattr(enrollment_record.initial_twin, "tags"): initial_twin_tags = enrollment_record.initial_twin.tags if initial_twin_properties != "" and not initial_twin_properties: - if hasattr(enrollment_record, 'initial_twin'): - if hasattr(enrollment_record.initial_twin, 'properties'): - if hasattr(enrollment_record.initial_twin.properties, 'desired'): - initial_twin_properties = enrollment_record.initial_twin.properties.desired + if hasattr(enrollment_record, "initial_twin"): + if hasattr(enrollment_record.initial_twin, "properties"): + if hasattr(enrollment_record.initial_twin.properties, "desired"): + initial_twin_properties = ( + enrollment_record.initial_twin.properties.desired + ) return _get_initial_twin(initial_twin_tags, initial_twin_properties) def _get_x509_certificate(certificate_path, secondary_certificate_path): - x509certificate = X509Certificates(_get_certificate_info(certificate_path), - _get_certificate_info(secondary_certificate_path)) + x509certificate = X509Certificates( + primary=_get_certificate_info(certificate_path), + secondary=_get_certificate_info(secondary_certificate_path), + ) return x509certificate @@ -459,73 +653,108 @@ def _get_certificate_info(certificate_path): if not certificate_path: return None certificate_content = open_certificate(certificate_path) - certificate_with_info = X509CertificateWithInfo(certificate_content) + certificate_with_info = X509CertificateWithInfo(certificate=certificate_content) return certificate_with_info -def _get_attestation_with_x509_client_cert(primary_certificate_path, secondary_certificate_path): +def _get_attestation_with_x509_client_cert( + primary_certificate_path, secondary_certificate_path +): if not primary_certificate_path and not secondary_certificate_path: - raise CLIError('Please provide at least one certificate path') - certificate = _get_x509_certificate(primary_certificate_path, secondary_certificate_path) - x509Attestation = X509Attestation(certificate) - attestation = AttestationMechanism(AttestationType.x509.value, None, x509Attestation) + raise CLIError("Please provide at least one certificate path") + certificate = _get_x509_certificate( + primary_certificate_path, secondary_certificate_path + ) + x509Attestation = X509Attestation(client_certificates=certificate) + attestation = AttestationMechanism( + type=AttestationType.x509.value, x509=x509Attestation + ) return attestation -def _get_updated_attestation_with_x509_client_cert(attestation, - primary_certificate_path, - secondary_certificate_path, - remove_primary_certificate, - remove_secondary_certificate): +def _get_updated_attestation_with_x509_client_cert( + attestation, + primary_certificate_path, + secondary_certificate_path, + remove_primary_certificate, + remove_secondary_certificate, +): if remove_primary_certificate: attestation.x509.client_certificates.primary = None if remove_secondary_certificate: attestation.x509.client_certificates.secondary = None if primary_certificate_path: - attestation.x509.client_certificates.primary = _get_certificate_info(primary_certificate_path) + attestation.x509.client_certificates.primary = _get_certificate_info( + primary_certificate_path + ) if secondary_certificate_path: - attestation.x509.client_certificates.secondary = _get_certificate_info(secondary_certificate_path) + attestation.x509.client_certificates.secondary = _get_certificate_info( + secondary_certificate_path + ) return attestation -def _get_attestation_with_x509_signing_cert(primary_certificate_path, secondary_certificate_path): - certificate = _get_x509_certificate(primary_certificate_path, secondary_certificate_path) - x509Attestation = X509Attestation(None, certificate) - attestation = AttestationMechanism(AttestationType.x509.value, None, x509Attestation) +def _get_attestation_with_x509_signing_cert( + primary_certificate_path, secondary_certificate_path +): + certificate = _get_x509_certificate( + primary_certificate_path, secondary_certificate_path + ) + x509Attestation = X509Attestation(signing_certificates=certificate) + attestation = AttestationMechanism( + type=AttestationType.x509.value, x509=x509Attestation + ) return attestation def _get_attestation_with_x509_ca_cert(root_ca_name, secondary_root_ca_name): - certificate = X509CAReferences(root_ca_name, secondary_root_ca_name) - x509Attestation = X509Attestation(None, None, certificate) - attestation = AttestationMechanism(AttestationType.x509.value, None, x509Attestation) + certificate = X509CAReferences( + primary=root_ca_name, secondary=secondary_root_ca_name + ) + x509Attestation = X509Attestation(ca_references=certificate) + attestation = AttestationMechanism( + type=AttestationType.x509.value, x509=x509Attestation + ) return attestation -def _get_updated_attestation_with_x509_signing_cert(attestation, - primary_certificate_path, - secondary_certificate_path, - remove_primary_certificate, - remove_secondary_certificate): - if hasattr(attestation.x509, 'signing_certificates'): +def _get_updated_attestation_with_x509_signing_cert( + attestation, + primary_certificate_path, + secondary_certificate_path, + remove_primary_certificate, + remove_secondary_certificate, +): + if hasattr(attestation.x509, "signing_certificates"): if remove_primary_certificate: attestation.x509.signing_certificates.primary = None if remove_secondary_certificate: attestation.x509.signing_certificates.secondary = None if primary_certificate_path: - attestation.x509.signing_certificates.primary = _get_certificate_info(primary_certificate_path) + attestation.x509.signing_certificates.primary = _get_certificate_info( + primary_certificate_path + ) if secondary_certificate_path: - attestation.x509.signing_certificates.secondary = _get_certificate_info(secondary_certificate_path) + attestation.x509.signing_certificates.secondary = _get_certificate_info( + secondary_certificate_path + ) return attestation - return _get_attestation_with_x509_signing_cert(primary_certificate_path, secondary_certificate_path) - - -def _get_updated_attestation_with_x509_ca_cert(attestation, - root_ca_name, - secondary_root_ca_name, - remove_primary_certificate, - remove_secondary_certificate): - if hasattr(attestation.x509, 'ca_references') and attestation.x509.ca_references is not None: + return _get_attestation_with_x509_signing_cert( + primary_certificate_path, secondary_certificate_path + ) + + +def _get_updated_attestation_with_x509_ca_cert( + attestation, + root_ca_name, + secondary_root_ca_name, + remove_primary_certificate, + remove_secondary_certificate, +): + if ( + hasattr(attestation.x509, "ca_references") + and attestation.x509.ca_references is not None + ): if remove_primary_certificate: attestation.x509.ca_references.primary = None if remove_secondary_certificate: @@ -540,26 +769,34 @@ def _get_updated_attestation_with_x509_ca_cert(attestation, def _can_remove_primary_certificate(remove_certificate, attestation): if remove_certificate: - if hasattr(attestation.x509, 'signing_certificates'): - if (not hasattr(attestation.x509.signing_certificates, 'secondary') or - not attestation.x509.signing_certificates.secondary): + if hasattr(attestation.x509, "signing_certificates"): + if ( + not hasattr(attestation.x509.signing_certificates, "secondary") + or not attestation.x509.signing_certificates.secondary + ): return False - if hasattr(attestation.x509, 'ca_references'): - if (not hasattr(attestation.x509.ca_references, 'secondary') or - not attestation.x509.ca_references.secondary): + if hasattr(attestation.x509, "ca_references"): + if ( + not hasattr(attestation.x509.ca_references, "secondary") + or not attestation.x509.ca_references.secondary + ): return False return True def _can_remove_secondary_certificate(remove_certificate, attestation): if remove_certificate: - if hasattr(attestation.x509, 'signing_certificates'): - if (not hasattr(attestation.x509.signing_certificates, 'primary') or - not attestation.x509.signing_certificates.primary): + if hasattr(attestation.x509, "signing_certificates"): + if ( + not hasattr(attestation.x509.signing_certificates, "primary") + or not attestation.x509.signing_certificates.primary + ): return False - if hasattr(attestation.x509, 'ca_references'): - if (not hasattr(attestation.x509.ca_references, 'primary') or - not attestation.x509.ca_references.primary): + if hasattr(attestation.x509, "ca_references"): + if ( + not hasattr(attestation.x509.ca_references, "primary") + or not attestation.x509.ca_references.primary + ): return False return True @@ -567,60 +804,96 @@ def _can_remove_secondary_certificate(remove_certificate, attestation): def _get_reprovision_policy(reprovision_policy): if reprovision_policy: if reprovision_policy == ReprovisionType.reprovisionandmigratedata.value: - reprovision = ReprovisionPolicy(True, True) + reprovision = ReprovisionPolicy( + update_hub_assignment=True, migrate_device_data=True + ) elif reprovision_policy == ReprovisionType.reprovisionandresetdata.value: - reprovision = ReprovisionPolicy(True, False) + reprovision = ReprovisionPolicy( + update_hub_assignment=True, migrate_device_data=False + ) elif reprovision_policy == ReprovisionType.never.value: - reprovision = ReprovisionPolicy(False, False) + reprovision = ReprovisionPolicy( + update_hub_assignment=False, migrate_device_data=False + ) else: - raise CLIError('Invalid Reprovision Policy.') + raise CLIError("Invalid Reprovision Policy.") else: - reprovision = ReprovisionPolicy(True, True) + reprovision = ReprovisionPolicy( + update_hub_assignment=True, migrate_device_data=True + ) return reprovision -def _validate_arguments_for_attestation_mechanism(attestation_type, - endorsement_key, - certificate_path, - secondary_certificate_path, - remove_certificate, - remove_secondary_certificate, - primary_key, - secondary_key): +def _validate_arguments_for_attestation_mechanism( + attestation_type, + endorsement_key, + certificate_path, + secondary_certificate_path, + remove_certificate, + remove_secondary_certificate, + primary_key, + secondary_key, +): if attestation_type == AttestationType.tpm.value: if certificate_path or secondary_certificate_path: - raise CLIError('Cannot update certificate while enrollment is using tpm attestation mechanism') + raise CLIError( + "Cannot update certificate while enrollment is using tpm attestation mechanism" + ) if remove_certificate or remove_secondary_certificate: - raise CLIError('Cannot remove certificate while enrollment is using tpm attestation mechanism') + raise CLIError( + "Cannot remove certificate while enrollment is using tpm attestation mechanism" + ) if primary_key or secondary_key: - raise CLIError('Cannot update primary or secondary key while enrollment is using tpm attestation mechanism') + raise CLIError( + "Cannot update primary or secondary key while enrollment is using tpm attestation mechanism" + ) elif attestation_type == AttestationType.x509.value: if endorsement_key: - raise CLIError('Cannot update endorsement key while enrollment is using x509 attestation mechanism') + raise CLIError( + "Cannot update endorsement key while enrollment is using x509 attestation mechanism" + ) if primary_key or secondary_key: - raise CLIError('Cannot update primary or secondary key while enrollment is using x509 attestation mechanism') + raise CLIError( + "Cannot update primary or secondary key while enrollment is using x509 attestation mechanism" + ) else: if certificate_path or secondary_certificate_path: - raise CLIError('Cannot update certificate while enrollment is using symmetric key attestation mechanism') + raise CLIError( + "Cannot update certificate while enrollment is using symmetric key attestation mechanism" + ) if remove_certificate or remove_secondary_certificate: - raise CLIError('Cannot remove certificate while enrollment is using symmetric key attestation mechanism') + raise CLIError( + "Cannot remove certificate while enrollment is using symmetric key attestation mechanism" + ) if endorsement_key: - raise CLIError('Cannot update endorsement key while enrollment is using symmetric key attestation mechanism') + raise CLIError( + "Cannot update endorsement key while enrollment is using symmetric key attestation mechanism" + ) -def _validate_allocation_policy_for_enrollment(allocation_policy, - iot_hub_host_name, - iot_hub_list): +def _validate_allocation_policy_for_enrollment( + allocation_policy, iot_hub_host_name, iot_hub_list, webhook_url, api_version +): if allocation_policy: if iot_hub_host_name is not None: - raise CLIError('\'iot_hub_host_name\' is not required when allocation-policy is defined.') - if not any(allocation_policy == allocation.value for allocation in AllocationType): - raise CLIError('Please provide valid allocation policy.') + raise CLIError( + "'iot_hub_host_name' is not required when allocation-policy is defined." + ) + if not any( + allocation_policy == allocation.value for allocation in AllocationType + ): + raise CLIError("Please provide valid allocation policy.") if allocation_policy == AllocationType.static.value: if iot_hub_list is None: - raise CLIError('Please provide a hub to be assigned with device.') + raise CLIError("Please provide a hub to be assigned with device.") if iot_hub_list and len(iot_hub_list) > 1: - raise CLIError('Only one hub is required in static allocation policy.') + raise CLIError("Only one hub is required in static allocation policy.") + if allocation_policy == AllocationType.custom.value: + if webhook_url is None or api_version is None: + raise CLIError( + "Please provide both the Azure function webhook url and provisioning" + " service api-version when the allocation-policy is defined as Custom." + ) else: if iot_hub_list: - raise CLIError('Please provide allocation policy.') + raise CLIError("Please provide allocation policy.") diff --git a/azext_iot/operations/events3/_builders.py b/azext_iot/operations/events3/_builders.py deleted file mode 100644 index 4ea2daf33..000000000 --- a/azext_iot/operations/events3/_builders.py +++ /dev/null @@ -1,134 +0,0 @@ -import asyncio -import uamqp - -from azext_iot.common.sas_token_auth import SasTokenAuthentication -from azext_iot.common.utility import (parse_entity, unicode_binary_map, url_encode_str) - -# To provide amqp frame trace -DEBUG = False - - -class AmqpBuilder(): - @classmethod - def build_iothub_amqp_endpoint_from_target(cls, target, duration=360): - hub_name = target['entity'].split('.')[0] - user = "{}@sas.root.{}".format(target['policy'], hub_name) - sas_token = SasTokenAuthentication(target['entity'], target['policy'], - target['primarykey'], duration).generate_sas_token() - return url_encode_str(user) + ":{}@{}".format(url_encode_str(sas_token), target['entity']) - - -class EventTargetBuilder(): - - def __init__(self): - self.eventLoop = asyncio.new_event_loop() - asyncio.set_event_loop(self.eventLoop) - - def build_iot_hub_target(self, target): - return self.eventLoop.run_until_complete(self._build_iot_hub_target_async(target)) - - def build_central_event_hub_target(self, cmd, app_id, central_api_uri): - return self.eventLoop.run_until_complete(self._build_central_event_hub_target_async(cmd, app_id, central_api_uri)) - - def _build_auth_container(self, target): - sas_uri = 'sb://{}/{}'.format(target['events']['endpoint'], target['events']['path']) - return uamqp.authentication.SASTokenAsync.from_shared_access_key(sas_uri, target['policy'], target['primarykey']) - - def _build_auth_container_from_token(self, endpoint, path, token, tokenExpiry): - sas_uri = 'sb://{}/{}'.format(endpoint, path) - return uamqp.authentication.SASTokenAsync(audience=sas_uri, uri=sas_uri, expires_at=tokenExpiry, token=token) - - async def _query_meta_data(self, endpoint, path, auth): - source = uamqp.address.Source(endpoint) - receive_client = uamqp.ReceiveClientAsync(source, auth=auth, timeout=30000, debug=DEBUG) - try: - await receive_client.open_async() - message = uamqp.Message(application_properties={'name': path}) - - response = await receive_client.mgmt_request_async( - message, - b'READ', - op_type=b'com.microsoft:eventhub', - status_code_field=b'status-code', - description_fields=b'status-description', - timeout=30000 - ) - test = response.get_data() - return test - finally: - await receive_client.close_async() - - async def _evaluate_redirect(self, endpoint): - source = uamqp.address.Source('amqps://{}/messages/events/$management'.format(endpoint)) - receive_client = uamqp.ReceiveClientAsync(source, timeout=30000, prefetch=1, debug=DEBUG) - - try: - await receive_client.open_async() - await receive_client.receive_message_batch_async(max_batch_size=1) - except uamqp.errors.LinkRedirect as redirect: - redirect = unicode_binary_map(parse_entity(redirect)) - result = {} - result['events'] = {} - result['events']['endpoint'] = redirect['hostname'] - result['events']['path'] = redirect['address'].replace('amqps://', '').split('/')[1] - result['events']['address'] = redirect['address'] - return redirect, result - finally: - await receive_client.close_async() - - async def _build_central_event_hub_target_async(self, cmd, app_id, central_api_uri): - from azext_iot.common._azure import get_iot_central_tokens - - tokens = get_iot_central_tokens(cmd, app_id, central_api_uri) - eventHubToken = tokens['eventhubSasToken'] - hostnameWithoutPrefix = eventHubToken['hostname'].split("/")[2] - endpoint = hostnameWithoutPrefix - path = eventHubToken["entityPath"] - tokenExpiry = tokens['expiry'] - auth = self._build_auth_container_from_token(endpoint, path, eventHubToken['sasToken'], tokenExpiry) - address = "amqps://{}/{}/$management".format(hostnameWithoutPrefix, eventHubToken["entityPath"]) - meta_data = await self._query_meta_data(address, path, auth) - partition_count = meta_data[b'partition_count'] - partition_ids = [] - for i in range(int(partition_count)): - partition_ids.append(str(i)) - partitions = partition_ids - auth = self._build_auth_container_from_token(endpoint, path, eventHubToken['sasToken'], tokenExpiry) - - eventHubTarget = { - 'endpoint': endpoint, - 'path': path, - 'auth': auth, - 'partitions': partitions - } - - return eventHubTarget - - async def _build_iot_hub_target_async(self, target): - if 'events' not in target: - endpoint = AmqpBuilder.build_iothub_amqp_endpoint_from_target(target) - _, update = await self._evaluate_redirect(endpoint) - target['events'] = update['events'] - endpoint = target['events']['endpoint'] - path = target['events']['path'] - auth = self._build_auth_container(target) - meta_data = await self._query_meta_data(target['events']['address'], target['events']['path'], auth) - partition_count = meta_data[b'partition_count'] - partition_ids = [] - for i in range(int(partition_count)): - partition_ids.append(str(i)) - target['events']['partition_ids'] = partition_ids - else: - endpoint = target['events']['endpoint'] - path = target['events']['path'] - partitions = target['events']['partition_ids'] - auth = self._build_auth_container(target) - - eventHubTarget = { - 'endpoint': endpoint, - 'path': path, - 'auth': auth, - 'partitions': partitions - } - - return eventHubTarget diff --git a/azext_iot/operations/events3/_events.py b/azext_iot/operations/events3/_events.py deleted file mode 100644 index 15f9bac36..000000000 --- a/azext_iot/operations/events3/_events.py +++ /dev/null @@ -1,403 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import asyncio -import json -import re -import sys -import six -import yaml -import uamqp - -from uuid import uuid4 -from knack.log import get_logger -from azext_iot.constants import VERSION, USER_AGENT -from azext_iot.common.utility import parse_entity, unicode_binary_map, process_json_arg -from azext_iot.operations.events3._builders import AmqpBuilder - -# To provide amqp frame trace -DEBUG = False -logger = get_logger(__name__) - - -def executor( - target, - consumer_group, - enqueued_time, - properties=None, - timeout=0, - device_id=None, - output=None, - content_type=None, - devices=None, - interface_name=None, - pnp_context=None, -): - - coroutines = [] - coroutines.append( - initiate_event_monitor( - target, - consumer_group, - enqueued_time, - device_id, - properties, - timeout, - output, - content_type, - devices, - interface_name, - pnp_context, - ) - ) - - loop = asyncio.get_event_loop() - if loop.is_closed(): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - future = asyncio.gather(*coroutines, loop=loop, return_exceptions=True) - result = None - - try: - device_filter_txt = None - if device_id: - device_filter_txt = " filtering on device: {},".format(device_id) - - def stop_and_suppress_eloop(): - try: - loop.stop() - except Exception: - pass - - six.print_( - "Starting {}event monitor,{} use ctrl-c to stop...".format( - "Digital Twin " if pnp_context else "", - device_filter_txt if device_filter_txt else "", - ) - ) - future.add_done_callback(lambda future: stop_and_suppress_eloop()) - result = loop.run_until_complete(future) - except KeyboardInterrupt: - six.print_("Stopping event monitor...") - for t in asyncio.Task.all_tasks(): - t.cancel() - loop.run_forever() - finally: - if result: - errors = result[0] - if errors and errors[0]: - logger.debug(errors) - raise RuntimeError(errors[0]) - - -async def initiate_event_monitor( - target, - consumer_group, - enqueued_time, - device_id=None, - properties=None, - timeout=0, - output=None, - content_type=None, - devices=None, - interface_name=None, - pnp_context=None, -): - def _get_conn_props(): - properties = {} - properties["product"] = USER_AGENT - properties["version"] = VERSION - properties["framework"] = "Python {}.{}.{}".format(*sys.version_info[0:3]) - properties["platform"] = sys.platform - return properties - - if not target["partitions"]: - logger.debug("No Event Hub partitions found to listen on.") - return - - coroutines = [] - - async with uamqp.ConnectionAsync( - target["endpoint"], - sasl=target["auth"], - debug=DEBUG, - container_id=_get_container_id(), - properties=_get_conn_props(), - ) as conn: - for p in target["partitions"]: - coroutines.append( - monitor_events( - endpoint=target["endpoint"], - connection=conn, - path=target["path"], - auth=target["auth"], - partition=p, - consumer_group=consumer_group, - enqueuedtimeutc=enqueued_time, - properties=properties, - device_id=device_id, - timeout=timeout, - output=output, - content_type=content_type, - devices=devices, - interface_name=interface_name, - pnp_context=pnp_context, - ) - ) - return await asyncio.gather(*coroutines, return_exceptions=True) - - -async def monitor_events( - endpoint, - connection, - path, - auth, - partition, - consumer_group, - enqueuedtimeutc, - properties, - device_id=None, - timeout=0, - output=None, - content_type=None, - devices=None, - interface_name=None, - pnp_context=None, -): - source = uamqp.address.Source( - "amqps://{}/{}/ConsumerGroups/{}/Partitions/{}".format( - endpoint, path, consumer_group, partition - ) - ) - source.set_filter( - bytes("amqp.annotation.x-opt-enqueuedtimeutc > " + str(enqueuedtimeutc), "utf8") - ) - - def _output_msg_kpi(msg): - origin = str(msg.annotations.get(b"iothub-connection-device-id"), "utf8") - if device_id and device_id != origin: - if "*" in device_id or "?" in device_id: - regex = ( - re.escape(device_id).replace("\\*", ".*").replace("\\?", ".") + "$" - ) - if not re.match(regex, origin): - return - else: - return - if devices and origin not in devices: - return - - event_source = {"event": {}} - event_source["event"]["origin"] = origin - payload = "" - - if pnp_context: - msg_interface_name = str( - msg.annotations.get(b"iothub-interface-name"), "utf8" - ) - if not msg_interface_name: - return - - if interface_name: - if msg_interface_name != interface_name: - return - - event_source["event"]["interface"] = msg_interface_name - - data = msg.get_data() - if data: - payload = str(next(data), "utf8") - - system_props = unicode_binary_map(parse_entity(msg.properties, True)) - - ct = content_type - if not ct: - ct = system_props["content_type"] if "content_type" in system_props else "" - - if ct and "application/json" in ct.lower(): - try: - payload = json.loads( - re.compile(r"(\\r\\n)+|\\r+|\\n+").sub("", payload) - ) - except Exception: - # We don't want to crash the monitor if JSON parsing fails - pass - - event_source["event"]["payload"] = payload - - if "anno" in properties or "all" in properties: - event_source["event"]["annotations"] = unicode_binary_map(msg.annotations) - if "sys" in properties or "all" in properties: - if not event_source["event"].get("properties"): - event_source["event"]["properties"] = {} - event_source["event"]["properties"]["system"] = system_props - if "app" in properties or "all" in properties: - if not event_source["event"].get("properties"): - event_source["event"]["properties"] = {} - app_prop = ( - msg.application_properties if msg.application_properties else None - ) - - if app_prop: - event_source["event"]["properties"]["application"] = unicode_binary_map( - app_prop - ) - if output.lower() == "json": - dump = json.dumps(event_source, indent=4) - else: - dump = yaml.safe_dump(event_source, default_flow_style=False) - six.print_(dump, flush=True) - - exp_cancelled = False - receive_client = uamqp.ReceiveClientAsync( - source, auth=auth, timeout=timeout, prefetch=0, client_name=_get_container_id(), debug=DEBUG - ) - - try: - if connection: - await receive_client.open_async(connection=connection) - - async for msg in receive_client.receive_messages_iter_async(): - _output_msg_kpi(msg) - - except asyncio.CancelledError: - exp_cancelled = True - await receive_client.close_async() - except uamqp.errors.LinkDetach as ld: - if isinstance(ld.description, bytes): - ld.description = str(ld.description, "utf8") - raise RuntimeError(ld.description) - except KeyboardInterrupt: - logger.info("Keyboard interrupt, closing monitor on partition %s", partition) - exp_cancelled = True - await receive_client.close_async() - raise - finally: - if not exp_cancelled: - await receive_client.close_async() - logger.info("Closed monitor on partition %s", partition) - - -def send_c2d_message( - target, - device_id, - data, - message_id=None, - correlation_id=None, - ack=None, - content_type=None, - user_id=None, - content_encoding="utf-8", - expiry_time_utc=None, - properties=None, -): - app_props = {} - if properties: - app_props.update(properties) - - app_props["iothub-ack"] = ack if ack else "none" - - msg_props = uamqp.message.MessageProperties() - msg_props.to = "/devices/{}/messages/devicebound".format(device_id) - - target_msg_id = message_id if message_id else str(uuid4()) - msg_props.message_id = target_msg_id - - if correlation_id: - msg_props.correlation_id = correlation_id - - if user_id: - msg_props.user_id = user_id - - if content_type: - msg_props.content_type = content_type - - # Ensures valid json when content_type is application/json - content_type = content_type.lower() - if content_type == "application/json": - data = json.dumps(process_json_arg(data, "data")) - - if content_encoding: - msg_props.content_encoding = content_encoding - - if expiry_time_utc: - msg_props.absolute_expiry_time = int(expiry_time_utc) - - msg_body = str.encode(data) - - message = uamqp.Message( - body=msg_body, properties=msg_props, application_properties=app_props - ) - - operation = "/messages/devicebound" - endpoint = AmqpBuilder.build_iothub_amqp_endpoint_from_target(target) - endpoint_with_op = endpoint + operation - client = uamqp.SendClient( - target="amqps://" + endpoint_with_op, client_name=_get_container_id(), debug=DEBUG - ) - client.queue_message(message) - result = client.send_all_messages() - errors = [m for m in result if m == uamqp.constants.MessageState.SendFailed] - return target_msg_id, errors - - -def monitor_feedback(target, device_id, wait_on_id=None, token_duration=3600): - def handle_msg(msg): - payload = next(msg.get_data()) - if isinstance(payload, bytes): - payload = str(payload, "utf8") - # assume json [] based on spec - payload = json.loads(payload) - for p in payload: - if ( - device_id - and p.get("deviceId") - and p["deviceId"].lower() != device_id.lower() - ): - return None - six.print_(yaml.dump({"feedback": p}, default_flow_style=False), flush=True) - if wait_on_id: - msg_id = p["originalMessageId"] - if msg_id == wait_on_id: - return msg_id - return None - - operation = "/messages/servicebound/feedback" - endpoint = AmqpBuilder.build_iothub_amqp_endpoint_from_target( - target, duration=token_duration - ) - endpoint = endpoint + operation - - device_filter_txt = None - if device_id: - device_filter_txt = " filtering on device: {},".format(device_id) - - six.print_( - "Starting C2D feedback monitor,{} use ctrl-c to stop...".format( - device_filter_txt if device_filter_txt else "" - ) - ) - - try: - client = uamqp.ReceiveClient( - "amqps://" + endpoint, client_name=_get_container_id(), debug=DEBUG - ) - message_generator = client.receive_messages_iter() - for msg in message_generator: - match = handle_msg(msg) - if match: - logger.info("Requested message Id has been matched...") - msg.accept() - return match - except uamqp.errors.AMQPConnectionError: - logger.debug("AMQPS connection has expired...") - finally: - client.close() - - -def _get_container_id(): - return "{}/{}".format(USER_AGENT, str(uuid4())) diff --git a/azext_iot/operations/hub.py b/azext_iot/operations/hub.py index b4f1a4c12..2a234aa53 100644 --- a/azext_iot/operations/hub.py +++ b/azext_iot/operations/hub.py @@ -9,24 +9,37 @@ import six from knack.log import get_logger from knack.util import CLIError -from azext_iot.constants import (EXTENSION_ROOT, - DEVICE_DEVICESCOPE_PREFIX, - TRACING_PROPERTY, - TRACING_ALLOWED_FOR_LOCATION, - TRACING_ALLOWED_FOR_SKU) +from enum import Enum, EnumMeta +from azext_iot.constants import ( + EXTENSION_ROOT, + DEVICE_DEVICESCOPE_PREFIX, + TRACING_PROPERTY, + TRACING_ALLOWED_FOR_LOCATION, + TRACING_ALLOWED_FOR_SKU, +) from azext_iot.common.sas_token_auth import SasTokenAuthentication -from azext_iot.common.shared import (DeviceAuthType, - SdkType, - ProtocolType, - ConfigType) -from azext_iot.common._azure import get_iot_hub_connection_string -from azext_iot.common.utility import (shell_safe_json_parse, - read_file_content, - validate_key_value_pairs, - url_encode_dict, - unpack_msrest_error, - init_monitoring, - process_json_arg) +from azext_iot.common.shared import ( + DeviceAuthType, + SdkType, + ProtocolType, + ConfigType, + KeyType, + SettleType, + RenewKeyType, + IoTHubStateType +) +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, + unpack_msrest_error, + init_monitoring, + process_json_arg, + ensure_iothub_sdk_min_version, + generate_key +) from azext_iot._factory import SdkResolver, CloudError from azext_iot.operations.generic import _execute_query, _process_top @@ -36,16 +49,21 @@ # Query -def iot_query(cmd, query_command, hub_name=None, top=None, resource_group_name=None, login=None): - top = _process_top(top) - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_query( + cmd, query_command, hub_name=None, top=None, resource_group_name=None, login=None +): + top = _process_top(top) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: query_args = [query_command] - query_method = service_sdk.registry_manager.query_iot_hub + query_method = service_sdk.query.get_twins return _execute_query(query_args, query_method, top) except CloudError as e: @@ -54,8 +72,14 @@ def iot_query(cmd, query_command, hub_name=None, top=None, resource_group_name=N # Device -def iot_device_show(cmd, device_id, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + +def iot_device_show( + cmd, device_id, hub_name=None, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) return _iot_device_show(target, device_id) @@ -64,266 +88,560 @@ def _iot_device_show(target, device_id): service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - device = service_sdk.registry_manager.get_device(id=device_id, raw=True).response.json() - device['hub'] = target.get('entity') + device = service_sdk.devices.get_identity( + id=device_id, raw=True + ).response.json() + device["hub"] = target.get("entity") return device except CloudError as e: raise CLIError(unpack_msrest_error(e)) -def iot_device_list(cmd, hub_name=None, top=1000, edge_enabled=False, resource_group_name=None, login=None): - query = 'select * from devices where capabilities.iotEdge = true' if edge_enabled else 'select * from devices' +def iot_device_list( + cmd, + hub_name=None, + top=1000, + edge_enabled=False, + resource_group_name=None, + login=None, +): + query = ( + "select * from devices where capabilities.iotEdge = true" + if edge_enabled + else "select * from devices" + ) result = iot_query(cmd, query, hub_name, top, resource_group_name, login=login) if not result: logger.info('No registered devices found on hub "%s".', hub_name) return result -def iot_device_create(cmd, device_id, hub_name=None, edge_enabled=False, - auth_method='shared_private_key', primary_thumbprint=None, - secondary_thumbprint=None, status='enabled', status_reason=None, - valid_days=None, output_dir=None, set_parent_id=None, add_children=None, - force=False, resource_group_name=None, login=None): - - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_device_create( + cmd, + device_id, + hub_name=None, + edge_enabled=False, + auth_method="shared_private_key", + primary_thumbprint=None, + secondary_thumbprint=None, + status="enabled", + status_reason=None, + valid_days=None, + output_dir=None, + set_parent_id=None, + add_children=None, + force=False, + resource_group_name=None, + login=None, +): + + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) + if add_children: + if not edge_enabled: + raise CLIError( + 'The device "{}" should be edge device in order to add children.'.format(device_id) + ) + + for child_device_id in add_children.split(","): + child_device = _iot_device_show(target, child_device_id.strip()) + _validate_parent_child_relation(child_device, force) + deviceScope = None - if edge_enabled: - if auth_method != DeviceAuthType.shared_private_key.name: - raise CLIError('currently edge devices are limited to symmetric key auth') - if add_children: - for non_edge_device_id in add_children.split(','): - nonedge_device = _iot_device_show(target, non_edge_device_id.strip()) - _validate_nonedge_device(nonedge_device) - _validate_parent_child_relation(nonedge_device, '-', force) - else: - if set_parent_id: - edge_device = _iot_device_show(target, set_parent_id) - _validate_edge_device(edge_device) - deviceScope = edge_device['deviceScope'] + if set_parent_id: + edge_device = _iot_device_show(target, set_parent_id) + _validate_edge_device(edge_device) + deviceScope = edge_device["deviceScope"] if any([valid_days, output_dir]): valid_days = 365 if not valid_days else int(valid_days) if output_dir and not exists(output_dir): - raise CLIError("certificate output directory of '{}' does not exist.".format(output_dir)) + raise CLIError( + "certificate output directory of '{}' does not exist.".format( + output_dir + ) + ) cert = _create_self_signed_cert(device_id, valid_days, output_dir) primary_thumbprint = cert["thumbprint"] try: - device = _assemble_device(device_id, auth_method, edge_enabled, primary_thumbprint, - secondary_thumbprint, status, status_reason, deviceScope) - output = service_sdk.registry_manager.create_or_update_device(id=device_id, device=device) + device = _assemble_device( + False, + device_id, + auth_method, + edge_enabled, + primary_thumbprint, + secondary_thumbprint, + status, + status_reason, + deviceScope + ) + output = service_sdk.devices.create_or_update_identity( + id=device_id, device=device + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) if add_children: - for non_edge_device_id in add_children.split(','): - nonedge_device = _iot_device_show(target, non_edge_device_id.strip()) - _update_nonedge_devicescope(target, nonedge_device, output.device_scope) + for child_device_id in add_children.split(","): + child_device = _iot_device_show(target, child_device_id.strip()) + _update_device_parent(target, child_device, child_device["capabilities"]["iotEdge"], output.device_scope) return output -def _assemble_device(device_id, auth_method, edge_enabled, pk=None, sk=None, - status="enabled", status_reason=None, device_scope=None): - from azext_iot.sdk.iothub.service.models import ( - DeviceCapabilities, - Device - ) +def _assemble_device( + is_update, + device_id, + auth_method, + edge_enabled, + pk=None, + sk=None, + status="enabled", + status_reason=None, + device_scope=None, +): + from azext_iot.sdk.iothub.service.models import DeviceCapabilities, Device auth = _assemble_auth(auth_method, pk, sk) cap = DeviceCapabilities(iot_edge=edge_enabled) - device = Device(device_id=device_id, authentication=auth, - capabilities=cap, status=status, status_reason=status_reason, - device_scope=device_scope) - return device + if is_update: + device = Device( + device_id=device_id, + authentication=auth, + capabilities=cap, + status=status, + status_reason=status_reason, + device_scope=device_scope, + ) + return device + if edge_enabled: + parent_scope = [] + if device_scope: + parent_scope = [device_scope] + device = Device( + device_id=device_id, + authentication=auth, + capabilities=cap, + status=status, + status_reason=status_reason, + parent_scopes=parent_scope, + ) + return device + else: + device = Device( + device_id=device_id, + authentication=auth, + capabilities=cap, + status=status, + status_reason=status_reason, + device_scope=device_scope, + ) + return device def _assemble_auth(auth_method, pk, sk): from azext_iot.sdk.iothub.service.models import ( AuthenticationMechanism, SymmetricKey, - X509Thumbprint + X509Thumbprint, ) auth = None if auth_method in [DeviceAuthType.shared_private_key.name, "sas"]: auth = AuthenticationMechanism( - symmetric_key=SymmetricKey(primary_key=pk, secondary_key=sk), type="sas") + symmetric_key=SymmetricKey(primary_key=pk, secondary_key=sk), type="sas" + ) elif auth_method in [DeviceAuthType.x509_thumbprint.name, "selfSigned"]: if not pk: raise ValueError("primary thumbprint required with selfSigned auth") - auth = AuthenticationMechanism(x509_thumbprint=X509Thumbprint( - primary_thumbprint=pk, secondary_thumbprint=sk), type="selfSigned") + auth = AuthenticationMechanism( + x509_thumbprint=X509Thumbprint( + primary_thumbprint=pk, secondary_thumbprint=sk + ), + type="selfSigned", + ) elif auth_method in [DeviceAuthType.x509_ca.name, "certificateAuthority"]: auth = AuthenticationMechanism(type="certificateAuthority") else: - raise ValueError( - "Authorization method {} invalid.".format(auth_method)) + raise ValueError("Authorization method {} invalid.".format(auth_method)) return auth def _create_self_signed_cert(subject, valid_days, output_path=None): from azext_iot.common.certops import create_self_signed_certificate + return create_self_signed_certificate(subject, valid_days, output_path) -def iot_device_update(cmd, device_id, parameters, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def update_iot_device_custom( + instance, + edge_enabled=None, + status=None, + status_reason=None, + auth_method=None, + primary_thumbprint=None, + secondary_thumbprint=None, + primary_key=None, + secondary_key=None, +): + if edge_enabled is not None: + instance["capabilities"]["iotEdge"] = edge_enabled + if status is not None: + instance["status"] = status + if status_reason is not None: + instance["statusReason"] = status_reason + + auth_type = instance['authentication']['type'] + if auth_method is not None: + if auth_method == DeviceAuthType.shared_private_key.name: + auth = "sas" + if (primary_key and not secondary_key) or ( + not primary_key and secondary_key + ): + raise CLIError("primary + secondary Key required with sas auth") + instance["authentication"]["symmetricKey"]["primaryKey"] = primary_key + instance["authentication"]["symmetricKey"]["secondaryKey"] = secondary_key + elif auth_method == DeviceAuthType.x509_thumbprint.name: + auth = "selfSigned" + if not any([primary_thumbprint, secondary_thumbprint]): + raise CLIError( + "primary or secondary Thumbprint required with selfSigned auth" + ) + if primary_thumbprint: + instance["authentication"]["x509Thumbprint"][ + "primaryThumbprint" + ] = primary_thumbprint + if secondary_thumbprint: + instance["authentication"]["x509Thumbprint"][ + "secondaryThumbprint" + ] = secondary_thumbprint + elif auth_method == DeviceAuthType.x509_ca.name: + auth = "certificateAuthority" + else: + raise ValueError("Authorization method {} invalid.".format(auth_method)) + instance["authentication"]["type"] = auth + + # if no new auth_method is provided, validate secondary auth arguments and update accordingly + elif auth_type == "sas": + if any([primary_thumbprint, secondary_thumbprint]): + raise ValueError( + "Device authorization method {} does not support primary or secondary thumbprints.".format( + DeviceAuthType.shared_private_key.name + ) + ) + if primary_key: + instance["authentication"]["symmetricKey"]["primaryKey"] = primary_key + if secondary_key: + instance["authentication"]["symmetricKey"]["secondaryKey"] = secondary_key + + elif auth_type == "selfSigned": + if any([primary_key, secondary_key]): + raise ValueError( + "Device authorization method {} does not support primary or secondary keys.".format( + DeviceAuthType.x509_thumbprint.name + ) + ) + if primary_thumbprint: + instance["authentication"]["x509Thumbprint"][ + "primaryThumbprint" + ] = primary_thumbprint + if secondary_thumbprint: + instance["authentication"]["x509Thumbprint"][ + "secondaryThumbprint" + ] = secondary_thumbprint + return instance + + +def iot_device_update( + cmd, device_id, parameters, hub_name=None, resource_group_name=None, login=None, etag=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) + + auth, pk, sk = _parse_auth(parameters) + updated_device = _assemble_device( + True, + parameters['deviceId'], + auth, + parameters['capabilities']['iotEdge'], + pk, + sk, + parameters['status'].lower(), + parameters.get('statusReason'), + parameters.get('deviceScope') + ) + updated_device.etag = etag if etag else "*" + return _iot_device_update(target, device_id, updated_device) + + +def _iot_device_update(target, device_id, device): resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - updated_device = _handle_device_update_params(parameters) - etag = parameters.get("etag", None) - if etag: - headers = {} - headers["If-Match"] = '"{}"'.format(etag) - return service_sdk.registry_manager.create_or_update_device( - id=device_id, - device=updated_device, - custom_headers=headers - ) - raise LookupError("device etag not found.") + headers = {} + headers["If-Match"] = '"{}"'.format(device.etag) + return service_sdk.devices.create_or_update_identity( + id=device_id, + device=device, + custom_headers=headers + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) - except LookupError as err: - raise CLIError(err) -def _handle_device_update_params(parameters): - status = parameters["status"].lower() - possible_status = ["enabled", "disabled"] - if status not in possible_status: - raise CLIError("status must be one of {}".format(possible_status)) - - edge = parameters["capabilities"].get('iotEdge') - if not isinstance(edge, bool): - raise CLIError("capabilities.iotEdge is of type bool") +def iot_device_delete( + cmd, device_id, hub_name=None, resource_group_name=None, login=None, etag=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) + resolver = SdkResolver(target=target) + service_sdk = resolver.get_sdk(SdkType.service_sdk) - auth, pk, sk = _parse_auth(parameters) - return _assemble_device(parameters["deviceId"], auth, edge, pk, sk, status, parameters.get("statusReason")) + try: + headers = {} + headers["If-Match"] = '"{}"'.format(etag if etag else "*") + service_sdk.devices.delete_identity( + id=device_id, custom_headers=headers + ) + return + except CloudError as e: + raise CLIError(unpack_msrest_error(e)) -def iot_device_delete(cmd, device_id, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def _update_device_key(target, device, auth_method, pk, sk, etag=None): resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - device = _iot_device_show(target=target, device_id=device_id) - etag = device.get("etag") - - if etag: - headers = {} - headers["If-Match"] = '"{}"'.format(etag) - service_sdk.registry_manager.delete_device(id=device_id, custom_headers=headers) - return - raise LookupError("device etag not found") + auth = _assemble_auth(auth_method, pk, sk) + device["authentication"] = auth + headers = {} + headers["If-Match"] = '"{}"'.format(etag if etag else "*") + return service_sdk.devices.create_or_update_identity( + id=device["deviceId"], + device=device, + custom_headers=headers, + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) - except LookupError as err: - raise CLIError(err) -def iot_device_get_parent(cmd, device_id, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_device_key_regenerate(cmd, hub_name, device_id, renew_key_type, resource_group_name=None, login=None, etag=None): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) + device = _iot_device_show(target, device_id) + if (device["authentication"]["type"] != "sas"): + raise CLIError("Device authentication should be of type sas") + + pk = device["authentication"]["symmetricKey"]["primaryKey"] + sk = device["authentication"]["symmetricKey"]["secondaryKey"] + + if renew_key_type == RenewKeyType.primary.value: + pk = generate_key() + if renew_key_type == RenewKeyType.secondary.value: + sk = generate_key() + if renew_key_type == RenewKeyType.swap.value: + temp = pk + pk = sk + sk = temp + + return _update_device_key(target, device, device["authentication"]["type"], pk, sk, etag) + + +def iot_device_get_parent( + cmd, device_id, hub_name=None, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) child_device = _iot_device_show(target, device_id) - _validate_nonedge_device(child_device) _validate_child_device(child_device) - device_scope = child_device['deviceScope'] - parent_device_id = device_scope[len(DEVICE_DEVICESCOPE_PREFIX):device_scope.rindex('-')] + parent_scope = child_device["parentScopes"][0] + parent_device_id = parent_scope[ + len(DEVICE_DEVICESCOPE_PREFIX) : parent_scope.rindex("-") + ] return _iot_device_show(target, parent_device_id) -def iot_device_set_parent(cmd, device_id, parent_id, force=False, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_device_set_parent( + cmd, + device_id, + parent_id, + force=False, + hub_name=None, + resource_group_name=None, + login=None, +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) parent_device = _iot_device_show(target, parent_id) _validate_edge_device(parent_device) child_device = _iot_device_show(target, device_id) - _validate_nonedge_device(child_device) - _validate_parent_child_relation(child_device, parent_device['deviceScope'], force) - _update_nonedge_devicescope(target, child_device, parent_device['deviceScope']) - - -def iot_device_children_add(cmd, device_id, child_list, force=False, hub_name=None, - resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + _validate_parent_child_relation(child_device, force) + + _update_device_parent(target, child_device, child_device["capabilities"]["iotEdge"], parent_device["deviceScope"]) + + +def iot_device_children_add( + cmd, + device_id, + child_list, + force=False, + hub_name=None, + resource_group_name=None, + login=None, +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) devices = [] edge_device = _iot_device_show(target, device_id) _validate_edge_device(edge_device) - for non_edge_device_id in child_list.split(','): - nonedge_device = _iot_device_show(target, non_edge_device_id.strip()) - _validate_nonedge_device(nonedge_device) - _validate_parent_child_relation(nonedge_device, edge_device['deviceScope'], force) - devices.append(nonedge_device) + converted_child_list = child_list + if isinstance(child_list, str): # this check would be removed once add-children command is deprecated + converted_child_list = child_list.split(",") + for child_device_id in converted_child_list: + child_device = _iot_device_show(target, child_device_id.strip()) + _validate_parent_child_relation( + child_device, force + ) + devices.append(child_device) for device in devices: - _update_nonedge_devicescope(target, device, edge_device['deviceScope']) - - -def iot_device_children_remove(cmd, device_id, child_list=None, remove_all=False, hub_name=None, - resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + _update_device_parent(target, device, device["capabilities"]["iotEdge"], edge_device["deviceScope"]) + + +def iot_device_children_remove( + cmd, + device_id, + child_list=None, + remove_all=False, + hub_name=None, + resource_group_name=None, + login=None, +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) devices = [] if remove_all: - result = _iot_device_children_list(cmd, device_id, hub_name, resource_group_name, login) + result = _iot_device_children_list( + cmd, device_id, hub_name, resource_group_name, login + ) if not result: - raise CLIError('No registered child devices found for "{}" edge device.'.format(device_id)) - for non_edge_device_id in ([str(x['deviceId']) for x in result]): - nonedge_device = _iot_device_show(target, non_edge_device_id.strip()) - devices.append(nonedge_device) + raise CLIError( + 'No registered child devices found for "{}" edge device.'.format( + device_id + ) + ) + for child_device_id in [str(x["deviceId"]) for x in result]: + child_device = _iot_device_show(target, child_device_id.strip()) + devices.append(child_device) elif child_list: edge_device = _iot_device_show(target, device_id) _validate_edge_device(edge_device) - for non_edge_device_id in child_list.split(','): - nonedge_device = _iot_device_show(target, non_edge_device_id.strip()) - _validate_nonedge_device(nonedge_device) - _validate_child_device(nonedge_device) - if nonedge_device['deviceScope'] == edge_device['deviceScope']: - devices.append(nonedge_device) + converted_child_list = child_list + if isinstance(child_list, str): # this check would be removed once remove-children command is deprecated + converted_child_list = child_list.split(",") + for child_device_id in converted_child_list: + child_device = _iot_device_show(target, child_device_id.strip()) + _validate_child_device(child_device) + if child_device["parentScopes"] == [edge_device["deviceScope"]]: + devices.append(child_device) else: - raise CLIError('The entered child device "{}" isn\'t assigned as a child of edge device "{}"' - .format(non_edge_device_id.strip(), device_id)) + raise CLIError( + 'The entered child device "{}" isn\'t assigned as a child of edge device "{}"'.format( + child_device_id.strip(), device_id + ) + ) else: - raise CLIError('Please specify comma-separated child list or use --remove-all to remove all children.') + raise CLIError( + "Please specify child list or use --remove-all to remove all children." + ) for device in devices: - _update_nonedge_devicescope(target, device) + _update_device_parent(target, device, device["capabilities"]["iotEdge"]) -def iot_device_children_list(cmd, device_id, hub_name=None, resource_group_name=None, login=None): - result = _iot_device_children_list(cmd, device_id, hub_name, resource_group_name, login) +def iot_device_children_list( + cmd, device_id, hub_name=None, resource_group_name=None, login=None +): + result = _iot_device_children_list( + cmd, device_id, hub_name, resource_group_name, login + ) + + return [device["deviceId"] for device in result] + + +# this method would be removed once remove-children command is deprecated +def iot_device_children_list_comma_separated( + cmd, device_id, hub_name=None, resource_group_name=None, login=None +): + result = _iot_device_children_list( + cmd, device_id, hub_name, resource_group_name, login + ) if not result: - raise CLIError('No registered child devices found for "{}" edge device.'.format(device_id)) - return ', '.join([str(x['deviceId']) for x in result]) + raise CLIError( + 'No registered child devices found for "{}" edge device.'.format(device_id) + ) + return ", ".join([str(x["deviceId"]) for x in result]) -def _iot_device_children_list(cmd, device_id, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def _iot_device_children_list( + cmd, device_id, hub_name=None, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) device = _iot_device_show(target, device_id) _validate_edge_device(device) - query = ('select * from devices where capabilities.iotEdge=false and deviceScope=\'{}\'' - .format(device['deviceScope'])) + query = "select deviceId from devices where array_contains(parentScopes, '{}')".format( + device["deviceScope"] + ) return iot_query(cmd, query, hub_name, None, resource_group_name, login=login) -def _update_nonedge_devicescope(target, nonedge_device, deviceScope=''): +def _update_device_parent(target, device, is_edge, device_scope=None): resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - nonedge_device['deviceScope'] = deviceScope - etag = nonedge_device.get('etag', None) + if is_edge: + parent_scopes = [] + if device_scope: + parent_scopes = [device_scope] + device["parentScopes"] = parent_scopes + else: + if not device_scope: + device_scope = "" + device["deviceScope"] = device_scope + etag = device.get("etag", None) if etag: headers = {} headers["If-Match"] = '"{}"'.format(etag) - service_sdk.registry_manager.create_or_update_device( - id=nonedge_device['deviceId'], - device=nonedge_device, - custom_headers=headers + service_sdk.devices.create_or_update_identity( + id=device["deviceId"], + device=device, + custom_headers=headers, ) return raise LookupError("device etag not found.") @@ -334,44 +652,71 @@ def _update_nonedge_devicescope(target, nonedge_device, deviceScope=''): def _validate_edge_device(device): - if not device['capabilities']['iotEdge']: - raise CLIError('The device "{}" should be edge device.'.format(device['deviceId'])) - - -def _validate_nonedge_device(device): - if device['capabilities']['iotEdge']: - raise CLIError('The entered child device "{}" should be non-edge device.'.format(device['deviceId'])) + if not device["capabilities"]["iotEdge"]: + raise CLIError( + 'The device "{}" should be edge device.'.format(device["deviceId"]) + ) def _validate_child_device(device): - if 'deviceScope' not in device or device['deviceScope'] == '': - raise CLIError('Device "{}" doesn\'t support parent device functionality.'.format(device['deviceId'])) + if "parentScopes" not in device: + raise CLIError( + 'Device "{}" doesn\'t support parent device functionality.'.format( + device["deviceId"] + ) + ) + if not device["parentScopes"]: + raise CLIError( + 'Device "{}" doesn\'t have any parent device.'.format( + device["deviceId"] + ) + ) -def _validate_parent_child_relation(child_device, deviceScope, force): - if 'deviceScope' not in child_device or child_device['deviceScope'] == '': +def _validate_parent_child_relation(child_device, force): + if "parentScopes" not in child_device or child_device["parentScopes"] == []: return - if child_device['deviceScope'] != deviceScope: + else: if not force: - raise CLIError('The entered device "{}" already has a parent device, please use \'--force\'' - ' to overwrite'.format(child_device['deviceId'])) + raise CLIError( + "The entered device \"{}\" already has a parent device, please use '--force'" + " to overwrite".format(child_device["deviceId"]) + ) return # Module -def iot_device_module_create(cmd, device_id, module_id, hub_name=None, auth_method='shared_private_key', - primary_thumbprint=None, secondary_thumbprint=None, valid_days=None, - output_dir=None, resource_group_name=None, login=None): + +def iot_device_module_create( + cmd, + device_id, + module_id, + hub_name=None, + auth_method="shared_private_key", + primary_thumbprint=None, + secondary_thumbprint=None, + valid_days=None, + output_dir=None, + resource_group_name=None, + login=None, +): if any([valid_days, output_dir]): valid_days = 365 if not valid_days else int(valid_days) if output_dir and not exists(output_dir): - raise CLIError("certificate output directory of '{}' does not exist.".format(output_dir)) + raise CLIError( + "certificate output directory of '{}' does not exist.".format( + output_dir + ) + ) cert = _create_self_signed_cert(module_id, valid_days, output_dir) primary_thumbprint = cert["thumbprint"] - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) @@ -381,9 +726,11 @@ def iot_device_module_create(cmd, device_id, module_id, hub_name=None, auth_meth module_id=module_id, auth_method=auth_method, pk=primary_thumbprint, - sk=secondary_thumbprint + sk=secondary_thumbprint, + ) + return service_sdk.modules.create_or_update_identity( + id=device_id, mid=module_id, module=module ) - return service_sdk.registry_manager.create_or_update_module(id=device_id, mid=module_id, module=module) except CloudError as e: raise CLIError(unpack_msrest_error(e)) @@ -396,29 +743,35 @@ def _assemble_module(device_id, module_id, auth_method, pk=None, sk=None): return module -def iot_device_module_update(cmd, device_id, module_id, parameters, - hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_device_module_update( + cmd, + device_id, + module_id, + parameters, + hub_name=None, + resource_group_name=None, + login=None, + etag=None, +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: updated_module = _handle_module_update_params(parameters) - etag = parameters.get("etag") - if etag: - headers = {} - headers["If-Match"] = '"{}"'.format(etag) - return service_sdk.registry_manager.create_or_update_module( - id=device_id, - mid=module_id, - module=updated_module, - custom_headers=headers - ) - raise LookupError("module etag not found.") + headers = {} + headers["If-Match"] = '"{}"'.format(etag if etag else "*") + return service_sdk.modules.create_or_update_identity( + id=device_id, + mid=module_id, + module=updated_module, + custom_headers=headers, + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) - except LookupError as err: - raise CLIError(err) def _handle_module_update_params(parameters): @@ -428,13 +781,13 @@ def _handle_module_update_params(parameters): module_id=parameters["moduleId"], auth_method=auth, pk=pk, - sk=sk + sk=sk, ) def _parse_auth(parameters): valid_auth = ["sas", "selfSigned", "certificateAuthority"] - auth = parameters['authentication'].get('type') + auth = parameters["authentication"].get("type") if auth not in valid_auth: raise CLIError("authentication.type must be one of {}".format(valid_auth)) pk = sk = None @@ -445,23 +798,35 @@ def _parse_auth(parameters): pk = parameters["authentication"]["x509Thumbprint"]["primaryThumbprint"] sk = parameters["authentication"]["x509Thumbprint"]["secondaryThumbprint"] if not any([pk, sk]): - raise CLIError("primary + secondary Thumbprint required with selfSigned auth") + raise CLIError( + "primary + secondary Thumbprint required with selfSigned auth" + ) return auth, pk, sk -def iot_device_module_list(cmd, device_id, hub_name=None, top=1000, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_device_module_list( + cmd, device_id, hub_name=None, top=1000, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - return service_sdk.registry_manager.get_modules_on_device(device_id)[:top] + return service_sdk.modules.get_modules_on_device(device_id)[:top] except CloudError as e: raise CLIError(unpack_msrest_error(e)) -def iot_device_module_show(cmd, device_id, module_id, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_device_module_show( + cmd, device_id, module_id, hub_name=None, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) return _iot_device_module_show(target, device_id, module_id) @@ -470,36 +835,46 @@ def _iot_device_module_show(target, device_id, module_id): service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - module = service_sdk.registry_manager.get_module(id=device_id, mid=module_id, raw=True).response.json() + module = service_sdk.modules.get_identity( + id=device_id, mid=module_id, raw=True + ).response.json() module["hub"] = target.get("entity") return module except CloudError as e: raise CLIError(unpack_msrest_error(e)) -def iot_device_module_delete(cmd, device_id, module_id, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_device_module_delete( + cmd, device_id, module_id, hub_name=None, resource_group_name=None, login=None, etag=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - module = _iot_device_module_show(target=target, device_id=device_id, module_id=module_id) - etag = module.get("etag") - if etag: - headers = {} - headers["If-Match"] = '"{}"'.format(etag) - service_sdk.registry_manager.delete_module(id=device_id, mid=module_id, custom_headers=headers) - return - raise LookupError("module etag not found") + headers = {} + headers["If-Match"] = '"{}"'.format(etag if etag else "*") + service_sdk.modules.delete_identity( + id=device_id, mid=module_id, custom_headers=headers + ) + return except CloudError as e: raise CLIError(unpack_msrest_error(e)) - except LookupError as err: - raise CLIError(err) -def iot_device_module_twin_show(cmd, device_id, module_id, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) - return _iot_device_module_twin_show(target=target, device_id=device_id, module_id=module_id) +def iot_device_module_twin_show( + cmd, device_id, module_id, hub_name=None, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) + return _iot_device_module_twin_show( + target=target, device_id=device_id, module_id=module_id + ) def _iot_device_module_twin_show(target, device_id, module_id): @@ -507,83 +882,102 @@ def _iot_device_module_twin_show(target, device_id, module_id): service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - return service_sdk.twin.get_module_twin(id=device_id, mid=module_id, raw=True).response.json() + return service_sdk.modules.get_twin( + id=device_id, mid=module_id, raw=True + ).response.json() except CloudError as e: raise CLIError(unpack_msrest_error(e)) -def iot_device_module_twin_update(cmd, device_id, module_id, parameters, hub_name=None, resource_group_name=None, login=None): +def iot_device_module_twin_update( + cmd, + device_id, + module_id, + parameters, + hub_name=None, + resource_group_name=None, + login=None, + etag=None +): from azext_iot.common.utility import verify_transform - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - etag = parameters.get("etag") - if etag: - headers = {} - headers["If-Match"] = '"{}"'.format(etag) - verify = {'properties.desired': dict} - - if parameters.get("tags"): - verify["tags"] = dict - - verify_transform(parameters, verify) - - return service_sdk.twin.update_module_twin( - id=device_id, - mid=module_id, - device_twin_info=parameters, - custom_headers=headers - ) - raise LookupError("module twin etag not found") + headers = {} + headers["If-Match"] = '"{}"'.format(etag if etag else "*") + verify = {} + if parameters.get("properties"): + if parameters["properties"].get("desired"): + verify = {"properties.desired": dict} + if parameters.get("tags"): + verify["tags"] = dict + verify_transform(parameters, verify) + return service_sdk.modules.update_twin( + id=device_id, + mid=module_id, + device_twin_info=parameters, + custom_headers=headers, + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) - except (AttributeError, LookupError, TypeError) as err: + except (AttributeError, TypeError) as err: raise CLIError(err) -def iot_device_module_twin_replace(cmd, device_id, module_id, target_json, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_device_module_twin_replace( + cmd, + device_id, + module_id, + target_json, + hub_name=None, + resource_group_name=None, + login=None, + etag=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: target_json = process_json_arg(target_json, argument_name="json") - module = _iot_device_module_twin_show( - target=target, - device_id=device_id, - module_id=module_id + headers = {} + headers["If-Match"] = '"{}"'.format(etag if etag else "*") + return service_sdk.modules.replace_twin( + id=device_id, + mid=module_id, + device_twin_info=target_json, + custom_headers=headers, ) - etag = module.get("etag") - if etag: - headers = {} - headers["If-Match"] = '"{}"'.format(etag) - return service_sdk.twin.replace_module_twin( - id=device_id, - mid=module_id, - device_twin_info=target_json, - custom_headers=headers - ) - raise LookupError("module twin etag not found") except CloudError as e: raise CLIError(unpack_msrest_error(e)) - except LookupError as err: - raise CLIError(err) -def iot_edge_set_modules(cmd, device_id, content, - hub_name=None, resource_group_name=None, login=None): +def iot_edge_set_modules( + cmd, device_id, content, hub_name=None, resource_group_name=None, login=None +): from azext_iot.sdk.iothub.service.models import ConfigurationContent - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - content = process_json_arg(content, argument_name='content') - processed_content = _process_config_content(content, config_type=ConfigType.edge) + content = process_json_arg(content, argument_name="content") + processed_content = _process_config_content( + content, config_type=ConfigType.edge + ) content = ConfigurationContent(**processed_content) service_sdk.configuration.apply_on_edge_device(id=device_id, content=content) @@ -592,32 +986,87 @@ def iot_edge_set_modules(cmd, device_id, content, raise CLIError(unpack_msrest_error(e)) -def iot_edge_deployment_create(cmd, config_id, content, hub_name=None, target_condition="", priority=0, - labels=None, metrics=None, layered=False, resource_group_name=None, login=None): - config_type = ConfigType.layered if layered else ConfigType.edge - return _iot_hub_configuration_create(cmd=cmd, config_id=config_id, content=content, hub_name=hub_name, - target_condition=target_condition, priority=priority, - labels=labels, metrics=metrics, resource_group_name=resource_group_name, - login=login, config_type=config_type) +def iot_edge_deployment_create( + cmd, + config_id, + content, + hub_name=None, + target_condition="", + priority=0, + labels=None, + metrics=None, + layered=False, + no_validation=False, + resource_group_name=None, + login=None, +): + # Short-term fix for --no-validation + config_type = ConfigType.layered if layered or no_validation else ConfigType.edge + return _iot_hub_configuration_create( + cmd=cmd, + config_id=config_id, + content=content, + hub_name=hub_name, + target_condition=target_condition, + priority=priority, + labels=labels, + metrics=metrics, + resource_group_name=resource_group_name, + login=login, + config_type=config_type, + ) -def iot_hub_configuration_create(cmd, config_id, content, hub_name=None, target_condition="", priority=0, - labels=None, metrics=None, resource_group_name=None, login=None): - return _iot_hub_configuration_create(cmd=cmd, config_id=config_id, content=content, hub_name=hub_name, - target_condition=target_condition, priority=priority, - labels=labels, metrics=metrics, resource_group_name=resource_group_name, - login=login, config_type=ConfigType.adm) +def iot_hub_configuration_create( + cmd, + config_id, + content, + hub_name=None, + target_condition="", + priority=0, + labels=None, + metrics=None, + resource_group_name=None, + login=None, +): + return _iot_hub_configuration_create( + cmd=cmd, + config_id=config_id, + content=content, + hub_name=hub_name, + target_condition=target_condition, + priority=priority, + labels=labels, + metrics=metrics, + resource_group_name=resource_group_name, + login=login, + config_type=ConfigType.adm, + ) -def _iot_hub_configuration_create(cmd, config_id, content, config_type, hub_name=None, target_condition="", priority=0, - labels=None, metrics=None, resource_group_name=None, login=None): - from azext_iot.sdk.service.models import ( +def _iot_hub_configuration_create( + cmd, + config_id, + content, + config_type, + hub_name=None, + target_condition="", + priority=0, + labels=None, + metrics=None, + resource_group_name=None, + login=None, +): + from azext_iot.sdk.iothub.service.models import ( Configuration, ConfigurationContent, - ConfigurationMetrics + ConfigurationMetrics, ) - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) @@ -631,7 +1080,11 @@ def _iot_hub_configuration_create(cmd, config_id, content, config_type, hub_name required_target_prefix = "from devices.modules where" lower_target_condition = target_condition.lower() if not lower_target_condition.startswith(required_target_prefix): - raise CLIError("The target condition for a module configuration must start with '{}'".format(required_target_prefix)) + raise CLIError( + "The target condition for a module configuration must start with '{}'".format( + required_target_prefix + ) + ) if metrics: metrics = process_json_arg(metrics, argument_name="metrics") @@ -639,7 +1092,9 @@ def _iot_hub_configuration_create(cmd, config_id, content, config_type, hub_name if "metrics" in metrics: metrics = metrics["metrics"] if metrics_key not in metrics: - raise CLIError("metrics json must include the '{}' property".format(metrics_key)) + raise CLIError( + "metrics json must include the '{}' property".format(metrics_key) + ) metrics = metrics[metrics_key] if labels: @@ -648,17 +1103,21 @@ def _iot_hub_configuration_create(cmd, config_id, content, config_type, hub_name config_content = ConfigurationContent(**processed_content) config_metrics = ConfigurationMetrics(queries=metrics) - config = Configuration(id=config_id, - schema_version="2.0", - labels=labels, - content=config_content, - metrics=config_metrics, - target_condition=target_condition, - etag="*", - priority=priority, - content_type="assignment") + config = Configuration( + id=config_id, + schema_version="2.0", + labels=labels, + content=config_content, + metrics=config_metrics, + target_condition=target_condition, + etag="*", + priority=priority, + ) + try: - return service_sdk.configuration.create_or_update(id=config_id, configuration=config) + return service_sdk.configuration.create_or_update( + id=config_id, configuration=config + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) @@ -680,9 +1139,11 @@ def _process_config_content(content, config_type): processed_content[to_snake_case(key)] = content[key] return processed_content - raise CLIError("Automatic device configuration payloads require property: {}".format( - ' or '.join(map(str, valid_adm_keys)) - )) + raise CLIError( + "Automatic device configuration payloads require property: {}".format( + " or ".join(map(str, valid_adm_keys)) + ) + ) if config_type == ConfigType.edge or config_type == ConfigType.layered: valid_edge_key = "modulesContent" @@ -703,12 +1164,16 @@ def _process_config_content(content, config_type): if config_type == ConfigType.edge: _validate_payload_schema(processed_content) - processed_content[to_snake_case(valid_edge_key)] = processed_content[valid_edge_key] + processed_content[to_snake_case(valid_edge_key)] = processed_content[ + valid_edge_key + ] del processed_content[valid_edge_key] return processed_content - raise CLIError("Edge deployment payloads require property: {}".format(valid_edge_key)) + raise CLIError( + "Edge deployment payloads require property: {}".format(valid_edge_key) + ) def _validate_payload_schema(content): @@ -733,46 +1198,58 @@ def _validate_payload_schema(content): errors = v.validate(content) if errors: # Pretty printing schema validation errors - raise CLIError(json.dumps({"validationErrors": errors}, separators=(",", ":"), indent=2)) + raise CLIError( + json.dumps({"validationErrors": errors}, separators=(",", ":"), indent=2) + ) return -def iot_hub_configuration_update(cmd, config_id, parameters, hub_name=None, resource_group_name=None, login=None): - from azext_iot.sdk.service.models.configuration import Configuration +def iot_hub_configuration_update( + cmd, config_id, parameters, hub_name=None, resource_group_name=None, login=None, etag=None +): + from azext_iot.sdk.iothub.service.models import Configuration from azext_iot.common.utility import verify_transform - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - etag = parameters.get("etag") - if not etag: - raise LookupError("invalid request, configuration etag not found") headers = {} - headers["If-Match"] = '"{}"'.format(etag) - verify = {'metrics': dict, 'metrics.queries': dict, 'content': dict} + headers["If-Match"] = '"{}"'.format(etag if etag else "*") + verify = {"metrics": dict, "metrics.queries": dict, "content": dict} if parameters.get("labels"): verify["labels"] = dict verify_transform(parameters, verify) - config = Configuration(id=parameters['id'], - schema_version=parameters['schemaVersion'], - labels=parameters['labels'], - content=parameters['content'], - metrics=parameters.get('metrics', None), - target_condition=parameters['targetCondition'], - priority=parameters['priority'], - content_type='assignment') - return service_sdk.configuration.create_or_update(id=config_id, configuration=config, custom_headers=headers) + config = Configuration( + id=parameters["id"], + schema_version=parameters["schemaVersion"], + labels=parameters["labels"], + content=parameters["content"], + metrics=parameters.get("metrics", None), + target_condition=parameters["targetCondition"], + priority=parameters["priority"] + ) + return service_sdk.configuration.create_or_update( + id=config_id, configuration=config, custom_headers=headers + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) - except (AttributeError, LookupError, TypeError) as err: + except (AttributeError, TypeError) as err: raise CLIError(err) -def iot_hub_configuration_show(cmd, config_id, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_hub_configuration_show( + cmd, config_id, hub_name=None, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) return _iot_hub_configuration_show(target=target, config_id=config_id) @@ -786,30 +1263,52 @@ def _iot_hub_configuration_show(target, config_id): raise CLIError(unpack_msrest_error(e)) -def iot_hub_configuration_list(cmd, hub_name=None, top=10, resource_group_name=None, login=None): - result = _iot_hub_configuration_list(cmd, hub_name=hub_name, top=top, - resource_group_name=resource_group_name, login=login) - filtered = [c for c in result if (c["content"].get("deviceContent") or c["content"].get("moduleContent"))] - return filtered[:top] - - -def iot_edge_deployment_list(cmd, hub_name=None, top=10, resource_group_name=None, login=None): - result = _iot_hub_configuration_list(cmd, hub_name=hub_name, top=top, - resource_group_name=resource_group_name, login=login) - - filtered = [c for c in result if c["content"].get("modulesContent")] - return filtered[:top] +def iot_hub_configuration_list( + cmd, hub_name=None, top=None, resource_group_name=None, login=None +): + result = _iot_hub_configuration_list( + cmd, + hub_name=hub_name, + resource_group_name=resource_group_name, + login=login, + ) + filtered = [ + c + for c in result + if ( + c["content"].get("deviceContent") is not None + or c["content"].get("moduleContent") is not None + ) + ] + return filtered[:top] # list[:None] == list[:len(list)] + + +def iot_edge_deployment_list( + cmd, hub_name=None, top=None, resource_group_name=None, login=None +): + result = _iot_hub_configuration_list( + cmd, + hub_name=hub_name, + resource_group_name=resource_group_name, + login=login, + ) + filtered = [c for c in result if c["content"].get("modulesContent") is not None] + return filtered[:top] # list[:None] == list[:len(list)] -def _iot_hub_configuration_list(cmd, hub_name=None, top=10, resource_group_name=None, login=None): - top = _process_top(top, upper_limit=100) - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def _iot_hub_configuration_list( + cmd, hub_name=None, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - result = service_sdk.configuration.get_configurations(top=top, raw=True).response.json() + result = service_sdk.configuration.get_configurations(raw=True).response.json() if not result: logger.info('No configurations found on hub "%s".', hub_name) return result @@ -817,36 +1316,57 @@ def _iot_hub_configuration_list(cmd, hub_name=None, top=10, resource_group_name= raise CLIError(unpack_msrest_error(e)) -def iot_hub_configuration_delete(cmd, config_id, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_hub_configuration_delete( + cmd, config_id, hub_name=None, resource_group_name=None, login=None, etag=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - config = _iot_hub_configuration_show(target=target, config_id=config_id) - etag = config.get("etag") - if etag: - headers = {} - headers["If-Match"] = '"{}"'.format(etag) - service_sdk.configuration.delete(id=config_id, custom_headers=headers) - return - raise LookupError("configuration etag not found") + headers = {} + headers["If-Match"] = '"{}"'.format(etag if etag else "*") + service_sdk.configuration.delete(id=config_id, custom_headers=headers) except CloudError as e: raise CLIError(unpack_msrest_error(e)) - except LookupError as err: - raise CLIError(err) -def iot_edge_deployment_metric_show(cmd, config_id, metric_id, metric_type='user', - hub_name=None, resource_group_name=None, login=None): - return iot_hub_configuration_metric_show(cmd, config_id=config_id, metric_id=metric_id, - metric_type=metric_type, hub_name=hub_name, - resource_group_name=resource_group_name, login=login) +def iot_edge_deployment_metric_show( + cmd, + config_id, + metric_id, + metric_type="user", + hub_name=None, + resource_group_name=None, + login=None, +): + return iot_hub_configuration_metric_show( + cmd, + config_id=config_id, + metric_id=metric_id, + metric_type=metric_type, + hub_name=hub_name, + resource_group_name=resource_group_name, + login=login, + ) -def iot_hub_configuration_metric_show(cmd, config_id, metric_id, metric_type='user', - hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_hub_configuration_metric_show( + cmd, + config_id, + metric_id, + metric_type="user", + hub_name=None, + resource_group_name=None, + login=None, +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) @@ -854,25 +1374,29 @@ def iot_hub_configuration_metric_show(cmd, config_id, metric_id, metric_type='us config = _iot_hub_configuration_show(target=target, config_id=config_id) metric_collection = None - if metric_type == 'system': - metric_collection = config['systemMetrics'].get('queries') + if metric_type == "system": + metric_collection = config["systemMetrics"].get("queries") else: - metric_collection = config['metrics'].get('queries') + metric_collection = config["metrics"].get("queries") if metric_id not in metric_collection: - raise CLIError("the metric '{}' is not defined in the device configuration '{}'".format(metric_id, config_id)) + raise CLIError( + "The {} metric '{}' is not defined in the configuration '{}'".format( + metric_type, metric_id, config_id + ) + ) metric_query = metric_collection[metric_id] query_args = [metric_query] - query_method = service_sdk.registry_manager.query_iot_hub + query_method = service_sdk.query.get_twins metric_result = _execute_query(query_args, query_method, None) output = {} - output['metric'] = metric_id - output['query'] = metric_query - output['result'] = metric_result + output["metric"] = metric_id + output["query"] = metric_query + output["result"] = metric_result return output except CloudError as e: @@ -881,8 +1405,14 @@ def iot_hub_configuration_metric_show(cmd, config_id, metric_id, metric_type='us # Device Twin -def iot_device_twin_show(cmd, device_id, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + +def iot_device_twin_show( + cmd, device_id, hub_name=None, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) return _iot_device_twin_show(target=target, device_id=device_id) @@ -891,74 +1421,105 @@ def _iot_device_twin_show(target, device_id): service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - return service_sdk.twin.get_device_twin(id=device_id, raw=True).response.json() + return service_sdk.devices.get_twin(id=device_id, raw=True).response.json() except CloudError as e: raise CLIError(unpack_msrest_error(e)) -def iot_device_twin_update(cmd, device_id, parameters, hub_name=None, resource_group_name=None, login=None): +def iot_twin_update_custom(instance, desired=None, tags=None): + payload = {} + is_patch = False + if desired: + is_patch = True + payload["properties"] = {"desired": process_json_arg(desired, "desired")} + + if tags: + is_patch = True + payload["tags"] = process_json_arg(tags, "tags") + + return payload if is_patch else instance + + +def iot_device_twin_update( + cmd, device_id, parameters, hub_name=None, resource_group_name=None, login=None, etag=None +): from azext_iot.common.utility import verify_transform - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: - etag = parameters.get("etag") - if etag: - headers = {} - headers["If-Match"] = '"{}"'.format(etag) - verify = {'properties.desired': dict} - if parameters.get('tags', None): - verify['tags'] = dict - verify_transform(parameters, verify) - return service_sdk.twin.update_device_twin( - id=device_id, - device_twin_info=parameters, - custom_headers=headers - ) - raise LookupError("device twin etag not found") + headers = {} + headers["If-Match"] = '"{}"'.format(etag if etag else "*") + verify = {} + if parameters.get("properties"): + if parameters["properties"].get("desired"): + verify = {"properties.desired": dict} + if parameters.get("tags"): + verify["tags"] = dict + verify_transform(parameters, verify) + return service_sdk.devices.update_twin( + id=device_id, device_twin_info=parameters, custom_headers=headers + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) - except (AttributeError, LookupError, TypeError) as err: + except (AttributeError, TypeError) as err: raise CLIError(err) -def iot_device_twin_replace(cmd, device_id, target_json, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_device_twin_replace( + cmd, device_id, target_json, hub_name=None, resource_group_name=None, login=None, etag=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) try: target_json = process_json_arg(target_json, argument_name="json") - device = _iot_device_twin_show(target=target, device_id=device_id) - etag = device.get("etag") - if etag: - headers = {} - headers["If-Match"] = '"{}"'.format(etag) - return service_sdk.twin.replace_device_twin( - id=device_id, - device_twin_info=target_json, - custom_headers=headers - ) - raise LookupError("device twin etag not found") + headers = {} + headers["If-Match"] = '"{}"'.format(etag if etag else "*") + return service_sdk.devices.replace_twin( + id=device_id, device_twin_info=target_json, custom_headers=headers + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) - except LookupError as err: - raise CLIError(err) -def iot_device_method(cmd, device_id, method_name, hub_name=None, method_payload="{}", - timeout=30, resource_group_name=None, login=None): - from azext_iot.sdk.service.models import CloudToDeviceMethod - from azext_iot.constants import METHOD_INVOKE_MAX_TIMEOUT_SEC, METHOD_INVOKE_MIN_TIMEOUT_SEC +def iot_device_method( + cmd, + device_id, + method_name, + hub_name=None, + method_payload="{}", + timeout=30, + resource_group_name=None, + login=None, +): + from azext_iot.constants import ( + METHOD_INVOKE_MAX_TIMEOUT_SEC, + METHOD_INVOKE_MIN_TIMEOUT_SEC, + ) if timeout > METHOD_INVOKE_MAX_TIMEOUT_SEC: - raise CLIError('timeout must not be over {} seconds'.format(METHOD_INVOKE_MAX_TIMEOUT_SEC)) + raise CLIError( + "timeout must not be over {} seconds".format(METHOD_INVOKE_MAX_TIMEOUT_SEC) + ) if timeout < METHOD_INVOKE_MIN_TIMEOUT_SEC: - raise CLIError('timeout must be at least {} seconds'.format(METHOD_INVOKE_MIN_TIMEOUT_SEC)) + raise CLIError( + "timeout must be at least {} seconds".format(METHOD_INVOKE_MIN_TIMEOUT_SEC) + ) - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) @@ -966,13 +1527,19 @@ def iot_device_method(cmd, device_id, method_name, hub_name=None, method_payload service_sdk.config.retry_policy.retries = 1 try: if method_payload: - method_payload = process_json_arg(method_payload, argument_name="method-payload") + method_payload = process_json_arg( + method_payload, argument_name="method-payload" + ) - method = CloudToDeviceMethod(method_name, timeout, timeout, method_payload) - return service_sdk.device_method.invoke_device_method( - device_id=device_id, - direct_method_request=method, - timeout=timeout + request_body = { + "methodName": method_name, + "payload": method_payload, + "responseTimeoutInSeconds": timeout, + "connectTimeoutInSeconds": timeout, + } + + return service_sdk.devices.invoke_method( + device_id=device_id, direct_method_request=request_body, timeout=timeout, ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) @@ -980,17 +1547,36 @@ def iot_device_method(cmd, device_id, method_name, hub_name=None, method_payload # Device Module Method Invoke -def iot_device_module_method(cmd, device_id, module_id, method_name, hub_name=None, method_payload="{}", - timeout=30, resource_group_name=None, login=None): - from azext_iot.sdk.service.models.cloud_to_device_method import CloudToDeviceMethod - from azext_iot.constants import METHOD_INVOKE_MAX_TIMEOUT_SEC, METHOD_INVOKE_MIN_TIMEOUT_SEC + +def iot_device_module_method( + cmd, + device_id, + module_id, + method_name, + hub_name=None, + method_payload="{}", + timeout=30, + resource_group_name=None, + login=None, +): + from azext_iot.constants import ( + METHOD_INVOKE_MAX_TIMEOUT_SEC, + METHOD_INVOKE_MIN_TIMEOUT_SEC, + ) if timeout > METHOD_INVOKE_MAX_TIMEOUT_SEC: - raise CLIError('timeout must not be over {} seconds'.format(METHOD_INVOKE_MAX_TIMEOUT_SEC)) + raise CLIError( + "timeout must not be over {} seconds".format(METHOD_INVOKE_MAX_TIMEOUT_SEC) + ) if timeout < METHOD_INVOKE_MIN_TIMEOUT_SEC: - raise CLIError('timeout must not be over {} seconds'.format(METHOD_INVOKE_MIN_TIMEOUT_SEC)) + raise CLIError( + "timeout must not be over {} seconds".format(METHOD_INVOKE_MIN_TIMEOUT_SEC) + ) - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target) service_sdk = resolver.get_sdk(SdkType.service_sdk) @@ -998,14 +1584,22 @@ def iot_device_module_method(cmd, device_id, module_id, method_name, hub_name=No service_sdk.config.retry_policy.retries = 1 try: if method_payload: - method_payload = process_json_arg(method_payload, argument_name="method-payload") + method_payload = process_json_arg( + method_payload, argument_name="method-payload" + ) + + request_body = { + "methodName": method_name, + "payload": method_payload, + "responseTimeoutInSeconds": timeout, + "connectTimeoutInSeconds": timeout, + } - method = CloudToDeviceMethod(method_name, timeout, timeout, method_payload) - return service_sdk.device_method.invoke_module_method( + return service_sdk.modules.invoke_method( device_id=device_id, module_id=module_id, - direct_method_request=method, - timeout=timeout + direct_method_request=request_body, + timeout=timeout, ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) @@ -1013,115 +1607,222 @@ def iot_device_module_method(cmd, device_id, module_id, method_name, hub_name=No # Utility -def iot_get_sas_token(cmd, hub_name=None, device_id=None, policy_name='iothubowner', key_type='primary', - duration=3600, resource_group_name=None, login=None, module_id=None): + +def iot_get_sas_token( + cmd, + hub_name=None, + device_id=None, + policy_name="iothubowner", + key_type="primary", + duration=3600, + resource_group_name=None, + login=None, + module_id=None, +): key_type = key_type.lower() policy_name = policy_name.lower() - if login and policy_name != 'iothubowner': - raise CLIError('You are unable to change the sas policy with a hub connection string login.') - if login and key_type != 'primary' and not device_id: - raise CLIError('For non-device sas, you are unable to change the key type with a connection string login.') + if login and policy_name != "iothubowner": + raise CLIError( + "You are unable to change the sas policy with a hub connection string login." + ) + if login and key_type != "primary" and not device_id: + raise CLIError( + "For non-device sas, you are unable to change the key type with a connection string login." + ) if module_id and not device_id: - raise CLIError('You are unable to get sas token for module without device information.') + raise CLIError( + "You are unable to get sas token for module without device information." + ) - return {'sas': _iot_build_sas_token(cmd, hub_name, device_id, module_id, - policy_name, key_type, duration, resource_group_name, login).generate_sas_token()} + return { + "sas": _iot_build_sas_token( + cmd, + hub_name, + device_id, + module_id, + policy_name, + key_type, + duration, + resource_group_name, + login, + ).generate_sas_token() + } -def _iot_build_sas_token(cmd, hub_name=None, device_id=None, module_id=None, policy_name='iothubowner', - key_type='primary', duration=3600, resource_group_name=None, login=None): - from azext_iot.common._azure import (parse_iot_device_connection_string, - parse_iot_device_module_connection_string) +def _iot_build_sas_token( + cmd, + hub_name=None, + device_id=None, + module_id=None, + policy_name="iothubowner", + key_type="primary", + duration=3600, + resource_group_name=None, + login=None, +): + from azext_iot.common._azure import ( + parse_iot_device_connection_string, + parse_iot_device_module_connection_string, + ) - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, policy_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, + resource_group_name=resource_group_name, + policy_name=policy_name, + login=login, + ) uri = None policy = None key = None if device_id: - logger.info('Obtaining device "%s" details from registry, using IoT Hub policy "%s"', device_id, policy_name) + logger.info( + 'Obtaining device "%s" details from registry, using IoT Hub policy "%s"', + device_id, + policy_name, + ) device = _iot_device_show(target, device_id) if module_id: module = _iot_device_module_show(target, device_id, module_id) - module_cs = _build_device_or_module_connection_string(entity=module, key_type=key_type) - uri = '{}/devices/{}/modules/{}'.format(target['entity'], device_id, module_id) + module_cs = _build_device_or_module_connection_string( + entity=module, key_type=key_type + ) + uri = "{}/devices/{}/modules/{}".format( + target["entity"], device_id, module_id + ) try: parsed_module_cs = parse_iot_device_module_connection_string(module_cs) except ValueError as e: logger.debug(e) - raise CLIError('This module does not support SAS auth.') + raise CLIError("This module does not support SAS auth.") - key = parsed_module_cs['SharedAccessKey'] + key = parsed_module_cs["SharedAccessKey"] else: - device_cs = _build_device_or_module_connection_string(entity=device, key_type=key_type) - uri = '{}/devices/{}'.format(target['entity'], device_id) + device_cs = _build_device_or_module_connection_string( + entity=device, key_type=key_type + ) + uri = "{}/devices/{}".format(target["entity"], device_id) try: parsed_device_cs = parse_iot_device_connection_string(device_cs) except ValueError as e: logger.debug(e) - raise CLIError('This device does not support SAS auth.') + raise CLIError("This device does not support SAS auth.") - key = parsed_device_cs['SharedAccessKey'] + key = parsed_device_cs["SharedAccessKey"] else: - uri = target['entity'] - policy = target['policy'] - key = target['primarykey'] if key_type == 'primary' else target['secondarykey'] + uri = target["entity"] + policy = target["policy"] + key = target["primarykey"] if key_type == "primary" else target["secondarykey"] return SasTokenAuthentication(uri, policy, key, duration) -def _build_device_or_module_connection_string(entity, key_type='primary'): - is_device = entity.get('moduleId') is None - template = 'HostName={};DeviceId={};{}' if is_device else 'HostName={};DeviceId={};ModuleId={};{}' - auth = entity['authentication'] +def _build_device_or_module_connection_string(entity, key_type="primary"): + is_device = entity.get("moduleId") is None + template = ( + "HostName={};DeviceId={};{}" + if is_device + else "HostName={};DeviceId={};ModuleId={};{}" + ) + auth = entity["authentication"] auth_type = auth["type"].lower() if auth_type == "sas": - key = 'SharedAccessKey={}'.format( - auth['symmetricKey']['primaryKey'] if key_type == 'primary' else auth['symmetricKey']['secondaryKey']) - elif auth_type in ['certificateauthority', 'selfsigned']: + key = "SharedAccessKey={}".format( + auth["symmetricKey"]["primaryKey"] + if key_type == "primary" + else auth["symmetricKey"]["secondaryKey"] + ) + elif auth_type in ["certificateauthority", "selfsigned"]: key = "x509=true" else: - raise CLIError('Unable to form target connection string') + raise CLIError("Unable to form target connection string") if is_device: - return template.format(entity.get('hub'), entity.get('deviceId'), key) + return template.format(entity.get("hub"), entity.get("deviceId"), key) else: - return template.format(entity.get('hub'), entity.get('deviceId'), entity.get('moduleId'), key) + return template.format( + entity.get("hub"), entity.get("deviceId"), entity.get("moduleId"), key + ) -def iot_get_device_connection_string(cmd, device_id, hub_name=None, key_type='primary', - resource_group_name=None, login=None): +def iot_get_device_connection_string( + cmd, + device_id, + hub_name=None, + key_type="primary", + resource_group_name=None, + login=None, +): result = {} - device = iot_device_show(cmd, device_id, - hub_name=hub_name, resource_group_name=resource_group_name, login=login) - result['connectionString'] = _build_device_or_module_connection_string(device, key_type) + device = iot_device_show( + cmd, + device_id, + hub_name=hub_name, + resource_group_name=resource_group_name, + login=login, + ) + result["connectionString"] = _build_device_or_module_connection_string( + device, key_type + ) return result -def iot_get_module_connection_string(cmd, device_id, module_id, hub_name=None, key_type='primary', - resource_group_name=None, login=None): +def iot_get_module_connection_string( + cmd, + device_id, + module_id, + hub_name=None, + key_type="primary", + resource_group_name=None, + login=None, +): result = {} - module = iot_device_module_show(cmd, device_id, module_id, - resource_group_name=resource_group_name, hub_name=hub_name, login=login) - result['connectionString'] = _build_device_or_module_connection_string(module, key_type) + module = iot_device_module_show( + cmd, + device_id, + module_id, + resource_group_name=resource_group_name, + hub_name=hub_name, + login=login, + ) + result["connectionString"] = _build_device_or_module_connection_string( + module, key_type + ) return result # Messaging -def iot_device_send_message(cmd, device_id, hub_name=None, data='Ping from Az CLI IoT Extension', - properties=None, msg_count=1, resource_group_name=None, login=None, qos=1): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + +def iot_device_send_message( + cmd, + device_id, + hub_name=None, + data="Ping from Az CLI IoT Extension", + properties=None, + msg_count=1, + resource_group_name=None, + login=None, + qos=1, +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) return _iot_device_send_message( target=target, device_id=device_id, data=data, properties=properties, msg_count=msg_count, - qos=qos) + qos=qos, + ) -def _iot_device_send_message(target, device_id, data, properties=None, msg_count=1, qos=1): +def _iot_device_send_message( + target, device_id, data, properties=None, msg_count=1, qos=1 +): from azext_iot.operations._mqtt import build_mqtt_device_username import paho.mqtt.publish as publish from paho.mqtt import client as mqtt @@ -1132,29 +1833,50 @@ def _iot_device_send_message(target, device_id, data, properties=None, msg_count if properties: properties = validate_key_value_pairs(properties) - sas = SasTokenAuthentication(target['entity'], target['policy'], target['primarykey'], 360).generate_sas_token() + sas = SasTokenAuthentication( + target["entity"], target["policy"], target["primarykey"], 360 + ).generate_sas_token() cwd = EXTENSION_ROOT - cert_path = os.path.join(cwd, 'digicert.pem') + cert_path = os.path.join(cwd, "digicert.pem") auth = { "username": build_mqtt_device_username(target["entity"], device_id), "password": sas, } - tls = {'ca_certs': cert_path, 'tls_version': ssl.PROTOCOL_SSLv23} - topic = 'devices/{}/messages/events/{}'.format(device_id, url_encode_dict(properties) if properties else '') + tls = {"ca_certs": cert_path, "tls_version": ssl.PROTOCOL_SSLv23} + topic = "devices/{}/messages/events/{}".format( + device_id, url_encode_dict(properties) if properties else "" + ) for _ in range(msg_count): - msgs.append({'topic': topic, 'payload': data, 'qos': int(qos)}) + msgs.append({"topic": topic, "payload": data, "qos": int(qos)}) try: - publish.multiple(msgs, client_id=device_id, hostname=target['entity'], - auth=auth, port=8883, protocol=mqtt.MQTTv311, tls=tls) + publish.multiple( + msgs, + client_id=device_id, + hostname=target["entity"], + auth=auth, + port=8883, + protocol=mqtt.MQTTv311, + tls=tls, + ) return except Exception as x: raise CLIError(x) -def iot_device_send_message_http(cmd, device_id, data, hub_name=None, headers=None, - resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_device_send_message_http( + cmd, + device_id, + data, + hub_name=None, + headers=None, + resource_group_name=None, + login=None, +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) return _iot_device_send_message_http(target, device_id, data, headers) @@ -1163,13 +1885,20 @@ def _iot_device_send_message_http(target, device_id, data, headers=None): device_sdk = resolver.get_sdk(SdkType.device_sdk) try: - return device_sdk.device.send_device_event(id=device_id, message=data, custom_headers=headers) + return device_sdk.device.send_device_event( + id=device_id, message=data, custom_headers=headers + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) -def iot_c2d_message_complete(cmd, device_id, etag, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_c2d_message_complete( + cmd, device_id, etag, hub_name=None, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) return _iot_c2d_message_complete(target, device_id, etag) @@ -1178,13 +1907,20 @@ def _iot_c2d_message_complete(target, device_id, etag): device_sdk = resolver.get_sdk(SdkType.device_sdk) try: - return device_sdk.device.complete_device_bound_notification(id=device_id, etag=etag) + return device_sdk.device.complete_device_bound_notification( + id=device_id, etag=etag + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) -def iot_c2d_message_reject(cmd, device_id, etag, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_c2d_message_reject( + cmd, device_id, etag, hub_name=None, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) return _iot_c2d_message_reject(target, device_id, etag) @@ -1193,13 +1929,20 @@ def _iot_c2d_message_reject(target, device_id, etag): device_sdk = resolver.get_sdk(SdkType.device_sdk) try: - return device_sdk.device.complete_device_bound_notification(id=device_id, etag=etag, reject='') + return device_sdk.device.complete_device_bound_notification( + id=device_id, etag=etag, reject="" + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) -def iot_c2d_message_abandon(cmd, device_id, etag, hub_name=None, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) +def iot_c2d_message_abandon( + cmd, device_id, etag, hub_name=None, resource_group_name=None, login=None +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) return _iot_c2d_message_abandon(target, device_id, etag) @@ -1208,17 +1951,46 @@ def _iot_c2d_message_abandon(target, device_id, etag): device_sdk = resolver.get_sdk(SdkType.device_sdk) try: - return device_sdk.device.abandon_device_bound_notification(id=device_id, etag=etag) + return device_sdk.device.abandon_device_bound_notification( + id=device_id, etag=etag + ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) -def iot_c2d_message_receive(cmd, device_id, hub_name=None, lock_timeout=60, resource_group_name=None, login=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) - return _iot_c2d_message_receive(target, device_id, lock_timeout) +def iot_c2d_message_receive( + cmd, + device_id, + hub_name=None, + lock_timeout=60, + resource_group_name=None, + login=None, + abandon=None, + complete=None, + reject=None, +): + ack = None + ack_vals = [abandon, complete, reject] + if any(ack_vals): + if len(list(filter(lambda val: val, ack_vals))) > 1: + raise CLIError( + "Only one c2d-message ack argument can be used [--complete, --abandon, --reject]" + ) + if abandon: + ack = SettleType.abandon.value + elif complete: + ack = SettleType.complete.value + elif reject: + ack = SettleType.reject.value + + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) + return _iot_c2d_message_receive(target, device_id, lock_timeout, ack) -def _iot_c2d_message_receive(target, device_id, lock_timeout=60): +def _iot_c2d_message_receive(target, device_id, lock_timeout=60, ack=None): from azext_iot.constants import MESSAGING_HTTP_C2D_SYSTEM_PROPERTIES resolver = SdkResolver(target=target, device_id=device_id) @@ -1226,26 +1998,54 @@ def _iot_c2d_message_receive(target, device_id, lock_timeout=60): request_headers = {} if lock_timeout: - request_headers['IotHub-MessageLockTimeout'] = str(lock_timeout) + request_headers["IotHub-MessageLockTimeout"] = str(lock_timeout) try: result = device_sdk.device.receive_device_bound_notification( - id=device_id, - custom_headers=request_headers, - raw=True).response + id=device_id, custom_headers=request_headers, raw=True + ).response if result and result.status_code == 200: payload = {"properties": {}} if "etag" in result.headers: - payload["etag"] = result.headers["etag"].strip('"') - - app_prop_prefix = 'iothub-app-' - app_prop_keys = [header for header in result.headers if header.lower().startswith(app_prop_prefix)] + eTag = result.headers["etag"].strip('"') + payload["etag"] = eTag + + if ack: + ack_response = {} + if ack == SettleType.abandon.value: + logger.debug("__Abandoning message__") + ack_response = device_sdk.device.abandon_device_bound_notification( + id=device_id, etag=eTag, raw=True + ) + elif ack == SettleType.reject.value: + logger.debug("__Rejecting message__") + ack_response = device_sdk.device.complete_device_bound_notification( + id=device_id, etag=eTag, reject="", raw=True + ) + else: + logger.debug("__Completing message__") + ack_response = device_sdk.device.complete_device_bound_notification( + id=device_id, etag=eTag, raw=True + ) + + payload["ack"] = ( + ack + if (ack_response and ack_response.response.status_code == 204) + else None + ) + + app_prop_prefix = "iothub-app-" + app_prop_keys = [ + header + for header in result.headers + if header.lower().startswith(app_prop_prefix) + ] app_props = {} for key in app_prop_keys: - app_props[key[len(app_prop_prefix):]] = result.headers[key] + app_props[key[len(app_prop_prefix) :]] = result.headers[key] if app_props: payload["properties"]["app"] = app_props @@ -1259,7 +2059,11 @@ def _iot_c2d_message_receive(target, device_id, lock_timeout=60): payload["properties"]["system"] = sys_props if result.text: - payload["data"] = result.text if not isinstance(result.text, six.binary_type) else result.text.decode("utf-8") + payload["data"] = ( + result.text + if not isinstance(result.text, six.binary_type) + else result.text.decode("utf-8") + ) return payload return @@ -1267,23 +2071,42 @@ def _iot_c2d_message_receive(target, device_id, lock_timeout=60): raise CLIError(unpack_msrest_error(e)) -def iot_c2d_message_send(cmd, device_id, hub_name=None, data='Ping from Az CLI IoT Extension', - message_id=None, correlation_id=None, user_id=None, content_encoding="utf-8", - content_type=None, expiry_time_utc=None, properties=None, ack=None, - wait_on_feedback=False, yes=False, repair=False, resource_group_name=None, login=None): - import importlib +def iot_c2d_message_send( + cmd, + device_id, + hub_name=None, + data="Ping from Az CLI IoT Extension", + message_id=None, + correlation_id=None, + user_id=None, + content_encoding="utf-8", + content_type=None, + expiry_time_utc=None, + properties=None, + ack=None, + wait_on_feedback=False, + yes=False, + repair=False, + resource_group_name=None, + login=None, +): from azext_iot.common.deps import ensure_uamqp from azext_iot.common.utility import validate_min_python_version validate_min_python_version(3, 4) if wait_on_feedback and not ack: - raise CLIError('To wait on device feedback, ack must be "full", "negative" or "positive"') + raise CLIError( + 'To wait on device feedback, ack must be "full", "negative" or "positive"' + ) config = cmd.cli_ctx.config ensure_uamqp(config, yes, repair) - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) if properties: properties = validate_key_value_pairs(properties) @@ -1294,48 +2117,74 @@ def iot_c2d_message_send(cmd, device_id, hub_name=None, data='Ping from Az CLI I if user_msg_expiry < now_in_milli: raise CLIError("Message expiry time utc is in the past!") - events3 = importlib.import_module('azext_iot.operations.events3._events') + from azext_iot.monitor import event - msg_id, errors = events3.send_c2d_message(target=target, device_id=device_id, data=data, - message_id=message_id, correlation_id=correlation_id, - user_id=user_id, content_encoding=content_encoding, - content_type=content_type, expiry_time_utc=expiry_time_utc, - properties=properties, ack=ack) + msg_id, errors = event.send_c2d_message( + target=target, + device_id=device_id, + data=data, + message_id=message_id, + correlation_id=correlation_id, + user_id=user_id, + content_encoding=content_encoding, + content_type=content_type, + expiry_time_utc=expiry_time_utc, + properties=properties, + ack=ack, + ) if errors: - raise CLIError('C2D message error: {}, use --debug for more details.'.format(errors)) + raise CLIError( + "C2D message error: {}, use --debug for more details.".format(errors) + ) if wait_on_feedback: _iot_hub_monitor_feedback(target=target, device_id=device_id, wait_on_id=msg_id) -def iot_simulate_device(cmd, device_id, hub_name=None, receive_settle='complete', - data='Ping from Az CLI IoT Extension', msg_count=100, - msg_interval=3, protocol_type='mqtt', properties=None, - resource_group_name=None, login=None): +def iot_simulate_device( + cmd, + device_id, + hub_name=None, + receive_settle="complete", + data="Ping from Az CLI IoT Extension", + msg_count=100, + msg_interval=3, + protocol_type="mqtt", + properties=None, + resource_group_name=None, + login=None, +): import sys import uuid import datetime import json from azext_iot.operations._mqtt import mqtt_client_wrap from azext_iot.common.utility import execute_onthread - from azext_iot.constants import MIN_SIM_MSG_INTERVAL, MIN_SIM_MSG_COUNT, SIM_RECEIVE_SLEEP_SEC + from azext_iot.constants import ( + MIN_SIM_MSG_INTERVAL, + MIN_SIM_MSG_COUNT, + SIM_RECEIVE_SLEEP_SEC, + ) protocol_type = protocol_type.lower() if protocol_type == ProtocolType.mqtt.name: - if receive_settle != 'complete': + if receive_settle != "complete": raise CLIError('mqtt protocol only supports settle type of "complete"') if msg_interval < MIN_SIM_MSG_INTERVAL: - raise CLIError('msg interval must be at least {}'.format(MIN_SIM_MSG_INTERVAL)) + raise CLIError("msg interval must be at least {}".format(MIN_SIM_MSG_INTERVAL)) if msg_count < MIN_SIM_MSG_COUNT: - raise CLIError('msg count must be at least {}'.format(MIN_SIM_MSG_COUNT)) + raise CLIError("msg count must be at least {}".format(MIN_SIM_MSG_COUNT)) properties_to_send = _iot_simulate_get_default_properties(protocol_type) user_properties = validate_key_value_pairs(properties) or {} properties_to_send.update(user_properties) - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) token = None class generator(object): @@ -1344,14 +2193,17 @@ def __init__(self): def generate(self, jsonify=True): self.calls += 1 - payload = {'id': str(uuid.uuid4()), 'timestamp': str(datetime.datetime.utcnow()), - 'data': str(data + ' #{}'.format(self.calls))} + payload = { + "id": str(uuid.uuid4()), + "timestamp": str(datetime.datetime.utcnow()), + "data": str(data + " #{}".format(self.calls)), + } return json.dumps(payload) if jsonify else payload def http_wrap(target, device_id, generator): d = generator.generate(False) _iot_device_send_message_http(target, device_id, d, headers=properties_to_send) - six.print_('.', end='', flush=True) + six.print_(".", end="", flush=True) try: if protocol_type == ProtocolType.mqtt.name: @@ -1359,16 +2211,20 @@ def http_wrap(target, device_id, generator): target=target, device_id=device_id, properties=properties_to_send, - sas_duration=(msg_count * msg_interval) + 60 # int type is enforced for msg_count and msg_interval + sas_duration=(msg_count * msg_interval) + + 60, # int type is enforced for msg_count and msg_interval ) wrap.execute(generator(), publish_delay=msg_interval, msg_count=msg_count) else: - six.print_('Sending and receiving events via https') - token, op = execute_onthread(method=http_wrap, - args=[target, device_id, generator()], - interval=msg_interval, max_runs=msg_count, - return_handle=True) - while True and op.is_alive(): + six.print_("Sending and receiving events via https") + token, op = execute_onthread( + method=http_wrap, + args=[target, device_id, generator()], + interval=msg_interval, + max_runs=msg_count, + return_handle=True, + ) + while op.is_alive(): _handle_c2d_msg(target, device_id, receive_settle) sleep(SIM_RECEIVE_SLEEP_SEC) @@ -1381,6 +2237,19 @@ def http_wrap(target, device_id, generator): token.set() +def iot_c2d_message_purge( + cmd, device_id, hub_name=None, resource_group_name=None, login=None, +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) + resolver = SdkResolver(target=target) + service_sdk = resolver.get_sdk(SdkType.service_sdk) + + return service_sdk.cloud_to_device_messages.purge_cloud_to_device_message_queue(device_id) + + def _iot_simulate_get_default_properties(protocol): default_properties = {} is_mqtt = protocol == ProtocolType.mqtt.name @@ -1391,44 +2260,141 @@ def _iot_simulate_get_default_properties(protocol): return default_properties -def _handle_c2d_msg(target, device_id, receive_settle): - result = _iot_c2d_message_receive(target, device_id) +def _handle_c2d_msg(target, device_id, receive_settle, lock_timeout=60): + result = _iot_c2d_message_receive(target, device_id, lock_timeout) if result: six.print_() - six.print_('__Received C2D Message__') + six.print_("__Received C2D Message__") six.print_(result) - if receive_settle == 'reject': - six.print_('__Rejecting message__') - _iot_c2d_message_reject(target, device_id, result['etag']) - elif receive_settle == 'abandon': - six.print_('__Abandoning message__') - _iot_c2d_message_abandon(target, device_id, result['etag']) + if receive_settle == "reject": + six.print_("__Rejecting message__") + _iot_c2d_message_reject(target, device_id, result["etag"]) + elif receive_settle == "abandon": + six.print_("__Abandoning message__") + _iot_c2d_message_abandon(target, device_id, result["etag"]) else: - six.print_('__Completing message__') - _iot_c2d_message_complete(target, device_id, result['etag']) + six.print_("__Completing message__") + _iot_c2d_message_complete(target, device_id, result["etag"]) return True return False -def iot_device_export(cmd, hub_name, blob_container_uri, include_keys=False, resource_group_name=None): +def iot_device_export( + cmd, + hub_name, + blob_container_uri, + include_keys=False, + storage_authentication_type=None, + resource_group_name=None, +): from azext_iot._factory import iot_hub_service_factory + client = iot_hub_service_factory(cmd.cli_ctx) - target = get_iot_hub_connection_string(client, hub_name, resource_group_name) - return client.export_devices(target['resourcegroup'], hub_name, blob_container_uri, not include_keys) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name + ) + + if exists(blob_container_uri): + blob_container_uri = read_file_content(blob_container_uri) + if ensure_iothub_sdk_min_version("0.12.0"): + from azure.mgmt.iothub.models import ExportDevicesRequest + from azext_iot.common.shared import AuthenticationType -def iot_device_import(cmd, hub_name, input_blob_container_uri, output_blob_container_uri, resource_group_name=None): + storage_authentication_type = ( + AuthenticationType(storage_authentication_type).name + if storage_authentication_type + else None + ) + export_request = ExportDevicesRequest( + export_blob_container_uri=blob_container_uri, + exclude_keys=not include_keys, + authentication_type=storage_authentication_type, + ) + return client.export_devices( + target["resourcegroup"], hub_name, export_devices_parameters=export_request, + ) + if storage_authentication_type: + raise CLIError( + "Device export authentication-type properties require a dependency of azure-mgmt-iothub>=0.12.0" + ) + return client.export_devices( + target["resourcegroup"], + hub_name, + export_blob_container_uri=blob_container_uri, + exclude_keys=not include_keys, + ) + + +def iot_device_import( + cmd, + hub_name, + input_blob_container_uri, + output_blob_container_uri, + storage_authentication_type=None, + resource_group_name=None, +): from azext_iot._factory import iot_hub_service_factory + client = iot_hub_service_factory(cmd.cli_ctx) - target = get_iot_hub_connection_string(client, hub_name, resource_group_name) - return client.import_devices(target['resourcegroup'], hub_name, - input_blob_container_uri, output_blob_container_uri) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name + ) + + if exists(input_blob_container_uri): + input_blob_container_uri = read_file_content(input_blob_container_uri) + + if exists(output_blob_container_uri): + output_blob_container_uri = read_file_content(output_blob_container_uri) + + if ensure_iothub_sdk_min_version("0.12.0"): + from azure.mgmt.iothub.models import ImportDevicesRequest + from azext_iot.common.shared import AuthenticationType + + storage_authentication_type = ( + AuthenticationType(storage_authentication_type).name + if storage_authentication_type + else None + ) + import_request = ImportDevicesRequest( + input_blob_container_uri=input_blob_container_uri, + output_blob_container_uri=output_blob_container_uri, + input_blob_name=None, + output_blob_name=None, + authentication_type=storage_authentication_type, + ) + return client.import_devices( + target["resourcegroup"], hub_name, import_devices_parameters=import_request, + ) + if storage_authentication_type: + raise CLIError( + "Device import authentication-type properties require a dependency of azure-mgmt-iothub>=0.12.0" + ) + return client.import_devices( + target["resourcegroup"], + hub_name, + input_blob_container_uri=input_blob_container_uri, + output_blob_container_uri=output_blob_container_uri, + ) -def iot_device_upload_file(cmd, device_id, file_path, content_type, hub_name=None, resource_group_name=None, login=None): +def iot_device_upload_file( + cmd, + device_id, + file_path, + content_type, + hub_name=None, + resource_group_name=None, + login=None, +): from azext_iot.sdk.iothub.device.models import FileUploadCompletionStatus - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) resolver = SdkResolver(target=target, device_id=device_id) device_sdk = resolver.get_sdk(SdkType.device_sdk) @@ -1441,50 +2407,81 @@ def iot_device_upload_file(cmd, device_id, file_path, content_type, hub_name=Non try: upload_meta = device_sdk.device.create_file_upload_sas_uri( - device_id=device_id, - blob_name=file_name, - raw=True + device_id=device_id, blob_name=file_name, raw=True ).response.json() storage_endpoint = "{}/{}/{}{}".format( upload_meta["hostName"], upload_meta["containerName"], upload_meta["blobName"], - upload_meta["sasToken"] + upload_meta["sasToken"], ) completion_status = FileUploadCompletionStatus( - correlation_id=upload_meta["correlationId"], - is_success=True + correlation_id=upload_meta["correlationId"], is_success=True ) upload_response = device_sdk.device.upload_file_to_container( storage_endpoint=storage_endpoint, content=content, - content_type=content_type + content_type=content_type, ) completion_status.status_code = upload_response.status_code completion_status.status_reason = upload_response.reason return device_sdk.device.update_file_upload_status( - device_id=device_id, - file_upload_completion_status=completion_status + device_id=device_id, file_upload_completion_status=completion_status ) except CloudError as e: raise CLIError(unpack_msrest_error(e)) -def iot_hub_monitor_events(cmd, hub_name=None, device_id=None, consumer_group='$Default', timeout=300, - enqueued_time=None, resource_group_name=None, yes=False, properties=None, repair=False, - login=None, content_type=None, device_query=None): +def iot_hub_monitor_events( + cmd, + hub_name=None, + device_id=None, + interface=None, + module_id=None, + consumer_group="$Default", + timeout=300, + enqueued_time=None, + resource_group_name=None, + yes=False, + properties=None, + repair=False, + login=None, + content_type=None, + device_query=None, +): try: - _iot_hub_monitor_events(cmd, hub_name=hub_name, device_id=device_id, - consumer_group=consumer_group, timeout=timeout, enqueued_time=enqueued_time, - resource_group_name=resource_group_name, yes=yes, properties=properties, - repair=repair, login=login, content_type=content_type, device_query=device_query) + _iot_hub_monitor_events( + cmd, + hub_name=hub_name, + device_id=device_id, + interface_name=interface, + module_id=module_id, + consumer_group=consumer_group, + timeout=timeout, + enqueued_time=enqueued_time, + resource_group_name=resource_group_name, + yes=yes, + properties=properties, + repair=repair, + login=login, + content_type=content_type, + device_query=device_query, + ) except RuntimeError as e: raise CLIError(e) -def iot_hub_monitor_feedback(cmd, hub_name=None, device_id=None, yes=False, - wait_on_id=None, repair=False, resource_group_name=None, login=None): +def iot_hub_monitor_feedback( + cmd, + hub_name=None, + device_id=None, + yes=False, + wait_on_id=None, + repair=False, + resource_group_name=None, + login=None, +): from azext_iot.common.deps import ensure_uamqp from azext_iot.common.utility import validate_min_python_version @@ -1493,102 +2490,291 @@ def iot_hub_monitor_feedback(cmd, hub_name=None, device_id=None, yes=False, config = cmd.cli_ctx.config ensure_uamqp(config, yes, repair) - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, login=login) - - return _iot_hub_monitor_feedback(target=target, device_id=device_id, wait_on_id=wait_on_id) + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) + return _iot_hub_monitor_feedback( + target=target, device_id=device_id, wait_on_id=wait_on_id + ) -def iot_hub_distributed_tracing_show(cmd, hub_name, device_id, resource_group_name=None): - device_twin = _iot_hub_distributed_tracing_show(cmd, hub_name, device_id, resource_group_name) - return _customize_device_tracing_output(device_twin['deviceId'], device_twin['properties']['desired'], - device_twin['properties']['reported']) +def iot_hub_distributed_tracing_show( + cmd, hub_name, device_id, resource_group_name=None, login=None, +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, resource_group_name=resource_group_name, login=login + ) -def _iot_hub_monitor_events(cmd, interface_name=None, pnp_context=None, - hub_name=None, device_id=None, consumer_group='$Default', timeout=300, - enqueued_time=None, resource_group_name=None, yes=False, properties=None, repair=False, - login=None, content_type=None, device_query=None): - import importlib + device_twin = _iot_hub_distributed_tracing_show(target=target, device_id=device_id) + return _customize_device_tracing_output( + device_twin["deviceId"], + device_twin["properties"]["desired"], + device_twin["properties"]["reported"], + ) - (enqueued_time, properties, timeout, output) = init_monitoring(cmd, timeout, properties, enqueued_time, repair, yes) - events3 = importlib.import_module('azext_iot.operations.events3._events') - builders = importlib.import_module('azext_iot.operations.events3._builders') +def _iot_hub_monitor_events( + cmd, + interface_name=None, + module_id=None, + hub_name=None, + device_id=None, + consumer_group="$Default", + timeout=300, + enqueued_time=None, + resource_group_name=None, + yes=False, + properties=None, + repair=False, + login=None, + content_type=None, + device_query=None, +): + (enqueued_time, properties, timeout, output) = init_monitoring( + cmd, timeout, properties, enqueued_time, repair, yes + ) device_ids = {} if device_query: - devices_result = iot_query(cmd, device_query, hub_name, None, resource_group_name, login=login) + devices_result = iot_query( + cmd, device_query, hub_name, None, resource_group_name, login=login + ) if devices_result: for device_result in devices_result: - device_ids[device_result['deviceId']] = True + device_ids[device_result["deviceId"]] = True + + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, + resource_group_name=resource_group_name, + include_events=True, + login=login, + ) + + from azext_iot.monitor.builders import hub_target_builder + from azext_iot.monitor.handlers import CommonHandler + from azext_iot.monitor.telemetry import start_single_monitor + from azext_iot.monitor.utility import generate_on_start_string + from azext_iot.monitor.models.arguments import ( + CommonParserArguments, + CommonHandlerArguments, + ) - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name, include_events=True, login=login) + target = hub_target_builder.EventTargetBuilder().build_iot_hub_target(target) + target.add_consumer_group(consumer_group) - eventHubTarget = builders.EventTargetBuilder().build_iot_hub_target(target) + on_start_string = generate_on_start_string(device_id=device_id) - events3.executor(eventHubTarget, - consumer_group=consumer_group, - enqueued_time=enqueued_time, - properties=properties, - timeout=timeout, - device_id=device_id, - output=output, - content_type=content_type, - devices=device_ids, - interface_name=interface_name, - pnp_context=pnp_context) + parser_args = CommonParserArguments( + properties=properties, content_type=content_type + ) + handler_args = CommonHandlerArguments( + output=output, + common_parser_args=parser_args, + devices=device_ids, + device_id=device_id, + interface_name=interface_name, + module_id=module_id, + ) + handler = CommonHandler(handler_args) + + start_single_monitor( + target=target, + enqueued_time_utc=enqueued_time, + on_start_string=on_start_string, + on_message_received=handler.parse_message, + timeout=timeout, + ) + + +def iot_hub_distributed_tracing_update( + cmd, + hub_name, + device_id, + sampling_mode, + sampling_rate, + resource_group_name=None, + login=None, +): + discovery = IotHubDiscovery(cmd) + target = discovery.get_target( + hub_name=hub_name, + resource_group_name=resource_group_name, + include_events=True, + login=login, + ) -def iot_hub_distributed_tracing_update(cmd, hub_name, device_id, sampling_mode, sampling_rate, - resource_group_name=None): if int(sampling_rate) not in range(0, 101): - raise CLIError('Sampling rate is a percentage, So only values from 0 to 100(inclusive) are permitted.') - device_twin = _iot_hub_distributed_tracing_show(cmd, hub_name, device_id, resource_group_name) - if TRACING_PROPERTY not in device_twin['properties']['desired']: - device_twin['properties']['desired'][TRACING_PROPERTY] = {} - device_twin['properties']['desired'][TRACING_PROPERTY]['sampling_rate'] = int(sampling_rate) - device_twin['properties']['desired'][TRACING_PROPERTY]['sampling_mode'] = 1 if sampling_mode.lower() == 'on' else 2 - result = iot_device_twin_update(cmd, device_id, device_twin, hub_name, resource_group_name) - return _customize_device_tracing_output(result.device_id, result.properties.desired, - result.properties.reported) + raise CLIError( + "Sampling rate is a percentage, So only values from 0 to 100(inclusive) are permitted." + ) + device_twin = _iot_hub_distributed_tracing_show(target=target, device_id=device_id) + if TRACING_PROPERTY not in device_twin["properties"]["desired"]: + device_twin["properties"]["desired"][TRACING_PROPERTY] = {} + device_twin["properties"]["desired"][TRACING_PROPERTY]["sampling_rate"] = int( + sampling_rate + ) + device_twin["properties"]["desired"][TRACING_PROPERTY]["sampling_mode"] = ( + 1 if sampling_mode.lower() == "on" else 2 + ) + result = iot_device_twin_update( + cmd, device_id, device_twin, hub_name, resource_group_name, login + ) + return _customize_device_tracing_output( + result.device_id, result.properties.desired, result.properties.reported + ) + + +def iot_hub_connection_string_show( + cmd, + hub_name=None, + resource_group_name=None, + policy_name="iothubowner", + key_type=KeyType.primary.value, + show_all=False, + default_eventhub=False, +): + discovery = IotHubDiscovery(cmd) + + if hub_name is None: + hubs = discovery.get_iothubs(resource_group_name) + if hubs is None: + raise CLIError("No IoT Hub found.") + + def conn_str_getter(hub): + return _get_hub_connection_string( + discovery, hub, policy_name, key_type, show_all, default_eventhub + ) + + connection_strings = [] + for hub in hubs: + if hub.properties.state == IoTHubStateType.Active.value: + try: + connection_strings.append({ + "name": hub.name, + "connectionString": conn_str_getter(hub) + if show_all + else conn_str_getter(hub)[0], + }) + except: + logger.warning(f"Warning: The IoT Hub {hub.name} in resource group " + + f"{hub.additional_properties['resourcegroup']} does " + + f"not have the target policy {policy_name}.") + else: + logger.warning(f"Warning: The IoT Hub {hub.name} in resource group " + + f"{hub.additional_properties['resourcegroup']} is skipped " + + "because the hub is not active.") + return connection_strings + + hub = discovery.find_iothub(hub_name, resource_group_name) + if hub: + conn_str = _get_hub_connection_string( + discovery, hub, policy_name, key_type, show_all, default_eventhub + ) + return {"connectionString": conn_str if show_all else conn_str[0]} + + +def _get_hub_connection_string( + discovery, hub, policy_name, key_type, show_all, default_eventhub +): + + policies = [] + if show_all: + policies.extend( + discovery.get_policies(hub.name, hub.additional_properties["resourcegroup"]) + ) + else: + policies.append( + discovery.find_policy( + hub.name, hub.additional_properties["resourcegroup"], policy_name + ) + ) + + if default_eventhub: + cs_template_eventhub = ( + "Endpoint={};SharedAccessKeyName={};SharedAccessKey={};EntityPath={}" + ) + endpoint = hub.properties.event_hub_endpoints["events"].endpoint + entityPath = hub.properties.event_hub_endpoints["events"].path + return [ + cs_template_eventhub.format( + endpoint, + p.key_name, + p.secondary_key + if key_type == KeyType.secondary.value + else p.primary_key, + entityPath, + ) + for p in policies + if "serviceconnect" in (p.rights.value.lower() if isinstance(p.rights, (Enum, EnumMeta)) else p.rights.lower()) + ] + + hostname = hub.properties.host_name + cs_template = "HostName={};SharedAccessKeyName={};SharedAccessKey={}" + return [ + cs_template.format( + hostname, + p.key_name, + p.secondary_key if key_type == KeyType.secondary.value else p.primary_key, + ) + for p in policies + ] def _iot_hub_monitor_feedback(target, device_id, wait_on_id): - import importlib + from azext_iot.monitor import event - events3 = importlib.import_module('azext_iot.operations.events3._events') - events3.monitor_feedback(target=target, device_id=device_id, wait_on_id=wait_on_id, token_duration=3600) + event.monitor_feedback( + target=target, device_id=device_id, wait_on_id=wait_on_id, token_duration=3600 + ) -def _iot_hub_distributed_tracing_show(cmd, hub_name, device_id, resource_group_name=None): - target = get_iot_hub_connection_string(cmd, hub_name, resource_group_name) - device_twin = iot_device_twin_show(cmd, device_id, hub_name, resource_group_name) +def _iot_hub_distributed_tracing_show(target, device_id): + device_twin = _iot_device_twin_show(target=target, device_id=device_id) _validate_device_tracing(target, device_twin) return device_twin def _validate_device_tracing(target, device_twin): - if target['location'].lower() not in TRACING_ALLOWED_FOR_LOCATION: - raise CLIError('Distributed tracing isn\'t supported for the hub located at "{}" location.' - .format(target['location'])) - if target['sku_tier'].lower() != TRACING_ALLOWED_FOR_SKU: - raise CLIError('Distributed tracing isn\'t supported for the hub belongs to "{}" sku tier.' - .format(target['sku_tier'])) - if device_twin['capabilities']['iotEdge']: - raise CLIError('The device "{}" should be non-edge device.'.format(device_twin['deviceId'])) + if target["location"].lower() not in TRACING_ALLOWED_FOR_LOCATION: + raise CLIError( + 'Distributed tracing isn\'t supported for the hub located at "{}" location.'.format( + target["location"] + ) + ) + if target["sku_tier"].lower() != TRACING_ALLOWED_FOR_SKU: + raise CLIError( + 'Distributed tracing isn\'t supported for the hub belongs to "{}" sku tier.'.format( + target["sku_tier"] + ) + ) + if device_twin["capabilities"]["iotEdge"]: + raise CLIError( + 'The device "{}" should be non-edge device.'.format(device_twin["deviceId"]) + ) def _customize_device_tracing_output(device_id, desired, reported): output = {} desired_tracing = desired.get(TRACING_PROPERTY, None) if desired_tracing: - output['deviceId'] = device_id - output['samplingMode'] = 'enabled' if desired_tracing.get('sampling_mode') == 1 else 'disabled' - output['samplingRate'] = '{}%'.format(desired_tracing.get('sampling_rate')) - output['isSynced'] = False + output["deviceId"] = device_id + output["samplingMode"] = ( + "enabled" if desired_tracing.get("sampling_mode") == 1 else "disabled" + ) + output["samplingRate"] = "{}%".format(desired_tracing.get("sampling_rate")) + output["isSynced"] = False reported_tracing = reported.get(TRACING_PROPERTY, None) - if (reported_tracing and - desired_tracing.get('sampling_mode') == reported_tracing.get('sampling_mode').get('value', None) and - desired_tracing.get('sampling_rate') == reported_tracing.get('sampling_rate').get('value', None)): - output['isSynced'] = True + if ( + reported_tracing + and desired_tracing.get("sampling_mode") + == reported_tracing.get("sampling_mode").get("value", None) + and desired_tracing.get("sampling_rate") + == reported_tracing.get("sampling_rate").get("value", None) + ): + output["isSynced"] = True return output diff --git a/azext_iot/operations/pnp.py b/azext_iot/operations/pnp.py deleted file mode 100644 index ed6d3be13..000000000 --- a/azext_iot/operations/pnp.py +++ /dev/null @@ -1,234 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import json -from os.path import exists -from knack.log import get_logger -from knack.util import CLIError -from azext_iot.common.shared import SdkType, PnPModelType -from azext_iot.common._azure import get_iot_pnp_connection_string -from azext_iot.sdk.pnp.models import SearchOptions -from azext_iot._factory import _bind_sdk -from azext_iot.constants import PNP_API_VERSION, PNP_ENDPOINT -from azext_iot.common.utility import (unpack_pnp_http_error, - get_sas_token, - shell_safe_json_parse, - read_file_content, - looks_like_file) - -logger = get_logger(__name__) - - -def iot_pnp_interface_publish(cmd, interface, repo_endpoint=PNP_ENDPOINT, repo_id=None, login=None): - _validate_repository(repo_id, login) - model_list = _iot_pnp_model_list(cmd, repo_endpoint, repo_id, interface, PnPModelType.interface, - -1, login=login) - if model_list and model_list[0].urn_id == interface: - etag = model_list[0].etag - else: - raise CLIError('No PnP Model definition found for @id "{}"'.format(interface)) - - target_interface = _iot_pnp_model_show(cmd, repo_endpoint, repo_id, - interface, False, PnPModelType.interface, login=login) - - return _iot_pnp_model_publish(cmd, repo_endpoint, repo_id, interface, target_interface, - etag, login=login) - - -def iot_pnp_interface_create(cmd, interface_definition, repo_endpoint=PNP_ENDPOINT, repo_id=None, login=None): - _validate_repository(repo_id, login) - return _iot_pnp_model_create_or_update(cmd, repo_endpoint, repo_id, interface_definition, - PnPModelType.interface, False, login=login) - - -def iot_pnp_interface_update(cmd, interface_definition, repo_endpoint=PNP_ENDPOINT, repo_id=None, login=None): - _validate_repository(repo_id, login) - return _iot_pnp_model_create_or_update(cmd, repo_endpoint, repo_id, interface_definition, - PnPModelType.interface, True, login=login) - - -def iot_pnp_interface_show(cmd, interface, repo_endpoint=PNP_ENDPOINT, repo_id=None, login=None): - return _iot_pnp_model_show(cmd, repo_endpoint, repo_id, - interface, False, PnPModelType.interface, login=login) - - -def iot_pnp_interface_list(cmd, repo_endpoint=PNP_ENDPOINT, repo_id=None, search_string=None, - top=1000, login=None): - return _iot_pnp_model_list(cmd, repo_endpoint, repo_id, - search_string, PnPModelType.interface, - top, login=login) - - -def iot_pnp_interface_delete(cmd, interface, repo_endpoint=PNP_ENDPOINT, repo_id=None, login=None): - _validate_repository(repo_id, login) - return _iot_pnp_model_delete(cmd, repo_endpoint, repo_id, interface, login) - - -def iot_pnp_model_publish(cmd, model, repo_endpoint=PNP_ENDPOINT, repo_id=None, login=None): - _validate_repository(repo_id, login) - model_list = _iot_pnp_model_list(cmd, repo_endpoint, repo_id, model, PnPModelType.capabilityModel, - -1, login=login) - if model_list and model_list[0].urn_id == model: - etag = model_list[0].etag - else: - raise CLIError('No PnP Model definition found for @id "{}"'.format(model)) - - target_model = _iot_pnp_model_show(cmd, repo_endpoint, repo_id, - model, False, PnPModelType.capabilityModel, login=login) - return _iot_pnp_model_publish(cmd, repo_endpoint, repo_id, model, target_model, - etag, login=login) - - -def iot_pnp_model_create(cmd, model_definition, repo_endpoint=PNP_ENDPOINT, repo_id=None, login=None): - _validate_repository(repo_id, login) - return _iot_pnp_model_create_or_update(cmd, repo_endpoint, repo_id, model_definition, - PnPModelType.capabilityModel, False, login=login) - - -def iot_pnp_model_update(cmd, model_definition, repo_endpoint=PNP_ENDPOINT, repo_id=None, login=None): - _validate_repository(repo_id, login) - return _iot_pnp_model_create_or_update(cmd, repo_endpoint, repo_id, model_definition, - PnPModelType.capabilityModel, True, login=login) - - -def iot_pnp_model_show(cmd, model, repo_endpoint=PNP_ENDPOINT, repo_id=None, expand=False, login=None): - return _iot_pnp_model_show(cmd, repo_endpoint, repo_id, - model, expand, PnPModelType.capabilityModel, login=login) - - -def iot_pnp_model_list(cmd, repo_endpoint=PNP_ENDPOINT, repo_id=None, search_string=None, - top=1000, login=None): - return _iot_pnp_model_list(cmd, repo_endpoint, repo_id, - search_string, PnPModelType.capabilityModel, - top, login=login) - - -def iot_pnp_model_delete(cmd, model, repo_endpoint=PNP_ENDPOINT, repo_id=None, login=None): - _validate_repository(repo_id, login) - return _iot_pnp_model_delete(cmd, repo_endpoint, repo_id, model, login) - - -def _iot_pnp_model_publish(cmd, endpoint, repository, model_id, model_def, etag, login): - - target = get_iot_pnp_connection_string(cmd, endpoint, repository, login=login) - pnp_sdk, errors = _bind_sdk(target, SdkType.pnp_sdk) - - contents = json.loads(json.dumps(model_def, separators=(',', ':'), indent=2)) - try: - headers = get_sas_token(target) - return pnp_sdk.create_or_update_model(model_id, - api_version=PNP_API_VERSION, - content=contents, - if_match=etag, - custom_headers=headers) - except errors.HttpOperationError as e: - raise CLIError(unpack_pnp_http_error(e)) - - -def _iot_pnp_model_create_or_update(cmd, endpoint, repository, model_def, pnpModelType, is_update, login): - - target = get_iot_pnp_connection_string(cmd, endpoint, repository, login=login) - pnp_sdk, errors = _bind_sdk(target, SdkType.pnp_sdk) - etag = None - model_def = _validate_model_definition(model_def) - model_id = model_def.get('@id') - if not model_id: - raise CLIError('PnP Model definition requires @id! Please include @id and try again.') - - if is_update: - model_list = _iot_pnp_model_list(cmd, endpoint, repository, model_id, pnpModelType, - -1, login=login) - if model_list and model_list[0].urn_id == model_id: - etag = model_list[0].etag - else: - raise CLIError('No PnP Model definition found for @id "{}"'.format(model_id)) - - contents = json.loads(json.dumps(model_def, separators=(',', ':'), indent=2)) - try: - headers = get_sas_token(target) - return pnp_sdk.create_or_update_model(model_id, - api_version=PNP_API_VERSION, - content=contents, - repository_id=target.get('repository_id', None), - if_match=etag, - custom_headers=headers) - except errors.HttpOperationError as e: - raise CLIError(unpack_pnp_http_error(e)) - - -def _iot_pnp_model_show(cmd, endpoint, repository, model_id, expand, pnpModelType, login): - target = get_iot_pnp_connection_string(cmd, endpoint, repository, login=login) - pnp_sdk, errors = _bind_sdk(target, SdkType.pnp_sdk) - try: - headers = get_sas_token(target) - result = pnp_sdk.get_model(model_id, api_version=PNP_API_VERSION, - repository_id=target.get('repository_id', None), - custom_headers=headers, - expand=expand) - - if not result or result["@type"].lower() != pnpModelType.value.lower(): - raise CLIError('PnP Model definition for "{}", not found.'.format(model_id)) - - return result - except errors.HttpOperationError as e: - raise CLIError(unpack_pnp_http_error(e)) - - -def _iot_pnp_model_list(cmd, endpoint, repository, search_string, - pnpModelType, top, login): - target = get_iot_pnp_connection_string(cmd, endpoint, repository, login=login) - - pnp_sdk, errors = _bind_sdk(target, SdkType.pnp_sdk) - try: - headers = get_sas_token(target) - search_options = SearchOptions(search_keyword=search_string, - model_filter_type=pnpModelType.value) - if top > 0: - search_options.page_size = top - - result = pnp_sdk.search(search_options, api_version=PNP_API_VERSION, - repository_id=target.get('repository_id', None), - custom_headers=headers) - return result.results - except errors.HttpOperationError as e: - raise CLIError(unpack_pnp_http_error(e)) - - -def _iot_pnp_model_delete(cmd, endpoint, repository, model_id, login): - target = get_iot_pnp_connection_string(cmd, endpoint, repository, login=login) - - pnp_sdk, errors = _bind_sdk(target, SdkType.pnp_sdk) - try: - headers = get_sas_token(target) - return pnp_sdk.delete_model(model_id, - repository_id=target.get('repository_id', None), - api_version=PNP_API_VERSION, - custom_headers=headers) - except errors.HttpOperationError as e: - raise CLIError(unpack_pnp_http_error(e)) - - -def _validate_model_definition(model_def): - if exists(model_def): - model_def = str(read_file_content(model_def)) - else: - logger.info('Definition not from file path or incorrect path given.') - - try: - return shell_safe_json_parse(model_def) - except ValueError as e: - logger.debug('Received definition: %s', model_def) - if looks_like_file(model_def): - raise CLIError('The definition content looks like its from a file. Please ensure the path is correct.') - raise CLIError('Malformed capability model definition. ' - 'Use --debug to see what was received. Error details: {}'.format(e)) - - -def _validate_repository(repo_id, login): - if not login and not repo_id: - raise CLIError('Please provide the model repository\'s repositoryId (via the \'--repo-id\' or \'-r\' parameter)' - ' and endpoint (via the \'--endpoint\' or \'-e\' parameter) or model repository\'s connection' - ' string via --login...') diff --git a/azext_iot/product/__init__.py b/azext_iot/product/__init__.py new file mode 100644 index 000000000..025118e43 --- /dev/null +++ b/azext_iot/product/__init__.py @@ -0,0 +1,9 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.product._help import load_help + +load_help() diff --git a/azext_iot/product/_help.py b/azext_iot/product/_help.py new file mode 100644 index 000000000..50e941768 --- /dev/null +++ b/azext_iot/product/_help.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +""" +Help definitions for Product Certification commands. +""" + +from knack.help_files import helps + + +def load_help(): + helps[ + "iot product" + ] = """ + type: group + short-summary: Manage device testing for product certification + """ + # certification requirements + helps[ + "iot product requirement" + ] = """ + type: group + short-summary: Manage product certification requirements + """ + helps[ + "iot product requirement list" + ] = """ + type: command + short-summary: Discover information about provisioning attestation methods that are supported for each badge type + examples: + - name: Basic usage + text: > + az iot product requirement list + """ diff --git a/azext_iot/product/command_map.py b/azext_iot/product/command_map.py new file mode 100644 index 000000000..1bbee92d4 --- /dev/null +++ b/azext_iot/product/command_map.py @@ -0,0 +1,33 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Load CLI commands +""" +from azure.cli.core.commands import CliCommandType + +product_ops = CliCommandType( + operations_tmpl="azext_iot.product.command_product#{}" +) + +requirements_ops = CliCommandType( + operations_tmpl="azext_iot.product.command_requirements#{}" +) + + +def load_product_commands(self, _): + with self.command_group( + "iot product", command_type=requirements_ops, is_preview=True + ) as g: + pass + + with self.command_group( + "iot product requirement", command_type=requirements_ops + ) as g: + g.command("list", "list") + + from azext_iot.product.test.command_map import load_product_test_commands + load_product_test_commands(self, _) diff --git a/azext_iot/product/command_requirements.py b/azext_iot/product/command_requirements.py new file mode 100644 index 000000000..0a486e28a --- /dev/null +++ b/azext_iot/product/command_requirements.py @@ -0,0 +1,13 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.product.shared import BadgeType +from azext_iot.product.providers.aics import AICSProvider + + +def list(cmd, badge_type=BadgeType.IotDevice.value, base_url=None): + ap = AICSProvider(cmd, base_url) + return ap.list_requirements(badge_type=badge_type) diff --git a/azext_iot/product/params.py b/azext_iot/product/params.py new file mode 100644 index 000000000..e2d32bf5e --- /dev/null +++ b/azext_iot/product/params.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +CLI parameter definitions. +""" + +from azure.cli.core.commands.parameters import get_enum_type +from azext_iot.product.shared import BadgeType + + +def load_product_params(self, _): + with self.argument_context("iot product") as c: + c.argument( + "test_id", + options_list=["--test-id", "-t"], + help="The generated Id for the device certification test", + arg_group="IoT Device Certification", + ) + c.argument( + "badge_type", + options_list=["--badge-type", "--bt"], + help="The type of certification badge", + arg_group="IoT Device Certification", + arg_type=get_enum_type(BadgeType), + ) + c.argument( + "base_url", + options_list=["--base-url"], + help="Override certification service URL to allow testing in non-production environements.", + arg_group="Development Settings" + ) + + # load az iot product test parameters + from azext_iot.product.test.params import load_product_test_params + load_product_test_params(self, _) diff --git a/azext_iot/product/providers/__init__.py b/azext_iot/product/providers/__init__.py new file mode 100644 index 000000000..903f27ac3 --- /dev/null +++ b/azext_iot/product/providers/__init__.py @@ -0,0 +1,32 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.product.providers.auth import AICSAuthentication +from azext_iot.sdk.product import AICSAPI +from azext_iot.product.shared import BASE_URL +from azext_iot.constants import VERSION as cli_version, USER_AGENT + +__all__ = ["aics_service_factory", "AICSServiceProvider"] + + +def aics_service_factory(cmd, base_url): + creds = AICSAuthentication(cmd=cmd, base_url=base_url or BASE_URL) + api = AICSAPI(base_url=base_url or BASE_URL, credentials=creds) + + api.config.add_user_agent(USER_AGENT) + api.config.add_user_agent("azcli/aics/{}".format(cli_version)) + + return api + + +class AICSServiceProvider(object): + def __init__(self, cmd, base_url): + assert cmd + self.cmd = cmd + self.base_url = base_url + + def get_mgmt_sdk(self): + return aics_service_factory(self.cmd, self.base_url) diff --git a/azext_iot/product/providers/aics.py b/azext_iot/product/providers/aics.py new file mode 100644 index 000000000..45108c6f9 --- /dev/null +++ b/azext_iot/product/providers/aics.py @@ -0,0 +1,122 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from functools import wraps +from azext_iot.product.providers import AICSServiceProvider +from azext_iot.product.shared import ( + TaskType, + BadgeType, +) +from msrestazure.azure_exceptions import CloudError +from azext_iot.common.utility import unpack_msrest_error +from knack.log import get_logger +from knack.util import CLIError + +logger = get_logger(__name__) + + +def process_cloud_error(func): + @wraps(func) + def catch_unpack_clouderror(*args, **kwargs): + """Process / unpack CloudError exceptions as CLIErrors""" + try: + return func(*args, **kwargs) + except CloudError as e: + return CLIError(unpack_msrest_error(e)) + + return catch_unpack_clouderror + + +class AICSProvider(AICSServiceProvider): + def __init__(self, cmd, base_url): + super(AICSProvider, self).__init__(cmd=cmd, base_url=base_url) + self.mgmt_sdk = self.get_mgmt_sdk() + + # Requirements + @process_cloud_error + def list_requirements(self, badge_type=BadgeType.IotDevice): + return self.mgmt_sdk.get_device_certification_requirements( + badge_type=badge_type + ) + + # Test Tasks + @process_cloud_error + def create_test_task( + self, test_id, task_type=TaskType.QueueTestRun, wait=False, poll_interval=3 + ): + return self.mgmt_sdk.create_device_test_task( + device_test_id=test_id, task_type=task_type + ) + + @process_cloud_error + def delete_test_task(self, test_id, task_id): + return self.mgmt_sdk.cancel_device_test_task( + task_id=task_id, device_test_id=test_id + ) + + @process_cloud_error + def show_test_task(self, test_id, task_id=None): + return self.mgmt_sdk.get_device_test_task( + task_id=task_id, device_test_id=test_id + ) + + @process_cloud_error + def show_running_test_task(self, test_id): + return self.mgmt_sdk.get_running_device_test_tasks(device_test_id=test_id) + + # Tests + @process_cloud_error + def show_test(self, test_id): + return self.mgmt_sdk.get_device_test( + device_test_id=test_id, raw=True + ).response.json() + + @process_cloud_error + def search_test(self, searchOptions): + return self.mgmt_sdk.search_device_test(body=searchOptions) + + @process_cloud_error + def update_test( + self, test_id, test_configuration, provisioning=False + ): + return self.mgmt_sdk.update_device_test( + device_test_id=test_id, + generate_provisioning_configuration=provisioning, + body=test_configuration, + raw=True, + ).response.json() + + @process_cloud_error + def create_test( + self, test_configuration, provisioning=True, + ): + return self.mgmt_sdk.create_device_test( + generate_provisioning_configuration=provisioning, body=test_configuration + ) + + # Test runs + @process_cloud_error + def show_test_run(self, test_id, run_id): + return self.mgmt_sdk.get_test_run(test_run_id=run_id, device_test_id=test_id) + + @process_cloud_error + def show_test_run_latest(self, test_id): + return self.mgmt_sdk.get_latest_test_run(device_test_id=test_id) + + @process_cloud_error + def submit_test_run(self, test_id, run_id): + return self.mgmt_sdk.submit_test_run(test_run_id=run_id, device_test_id=test_id) + + # Test cases + @process_cloud_error + def show_test_cases(self, test_id): + return self.mgmt_sdk.get_test_cases(device_test_id=test_id) + + @process_cloud_error + def update_test_cases(self, test_id, patch): + return self.mgmt_sdk.update_test_cases( + device_test_id=test_id, certification_badge_test_cases=patch + ) diff --git a/azext_iot/product/providers/auth.py b/azext_iot/product/providers/auth.py new file mode 100644 index 000000000..d441fd712 --- /dev/null +++ b/azext_iot/product/providers/auth.py @@ -0,0 +1,57 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from msrest.authentication import Authentication + + +class AICSAuthentication(Authentication): + def __init__(self, cmd, base_url): + self.cmd = cmd + self.base_url = base_url + + def signed_session(self, session=None): + """ + Create requests session with SAS auth headers. + + If a session object is provided, configure it directly. Otherwise, + create a new session and return it. + + Returns: + session (): requests.Session. + """ + + return self.refresh_session(session) + + def refresh_session( + self, session=None, + ): + """ + Refresh requests session with SAS auth headers. + + If a session object is provided, configure it directly. Otherwise, + create a new session and return it. + + Returns: + session (): requests.Session. + """ + + session = session or super(AICSAuthentication, self).signed_session() + session.headers["Authorization"] = self.generate_token() + return session + + def generate_token(self): + from azure.cli.core._profile import Profile + + profile = Profile(cli_ctx=self.cmd.cli_ctx) + creds, subscription, tenant = profile.get_raw_token() + parsed_token = { + "tokenType": creds[0], + "accessToken": creds[1], + "expiresOn": creds[2].get("expiresOn", "N/A"), + "tenant": tenant, + "subscription": subscription, + } + return "{} {}".format(parsed_token["tokenType"], parsed_token["accessToken"]) diff --git a/azext_iot/product/shared.py b/azext_iot/product/shared.py new file mode 100644 index 000000000..603182820 --- /dev/null +++ b/azext_iot/product/shared.py @@ -0,0 +1,47 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from enum import Enum + + +class TaskType(Enum): + QueueTestRun = "QueueTestRun" + GenerateTestCases = "GenerateTestCases" + + +class BadgeType(Enum): + IotDevice = "IotDevice" + Pnp = "Pnp" + IotEdgeCompatible = "IotEdgeCompatible" + + +class AttestationType(Enum): + symmetricKey = "SymmetricKey" + tpm = "TPM" + x509 = "X509" + connectionString = "ConnectionString" + + +class DeviceType(Enum): + FinishedProduct = "FinishedProduct" + DevKit = "DevKit" + + +class DeviceTestTaskStatus(Enum): + queued = "Queued" + started = "Started" + running = "Running" + completed = "Completed" + failed = "Failed" + cancelled = "Cancelled" + + +class ValidationType(Enum): + test = "Test" + certification = "Certification" + + +BASE_URL = "https://certify.azureiotsolutions.com" diff --git a/azext_iot/product/test/__init__.py b/azext_iot/product/test/__init__.py new file mode 100644 index 000000000..5d773cc29 --- /dev/null +++ b/azext_iot/product/test/__init__.py @@ -0,0 +1,9 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.product.test._help import load_help + +load_help() diff --git a/azext_iot/product/test/_help.py b/azext_iot/product/test/_help.py new file mode 100644 index 000000000..488faa9d0 --- /dev/null +++ b/azext_iot/product/test/_help.py @@ -0,0 +1,190 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +""" +Help definitions for Product Certification Testing commands. +""" + +from knack.help_files import helps + + +def load_help(): + # product tests + helps[ + "iot product test" + ] = """ + type: group + short-summary: Manage device tests for product certification + """ + helps[ + "iot product test create" + ] = """ + type: command + short-summary: Create a new product test for product certification + examples: + - name: Basic usage + text: > + az iot product test create --configuration-file {configuration_file} + - name: Do not have service create provisioning configuration + text: > + az iot product test create --configuration-file {configuration_file} --skip-provisioning + - name: Creating test with symmetric key attestation + text: > + az iot product test create --attestation-type SymmetricKey --device-type {device_type} + - name: Creating test with TPM attestation + text: > + az iot product test create --attestation-type TPM --device-type {device_type} --endorsement-key {endorsement_key} + - name: Creating test with x509 attestation + text: > + az iot product test create --attestation-type x509 --device-type {device_type} --certificate-path {certificate_path} + - name: Creating test for Edge module + text: > + az iot product test create --attestation-type ConnectionString --device-type {device_type} --badge-type IotEdgeCompatible --connection-string {connection_string} + - name: Creating test with symmetric key attestation and specified validation type + text: > + az iot product test create --attestation-type SymmetricKey --device-type {device_type} --validation-type Certification --product-id {product_id} + """ + helps[ + "iot product test search" + ] = """ + type: command + short-summary: Search product repository for testing data + examples: + - name: Search by product id + text: > + az iot product test search --product-id {product_id} + - name: Search by DPS registration + text: > + az iot product test search --registration-id {registration_id} + - name: Search by x509 certifcate common name (CN) + text: > + az iot product test search --certificate-name {certificate_name} + - name: Search by multiple values + text: > + az iot product test search --product-id {product_id} --certificate-name {certificate_name} + """ + helps[ + "iot product test show" + ] = """ + type: command + short-summary: View product test data + examples: + - name: Basic usage + text: > + az iot product test show --test-id {test_id} + """ + helps[ + "iot product test update" + ] = """ + type: command + short-summary: Update the product certification test data + examples: + - name: Basic usage + text: > + az iot product test update --test-id {test_id} --configuration-file {configuration_file} + """ + # Product Test Tasks + helps[ + "iot product test task" + ] = """ + type: group + short-summary: Manage product testing certification tasks + """ + helps[ + "iot product test task create" + ] = """ + type: command + short-summary: Queue a new testing task. Only one testing task can be running at a time + examples: + - name: Basic usage + text: > + az iot product test task create --test-id {test_id} + - name: Wait for completion and return test case + text: > + az iot product test task create --test-id {test_id} --wait + - name: Wait for completion with custom polling interval to completion and return test case + text: > + az iot product test task create --test-id {test_id} --wait --poll-interval 5 + """ + helps[ + "iot product test task delete" + ] = """ + type: command + short-summary: Cancel a running task matching the specified --task-id + examples: + - name: Basic usage + text: > + az iot product test task delete --test-id {test_id} --task-id {task_id} + """ + helps[ + "iot product test task show" + ] = """ + type: command + short-summary: Show the status of a testing task. Use --running for current running task or --task-id + examples: + - name: Task status by --task-id + text: > + az iot product test task show --test-id {test_id} --task-id {task_id} + - name: Currently running task of product test + text: > + az iot product test task show --test-id {test_id} --running + """ + # Test Cases + helps[ + "iot product test case" + ] = """ + type: group + short-summary: Manage product testing certification test cases + """ + helps[ + "iot product test case update" + ] = """ + type: command + short-summary: Update the product certification test case data + examples: + - name: Basic usage + text: > + az iot product test case update --test-id {test_id} --configuration-file {configuration_file} + """ + helps[ + "iot product test case list" + ] = """ + type: command + short-summary: List the test cases of a product certification test + examples: + - name: Basic usage + text: > + az iot product test case list --test-id {test_id} + """ + # Test Runs + helps[ + "iot product test run" + ] = """ + type: group + short-summary: Manage product testing certification test runs + """ + helps[ + "iot product test run submit" + ] = """ + type: command + short-summary: Submit a completed test run to the partner/product service + examples: + - name: Basic usage + text: > + az iot product test run submit --test-id {test_id} --run-id {run_id} + """ + helps[ + "iot product test run show" + ] = """ + type: command + short-summary: Show the status of a testing run. + examples: + - name: Latest product test run + text: > + az iot product test run show --test-id {test_id} + - name: Testing status by --run-id + text: > + az iot product test run show --test-id {test_id} --run-id {run_id} + """ diff --git a/azext_iot/product/test/command_map.py b/azext_iot/product/test/command_map.py new file mode 100644 index 000000000..a6f9f457f --- /dev/null +++ b/azext_iot/product/test/command_map.py @@ -0,0 +1,40 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Load CLI commands +""" +from azure.cli.core.commands import CliCommandType + +tests_ops = CliCommandType(operations_tmpl="azext_iot.product.test.command_tests#{}") + +test_tasks_ops = CliCommandType( + operations_tmpl="azext_iot.product.test.command_test_tasks#{}" +) + +test_cases_ops = CliCommandType( + operations_tmpl="azext_iot.product.test.command_test_cases#{}" +) + +test_runs_ops = CliCommandType(operations_tmpl="azext_iot.product.test.command_test_runs#{}") + + +def load_product_test_commands(self, _): + with self.command_group("iot product test", command_type=tests_ops) as g: + g.command("create", "create") + g.command("update", "update") + g.show_command("show", "show") + g.command("search", "search") + with self.command_group("iot product test case", command_type=test_cases_ops) as g: + g.command("list", "list") + g.command("update", "update") + with self.command_group("iot product test task", command_type=test_tasks_ops) as g: + g.command("create", "create") + g.command("delete", "delete") + g.show_command("show", "show") + with self.command_group("iot product test run", command_type=test_runs_ops) as g: + g.show_command("show", "show") + g.command("submit", "submit") diff --git a/azext_iot/product/test/command_test_cases.py b/azext_iot/product/test/command_test_cases.py new file mode 100644 index 000000000..d08d4b78c --- /dev/null +++ b/azext_iot/product/test/command_test_cases.py @@ -0,0 +1,26 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.product.providers.aics import AICSProvider +from azext_iot.common.utility import process_json_arg +from knack.util import CLIError + + +def list(cmd, test_id, base_url=None): + ap = AICSProvider(cmd, base_url) + return ap.show_test_cases(test_id=test_id) + + +def update(cmd, test_id, configuration_file, base_url=None): + import os + + if not os.path.exists(configuration_file): + raise CLIError("Specified configuration file does not exist") + ap = AICSProvider(cmd, base_url) + return ap.update_test_cases( + test_id=test_id, + patch=process_json_arg(configuration_file, "configuration_file"), + ) diff --git a/azext_iot/product/test/command_test_runs.py b/azext_iot/product/test/command_test_runs.py new file mode 100644 index 000000000..5537cb5cc --- /dev/null +++ b/azext_iot/product/test/command_test_runs.py @@ -0,0 +1,41 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from time import sleep +from azext_iot.product.providers.aics import AICSProvider +from azext_iot.product.shared import DeviceTestTaskStatus as Status +from knack.util import CLIError + + +def show(cmd, test_id, run_id=None, wait=False, poll_interval=3, base_url=None): + final_statuses = [ + Status.failed.value, + Status.completed.value, + Status.cancelled.value, + ] + ap = AICSProvider(cmd, base_url) + if run_id: + response = ap.show_test_run(test_id=test_id, run_id=run_id) + else: + response = ap.show_test_run_latest(test_id=test_id) + + if not response: + error = "No test run found for test ID '{}'".format(test_id) + if run_id: + error = error + " with run ID '{}'".format(run_id) + raise CLIError(error) + status = response.status + run_id = response.id + while all([wait, status, run_id]) and status not in final_statuses: + sleep(poll_interval) + response = ap.show_test_run(test_id=test_id, run_id=run_id) + status = response.status + return response + + +def submit(cmd, test_id, run_id, base_url=None): + ap = AICSProvider(cmd, base_url) + return ap.submit_test_run(test_id=test_id, run_id=run_id) diff --git a/azext_iot/product/test/command_test_tasks.py b/azext_iot/product/test/command_test_tasks.py new file mode 100644 index 000000000..0d997270a --- /dev/null +++ b/azext_iot/product/test/command_test_tasks.py @@ -0,0 +1,69 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from time import sleep +from azext_iot.product.shared import TaskType, DeviceTestTaskStatus as Status +from azext_iot.product.providers.aics import AICSProvider +from knack.util import CLIError + + +def create( + cmd, test_id, task_type=TaskType.QueueTestRun.value, wait=False, poll_interval=3, base_url=None +): + ap = AICSProvider(cmd, base_url) + final_statuses = [ + Status.failed.value, + Status.completed.value, + Status.cancelled.value, + ] + response = ap.create_test_task( + test_id=test_id, task_type=task_type, wait=wait, poll_interval=poll_interval + ) + if not response: + raise CLIError( + "Failed to create device test task - please ensure a device test exists with Id {}".format( + test_id + ) + ) + if isinstance(response, dict): + raise CLIError(response) + + status = response.status + task_id = response.id + while all([wait, status, task_id]) and status not in final_statuses: + sleep(poll_interval) + response = ap.show_test_task(test_id=test_id, task_id=task_id) + status = response.status + + # if a task of 'queueTestRun' is awaited, return the run result + if all( + [ + wait, + status in final_statuses, + task_type == TaskType.QueueTestRun.value, + response.result_link, + ] + ): + run_id = response.result_link.split("/")[-1] + return ap.show_test_run(test_id=test_id, run_id=run_id) if run_id else response + + return response + + +def delete(cmd, test_id, task_id, base_url=None): + ap = AICSProvider(cmd, base_url) + return ap.delete_test_task(test_id=test_id, task_id=task_id) + + +def show(cmd, test_id, task_id=None, running=False, base_url=None): + ap = AICSProvider(cmd, base_url) + if task_id: + return ap.show_test_task(test_id=test_id, task_id=task_id) + elif running: + return ap.show_running_test_task(test_id=test_id) + raise CLIError( + "Please provide a task-id for individual task details, or use the --running argument to list all running tasks" + ) diff --git a/azext_iot/product/test/command_tests.py b/azext_iot/product/test/command_tests.py new file mode 100644 index 000000000..3e5cb9948 --- /dev/null +++ b/azext_iot/product/test/command_tests.py @@ -0,0 +1,295 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.product.providers.aics import AICSProvider +from azext_iot.sdk.product.models import DeviceTestSearchOptions +from azext_iot.product.shared import BadgeType, AttestationType, ValidationType +from knack.log import get_logger +from knack.util import CLIError +import os + +logger = get_logger(__name__) + + +def create( + cmd, + configuration_file=None, + product_id=None, + device_type=None, + attestation_type=None, + certificate_path=None, + connection_string=None, + endorsement_key=None, + badge_type=BadgeType.IotDevice.value, + validation_type=ValidationType.test.value, + models=None, + skip_provisioning=False, + base_url=None, +): + if attestation_type == AttestationType.x509.value and not certificate_path: + raise CLIError("If attestation type is x509, certificate path is required") + if attestation_type == AttestationType.tpm.value and not endorsement_key: + raise CLIError("If attestation type is TPM, endorsement key is required") + if badge_type == BadgeType.Pnp.value and not models: + raise CLIError("If badge type is Pnp, models is required") + if badge_type == BadgeType.IotEdgeCompatible.value and not all( + [connection_string, attestation_type == AttestationType.connectionString.value] + ): + raise CLIError( + "Connection string is required for Edge Compatible modules testing" + ) + if badge_type != BadgeType.IotEdgeCompatible.value and ( + connection_string or attestation_type == AttestationType.connectionString.value + ): + raise CLIError( + "Connection string is only available for Edge Compatible modules testing" + ) + if validation_type != ValidationType.test.value and not product_id: + raise CLIError( + "Product Id is required for validation type {}".format(validation_type) + ) + if not any( + [ + configuration_file, + all([device_type, attestation_type, badge_type]), + ] + ): + raise CLIError( + "If configuration file is not specified, attestation and device definition parameters must be specified" + ) + test_configuration = ( + _create_from_file(configuration_file) + if configuration_file + else _build_test_configuration( + product_id=product_id, + device_type=device_type, + attestation_type=attestation_type, + certificate_path=certificate_path, + endorsement_key=endorsement_key, + badge_type=badge_type, + connection_string=connection_string, + models=models, + validation_type=validation_type + ) + ) + + ap = AICSProvider(cmd, base_url) + + provisioning = not skip_provisioning + test_data = ap.create_test( + test_configuration=test_configuration, provisioning=provisioning + ) + + return test_data + + +def show(cmd, test_id, base_url=None): + ap = AICSProvider(cmd, base_url) + return ap.show_test(test_id) + + +def update( + cmd, + test_id, + configuration_file=None, + attestation_type=None, + certificate_path=None, + connection_string=None, + endorsement_key=None, + badge_type=None, + models=None, + base_url=None, +): + provisioning = False + # verify required parameters for various options + if attestation_type == AttestationType.x509.value and not certificate_path: + raise CLIError("If attestation type is x509, certificate path is required") + if attestation_type == AttestationType.tpm.value and not endorsement_key: + raise CLIError("If attestation type is tpm, endorsement key is required") + if badge_type == BadgeType.Pnp.value and not models: + raise CLIError("If badge type is Pnp, models is required") + if badge_type == BadgeType.IotEdgeCompatible.value and not all( + [connection_string, attestation_type == AttestationType.connectionString.value] + ): + raise CLIError( + "Connection string is required for Edge Compatible modules testing" + ) + if badge_type != BadgeType.IotEdgeCompatible.value and ( + connection_string or attestation_type == AttestationType.connectionString.value + ): + raise CLIError( + "Connection string is only available for Edge Compatible modules testing" + ) + ap = AICSProvider(cmd, base_url) + if configuration_file: + test_configuration = _create_from_file(configuration_file) + return ap.update_test( + test_id=test_id, + test_configuration=test_configuration, + provisioning=provisioning, + ) + + if not any([attestation_type, badge_type, models]): + raise CLIError( + "Configuration file, attestation information, or device configuration must be specified" + ) + + test_configuration = ap.show_test(test_id=test_id) + + provisioning_configuration = test_configuration["provisioningConfiguration"] + registration_id = provisioning_configuration["dpsRegistrationId"] + + # change attestation + if attestation_type: + # reset the provisioningConfiguration + test_configuration["provisioningConfiguration"] = { + "type": attestation_type, + "dpsRegistrationId": registration_id, + } + provisioning = True + if attestation_type == AttestationType.symmetricKey.value: + test_configuration["provisioningConfiguration"][ + "symmetricKeyEnrollmentInformation" + ] = {} + elif attestation_type == AttestationType.tpm.value: + test_configuration["provisioningConfiguration"][ + "tpmEnrollmentInformation" + ] = {"endorsementKey": endorsement_key} + elif attestation_type == AttestationType.x509.value: + test_configuration["provisioningConfiguration"][ + "x509EnrollmentInformation" + ] = { + "base64EncodedX509Certificate": _read_certificate_from_file( + certificate_path + ) + } + elif attestation_type == AttestationType.connectionString.value: + test_configuration["provisioningConfiguration"][ + "deviceConnectionString" + ] = connection_string + + # reset PnP models + badge_config = test_configuration["certificationBadgeConfigurations"] + + if ( + badge_type == BadgeType.Pnp.value + or badge_config[0]["type"].lower() == BadgeType.Pnp.value.lower() + ) and models: + models_array = _process_models_directory(models) + test_configuration["certificationBadgeConfigurations"] = [ + {"type": BadgeType.Pnp.value, "digitalTwinModelDefinitions": models_array} + ] + elif badge_type: + test_configuration["certificationBadgeConfigurations"] = [{"type": badge_type}] + + return ap.update_test( + test_id=test_id, + test_configuration=test_configuration, + provisioning=provisioning, + ) + + +def search( + cmd, product_id=None, registration_id=None, certificate_name=None, base_url=None +): + if not any([product_id or registration_id or certificate_name]): + raise CLIError("At least one search criteria must be specified") + + ap = AICSProvider(cmd, base_url) + searchOptions = DeviceTestSearchOptions( + product_id=product_id, + dps_registration_id=registration_id, + dps_x509_certificate_common_name=certificate_name, + ) + return ap.search_test(searchOptions) + + +def _build_test_configuration( + product_id, + device_type, + attestation_type, + certificate_path, + endorsement_key, + connection_string, + badge_type, + models, + validation_type +): + config = { + "validationType": validation_type, + "productId": product_id, + "deviceType": device_type, + "provisioningConfiguration": {"type": attestation_type}, + "certificationBadgeConfigurations": [{"type": badge_type}], + } + if attestation_type == AttestationType.symmetricKey.value: + config["provisioningConfiguration"]["symmetricKeyEnrollmentInformation"] = {} + elif attestation_type == AttestationType.tpm.value: + config["provisioningConfiguration"]["tpmEnrollmentInformation"] = { + "endorsementKey": endorsement_key + } + elif attestation_type == AttestationType.x509.value: + config["provisioningConfiguration"]["x509EnrollmentInformation"] = { + "base64EncodedX509Certificate": _read_certificate_from_file( + certificate_path + ) + } + elif attestation_type == AttestationType.connectionString.value: + config["provisioningConfiguration"][ + "deviceConnectionString" + ] = connection_string + + if badge_type == BadgeType.Pnp.value and models: + models_array = _process_models_directory(models) + config["certificationBadgeConfigurations"][0][ + "digitalTwinModelDefinitions" + ] = models_array + + return config + + +def _process_models_directory(from_directory): + from azext_iot.common.utility import scantree, process_json_arg, read_file_content + # we need to double-encode the JSON string + from json import dumps + + models = [] + if os.path.isfile(from_directory) and (from_directory.endswith(".json") or from_directory.endswith(".dtdl")): + models.append(dumps(read_file_content(file_path=from_directory))) + return models + for entry in scantree(from_directory): + if not any([entry.name.endswith(".json"), entry.name.endswith(".dtdl")]): + logger.debug( + "Skipping {} - model file must end with .json or .dtdl".format( + entry.path + ) + ) + continue + entry_json = process_json_arg(content=entry.path, argument_name=entry.name) + + models.append(dumps(entry_json)) + return models + + +def _read_certificate_from_file(certificate_path): + with open(file=certificate_path, mode="rb") as f: + data = f.read() + + from base64 import b64encode # pylint: disable=no-name-in-module + return b64encode(data).decode('utf-8') + + +def _create_from_file(configuration_file): + if not (os.path.exists(configuration_file)): + raise CLIError("Specified configuration file does not exist") + + # read the json file and POST /deviceTests + with open(file=configuration_file, encoding="utf-8") as f: + file_contents = f.read() + + from json import loads + + return loads(file_contents) diff --git a/azext_iot/product/test/params.py b/azext_iot/product/test/params.py new file mode 100644 index 000000000..0a0c40cad --- /dev/null +++ b/azext_iot/product/test/params.py @@ -0,0 +1,165 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +CLI parameter definitions. +""" + +from azure.cli.core.commands.parameters import get_three_state_flag, get_enum_type +from azext_iot.product.shared import AttestationType, DeviceType, TaskType, ValidationType + + +def load_product_test_params(self, _): + with self.argument_context("iot product test") as c: + c.argument( + "skip_provisioning", + options_list=["--skip-provisioning", "--sp"], + help="Determines whether the service skips generating provisioning configuration. " + + "Only applies to SymmetricKey and ConnectionString provisioning types", + arg_group="IoT Device Certification", + ) + c.argument( + "configuration_file", + options_list=["--configuration-file", "--cf"], + help="Path to device test JSON file. " + "If not specified, attestation and device definition parameters must be specified", + arg_group="IoT Device Certification", + ) + c.argument( + "attestation_type", + options_list=["--attestation-type", "--at"], + help="How the device will authenticate to testing service Device Provisioning Service", + arg_group="IoT Device Certification Attestation", + arg_type=get_enum_type(AttestationType), + ) + c.argument( + "certificate_path", + options_list=["--certificate-path", "--cp"], + help="The path to the file containing the primary certificate. " + "When choosing x509 as attestation type, one of the certificate path is required", + arg_group="IoT Device Certification Attestation", + ) + c.argument( + "endorsement_key", + options_list=["--endorsement-key", "--ek"], + help="TPM endorsement key for a TPM device. " + "When choosing TPM as attestation type, endorsement key is required", + arg_group="IoT Device Certification Attestation", + ) + c.argument( + "connection_string", + options_list=["--connection-string", "--cs"], + help="Edge module connection string" + "When choosing IotEdgeCompatible badge type, connection string and attestaion-type of connection string are required", + arg_group="IoT Device Certification Attestation", + ) + c.argument( + "models", + options_list=["--models", "-m"], + help="Path containing Azure IoT Plug and Play interfaces implemented by the device being tested. " + "When badge type is Pnp, models is required", + arg_group="IoT Device Certification Device Definition", + ) + c.argument( + "device_type", + options_list=["--device-type", "--dt"], + help="Defines the type of device to be tested", + arg_group="IoT Device Certification Device Definition", + arg_type=get_enum_type(DeviceType), + ) + c.argument( + "product_id", + options_list=["--product-id", "-p"], + help="The submitted product id. Required when validation-type is 'Certification'.", + arg_group="IoT Device Certification Device Definition", + ) + c.argument( + "validation_type", + options_list=["--validation-type", "--vt"], + help="The type of validations testing to be performed", + arg_group="IoT Device Certification Device Definition", + arg_type=get_enum_type(ValidationType) + ) + with self.argument_context("iot product test search") as c: + c.argument( + "product_id", + options_list=["--product-id", "-p"], + help="The submitted product id", + arg_group="IoT Device Certification", + ) + c.argument( + "registration_id", + options_list=["--registration-id", "-r"], + help="The regstration Id for Device Provisioning Service", + arg_group="IoT Device Certification", + ) + c.argument( + "certificate_name", + options_list=["--certificate-name", "--cn"], + help="The x509 Certificate Common Name (cn) used for Device Provisioning Service attestation", + arg_group="IoT Device Certification", + ) + with self.argument_context("iot product test case") as c: + c.argument( + "configuration_file", + options_list=["--configuration-file", "--cf"], + help="The file path for test case configuration JSON", + arg_group="IoT Device Certification", + ) + with self.argument_context("iot product test task") as c: + c.argument( + "task_id", + options_list=["--task-id"], + help="The generated Id of the testing task", + arg_group="IoT Device Certification", + ) + c.argument( + "running", + options_list=["--running"], + help="Get the running tasks of a device test", + arg_group="IoT Device Certification", + arg_type=get_three_state_flag(), + ) + c.argument( + "task_type", + options_list=["--type"], + help="The type of task for the device test", + arg_group="IoT Device Certification", + arg_type=get_enum_type(TaskType), + ) + c.argument( + "wait", + options_list=["--wait", "-w"], + help="Wait for task completion and return test case data when available", + arg_group="IoT Device Certification", + arg_type=get_three_state_flag(), + ) + c.argument( + "poll_interval", + options_list=["--poll-interval", "--interval"], + help="Used in conjunction with --wait. Sepcifies how frequently (in seconds) polling occurs", + arg_group="IoT Device Certification", + ) + with self.argument_context("iot product test run") as c: + c.argument( + "run_id", + options_list=["--run-id", "-r"], + help="The generated Id of a test run", + arg_group="IoT Device Certification", + ) + c.argument( + "wait", + options_list=["--wait", "-w"], + help='Wait until test run status is "started" or "running"', + arg_group="IoT Device Certification", + arg_type=get_three_state_flag(), + ) + c.argument( + "poll_interval", + options_list=["--poll-interval", "--interval"], + help="Used in conjunction with --wait. Specifies how frequently (in seconds) polling occurs", + arg_group="IoT Device Certification", + ) diff --git a/azext_iot/sdk/digitaltwins/__init__.py b/azext_iot/sdk/digitaltwins/__init__.py new file mode 100644 index 000000000..55614acbf --- /dev/null +++ b/azext_iot/sdk/digitaltwins/__init__.py @@ -0,0 +1,5 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/azext_iot/sdk/digitaltwins/controlplane/__init__.py b/azext_iot/sdk/digitaltwins/controlplane/__init__.py new file mode 100644 index 000000000..3d6c9db37 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/__init__.py @@ -0,0 +1,18 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .azure_digital_twins_management_client import AzureDigitalTwinsManagementClient +from .version import VERSION + +__all__ = ['AzureDigitalTwinsManagementClient'] + +__version__ = VERSION + diff --git a/azext_iot/sdk/digitaltwins/controlplane/azure_digital_twins_management_client.py b/azext_iot/sdk/digitaltwins/controlplane/azure_digital_twins_management_client.py new file mode 100644 index 000000000..03e9b6150 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/azure_digital_twins_management_client.py @@ -0,0 +1,100 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.service_client import SDKClient +from msrest import Serializer, Deserializer +from msrestazure import AzureConfiguration +from .version import VERSION +from .operations.digital_twins_operations import DigitalTwinsOperations +from .operations.digital_twins_endpoint_operations import DigitalTwinsEndpointOperations +from .operations.operations import Operations +from .operations.private_link_resources_operations import PrivateLinkResourcesOperations +from .operations.private_endpoint_connections_operations import PrivateEndpointConnectionsOperations +from . import models + + +class AzureDigitalTwinsManagementClientConfiguration(AzureConfiguration): + """Configuration for AzureDigitalTwinsManagementClient + Note that all parameters used to create this instance are saved as instance + attributes. + + :param credentials: Credentials needed for the client to connect to Azure. + :type credentials: :mod:`A msrestazure Credentials + object` + :param subscription_id: The subscription identifier. + :type subscription_id: str + :param str base_url: Service URL + """ + + def __init__( + self, credentials, subscription_id, base_url=None): + + if credentials is None: + raise ValueError("Parameter 'credentials' must not be None.") + if subscription_id is None: + raise ValueError("Parameter 'subscription_id' must not be None.") + if not base_url: + base_url = 'https://management.azure.com' + + super(AzureDigitalTwinsManagementClientConfiguration, self).__init__(base_url) + + self.add_user_agent('azure-mgmt-digitaltwins/{}'.format(VERSION)) + + self.credentials = credentials + self.subscription_id = subscription_id + + +class AzureDigitalTwinsManagementClient(SDKClient): + """Azure Digital Twins Client for managing DigitalTwinsInstance + + :ivar config: Configuration for client. + :vartype config: AzureDigitalTwinsManagementClientConfiguration + + :ivar digital_twins: DigitalTwins operations + :vartype digital_twins: controlplane.operations.DigitalTwinsOperations + :ivar digital_twins_endpoint: DigitalTwinsEndpoint operations + :vartype digital_twins_endpoint: controlplane.operations.DigitalTwinsEndpointOperations + :ivar operations: Operations operations + :vartype operations: controlplane.operations.Operations + :ivar private_link_resources: PrivateLinkResources operations + :vartype private_link_resources: controlplane.operations.PrivateLinkResourcesOperations + :ivar private_endpoint_connections: PrivateEndpointConnections operations + :vartype private_endpoint_connections: controlplane.operations.PrivateEndpointConnectionsOperations + + :param credentials: Credentials needed for the client to connect to Azure. + :type credentials: :mod:`A msrestazure Credentials + object` + :param subscription_id: The subscription identifier. + :type subscription_id: str + :param str base_url: Service URL + """ + + def __init__( + self, credentials, subscription_id, base_url=None): + + self.config = AzureDigitalTwinsManagementClientConfiguration(credentials, subscription_id, base_url) + super(AzureDigitalTwinsManagementClient, self).__init__(self.config.credentials, self.config) + + client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} + self.api_version = '2020-12-01' + self._serialize = Serializer(client_models) + self._deserialize = Deserializer(client_models) + + self.digital_twins = DigitalTwinsOperations( + self._client, self.config, self._serialize, self._deserialize) + self.digital_twins_endpoint = DigitalTwinsEndpointOperations( + self._client, self.config, self._serialize, self._deserialize) + self.operations = Operations( + self._client, self.config, self._serialize, self._deserialize) + self.private_link_resources = PrivateLinkResourcesOperations( + self._client, self.config, self._serialize, self._deserialize) + self.private_endpoint_connections = PrivateEndpointConnectionsOperations( + self._client, self.config, self._serialize, self._deserialize) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/__init__.py b/azext_iot/sdk/digitaltwins/controlplane/models/__init__.py new file mode 100644 index 000000000..ed6089378 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/__init__.py @@ -0,0 +1,127 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +try: + from .digital_twins_patch_properties_py3 import DigitalTwinsPatchProperties + from .private_endpoint_connection_properties_py3 import PrivateEndpointConnectionProperties + from .private_endpoint_connection_py3 import PrivateEndpointConnection + from .digital_twins_description_py3 import DigitalTwinsDescription + from .digital_twins_identity_py3 import DigitalTwinsIdentity + from .digital_twins_patch_description_py3 import DigitalTwinsPatchDescription + from .digital_twins_resource_py3 import DigitalTwinsResource + from .error_definition_py3 import ErrorDefinition + from .error_response_py3 import ErrorResponse, ErrorResponseException + from .operation_display_py3 import OperationDisplay + from .operation_py3 import Operation + from .check_name_request_py3 import CheckNameRequest + from .check_name_result_py3 import CheckNameResult + from .external_resource_py3 import ExternalResource + from .digital_twins_endpoint_resource_properties_py3 import DigitalTwinsEndpointResourceProperties + from .digital_twins_endpoint_resource_py3 import DigitalTwinsEndpointResource + from .service_bus_py3 import ServiceBus + from .event_hub_py3 import EventHub + from .event_grid_py3 import EventGrid + from .group_id_information_properties_py3 import GroupIdInformationProperties + from .group_id_information_properties_model_py3 import GroupIdInformationPropertiesModel + from .group_id_information_py3 import GroupIdInformation + from .private_endpoint_connections_response_py3 import PrivateEndpointConnectionsResponse + from .group_id_information_response_py3 import GroupIdInformationResponse + from .connection_state_py3 import ConnectionState + from .private_endpoint_py3 import PrivateEndpoint + from .connection_properties_private_endpoint_py3 import ConnectionPropertiesPrivateEndpoint + from .connection_properties_private_link_service_connection_state_py3 import ConnectionPropertiesPrivateLinkServiceConnectionState + from .connection_properties_py3 import ConnectionProperties +except (SyntaxError, ImportError): + from .digital_twins_patch_properties import DigitalTwinsPatchProperties + from .private_endpoint_connection_properties import PrivateEndpointConnectionProperties + from .private_endpoint_connection import PrivateEndpointConnection + from .digital_twins_description import DigitalTwinsDescription + from .digital_twins_identity import DigitalTwinsIdentity + from .digital_twins_patch_description import DigitalTwinsPatchDescription + from .digital_twins_resource import DigitalTwinsResource + from .error_definition import ErrorDefinition + from .error_response import ErrorResponse, ErrorResponseException + from .operation_display import OperationDisplay + from .operation import Operation + from .check_name_request import CheckNameRequest + from .check_name_result import CheckNameResult + from .external_resource import ExternalResource + from .digital_twins_endpoint_resource_properties import DigitalTwinsEndpointResourceProperties + from .digital_twins_endpoint_resource import DigitalTwinsEndpointResource + from .service_bus import ServiceBus + from .event_hub import EventHub + from .event_grid import EventGrid + from .group_id_information_properties import GroupIdInformationProperties + from .group_id_information_properties_model import GroupIdInformationPropertiesModel + from .group_id_information import GroupIdInformation + from .private_endpoint_connections_response import PrivateEndpointConnectionsResponse + from .group_id_information_response import GroupIdInformationResponse + from .connection_state import ConnectionState + from .private_endpoint import PrivateEndpoint + from .connection_properties_private_endpoint import ConnectionPropertiesPrivateEndpoint + from .connection_properties_private_link_service_connection_state import ConnectionPropertiesPrivateLinkServiceConnectionState + from .connection_properties import ConnectionProperties +from .digital_twins_description_paged import DigitalTwinsDescriptionPaged +from .digital_twins_endpoint_resource_paged import DigitalTwinsEndpointResourcePaged +from .operation_paged import OperationPaged +from .azure_digital_twins_management_client_enums import ( + PublicNetworkAccess, + ProvisioningState, + DigitalTwinsIdentityType, + Reason, + EndpointProvisioningState, + AuthenticationType, + PrivateLinkServiceConnectionStatus, + ConnectionPropertiesProvisioningState, +) + +__all__ = [ + 'DigitalTwinsPatchProperties', + 'PrivateEndpointConnectionProperties', + 'PrivateEndpointConnection', + 'DigitalTwinsDescription', + 'DigitalTwinsIdentity', + 'DigitalTwinsPatchDescription', + 'DigitalTwinsResource', + 'ErrorDefinition', + 'ErrorResponse', 'ErrorResponseException', + 'OperationDisplay', + 'Operation', + 'CheckNameRequest', + 'CheckNameResult', + 'ExternalResource', + 'DigitalTwinsEndpointResourceProperties', + 'DigitalTwinsEndpointResource', + 'ServiceBus', + 'EventHub', + 'EventGrid', + 'GroupIdInformationProperties', + 'GroupIdInformationPropertiesModel', + 'GroupIdInformation', + 'PrivateEndpointConnectionsResponse', + 'GroupIdInformationResponse', + 'ConnectionState', + 'PrivateEndpoint', + 'ConnectionPropertiesPrivateEndpoint', + 'ConnectionPropertiesPrivateLinkServiceConnectionState', + 'ConnectionProperties', + 'DigitalTwinsDescriptionPaged', + 'DigitalTwinsEndpointResourcePaged', + 'OperationPaged', + 'PublicNetworkAccess', + 'ProvisioningState', + 'DigitalTwinsIdentityType', + 'Reason', + 'EndpointProvisioningState', + 'AuthenticationType', + 'PrivateLinkServiceConnectionStatus', + 'ConnectionPropertiesProvisioningState', +] diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/azure_digital_twins_management_client_enums.py b/azext_iot/sdk/digitaltwins/controlplane/models/azure_digital_twins_management_client_enums.py new file mode 100644 index 000000000..c5cef2830 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/azure_digital_twins_management_client_enums.py @@ -0,0 +1,82 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from enum import Enum + + +class PublicNetworkAccess(str, Enum): + + enabled = "Enabled" + disabled = "Disabled" + + +class ProvisioningState(str, Enum): + + provisioning = "Provisioning" + deleting = "Deleting" + updating = "Updating" + succeeded = "Succeeded" + failed = "Failed" + canceled = "Canceled" + deleted = "Deleted" + warning = "Warning" + suspending = "Suspending" + restoring = "Restoring" + moving = "Moving" + + +class DigitalTwinsIdentityType(str, Enum): + + none = "None" + system_assigned = "SystemAssigned" + + +class Reason(str, Enum): + + invalid = "Invalid" + already_exists = "AlreadyExists" + + +class EndpointProvisioningState(str, Enum): + + provisioning = "Provisioning" + deleting = "Deleting" + succeeded = "Succeeded" + failed = "Failed" + canceled = "Canceled" + deleted = "Deleted" + warning = "Warning" + suspending = "Suspending" + restoring = "Restoring" + moving = "Moving" + disabled = "Disabled" + + +class AuthenticationType(str, Enum): + + key_based = "KeyBased" + identity_based = "IdentityBased" + + +class PrivateLinkServiceConnectionStatus(str, Enum): + + pending = "Pending" + approved = "Approved" + rejected = "Rejected" + disconnected = "Disconnected" + + +class ConnectionPropertiesProvisioningState(str, Enum): + + pending = "Pending" + approved = "Approved" + rejected = "Rejected" + disconnected = "Disconnected" diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/check_name_request.py b/azext_iot/sdk/digitaltwins/controlplane/models/check_name_request.py new file mode 100644 index 000000000..335a3bee7 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/check_name_request.py @@ -0,0 +1,45 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class CheckNameRequest(Model): + """The result returned from a database check name availability request. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :param name: Required. Resource name. + :type name: str + :ivar type: Required. The type of resource, for instance + Microsoft.DigitalTwins/digitalTwinsInstances. Default value: + "Microsoft.DigitalTwins/digitalTwinsInstances" . + :vartype type: str + """ + + _validation = { + 'name': {'required': True}, + 'type': {'required': True, 'constant': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + type = "Microsoft.DigitalTwins/digitalTwinsInstances" + + def __init__(self, **kwargs): + super(CheckNameRequest, self).__init__(**kwargs) + self.name = kwargs.get('name', None) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/check_name_request_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/check_name_request_py3.py new file mode 100644 index 000000000..e75be54d7 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/check_name_request_py3.py @@ -0,0 +1,45 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class CheckNameRequest(Model): + """The result returned from a database check name availability request. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :param name: Required. Resource name. + :type name: str + :ivar type: Required. The type of resource, for instance + Microsoft.DigitalTwins/digitalTwinsInstances. Default value: + "Microsoft.DigitalTwins/digitalTwinsInstances" . + :vartype type: str + """ + + _validation = { + 'name': {'required': True}, + 'type': {'required': True, 'constant': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + type = "Microsoft.DigitalTwins/digitalTwinsInstances" + + def __init__(self, *, name: str, **kwargs) -> None: + super(CheckNameRequest, self).__init__(**kwargs) + self.name = name diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/check_name_result.py b/azext_iot/sdk/digitaltwins/controlplane/models/check_name_result.py new file mode 100644 index 000000000..bd2213e80 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/check_name_result.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class CheckNameResult(Model): + """The result returned from a check name availability request. + + :param name_available: Specifies a Boolean value that indicates if the + name is available. + :type name_available: bool + :param message: Message indicating an unavailable name due to a conflict, + or a description of the naming rules that are violated. + :type message: str + :param reason: Message providing the reason why the given name is invalid. + Possible values include: 'Invalid', 'AlreadyExists' + :type reason: str or ~controlplane.models.Reason + """ + + _attribute_map = { + 'name_available': {'key': 'nameAvailable', 'type': 'bool'}, + 'message': {'key': 'message', 'type': 'str'}, + 'reason': {'key': 'reason', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(CheckNameResult, self).__init__(**kwargs) + self.name_available = kwargs.get('name_available', None) + self.message = kwargs.get('message', None) + self.reason = kwargs.get('reason', None) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/check_name_result_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/check_name_result_py3.py new file mode 100644 index 000000000..76d83ffcf --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/check_name_result_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class CheckNameResult(Model): + """The result returned from a check name availability request. + + :param name_available: Specifies a Boolean value that indicates if the + name is available. + :type name_available: bool + :param message: Message indicating an unavailable name due to a conflict, + or a description of the naming rules that are violated. + :type message: str + :param reason: Message providing the reason why the given name is invalid. + Possible values include: 'Invalid', 'AlreadyExists' + :type reason: str or ~controlplane.models.Reason + """ + + _attribute_map = { + 'name_available': {'key': 'nameAvailable', 'type': 'bool'}, + 'message': {'key': 'message', 'type': 'str'}, + 'reason': {'key': 'reason', 'type': 'str'}, + } + + def __init__(self, *, name_available: bool=None, message: str=None, reason=None, **kwargs) -> None: + super(CheckNameResult, self).__init__(**kwargs) + self.name_available = name_available + self.message = message + self.reason = reason diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties.py b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties.py new file mode 100644 index 000000000..0fb707cf0 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ConnectionProperties(Model): + """The properties of a private endpoint connection. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Pending', 'Approved', 'Rejected', 'Disconnected' + :vartype provisioning_state: str or + ~controlplane.models.ConnectionPropertiesProvisioningState + :param private_endpoint: + :type private_endpoint: + ~controlplane.models.ConnectionPropertiesPrivateEndpoint + :param group_ids: The list of group ids for the private endpoint + connection. + :type group_ids: list[str] + :param private_link_service_connection_state: + :type private_link_service_connection_state: + ~controlplane.models.ConnectionPropertiesPrivateLinkServiceConnectionState + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'private_endpoint': {'key': 'privateEndpoint', 'type': 'ConnectionPropertiesPrivateEndpoint'}, + 'group_ids': {'key': 'groupIds', 'type': '[str]'}, + 'private_link_service_connection_state': {'key': 'privateLinkServiceConnectionState', 'type': 'ConnectionPropertiesPrivateLinkServiceConnectionState'}, + } + + def __init__(self, **kwargs): + super(ConnectionProperties, self).__init__(**kwargs) + self.provisioning_state = None + self.private_endpoint = kwargs.get('private_endpoint', None) + self.group_ids = kwargs.get('group_ids', None) + self.private_link_service_connection_state = kwargs.get('private_link_service_connection_state', None) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_endpoint.py b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_endpoint.py new file mode 100644 index 000000000..3522e5561 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_endpoint.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .private_endpoint import PrivateEndpoint + + +class ConnectionPropertiesPrivateEndpoint(PrivateEndpoint): + """ConnectionPropertiesPrivateEndpoint. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar id: The resource identifier. + :vartype id: str + """ + + _validation = { + 'id': {'readonly': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(ConnectionPropertiesPrivateEndpoint, self).__init__(**kwargs) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_endpoint_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_endpoint_py3.py new file mode 100644 index 000000000..b3d1e2b29 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_endpoint_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .private_endpoint_py3 import PrivateEndpoint + + +class ConnectionPropertiesPrivateEndpoint(PrivateEndpoint): + """ConnectionPropertiesPrivateEndpoint. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar id: The resource identifier. + :vartype id: str + """ + + _validation = { + 'id': {'readonly': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + } + + def __init__(self, **kwargs) -> None: + super(ConnectionPropertiesPrivateEndpoint, self).__init__(**kwargs) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_link_service_connection_state.py b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_link_service_connection_state.py new file mode 100644 index 000000000..1d6a9870b --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_link_service_connection_state.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .connection_state import ConnectionState + + +class ConnectionPropertiesPrivateLinkServiceConnectionState(ConnectionState): + """ConnectionPropertiesPrivateLinkServiceConnectionState. + + All required parameters must be populated in order to send to Azure. + + :param status: Required. The status of a private endpoint connection. + Possible values include: 'Pending', 'Approved', 'Rejected', 'Disconnected' + :type status: str or + ~controlplane.models.PrivateLinkServiceConnectionStatus + :param description: Required. The description for the current state of a + private endpoint connection. + :type description: str + :param actions_required: Actions required for a private endpoint + connection. + :type actions_required: str + """ + + _validation = { + 'status': {'required': True}, + 'description': {'required': True}, + } + + _attribute_map = { + 'status': {'key': 'status', 'type': 'str'}, + 'description': {'key': 'description', 'type': 'str'}, + 'actions_required': {'key': 'actionsRequired', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(ConnectionPropertiesPrivateLinkServiceConnectionState, self).__init__(**kwargs) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_link_service_connection_state_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_link_service_connection_state_py3.py new file mode 100644 index 000000000..6e5964952 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_private_link_service_connection_state_py3.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .connection_state_py3 import ConnectionState + + +class ConnectionPropertiesPrivateLinkServiceConnectionState(ConnectionState): + """ConnectionPropertiesPrivateLinkServiceConnectionState. + + All required parameters must be populated in order to send to Azure. + + :param status: Required. The status of a private endpoint connection. + Possible values include: 'Pending', 'Approved', 'Rejected', 'Disconnected' + :type status: str or + ~controlplane.models.PrivateLinkServiceConnectionStatus + :param description: Required. The description for the current state of a + private endpoint connection. + :type description: str + :param actions_required: Actions required for a private endpoint + connection. + :type actions_required: str + """ + + _validation = { + 'status': {'required': True}, + 'description': {'required': True}, + } + + _attribute_map = { + 'status': {'key': 'status', 'type': 'str'}, + 'description': {'key': 'description', 'type': 'str'}, + 'actions_required': {'key': 'actionsRequired', 'type': 'str'}, + } + + def __init__(self, *, status, description: str, actions_required: str=None, **kwargs) -> None: + super(ConnectionPropertiesPrivateLinkServiceConnectionState, self).__init__(status=status, description=description, actions_required=actions_required, **kwargs) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_py3.py new file mode 100644 index 000000000..25ef458e9 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/connection_properties_py3.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ConnectionProperties(Model): + """The properties of a private endpoint connection. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Pending', 'Approved', 'Rejected', 'Disconnected' + :vartype provisioning_state: str or + ~controlplane.models.ConnectionPropertiesProvisioningState + :param private_endpoint: + :type private_endpoint: + ~controlplane.models.ConnectionPropertiesPrivateEndpoint + :param group_ids: The list of group ids for the private endpoint + connection. + :type group_ids: list[str] + :param private_link_service_connection_state: + :type private_link_service_connection_state: + ~controlplane.models.ConnectionPropertiesPrivateLinkServiceConnectionState + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'private_endpoint': {'key': 'privateEndpoint', 'type': 'ConnectionPropertiesPrivateEndpoint'}, + 'group_ids': {'key': 'groupIds', 'type': '[str]'}, + 'private_link_service_connection_state': {'key': 'privateLinkServiceConnectionState', 'type': 'ConnectionPropertiesPrivateLinkServiceConnectionState'}, + } + + def __init__(self, *, private_endpoint=None, group_ids=None, private_link_service_connection_state=None, **kwargs) -> None: + super(ConnectionProperties, self).__init__(**kwargs) + self.provisioning_state = None + self.private_endpoint = private_endpoint + self.group_ids = group_ids + self.private_link_service_connection_state = private_link_service_connection_state diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/connection_state.py b/azext_iot/sdk/digitaltwins/controlplane/models/connection_state.py new file mode 100644 index 000000000..01ecfdc8a --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/connection_state.py @@ -0,0 +1,47 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ConnectionState(Model): + """The current state of a private endpoint connection. + + All required parameters must be populated in order to send to Azure. + + :param status: Required. The status of a private endpoint connection. + Possible values include: 'Pending', 'Approved', 'Rejected', 'Disconnected' + :type status: str or + ~controlplane.models.PrivateLinkServiceConnectionStatus + :param description: Required. The description for the current state of a + private endpoint connection. + :type description: str + :param actions_required: Actions required for a private endpoint + connection. + :type actions_required: str + """ + + _validation = { + 'status': {'required': True}, + 'description': {'required': True}, + } + + _attribute_map = { + 'status': {'key': 'status', 'type': 'str'}, + 'description': {'key': 'description', 'type': 'str'}, + 'actions_required': {'key': 'actionsRequired', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(ConnectionState, self).__init__(**kwargs) + self.status = kwargs.get('status', None) + self.description = kwargs.get('description', None) + self.actions_required = kwargs.get('actions_required', None) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/connection_state_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/connection_state_py3.py new file mode 100644 index 000000000..938552448 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/connection_state_py3.py @@ -0,0 +1,47 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ConnectionState(Model): + """The current state of a private endpoint connection. + + All required parameters must be populated in order to send to Azure. + + :param status: Required. The status of a private endpoint connection. + Possible values include: 'Pending', 'Approved', 'Rejected', 'Disconnected' + :type status: str or + ~controlplane.models.PrivateLinkServiceConnectionStatus + :param description: Required. The description for the current state of a + private endpoint connection. + :type description: str + :param actions_required: Actions required for a private endpoint + connection. + :type actions_required: str + """ + + _validation = { + 'status': {'required': True}, + 'description': {'required': True}, + } + + _attribute_map = { + 'status': {'key': 'status', 'type': 'str'}, + 'description': {'key': 'description', 'type': 'str'}, + 'actions_required': {'key': 'actionsRequired', 'type': 'str'}, + } + + def __init__(self, *, status, description: str, actions_required: str=None, **kwargs) -> None: + super(ConnectionState, self).__init__(**kwargs) + self.status = status + self.description = description + self.actions_required = actions_required diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_description.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_description.py new file mode 100644 index 000000000..22e7d559b --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_description.py @@ -0,0 +1,87 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .digital_twins_resource import DigitalTwinsResource + + +class DigitalTwinsDescription(DigitalTwinsResource): + """The description of the DigitalTwins service. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar id: The resource identifier. + :vartype id: str + :ivar name: The resource name. + :vartype name: str + :ivar type: The resource type. + :vartype type: str + :param location: Required. The resource location. + :type location: str + :param tags: The resource tags. + :type tags: dict[str, str] + :param identity: The managed identity for the DigitalTwinsInstance. + :type identity: ~controlplane.models.DigitalTwinsIdentity + :ivar created_time: Time when DigitalTwinsInstance was created. + :vartype created_time: datetime + :ivar last_updated_time: Time when DigitalTwinsInstance was updated. + :vartype last_updated_time: datetime + :ivar provisioning_state: The provisioning state. Possible values include: + 'Provisioning', 'Deleting', 'Updating', 'Succeeded', 'Failed', 'Canceled', + 'Deleted', 'Warning', 'Suspending', 'Restoring', 'Moving' + :vartype provisioning_state: str or ~controlplane.models.ProvisioningState + :ivar host_name: Api endpoint to work with DigitalTwinsInstance. + :vartype host_name: str + :param private_endpoint_connections: + :type private_endpoint_connections: + list[~controlplane.models.PrivateEndpointConnection] + :param public_network_access: Public network access for the + DigitalTwinsInstance. Possible values include: 'Enabled', 'Disabled' + :type public_network_access: str or + ~controlplane.models.PublicNetworkAccess + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True, 'pattern': r'^(?!-)[A-Za-z0-9-]{3,63}(?` object + """ + + _attribute_map = { + 'next_link': {'key': 'nextLink', 'type': 'str'}, + 'current_page': {'key': 'value', 'type': '[DigitalTwinsDescription]'} + } + + def __init__(self, *args, **kwargs): + + super(DigitalTwinsDescriptionPaged, self).__init__(*args, **kwargs) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_description_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_description_py3.py new file mode 100644 index 000000000..baeaccee7 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_description_py3.py @@ -0,0 +1,87 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .digital_twins_resource_py3 import DigitalTwinsResource + + +class DigitalTwinsDescription(DigitalTwinsResource): + """The description of the DigitalTwins service. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar id: The resource identifier. + :vartype id: str + :ivar name: The resource name. + :vartype name: str + :ivar type: The resource type. + :vartype type: str + :param location: Required. The resource location. + :type location: str + :param tags: The resource tags. + :type tags: dict[str, str] + :param identity: The managed identity for the DigitalTwinsInstance. + :type identity: ~controlplane.models.DigitalTwinsIdentity + :ivar created_time: Time when DigitalTwinsInstance was created. + :vartype created_time: datetime + :ivar last_updated_time: Time when DigitalTwinsInstance was updated. + :vartype last_updated_time: datetime + :ivar provisioning_state: The provisioning state. Possible values include: + 'Provisioning', 'Deleting', 'Updating', 'Succeeded', 'Failed', 'Canceled', + 'Deleted', 'Warning', 'Suspending', 'Restoring', 'Moving' + :vartype provisioning_state: str or ~controlplane.models.ProvisioningState + :ivar host_name: Api endpoint to work with DigitalTwinsInstance. + :vartype host_name: str + :param private_endpoint_connections: + :type private_endpoint_connections: + list[~controlplane.models.PrivateEndpointConnection] + :param public_network_access: Public network access for the + DigitalTwinsInstance. Possible values include: 'Enabled', 'Disabled' + :type public_network_access: str or + ~controlplane.models.PublicNetworkAccess + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True, 'pattern': r'^(?!-)[A-Za-z0-9-]{3,63}(? None: + super(DigitalTwinsDescription, self).__init__(location=location, tags=tags, identity=identity, **kwargs) + self.created_time = None + self.last_updated_time = None + self.provisioning_state = None + self.host_name = None + self.private_endpoint_connections = private_endpoint_connections + self.public_network_access = public_network_access diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource.py new file mode 100644 index 000000000..77f8d379d --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .external_resource import ExternalResource + + +class DigitalTwinsEndpointResource(ExternalResource): + """DigitalTwinsInstance endpoint resource. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar id: The resource identifier. + :vartype id: str + :ivar name: Extension resource name. + :vartype name: str + :ivar type: The resource type. + :vartype type: str + :param properties: Required. DigitalTwinsInstance endpoint resource + properties. + :type properties: + ~controlplane.models.DigitalTwinsEndpointResourceProperties + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True, 'pattern': r'^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{2,49}[a-zA-Z0-9]$'}, + 'type': {'readonly': True}, + 'properties': {'required': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + 'properties': {'key': 'properties', 'type': 'DigitalTwinsEndpointResourceProperties'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsEndpointResource, self).__init__(**kwargs) + self.properties = kwargs.get('properties', None) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_paged.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_paged.py new file mode 100644 index 000000000..c66d5f8c1 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_paged.py @@ -0,0 +1,27 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.paging import Paged + + +class DigitalTwinsEndpointResourcePaged(Paged): + """ + A paging container for iterating over a list of :class:`DigitalTwinsEndpointResource ` object + """ + + _attribute_map = { + 'next_link': {'key': 'nextLink', 'type': 'str'}, + 'current_page': {'key': 'value', 'type': '[DigitalTwinsEndpointResource]'} + } + + def __init__(self, *args, **kwargs): + + super(DigitalTwinsEndpointResourcePaged, self).__init__(*args, **kwargs) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_properties.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_properties.py new file mode 100644 index 000000000..f9bd90a60 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_properties.py @@ -0,0 +1,74 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsEndpointResourceProperties(Model): + """Properties related to Digital Twins Endpoint. + + You probably want to use the sub-classes and not this class directly. Known + sub-classes are: ServiceBus, EventHub, EventGrid + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Provisioning', 'Deleting', 'Succeeded', 'Failed', 'Canceled', 'Deleted', + 'Warning', 'Suspending', 'Restoring', 'Moving', 'Disabled' + :vartype provisioning_state: str or + ~controlplane.models.EndpointProvisioningState + :ivar created_time: Time when the Endpoint was added to + DigitalTwinsInstance. + :vartype created_time: datetime + :param authentication_type: Specifies the authentication type being used + for connecting to the endpoint. Possible values include: 'KeyBased', + 'IdentityBased' + :type authentication_type: str or ~controlplane.models.AuthenticationType + :param dead_letter_secret: Dead letter storage secret for key-based + authentication. Will be obfuscated during read. + :type dead_letter_secret: str + :param dead_letter_uri: Dead letter storage URL for identity-based + authentication. + :type dead_letter_uri: str + :param endpoint_type: Required. Constant filled by server. + :type endpoint_type: str + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + 'created_time': {'readonly': True}, + 'endpoint_type': {'required': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'created_time': {'key': 'createdTime', 'type': 'iso-8601'}, + 'authentication_type': {'key': 'authenticationType', 'type': 'str'}, + 'dead_letter_secret': {'key': 'deadLetterSecret', 'type': 'str'}, + 'dead_letter_uri': {'key': 'deadLetterUri', 'type': 'str'}, + 'endpoint_type': {'key': 'endpointType', 'type': 'str'}, + } + + _subtype_map = { + 'endpoint_type': {'ServiceBus': 'ServiceBus', 'EventHub': 'EventHub', 'EventGrid': 'EventGrid'} + } + + def __init__(self, **kwargs): + super(DigitalTwinsEndpointResourceProperties, self).__init__(**kwargs) + self.provisioning_state = None + self.created_time = None + self.authentication_type = kwargs.get('authentication_type', None) + self.dead_letter_secret = kwargs.get('dead_letter_secret', None) + self.dead_letter_uri = kwargs.get('dead_letter_uri', None) + self.endpoint_type = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_properties_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_properties_py3.py new file mode 100644 index 000000000..19a172fc1 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_properties_py3.py @@ -0,0 +1,74 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsEndpointResourceProperties(Model): + """Properties related to Digital Twins Endpoint. + + You probably want to use the sub-classes and not this class directly. Known + sub-classes are: ServiceBus, EventHub, EventGrid + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Provisioning', 'Deleting', 'Succeeded', 'Failed', 'Canceled', 'Deleted', + 'Warning', 'Suspending', 'Restoring', 'Moving', 'Disabled' + :vartype provisioning_state: str or + ~controlplane.models.EndpointProvisioningState + :ivar created_time: Time when the Endpoint was added to + DigitalTwinsInstance. + :vartype created_time: datetime + :param authentication_type: Specifies the authentication type being used + for connecting to the endpoint. Possible values include: 'KeyBased', + 'IdentityBased' + :type authentication_type: str or ~controlplane.models.AuthenticationType + :param dead_letter_secret: Dead letter storage secret for key-based + authentication. Will be obfuscated during read. + :type dead_letter_secret: str + :param dead_letter_uri: Dead letter storage URL for identity-based + authentication. + :type dead_letter_uri: str + :param endpoint_type: Required. Constant filled by server. + :type endpoint_type: str + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + 'created_time': {'readonly': True}, + 'endpoint_type': {'required': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'created_time': {'key': 'createdTime', 'type': 'iso-8601'}, + 'authentication_type': {'key': 'authenticationType', 'type': 'str'}, + 'dead_letter_secret': {'key': 'deadLetterSecret', 'type': 'str'}, + 'dead_letter_uri': {'key': 'deadLetterUri', 'type': 'str'}, + 'endpoint_type': {'key': 'endpointType', 'type': 'str'}, + } + + _subtype_map = { + 'endpoint_type': {'ServiceBus': 'ServiceBus', 'EventHub': 'EventHub', 'EventGrid': 'EventGrid'} + } + + def __init__(self, *, authentication_type=None, dead_letter_secret: str=None, dead_letter_uri: str=None, **kwargs) -> None: + super(DigitalTwinsEndpointResourceProperties, self).__init__(**kwargs) + self.provisioning_state = None + self.created_time = None + self.authentication_type = authentication_type + self.dead_letter_secret = dead_letter_secret + self.dead_letter_uri = dead_letter_uri + self.endpoint_type = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_py3.py new file mode 100644 index 000000000..bc3189328 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_endpoint_resource_py3.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .external_resource_py3 import ExternalResource + + +class DigitalTwinsEndpointResource(ExternalResource): + """DigitalTwinsInstance endpoint resource. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar id: The resource identifier. + :vartype id: str + :ivar name: Extension resource name. + :vartype name: str + :ivar type: The resource type. + :vartype type: str + :param properties: Required. DigitalTwinsInstance endpoint resource + properties. + :type properties: + ~controlplane.models.DigitalTwinsEndpointResourceProperties + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True, 'pattern': r'^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{2,49}[a-zA-Z0-9]$'}, + 'type': {'readonly': True}, + 'properties': {'required': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + 'properties': {'key': 'properties', 'type': 'DigitalTwinsEndpointResourceProperties'}, + } + + def __init__(self, *, properties, **kwargs) -> None: + super(DigitalTwinsEndpointResource, self).__init__(**kwargs) + self.properties = properties diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_identity.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_identity.py new file mode 100644 index 000000000..a7560f9cc --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_identity.py @@ -0,0 +1,50 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsIdentity(Model): + """The managed identity for the DigitalTwinsInstance. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param type: The type of Managed Identity used by the + DigitalTwinsInstance. Only SystemAssigned is supported. Possible values + include: 'None', 'SystemAssigned' + :type type: str or ~controlplane.models.DigitalTwinsIdentityType + :ivar principal_id: The object id of the Managed Identity Resource. This + will be sent to the RP from ARM via the x-ms-identity-principal-id header + in the PUT request if the resource has a systemAssigned(implicit) identity + :vartype principal_id: str + :ivar tenant_id: The tenant id of the Managed Identity Resource. This will + be sent to the RP from ARM via the x-ms-client-tenant-id header in the PUT + request if the resource has a systemAssigned(implicit) identity + :vartype tenant_id: str + """ + + _validation = { + 'principal_id': {'readonly': True}, + 'tenant_id': {'readonly': True}, + } + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'principal_id': {'key': 'principalId', 'type': 'str'}, + 'tenant_id': {'key': 'tenantId', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsIdentity, self).__init__(**kwargs) + self.type = kwargs.get('type', None) + self.principal_id = None + self.tenant_id = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_identity_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_identity_py3.py new file mode 100644 index 000000000..e2c7b19d6 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_identity_py3.py @@ -0,0 +1,50 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsIdentity(Model): + """The managed identity for the DigitalTwinsInstance. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param type: The type of Managed Identity used by the + DigitalTwinsInstance. Only SystemAssigned is supported. Possible values + include: 'None', 'SystemAssigned' + :type type: str or ~controlplane.models.DigitalTwinsIdentityType + :ivar principal_id: The object id of the Managed Identity Resource. This + will be sent to the RP from ARM via the x-ms-identity-principal-id header + in the PUT request if the resource has a systemAssigned(implicit) identity + :vartype principal_id: str + :ivar tenant_id: The tenant id of the Managed Identity Resource. This will + be sent to the RP from ARM via the x-ms-client-tenant-id header in the PUT + request if the resource has a systemAssigned(implicit) identity + :vartype tenant_id: str + """ + + _validation = { + 'principal_id': {'readonly': True}, + 'tenant_id': {'readonly': True}, + } + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'principal_id': {'key': 'principalId', 'type': 'str'}, + 'tenant_id': {'key': 'tenantId', 'type': 'str'}, + } + + def __init__(self, *, type=None, **kwargs) -> None: + super(DigitalTwinsIdentity, self).__init__(**kwargs) + self.type = type + self.principal_id = None + self.tenant_id = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_description.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_description.py new file mode 100644 index 000000000..45eca2eab --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_description.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsPatchDescription(Model): + """The description of the DigitalTwins service. + + :param tags: Instance patch properties + :type tags: dict[str, str] + :param identity: The managed identity for the DigitalTwinsInstance. + :type identity: ~controlplane.models.DigitalTwinsIdentity + :param properties: Properties for the DigitalTwinsInstance. + :type properties: ~controlplane.models.DigitalTwinsPatchProperties + """ + + _attribute_map = { + 'tags': {'key': 'tags', 'type': '{str}'}, + 'identity': {'key': 'identity', 'type': 'DigitalTwinsIdentity'}, + 'properties': {'key': 'properties', 'type': 'DigitalTwinsPatchProperties'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsPatchDescription, self).__init__(**kwargs) + self.tags = kwargs.get('tags', None) + self.identity = kwargs.get('identity', None) + self.properties = kwargs.get('properties', None) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_description_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_description_py3.py new file mode 100644 index 000000000..a4921909e --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_description_py3.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsPatchDescription(Model): + """The description of the DigitalTwins service. + + :param tags: Instance patch properties + :type tags: dict[str, str] + :param identity: The managed identity for the DigitalTwinsInstance. + :type identity: ~controlplane.models.DigitalTwinsIdentity + :param properties: Properties for the DigitalTwinsInstance. + :type properties: ~controlplane.models.DigitalTwinsPatchProperties + """ + + _attribute_map = { + 'tags': {'key': 'tags', 'type': '{str}'}, + 'identity': {'key': 'identity', 'type': 'DigitalTwinsIdentity'}, + 'properties': {'key': 'properties', 'type': 'DigitalTwinsPatchProperties'}, + } + + def __init__(self, *, tags=None, identity=None, properties=None, **kwargs) -> None: + super(DigitalTwinsPatchDescription, self).__init__(**kwargs) + self.tags = tags + self.identity = identity + self.properties = properties diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_properties.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_properties.py new file mode 100644 index 000000000..9bee963ae --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_properties.py @@ -0,0 +1,30 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsPatchProperties(Model): + """The properties of a DigitalTwinsInstance. + + :param public_network_access: Public network access for the + DigitalTwinsInstance. Possible values include: 'Enabled', 'Disabled' + :type public_network_access: str or + ~controlplane.models.PublicNetworkAccess + """ + + _attribute_map = { + 'public_network_access': {'key': 'publicNetworkAccess', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsPatchProperties, self).__init__(**kwargs) + self.public_network_access = kwargs.get('public_network_access', None) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_properties_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_properties_py3.py new file mode 100644 index 000000000..3e356513b --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_patch_properties_py3.py @@ -0,0 +1,30 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsPatchProperties(Model): + """The properties of a DigitalTwinsInstance. + + :param public_network_access: Public network access for the + DigitalTwinsInstance. Possible values include: 'Enabled', 'Disabled' + :type public_network_access: str or + ~controlplane.models.PublicNetworkAccess + """ + + _attribute_map = { + 'public_network_access': {'key': 'publicNetworkAccess', 'type': 'str'}, + } + + def __init__(self, *, public_network_access=None, **kwargs) -> None: + super(DigitalTwinsPatchProperties, self).__init__(**kwargs) + self.public_network_access = public_network_access diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_resource.py b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_resource.py new file mode 100644 index 000000000..08674430c --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/digital_twins_resource.py @@ -0,0 +1,60 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsResource(Model): + """The common properties of a DigitalTwinsInstance. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar id: The resource identifier. + :vartype id: str + :ivar name: The resource name. + :vartype name: str + :ivar type: The resource type. + :vartype type: str + :param location: Required. The resource location. + :type location: str + :param tags: The resource tags. + :type tags: dict[str, str] + :param identity: The managed identity for the DigitalTwinsInstance. + :type identity: ~controlplane.models.DigitalTwinsIdentity + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True, 'pattern': r'^(?!-)[A-Za-z0-9-]{3,63}(? None: + super(DigitalTwinsResource, self).__init__(**kwargs) + self.id = None + self.name = None + self.type = None + self.location = location + self.tags = tags + self.identity = identity diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/error_definition.py b/azext_iot/sdk/digitaltwins/controlplane/models/error_definition.py new file mode 100644 index 000000000..161802641 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/error_definition.py @@ -0,0 +1,46 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ErrorDefinition(Model): + """Error definition. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar code: Service specific error code which serves as the substatus for + the HTTP error code. + :vartype code: str + :ivar message: Description of the error. + :vartype message: str + :ivar details: Internal error details. + :vartype details: list[~controlplane.models.ErrorDefinition] + """ + + _validation = { + 'code': {'readonly': True}, + 'message': {'readonly': True}, + 'details': {'readonly': True}, + } + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'details': {'key': 'details', 'type': '[ErrorDefinition]'}, + } + + def __init__(self, **kwargs): + super(ErrorDefinition, self).__init__(**kwargs) + self.code = None + self.message = None + self.details = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/error_definition_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/error_definition_py3.py new file mode 100644 index 000000000..09504ef69 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/error_definition_py3.py @@ -0,0 +1,46 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ErrorDefinition(Model): + """Error definition. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar code: Service specific error code which serves as the substatus for + the HTTP error code. + :vartype code: str + :ivar message: Description of the error. + :vartype message: str + :ivar details: Internal error details. + :vartype details: list[~controlplane.models.ErrorDefinition] + """ + + _validation = { + 'code': {'readonly': True}, + 'message': {'readonly': True}, + 'details': {'readonly': True}, + } + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'details': {'key': 'details', 'type': '[ErrorDefinition]'}, + } + + def __init__(self, **kwargs) -> None: + super(ErrorDefinition, self).__init__(**kwargs) + self.code = None + self.message = None + self.details = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/error_response.py b/azext_iot/sdk/digitaltwins/controlplane/models/error_response.py new file mode 100644 index 000000000..ddc297c39 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/error_response.py @@ -0,0 +1,41 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model +from msrest.exceptions import HttpOperationError + + +class ErrorResponse(Model): + """Error response. + + :param error: Error description + :type error: ~controlplane.models.ErrorDefinition + """ + + _attribute_map = { + 'error': {'key': 'error', 'type': 'ErrorDefinition'}, + } + + def __init__(self, **kwargs): + super(ErrorResponse, self).__init__(**kwargs) + self.error = kwargs.get('error', None) + + +class ErrorResponseException(HttpOperationError): + """Server responsed with exception of type: 'ErrorResponse'. + + :param deserialize: A deserializer + :param response: Server response to be deserialized. + """ + + def __init__(self, deserialize, response, *args): + + super(ErrorResponseException, self).__init__(deserialize, response, 'ErrorResponse', *args) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/error_response_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/error_response_py3.py new file mode 100644 index 000000000..2d9a3da48 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/error_response_py3.py @@ -0,0 +1,41 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model +from msrest.exceptions import HttpOperationError + + +class ErrorResponse(Model): + """Error response. + + :param error: Error description + :type error: ~controlplane.models.ErrorDefinition + """ + + _attribute_map = { + 'error': {'key': 'error', 'type': 'ErrorDefinition'}, + } + + def __init__(self, *, error=None, **kwargs) -> None: + super(ErrorResponse, self).__init__(**kwargs) + self.error = error + + +class ErrorResponseException(HttpOperationError): + """Server responsed with exception of type: 'ErrorResponse'. + + :param deserialize: A deserializer + :param response: Server response to be deserialized. + """ + + def __init__(self, deserialize, response, *args): + + super(ErrorResponseException, self).__init__(deserialize, response, 'ErrorResponse', *args) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/event_grid.py b/azext_iot/sdk/digitaltwins/controlplane/models/event_grid.py new file mode 100644 index 000000000..c2e176bed --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/event_grid.py @@ -0,0 +1,78 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .digital_twins_endpoint_resource_properties import DigitalTwinsEndpointResourceProperties + + +class EventGrid(DigitalTwinsEndpointResourceProperties): + """Properties related to EventGrid. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Provisioning', 'Deleting', 'Succeeded', 'Failed', 'Canceled', 'Deleted', + 'Warning', 'Suspending', 'Restoring', 'Moving', 'Disabled' + :vartype provisioning_state: str or + ~controlplane.models.EndpointProvisioningState + :ivar created_time: Time when the Endpoint was added to + DigitalTwinsInstance. + :vartype created_time: datetime + :param authentication_type: Specifies the authentication type being used + for connecting to the endpoint. Possible values include: 'KeyBased', + 'IdentityBased' + :type authentication_type: str or ~controlplane.models.AuthenticationType + :param dead_letter_secret: Dead letter storage secret for key-based + authentication. Will be obfuscated during read. + :type dead_letter_secret: str + :param dead_letter_uri: Dead letter storage URL for identity-based + authentication. + :type dead_letter_uri: str + :param endpoint_type: Required. Constant filled by server. + :type endpoint_type: str + :param topic_endpoint: Required. EventGrid Topic Endpoint + :type topic_endpoint: str + :param access_key1: Required. EventGrid secondary accesskey. Will be + obfuscated during read. + :type access_key1: str + :param access_key2: EventGrid secondary accesskey. Will be obfuscated + during read. + :type access_key2: str + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + 'created_time': {'readonly': True}, + 'endpoint_type': {'required': True}, + 'topic_endpoint': {'required': True}, + 'access_key1': {'required': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'created_time': {'key': 'createdTime', 'type': 'iso-8601'}, + 'authentication_type': {'key': 'authenticationType', 'type': 'str'}, + 'dead_letter_secret': {'key': 'deadLetterSecret', 'type': 'str'}, + 'dead_letter_uri': {'key': 'deadLetterUri', 'type': 'str'}, + 'endpoint_type': {'key': 'endpointType', 'type': 'str'}, + 'topic_endpoint': {'key': 'TopicEndpoint', 'type': 'str'}, + 'access_key1': {'key': 'accessKey1', 'type': 'str'}, + 'access_key2': {'key': 'accessKey2', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(EventGrid, self).__init__(**kwargs) + self.topic_endpoint = kwargs.get('topic_endpoint', None) + self.access_key1 = kwargs.get('access_key1', None) + self.access_key2 = kwargs.get('access_key2', None) + self.endpoint_type = 'EventGrid' diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/event_grid_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/event_grid_py3.py new file mode 100644 index 000000000..5643ed6ed --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/event_grid_py3.py @@ -0,0 +1,78 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .digital_twins_endpoint_resource_properties_py3 import DigitalTwinsEndpointResourceProperties + + +class EventGrid(DigitalTwinsEndpointResourceProperties): + """Properties related to EventGrid. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Provisioning', 'Deleting', 'Succeeded', 'Failed', 'Canceled', 'Deleted', + 'Warning', 'Suspending', 'Restoring', 'Moving', 'Disabled' + :vartype provisioning_state: str or + ~controlplane.models.EndpointProvisioningState + :ivar created_time: Time when the Endpoint was added to + DigitalTwinsInstance. + :vartype created_time: datetime + :param authentication_type: Specifies the authentication type being used + for connecting to the endpoint. Possible values include: 'KeyBased', + 'IdentityBased' + :type authentication_type: str or ~controlplane.models.AuthenticationType + :param dead_letter_secret: Dead letter storage secret for key-based + authentication. Will be obfuscated during read. + :type dead_letter_secret: str + :param dead_letter_uri: Dead letter storage URL for identity-based + authentication. + :type dead_letter_uri: str + :param endpoint_type: Required. Constant filled by server. + :type endpoint_type: str + :param topic_endpoint: Required. EventGrid Topic Endpoint + :type topic_endpoint: str + :param access_key1: Required. EventGrid secondary accesskey. Will be + obfuscated during read. + :type access_key1: str + :param access_key2: EventGrid secondary accesskey. Will be obfuscated + during read. + :type access_key2: str + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + 'created_time': {'readonly': True}, + 'endpoint_type': {'required': True}, + 'topic_endpoint': {'required': True}, + 'access_key1': {'required': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'created_time': {'key': 'createdTime', 'type': 'iso-8601'}, + 'authentication_type': {'key': 'authenticationType', 'type': 'str'}, + 'dead_letter_secret': {'key': 'deadLetterSecret', 'type': 'str'}, + 'dead_letter_uri': {'key': 'deadLetterUri', 'type': 'str'}, + 'endpoint_type': {'key': 'endpointType', 'type': 'str'}, + 'topic_endpoint': {'key': 'TopicEndpoint', 'type': 'str'}, + 'access_key1': {'key': 'accessKey1', 'type': 'str'}, + 'access_key2': {'key': 'accessKey2', 'type': 'str'}, + } + + def __init__(self, *, topic_endpoint: str, access_key1: str, authentication_type=None, dead_letter_secret: str=None, dead_letter_uri: str=None, access_key2: str=None, **kwargs) -> None: + super(EventGrid, self).__init__(authentication_type=authentication_type, dead_letter_secret=dead_letter_secret, dead_letter_uri=dead_letter_uri, **kwargs) + self.topic_endpoint = topic_endpoint + self.access_key1 = access_key1 + self.access_key2 = access_key2 + self.endpoint_type = 'EventGrid' diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/event_hub.py b/azext_iot/sdk/digitaltwins/controlplane/models/event_hub.py new file mode 100644 index 000000000..2bb4e0584 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/event_hub.py @@ -0,0 +1,82 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .digital_twins_endpoint_resource_properties import DigitalTwinsEndpointResourceProperties + + +class EventHub(DigitalTwinsEndpointResourceProperties): + """Properties related to EventHub. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Provisioning', 'Deleting', 'Succeeded', 'Failed', 'Canceled', 'Deleted', + 'Warning', 'Suspending', 'Restoring', 'Moving', 'Disabled' + :vartype provisioning_state: str or + ~controlplane.models.EndpointProvisioningState + :ivar created_time: Time when the Endpoint was added to + DigitalTwinsInstance. + :vartype created_time: datetime + :param authentication_type: Specifies the authentication type being used + for connecting to the endpoint. Possible values include: 'KeyBased', + 'IdentityBased' + :type authentication_type: str or ~controlplane.models.AuthenticationType + :param dead_letter_secret: Dead letter storage secret for key-based + authentication. Will be obfuscated during read. + :type dead_letter_secret: str + :param dead_letter_uri: Dead letter storage URL for identity-based + authentication. + :type dead_letter_uri: str + :param endpoint_type: Required. Constant filled by server. + :type endpoint_type: str + :param connection_string_primary_key: PrimaryConnectionString of the + endpoint for key-based authentication. Will be obfuscated during read. + :type connection_string_primary_key: str + :param connection_string_secondary_key: SecondaryConnectionString of the + endpoint for key-based authentication. Will be obfuscated during read. + :type connection_string_secondary_key: str + :param endpoint_uri: The URL of the EventHub namespace for identity-based + authentication. It must include the protocol sb:// + :type endpoint_uri: str + :param entity_path: The EventHub name in the EventHub namespace for + identity-based authentication. + :type entity_path: str + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + 'created_time': {'readonly': True}, + 'endpoint_type': {'required': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'created_time': {'key': 'createdTime', 'type': 'iso-8601'}, + 'authentication_type': {'key': 'authenticationType', 'type': 'str'}, + 'dead_letter_secret': {'key': 'deadLetterSecret', 'type': 'str'}, + 'dead_letter_uri': {'key': 'deadLetterUri', 'type': 'str'}, + 'endpoint_type': {'key': 'endpointType', 'type': 'str'}, + 'connection_string_primary_key': {'key': 'connectionStringPrimaryKey', 'type': 'str'}, + 'connection_string_secondary_key': {'key': 'connectionStringSecondaryKey', 'type': 'str'}, + 'endpoint_uri': {'key': 'endpointUri', 'type': 'str'}, + 'entity_path': {'key': 'entityPath', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(EventHub, self).__init__(**kwargs) + self.connection_string_primary_key = kwargs.get('connection_string_primary_key', None) + self.connection_string_secondary_key = kwargs.get('connection_string_secondary_key', None) + self.endpoint_uri = kwargs.get('endpoint_uri', None) + self.entity_path = kwargs.get('entity_path', None) + self.endpoint_type = 'EventHub' diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/event_hub_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/event_hub_py3.py new file mode 100644 index 000000000..8c22226af --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/event_hub_py3.py @@ -0,0 +1,82 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .digital_twins_endpoint_resource_properties_py3 import DigitalTwinsEndpointResourceProperties + + +class EventHub(DigitalTwinsEndpointResourceProperties): + """Properties related to EventHub. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Provisioning', 'Deleting', 'Succeeded', 'Failed', 'Canceled', 'Deleted', + 'Warning', 'Suspending', 'Restoring', 'Moving', 'Disabled' + :vartype provisioning_state: str or + ~controlplane.models.EndpointProvisioningState + :ivar created_time: Time when the Endpoint was added to + DigitalTwinsInstance. + :vartype created_time: datetime + :param authentication_type: Specifies the authentication type being used + for connecting to the endpoint. Possible values include: 'KeyBased', + 'IdentityBased' + :type authentication_type: str or ~controlplane.models.AuthenticationType + :param dead_letter_secret: Dead letter storage secret for key-based + authentication. Will be obfuscated during read. + :type dead_letter_secret: str + :param dead_letter_uri: Dead letter storage URL for identity-based + authentication. + :type dead_letter_uri: str + :param endpoint_type: Required. Constant filled by server. + :type endpoint_type: str + :param connection_string_primary_key: PrimaryConnectionString of the + endpoint for key-based authentication. Will be obfuscated during read. + :type connection_string_primary_key: str + :param connection_string_secondary_key: SecondaryConnectionString of the + endpoint for key-based authentication. Will be obfuscated during read. + :type connection_string_secondary_key: str + :param endpoint_uri: The URL of the EventHub namespace for identity-based + authentication. It must include the protocol sb:// + :type endpoint_uri: str + :param entity_path: The EventHub name in the EventHub namespace for + identity-based authentication. + :type entity_path: str + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + 'created_time': {'readonly': True}, + 'endpoint_type': {'required': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'created_time': {'key': 'createdTime', 'type': 'iso-8601'}, + 'authentication_type': {'key': 'authenticationType', 'type': 'str'}, + 'dead_letter_secret': {'key': 'deadLetterSecret', 'type': 'str'}, + 'dead_letter_uri': {'key': 'deadLetterUri', 'type': 'str'}, + 'endpoint_type': {'key': 'endpointType', 'type': 'str'}, + 'connection_string_primary_key': {'key': 'connectionStringPrimaryKey', 'type': 'str'}, + 'connection_string_secondary_key': {'key': 'connectionStringSecondaryKey', 'type': 'str'}, + 'endpoint_uri': {'key': 'endpointUri', 'type': 'str'}, + 'entity_path': {'key': 'entityPath', 'type': 'str'}, + } + + def __init__(self, *, authentication_type=None, dead_letter_secret: str=None, dead_letter_uri: str=None, connection_string_primary_key: str=None, connection_string_secondary_key: str=None, endpoint_uri: str=None, entity_path: str=None, **kwargs) -> None: + super(EventHub, self).__init__(authentication_type=authentication_type, dead_letter_secret=dead_letter_secret, dead_letter_uri=dead_letter_uri, **kwargs) + self.connection_string_primary_key = connection_string_primary_key + self.connection_string_secondary_key = connection_string_secondary_key + self.endpoint_uri = endpoint_uri + self.entity_path = entity_path + self.endpoint_type = 'EventHub' diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/external_resource.py b/azext_iot/sdk/digitaltwins/controlplane/models/external_resource.py new file mode 100644 index 000000000..a22ca0a06 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/external_resource.py @@ -0,0 +1,45 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ExternalResource(Model): + """Definition of a resource. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar id: The resource identifier. + :vartype id: str + :ivar name: Extension resource name. + :vartype name: str + :ivar type: The resource type. + :vartype type: str + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True, 'pattern': r'^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{2,49}[a-zA-Z0-9]$'}, + 'type': {'readonly': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(ExternalResource, self).__init__(**kwargs) + self.id = None + self.name = None + self.type = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/external_resource_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/external_resource_py3.py new file mode 100644 index 000000000..83ed4d616 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/external_resource_py3.py @@ -0,0 +1,45 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ExternalResource(Model): + """Definition of a resource. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar id: The resource identifier. + :vartype id: str + :ivar name: Extension resource name. + :vartype name: str + :ivar type: The resource type. + :vartype type: str + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True, 'pattern': r'^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{2,49}[a-zA-Z0-9]$'}, + 'type': {'readonly': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, **kwargs) -> None: + super(ExternalResource, self).__init__(**kwargs) + self.id = None + self.name = None + self.type = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information.py b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information.py new file mode 100644 index 000000000..ac990009c --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class GroupIdInformation(Model): + """The group information for creating a private endpoint on Digital Twin. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :param properties: Required. + :type properties: ~controlplane.models.GroupIdInformationPropertiesModel + :param id: The resource identifier. + :type id: str + :ivar name: The resource name. + :vartype name: str + :ivar type: The resource type. + :vartype type: str + """ + + _validation = { + 'properties': {'required': True}, + 'name': {'readonly': True, 'pattern': r'^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{2,49}[a-zA-Z0-9]$'}, + 'type': {'readonly': True}, + } + + _attribute_map = { + 'properties': {'key': 'properties', 'type': 'GroupIdInformationPropertiesModel'}, + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(GroupIdInformation, self).__init__(**kwargs) + self.properties = kwargs.get('properties', None) + self.id = kwargs.get('id', None) + self.name = None + self.type = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties.py b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties.py new file mode 100644 index 000000000..1398514fb --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties.py @@ -0,0 +1,37 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class GroupIdInformationProperties(Model): + """The properties for a group information object. + + :param group_id: The group id + :type group_id: str + :param required_members: The required members for a specific group id. + :type required_members: list[str] + :param required_zone_names: The required DNS zones for a specific group + id. + :type required_zone_names: list[str] + """ + + _attribute_map = { + 'group_id': {'key': 'groupId', 'type': 'str'}, + 'required_members': {'key': 'requiredMembers', 'type': '[str]'}, + 'required_zone_names': {'key': 'requiredZoneNames', 'type': '[str]'}, + } + + def __init__(self, **kwargs): + super(GroupIdInformationProperties, self).__init__(**kwargs) + self.group_id = kwargs.get('group_id', None) + self.required_members = kwargs.get('required_members', None) + self.required_zone_names = kwargs.get('required_zone_names', None) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties_model.py b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties_model.py new file mode 100644 index 000000000..a60bfad30 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties_model.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .group_id_information_properties import GroupIdInformationProperties + + +class GroupIdInformationPropertiesModel(GroupIdInformationProperties): + """GroupIdInformationPropertiesModel. + + :param group_id: The group id + :type group_id: str + :param required_members: The required members for a specific group id. + :type required_members: list[str] + :param required_zone_names: The required DNS zones for a specific group + id. + :type required_zone_names: list[str] + """ + + _attribute_map = { + 'group_id': {'key': 'groupId', 'type': 'str'}, + 'required_members': {'key': 'requiredMembers', 'type': '[str]'}, + 'required_zone_names': {'key': 'requiredZoneNames', 'type': '[str]'}, + } + + def __init__(self, **kwargs): + super(GroupIdInformationPropertiesModel, self).__init__(**kwargs) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties_model_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties_model_py3.py new file mode 100644 index 000000000..b472ea7a6 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties_model_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .group_id_information_properties_py3 import GroupIdInformationProperties + + +class GroupIdInformationPropertiesModel(GroupIdInformationProperties): + """GroupIdInformationPropertiesModel. + + :param group_id: The group id + :type group_id: str + :param required_members: The required members for a specific group id. + :type required_members: list[str] + :param required_zone_names: The required DNS zones for a specific group + id. + :type required_zone_names: list[str] + """ + + _attribute_map = { + 'group_id': {'key': 'groupId', 'type': 'str'}, + 'required_members': {'key': 'requiredMembers', 'type': '[str]'}, + 'required_zone_names': {'key': 'requiredZoneNames', 'type': '[str]'}, + } + + def __init__(self, *, group_id: str=None, required_members=None, required_zone_names=None, **kwargs) -> None: + super(GroupIdInformationPropertiesModel, self).__init__(group_id=group_id, required_members=required_members, required_zone_names=required_zone_names, **kwargs) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties_py3.py new file mode 100644 index 000000000..3e44ba4a5 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_properties_py3.py @@ -0,0 +1,37 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class GroupIdInformationProperties(Model): + """The properties for a group information object. + + :param group_id: The group id + :type group_id: str + :param required_members: The required members for a specific group id. + :type required_members: list[str] + :param required_zone_names: The required DNS zones for a specific group + id. + :type required_zone_names: list[str] + """ + + _attribute_map = { + 'group_id': {'key': 'groupId', 'type': 'str'}, + 'required_members': {'key': 'requiredMembers', 'type': '[str]'}, + 'required_zone_names': {'key': 'requiredZoneNames', 'type': '[str]'}, + } + + def __init__(self, *, group_id: str=None, required_members=None, required_zone_names=None, **kwargs) -> None: + super(GroupIdInformationProperties, self).__init__(**kwargs) + self.group_id = group_id + self.required_members = required_members + self.required_zone_names = required_zone_names diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_py3.py new file mode 100644 index 000000000..4574232c2 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_py3.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class GroupIdInformation(Model): + """The group information for creating a private endpoint on Digital Twin. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :param properties: Required. + :type properties: ~controlplane.models.GroupIdInformationPropertiesModel + :param id: The resource identifier. + :type id: str + :ivar name: The resource name. + :vartype name: str + :ivar type: The resource type. + :vartype type: str + """ + + _validation = { + 'properties': {'required': True}, + 'name': {'readonly': True, 'pattern': r'^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{2,49}[a-zA-Z0-9]$'}, + 'type': {'readonly': True}, + } + + _attribute_map = { + 'properties': {'key': 'properties', 'type': 'GroupIdInformationPropertiesModel'}, + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, *, properties, id: str=None, **kwargs) -> None: + super(GroupIdInformation, self).__init__(**kwargs) + self.properties = properties + self.id = id + self.name = None + self.type = None diff --git a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_desired.py b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_response.py similarity index 60% rename from azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_desired.py rename to azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_response.py index 24f07c3d8..09c4dd92c 100644 --- a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_desired.py +++ b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_response.py @@ -12,18 +12,18 @@ from msrest.serialization import Model -class DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired(Model): - """DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired. +class GroupIdInformationResponse(Model): + """The available private link resources for a Digital Twin. - :param value: The desired value of the interface property to set in a - digitalTwin. - :type value: object + :param value: The list of available private link resources for a Digital + Twin. + :type value: list[~controlplane.models.GroupIdInformation] """ _attribute_map = { - 'value': {'key': 'value', 'type': 'object'}, + 'value': {'key': 'value', 'type': '[GroupIdInformation]'}, } def __init__(self, **kwargs): - super(DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired, self).__init__(**kwargs) + super(GroupIdInformationResponse, self).__init__(**kwargs) self.value = kwargs.get('value', None) diff --git a/azext_iot/sdk/iothub/service/models/desired_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_response_py3.py similarity index 62% rename from azext_iot/sdk/iothub/service/models/desired_py3.py rename to azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_response_py3.py index 2231d878f..0ae4024a9 100644 --- a/azext_iot/sdk/iothub/service/models/desired_py3.py +++ b/azext_iot/sdk/digitaltwins/controlplane/models/group_id_information_response_py3.py @@ -12,18 +12,18 @@ from msrest.serialization import Model -class Desired(Model): - """Desired. +class GroupIdInformationResponse(Model): + """The available private link resources for a Digital Twin. - :param value: The desired value of the interface property to set in a - digitalTwin. - :type value: object + :param value: The list of available private link resources for a Digital + Twin. + :type value: list[~controlplane.models.GroupIdInformation] """ _attribute_map = { - 'value': {'key': 'value', 'type': 'object'}, + 'value': {'key': 'value', 'type': '[GroupIdInformation]'}, } def __init__(self, *, value=None, **kwargs) -> None: - super(Desired, self).__init__(**kwargs) + super(GroupIdInformationResponse, self).__init__(**kwargs) self.value = value diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/operation.py b/azext_iot/sdk/digitaltwins/controlplane/models/operation.py new file mode 100644 index 000000000..234ed09af --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/operation.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class Operation(Model): + """DigitalTwins service REST API operation. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar name: Operation name: {provider}/{resource}/{read | write | action | + delete} + :vartype name: str + :param display: Operation properties display + :type display: ~controlplane.models.OperationDisplay + :ivar origin: The intended executor of the operation. + :vartype origin: str + :ivar is_data_action: If the operation is a data action (for data plane + rbac). + :vartype is_data_action: bool + """ + + _validation = { + 'name': {'readonly': True}, + 'origin': {'readonly': True}, + 'is_data_action': {'readonly': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'display': {'key': 'display', 'type': 'OperationDisplay'}, + 'origin': {'key': 'origin', 'type': 'str'}, + 'is_data_action': {'key': 'isDataAction', 'type': 'bool'}, + } + + def __init__(self, **kwargs): + super(Operation, self).__init__(**kwargs) + self.name = None + self.display = kwargs.get('display', None) + self.origin = None + self.is_data_action = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/operation_display.py b/azext_iot/sdk/digitaltwins/controlplane/models/operation_display.py new file mode 100644 index 000000000..8db099ffc --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/operation_display.py @@ -0,0 +1,50 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class OperationDisplay(Model): + """The object that represents the operation. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar provider: Service provider: Microsoft DigitalTwins + :vartype provider: str + :ivar resource: Resource Type: DigitalTwinsInstances + :vartype resource: str + :ivar operation: Name of the operation + :vartype operation: str + :ivar description: Friendly description for the operation, + :vartype description: str + """ + + _validation = { + 'provider': {'readonly': True}, + 'resource': {'readonly': True}, + 'operation': {'readonly': True}, + 'description': {'readonly': True}, + } + + _attribute_map = { + 'provider': {'key': 'provider', 'type': 'str'}, + 'resource': {'key': 'resource', 'type': 'str'}, + 'operation': {'key': 'operation', 'type': 'str'}, + 'description': {'key': 'description', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(OperationDisplay, self).__init__(**kwargs) + self.provider = None + self.resource = None + self.operation = None + self.description = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/operation_display_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/operation_display_py3.py new file mode 100644 index 000000000..82c6ceb80 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/operation_display_py3.py @@ -0,0 +1,50 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class OperationDisplay(Model): + """The object that represents the operation. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar provider: Service provider: Microsoft DigitalTwins + :vartype provider: str + :ivar resource: Resource Type: DigitalTwinsInstances + :vartype resource: str + :ivar operation: Name of the operation + :vartype operation: str + :ivar description: Friendly description for the operation, + :vartype description: str + """ + + _validation = { + 'provider': {'readonly': True}, + 'resource': {'readonly': True}, + 'operation': {'readonly': True}, + 'description': {'readonly': True}, + } + + _attribute_map = { + 'provider': {'key': 'provider', 'type': 'str'}, + 'resource': {'key': 'resource', 'type': 'str'}, + 'operation': {'key': 'operation', 'type': 'str'}, + 'description': {'key': 'description', 'type': 'str'}, + } + + def __init__(self, **kwargs) -> None: + super(OperationDisplay, self).__init__(**kwargs) + self.provider = None + self.resource = None + self.operation = None + self.description = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/operation_paged.py b/azext_iot/sdk/digitaltwins/controlplane/models/operation_paged.py new file mode 100644 index 000000000..ff83d7bef --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/operation_paged.py @@ -0,0 +1,27 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.paging import Paged + + +class OperationPaged(Paged): + """ + A paging container for iterating over a list of :class:`Operation ` object + """ + + _attribute_map = { + 'next_link': {'key': 'nextLink', 'type': 'str'}, + 'current_page': {'key': 'value', 'type': '[Operation]'} + } + + def __init__(self, *args, **kwargs): + + super(OperationPaged, self).__init__(*args, **kwargs) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/operation_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/operation_py3.py new file mode 100644 index 000000000..c60799393 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/operation_py3.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class Operation(Model): + """DigitalTwins service REST API operation. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar name: Operation name: {provider}/{resource}/{read | write | action | + delete} + :vartype name: str + :param display: Operation properties display + :type display: ~controlplane.models.OperationDisplay + :ivar origin: The intended executor of the operation. + :vartype origin: str + :ivar is_data_action: If the operation is a data action (for data plane + rbac). + :vartype is_data_action: bool + """ + + _validation = { + 'name': {'readonly': True}, + 'origin': {'readonly': True}, + 'is_data_action': {'readonly': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'display': {'key': 'display', 'type': 'OperationDisplay'}, + 'origin': {'key': 'origin', 'type': 'str'}, + 'is_data_action': {'key': 'isDataAction', 'type': 'bool'}, + } + + def __init__(self, *, display=None, **kwargs) -> None: + super(Operation, self).__init__(**kwargs) + self.name = None + self.display = display + self.origin = None + self.is_data_action = None diff --git a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch.py b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint.py similarity index 55% rename from azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch.py rename to azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint.py index 266a62caf..4cebb4acc 100644 --- a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch.py +++ b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint.py @@ -12,18 +12,24 @@ from msrest.serialization import Model -class DigitalTwinInterfacesPatch(Model): - """DigitalTwinInterfacesPatch. +class PrivateEndpoint(Model): + """The private endpoint property of a private endpoint connection. - :param interfaces: Interface(s) data to patch in the digital twin. - :type interfaces: dict[str, - ~service.models.DigitalTwinInterfacesPatchInterfacesValue] + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar id: The resource identifier. + :vartype id: str """ + _validation = { + 'id': {'readonly': True}, + } + _attribute_map = { - 'interfaces': {'key': 'interfaces', 'type': '{DigitalTwinInterfacesPatchInterfacesValue}'}, + 'id': {'key': 'id', 'type': 'str'}, } def __init__(self, **kwargs): - super(DigitalTwinInterfacesPatch, self).__init__(**kwargs) - self.interfaces = kwargs.get('interfaces', None) + super(PrivateEndpoint, self).__init__(**kwargs) + self.id = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection.py b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection.py new file mode 100644 index 000000000..085f56fbc --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PrivateEndpointConnection(Model): + """The private endpoint connection of a Digital Twin. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar id: The resource identifier. + :vartype id: str + :ivar name: The resource name. + :vartype name: str + :ivar type: The resource type. + :vartype type: str + :param properties: Required. + :type properties: ~controlplane.models.PrivateEndpointConnectionProperties + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True, 'pattern': r'^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{2,49}[a-zA-Z0-9]$'}, + 'type': {'readonly': True}, + 'properties': {'required': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + 'properties': {'key': 'properties', 'type': 'PrivateEndpointConnectionProperties'}, + } + + def __init__(self, **kwargs): + super(PrivateEndpointConnection, self).__init__(**kwargs) + self.id = None + self.name = None + self.type = None + self.properties = kwargs.get('properties', None) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection_properties.py b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection_properties.py new file mode 100644 index 000000000..9ef003a5a --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection_properties.py @@ -0,0 +1,48 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .connection_properties import ConnectionProperties + + +class PrivateEndpointConnectionProperties(ConnectionProperties): + """PrivateEndpointConnectionProperties. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Pending', 'Approved', 'Rejected', 'Disconnected' + :vartype provisioning_state: str or + ~controlplane.models.ConnectionPropertiesProvisioningState + :param private_endpoint: + :type private_endpoint: + ~controlplane.models.ConnectionPropertiesPrivateEndpoint + :param group_ids: The list of group ids for the private endpoint + connection. + :type group_ids: list[str] + :param private_link_service_connection_state: + :type private_link_service_connection_state: + ~controlplane.models.ConnectionPropertiesPrivateLinkServiceConnectionState + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'private_endpoint': {'key': 'privateEndpoint', 'type': 'ConnectionPropertiesPrivateEndpoint'}, + 'group_ids': {'key': 'groupIds', 'type': '[str]'}, + 'private_link_service_connection_state': {'key': 'privateLinkServiceConnectionState', 'type': 'ConnectionPropertiesPrivateLinkServiceConnectionState'}, + } + + def __init__(self, **kwargs): + super(PrivateEndpointConnectionProperties, self).__init__(**kwargs) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection_properties_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection_properties_py3.py new file mode 100644 index 000000000..b1b829ca4 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection_properties_py3.py @@ -0,0 +1,48 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .connection_properties_py3 import ConnectionProperties + + +class PrivateEndpointConnectionProperties(ConnectionProperties): + """PrivateEndpointConnectionProperties. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Pending', 'Approved', 'Rejected', 'Disconnected' + :vartype provisioning_state: str or + ~controlplane.models.ConnectionPropertiesProvisioningState + :param private_endpoint: + :type private_endpoint: + ~controlplane.models.ConnectionPropertiesPrivateEndpoint + :param group_ids: The list of group ids for the private endpoint + connection. + :type group_ids: list[str] + :param private_link_service_connection_state: + :type private_link_service_connection_state: + ~controlplane.models.ConnectionPropertiesPrivateLinkServiceConnectionState + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'private_endpoint': {'key': 'privateEndpoint', 'type': 'ConnectionPropertiesPrivateEndpoint'}, + 'group_ids': {'key': 'groupIds', 'type': '[str]'}, + 'private_link_service_connection_state': {'key': 'privateLinkServiceConnectionState', 'type': 'ConnectionPropertiesPrivateLinkServiceConnectionState'}, + } + + def __init__(self, *, private_endpoint=None, group_ids=None, private_link_service_connection_state=None, **kwargs) -> None: + super(PrivateEndpointConnectionProperties, self).__init__(private_endpoint=private_endpoint, group_ids=group_ids, private_link_service_connection_state=private_link_service_connection_state, **kwargs) diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection_py3.py new file mode 100644 index 000000000..936c84d9d --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connection_py3.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PrivateEndpointConnection(Model): + """The private endpoint connection of a Digital Twin. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar id: The resource identifier. + :vartype id: str + :ivar name: The resource name. + :vartype name: str + :ivar type: The resource type. + :vartype type: str + :param properties: Required. + :type properties: ~controlplane.models.PrivateEndpointConnectionProperties + """ + + _validation = { + 'id': {'readonly': True}, + 'name': {'readonly': True, 'pattern': r'^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{2,49}[a-zA-Z0-9]$'}, + 'type': {'readonly': True}, + 'properties': {'required': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + 'properties': {'key': 'properties', 'type': 'PrivateEndpointConnectionProperties'}, + } + + def __init__(self, *, properties, **kwargs) -> None: + super(PrivateEndpointConnection, self).__init__(**kwargs) + self.id = None + self.name = None + self.type = None + self.properties = properties diff --git a/azext_iot/sdk/iothub/service/models/reported.py b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connections_response.py similarity index 58% rename from azext_iot/sdk/iothub/service/models/reported.py rename to azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connections_response.py index 702c81416..cb53dc315 100644 --- a/azext_iot/sdk/iothub/service/models/reported.py +++ b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connections_response.py @@ -12,21 +12,18 @@ from msrest.serialization import Model -class Reported(Model): - """Reported. +class PrivateEndpointConnectionsResponse(Model): + """The available private link connections for a Digital Twin. - :param value: The current interface property value in a digitalTwin. - :type value: object - :param desired_state: - :type desired_state: ~service.models.DesiredState + :param value: The list of available private link connections for a Digital + Twin. + :type value: list[~controlplane.models.PrivateEndpointConnection] """ _attribute_map = { - 'value': {'key': 'value', 'type': 'object'}, - 'desired_state': {'key': 'desiredState', 'type': 'DesiredState'}, + 'value': {'key': 'value', 'type': '[PrivateEndpointConnection]'}, } def __init__(self, **kwargs): - super(Reported, self).__init__(**kwargs) + super(PrivateEndpointConnectionsResponse, self).__init__(**kwargs) self.value = kwargs.get('value', None) - self.desired_state = kwargs.get('desired_state', None) diff --git a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_desired_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connections_response_py3.py similarity index 60% rename from azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_desired_py3.py rename to azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connections_response_py3.py index 2bf2325b3..51197678c 100644 --- a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_desired_py3.py +++ b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_connections_response_py3.py @@ -12,18 +12,18 @@ from msrest.serialization import Model -class DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired(Model): - """DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired. +class PrivateEndpointConnectionsResponse(Model): + """The available private link connections for a Digital Twin. - :param value: The desired value of the interface property to set in a - digitalTwin. - :type value: object + :param value: The list of available private link connections for a Digital + Twin. + :type value: list[~controlplane.models.PrivateEndpointConnection] """ _attribute_map = { - 'value': {'key': 'value', 'type': 'object'}, + 'value': {'key': 'value', 'type': '[PrivateEndpointConnection]'}, } def __init__(self, *, value=None, **kwargs) -> None: - super(DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired, self).__init__(**kwargs) + super(PrivateEndpointConnectionsResponse, self).__init__(**kwargs) self.value = value diff --git a/azext_iot/sdk/iothub/service/models/property_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_py3.py similarity index 53% rename from azext_iot/sdk/iothub/service/models/property_py3.py rename to azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_py3.py index 52d2c0846..4faffd817 100644 --- a/azext_iot/sdk/iothub/service/models/property_py3.py +++ b/azext_iot/sdk/digitaltwins/controlplane/models/private_endpoint_py3.py @@ -12,21 +12,24 @@ from msrest.serialization import Model -class Property(Model): - """Property. +class PrivateEndpoint(Model): + """The private endpoint property of a private endpoint connection. - :param reported: - :type reported: ~service.models.Reported - :param desired: - :type desired: ~service.models.Desired + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar id: The resource identifier. + :vartype id: str """ + _validation = { + 'id': {'readonly': True}, + } + _attribute_map = { - 'reported': {'key': 'reported', 'type': 'Reported'}, - 'desired': {'key': 'desired', 'type': 'Desired'}, + 'id': {'key': 'id', 'type': 'str'}, } - def __init__(self, *, reported=None, desired=None, **kwargs) -> None: - super(Property, self).__init__(**kwargs) - self.reported = reported - self.desired = desired + def __init__(self, **kwargs) -> None: + super(PrivateEndpoint, self).__init__(**kwargs) + self.id = None diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/service_bus.py b/azext_iot/sdk/digitaltwins/controlplane/models/service_bus.py new file mode 100644 index 000000000..261bb07d5 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/service_bus.py @@ -0,0 +1,82 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .digital_twins_endpoint_resource_properties import DigitalTwinsEndpointResourceProperties + + +class ServiceBus(DigitalTwinsEndpointResourceProperties): + """Properties related to ServiceBus. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Provisioning', 'Deleting', 'Succeeded', 'Failed', 'Canceled', 'Deleted', + 'Warning', 'Suspending', 'Restoring', 'Moving', 'Disabled' + :vartype provisioning_state: str or + ~controlplane.models.EndpointProvisioningState + :ivar created_time: Time when the Endpoint was added to + DigitalTwinsInstance. + :vartype created_time: datetime + :param authentication_type: Specifies the authentication type being used + for connecting to the endpoint. Possible values include: 'KeyBased', + 'IdentityBased' + :type authentication_type: str or ~controlplane.models.AuthenticationType + :param dead_letter_secret: Dead letter storage secret for key-based + authentication. Will be obfuscated during read. + :type dead_letter_secret: str + :param dead_letter_uri: Dead letter storage URL for identity-based + authentication. + :type dead_letter_uri: str + :param endpoint_type: Required. Constant filled by server. + :type endpoint_type: str + :param primary_connection_string: PrimaryConnectionString of the endpoint + for key-based authentication. Will be obfuscated during read. + :type primary_connection_string: str + :param secondary_connection_string: SecondaryConnectionString of the + endpoint for key-based authentication. Will be obfuscated during read. + :type secondary_connection_string: str + :param endpoint_uri: The URL of the ServiceBus namespace for + identity-based authentication. It must include the protocol sb:// + :type endpoint_uri: str + :param entity_path: The ServiceBus Topic name for identity-based + authentication + :type entity_path: str + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + 'created_time': {'readonly': True}, + 'endpoint_type': {'required': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'created_time': {'key': 'createdTime', 'type': 'iso-8601'}, + 'authentication_type': {'key': 'authenticationType', 'type': 'str'}, + 'dead_letter_secret': {'key': 'deadLetterSecret', 'type': 'str'}, + 'dead_letter_uri': {'key': 'deadLetterUri', 'type': 'str'}, + 'endpoint_type': {'key': 'endpointType', 'type': 'str'}, + 'primary_connection_string': {'key': 'primaryConnectionString', 'type': 'str'}, + 'secondary_connection_string': {'key': 'secondaryConnectionString', 'type': 'str'}, + 'endpoint_uri': {'key': 'endpointUri', 'type': 'str'}, + 'entity_path': {'key': 'entityPath', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(ServiceBus, self).__init__(**kwargs) + self.primary_connection_string = kwargs.get('primary_connection_string', None) + self.secondary_connection_string = kwargs.get('secondary_connection_string', None) + self.endpoint_uri = kwargs.get('endpoint_uri', None) + self.entity_path = kwargs.get('entity_path', None) + self.endpoint_type = 'ServiceBus' diff --git a/azext_iot/sdk/digitaltwins/controlplane/models/service_bus_py3.py b/azext_iot/sdk/digitaltwins/controlplane/models/service_bus_py3.py new file mode 100644 index 000000000..30782f1c0 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/models/service_bus_py3.py @@ -0,0 +1,82 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .digital_twins_endpoint_resource_properties_py3 import DigitalTwinsEndpointResourceProperties + + +class ServiceBus(DigitalTwinsEndpointResourceProperties): + """Properties related to ServiceBus. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar provisioning_state: The provisioning state. Possible values include: + 'Provisioning', 'Deleting', 'Succeeded', 'Failed', 'Canceled', 'Deleted', + 'Warning', 'Suspending', 'Restoring', 'Moving', 'Disabled' + :vartype provisioning_state: str or + ~controlplane.models.EndpointProvisioningState + :ivar created_time: Time when the Endpoint was added to + DigitalTwinsInstance. + :vartype created_time: datetime + :param authentication_type: Specifies the authentication type being used + for connecting to the endpoint. Possible values include: 'KeyBased', + 'IdentityBased' + :type authentication_type: str or ~controlplane.models.AuthenticationType + :param dead_letter_secret: Dead letter storage secret for key-based + authentication. Will be obfuscated during read. + :type dead_letter_secret: str + :param dead_letter_uri: Dead letter storage URL for identity-based + authentication. + :type dead_letter_uri: str + :param endpoint_type: Required. Constant filled by server. + :type endpoint_type: str + :param primary_connection_string: PrimaryConnectionString of the endpoint + for key-based authentication. Will be obfuscated during read. + :type primary_connection_string: str + :param secondary_connection_string: SecondaryConnectionString of the + endpoint for key-based authentication. Will be obfuscated during read. + :type secondary_connection_string: str + :param endpoint_uri: The URL of the ServiceBus namespace for + identity-based authentication. It must include the protocol sb:// + :type endpoint_uri: str + :param entity_path: The ServiceBus Topic name for identity-based + authentication + :type entity_path: str + """ + + _validation = { + 'provisioning_state': {'readonly': True}, + 'created_time': {'readonly': True}, + 'endpoint_type': {'required': True}, + } + + _attribute_map = { + 'provisioning_state': {'key': 'provisioningState', 'type': 'str'}, + 'created_time': {'key': 'createdTime', 'type': 'iso-8601'}, + 'authentication_type': {'key': 'authenticationType', 'type': 'str'}, + 'dead_letter_secret': {'key': 'deadLetterSecret', 'type': 'str'}, + 'dead_letter_uri': {'key': 'deadLetterUri', 'type': 'str'}, + 'endpoint_type': {'key': 'endpointType', 'type': 'str'}, + 'primary_connection_string': {'key': 'primaryConnectionString', 'type': 'str'}, + 'secondary_connection_string': {'key': 'secondaryConnectionString', 'type': 'str'}, + 'endpoint_uri': {'key': 'endpointUri', 'type': 'str'}, + 'entity_path': {'key': 'entityPath', 'type': 'str'}, + } + + def __init__(self, *, authentication_type=None, dead_letter_secret: str=None, dead_letter_uri: str=None, primary_connection_string: str=None, secondary_connection_string: str=None, endpoint_uri: str=None, entity_path: str=None, **kwargs) -> None: + super(ServiceBus, self).__init__(authentication_type=authentication_type, dead_letter_secret=dead_letter_secret, dead_letter_uri=dead_letter_uri, **kwargs) + self.primary_connection_string = primary_connection_string + self.secondary_connection_string = secondary_connection_string + self.endpoint_uri = endpoint_uri + self.entity_path = entity_path + self.endpoint_type = 'ServiceBus' diff --git a/azext_iot/sdk/digitaltwins/controlplane/operations/__init__.py b/azext_iot/sdk/digitaltwins/controlplane/operations/__init__.py new file mode 100644 index 000000000..00f62af04 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/operations/__init__.py @@ -0,0 +1,24 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .digital_twins_operations import DigitalTwinsOperations +from .digital_twins_endpoint_operations import DigitalTwinsEndpointOperations +from .operations import Operations +from .private_link_resources_operations import PrivateLinkResourcesOperations +from .private_endpoint_connections_operations import PrivateEndpointConnectionsOperations + +__all__ = [ + 'DigitalTwinsOperations', + 'DigitalTwinsEndpointOperations', + 'Operations', + 'PrivateLinkResourcesOperations', + 'PrivateEndpointConnectionsOperations', +] diff --git a/azext_iot/sdk/digitaltwins/controlplane/operations/digital_twins_endpoint_operations.py b/azext_iot/sdk/digitaltwins/controlplane/operations/digital_twins_endpoint_operations.py new file mode 100644 index 000000000..e6bbb2bc2 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/operations/digital_twins_endpoint_operations.py @@ -0,0 +1,384 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse +from msrest.polling import LROPoller, NoPolling +from msrestazure.polling.arm_polling import ARMPolling + +from .. import models + + +class DigitalTwinsEndpointOperations(object): + """DigitalTwinsEndpointOperations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: Version of the DigitalTwinsInstance Management API. Constant value: "2020-12-01". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-12-01" + + self.config = config + + def list( + self, resource_group_name, resource_name, custom_headers=None, raw=False, **operation_config): + """Get DigitalTwinsInstance Endpoints. + + :param resource_group_name: The name of the resource group that + contains the DigitalTwinsInstance. + :type resource_group_name: str + :param resource_name: The name of the DigitalTwinsInstance. + :type resource_name: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: An iterator like instance of DigitalTwinsEndpointResource + :rtype: + ~controlplane.models.DigitalTwinsEndpointResourcePaged[~controlplane.models.DigitalTwinsEndpointResource] + :raises: + :class:`ErrorResponseException` + """ + def internal_paging(next_link=None, raw=False): + + if not next_link: + # Construct URL + url = self.list.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str', max_length=63, min_length=3, pattern=r'^(?!-)[A-Za-z0-9-]{3,63}(?`. + :return: DigitalTwinsEndpointResource or ClientRawResponse if raw=true + :rtype: ~controlplane.models.DigitalTwinsEndpointResource or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + # Construct URL + url = self.get.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str', max_length=63, min_length=3, pattern=r'^(?!-)[A-Za-z0-9-]{3,63}(? if raw==True + :rtype: + ~msrestazure.azure_operation.AzureOperationPoller[~controlplane.models.DigitalTwinsEndpointResource] + or + ~msrestazure.azure_operation.AzureOperationPoller[~msrest.pipeline.ClientRawResponse[~controlplane.models.DigitalTwinsEndpointResource]] + :raises: + :class:`ErrorResponseException` + """ + raw_result = self._create_or_update_initial( + resource_group_name=resource_group_name, + resource_name=resource_name, + endpoint_name=endpoint_name, + properties=properties, + custom_headers=custom_headers, + raw=True, + **operation_config + ) + + def get_long_running_output(response): + deserialized = self._deserialize('DigitalTwinsEndpointResource', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + + lro_delay = operation_config.get( + 'long_running_operation_timeout', + self.config.long_running_operation_timeout) + if polling is True: polling_method = ARMPolling(lro_delay, **operation_config) + elif polling is False: polling_method = NoPolling() + else: polling_method = polling + return LROPoller(self._client, raw_result, get_long_running_output, polling_method) + create_or_update.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.DigitalTwins/digitalTwinsInstances/{resourceName}/endpoints/{endpointName}'} + + + def _delete_initial( + self, resource_group_name, resource_name, endpoint_name, custom_headers=None, raw=False, **operation_config): + # Construct URL + url = self.delete.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str', max_length=63, min_length=3, pattern=r'^(?!-)[A-Za-z0-9-]{3,63}(? if raw==True + :rtype: + ~msrestazure.azure_operation.AzureOperationPoller[~controlplane.models.DigitalTwinsEndpointResource] + or + ~msrestazure.azure_operation.AzureOperationPoller[~msrest.pipeline.ClientRawResponse[~controlplane.models.DigitalTwinsEndpointResource]] + :raises: + :class:`ErrorResponseException` + """ + raw_result = self._delete_initial( + resource_group_name=resource_group_name, + resource_name=resource_name, + endpoint_name=endpoint_name, + custom_headers=custom_headers, + raw=True, + **operation_config + ) + + def get_long_running_output(response): + deserialized = self._deserialize('DigitalTwinsEndpointResource', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + + lro_delay = operation_config.get( + 'long_running_operation_timeout', + self.config.long_running_operation_timeout) + if polling is True: polling_method = ARMPolling(lro_delay, **operation_config) + elif polling is False: polling_method = NoPolling() + else: polling_method = polling + return LROPoller(self._client, raw_result, get_long_running_output, polling_method) + delete.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.DigitalTwins/digitalTwinsInstances/{resourceName}/endpoints/{endpointName}'} diff --git a/azext_iot/sdk/digitaltwins/controlplane/operations/digital_twins_operations.py b/azext_iot/sdk/digitaltwins/controlplane/operations/digital_twins_operations.py new file mode 100644 index 000000000..3dbde2af7 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/operations/digital_twins_operations.py @@ -0,0 +1,603 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse +from msrest.polling import LROPoller, NoPolling +from msrestazure.polling.arm_polling import ARMPolling + +from .. import models + + +class DigitalTwinsOperations(object): + """DigitalTwinsOperations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: Version of the DigitalTwinsInstance Management API. Constant value: "2020-12-01". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-12-01" + + self.config = config + + def get( + self, resource_group_name, resource_name, custom_headers=None, raw=False, **operation_config): + """Get DigitalTwinsInstances resource. + + :param resource_group_name: The name of the resource group that + contains the DigitalTwinsInstance. + :type resource_group_name: str + :param resource_name: The name of the DigitalTwinsInstance. + :type resource_name: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: DigitalTwinsDescription or ClientRawResponse if raw=true + :rtype: ~controlplane.models.DigitalTwinsDescription or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + # Construct URL + url = self.get.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str', max_length=63, min_length=3, pattern=r'^(?!-)[A-Za-z0-9-]{3,63}(? if raw==True + :rtype: + ~msrestazure.azure_operation.AzureOperationPoller[~controlplane.models.DigitalTwinsDescription] + or + ~msrestazure.azure_operation.AzureOperationPoller[~msrest.pipeline.ClientRawResponse[~controlplane.models.DigitalTwinsDescription]] + :raises: + :class:`ErrorResponseException` + """ + raw_result = self._create_or_update_initial( + resource_group_name=resource_group_name, + resource_name=resource_name, + digital_twins_create=digital_twins_create, + custom_headers=custom_headers, + raw=True, + **operation_config + ) + + def get_long_running_output(response): + deserialized = self._deserialize('DigitalTwinsDescription', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + + lro_delay = operation_config.get( + 'long_running_operation_timeout', + self.config.long_running_operation_timeout) + if polling is True: polling_method = ARMPolling(lro_delay, **operation_config) + elif polling is False: polling_method = NoPolling() + else: polling_method = polling + return LROPoller(self._client, raw_result, get_long_running_output, polling_method) + create_or_update.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.DigitalTwins/digitalTwinsInstances/{resourceName}'} + + + def _update_initial( + self, resource_group_name, resource_name, digital_twins_patch_description, custom_headers=None, raw=False, **operation_config): + # Construct URL + url = self.update.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str', max_length=63, min_length=3, pattern=r'^(?!-)[A-Za-z0-9-]{3,63}(? if raw==True + :rtype: + ~msrestazure.azure_operation.AzureOperationPoller[~controlplane.models.DigitalTwinsDescription] + or + ~msrestazure.azure_operation.AzureOperationPoller[~msrest.pipeline.ClientRawResponse[~controlplane.models.DigitalTwinsDescription]] + :raises: + :class:`ErrorResponseException` + """ + raw_result = self._update_initial( + resource_group_name=resource_group_name, + resource_name=resource_name, + digital_twins_patch_description=digital_twins_patch_description, + custom_headers=custom_headers, + raw=True, + **operation_config + ) + + def get_long_running_output(response): + deserialized = self._deserialize('DigitalTwinsDescription', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + + lro_delay = operation_config.get( + 'long_running_operation_timeout', + self.config.long_running_operation_timeout) + if polling is True: polling_method = ARMPolling(lro_delay, **operation_config) + elif polling is False: polling_method = NoPolling() + else: polling_method = polling + return LROPoller(self._client, raw_result, get_long_running_output, polling_method) + update.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.DigitalTwins/digitalTwinsInstances/{resourceName}'} + + + def _delete_initial( + self, resource_group_name, resource_name, custom_headers=None, raw=False, **operation_config): + # Construct URL + url = self.delete.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str', max_length=63, min_length=3, pattern=r'^(?!-)[A-Za-z0-9-]{3,63}(? if raw==True + :rtype: + ~msrestazure.azure_operation.AzureOperationPoller[~controlplane.models.DigitalTwinsDescription] + or + ~msrestazure.azure_operation.AzureOperationPoller[~msrest.pipeline.ClientRawResponse[~controlplane.models.DigitalTwinsDescription]] + :raises: + :class:`ErrorResponseException` + """ + raw_result = self._delete_initial( + resource_group_name=resource_group_name, + resource_name=resource_name, + custom_headers=custom_headers, + raw=True, + **operation_config + ) + + def get_long_running_output(response): + deserialized = self._deserialize('DigitalTwinsDescription', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + + lro_delay = operation_config.get( + 'long_running_operation_timeout', + self.config.long_running_operation_timeout) + if polling is True: polling_method = ARMPolling(lro_delay, **operation_config) + elif polling is False: polling_method = NoPolling() + else: polling_method = polling + return LROPoller(self._client, raw_result, get_long_running_output, polling_method) + delete.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.DigitalTwins/digitalTwinsInstances/{resourceName}'} + + def list( + self, custom_headers=None, raw=False, **operation_config): + """Get all the DigitalTwinsInstances in a subscription. + + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: An iterator like instance of DigitalTwinsDescription + :rtype: + ~controlplane.models.DigitalTwinsDescriptionPaged[~controlplane.models.DigitalTwinsDescription] + :raises: + :class:`ErrorResponseException` + """ + def internal_paging(next_link=None, raw=False): + + if not next_link: + # Construct URL + url = self.list.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str', min_length=10) + + else: + url = next_link + query_parameters = {} + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + return response + + # Deserialize response + deserialized = models.DigitalTwinsDescriptionPaged(internal_paging, self._deserialize.dependencies) + + if raw: + header_dict = {} + client_raw_response = models.DigitalTwinsDescriptionPaged(internal_paging, self._deserialize.dependencies, header_dict) + return client_raw_response + + return deserialized + list.metadata = {'url': '/subscriptions/{subscriptionId}/providers/Microsoft.DigitalTwins/digitalTwinsInstances'} + + def list_by_resource_group( + self, resource_group_name, custom_headers=None, raw=False, **operation_config): + """Get all the DigitalTwinsInstances in a resource group. + + :param resource_group_name: The name of the resource group that + contains the DigitalTwinsInstance. + :type resource_group_name: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: An iterator like instance of DigitalTwinsDescription + :rtype: + ~controlplane.models.DigitalTwinsDescriptionPaged[~controlplane.models.DigitalTwinsDescription] + :raises: + :class:`ErrorResponseException` + """ + def internal_paging(next_link=None, raw=False): + + if not next_link: + # Construct URL + url = self.list_by_resource_group.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1) + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str', min_length=10) + + else: + url = next_link + query_parameters = {} + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + return response + + # Deserialize response + deserialized = models.DigitalTwinsDescriptionPaged(internal_paging, self._deserialize.dependencies) + + if raw: + header_dict = {} + client_raw_response = models.DigitalTwinsDescriptionPaged(internal_paging, self._deserialize.dependencies, header_dict) + return client_raw_response + + return deserialized + list_by_resource_group.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.DigitalTwins/digitalTwinsInstances'} + + def check_name_availability( + self, location, name, custom_headers=None, raw=False, **operation_config): + """Check if a DigitalTwinsInstance name is available. + + :param location: Location of DigitalTwinsInstance. + :type location: str + :param name: Resource name. + :type name: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: CheckNameResult or ClientRawResponse if raw=true + :rtype: ~controlplane.models.CheckNameResult or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + digital_twins_instance_check_name = models.CheckNameRequest(name=name) + + # Construct URL + url = self.check_name_availability.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'location': self._serialize.url("location", location, 'str', min_length=3) + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str', min_length=10) + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + body_content = self._serialize.body(digital_twins_instance_check_name, 'CheckNameRequest') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('CheckNameResult', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + check_name_availability.metadata = {'url': '/subscriptions/{subscriptionId}/providers/Microsoft.DigitalTwins/locations/{location}/checkNameAvailability'} diff --git a/azext_iot/sdk/digitaltwins/controlplane/operations/operations.py b/azext_iot/sdk/digitaltwins/controlplane/operations/operations.py new file mode 100644 index 000000000..2db70e68a --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/operations/operations.py @@ -0,0 +1,96 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse + +from .. import models + + +class Operations(object): + """Operations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: Version of the DigitalTwinsInstance Management API. Constant value: "2020-12-01". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-12-01" + + self.config = config + + def list( + self, custom_headers=None, raw=False, **operation_config): + """Lists all of the available DigitalTwins service REST API operations. + + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: An iterator like instance of Operation + :rtype: + ~controlplane.models.OperationPaged[~controlplane.models.Operation] + :raises: + :class:`ErrorResponseException` + """ + def internal_paging(next_link=None, raw=False): + + if not next_link: + # Construct URL + url = self.list.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str', min_length=10) + + else: + url = next_link + query_parameters = {} + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + return response + + # Deserialize response + deserialized = models.OperationPaged(internal_paging, self._deserialize.dependencies) + + if raw: + header_dict = {} + client_raw_response = models.OperationPaged(internal_paging, self._deserialize.dependencies, header_dict) + return client_raw_response + + return deserialized + list.metadata = {'url': '/providers/Microsoft.DigitalTwins/operations'} diff --git a/azext_iot/sdk/digitaltwins/controlplane/operations/private_endpoint_connections_operations.py b/azext_iot/sdk/digitaltwins/controlplane/operations/private_endpoint_connections_operations.py new file mode 100644 index 000000000..5b74d372b --- /dev/null +++ b/azext_iot/sdk/digitaltwins/controlplane/operations/private_endpoint_connections_operations.py @@ -0,0 +1,364 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse +from msrest.polling import LROPoller, NoPolling +from msrestazure.polling.arm_polling import ARMPolling + +from .. import models + + +class PrivateEndpointConnectionsOperations(object): + """PrivateEndpointConnectionsOperations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: Version of the DigitalTwinsInstance Management API. Constant value: "2020-12-01". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-12-01" + + self.config = config + + def list( + self, resource_group_name, resource_name, custom_headers=None, raw=False, **operation_config): + """List private endpoint connection properties. + + :param resource_group_name: The name of the resource group that + contains the DigitalTwinsInstance. + :type resource_group_name: str + :param resource_name: The name of the DigitalTwinsInstance. + :type resource_name: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: PrivateEndpointConnectionsResponse or ClientRawResponse if + raw=true + :rtype: ~controlplane.models.PrivateEndpointConnectionsResponse or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + # Construct URL + url = self.list.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str', max_length=63, min_length=3, pattern=r'^(?!-)[A-Za-z0-9-]{3,63}(?`. + :return: PrivateEndpointConnection or ClientRawResponse if raw=true + :rtype: ~controlplane.models.PrivateEndpointConnection or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + # Construct URL + url = self.get.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str', max_length=63, min_length=3, pattern=r'^(?!-)[A-Za-z0-9-]{3,63}(? if raw==True + :rtype: ~msrestazure.azure_operation.AzureOperationPoller[None] or + ~msrestazure.azure_operation.AzureOperationPoller[~msrest.pipeline.ClientRawResponse[None]] + :raises: + :class:`ErrorResponseException` + """ + raw_result = self._delete_initial( + resource_group_name=resource_group_name, + resource_name=resource_name, + private_endpoint_connection_name=private_endpoint_connection_name, + custom_headers=custom_headers, + raw=True, + **operation_config + ) + + def get_long_running_output(response): + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + + lro_delay = operation_config.get( + 'long_running_operation_timeout', + self.config.long_running_operation_timeout) + if polling is True: polling_method = ARMPolling(lro_delay, **operation_config) + elif polling is False: polling_method = NoPolling() + else: polling_method = polling + return LROPoller(self._client, raw_result, get_long_running_output, polling_method) + delete.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.DigitalTwins/digitalTwinsInstances/{resourceName}/privateEndpointConnections/{privateEndpointConnectionName}'} + + + def _create_or_update_initial( + self, resource_group_name, resource_name, private_endpoint_connection_name, properties, custom_headers=None, raw=False, **operation_config): + private_endpoint_connection = models.PrivateEndpointConnection(properties=properties) + + # Construct URL + url = self.create_or_update.metadata['url'] + path_format_arguments = { + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str', max_length=63, min_length=3, pattern=r'^(?!-)[A-Za-z0-9-]{3,63}(? if raw==True + :rtype: + ~msrestazure.azure_operation.AzureOperationPoller[~controlplane.models.PrivateEndpointConnection] + or + ~msrestazure.azure_operation.AzureOperationPoller[~msrest.pipeline.ClientRawResponse[~controlplane.models.PrivateEndpointConnection]] + :raises: + :class:`ErrorResponseException` + """ + raw_result = self._create_or_update_initial( + resource_group_name=resource_group_name, + resource_name=resource_name, + private_endpoint_connection_name=private_endpoint_connection_name, + properties=properties, + custom_headers=custom_headers, + raw=True, + **operation_config + ) + + def get_long_running_output(response): + deserialized = self._deserialize('PrivateEndpointConnection', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + + lro_delay = operation_config.get( + 'long_running_operation_timeout', + self.config.long_running_operation_timeout) + if polling is True: polling_method = ARMPolling(lro_delay, **operation_config) + elif polling is False: polling_method = NoPolling() + else: polling_method = polling + return LROPoller(self._client, raw_result, get_long_running_output, polling_method) + create_or_update.metadata = {'url': '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.DigitalTwins/digitalTwinsInstances/{resourceName}/privateEndpointConnections/{privateEndpointConnectionName}'} diff --git a/azext_iot/sdk/iothub/service/operations/device_method_operations.py b/azext_iot/sdk/digitaltwins/controlplane/operations/private_link_resources_operations.py similarity index 52% rename from azext_iot/sdk/iothub/service/operations/device_method_operations.py rename to azext_iot/sdk/digitaltwins/controlplane/operations/private_link_resources_operations.py index 74b5ff6a7..44e7d4c9e 100644 --- a/azext_iot/sdk/iothub/service/operations/device_method_operations.py +++ b/azext_iot/sdk/digitaltwins/controlplane/operations/private_link_resources_operations.py @@ -11,19 +11,18 @@ import uuid from msrest.pipeline import ClientRawResponse -from msrestazure.azure_exceptions import CloudError from .. import models -class DeviceMethodOperations(object): - """DeviceMethodOperations operations. +class PrivateLinkResourcesOperations(object): + """PrivateLinkResourcesOperations operations. :param client: Client for service requests. :param config: Configuration of service client. :param serializer: An object model serializer. :param deserializer: An object model deserializer. - :ivar api_version: Version of the Api. Constant value: "2019-10-01". + :ivar api_version: Version of the DigitalTwinsInstance Management API. Constant value: "2020-12-01". """ models = models @@ -33,47 +32,46 @@ def __init__(self, client, config, serializer, deserializer): self._client = client self._serialize = serializer self._deserialize = deserializer - self.api_version = "2019-10-01" + self.api_version = "2020-12-01" self.config = config - def invoke_device_method( - self, device_id, direct_method_request, custom_headers=None, raw=False, **operation_config): - """Invoke a direct method on a device. + def list( + self, resource_group_name, resource_name, custom_headers=None, raw=False, **operation_config): + """List private link resources for given Digital Twin. - Invoke a direct method on a device. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-direct-methods - for more information. - - :param device_id: - :type device_id: str - :param direct_method_request: - :type direct_method_request: ~service.models.CloudToDeviceMethod + :param resource_group_name: The name of the resource group that + contains the DigitalTwinsInstance. + :type resource_group_name: str + :param resource_name: The name of the DigitalTwinsInstance. + :type resource_name: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the deserialized response :param operation_config: :ref:`Operation configuration overrides`. - :return: CloudToDeviceMethodResult or ClientRawResponse if raw=true - :rtype: ~service.models.CloudToDeviceMethodResult or + :return: GroupIdInformationResponse or ClientRawResponse if raw=true + :rtype: ~controlplane.models.GroupIdInformationResponse or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` + :raises: + :class:`ErrorResponseException` """ # Construct URL - url = self.invoke_device_method.metadata['url'] + url = self.list.metadata['url'] path_format_arguments = { - 'deviceId': self._serialize.url("device_id", device_id, 'str') + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str', max_length=63, min_length=3, pattern=r'^(?!-)[A-Za-z0-9-]{3,63}(?`. - :return: CloudToDeviceMethodResult or ClientRawResponse if raw=true - :rtype: ~service.models.CloudToDeviceMethodResult or + :return: GroupIdInformation or ClientRawResponse if raw=true + :rtype: ~controlplane.models.GroupIdInformation or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` + :raises: + :class:`ErrorResponseException` """ # Construct URL - url = self.invoke_module_method.metadata['url'] + url = self.get.metadata['url'] path_format_arguments = { - 'deviceId': self._serialize.url("device_id", device_id, 'str'), - 'moduleId': self._serialize.url("module_id", module_id, 'str') + 'subscriptionId': self._serialize.url("self.config.subscription_id", self.config.subscription_id, 'str'), + 'resourceGroupName': self._serialize.url("resource_group_name", resource_group_name, 'str', max_length=90, min_length=1), + 'resourceName': self._serialize.url("resource_name", resource_name, 'str', max_length=63, min_length=3, pattern=r'^(?!-)[A-Za-z0-9-]{3,63}(?` + :param str base_url: Service URL + """ + + def __init__( + self, credentials, base_url=None): + + if credentials is None: + raise ValueError("Parameter 'credentials' must not be None.") + if not base_url: + base_url = 'http://localhost' + + super(AzureDigitalTwinsAPIConfiguration, self).__init__(base_url) + + self.add_user_agent('azuredigitaltwinsapi/{}'.format(VERSION)) + self.credentials = credentials + + +class AzureDigitalTwinsAPI(SDKClient): + """A service for managing and querying digital twins and digital twin models. + + :ivar config: Configuration for client. + :vartype config: AzureDigitalTwinsAPIConfiguration + + :ivar digital_twin_models: DigitalTwinModels operations + :vartype digital_twin_models: digitaltwins.operations.DigitalTwinModelsOperations + :ivar query: Query operations + :vartype query: digitaltwins.operations.QueryOperations + :ivar digital_twins: DigitalTwins operations + :vartype digital_twins: digitaltwins.operations.DigitalTwinsOperations + :ivar event_routes: EventRoutes operations + :vartype event_routes: digitaltwins.operations.EventRoutesOperations + + :param credentials: Credentials needed for the client to connect to Azure. + :type credentials: :mod:`A msrestazure Credentials + object` + :param str base_url: Service URL + """ + + def __init__( + self, credentials, base_url=None): + + self.config = AzureDigitalTwinsAPIConfiguration(credentials, base_url) + super(AzureDigitalTwinsAPI, self).__init__(self.config.credentials, self.config) + + client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} + self.api_version = '2020-10-31' + self._serialize = Serializer(client_models) + self._deserialize = Deserializer(client_models) + + self.digital_twin_models = DigitalTwinModelsOperations( + self._client, self.config, self._serialize, self._deserialize) + self.query = QueryOperations( + self._client, self.config, self._serialize, self._deserialize) + self.digital_twins = DigitalTwinsOperations( + self._client, self.config, self._serialize, self._deserialize) + self.event_routes = EventRoutesOperations( + self._client, self.config, self._serialize, self._deserialize) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/__init__.py b/azext_iot/sdk/digitaltwins/dataplane/models/__init__.py new file mode 100644 index 000000000..bf7a6fc63 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/__init__.py @@ -0,0 +1,120 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +try: + from .event_route_py3 import EventRoute + from .digital_twins_model_data_py3 import DigitalTwinsModelData + from .incoming_relationship_py3 import IncomingRelationship + from .query_specification_py3 import QuerySpecification + from .query_result_py3 import QueryResult + from .inner_error_py3 import InnerError + from .error_py3 import Error + from .error_response_py3 import ErrorResponse, ErrorResponseException + from .digital_twin_models_add_options_py3 import DigitalTwinModelsAddOptions + from .digital_twin_models_list_options_py3 import DigitalTwinModelsListOptions + from .digital_twin_models_get_by_id_options_py3 import DigitalTwinModelsGetByIdOptions + from .digital_twin_models_update_options_py3 import DigitalTwinModelsUpdateOptions + from .digital_twin_models_delete_options_py3 import DigitalTwinModelsDeleteOptions + from .query_query_twins_options_py3 import QueryQueryTwinsOptions + from .digital_twins_get_by_id_options_py3 import DigitalTwinsGetByIdOptions + from .digital_twins_add_options_py3 import DigitalTwinsAddOptions + from .digital_twins_delete_options_py3 import DigitalTwinsDeleteOptions + from .digital_twins_update_options_py3 import DigitalTwinsUpdateOptions + from .digital_twins_get_relationship_by_id_options_py3 import DigitalTwinsGetRelationshipByIdOptions + from .digital_twins_add_relationship_options_py3 import DigitalTwinsAddRelationshipOptions + from .digital_twins_delete_relationship_options_py3 import DigitalTwinsDeleteRelationshipOptions + from .digital_twins_update_relationship_options_py3 import DigitalTwinsUpdateRelationshipOptions + from .digital_twins_list_relationships_options_py3 import DigitalTwinsListRelationshipsOptions + from .digital_twins_list_incoming_relationships_options_py3 import DigitalTwinsListIncomingRelationshipsOptions + from .digital_twins_send_telemetry_options_py3 import DigitalTwinsSendTelemetryOptions + from .digital_twins_send_component_telemetry_options_py3 import DigitalTwinsSendComponentTelemetryOptions + from .digital_twins_get_component_options_py3 import DigitalTwinsGetComponentOptions + from .digital_twins_update_component_options_py3 import DigitalTwinsUpdateComponentOptions + from .event_routes_list_options_py3 import EventRoutesListOptions + from .event_routes_get_by_id_options_py3 import EventRoutesGetByIdOptions + from .event_routes_add_options_py3 import EventRoutesAddOptions + from .event_routes_delete_options_py3 import EventRoutesDeleteOptions +except (SyntaxError, ImportError): + from .event_route import EventRoute + from .digital_twins_model_data import DigitalTwinsModelData + from .incoming_relationship import IncomingRelationship + from .query_specification import QuerySpecification + from .query_result import QueryResult + from .inner_error import InnerError + from .error import Error + from .error_response import ErrorResponse, ErrorResponseException + from .digital_twin_models_add_options import DigitalTwinModelsAddOptions + from .digital_twin_models_list_options import DigitalTwinModelsListOptions + from .digital_twin_models_get_by_id_options import DigitalTwinModelsGetByIdOptions + from .digital_twin_models_update_options import DigitalTwinModelsUpdateOptions + from .digital_twin_models_delete_options import DigitalTwinModelsDeleteOptions + from .query_query_twins_options import QueryQueryTwinsOptions + from .digital_twins_get_by_id_options import DigitalTwinsGetByIdOptions + from .digital_twins_add_options import DigitalTwinsAddOptions + from .digital_twins_delete_options import DigitalTwinsDeleteOptions + from .digital_twins_update_options import DigitalTwinsUpdateOptions + from .digital_twins_get_relationship_by_id_options import DigitalTwinsGetRelationshipByIdOptions + from .digital_twins_add_relationship_options import DigitalTwinsAddRelationshipOptions + from .digital_twins_delete_relationship_options import DigitalTwinsDeleteRelationshipOptions + from .digital_twins_update_relationship_options import DigitalTwinsUpdateRelationshipOptions + from .digital_twins_list_relationships_options import DigitalTwinsListRelationshipsOptions + from .digital_twins_list_incoming_relationships_options import DigitalTwinsListIncomingRelationshipsOptions + from .digital_twins_send_telemetry_options import DigitalTwinsSendTelemetryOptions + from .digital_twins_send_component_telemetry_options import DigitalTwinsSendComponentTelemetryOptions + from .digital_twins_get_component_options import DigitalTwinsGetComponentOptions + from .digital_twins_update_component_options import DigitalTwinsUpdateComponentOptions + from .event_routes_list_options import EventRoutesListOptions + from .event_routes_get_by_id_options import EventRoutesGetByIdOptions + from .event_routes_add_options import EventRoutesAddOptions + from .event_routes_delete_options import EventRoutesDeleteOptions +from .digital_twins_model_data_paged import DigitalTwinsModelDataPaged +from .object_paged import ObjectPaged +from .incoming_relationship_paged import IncomingRelationshipPaged +from .event_route_paged import EventRoutePaged + +__all__ = [ + 'EventRoute', + 'DigitalTwinsModelData', + 'IncomingRelationship', + 'QuerySpecification', + 'QueryResult', + 'InnerError', + 'Error', + 'ErrorResponse', 'ErrorResponseException', + 'DigitalTwinModelsAddOptions', + 'DigitalTwinModelsListOptions', + 'DigitalTwinModelsGetByIdOptions', + 'DigitalTwinModelsUpdateOptions', + 'DigitalTwinModelsDeleteOptions', + 'QueryQueryTwinsOptions', + 'DigitalTwinsGetByIdOptions', + 'DigitalTwinsAddOptions', + 'DigitalTwinsDeleteOptions', + 'DigitalTwinsUpdateOptions', + 'DigitalTwinsGetRelationshipByIdOptions', + 'DigitalTwinsAddRelationshipOptions', + 'DigitalTwinsDeleteRelationshipOptions', + 'DigitalTwinsUpdateRelationshipOptions', + 'DigitalTwinsListRelationshipsOptions', + 'DigitalTwinsListIncomingRelationshipsOptions', + 'DigitalTwinsSendTelemetryOptions', + 'DigitalTwinsSendComponentTelemetryOptions', + 'DigitalTwinsGetComponentOptions', + 'DigitalTwinsUpdateComponentOptions', + 'EventRoutesListOptions', + 'EventRoutesGetByIdOptions', + 'EventRoutesAddOptions', + 'EventRoutesDeleteOptions', + 'DigitalTwinsModelDataPaged', + 'ObjectPaged', + 'IncomingRelationshipPaged', + 'EventRoutePaged', +] diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_add_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_add_options.py new file mode 100644 index 000000000..48badddbc --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_add_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinModelsAddOptions(Model): + """Additional parameters for add operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinModelsAddOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_add_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_add_options_py3.py new file mode 100644 index 000000000..400b238d3 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_add_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinModelsAddOptions(Model): + """Additional parameters for add operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(DigitalTwinModelsAddOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_delete_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_delete_options.py new file mode 100644 index 000000000..45ea0a951 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_delete_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinModelsDeleteOptions(Model): + """Additional parameters for delete operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinModelsDeleteOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_delete_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_delete_options_py3.py new file mode 100644 index 000000000..006373249 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_delete_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinModelsDeleteOptions(Model): + """Additional parameters for delete operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(DigitalTwinModelsDeleteOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_get_by_id_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_get_by_id_options.py new file mode 100644 index 000000000..8f7ec8e26 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_get_by_id_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinModelsGetByIdOptions(Model): + """Additional parameters for get_by_id operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinModelsGetByIdOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_get_by_id_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_get_by_id_options_py3.py new file mode 100644 index 000000000..c0c2412ee --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_get_by_id_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinModelsGetByIdOptions(Model): + """Additional parameters for get_by_id operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(DigitalTwinModelsGetByIdOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_list_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_list_options.py new file mode 100644 index 000000000..3fad49f0d --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_list_options.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinModelsListOptions(Model): + """Additional parameters for list operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param max_items_per_page: The maximum number of items to retrieve per + request. The server may choose to return less than the requested number. + :type max_items_per_page: int + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'max_items_per_page': {'key': '', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinModelsListOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) + self.max_items_per_page = kwargs.get('max_items_per_page', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_list_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_list_options_py3.py new file mode 100644 index 000000000..75bffa726 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_list_options_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinModelsListOptions(Model): + """Additional parameters for list operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param max_items_per_page: The maximum number of items to retrieve per + request. The server may choose to return less than the requested number. + :type max_items_per_page: int + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'max_items_per_page': {'key': '', 'type': 'int'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, max_items_per_page: int=None, **kwargs) -> None: + super(DigitalTwinModelsListOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate + self.max_items_per_page = max_items_per_page diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_update_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_update_options.py new file mode 100644 index 000000000..25015b17e --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_update_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinModelsUpdateOptions(Model): + """Additional parameters for update operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinModelsUpdateOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_update_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_update_options_py3.py new file mode 100644 index 000000000..a47f2d9ff --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twin_models_update_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinModelsUpdateOptions(Model): + """Additional parameters for update operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(DigitalTwinModelsUpdateOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_options.py new file mode 100644 index 000000000..3cdd70078 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_options.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsAddOptions(Model): + """Additional parameters for add operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_none_match: Only perform the operation if the entity does not + already exist. Possible values include: '*' + :type if_none_match: str or ~digitaltwins.models.enum + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_none_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsAddOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) + self.if_none_match = kwargs.get('if_none_match', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_options_py3.py new file mode 100644 index 000000000..288bd24e1 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_options_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsAddOptions(Model): + """Additional parameters for add operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_none_match: Only perform the operation if the entity does not + already exist. Possible values include: '*' + :type if_none_match: str or ~digitaltwins.models.enum + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_none_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, if_none_match=None, **kwargs) -> None: + super(DigitalTwinsAddOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate + self.if_none_match = if_none_match diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_relationship_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_relationship_options.py new file mode 100644 index 000000000..2be7fd3c4 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_relationship_options.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsAddRelationshipOptions(Model): + """Additional parameters for add_relationship operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_none_match: Only perform the operation if the entity does not + already exist. Possible values include: '*' + :type if_none_match: str or ~digitaltwins.models.enum + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_none_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsAddRelationshipOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) + self.if_none_match = kwargs.get('if_none_match', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_relationship_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_relationship_options_py3.py new file mode 100644 index 000000000..b5ade7953 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_add_relationship_options_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsAddRelationshipOptions(Model): + """Additional parameters for add_relationship operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_none_match: Only perform the operation if the entity does not + already exist. Possible values include: '*' + :type if_none_match: str or ~digitaltwins.models.enum + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_none_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, if_none_match=None, **kwargs) -> None: + super(DigitalTwinsAddRelationshipOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate + self.if_none_match = if_none_match diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_options.py new file mode 100644 index 000000000..9286f6091 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_options.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsDeleteOptions(Model): + """Additional parameters for delete operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_match: Only perform the operation if the entity's etag matches + one of the etags provided or * is provided. + :type if_match: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsDeleteOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) + self.if_match = kwargs.get('if_match', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_options_py3.py new file mode 100644 index 000000000..70548af15 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_options_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsDeleteOptions(Model): + """Additional parameters for delete operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_match: Only perform the operation if the entity's etag matches + one of the etags provided or * is provided. + :type if_match: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, if_match: str=None, **kwargs) -> None: + super(DigitalTwinsDeleteOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate + self.if_match = if_match diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_relationship_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_relationship_options.py new file mode 100644 index 000000000..049be2bf3 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_relationship_options.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsDeleteRelationshipOptions(Model): + """Additional parameters for delete_relationship operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_match: Only perform the operation if the entity's etag matches + one of the etags provided or * is provided. + :type if_match: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsDeleteRelationshipOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) + self.if_match = kwargs.get('if_match', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_relationship_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_relationship_options_py3.py new file mode 100644 index 000000000..bb9aa5e53 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_delete_relationship_options_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsDeleteRelationshipOptions(Model): + """Additional parameters for delete_relationship operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_match: Only perform the operation if the entity's etag matches + one of the etags provided or * is provided. + :type if_match: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, if_match: str=None, **kwargs) -> None: + super(DigitalTwinsDeleteRelationshipOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate + self.if_match = if_match diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_by_id_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_by_id_options.py new file mode 100644 index 000000000..7a769771b --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_by_id_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsGetByIdOptions(Model): + """Additional parameters for get_by_id operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsGetByIdOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_by_id_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_by_id_options_py3.py new file mode 100644 index 000000000..43042e223 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_by_id_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsGetByIdOptions(Model): + """Additional parameters for get_by_id operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(DigitalTwinsGetByIdOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_component_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_component_options.py new file mode 100644 index 000000000..a2228fe74 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_component_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsGetComponentOptions(Model): + """Additional parameters for get_component operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsGetComponentOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_component_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_component_options_py3.py new file mode 100644 index 000000000..a12a5074c --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_component_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsGetComponentOptions(Model): + """Additional parameters for get_component operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(DigitalTwinsGetComponentOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_relationship_by_id_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_relationship_by_id_options.py new file mode 100644 index 000000000..6e670a7da --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_relationship_by_id_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsGetRelationshipByIdOptions(Model): + """Additional parameters for get_relationship_by_id operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsGetRelationshipByIdOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_relationship_by_id_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_relationship_by_id_options_py3.py new file mode 100644 index 000000000..6607d075e --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_get_relationship_by_id_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsGetRelationshipByIdOptions(Model): + """Additional parameters for get_relationship_by_id operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(DigitalTwinsGetRelationshipByIdOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_incoming_relationships_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_incoming_relationships_options.py new file mode 100644 index 000000000..8a64942cd --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_incoming_relationships_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsListIncomingRelationshipsOptions(Model): + """Additional parameters for list_incoming_relationships operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsListIncomingRelationshipsOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_incoming_relationships_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_incoming_relationships_options_py3.py new file mode 100644 index 000000000..726b262f9 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_incoming_relationships_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsListIncomingRelationshipsOptions(Model): + """Additional parameters for list_incoming_relationships operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(DigitalTwinsListIncomingRelationshipsOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_relationships_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_relationships_options.py new file mode 100644 index 000000000..6e3847224 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_relationships_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsListRelationshipsOptions(Model): + """Additional parameters for list_relationships operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsListRelationshipsOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_relationships_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_relationships_options_py3.py new file mode 100644 index 000000000..2247449d3 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_list_relationships_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsListRelationshipsOptions(Model): + """Additional parameters for list_relationships operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(DigitalTwinsListRelationshipsOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_model_data.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_model_data.py new file mode 100644 index 000000000..ecfd9c8b5 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_model_data.py @@ -0,0 +1,59 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsModelData(Model): + """A model definition and metadata for that model. + + All required parameters must be populated in order to send to Azure. + + :param display_name: A language map that contains the localized display + names as specified in the model definition. + :type display_name: dict[str, str] + :param description: A language map that contains the localized + descriptions as specified in the model definition. + :type description: dict[str, str] + :param id: Required. The id of the model as specified in the model + definition. + :type id: str + :param upload_time: The time the model was uploaded to the service. + :type upload_time: datetime + :param decommissioned: Indicates if the model is decommissioned. + Decommissioned models cannot be referenced by newly created digital twins. + Default value: False . + :type decommissioned: bool + :param model: The model definition. + :type model: object + """ + + _validation = { + 'id': {'required': True}, + } + + _attribute_map = { + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'upload_time': {'key': 'uploadTime', 'type': 'iso-8601'}, + 'decommissioned': {'key': 'decommissioned', 'type': 'bool'}, + 'model': {'key': 'model', 'type': 'object'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsModelData, self).__init__(**kwargs) + self.display_name = kwargs.get('display_name', None) + self.description = kwargs.get('description', None) + self.id = kwargs.get('id', None) + self.upload_time = kwargs.get('upload_time', None) + self.decommissioned = kwargs.get('decommissioned', False) + self.model = kwargs.get('model', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_model_data_paged.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_model_data_paged.py new file mode 100644 index 000000000..661260b1f --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_model_data_paged.py @@ -0,0 +1,27 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.paging import Paged + + +class DigitalTwinsModelDataPaged(Paged): + """ + A paging container for iterating over a list of :class:`DigitalTwinsModelData ` object + """ + + _attribute_map = { + 'next_link': {'key': 'nextLink', 'type': 'str'}, + 'current_page': {'key': 'value', 'type': '[DigitalTwinsModelData]'} + } + + def __init__(self, *args, **kwargs): + + super(DigitalTwinsModelDataPaged, self).__init__(*args, **kwargs) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_model_data_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_model_data_py3.py new file mode 100644 index 000000000..70505731d --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_model_data_py3.py @@ -0,0 +1,59 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsModelData(Model): + """A model definition and metadata for that model. + + All required parameters must be populated in order to send to Azure. + + :param display_name: A language map that contains the localized display + names as specified in the model definition. + :type display_name: dict[str, str] + :param description: A language map that contains the localized + descriptions as specified in the model definition. + :type description: dict[str, str] + :param id: Required. The id of the model as specified in the model + definition. + :type id: str + :param upload_time: The time the model was uploaded to the service. + :type upload_time: datetime + :param decommissioned: Indicates if the model is decommissioned. + Decommissioned models cannot be referenced by newly created digital twins. + Default value: False . + :type decommissioned: bool + :param model: The model definition. + :type model: object + """ + + _validation = { + 'id': {'required': True}, + } + + _attribute_map = { + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'upload_time': {'key': 'uploadTime', 'type': 'iso-8601'}, + 'decommissioned': {'key': 'decommissioned', 'type': 'bool'}, + 'model': {'key': 'model', 'type': 'object'}, + } + + def __init__(self, *, id: str, display_name=None, description=None, upload_time=None, decommissioned: bool=False, model=None, **kwargs) -> None: + super(DigitalTwinsModelData, self).__init__(**kwargs) + self.display_name = display_name + self.description = description + self.id = id + self.upload_time = upload_time + self.decommissioned = decommissioned + self.model = model diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_component_telemetry_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_component_telemetry_options.py new file mode 100644 index 000000000..22b9d2e74 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_component_telemetry_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsSendComponentTelemetryOptions(Model): + """Additional parameters for send_component_telemetry operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsSendComponentTelemetryOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_component_telemetry_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_component_telemetry_options_py3.py new file mode 100644 index 000000000..278917a31 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_component_telemetry_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsSendComponentTelemetryOptions(Model): + """Additional parameters for send_component_telemetry operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(DigitalTwinsSendComponentTelemetryOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_telemetry_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_telemetry_options.py new file mode 100644 index 000000000..30a51aa17 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_telemetry_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsSendTelemetryOptions(Model): + """Additional parameters for send_telemetry operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsSendTelemetryOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_telemetry_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_telemetry_options_py3.py new file mode 100644 index 000000000..895781371 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_send_telemetry_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsSendTelemetryOptions(Model): + """Additional parameters for send_telemetry operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(DigitalTwinsSendTelemetryOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_component_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_component_options.py new file mode 100644 index 000000000..f2395dbf0 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_component_options.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsUpdateComponentOptions(Model): + """Additional parameters for update_component operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_match: Only perform the operation if the entity's etag matches + one of the etags provided or * is provided. + :type if_match: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsUpdateComponentOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) + self.if_match = kwargs.get('if_match', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_component_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_component_options_py3.py new file mode 100644 index 000000000..334416799 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_component_options_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsUpdateComponentOptions(Model): + """Additional parameters for update_component operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_match: Only perform the operation if the entity's etag matches + one of the etags provided or * is provided. + :type if_match: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, if_match: str=None, **kwargs) -> None: + super(DigitalTwinsUpdateComponentOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate + self.if_match = if_match diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_options.py new file mode 100644 index 000000000..9a9cb33e9 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_options.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsUpdateOptions(Model): + """Additional parameters for update operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_match: Only perform the operation if the entity's etag matches + one of the etags provided or * is provided. + :type if_match: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsUpdateOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) + self.if_match = kwargs.get('if_match', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_options_py3.py new file mode 100644 index 000000000..7dcd4a2d7 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_options_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsUpdateOptions(Model): + """Additional parameters for update operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_match: Only perform the operation if the entity's etag matches + one of the etags provided or * is provided. + :type if_match: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, if_match: str=None, **kwargs) -> None: + super(DigitalTwinsUpdateOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate + self.if_match = if_match diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_relationship_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_relationship_options.py new file mode 100644 index 000000000..2fc4e0eb1 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_relationship_options.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsUpdateRelationshipOptions(Model): + """Additional parameters for update_relationship operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_match: Only perform the operation if the entity's etag matches + one of the etags provided or * is provided. + :type if_match: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinsUpdateRelationshipOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) + self.if_match = kwargs.get('if_match', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_relationship_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_relationship_options_py3.py new file mode 100644 index 000000000..0c5f880d0 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/digital_twins_update_relationship_options_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinsUpdateRelationshipOptions(Model): + """Additional parameters for update_relationship operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param if_match: Only perform the operation if the entity's etag matches + one of the etags provided or * is provided. + :type if_match: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'if_match': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, if_match: str=None, **kwargs) -> None: + super(DigitalTwinsUpdateRelationshipOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate + self.if_match = if_match diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/error.py b/azext_iot/sdk/digitaltwins/dataplane/models/error.py new file mode 100644 index 000000000..be8f664d8 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/error.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class Error(Model): + """Error definition. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar code: Service specific error code which serves as the substatus for + the HTTP error code. + :vartype code: str + :ivar message: A human-readable representation of the error. + :vartype message: str + :ivar details: Internal error details. + :vartype details: list[~digitaltwins.models.Error] + :param innererror: An object containing more specific information than the + current object about the error. + :type innererror: ~digitaltwins.models.InnerError + """ + + _validation = { + 'code': {'readonly': True}, + 'message': {'readonly': True}, + 'details': {'readonly': True}, + } + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'details': {'key': 'details', 'type': '[Error]'}, + 'innererror': {'key': 'innererror', 'type': 'InnerError'}, + } + + def __init__(self, **kwargs): + super(Error, self).__init__(**kwargs) + self.code = None + self.message = None + self.details = None + self.innererror = kwargs.get('innererror', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/error_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/error_py3.py new file mode 100644 index 000000000..caf7f5d51 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/error_py3.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class Error(Model): + """Error definition. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar code: Service specific error code which serves as the substatus for + the HTTP error code. + :vartype code: str + :ivar message: A human-readable representation of the error. + :vartype message: str + :ivar details: Internal error details. + :vartype details: list[~digitaltwins.models.Error] + :param innererror: An object containing more specific information than the + current object about the error. + :type innererror: ~digitaltwins.models.InnerError + """ + + _validation = { + 'code': {'readonly': True}, + 'message': {'readonly': True}, + 'details': {'readonly': True}, + } + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'details': {'key': 'details', 'type': '[Error]'}, + 'innererror': {'key': 'innererror', 'type': 'InnerError'}, + } + + def __init__(self, *, innererror=None, **kwargs) -> None: + super(Error, self).__init__(**kwargs) + self.code = None + self.message = None + self.details = None + self.innererror = innererror diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/error_response.py b/azext_iot/sdk/digitaltwins/dataplane/models/error_response.py new file mode 100644 index 000000000..3e179610e --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/error_response.py @@ -0,0 +1,41 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model +from msrest.exceptions import HttpOperationError + + +class ErrorResponse(Model): + """Error response. + + :param error: The error details. + :type error: ~digitaltwins.models.Error + """ + + _attribute_map = { + 'error': {'key': 'error', 'type': 'Error'}, + } + + def __init__(self, **kwargs): + super(ErrorResponse, self).__init__(**kwargs) + self.error = kwargs.get('error', None) + + +class ErrorResponseException(HttpOperationError): + """Server responsed with exception of type: 'ErrorResponse'. + + :param deserialize: A deserializer + :param response: Server response to be deserialized. + """ + + def __init__(self, deserialize, response, *args): + + super(ErrorResponseException, self).__init__(deserialize, response, 'ErrorResponse', *args) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/error_response_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/error_response_py3.py new file mode 100644 index 000000000..89ddba1fb --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/error_response_py3.py @@ -0,0 +1,41 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model +from msrest.exceptions import HttpOperationError + + +class ErrorResponse(Model): + """Error response. + + :param error: The error details. + :type error: ~digitaltwins.models.Error + """ + + _attribute_map = { + 'error': {'key': 'error', 'type': 'Error'}, + } + + def __init__(self, *, error=None, **kwargs) -> None: + super(ErrorResponse, self).__init__(**kwargs) + self.error = error + + +class ErrorResponseException(HttpOperationError): + """Server responsed with exception of type: 'ErrorResponse'. + + :param deserialize: A deserializer + :param response: Server response to be deserialized. + """ + + def __init__(self, deserialize, response, *args): + + super(ErrorResponseException, self).__init__(deserialize, response, 'ErrorResponse', *args) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/event_route.py b/azext_iot/sdk/digitaltwins/dataplane/models/event_route.py new file mode 100644 index 000000000..945fe652a --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/event_route.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EventRoute(Model): + """A route which directs notification and telemetry events to an endpoint. + Endpoints are a destination outside of Azure Digital Twins such as an + EventHub. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar id: The id of the event route. + :vartype id: str + :param endpoint_name: Required. The name of the endpoint this event route + is bound to. + :type endpoint_name: str + :param filter: Required. An expression which describes the events which + are routed to the endpoint. + :type filter: str + """ + + _validation = { + 'id': {'readonly': True}, + 'endpoint_name': {'required': True}, + 'filter': {'required': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'endpoint_name': {'key': 'endpointName', 'type': 'str'}, + 'filter': {'key': 'filter', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(EventRoute, self).__init__(**kwargs) + self.id = None + self.endpoint_name = kwargs.get('endpoint_name', None) + self.filter = kwargs.get('filter', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/event_route_paged.py b/azext_iot/sdk/digitaltwins/dataplane/models/event_route_paged.py new file mode 100644 index 000000000..7b1ce3ea3 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/event_route_paged.py @@ -0,0 +1,27 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.paging import Paged + + +class EventRoutePaged(Paged): + """ + A paging container for iterating over a list of :class:`EventRoute ` object + """ + + _attribute_map = { + 'next_link': {'key': 'nextLink', 'type': 'str'}, + 'current_page': {'key': 'value', 'type': '[EventRoute]'} + } + + def __init__(self, *args, **kwargs): + + super(EventRoutePaged, self).__init__(*args, **kwargs) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/event_route_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/event_route_py3.py new file mode 100644 index 000000000..c7508a44b --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/event_route_py3.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EventRoute(Model): + """A route which directs notification and telemetry events to an endpoint. + Endpoints are a destination outside of Azure Digital Twins such as an + EventHub. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :ivar id: The id of the event route. + :vartype id: str + :param endpoint_name: Required. The name of the endpoint this event route + is bound to. + :type endpoint_name: str + :param filter: Required. An expression which describes the events which + are routed to the endpoint. + :type filter: str + """ + + _validation = { + 'id': {'readonly': True}, + 'endpoint_name': {'required': True}, + 'filter': {'required': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'endpoint_name': {'key': 'endpointName', 'type': 'str'}, + 'filter': {'key': 'filter', 'type': 'str'}, + } + + def __init__(self, *, endpoint_name: str, filter: str, **kwargs) -> None: + super(EventRoute, self).__init__(**kwargs) + self.id = None + self.endpoint_name = endpoint_name + self.filter = filter diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_add_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_add_options.py new file mode 100644 index 000000000..59227319e --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_add_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EventRoutesAddOptions(Model): + """Additional parameters for add operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(EventRoutesAddOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_add_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_add_options_py3.py new file mode 100644 index 000000000..0a296fab6 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_add_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EventRoutesAddOptions(Model): + """Additional parameters for add operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(EventRoutesAddOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_delete_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_delete_options.py new file mode 100644 index 000000000..c572c6e60 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_delete_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EventRoutesDeleteOptions(Model): + """Additional parameters for delete operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(EventRoutesDeleteOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_delete_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_delete_options_py3.py new file mode 100644 index 000000000..b333cfbcf --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_delete_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EventRoutesDeleteOptions(Model): + """Additional parameters for delete operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(EventRoutesDeleteOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_get_by_id_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_get_by_id_options.py new file mode 100644 index 000000000..a36ab7d0c --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_get_by_id_options.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EventRoutesGetByIdOptions(Model): + """Additional parameters for get_by_id operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(EventRoutesGetByIdOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_get_by_id_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_get_by_id_options_py3.py new file mode 100644 index 000000000..c8bb4c3f1 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_get_by_id_options_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EventRoutesGetByIdOptions(Model): + """Additional parameters for get_by_id operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, **kwargs) -> None: + super(EventRoutesGetByIdOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_list_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_list_options.py new file mode 100644 index 000000000..f66b58b96 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_list_options.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EventRoutesListOptions(Model): + """Additional parameters for list operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param max_items_per_page: The maximum number of items to retrieve per + request. The server may choose to return less than the requested number. + :type max_items_per_page: int + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'max_items_per_page': {'key': '', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(EventRoutesListOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) + self.max_items_per_page = kwargs.get('max_items_per_page', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_list_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_list_options_py3.py new file mode 100644 index 000000000..f523dabe0 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/event_routes_list_options_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EventRoutesListOptions(Model): + """Additional parameters for list operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param max_items_per_page: The maximum number of items to retrieve per + request. The server may choose to return less than the requested number. + :type max_items_per_page: int + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'max_items_per_page': {'key': '', 'type': 'int'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, max_items_per_page: int=None, **kwargs) -> None: + super(EventRoutesListOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate + self.max_items_per_page = max_items_per_page diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/incoming_relationship.py b/azext_iot/sdk/digitaltwins/dataplane/models/incoming_relationship.py new file mode 100644 index 000000000..25d97313c --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/incoming_relationship.py @@ -0,0 +1,43 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IncomingRelationship(Model): + """An incoming relationship. + + :param relationship_id: A user-provided string representing the id of this + relationship, unique in the context of the source digital twin, i.e. + sourceId + relationshipId is unique in the context of the service. + :type relationship_id: str + :param source_id: The id of the source digital twin. + :type source_id: str + :param relationship_name: The name of the relationship. + :type relationship_name: str + :param relationship_link: Link to the relationship, to be used for + deletion. + :type relationship_link: str + """ + + _attribute_map = { + 'relationship_id': {'key': '$relationshipId', 'type': 'str'}, + 'source_id': {'key': '$sourceId', 'type': 'str'}, + 'relationship_name': {'key': '$relationshipName', 'type': 'str'}, + 'relationship_link': {'key': '$relationshipLink', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(IncomingRelationship, self).__init__(**kwargs) + self.relationship_id = kwargs.get('relationship_id', None) + self.source_id = kwargs.get('source_id', None) + self.relationship_name = kwargs.get('relationship_name', None) + self.relationship_link = kwargs.get('relationship_link', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/incoming_relationship_paged.py b/azext_iot/sdk/digitaltwins/dataplane/models/incoming_relationship_paged.py new file mode 100644 index 000000000..d51ad0e33 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/incoming_relationship_paged.py @@ -0,0 +1,27 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.paging import Paged + + +class IncomingRelationshipPaged(Paged): + """ + A paging container for iterating over a list of :class:`IncomingRelationship ` object + """ + + _attribute_map = { + 'next_link': {'key': 'nextLink', 'type': 'str'}, + 'current_page': {'key': 'value', 'type': '[IncomingRelationship]'} + } + + def __init__(self, *args, **kwargs): + + super(IncomingRelationshipPaged, self).__init__(*args, **kwargs) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/incoming_relationship_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/incoming_relationship_py3.py new file mode 100644 index 000000000..6bb774195 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/incoming_relationship_py3.py @@ -0,0 +1,43 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IncomingRelationship(Model): + """An incoming relationship. + + :param relationship_id: A user-provided string representing the id of this + relationship, unique in the context of the source digital twin, i.e. + sourceId + relationshipId is unique in the context of the service. + :type relationship_id: str + :param source_id: The id of the source digital twin. + :type source_id: str + :param relationship_name: The name of the relationship. + :type relationship_name: str + :param relationship_link: Link to the relationship, to be used for + deletion. + :type relationship_link: str + """ + + _attribute_map = { + 'relationship_id': {'key': '$relationshipId', 'type': 'str'}, + 'source_id': {'key': '$sourceId', 'type': 'str'}, + 'relationship_name': {'key': '$relationshipName', 'type': 'str'}, + 'relationship_link': {'key': '$relationshipLink', 'type': 'str'}, + } + + def __init__(self, *, relationship_id: str=None, source_id: str=None, relationship_name: str=None, relationship_link: str=None, **kwargs) -> None: + super(IncomingRelationship, self).__init__(**kwargs) + self.relationship_id = relationship_id + self.source_id = source_id + self.relationship_name = relationship_name + self.relationship_link = relationship_link diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/inner_error.py b/azext_iot/sdk/digitaltwins/dataplane/models/inner_error.py new file mode 100644 index 000000000..36204f33a --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/inner_error.py @@ -0,0 +1,35 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class InnerError(Model): + """A more specific error description than was provided by the containing + error. + + :param code: A more specific error code than was provided by the + containing error. + :type code: str + :param innererror: An object containing more specific information than the + current object about the error. + :type innererror: ~digitaltwins.models.InnerError + """ + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'innererror': {'key': 'innererror', 'type': 'InnerError'}, + } + + def __init__(self, **kwargs): + super(InnerError, self).__init__(**kwargs) + self.code = kwargs.get('code', None) + self.innererror = kwargs.get('innererror', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/inner_error_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/inner_error_py3.py new file mode 100644 index 000000000..46071b562 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/inner_error_py3.py @@ -0,0 +1,35 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class InnerError(Model): + """A more specific error description than was provided by the containing + error. + + :param code: A more specific error code than was provided by the + containing error. + :type code: str + :param innererror: An object containing more specific information than the + current object about the error. + :type innererror: ~digitaltwins.models.InnerError + """ + + _attribute_map = { + 'code': {'key': 'code', 'type': 'str'}, + 'innererror': {'key': 'innererror', 'type': 'InnerError'}, + } + + def __init__(self, *, code: str=None, innererror=None, **kwargs) -> None: + super(InnerError, self).__init__(**kwargs) + self.code = code + self.innererror = innererror diff --git a/azext_iot/sdk/service/models/desired.py b/azext_iot/sdk/digitaltwins/dataplane/models/object_paged.py similarity index 59% rename from azext_iot/sdk/service/models/desired.py rename to azext_iot/sdk/digitaltwins/dataplane/models/object_paged.py index 8ac77f637..ebed1de3a 100644 --- a/azext_iot/sdk/service/models/desired.py +++ b/azext_iot/sdk/digitaltwins/dataplane/models/object_paged.py @@ -9,21 +9,19 @@ # regenerated. # -------------------------------------------------------------------------- -from msrest.serialization import Model +from msrest.paging import Paged -class Desired(Model): - """Desired. - - :param value: The desired value of the interface property to set in a - digitalTwin. - :type value: object +class ObjectPaged(Paged): + """ + A paging container for iterating over a list of object object """ _attribute_map = { - 'value': {'key': 'value', 'type': 'object'}, + 'next_link': {'key': 'nextLink', 'type': 'str'}, + 'current_page': {'key': 'value', 'type': '[object]'} } - def __init__(self, value=None): - super(Desired, self).__init__() - self.value = value + def __init__(self, *args, **kwargs): + + super(ObjectPaged, self).__init__(*args, **kwargs) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/query_query_twins_options.py b/azext_iot/sdk/digitaltwins/dataplane/models/query_query_twins_options.py new file mode 100644 index 000000000..45739d288 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/query_query_twins_options.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class QueryQueryTwinsOptions(Model): + """Additional parameters for query_twins operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param max_items_per_page: The maximum number of items to retrieve per + request. The server may choose to return less than the requested number. + :type max_items_per_page: int + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'max_items_per_page': {'key': '', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(QueryQueryTwinsOptions, self).__init__(**kwargs) + self.traceparent = kwargs.get('traceparent', None) + self.tracestate = kwargs.get('tracestate', None) + self.max_items_per_page = kwargs.get('max_items_per_page', None) diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/query_query_twins_options_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/query_query_twins_options_py3.py new file mode 100644 index 000000000..81a3dcb12 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/query_query_twins_options_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class QueryQueryTwinsOptions(Model): + """Additional parameters for query_twins operation. + + :param traceparent: Identifies the request in a distributed tracing + system. + :type traceparent: str + :param tracestate: Provides vendor-specific trace identification + information and is a companion to traceparent. + :type tracestate: str + :param max_items_per_page: The maximum number of items to retrieve per + request. The server may choose to return less than the requested number. + :type max_items_per_page: int + """ + + _attribute_map = { + 'traceparent': {'key': '', 'type': 'str'}, + 'tracestate': {'key': '', 'type': 'str'}, + 'max_items_per_page': {'key': '', 'type': 'int'}, + } + + def __init__(self, *, traceparent: str=None, tracestate: str=None, max_items_per_page: int=None, **kwargs) -> None: + super(QueryQueryTwinsOptions, self).__init__(**kwargs) + self.traceparent = traceparent + self.tracestate = tracestate + self.max_items_per_page = max_items_per_page diff --git a/azext_iot/sdk/pnp/models/search_response.py b/azext_iot/sdk/digitaltwins/dataplane/models/query_result.py similarity index 62% rename from azext_iot/sdk/pnp/models/search_response.py rename to azext_iot/sdk/digitaltwins/dataplane/models/query_result.py index a6e36d521..2af8ae62d 100644 --- a/azext_iot/sdk/pnp/models/search_response.py +++ b/azext_iot/sdk/digitaltwins/dataplane/models/query_result.py @@ -12,22 +12,22 @@ from msrest.serialization import Model -class SearchResponse(Model): - """SearchResponse. +class QueryResult(Model): + """The results of a query operation and an optional continuation token. - :param continuation_token: + :param value: The query results. + :type value: list[object] + :param continuation_token: A token which can be used to construct a new + QuerySpecification to retrieve the next set of results. :type continuation_token: str - :param results: - :type results: - list[~digitaltwinmodelrepositoryservice.models.ModelInformation] """ _attribute_map = { + 'value': {'key': 'value', 'type': '[object]'}, 'continuation_token': {'key': 'continuationToken', 'type': 'str'}, - 'results': {'key': 'results', 'type': '[ModelInformation]'}, } def __init__(self, **kwargs): - super(SearchResponse, self).__init__(**kwargs) + super(QueryResult, self).__init__(**kwargs) + self.value = kwargs.get('value', None) self.continuation_token = kwargs.get('continuation_token', None) - self.results = kwargs.get('results', None) diff --git a/azext_iot/sdk/pnp/models/search_response_py3.py b/azext_iot/sdk/digitaltwins/dataplane/models/query_result_py3.py similarity index 57% rename from azext_iot/sdk/pnp/models/search_response_py3.py rename to azext_iot/sdk/digitaltwins/dataplane/models/query_result_py3.py index 94d05c147..72c68e909 100644 --- a/azext_iot/sdk/pnp/models/search_response_py3.py +++ b/azext_iot/sdk/digitaltwins/dataplane/models/query_result_py3.py @@ -12,22 +12,22 @@ from msrest.serialization import Model -class SearchResponse(Model): - """SearchResponse. +class QueryResult(Model): + """The results of a query operation and an optional continuation token. - :param continuation_token: + :param value: The query results. + :type value: list[object] + :param continuation_token: A token which can be used to construct a new + QuerySpecification to retrieve the next set of results. :type continuation_token: str - :param results: - :type results: - list[~digitaltwinmodelrepositoryservice.models.ModelInformation] """ _attribute_map = { + 'value': {'key': 'value', 'type': '[object]'}, 'continuation_token': {'key': 'continuationToken', 'type': 'str'}, - 'results': {'key': 'results', 'type': '[ModelInformation]'}, } - def __init__(self, *, continuation_token: str=None, results=None, **kwargs) -> None: - super(SearchResponse, self).__init__(**kwargs) + def __init__(self, *, value=None, continuation_token: str=None, **kwargs) -> None: + super(QueryResult, self).__init__(**kwargs) + self.value = value self.continuation_token = continuation_token - self.results = results diff --git a/azext_iot/sdk/digitaltwins/dataplane/models/query_specification.py b/azext_iot/sdk/digitaltwins/dataplane/models/query_specification.py new file mode 100644 index 000000000..854db4323 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/models/query_specification.py @@ -0,0 +1,35 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class QuerySpecification(Model): + """A query specification containing either a query statement or a continuation + token from a previous query result. + + :param query: The query to execute. This value is ignored if a + continuation token is provided. + :type query: str + :param continuation_token: A token which is used to retrieve the next set + of results from a previous query. + :type continuation_token: str + """ + + _attribute_map = { + 'query': {'key': 'query', 'type': 'str'}, + 'continuation_token': {'key': 'continuationToken', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(QuerySpecification, self).__init__(**kwargs) + self.query = kwargs.get('query', None) + self.continuation_token = kwargs.get('continuation_token', None) diff --git a/azext_iot/sdk/service/models/query_result.py b/azext_iot/sdk/digitaltwins/dataplane/models/query_specification_py3.py similarity index 50% rename from azext_iot/sdk/service/models/query_result.py rename to azext_iot/sdk/digitaltwins/dataplane/models/query_specification_py3.py index 77dfdc59d..7aa1b5449 100644 --- a/azext_iot/sdk/service/models/query_result.py +++ b/azext_iot/sdk/digitaltwins/dataplane/models/query_specification_py3.py @@ -12,27 +12,24 @@ from msrest.serialization import Model -class QueryResult(Model): - """The query result. +class QuerySpecification(Model): + """A query specification containing either a query statement or a continuation + token from a previous query result. - :param type: The query result type. Possible values include: 'unknown', - 'twin', 'deviceJob', 'jobResponse', 'raw', 'enrollment', - 'enrollmentGroup', 'deviceRegistration' - :type type: str or ~service.models.enum - :param items: The query result items, as a collection. - :type items: list[object] - :param continuation_token: Request continuation token. + :param query: The query to execute. This value is ignored if a + continuation token is provided. + :type query: str + :param continuation_token: A token which is used to retrieve the next set + of results from a previous query. :type continuation_token: str """ _attribute_map = { - 'type': {'key': 'type', 'type': 'str'}, - 'items': {'key': 'items', 'type': '[object]'}, + 'query': {'key': 'query', 'type': 'str'}, 'continuation_token': {'key': 'continuationToken', 'type': 'str'}, } - def __init__(self, type=None, items=None, continuation_token=None): - super(QueryResult, self).__init__() - self.type = type - self.items = items + def __init__(self, *, query: str=None, continuation_token: str=None, **kwargs) -> None: + super(QuerySpecification, self).__init__(**kwargs) + self.query = query self.continuation_token = continuation_token diff --git a/azext_iot/sdk/digitaltwins/dataplane/operations/__init__.py b/azext_iot/sdk/digitaltwins/dataplane/operations/__init__.py new file mode 100644 index 000000000..80eca0cbf --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/operations/__init__.py @@ -0,0 +1,22 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .digital_twin_models_operations import DigitalTwinModelsOperations +from .query_operations import QueryOperations +from .digital_twins_operations import DigitalTwinsOperations +from .event_routes_operations import EventRoutesOperations + +__all__ = [ + 'DigitalTwinModelsOperations', + 'QueryOperations', + 'DigitalTwinsOperations', + 'EventRoutesOperations', +] diff --git a/azext_iot/sdk/digitaltwins/dataplane/operations/digital_twin_models_operations.py b/azext_iot/sdk/digitaltwins/dataplane/operations/digital_twin_models_operations.py new file mode 100644 index 000000000..32851bfbe --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/operations/digital_twin_models_operations.py @@ -0,0 +1,460 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse + +from .. import models + + +class DigitalTwinModelsOperations(object): + """DigitalTwinModelsOperations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: The API version to use for the request. Constant value: "2020-10-31". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-10-31" + + self.config = config + + def add( + self, models=None, digital_twin_models_add_options=None, custom_headers=None, raw=False, **operation_config): + """Uploads one or more models. When any error occurs, no models are + uploaded. + Status codes: + * 201 Created + * 400 Bad Request + * DTDLParserError - The models provided are not valid DTDL. + * InvalidArgument - The model id is invalid. + * LimitExceeded - The maximum number of model ids allowed in + 'dependenciesFor' has been reached. + * ModelVersionNotSupported - The version of DTDL used is not supported. + * 409 Conflict + * ModelAlreadyExists - The model provided already exists. + + :param models: An array of models to add. + :type models: list[object] + :param digital_twin_models_add_options: Additional parameters for the + operation + :type digital_twin_models_add_options: + ~digitaltwins.models.DigitalTwinModelsAddOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: list or ClientRawResponse if raw=true + :rtype: list[~digitaltwins.models.DigitalTwinsModelData] or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twin_models_add_options is not None: + traceparent = digital_twin_models_add_options.traceparent + tracestate = None + if digital_twin_models_add_options is not None: + tracestate = digital_twin_models_add_options.tracestate + + # Construct URL + url = self.add.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct body + if models is not None: + body_content = self._serialize.body(models, '[object]') + else: + body_content = None + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + # @digimaun - custom response handling + return response + + add.metadata = {'url': '/models'} + + def list( + self, dependencies_for=None, include_model_definition=False, digital_twin_models_list_options=None, custom_headers=None, raw=False, **operation_config): + """Retrieves model metadata and, optionally, model definitions. + Status codes: + * 200 OK + * 400 Bad Request + * InvalidArgument - The model id is invalid. + * LimitExceeded - The maximum number of model ids allowed in + 'dependenciesFor' has been reached. + * 404 Not Found + * ModelNotFound - The model was not found. + + :param dependencies_for: The set of the models which will have their + dependencies retrieved. If omitted, all models are retrieved. + :type dependencies_for: list[str] + :param include_model_definition: When true the model definition will + be returned as part of the result. + :type include_model_definition: bool + :param digital_twin_models_list_options: Additional parameters for the + operation + :type digital_twin_models_list_options: + ~digitaltwins.models.DigitalTwinModelsListOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: An iterator like instance of DigitalTwinsModelData + :rtype: + ~digitaltwins.models.DigitalTwinsModelDataPaged[~digitaltwins.models.DigitalTwinsModelData] + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twin_models_list_options is not None: + traceparent = digital_twin_models_list_options.traceparent + tracestate = None + if digital_twin_models_list_options is not None: + tracestate = digital_twin_models_list_options.tracestate + max_items_per_page = None + if digital_twin_models_list_options is not None: + max_items_per_page = digital_twin_models_list_options.max_items_per_page + + def internal_paging(next_link=None, raw=False): + + if not next_link: + # Construct URL + url = self.list.metadata['url'] + + # Construct parameters + query_parameters = {} + if dependencies_for is not None: + # @digimaun - super hackery to support collectionFormat: multi + concatted_qs = "" + for dtmi in dependencies_for: + concatted_qs = concatted_qs + self._serialize.query("dependencies_for", dtmi, 'str') + if dtmi != dependencies_for[-1]: + concatted_qs = concatted_qs + "&dependenciesFor=" + + query_parameters['dependenciesFor'] = concatted_qs + + if include_model_definition is not None: + query_parameters['includeModelDefinition'] = self._serialize.query("include_model_definition", include_model_definition, 'bool') + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + else: + url = next_link + query_parameters = {} + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + if max_items_per_page is not None: + header_parameters['max-items-per-page'] = self._serialize.header("max_items_per_page", max_items_per_page, 'int') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + return response + + # Deserialize response + deserialized = models.DigitalTwinsModelDataPaged(internal_paging, self._deserialize.dependencies) + + if raw: + header_dict = {} + client_raw_response = models.DigitalTwinsModelDataPaged(internal_paging, self._deserialize.dependencies, header_dict) + return client_raw_response + + return deserialized + list.metadata = {'url': '/models'} + + def get_by_id( + self, id, include_model_definition=False, digital_twin_models_get_by_id_options=None, custom_headers=None, raw=False, **operation_config): + """Retrieves model metadata and optionally the model definition. + Status codes: + * 200 OK + * 400 Bad Request + * InvalidArgument - The model id is invalid. + * MissingArgument - The model id was not provided. + * 404 Not Found + * ModelNotFound - The model was not found. + + :param id: The id for the model. The id is globally unique and case + sensitive. + :type id: str + :param include_model_definition: When true the model definition will + be returned as part of the result. + :type include_model_definition: bool + :param digital_twin_models_get_by_id_options: Additional parameters + for the operation + :type digital_twin_models_get_by_id_options: + ~digitaltwins.models.DigitalTwinModelsGetByIdOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: DigitalTwinsModelData or ClientRawResponse if raw=true + :rtype: ~digitaltwins.models.DigitalTwinsModelData or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twin_models_get_by_id_options is not None: + traceparent = digital_twin_models_get_by_id_options.traceparent + tracestate = None + if digital_twin_models_get_by_id_options is not None: + tracestate = digital_twin_models_get_by_id_options.tracestate + + # Construct URL + url = self.get_by_id.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + if include_model_definition is not None: + query_parameters['includeModelDefinition'] = self._serialize.query("include_model_definition", include_model_definition, 'bool') + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('DigitalTwinsModelData', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_by_id.metadata = {'url': '/models/{id}'} + + def update( + self, update_model, id, digital_twin_models_update_options=None, custom_headers=None, raw=False, **operation_config): + """Updates the metadata for a model. + Status codes: + * 204 No Content + * 400 Bad Request + * InvalidArgument - The model id is invalid. + * JsonPatchInvalid - The JSON Patch provided is invalid. + * MissingArgument - The model id was not provided. + * 404 Not Found + * ModelNotFound - The model was not found. + * 409 Conflict + * ModelReferencesNotDecommissioned - The model refers to models that + are not decommissioned. + + :param update_model: An update specification described by JSON Patch. + Only the decommissioned property can be replaced. + :type update_model: list[object] + :param id: The id for the model. The id is globally unique and case + sensitive. + :type id: str + :param digital_twin_models_update_options: Additional parameters for + the operation + :type digital_twin_models_update_options: + ~digitaltwins.models.DigitalTwinModelsUpdateOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twin_models_update_options is not None: + traceparent = digital_twin_models_update_options.traceparent + tracestate = None + if digital_twin_models_update_options is not None: + tracestate = digital_twin_models_update_options.tracestate + + # Construct URL + url = self.update.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Content-Type'] = 'application/json-patch+json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct body + body_content = self._serialize.body(update_model, '[object]') + + # Construct and send request + request = self._client.patch(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ErrorResponseException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + update.metadata = {'url': '/models/{id}'} + + def delete( + self, id, digital_twin_models_delete_options=None, custom_headers=None, raw=False, **operation_config): + """Deletes a model. A model can only be deleted if no other models + reference it. + Status codes: + * 204 No Content + * 400 Bad Request + * InvalidArgument - The model id is invalid. + * MissingArgument - The model id was not provided. + * 404 Not Found + * ModelNotFound - The model was not found. + * 409 Conflict + * ModelReferencesNotDeleted - The model refers to models that are not + deleted. + + :param id: The id for the model. The id is globally unique and case + sensitive. + :type id: str + :param digital_twin_models_delete_options: Additional parameters for + the operation + :type digital_twin_models_delete_options: + ~digitaltwins.models.DigitalTwinModelsDeleteOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twin_models_delete_options is not None: + traceparent = digital_twin_models_delete_options.traceparent + tracestate = None + if digital_twin_models_delete_options is not None: + tracestate = digital_twin_models_delete_options.tracestate + + # Construct URL + url = self.delete.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ErrorResponseException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + delete.metadata = {'url': '/models/{id}'} diff --git a/azext_iot/sdk/digitaltwins/dataplane/operations/digital_twins_operations.py b/azext_iot/sdk/digitaltwins/dataplane/operations/digital_twins_operations.py new file mode 100644 index 000000000..1d9e1ea35 --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/operations/digital_twins_operations.py @@ -0,0 +1,1309 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse + +from .. import models + + +class DigitalTwinsOperations(object): + """DigitalTwinsOperations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: The API version to use for the request. Constant value: "2020-10-31". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-10-31" + + self.config = config + + def get_by_id( + self, id, digital_twins_get_by_id_options=None, custom_headers=None, raw=False, **operation_config): + """Retrieves a digital twin. + Status codes: + * 200 OK + * 400 Bad Request + * InvalidArgument - The digital twin id is invalid. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param digital_twins_get_by_id_options: Additional parameters for the + operation + :type digital_twins_get_by_id_options: + ~digitaltwins.models.DigitalTwinsGetByIdOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_get_by_id_options is not None: + traceparent = digital_twins_get_by_id_options.traceparent + tracestate = None + if digital_twins_get_by_id_options is not None: + tracestate = digital_twins_get_by_id_options.tracestate + + # Construct URL + url = self.get_by_id.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + deserialized = None + header_dict = {} + + if response.status_code == 200: + deserialized = self._deserialize('object', response) + header_dict = { + 'ETag': 'str', + } + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + client_raw_response.add_headers(header_dict) + return client_raw_response + + return deserialized + get_by_id.metadata = {'url': '/digitaltwins/{id}'} + + def add( + self, twin, id, digital_twins_add_options=None, custom_headers=None, raw=False, **operation_config): + """Adds or replaces a digital twin. + Status codes: + * 200 OK + * 400 Bad Request + * InvalidArgument - The digital twin id or payload is invalid. + * ModelDecommissioned - The model for the digital twin is + decommissioned. + * TwinLimitReached - The maximum number of digital twins allowed has + been reached. + * ValidationFailed - The digital twin payload is not valid. + * 412 Precondition Failed + * PreconditionFailed - The precondition check (If-Match or + If-None-Match) failed. + + :param twin: The digital twin instance being added. If provided, the + $dtId property is ignored. + :type twin: object + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param digital_twins_add_options: Additional parameters for the + operation + :type digital_twins_add_options: + ~digitaltwins.models.DigitalTwinsAddOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_add_options is not None: + traceparent = digital_twins_add_options.traceparent + tracestate = None + if digital_twins_add_options is not None: + tracestate = digital_twins_add_options.tracestate + if_none_match = None + if digital_twins_add_options is not None: + if_none_match = digital_twins_add_options.if_none_match + + # Construct URL + url = self.add.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + if if_none_match is not None: + header_parameters['If-None-Match'] = self._serialize.header("if_none_match", if_none_match, 'str') + + # Construct body + body_content = self._serialize.body(twin, 'object') + + # Construct and send request + request = self._client.put(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 202]: + raise models.ErrorResponseException(self._deserialize, response) + + deserialized = None + header_dict = {} + + if response.status_code == 200: + deserialized = self._deserialize('object', response) + header_dict = { + 'ETag': 'str', + } + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + client_raw_response.add_headers(header_dict) + return client_raw_response + + return deserialized + add.metadata = {'url': '/digitaltwins/{id}'} + + def delete( + self, id, digital_twins_delete_options=None, custom_headers=None, raw=False, **operation_config): + """Deletes a digital twin. All relationships referencing the digital twin + must already be deleted. + Status codes: + * 204 No Content + * 400 Bad Request + * InvalidArgument - The digital twin id is invalid. + * RelationshipsNotDeleted - The digital twin contains relationships. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + * 412 Precondition Failed + * PreconditionFailed - The precondition check (If-Match or + If-None-Match) failed. + + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param digital_twins_delete_options: Additional parameters for the + operation + :type digital_twins_delete_options: + ~digitaltwins.models.DigitalTwinsDeleteOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_delete_options is not None: + traceparent = digital_twins_delete_options.traceparent + tracestate = None + if digital_twins_delete_options is not None: + tracestate = digital_twins_delete_options.tracestate + if_match = None + if digital_twins_delete_options is not None: + if_match = digital_twins_delete_options.if_match + + # Construct URL + url = self.delete.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ErrorResponseException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + delete.metadata = {'url': '/digitaltwins/{id}'} + + def update( + self, patch_document, id, digital_twins_update_options=None, custom_headers=None, raw=False, **operation_config): + """Updates a digital twin. + Status codes: + * 204 No Content + * 400 Bad Request + * InvalidArgument - The digital twin id or payload is invalid. + * JsonPatchInvalid - The JSON Patch provided is invalid. + * ValidationFailed - Applying the patch results in an invalid digital + twin. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + * 412 Precondition Failed + * PreconditionFailed - The precondition check (If-Match or + If-None-Match) failed. + + :param patch_document: An update specification described by JSON + Patch. Updates to property values and $model elements may happen in + the same request. Operations are limited to add, replace and remove. + :type patch_document: list[object] + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param digital_twins_update_options: Additional parameters for the + operation + :type digital_twins_update_options: + ~digitaltwins.models.DigitalTwinsUpdateOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_update_options is not None: + traceparent = digital_twins_update_options.traceparent + tracestate = None + if digital_twins_update_options is not None: + tracestate = digital_twins_update_options.tracestate + if_match = None + if digital_twins_update_options is not None: + if_match = digital_twins_update_options.if_match + + # Construct URL + url = self.update.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Content-Type'] = 'application/json-patch+json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + + # Construct body + body_content = self._serialize.body(patch_document, '[object]') + + # Construct and send request + request = self._client.patch(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [202, 204]: + raise models.ErrorResponseException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + client_raw_response.add_headers({ + 'ETag': 'str', + }) + return client_raw_response + update.metadata = {'url': '/digitaltwins/{id}'} + + def get_relationship_by_id( + self, id, relationship_id, digital_twins_get_relationship_by_id_options=None, custom_headers=None, raw=False, **operation_config): + """Retrieves a relationship between two digital twins. + Status codes: + * 200 OK + * 400 Bad Request + * InvalidArgument - The digital twin id or relationship id is invalid. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + * RelationshipNotFound - The relationship was not found. + + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param relationship_id: The id of the relationship. The id is unique + within the digital twin and case sensitive. + :type relationship_id: str + :param digital_twins_get_relationship_by_id_options: Additional + parameters for the operation + :type digital_twins_get_relationship_by_id_options: + ~digitaltwins.models.DigitalTwinsGetRelationshipByIdOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_get_relationship_by_id_options is not None: + traceparent = digital_twins_get_relationship_by_id_options.traceparent + tracestate = None + if digital_twins_get_relationship_by_id_options is not None: + tracestate = digital_twins_get_relationship_by_id_options.tracestate + + # Construct URL + url = self.get_relationship_by_id.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'relationshipId': self._serialize.url("relationship_id", relationship_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + deserialized = None + header_dict = {} + + if response.status_code == 200: + deserialized = self._deserialize('object', response) + header_dict = { + 'ETag': 'str', + } + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + client_raw_response.add_headers(header_dict) + return client_raw_response + + return deserialized + get_relationship_by_id.metadata = {'url': '/digitaltwins/{id}/relationships/{relationshipId}'} + + def add_relationship( + self, relationship, id, relationship_id, digital_twins_add_relationship_options=None, custom_headers=None, raw=False, **operation_config): + """Adds a relationship between two digital twins. + Status codes: + * 200 OK + * 400 Bad Request + * InvalidArgument - The digital twin id, relationship id, or payload is + invalid. + * InvalidRelationship - The relationship is invalid. + * OperationNotAllowed - The relationship cannot connect to the same + digital twin. + * ValidationFailed - The relationship content is invalid. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + * TargetTwinNotFound - The digital twin target of the relationship was + not found. + * 412 Precondition Failed + * PreconditionFailed - The precondition check (If-Match or + If-None-Match) failed. + + :param relationship: The data for the relationship. + :type relationship: object + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param relationship_id: The id of the relationship. The id is unique + within the digital twin and case sensitive. + :type relationship_id: str + :param digital_twins_add_relationship_options: Additional parameters + for the operation + :type digital_twins_add_relationship_options: + ~digitaltwins.models.DigitalTwinsAddRelationshipOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_add_relationship_options is not None: + traceparent = digital_twins_add_relationship_options.traceparent + tracestate = None + if digital_twins_add_relationship_options is not None: + tracestate = digital_twins_add_relationship_options.tracestate + if_none_match = None + if digital_twins_add_relationship_options is not None: + if_none_match = digital_twins_add_relationship_options.if_none_match + + # Construct URL + url = self.add_relationship.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'relationshipId': self._serialize.url("relationship_id", relationship_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + if if_none_match is not None: + header_parameters['If-None-Match'] = self._serialize.header("if_none_match", if_none_match, 'str') + + # Construct body + body_content = self._serialize.body(relationship, 'object') + + # Construct and send request + request = self._client.put(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + deserialized = None + header_dict = {} + + if response.status_code == 200: + deserialized = self._deserialize('object', response) + header_dict = { + 'ETag': 'str', + } + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + client_raw_response.add_headers(header_dict) + return client_raw_response + + return deserialized + add_relationship.metadata = {'url': '/digitaltwins/{id}/relationships/{relationshipId}'} + + def delete_relationship( + self, id, relationship_id, digital_twins_delete_relationship_options=None, custom_headers=None, raw=False, **operation_config): + """Deletes a relationship between two digital twins. + Status codes: + * 204 No Content + * 400 Bad Request + * InvalidArgument - The digital twin id or relationship id is invalid. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + * RelationshipNotFound - The relationship was not found. + * 412 Precondition Failed + * PreconditionFailed - The precondition check (If-Match or + If-None-Match) failed. + + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param relationship_id: The id of the relationship. The id is unique + within the digital twin and case sensitive. + :type relationship_id: str + :param digital_twins_delete_relationship_options: Additional + parameters for the operation + :type digital_twins_delete_relationship_options: + ~digitaltwins.models.DigitalTwinsDeleteRelationshipOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_delete_relationship_options is not None: + traceparent = digital_twins_delete_relationship_options.traceparent + tracestate = None + if digital_twins_delete_relationship_options is not None: + tracestate = digital_twins_delete_relationship_options.tracestate + if_match = None + if digital_twins_delete_relationship_options is not None: + if_match = digital_twins_delete_relationship_options.if_match + + # Construct URL + url = self.delete_relationship.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'relationshipId': self._serialize.url("relationship_id", relationship_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ErrorResponseException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + delete_relationship.metadata = {'url': '/digitaltwins/{id}/relationships/{relationshipId}'} + + def update_relationship( + self, patch_document, id, relationship_id, digital_twins_update_relationship_options=None, custom_headers=None, raw=False, **operation_config): + """Updates the properties on a relationship between two digital twins. + Status codes: + * 204 No Content + * 400 Bad Request + * InvalidArgument - The digital twin id or relationship id is invalid. + * InvalidRelationship - The relationship is invalid. + * JsonPatchInvalid - The JSON Patch provided is invalid. + * ValidationFailed - The relationship content is invalid. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + * RelationshipNotFound - The relationship was not found. + * 409 Conflict + * RelationshipAlreadyExists - The relationship already exists. + * 412 Precondition Failed + * PreconditionFailed - The precondition check (If-Match or + If-None-Match) failed. + + :param patch_document: JSON Patch description of the update to the + relationship properties. + :type patch_document: list[object] + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param relationship_id: The id of the relationship. The id is unique + within the digital twin and case sensitive. + :type relationship_id: str + :param digital_twins_update_relationship_options: Additional + parameters for the operation + :type digital_twins_update_relationship_options: + ~digitaltwins.models.DigitalTwinsUpdateRelationshipOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_update_relationship_options is not None: + traceparent = digital_twins_update_relationship_options.traceparent + tracestate = None + if digital_twins_update_relationship_options is not None: + tracestate = digital_twins_update_relationship_options.tracestate + if_match = None + if digital_twins_update_relationship_options is not None: + if_match = digital_twins_update_relationship_options.if_match + + # Construct URL + url = self.update_relationship.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'relationshipId': self._serialize.url("relationship_id", relationship_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Content-Type'] = 'application/json-patch+json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + + # Construct body + body_content = self._serialize.body(patch_document, '[object]') + + # Construct and send request + request = self._client.patch(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ErrorResponseException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + client_raw_response.add_headers({ + 'ETag': 'str', + }) + return client_raw_response + update_relationship.metadata = {'url': '/digitaltwins/{id}/relationships/{relationshipId}'} + + def list_relationships( + self, id, relationship_name=None, digital_twins_list_relationships_options=None, custom_headers=None, raw=False, **operation_config): + """Retrieves the relationships from a digital twin. + Status codes: + * 200 OK + * 400 Bad Request + * InvalidArgument - The digital twin id is invalid. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param relationship_name: The name of the relationship. + :type relationship_name: str + :param digital_twins_list_relationships_options: Additional parameters + for the operation + :type digital_twins_list_relationships_options: + ~digitaltwins.models.DigitalTwinsListRelationshipsOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: An iterator like instance of object + :rtype: ~digitaltwins.models.ObjectPaged[object] + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_list_relationships_options is not None: + traceparent = digital_twins_list_relationships_options.traceparent + tracestate = None + if digital_twins_list_relationships_options is not None: + tracestate = digital_twins_list_relationships_options.tracestate + + def internal_paging(next_link=None, raw=False): + + if not next_link: + # Construct URL + url = self.list_relationships.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + if relationship_name is not None: + query_parameters['relationshipName'] = self._serialize.query("relationship_name", relationship_name, 'str') + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + else: + url = next_link + query_parameters = {} + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + return response + + # Deserialize response + deserialized = models.ObjectPaged(internal_paging, self._deserialize.dependencies) + + if raw: + header_dict = {} + client_raw_response = models.ObjectPaged(internal_paging, self._deserialize.dependencies, header_dict) + return client_raw_response + + return deserialized + list_relationships.metadata = {'url': '/digitaltwins/{id}/relationships'} + + def list_incoming_relationships( + self, id, digital_twins_list_incoming_relationships_options=None, custom_headers=None, raw=False, **operation_config): + """Retrieves all incoming relationship for a digital twin. + Status codes: + * 200 OK + * 400 Bad Request + * InvalidArgument - The digital twin id is invalid. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param digital_twins_list_incoming_relationships_options: Additional + parameters for the operation + :type digital_twins_list_incoming_relationships_options: + ~digitaltwins.models.DigitalTwinsListIncomingRelationshipsOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: An iterator like instance of IncomingRelationship + :rtype: + ~digitaltwins.models.IncomingRelationshipPaged[~digitaltwins.models.IncomingRelationship] + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_list_incoming_relationships_options is not None: + traceparent = digital_twins_list_incoming_relationships_options.traceparent + tracestate = None + if digital_twins_list_incoming_relationships_options is not None: + tracestate = digital_twins_list_incoming_relationships_options.tracestate + + def internal_paging(next_link=None, raw=False): + + if not next_link: + # Construct URL + url = self.list_incoming_relationships.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + else: + url = next_link + query_parameters = {} + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + return response + + # Deserialize response + deserialized = models.IncomingRelationshipPaged(internal_paging, self._deserialize.dependencies) + + if raw: + header_dict = {} + client_raw_response = models.IncomingRelationshipPaged(internal_paging, self._deserialize.dependencies, header_dict) + return client_raw_response + + return deserialized + list_incoming_relationships.metadata = {'url': '/digitaltwins/{id}/incomingrelationships'} + + def send_telemetry( + self, telemetry, id, message_id, telemetry_source_time=None, digital_twins_send_telemetry_options=None, custom_headers=None, raw=False, **operation_config): + """Sends telemetry on behalf of a digital twin. + Status codes: + * 204 No Content + * 400 Bad Request + * InvalidArgument - The digital twin id or message id is invalid. + * ValidationFailed - The telemetry content is invalid. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + + :param telemetry: The telemetry measurements to send from the digital + twin. + :type telemetry: object + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param message_id: A unique message identifier (in the scope of the + digital twin id) that is commonly used for de-duplicating messages. + :type message_id: str + :param telemetry_source_time: An RFC 3339 timestamp that identifies + the time the telemetry was measured. + :type telemetry_source_time: str + :param digital_twins_send_telemetry_options: Additional parameters for + the operation + :type digital_twins_send_telemetry_options: + ~digitaltwins.models.DigitalTwinsSendTelemetryOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_send_telemetry_options is not None: + traceparent = digital_twins_send_telemetry_options.traceparent + tracestate = None + if digital_twins_send_telemetry_options is not None: + tracestate = digital_twins_send_telemetry_options.tracestate + + # Construct URL + url = self.send_telemetry.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + header_parameters['Message-Id'] = self._serialize.header("message_id", message_id, 'str') + if telemetry_source_time is not None: + header_parameters['Telemetry-Source-Time'] = self._serialize.header("telemetry_source_time", telemetry_source_time, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct body + body_content = self._serialize.body(telemetry, 'object') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ErrorResponseException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + send_telemetry.metadata = {'url': '/digitaltwins/{id}/telemetry'} + + def send_component_telemetry( + self, telemetry, id, component_path, message_id, telemetry_source_time=None, digital_twins_send_component_telemetry_options=None, custom_headers=None, raw=False, **operation_config): + """Sends telemetry on behalf of a component in a digital twin. + Status codes: + * 204 No Content + * 400 Bad Request + * InvalidArgument - The digital twin id, message id, or component path + is invalid. + * ValidationFailed - The telemetry content is invalid. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + * ComponentNotFound - The component path was not found. + + :param telemetry: The telemetry measurements to send from the digital + twin's component. + :type telemetry: object + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param component_path: The name of the DTDL component. + :type component_path: str + :param message_id: A unique message identifier (in the scope of the + digital twin id) that is commonly used for de-duplicating messages. + :type message_id: str + :param telemetry_source_time: An RFC 3339 timestamp that identifies + the time the telemetry was measured. + :type telemetry_source_time: str + :param digital_twins_send_component_telemetry_options: Additional + parameters for the operation + :type digital_twins_send_component_telemetry_options: + ~digitaltwins.models.DigitalTwinsSendComponentTelemetryOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_send_component_telemetry_options is not None: + traceparent = digital_twins_send_component_telemetry_options.traceparent + tracestate = None + if digital_twins_send_component_telemetry_options is not None: + tracestate = digital_twins_send_component_telemetry_options.tracestate + + # Construct URL + url = self.send_component_telemetry.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'componentPath': self._serialize.url("component_path", component_path, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + header_parameters['Message-Id'] = self._serialize.header("message_id", message_id, 'str') + if telemetry_source_time is not None: + header_parameters['Telemetry-Source-Time'] = self._serialize.header("telemetry_source_time", telemetry_source_time, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct body + body_content = self._serialize.body(telemetry, 'object') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ErrorResponseException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + send_component_telemetry.metadata = {'url': '/digitaltwins/{id}/components/{componentPath}/telemetry'} + + def get_component( + self, id, component_path, digital_twins_get_component_options=None, custom_headers=None, raw=False, **operation_config): + """Retrieves a component from a digital twin. + Status codes: + * 200 OK + * 400 Bad Request + * InvalidArgument - The digital twin id or component path is invalid. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + * ComponentNotFound - The component path was not found. + + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param component_path: The name of the DTDL component. + :type component_path: str + :param digital_twins_get_component_options: Additional parameters for + the operation + :type digital_twins_get_component_options: + ~digitaltwins.models.DigitalTwinsGetComponentOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_get_component_options is not None: + traceparent = digital_twins_get_component_options.traceparent + tracestate = None + if digital_twins_get_component_options is not None: + tracestate = digital_twins_get_component_options.tracestate + + # Construct URL + url = self.get_component.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'componentPath': self._serialize.url("component_path", component_path, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + deserialized = None + header_dict = {} + + if response.status_code == 200: + deserialized = self._deserialize('object', response) + header_dict = { + 'ETag': 'str', + } + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + client_raw_response.add_headers(header_dict) + return client_raw_response + + return deserialized + get_component.metadata = {'url': '/digitaltwins/{id}/components/{componentPath}'} + + def update_component( + self, patch_document, id, component_path, digital_twins_update_component_options=None, custom_headers=None, raw=False, **operation_config): + """Updates a component on a digital twin. + Status codes: + * 204 No Content + * 400 Bad Request + * InvalidArgument - The digital twin id, component path, or payload is + invalid. + * JsonPatchInvalid - The JSON Patch provided is invalid. + * ValidationFailed - Applying the patch results in an invalid digital + twin. + * 404 Not Found + * DigitalTwinNotFound - The digital twin was not found. + * 412 Precondition Failed + * PreconditionFailed - The precondition check (If-Match or + If-None-Match) failed. + + :param patch_document: An update specification described by JSON + Patch. Updates to property values and $model elements may happen in + the same request. Operations are limited to add, replace and remove. + :type patch_document: list[object] + :param id: The id of the digital twin. The id is unique within the + service and case sensitive. + :type id: str + :param component_path: The name of the DTDL component. + :type component_path: str + :param digital_twins_update_component_options: Additional parameters + for the operation + :type digital_twins_update_component_options: + ~digitaltwins.models.DigitalTwinsUpdateComponentOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if digital_twins_update_component_options is not None: + traceparent = digital_twins_update_component_options.traceparent + tracestate = None + if digital_twins_update_component_options is not None: + tracestate = digital_twins_update_component_options.tracestate + if_match = None + if digital_twins_update_component_options is not None: + if_match = digital_twins_update_component_options.if_match + + # Construct URL + url = self.update_component.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'componentPath': self._serialize.url("component_path", component_path, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Content-Type'] = 'application/json-patch+json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + + # Construct body + body_content = self._serialize.body(patch_document, '[object]') + + # Construct and send request + request = self._client.patch(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [202, 204]: + raise models.ErrorResponseException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + client_raw_response.add_headers({ + 'ETag': 'str', + }) + return client_raw_response + update_component.metadata = {'url': '/digitaltwins/{id}/components/{componentPath}'} diff --git a/azext_iot/sdk/digitaltwins/dataplane/operations/event_routes_operations.py b/azext_iot/sdk/digitaltwins/dataplane/operations/event_routes_operations.py new file mode 100644 index 000000000..83d6275cd --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/operations/event_routes_operations.py @@ -0,0 +1,352 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse + +from .. import models + + +class EventRoutesOperations(object): + """EventRoutesOperations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: The API version to use for the request. Constant value: "2020-10-31". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-10-31" + + self.config = config + + def list( + self, event_routes_list_options=None, custom_headers=None, raw=False, **operation_config): + """Retrieves all event routes. + Status codes: + * 200 OK. + + :param event_routes_list_options: Additional parameters for the + operation + :type event_routes_list_options: + ~digitaltwins.models.EventRoutesListOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: An iterator like instance of EventRoute + :rtype: + ~digitaltwins.models.EventRoutePaged[~digitaltwins.models.EventRoute] + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if event_routes_list_options is not None: + traceparent = event_routes_list_options.traceparent + tracestate = None + if event_routes_list_options is not None: + tracestate = event_routes_list_options.tracestate + max_items_per_page = None + if event_routes_list_options is not None: + max_items_per_page = event_routes_list_options.max_items_per_page + + def internal_paging(next_link=None, raw=False): + + if not next_link: + # Construct URL + url = self.list.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + else: + url = next_link + query_parameters = {} + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + if max_items_per_page is not None: + header_parameters['max-items-per-page'] = self._serialize.header("max_items_per_page", max_items_per_page, 'int') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + return response + + # Deserialize response + deserialized = models.EventRoutePaged(internal_paging, self._deserialize.dependencies) + + if raw: + header_dict = {} + client_raw_response = models.EventRoutePaged(internal_paging, self._deserialize.dependencies, header_dict) + return client_raw_response + + return deserialized + list.metadata = {'url': '/eventroutes'} + + def get_by_id( + self, id, event_routes_get_by_id_options=None, custom_headers=None, raw=False, **operation_config): + """Retrieves an event route. + Status codes: + * 200 OK + * 404 Not Found + * EventRouteNotFound - The event route was not found. + + :param id: The id for an event route. The id is unique within event + routes and case sensitive. + :type id: str + :param event_routes_get_by_id_options: Additional parameters for the + operation + :type event_routes_get_by_id_options: + ~digitaltwins.models.EventRoutesGetByIdOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: EventRoute or ClientRawResponse if raw=true + :rtype: ~digitaltwins.models.EventRoute or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if event_routes_get_by_id_options is not None: + traceparent = event_routes_get_by_id_options.traceparent + tracestate = None + if event_routes_get_by_id_options is not None: + tracestate = event_routes_get_by_id_options.tracestate + + # Construct URL + url = self.get_by_id.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('EventRoute', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_by_id.metadata = {'url': '/eventroutes/{id}'} + + def add( + self, id, endpoint_name, filter, event_routes_add_options=None, custom_headers=None, raw=False, **operation_config): + """Adds or replaces an event route. + Status codes: + * 204 No Content + * 400 Bad Request + * EventRouteEndpointInvalid - The endpoint provided does not exist or + is not active. + * EventRouteFilterInvalid - The event route filter is invalid. + * EventRouteIdInvalid - The event route id is invalid. + * LimitExceeded - The maximum number of event routes allowed has been + reached. + + :param id: The id for an event route. The id is unique within event + routes and case sensitive. + :type id: str + :param endpoint_name: The name of the endpoint this event route is + bound to. + :type endpoint_name: str + :param filter: An expression which describes the events which are + routed to the endpoint. + :type filter: str + :param event_routes_add_options: Additional parameters for the + operation + :type event_routes_add_options: + ~digitaltwins.models.EventRoutesAddOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if event_routes_add_options is not None: + traceparent = event_routes_add_options.traceparent + tracestate = None + if event_routes_add_options is not None: + tracestate = event_routes_add_options.tracestate + event_route = None + if endpoint_name is not None or filter is not None: + event_route = models.EventRoute(endpoint_name=endpoint_name, filter=filter) + + # Construct URL + url = self.add.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct body + if event_route is not None: + body_content = self._serialize.body(event_route, 'EventRoute') + else: + body_content = None + + # Construct and send request + request = self._client.put(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ErrorResponseException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + add.metadata = {'url': '/eventroutes/{id}'} + + def delete( + self, id, event_routes_delete_options=None, custom_headers=None, raw=False, **operation_config): + """Deletes an event route. + Status codes: + * 204 No Content + * 404 Not Found + * EventRouteNotFound - The event route was not found. + + :param id: The id for an event route. The id is unique within event + routes and case sensitive. + :type id: str + :param event_routes_delete_options: Additional parameters for the + operation + :type event_routes_delete_options: + ~digitaltwins.models.EventRoutesDeleteOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if event_routes_delete_options is not None: + traceparent = event_routes_delete_options.traceparent + tracestate = None + if event_routes_delete_options is not None: + tracestate = event_routes_delete_options.tracestate + + # Construct URL + url = self.delete.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ErrorResponseException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + delete.metadata = {'url': '/eventroutes/{id}'} diff --git a/azext_iot/sdk/digitaltwins/dataplane/operations/query_operations.py b/azext_iot/sdk/digitaltwins/dataplane/operations/query_operations.py new file mode 100644 index 000000000..5da512a4b --- /dev/null +++ b/azext_iot/sdk/digitaltwins/dataplane/operations/query_operations.py @@ -0,0 +1,132 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse + +from .. import models + + +class QueryOperations(object): + """QueryOperations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: The API version to use for the request. Constant value: "2020-10-31". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-10-31" + + self.config = config + + def query_twins( + self, query=None, continuation_token=None, query_query_twins_options=None, custom_headers=None, raw=False, **operation_config): + """Executes a query that allows traversing relationships and filtering by + property values. + Status codes: + * 200 OK + * 400 Bad Request + * BadRequest - The continuation token is invalid. + * SqlQueryError - The query contains some errors. + * 429 Too Many Requests + * QuotaReachedError - The maximum query rate limit has been reached. + + :param query: The query to execute. This value is ignored if a + continuation token is provided. + :type query: str + :param continuation_token: A token which is used to retrieve the next + set of results from a previous query. + :type continuation_token: str + :param query_query_twins_options: Additional parameters for the + operation + :type query_query_twins_options: + ~digitaltwins.models.QueryQueryTwinsOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: QueryResult or ClientRawResponse if raw=true + :rtype: ~digitaltwins.models.QueryResult or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ErrorResponseException` + """ + traceparent = None + if query_query_twins_options is not None: + traceparent = query_query_twins_options.traceparent + tracestate = None + if query_query_twins_options is not None: + tracestate = query_query_twins_options.tracestate + max_items_per_page = None + if query_query_twins_options is not None: + max_items_per_page = query_query_twins_options.max_items_per_page + query_specification = models.QuerySpecification(query=query, continuation_token=continuation_token) + + # Construct URL + url = self.query_twins.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + if traceparent is not None: + header_parameters['traceparent'] = self._serialize.header("traceparent", traceparent, 'str') + if tracestate is not None: + header_parameters['tracestate'] = self._serialize.header("tracestate", tracestate, 'str') + if max_items_per_page is not None: + header_parameters['max-items-per-page'] = self._serialize.header("max_items_per_page", max_items_per_page, 'int') + + # Construct body + body_content = self._serialize.body(query_specification, 'QuerySpecification') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ErrorResponseException(self._deserialize, response) + + deserialized = None + header_dict = {} + + if response.status_code == 200: + deserialized = self._deserialize('QueryResult', response) + header_dict = { + 'query-charge': 'float', + } + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + client_raw_response.add_headers(header_dict) + return client_raw_response + + return deserialized + query_twins.metadata = {'url': '/query'} diff --git a/azext_iot/sdk/service/version.py b/azext_iot/sdk/digitaltwins/dataplane/version.py similarity index 93% rename from azext_iot/sdk/service/version.py rename to azext_iot/sdk/digitaltwins/dataplane/version.py index 729e580c0..ac2482d2d 100644 --- a/azext_iot/sdk/service/version.py +++ b/azext_iot/sdk/digitaltwins/dataplane/version.py @@ -9,4 +9,5 @@ # regenerated. # -------------------------------------------------------------------------- -VERSION = "2019-07-01-preview" +VERSION = "2020-10-31" + diff --git a/azext_iot/sdk/dps/__init__.py b/azext_iot/sdk/dps/__init__.py index 7cc65f931..55614acbf 100644 --- a/azext_iot/sdk/dps/__init__.py +++ b/azext_iot/sdk/dps/__init__.py @@ -1,14 +1,5 @@ # coding=utf-8 -# -------------------------------------------------------------------------- -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from .provisioning_service_client import ProvisioningServiceClient -from .version import VERSION - -__all__ = ['ProvisioningServiceClient'] - -__version__ = VERSION - +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/azext_iot/sdk/dps/models/__init__.py b/azext_iot/sdk/dps/models/__init__.py deleted file mode 100644 index 0c8326d2e..000000000 --- a/azext_iot/sdk/dps/models/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from .provisioning_service_error_details import ProvisioningServiceErrorDetails, ProvisioningServiceErrorDetailsException -from .device_capabilities import DeviceCapabilities -from .individual_enrollment_registration_state import IndividualEnrollmentRegistrationState -from .tpm_attestation import TpmAttestation -from .x509_certificate_info import X509CertificateInfo -from .x509_certificate_with_info import X509CertificateWithInfo -from .x509_certificates import X509Certificates -from .x509_ca_references import X509CAReferences -from .x509_attestation import X509Attestation -from .symmetric_key_attestation import SymmetricKeyAttestation -from .attestation_mechanism import AttestationMechanism -from .metadata import Metadata -from .twin_collection import TwinCollection -from .initial_twin_properties import InitialTwinProperties -from .initial_twin import InitialTwin -from .reprovision_policy import ReprovisionPolicy -from .custom_allocation_definition import CustomAllocationDefinition -from .individual_enrollment import IndividualEnrollment -from .device_registration_state import DeviceRegistrationState -from .enrollment_group import EnrollmentGroup -from .bulk_enrollment_operation import BulkEnrollmentOperation -from .bulk_enrollment_operation_error import BulkEnrollmentOperationError -from .bulk_enrollment_operation_result import BulkEnrollmentOperationResult -from .query_specification import QuerySpecification - -__all__ = [ - 'ProvisioningServiceErrorDetails', 'ProvisioningServiceErrorDetailsException', - 'DeviceCapabilities', - 'IndividualEnrollmentRegistrationState', - 'TpmAttestation', - 'X509CertificateInfo', - 'X509CertificateWithInfo', - 'X509Certificates', - 'X509CAReferences', - 'X509Attestation', - 'SymmetricKeyAttestation', - 'AttestationMechanism', - 'Metadata', - 'TwinCollection', - 'InitialTwinProperties', - 'InitialTwin', - 'ReprovisionPolicy', - 'CustomAllocationDefinition', - 'IndividualEnrollment', - 'DeviceRegistrationState', - 'EnrollmentGroup', - 'BulkEnrollmentOperation', - 'BulkEnrollmentOperationError', - 'BulkEnrollmentOperationResult', - 'QuerySpecification', -] diff --git a/azext_iot/sdk/dps/operations/__init__.py b/azext_iot/sdk/dps/operations/__init__.py deleted file mode 100644 index 54916bd89..000000000 --- a/azext_iot/sdk/dps/operations/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from .device_enrollment_operations import DeviceEnrollmentOperations -from .device_enrollment_group_operations import DeviceEnrollmentGroupOperations -from .registration_state_operations import RegistrationStateOperations - -__all__ = [ - 'DeviceEnrollmentOperations', - 'DeviceEnrollmentGroupOperations', - 'RegistrationStateOperations', -] diff --git a/azext_iot/sdk/dps/operations/device_enrollment_group_operations.py b/azext_iot/sdk/dps/operations/device_enrollment_group_operations.py deleted file mode 100644 index 2ada50461..000000000 --- a/azext_iot/sdk/dps/operations/device_enrollment_group_operations.py +++ /dev/null @@ -1,347 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- -import uuid -from msrest.pipeline import ClientRawResponse - -from .. import models - - -class DeviceEnrollmentGroupOperations(object): - """DeviceEnrollmentGroupOperations operations. - - :param client: Client for service requests. - :param config: Configuration of service client. - :param serializer: An object model serializer. - :param deserializer: An object model deserializer. - :ivar api_version: The API version to use for the request. Supported versions include: 2018-09-01-preview. Constant value: "2018-09-01-preview". - """ - - models = models - - def __init__(self, client, config, serializer, deserializer): - - self._client = client - self._serialize = serializer - self._deserialize = deserializer - self.api_version = "2019-03-31" - - self.config = config - - def get( - self, id, custom_headers=None, raw=False, **operation_config): - """Get a device enrollment group. - - :param id: Enrollment group ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: EnrollmentGroup or ClientRawResponse if raw=true - :rtype: - ~microsoft.azure.management.provisioningservices.models.EnrollmentGroup - or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.get.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('EnrollmentGroup', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get.metadata = {'url': '/enrollmentGroups/{id}'} - - def create_or_update( - self, id, enrollment_group, if_match=None, custom_headers=None, raw=False, **operation_config): - """Create or update a device enrollment group. - - :param id: Enrollment group ID. - :type id: str - :param enrollment_group: The device enrollment group. - :type enrollment_group: - ~microsoft.azure.management.provisioningservices.models.EnrollmentGroup - :param if_match: The ETag of the enrollment record. - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: EnrollmentGroup or ClientRawResponse if raw=true - :rtype: - ~microsoft.azure.management.provisioningservices.models.EnrollmentGroup - or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.create_or_update.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(enrollment_group, 'EnrollmentGroup') - - # Construct and send request - request = self._client.put(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('EnrollmentGroup', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - create_or_update.metadata = {'url': '/enrollmentGroups/{id}'} - - def delete( - self, id, if_match=None, custom_headers=None, raw=False, **operation_config): - """Delete a device enrollment group. - - :param id: Enrollment group ID. - :type id: str - :param if_match: The ETag of the enrollment group record. - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: None or ClientRawResponse if raw=true - :rtype: None or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.delete.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [204]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - if raw: - client_raw_response = ClientRawResponse(None, response) - return client_raw_response - delete.metadata = {'url': '/enrollmentGroups/{id}'} - - def query( - self, query_specification, x_ms_max_item_count=None, x_ms_continuation=None, custom_headers=None, raw=False, **operation_config): - """Query the device enrollment groups. - - :param query_specification: The query specification. - :type query_specification: - ~microsoft.azure.management.provisioningservices.models.QuerySpecification - :param x_ms_max_item_count: pageSize - :type x_ms_max_item_count: int - :param x_ms_continuation: continuation token - :type x_ms_continuation: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: list or ClientRawResponse if raw=true - :rtype: - list[~microsoft.azure.management.provisioningservices.models.EnrollmentGroup] - or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.query.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if x_ms_max_item_count is not None: - header_parameters['x-ms-max-item-count'] = self._serialize.header("x_ms_max_item_count", x_ms_max_item_count, 'int') - if x_ms_continuation is not None: - header_parameters['x-ms-continuation'] = self._serialize.header("x_ms_continuation", x_ms_continuation, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(query_specification, 'QuerySpecification') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - deserialized = None - header_dict = {} - - if response.status_code == 200: - deserialized = self._deserialize('[EnrollmentGroup]', response) - header_dict = { - 'x-ms-continuation': 'str', - 'x-ms-max-item-count': 'int', - 'x-ms-item-type': 'str', - } - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - client_raw_response.add_headers(header_dict) - return client_raw_response - - # Added Custom - continuation = response.headers.get('x-ms-continuation') - - return deserialized, continuation - query.metadata = {'url': '/enrollmentGroups/query'} - - def attestation_mechanism_method( - self, id, custom_headers=None, raw=False, **operation_config): - """Get the attestation mechanism in the device enrollment group record. - - :param id: Enrollment group ID - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: AttestationMechanism or ClientRawResponse if raw=true - :rtype: - ~microsoft.azure.management.provisioningservices.models.AttestationMechanism - or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.attestation_mechanism_method.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('AttestationMechanism', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - attestation_mechanism_method.metadata = {'url': '/enrollmentGroups/{id}/attestationmechanism'} diff --git a/azext_iot/sdk/dps/operations/device_enrollment_operations.py b/azext_iot/sdk/dps/operations/device_enrollment_operations.py deleted file mode 100644 index 277d7c626..000000000 --- a/azext_iot/sdk/dps/operations/device_enrollment_operations.py +++ /dev/null @@ -1,409 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -import uuid -from msrest.pipeline import ClientRawResponse - -from .. import models - - -class DeviceEnrollmentOperations(object): - """DeviceEnrollmentOperations operations. - - :param client: Client for service requests. - :param config: Configuration of service client. - :param serializer: An object model serializer. - :param deserializer: An object model deserializer. - :ivar api_version: The API version to use for the request. Supported versions include: 2018-09-01-preview. Constant value: "2018-09-01-preview". - """ - - models = models - - def __init__(self, client, config, serializer, deserializer): - - self._client = client - self._serialize = serializer - self._deserialize = deserializer - self.api_version = "2019-03-31" - - self.config = config - - def get( - self, id, custom_headers=None, raw=False, **operation_config): - """Get a device enrollment record. - - :param id: Registration ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: IndividualEnrollment or ClientRawResponse if raw=true - :rtype: - ~microsoft.azure.management.provisioningservices.models.IndividualEnrollment - or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.get.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('IndividualEnrollment', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get.metadata = {'url': '/enrollments/{id}'} - - def create_or_update( - self, id, enrollment, if_match=None, custom_headers=None, raw=False, **operation_config): - """Create or update a device enrollment record. - - :param id: The registration ID is alphanumeric, lowercase, and may - contain hyphens. - :type id: str - :param enrollment: The device enrollment record. - :type enrollment: - ~microsoft.azure.management.provisioningservices.models.IndividualEnrollment - :param if_match: The ETag of the enrollment record. - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: IndividualEnrollment or ClientRawResponse if raw=true - :rtype: - ~microsoft.azure.management.provisioningservices.models.IndividualEnrollment - or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.create_or_update.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(enrollment, 'IndividualEnrollment') - - # Construct and send request - request = self._client.put(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('IndividualEnrollment', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - create_or_update.metadata = {'url': '/enrollments/{id}'} - - def delete( - self, id, if_match=None, custom_headers=None, raw=False, **operation_config): - """Delete a device enrollment record. - - :param id: Registration ID. - :type id: str - :param if_match: The ETag of the enrollment record. - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: None or ClientRawResponse if raw=true - :rtype: None or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.delete.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [204]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - if raw: - client_raw_response = ClientRawResponse(None, response) - return client_raw_response - delete.metadata = {'url': '/enrollments/{id}'} - - def bulk_operation( - self, bulk_operation, custom_headers=None, raw=False, **operation_config): - """Bulk device enrollment operation. - - :param bulk_operation: Bulk operation. - :type bulk_operation: - ~microsoft.azure.management.provisioningservices.models.BulkEnrollmentOperation - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: BulkEnrollmentOperationResult or ClientRawResponse if - raw=true - :rtype: - ~microsoft.azure.management.provisioningservices.models.BulkEnrollmentOperationResult - or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.bulk_operation.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(bulk_operation, 'BulkEnrollmentOperation') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('BulkEnrollmentOperationResult', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - bulk_operation.metadata = {'url': '/enrollments'} - - def query( - self, query_specification, x_ms_max_item_count=None, x_ms_continuation=None, custom_headers=None, raw=False, **operation_config): - """Query the device enrollment records. - - :param query_specification: The query specification. - :type query_specification: - ~microsoft.azure.management.provisioningservices.models.QuerySpecification - :param x_ms_max_item_count: pageSize - :type x_ms_max_item_count: int - :param x_ms_continuation: continuation token - :type x_ms_continuation: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: list or ClientRawResponse if raw=true - :rtype: - list[~microsoft.azure.management.provisioningservices.models.IndividualEnrollment] - or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.query.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if x_ms_max_item_count is not None: - header_parameters['x-ms-max-item-count'] = self._serialize.header("x_ms_max_item_count", x_ms_max_item_count, 'int') - if x_ms_continuation is not None: - header_parameters['x-ms-continuation'] = self._serialize.header("x_ms_continuation", x_ms_continuation, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(query_specification, 'QuerySpecification') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - deserialized = None - header_dict = {} - - if response.status_code == 200: - deserialized = self._deserialize('[IndividualEnrollment]', response) - header_dict = { - 'x-ms-continuation': 'str', - 'x-ms-max-item-count': 'int', - 'x-ms-item-type': 'str', - } - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - client_raw_response.add_headers(header_dict) - return client_raw_response - - # Added Custom - continuation = response.headers.get('x-ms-continuation') - - return deserialized, continuation - query.metadata = {'url': '/enrollments/query'} - - def attestation_mechanism_method( - self, id, custom_headers=None, raw=False, **operation_config): - """Get the attestation mechanism in the device enrollment record. - - :param id: Registration ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: AttestationMechanism or ClientRawResponse if raw=true - :rtype: - ~microsoft.azure.management.provisioningservices.models.AttestationMechanism - or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.attestation_mechanism_method.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('AttestationMechanism', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - attestation_mechanism_method.metadata = {'url': '/enrollments/{id}/attestationmechanism'} diff --git a/azext_iot/sdk/dps/operations/registration_state_operations.py b/azext_iot/sdk/dps/operations/registration_state_operations.py deleted file mode 100644 index c74ecca7e..000000000 --- a/azext_iot/sdk/dps/operations/registration_state_operations.py +++ /dev/null @@ -1,202 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -import uuid -from msrest.pipeline import ClientRawResponse - -from .. import models - - -class RegistrationStateOperations(object): - """RegistrationStateOperations operations. - - :param client: Client for service requests. - :param config: Configuration of service client. - :param serializer: An object model serializer. - :param deserializer: An object model deserializer. - :ivar api_version: The API version to use for the request. Supported versions include: 2018-09-01-preview. Constant value: "2018-09-01-preview". - """ - - models = models - - def __init__(self, client, config, serializer, deserializer): - - self._client = client - self._serialize = serializer - self._deserialize = deserializer - self.api_version = "2019-03-31" - - self.config = config - - def get_registration_state( - self, id, custom_headers=None, raw=False, **operation_config): - """Gets the device registration state. - - :param id: Registration ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: DeviceRegistrationState or ClientRawResponse if raw=true - :rtype: - ~microsoft.azure.management.provisioningservices.models.DeviceRegistrationState - or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.get_registration_state.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('DeviceRegistrationState', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_registration_state.metadata = {'url': '/registrations/{id}'} - - def delete_registration_state( - self, id, if_match=None, custom_headers=None, raw=False, **operation_config): - """Deletes the device registration. - - :param id: Registration ID. - :type id: str - :param if_match: The ETag of the registration status record. - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: None or ClientRawResponse if raw=true - :rtype: None or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.delete_registration_state.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [204]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - if raw: - client_raw_response = ClientRawResponse(None, response) - return client_raw_response - delete_registration_state.metadata = {'url': '/registrations/{id}'} - - def query_registration_state( - self, id, custom_headers=None, raw=False, **operation_config): - """Gets the registration state of devices in this enrollmentGroup. - - :param id: Enrollment group ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: list or ClientRawResponse if raw=true - :rtype: - list[~microsoft.azure.management.provisioningservices.models.DeviceRegistrationState] - or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`ProvisioningServiceErrorDetailsException` - """ - # Construct URL - url = self.query_registration_state.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('[DeviceRegistrationState]', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - query_registration_state.metadata = {'url': '/registrations/{id}/query'} diff --git a/azext_iot/sdk/dps/provisioning_service_client.py b/azext_iot/sdk/dps/provisioning_service_client.py deleted file mode 100644 index ffeae8354..000000000 --- a/azext_iot/sdk/dps/provisioning_service_client.py +++ /dev/null @@ -1,80 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.service_client import ServiceClient -from msrest import Serializer, Deserializer -from msrestazure import AzureConfiguration -from .version import VERSION -from .operations.device_enrollment_operations import DeviceEnrollmentOperations -from .operations.device_enrollment_group_operations import DeviceEnrollmentGroupOperations -from .operations.registration_state_operations import RegistrationStateOperations -from . import models -from azext_iot.constants import USER_AGENT - - -class ProvisioningServiceClientConfiguration(AzureConfiguration): - """Configuration for ProvisioningServiceClient - Note that all parameters used to create this instance are saved as instance - attributes. - - :param credentials: Credentials needed for the client to connect to Azure. - :type credentials: :mod:`A msrestazure Credentials - object` - :param str base_url: Service URL - """ - - def __init__( - self, credentials, base_url=None): - - if credentials is None: - raise ValueError("Parameter 'credentials' must not be None.") - if not base_url: - base_url = 'https://localhost' - - super(ProvisioningServiceClientConfiguration, self).__init__(base_url) - self.add_user_agent('provisioningserviceclient/{}'.format(VERSION)) - self.add_user_agent(USER_AGENT) - - self.credentials = credentials - - -class ProvisioningServiceClient(object): - """API for service operations with the Azure IoT Hub Device Provisioning Service - - :ivar config: Configuration for client. - :vartype config: ProvisioningServiceClientConfiguration - - :ivar device_enrollment: DeviceEnrollment operations - :vartype device_enrollment: microsoft.azure.management.provisioningservices.operations.DeviceEnrollmentOperations - :ivar device_enrollment_group: DeviceEnrollmentGroup operations - :vartype device_enrollment_group: microsoft.azure.management.provisioningservices.operations.DeviceEnrollmentGroupOperations - :ivar registration_state: RegistrationState operations - :vartype registration_state: microsoft.azure.management.provisioningservices.operations.RegistrationStateOperations - - :param credentials: Credentials needed for the client to connect to Azure. - :type credentials: :mod:`A msrestazure Credentials - object` - :param str base_url: Service URL - """ - - def __init__( - self, credentials, base_url=None): - - self.config = ProvisioningServiceClientConfiguration(credentials, base_url) - self._client = ServiceClient(self.config.credentials, self.config) - - client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} - self.api_version = '2019-03-31' # @digimaun - self._serialize = Serializer(client_models) - self._deserialize = Deserializer(client_models) - - self.device_enrollment = DeviceEnrollmentOperations( - self._client, self.config, self._serialize, self._deserialize) - self.device_enrollment_group = DeviceEnrollmentGroupOperations( - self._client, self.config, self._serialize, self._deserialize) - self.registration_state = RegistrationStateOperations( - self._client, self.config, self._serialize, self._deserialize) diff --git a/azext_iot/sdk/service/__init__.py b/azext_iot/sdk/dps/service/__init__.py similarity index 73% rename from azext_iot/sdk/service/__init__.py rename to azext_iot/sdk/dps/service/__init__.py index 8c7711c56..a2cf0b44c 100644 --- a/azext_iot/sdk/service/__init__.py +++ b/azext_iot/sdk/dps/service/__init__.py @@ -4,15 +4,15 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -# Code generated by Microsoft (R) AutoRest Code Generator 2.3.33.0 +# Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. # -------------------------------------------------------------------------- -from .iot_hub_gateway_service_apis import IotHubGatewayServiceAPIs +from .provisioning_service_client import ProvisioningServiceClient from .version import VERSION -__all__ = ['IotHubGatewayServiceAPIs'] +__all__ = ['ProvisioningServiceClient'] __version__ = VERSION diff --git a/azext_iot/sdk/dps/service/models/__init__.py b/azext_iot/sdk/dps/service/models/__init__.py new file mode 100644 index 000000000..96dff47da --- /dev/null +++ b/azext_iot/sdk/dps/service/models/__init__.py @@ -0,0 +1,94 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +try: + from .device_registration_state_py3 import DeviceRegistrationState + from .tpm_attestation_py3 import TpmAttestation + from .x509_certificate_info_py3 import X509CertificateInfo + from .x509_certificate_with_info_py3 import X509CertificateWithInfo + from .x509_certificates_py3 import X509Certificates + from .x509_ca_references_py3 import X509CAReferences + from .x509_attestation_py3 import X509Attestation + from .symmetric_key_attestation_py3 import SymmetricKeyAttestation + from .attestation_mechanism_py3 import AttestationMechanism + from .device_capabilities_py3 import DeviceCapabilities + from .metadata_py3 import Metadata + from .twin_collection_py3 import TwinCollection + from .initial_twin_properties_py3 import InitialTwinProperties + from .initial_twin_py3 import InitialTwin + from .reprovision_policy_py3 import ReprovisionPolicy + from .custom_allocation_definition_py3 import CustomAllocationDefinition + from .individual_enrollment_py3 import IndividualEnrollment + from .enrollment_group_py3 import EnrollmentGroup + from .provisioning_service_error_details_py3 import ProvisioningServiceErrorDetails, ProvisioningServiceErrorDetailsException + from .query_specification_py3 import QuerySpecification + from .bulk_enrollment_operation_py3 import BulkEnrollmentOperation + from .bulk_enrollment_operation_error_py3 import BulkEnrollmentOperationError + from .bulk_enrollment_operation_result_py3 import BulkEnrollmentOperationResult + from .bulk_enrollment_group_operation_py3 import BulkEnrollmentGroupOperation + from .bulk_enrollment_group_operation_error_py3 import BulkEnrollmentGroupOperationError + from .bulk_enrollment_group_operation_result_py3 import BulkEnrollmentGroupOperationResult +except (SyntaxError, ImportError): + from .device_registration_state import DeviceRegistrationState + from .tpm_attestation import TpmAttestation + from .x509_certificate_info import X509CertificateInfo + from .x509_certificate_with_info import X509CertificateWithInfo + from .x509_certificates import X509Certificates + from .x509_ca_references import X509CAReferences + from .x509_attestation import X509Attestation + from .symmetric_key_attestation import SymmetricKeyAttestation + from .attestation_mechanism import AttestationMechanism + from .device_capabilities import DeviceCapabilities + from .metadata import Metadata + from .twin_collection import TwinCollection + from .initial_twin_properties import InitialTwinProperties + from .initial_twin import InitialTwin + from .reprovision_policy import ReprovisionPolicy + from .custom_allocation_definition import CustomAllocationDefinition + from .individual_enrollment import IndividualEnrollment + from .enrollment_group import EnrollmentGroup + from .provisioning_service_error_details import ProvisioningServiceErrorDetails, ProvisioningServiceErrorDetailsException + from .query_specification import QuerySpecification + from .bulk_enrollment_operation import BulkEnrollmentOperation + from .bulk_enrollment_operation_error import BulkEnrollmentOperationError + from .bulk_enrollment_operation_result import BulkEnrollmentOperationResult + from .bulk_enrollment_group_operation import BulkEnrollmentGroupOperation + from .bulk_enrollment_group_operation_error import BulkEnrollmentGroupOperationError + from .bulk_enrollment_group_operation_result import BulkEnrollmentGroupOperationResult + +__all__ = [ + 'DeviceRegistrationState', + 'TpmAttestation', + 'X509CertificateInfo', + 'X509CertificateWithInfo', + 'X509Certificates', + 'X509CAReferences', + 'X509Attestation', + 'SymmetricKeyAttestation', + 'AttestationMechanism', + 'DeviceCapabilities', + 'Metadata', + 'TwinCollection', + 'InitialTwinProperties', + 'InitialTwin', + 'ReprovisionPolicy', + 'CustomAllocationDefinition', + 'IndividualEnrollment', + 'EnrollmentGroup', + 'ProvisioningServiceErrorDetails', 'ProvisioningServiceErrorDetailsException', + 'QuerySpecification', + 'BulkEnrollmentOperation', + 'BulkEnrollmentOperationError', + 'BulkEnrollmentOperationResult', + 'BulkEnrollmentGroupOperation', + 'BulkEnrollmentGroupOperationError', + 'BulkEnrollmentGroupOperationResult', +] diff --git a/azext_iot/sdk/dps/service/models/attestation_mechanism.py b/azext_iot/sdk/dps/service/models/attestation_mechanism.py new file mode 100644 index 000000000..d31e8ab0e --- /dev/null +++ b/azext_iot/sdk/dps/service/models/attestation_mechanism.py @@ -0,0 +1,47 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class AttestationMechanism(Model): + """Attestation mechanism for individualEnrollment as well as enrollmentGroup. + + All required parameters must be populated in order to send to Azure. + + :param type: Required. Attestation Type. Possible values include: 'none', + 'tpm', 'x509', 'symmetricKey' + :type type: str or ~dps.models.enum + :param tpm: TPM attestation method. + :type tpm: ~dps.models.TpmAttestation + :param x509: X509 attestation method. + :type x509: ~dps.models.X509Attestation + :param symmetric_key: Symmetric Key attestation method. + :type symmetric_key: ~dps.models.SymmetricKeyAttestation + """ + + _validation = { + 'type': {'required': True}, + } + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'tpm': {'key': 'tpm', 'type': 'TpmAttestation'}, + 'x509': {'key': 'x509', 'type': 'X509Attestation'}, + 'symmetric_key': {'key': 'symmetricKey', 'type': 'SymmetricKeyAttestation'}, + } + + def __init__(self, **kwargs): + super(AttestationMechanism, self).__init__(**kwargs) + self.type = kwargs.get('type', None) + self.tpm = kwargs.get('tpm', None) + self.x509 = kwargs.get('x509', None) + self.symmetric_key = kwargs.get('symmetric_key', None) diff --git a/azext_iot/sdk/dps/models/attestation_mechanism.py b/azext_iot/sdk/dps/service/models/attestation_mechanism_py3.py similarity index 61% rename from azext_iot/sdk/dps/models/attestation_mechanism.py rename to azext_iot/sdk/dps/service/models/attestation_mechanism_py3.py index dca688476..299119c80 100644 --- a/azext_iot/sdk/dps/models/attestation_mechanism.py +++ b/azext_iot/sdk/dps/service/models/attestation_mechanism_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -11,19 +15,17 @@ class AttestationMechanism(Model): """Attestation mechanism for individualEnrollment as well as enrollmentGroup. - :param type: Attestation Type. Possible values include: 'none', 'tpm', - 'x509', 'symmetricKey' - :type type: str or - ~microsoft.azure.management.provisioningservices.models.enum + All required parameters must be populated in order to send to Azure. + + :param type: Required. Attestation Type. Possible values include: 'none', + 'tpm', 'x509', 'symmetricKey' + :type type: str or ~dps.models.enum :param tpm: TPM attestation method. - :type tpm: - ~microsoft.azure.management.provisioningservices.models.TpmAttestation + :type tpm: ~dps.models.TpmAttestation :param x509: X509 attestation method. - :type x509: - ~microsoft.azure.management.provisioningservices.models.X509Attestation + :type x509: ~dps.models.X509Attestation :param symmetric_key: Symmetric Key attestation method. - :type symmetric_key: - ~microsoft.azure.management.provisioningservices.models.SymmetricKeyAttestation + :type symmetric_key: ~dps.models.SymmetricKeyAttestation """ _validation = { @@ -37,8 +39,8 @@ class AttestationMechanism(Model): 'symmetric_key': {'key': 'symmetricKey', 'type': 'SymmetricKeyAttestation'}, } - def __init__(self, type, tpm=None, x509=None, symmetric_key=None): - super(AttestationMechanism, self).__init__() + def __init__(self, *, type, tpm=None, x509=None, symmetric_key=None, **kwargs) -> None: + super(AttestationMechanism, self).__init__(**kwargs) self.type = type self.tpm = tpm self.x509 = x509 diff --git a/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation.py new file mode 100644 index 000000000..70279cdfb --- /dev/null +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation.py @@ -0,0 +1,40 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class BulkEnrollmentGroupOperation(Model): + """Bulk enrollment operation. + + All required parameters must be populated in order to send to Azure. + + :param enrollment_groups: Required. Enrollment items + :type enrollment_groups: list[~dps.models.EnrollmentGroup] + :param mode: Required. Operation mode. Possible values include: 'create', + 'update', 'updateIfMatchETag', 'delete' + :type mode: str or ~dps.models.enum + """ + + _validation = { + 'enrollment_groups': {'required': True}, + 'mode': {'required': True}, + } + + _attribute_map = { + 'enrollment_groups': {'key': 'enrollmentGroups', 'type': '[EnrollmentGroup]'}, + 'mode': {'key': 'mode', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(BulkEnrollmentGroupOperation, self).__init__(**kwargs) + self.enrollment_groups = kwargs.get('enrollment_groups', None) + self.mode = kwargs.get('mode', None) diff --git a/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_error.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_error.py new file mode 100644 index 000000000..11c583c4d --- /dev/null +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_error.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class BulkEnrollmentGroupOperationError(Model): + """Bulk enrollment operation error. + + All required parameters must be populated in order to send to Azure. + + :param enrollment_group_id: Required. Enrollment group id. + :type enrollment_group_id: str + :param error_code: Required. Error code + :type error_code: int + :param error_status: Required. Error status. + :type error_status: str + """ + + _validation = { + 'enrollment_group_id': {'required': True}, + 'error_code': {'required': True}, + 'error_status': {'required': True}, + } + + _attribute_map = { + 'enrollment_group_id': {'key': 'enrollmentGroupId', 'type': 'str'}, + 'error_code': {'key': 'errorCode', 'type': 'int'}, + 'error_status': {'key': 'errorStatus', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(BulkEnrollmentGroupOperationError, self).__init__(**kwargs) + self.enrollment_group_id = kwargs.get('enrollment_group_id', None) + self.error_code = kwargs.get('error_code', None) + self.error_status = kwargs.get('error_status', None) diff --git a/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_error_py3.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_error_py3.py new file mode 100644 index 000000000..09904f613 --- /dev/null +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_error_py3.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class BulkEnrollmentGroupOperationError(Model): + """Bulk enrollment operation error. + + All required parameters must be populated in order to send to Azure. + + :param enrollment_group_id: Required. Enrollment group id. + :type enrollment_group_id: str + :param error_code: Required. Error code + :type error_code: int + :param error_status: Required. Error status. + :type error_status: str + """ + + _validation = { + 'enrollment_group_id': {'required': True}, + 'error_code': {'required': True}, + 'error_status': {'required': True}, + } + + _attribute_map = { + 'enrollment_group_id': {'key': 'enrollmentGroupId', 'type': 'str'}, + 'error_code': {'key': 'errorCode', 'type': 'int'}, + 'error_status': {'key': 'errorStatus', 'type': 'str'}, + } + + def __init__(self, *, enrollment_group_id: str, error_code: int, error_status: str, **kwargs) -> None: + super(BulkEnrollmentGroupOperationError, self).__init__(**kwargs) + self.enrollment_group_id = enrollment_group_id + self.error_code = error_code + self.error_status = error_status diff --git a/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_py3.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_py3.py new file mode 100644 index 000000000..0fd1c67a1 --- /dev/null +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_py3.py @@ -0,0 +1,40 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class BulkEnrollmentGroupOperation(Model): + """Bulk enrollment operation. + + All required parameters must be populated in order to send to Azure. + + :param enrollment_groups: Required. Enrollment items + :type enrollment_groups: list[~dps.models.EnrollmentGroup] + :param mode: Required. Operation mode. Possible values include: 'create', + 'update', 'updateIfMatchETag', 'delete' + :type mode: str or ~dps.models.enum + """ + + _validation = { + 'enrollment_groups': {'required': True}, + 'mode': {'required': True}, + } + + _attribute_map = { + 'enrollment_groups': {'key': 'enrollmentGroups', 'type': '[EnrollmentGroup]'}, + 'mode': {'key': 'mode', 'type': 'str'}, + } + + def __init__(self, *, enrollment_groups, mode, **kwargs) -> None: + super(BulkEnrollmentGroupOperation, self).__init__(**kwargs) + self.enrollment_groups = enrollment_groups + self.mode = mode diff --git a/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_result.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_result.py new file mode 100644 index 000000000..cd110069b --- /dev/null +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_result.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class BulkEnrollmentGroupOperationResult(Model): + """Results of a bulk enrollment group operation. + + All required parameters must be populated in order to send to Azure. + + :param errors: Registration errors + :type errors: list[~dps.models.BulkEnrollmentGroupOperationError] + :param is_successful: Required. Indicates if the operation was successful + in its entirety. + :type is_successful: bool + """ + + _validation = { + 'is_successful': {'required': True}, + } + + _attribute_map = { + 'errors': {'key': 'errors', 'type': '[BulkEnrollmentGroupOperationError]'}, + 'is_successful': {'key': 'isSuccessful', 'type': 'bool'}, + } + + def __init__(self, **kwargs): + super(BulkEnrollmentGroupOperationResult, self).__init__(**kwargs) + self.errors = kwargs.get('errors', None) + self.is_successful = kwargs.get('is_successful', None) diff --git a/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_result_py3.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_result_py3.py new file mode 100644 index 000000000..d4818887f --- /dev/null +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_group_operation_result_py3.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class BulkEnrollmentGroupOperationResult(Model): + """Results of a bulk enrollment group operation. + + All required parameters must be populated in order to send to Azure. + + :param errors: Registration errors + :type errors: list[~dps.models.BulkEnrollmentGroupOperationError] + :param is_successful: Required. Indicates if the operation was successful + in its entirety. + :type is_successful: bool + """ + + _validation = { + 'is_successful': {'required': True}, + } + + _attribute_map = { + 'errors': {'key': 'errors', 'type': '[BulkEnrollmentGroupOperationError]'}, + 'is_successful': {'key': 'isSuccessful', 'type': 'bool'}, + } + + def __init__(self, *, is_successful: bool, errors=None, **kwargs) -> None: + super(BulkEnrollmentGroupOperationResult, self).__init__(**kwargs) + self.errors = errors + self.is_successful = is_successful diff --git a/azext_iot/sdk/dps/service/models/bulk_enrollment_operation.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation.py new file mode 100644 index 000000000..096c0f78b --- /dev/null +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation.py @@ -0,0 +1,40 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class BulkEnrollmentOperation(Model): + """Bulk enrollment operation. + + All required parameters must be populated in order to send to Azure. + + :param enrollments: Required. Enrollment items + :type enrollments: list[~dps.models.IndividualEnrollment] + :param mode: Required. Operation mode. Possible values include: 'create', + 'update', 'updateIfMatchETag', 'delete' + :type mode: str or ~dps.models.enum + """ + + _validation = { + 'enrollments': {'required': True}, + 'mode': {'required': True}, + } + + _attribute_map = { + 'enrollments': {'key': 'enrollments', 'type': '[IndividualEnrollment]'}, + 'mode': {'key': 'mode', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(BulkEnrollmentOperation, self).__init__(**kwargs) + self.enrollments = kwargs.get('enrollments', None) + self.mode = kwargs.get('mode', None) diff --git a/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_error.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_error.py new file mode 100644 index 000000000..73c137939 --- /dev/null +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_error.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class BulkEnrollmentOperationError(Model): + """Bulk enrollment operation error. + + All required parameters must be populated in order to send to Azure. + + :param registration_id: Required. Device registration id. + :type registration_id: str + :param error_code: Required. Error code + :type error_code: int + :param error_status: Required. Error status. + :type error_status: str + """ + + _validation = { + 'registration_id': {'required': True}, + 'error_code': {'required': True}, + 'error_status': {'required': True}, + } + + _attribute_map = { + 'registration_id': {'key': 'registrationId', 'type': 'str'}, + 'error_code': {'key': 'errorCode', 'type': 'int'}, + 'error_status': {'key': 'errorStatus', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(BulkEnrollmentOperationError, self).__init__(**kwargs) + self.registration_id = kwargs.get('registration_id', None) + self.error_code = kwargs.get('error_code', None) + self.error_status = kwargs.get('error_status', None) diff --git a/azext_iot/sdk/dps/models/bulk_enrollment_operation_error.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_error_py3.py similarity index 65% rename from azext_iot/sdk/dps/models/bulk_enrollment_operation_error.py rename to azext_iot/sdk/dps/service/models/bulk_enrollment_operation_error_py3.py index c995e899c..a8f798ba5 100644 --- a/azext_iot/sdk/dps/models/bulk_enrollment_operation_error.py +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_error_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -11,11 +15,13 @@ class BulkEnrollmentOperationError(Model): """Bulk enrollment operation error. - :param registration_id: Device registration id. + All required parameters must be populated in order to send to Azure. + + :param registration_id: Required. Device registration id. :type registration_id: str - :param error_code: Error code + :param error_code: Required. Error code :type error_code: int - :param error_status: Error status + :param error_status: Required. Error status. :type error_status: str """ @@ -31,8 +37,8 @@ class BulkEnrollmentOperationError(Model): 'error_status': {'key': 'errorStatus', 'type': 'str'}, } - def __init__(self, registration_id, error_code, error_status): - super(BulkEnrollmentOperationError, self).__init__() + def __init__(self, *, registration_id: str, error_code: int, error_status: str, **kwargs) -> None: + super(BulkEnrollmentOperationError, self).__init__(**kwargs) self.registration_id = registration_id self.error_code = error_code self.error_status = error_status diff --git a/azext_iot/sdk/dps/models/bulk_enrollment_operation.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_py3.py similarity index 52% rename from azext_iot/sdk/dps/models/bulk_enrollment_operation.py rename to azext_iot/sdk/dps/service/models/bulk_enrollment_operation_py3.py index 4a86015e9..7f2c61530 100644 --- a/azext_iot/sdk/dps/models/bulk_enrollment_operation.py +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -9,15 +13,15 @@ class BulkEnrollmentOperation(Model): - """Bulk operation. - - :param enrollments: Enrollment items - :type enrollments: - list[~microsoft.azure.management.provisioningservices.models.IndividualEnrollment] - :param mode: Operation mode. Possible values include: 'create', 'update', - 'updateIfMatchETag', 'delete' - :type mode: str or - ~microsoft.azure.management.provisioningservices.models.enum + """Bulk enrollment operation. + + All required parameters must be populated in order to send to Azure. + + :param enrollments: Required. Enrollment items + :type enrollments: list[~dps.models.IndividualEnrollment] + :param mode: Required. Operation mode. Possible values include: 'create', + 'update', 'updateIfMatchETag', 'delete' + :type mode: str or ~dps.models.enum """ _validation = { @@ -30,7 +34,7 @@ class BulkEnrollmentOperation(Model): 'mode': {'key': 'mode', 'type': 'str'}, } - def __init__(self, enrollments, mode): - super(BulkEnrollmentOperation, self).__init__() + def __init__(self, *, enrollments, mode, **kwargs) -> None: + super(BulkEnrollmentOperation, self).__init__(**kwargs) self.enrollments = enrollments self.mode = mode diff --git a/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_result.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_result.py new file mode 100644 index 000000000..47df002ac --- /dev/null +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_result.py @@ -0,0 +1,39 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class BulkEnrollmentOperationResult(Model): + """Results of a bulk enrollment operation. + + All required parameters must be populated in order to send to Azure. + + :param errors: Registration errors + :type errors: list[~dps.models.BulkEnrollmentOperationError] + :param is_successful: Required. Indicates if the operation was successful + in its entirety. + :type is_successful: bool + """ + + _validation = { + 'is_successful': {'required': True}, + } + + _attribute_map = { + 'errors': {'key': 'errors', 'type': '[BulkEnrollmentOperationError]'}, + 'is_successful': {'key': 'isSuccessful', 'type': 'bool'}, + } + + def __init__(self, **kwargs): + super(BulkEnrollmentOperationResult, self).__init__(**kwargs) + self.errors = kwargs.get('errors', None) + self.is_successful = kwargs.get('is_successful', None) diff --git a/azext_iot/sdk/dps/models/bulk_enrollment_operation_result.py b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_result_py3.py similarity index 57% rename from azext_iot/sdk/dps/models/bulk_enrollment_operation_result.py rename to azext_iot/sdk/dps/service/models/bulk_enrollment_operation_result_py3.py index c9f8ab121..cfaccb020 100644 --- a/azext_iot/sdk/dps/models/bulk_enrollment_operation_result.py +++ b/azext_iot/sdk/dps/service/models/bulk_enrollment_operation_result_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -9,14 +13,15 @@ class BulkEnrollmentOperationResult(Model): - """BulkEnrollmentOperationResult. + """Results of a bulk enrollment operation. + + All required parameters must be populated in order to send to Azure. - :param is_successful: Indicates if the operation was successful in its - entirety - :type is_successful: bool :param errors: Registration errors - :type errors: - list[~microsoft.azure.management.provisioningservices.models.BulkEnrollmentOperationError] + :type errors: list[~dps.models.BulkEnrollmentOperationError] + :param is_successful: Required. Indicates if the operation was successful + in its entirety. + :type is_successful: bool """ _validation = { @@ -24,11 +29,11 @@ class BulkEnrollmentOperationResult(Model): } _attribute_map = { - 'is_successful': {'key': 'isSuccessful', 'type': 'bool'}, 'errors': {'key': 'errors', 'type': '[BulkEnrollmentOperationError]'}, + 'is_successful': {'key': 'isSuccessful', 'type': 'bool'}, } - def __init__(self, is_successful, errors=None): - super(BulkEnrollmentOperationResult, self).__init__() - self.is_successful = is_successful + def __init__(self, *, is_successful: bool, errors=None, **kwargs) -> None: + super(BulkEnrollmentOperationResult, self).__init__(**kwargs) self.errors = errors + self.is_successful = is_successful diff --git a/azext_iot/sdk/dps/service/models/custom_allocation_definition.py b/azext_iot/sdk/dps/service/models/custom_allocation_definition.py new file mode 100644 index 000000000..b53f86fe8 --- /dev/null +++ b/azext_iot/sdk/dps/service/models/custom_allocation_definition.py @@ -0,0 +1,42 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class CustomAllocationDefinition(Model): + """Custom allocation definition. + + All required parameters must be populated in order to send to Azure. + + :param webhook_url: Required. The webhook URL used for allocation + requests. + :type webhook_url: str + :param api_version: Required. The API version of the provisioning service + types (such as IndividualEnrollment) sent in the custom allocation + request. Minimum supported version: "2018-09-01-preview". + :type api_version: str + """ + + _validation = { + 'webhook_url': {'required': True}, + 'api_version': {'required': True}, + } + + _attribute_map = { + 'webhook_url': {'key': 'webhookUrl', 'type': 'str'}, + 'api_version': {'key': 'apiVersion', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(CustomAllocationDefinition, self).__init__(**kwargs) + self.webhook_url = kwargs.get('webhook_url', None) + self.api_version = kwargs.get('api_version', None) diff --git a/azext_iot/sdk/dps/models/custom_allocation_definition.py b/azext_iot/sdk/dps/service/models/custom_allocation_definition_py3.py similarity index 55% rename from azext_iot/sdk/dps/models/custom_allocation_definition.py rename to azext_iot/sdk/dps/service/models/custom_allocation_definition_py3.py index 32c7e7a02..4174a9d4a 100644 --- a/azext_iot/sdk/dps/models/custom_allocation_definition.py +++ b/azext_iot/sdk/dps/service/models/custom_allocation_definition_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -11,11 +15,14 @@ class CustomAllocationDefinition(Model): """Custom allocation definition. - :param webhook_url: The webhook URL used for allocation requests. + All required parameters must be populated in order to send to Azure. + + :param webhook_url: Required. The webhook URL used for allocation + requests. :type webhook_url: str - :param api_version: The API version of the provisioning service types - (such as IndividualEnrollment) sent in the custom allocation request. - Supported versions include: "2018-09-01-preview" + :param api_version: Required. The API version of the provisioning service + types (such as IndividualEnrollment) sent in the custom allocation + request. Minimum supported version: "2018-09-01-preview". :type api_version: str """ @@ -29,7 +36,7 @@ class CustomAllocationDefinition(Model): 'api_version': {'key': 'apiVersion', 'type': 'str'}, } - def __init__(self, webhook_url, api_version): - super(CustomAllocationDefinition, self).__init__() + def __init__(self, *, webhook_url: str, api_version: str, **kwargs) -> None: + super(CustomAllocationDefinition, self).__init__(**kwargs) self.webhook_url = webhook_url self.api_version = api_version diff --git a/azext_iot/sdk/dps/models/device_capabilities.py b/azext_iot/sdk/dps/service/models/device_capabilities.py similarity index 55% rename from azext_iot/sdk/dps/models/device_capabilities.py rename to azext_iot/sdk/dps/service/models/device_capabilities.py index 5aeed1c3c..271a48292 100644 --- a/azext_iot/sdk/dps/models/device_capabilities.py +++ b/azext_iot/sdk/dps/service/models/device_capabilities.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -11,8 +15,10 @@ class DeviceCapabilities(Model): """Device capabilities. - :param iot_edge: If set to true, this device is an IoTEdge device. Default - value: False . + All required parameters must be populated in order to send to Azure. + + :param iot_edge: Required. If set to true, this device is an IoTEdge + device. Default value: False . :type iot_edge: bool """ @@ -24,6 +30,6 @@ class DeviceCapabilities(Model): 'iot_edge': {'key': 'iotEdge', 'type': 'bool'}, } - def __init__(self, iot_edge=False): - super(DeviceCapabilities, self).__init__() - self.iot_edge = iot_edge + def __init__(self, **kwargs): + super(DeviceCapabilities, self).__init__(**kwargs) + self.iot_edge = kwargs.get('iot_edge', False) diff --git a/azext_iot/sdk/service/models/device_capabilities.py b/azext_iot/sdk/dps/service/models/device_capabilities_py3.py similarity index 63% rename from azext_iot/sdk/service/models/device_capabilities.py rename to azext_iot/sdk/dps/service/models/device_capabilities_py3.py index 5c5e4207a..55539a142 100644 --- a/azext_iot/sdk/service/models/device_capabilities.py +++ b/azext_iot/sdk/dps/service/models/device_capabilities_py3.py @@ -13,16 +13,23 @@ class DeviceCapabilities(Model): - """Status of Capabilities enabled on the device. + """Device capabilities. - :param iot_edge: + All required parameters must be populated in order to send to Azure. + + :param iot_edge: Required. If set to true, this device is an IoTEdge + device. Default value: False . :type iot_edge: bool """ + _validation = { + 'iot_edge': {'required': True}, + } + _attribute_map = { 'iot_edge': {'key': 'iotEdge', 'type': 'bool'}, } - def __init__(self, iot_edge=None): - super(DeviceCapabilities, self).__init__() + def __init__(self, *, iot_edge: bool=False, **kwargs) -> None: + super(DeviceCapabilities, self).__init__(**kwargs) self.iot_edge = iot_edge diff --git a/azext_iot/sdk/dps/models/device_registration_state.py b/azext_iot/sdk/dps/service/models/device_registration_state.py similarity index 85% rename from azext_iot/sdk/dps/models/device_registration_state.py rename to azext_iot/sdk/dps/service/models/device_registration_state.py index 235205aa5..9d34f7853 100644 --- a/azext_iot/sdk/dps/models/device_registration_state.py +++ b/azext_iot/sdk/dps/service/models/device_registration_state.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -25,8 +29,7 @@ class DeviceRegistrationState(Model): :vartype device_id: str :ivar status: Enrollment status. Possible values include: 'unassigned', 'assigning', 'assigned', 'failed', 'disabled' - :vartype status: str or - ~microsoft.azure.management.provisioningservices.models.enum + :vartype status: str or ~dps.models.enum :ivar substatus: Substatus for 'Assigned' devices. Possible values include - 'initialAssignment': Device has been assigned to an IoT hub for the first time, 'deviceDataMigrated': Device has been assigned to a different @@ -37,8 +40,7 @@ class DeviceRegistrationState(Model): enrollment. Device data was removed from the previously assigned IoT hub. Possible values include: 'initialAssignment', 'deviceDataMigrated', 'deviceDataReset' - :vartype substatus: str or - ~microsoft.azure.management.provisioningservices.models.enum + :vartype substatus: str or ~dps.models.enum :ivar error_code: Error code. :vartype error_code: int :ivar error_message: Error message. @@ -47,6 +49,9 @@ class DeviceRegistrationState(Model): :vartype last_updated_date_time_utc: datetime :ivar etag: The entity tag associated with the resource. :vartype etag: str + :ivar payload: Custom allocation payload returned from the webhook to the + device. + :vartype payload: object """ _validation = { @@ -60,6 +65,7 @@ class DeviceRegistrationState(Model): 'error_message': {'readonly': True}, 'last_updated_date_time_utc': {'readonly': True}, 'etag': {'readonly': True}, + 'payload': {'readonly': True}, } _attribute_map = { @@ -73,10 +79,11 @@ class DeviceRegistrationState(Model): 'error_message': {'key': 'errorMessage', 'type': 'str'}, 'last_updated_date_time_utc': {'key': 'lastUpdatedDateTimeUtc', 'type': 'iso-8601'}, 'etag': {'key': 'etag', 'type': 'str'}, + 'payload': {'key': 'payload', 'type': 'object'}, } - def __init__(self): - super(DeviceRegistrationState, self).__init__() + def __init__(self, **kwargs): + super(DeviceRegistrationState, self).__init__(**kwargs) self.registration_id = None self.created_date_time_utc = None self.assigned_hub = None @@ -87,3 +94,4 @@ def __init__(self): self.error_message = None self.last_updated_date_time_utc = None self.etag = None + self.payload = None diff --git a/azext_iot/sdk/dps/models/individual_enrollment_registration_state.py b/azext_iot/sdk/dps/service/models/device_registration_state_py3.py similarity index 58% rename from azext_iot/sdk/dps/models/individual_enrollment_registration_state.py rename to azext_iot/sdk/dps/service/models/device_registration_state_py3.py index 46c77709a..dd61aab73 100644 --- a/azext_iot/sdk/dps/models/individual_enrollment_registration_state.py +++ b/azext_iot/sdk/dps/service/models/device_registration_state_py3.py @@ -1,15 +1,19 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. # -------------------------------------------------------------------------- -from .device_registration_state import DeviceRegistrationState +from msrest.serialization import Model -class IndividualEnrollmentRegistrationState(DeviceRegistrationState): - """Current registration status. +class DeviceRegistrationState(Model): + """Device registration state. Variables are only populated by the server, and will be ignored when sending a request. @@ -25,8 +29,7 @@ class IndividualEnrollmentRegistrationState(DeviceRegistrationState): :vartype device_id: str :ivar status: Enrollment status. Possible values include: 'unassigned', 'assigning', 'assigned', 'failed', 'disabled' - :vartype status: str or - ~microsoft.azure.management.provisioningservices.models.enum + :vartype status: str or ~dps.models.enum :ivar substatus: Substatus for 'Assigned' devices. Possible values include - 'initialAssignment': Device has been assigned to an IoT hub for the first time, 'deviceDataMigrated': Device has been assigned to a different @@ -37,8 +40,7 @@ class IndividualEnrollmentRegistrationState(DeviceRegistrationState): enrollment. Device data was removed from the previously assigned IoT hub. Possible values include: 'initialAssignment', 'deviceDataMigrated', 'deviceDataReset' - :vartype substatus: str or - ~microsoft.azure.management.provisioningservices.models.enum + :vartype substatus: str or ~dps.models.enum :ivar error_code: Error code. :vartype error_code: int :ivar error_message: Error message. @@ -47,6 +49,9 @@ class IndividualEnrollmentRegistrationState(DeviceRegistrationState): :vartype last_updated_date_time_utc: datetime :ivar etag: The entity tag associated with the resource. :vartype etag: str + :ivar payload: Custom allocation payload returned from the webhook to the + device. + :vartype payload: object """ _validation = { @@ -60,7 +65,33 @@ class IndividualEnrollmentRegistrationState(DeviceRegistrationState): 'error_message': {'readonly': True}, 'last_updated_date_time_utc': {'readonly': True}, 'etag': {'readonly': True}, + 'payload': {'readonly': True}, } - def __init__(self): - super(IndividualEnrollmentRegistrationState, self).__init__() + _attribute_map = { + 'registration_id': {'key': 'registrationId', 'type': 'str'}, + 'created_date_time_utc': {'key': 'createdDateTimeUtc', 'type': 'iso-8601'}, + 'assigned_hub': {'key': 'assignedHub', 'type': 'str'}, + 'device_id': {'key': 'deviceId', 'type': 'str'}, + 'status': {'key': 'status', 'type': 'str'}, + 'substatus': {'key': 'substatus', 'type': 'str'}, + 'error_code': {'key': 'errorCode', 'type': 'int'}, + 'error_message': {'key': 'errorMessage', 'type': 'str'}, + 'last_updated_date_time_utc': {'key': 'lastUpdatedDateTimeUtc', 'type': 'iso-8601'}, + 'etag': {'key': 'etag', 'type': 'str'}, + 'payload': {'key': 'payload', 'type': 'object'}, + } + + def __init__(self, **kwargs) -> None: + super(DeviceRegistrationState, self).__init__(**kwargs) + self.registration_id = None + self.created_date_time_utc = None + self.assigned_hub = None + self.device_id = None + self.status = None + self.substatus = None + self.error_code = None + self.error_message = None + self.last_updated_date_time_utc = None + self.etag = None + self.payload = None diff --git a/azext_iot/sdk/dps/service/models/enrollment_group.py b/azext_iot/sdk/dps/service/models/enrollment_group.py new file mode 100644 index 000000000..1b19345a4 --- /dev/null +++ b/azext_iot/sdk/dps/service/models/enrollment_group.py @@ -0,0 +1,106 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EnrollmentGroup(Model): + """Enrollment group record. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :param enrollment_group_id: Required. Enrollment Group ID. + :type enrollment_group_id: str + :param attestation: Required. Attestation method used by the device. + :type attestation: ~dps.models.AttestationMechanism + :param capabilities: Capabilities of the device. + :type capabilities: ~dps.models.DeviceCapabilities + :param iot_hub_host_name: The Iot Hub host name. + :type iot_hub_host_name: str + :param initial_twin: Initial device twin. + :type initial_twin: ~dps.models.InitialTwin + :param etag: The entity tag associated with the resource. + :type etag: str + :param provisioning_status: The provisioning status. Possible values + include: 'enabled', 'disabled'. Default value: "enabled" . + :type provisioning_status: str or ~dps.models.enum + :param reprovision_policy: The behavior when a device is re-provisioned to + an IoT hub. + :type reprovision_policy: ~dps.models.ReprovisionPolicy + :ivar created_date_time_utc: The DateTime this resource was created. + :vartype created_date_time_utc: datetime + :ivar last_updated_date_time_utc: The DateTime this resource was last + updated. + :vartype last_updated_date_time_utc: datetime + :param allocation_policy: The allocation policy of this resource. This + policy overrides the tenant level allocation policy for this individual + enrollment or enrollment group. Possible values include 'hashed': Linked + IoT hubs are equally likely to have devices provisioned to them, + 'geoLatency': Devices are provisioned to an IoT hub with the lowest + latency to the device.If multiple linked IoT hubs would provide the same + lowest latency, the provisioning service hashes devices across those hubs, + 'static' : Specification of the desired IoT hub in the enrollment list + takes priority over the service-level allocation policy, 'custom': Devices + are provisioned to an IoT hub based on your own custom logic. The + provisioning service passes information about the device to the logic, and + the logic returns the desired IoT hub as well as the desired initial + configuration. We recommend using Azure Functions to host your logic. + Possible values include: 'hashed', 'geoLatency', 'static', 'custom' + :type allocation_policy: str or ~dps.models.enum + :param iot_hubs: The list of names of IoT hubs the device(s) in this + resource can be allocated to. Must be a subset of tenant level list of IoT + hubs. + :type iot_hubs: list[str] + :param custom_allocation_definition: Custom allocation definition. + :type custom_allocation_definition: ~dps.models.CustomAllocationDefinition + """ + + _validation = { + 'enrollment_group_id': {'required': True}, + 'attestation': {'required': True}, + 'created_date_time_utc': {'readonly': True}, + 'last_updated_date_time_utc': {'readonly': True}, + } + + _attribute_map = { + 'enrollment_group_id': {'key': 'enrollmentGroupId', 'type': 'str'}, + 'attestation': {'key': 'attestation', 'type': 'AttestationMechanism'}, + 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, + 'iot_hub_host_name': {'key': 'iotHubHostName', 'type': 'str'}, + 'initial_twin': {'key': 'initialTwin', 'type': 'InitialTwin'}, + 'etag': {'key': 'etag', 'type': 'str'}, + 'provisioning_status': {'key': 'provisioningStatus', 'type': 'str'}, + 'reprovision_policy': {'key': 'reprovisionPolicy', 'type': 'ReprovisionPolicy'}, + 'created_date_time_utc': {'key': 'createdDateTimeUtc', 'type': 'iso-8601'}, + 'last_updated_date_time_utc': {'key': 'lastUpdatedDateTimeUtc', 'type': 'iso-8601'}, + 'allocation_policy': {'key': 'allocationPolicy', 'type': 'str'}, + 'iot_hubs': {'key': 'iotHubs', 'type': '[str]'}, + 'custom_allocation_definition': {'key': 'customAllocationDefinition', 'type': 'CustomAllocationDefinition'}, + } + + def __init__(self, **kwargs): + super(EnrollmentGroup, self).__init__(**kwargs) + self.enrollment_group_id = kwargs.get('enrollment_group_id', None) + self.attestation = kwargs.get('attestation', None) + self.capabilities = kwargs.get('capabilities', None) + self.iot_hub_host_name = kwargs.get('iot_hub_host_name', None) + self.initial_twin = kwargs.get('initial_twin', None) + self.etag = kwargs.get('etag', None) + self.provisioning_status = kwargs.get('provisioning_status', "enabled") + self.reprovision_policy = kwargs.get('reprovision_policy', None) + self.created_date_time_utc = None + self.last_updated_date_time_utc = None + self.allocation_policy = kwargs.get('allocation_policy', None) + self.iot_hubs = kwargs.get('iot_hubs', None) + self.custom_allocation_definition = kwargs.get('custom_allocation_definition', None) diff --git a/azext_iot/sdk/dps/models/enrollment_group.py b/azext_iot/sdk/dps/service/models/enrollment_group_py3.py similarity index 76% rename from azext_iot/sdk/dps/models/enrollment_group.py rename to azext_iot/sdk/dps/service/models/enrollment_group_py3.py index 5acc55954..11dbb59bf 100644 --- a/azext_iot/sdk/dps/models/enrollment_group.py +++ b/azext_iot/sdk/dps/service/models/enrollment_group_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -14,29 +18,26 @@ class EnrollmentGroup(Model): Variables are only populated by the server, and will be ignored when sending a request. - :param enrollment_group_id: Enrollment Group ID. + All required parameters must be populated in order to send to Azure. + + :param enrollment_group_id: Required. Enrollment Group ID. :type enrollment_group_id: str - :param attestation: Attestation method used by the device. - :type attestation: - ~microsoft.azure.management.provisioningservices.models.AttestationMechanism - :param capabilities: Capabilities of the device - :type capabilities: - ~microsoft.azure.management.provisioningservices.models.DeviceCapabilities + :param attestation: Required. Attestation method used by the device. + :type attestation: ~dps.models.AttestationMechanism + :param capabilities: Capabilities of the device. + :type capabilities: ~dps.models.DeviceCapabilities :param iot_hub_host_name: The Iot Hub host name. :type iot_hub_host_name: str :param initial_twin: Initial device twin. - :type initial_twin: - ~microsoft.azure.management.provisioningservices.models.InitialTwin + :type initial_twin: ~dps.models.InitialTwin :param etag: The entity tag associated with the resource. :type etag: str :param provisioning_status: The provisioning status. Possible values include: 'enabled', 'disabled'. Default value: "enabled" . - :type provisioning_status: str or - ~microsoft.azure.management.provisioningservices.models.enum + :type provisioning_status: str or ~dps.models.enum :param reprovision_policy: The behavior when a device is re-provisioned to an IoT hub. - :type reprovision_policy: - ~microsoft.azure.management.provisioningservices.models.ReprovisionPolicy + :type reprovision_policy: ~dps.models.ReprovisionPolicy :ivar created_date_time_utc: The DateTime this resource was created. :vartype created_date_time_utc: datetime :ivar last_updated_date_time_utc: The DateTime this resource was last @@ -56,15 +57,13 @@ class EnrollmentGroup(Model): the logic returns the desired IoT hub as well as the desired initial configuration. We recommend using Azure Functions to host your logic. Possible values include: 'hashed', 'geoLatency', 'static', 'custom' - :type allocation_policy: str or - ~microsoft.azure.management.provisioningservices.models.enum + :type allocation_policy: str or ~dps.models.enum :param iot_hubs: The list of names of IoT hubs the device(s) in this resource can be allocated to. Must be a subset of tenant level list of IoT hubs. :type iot_hubs: list[str] :param custom_allocation_definition: Custom allocation definition. - :type custom_allocation_definition: - ~microsoft.azure.management.provisioningservices.models.CustomAllocationDefinition + :type custom_allocation_definition: ~dps.models.CustomAllocationDefinition """ _validation = { @@ -75,9 +74,9 @@ class EnrollmentGroup(Model): } _attribute_map = { - 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, # @digimaun - added capabilities custom 'enrollment_group_id': {'key': 'enrollmentGroupId', 'type': 'str'}, 'attestation': {'key': 'attestation', 'type': 'AttestationMechanism'}, + 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, 'iot_hub_host_name': {'key': 'iotHubHostName', 'type': 'str'}, 'initial_twin': {'key': 'initialTwin', 'type': 'InitialTwin'}, 'etag': {'key': 'etag', 'type': 'str'}, @@ -90,11 +89,11 @@ class EnrollmentGroup(Model): 'custom_allocation_definition': {'key': 'customAllocationDefinition', 'type': 'CustomAllocationDefinition'}, } - def __init__(self, enrollment_group_id, attestation, capabilities=None, iot_hub_host_name=None, initial_twin=None, etag=None, provisioning_status="enabled", reprovision_policy=None, allocation_policy=None, iot_hubs=None, custom_allocation_definition=None): - super(EnrollmentGroup, self).__init__() - self.capabilities = capabilities # @digmaun - added capabilities custom + def __init__(self, *, enrollment_group_id: str, attestation, capabilities=None, iot_hub_host_name: str=None, initial_twin=None, etag: str=None, provisioning_status="enabled", reprovision_policy=None, allocation_policy=None, iot_hubs=None, custom_allocation_definition=None, **kwargs) -> None: + super(EnrollmentGroup, self).__init__(**kwargs) self.enrollment_group_id = enrollment_group_id self.attestation = attestation + self.capabilities = capabilities self.iot_hub_host_name = iot_hub_host_name self.initial_twin = initial_twin self.etag = etag diff --git a/azext_iot/sdk/dps/service/models/individual_enrollment.py b/azext_iot/sdk/dps/service/models/individual_enrollment.py new file mode 100644 index 000000000..5fe9a325a --- /dev/null +++ b/azext_iot/sdk/dps/service/models/individual_enrollment.py @@ -0,0 +1,116 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IndividualEnrollment(Model): + """The device enrollment record. + + Variables are only populated by the server, and will be ignored when + sending a request. + + All required parameters must be populated in order to send to Azure. + + :param registration_id: Required. The registration ID is alphanumeric, + lowercase, and may contain hyphens. + :type registration_id: str + :param device_id: Desired IoT Hub device ID (optional). + :type device_id: str + :ivar registration_state: Current registration status. + :vartype registration_state: ~dps.models.DeviceRegistrationState + :param attestation: Required. Attestation method used by the device. + :type attestation: ~dps.models.AttestationMechanism + :param capabilities: Capabilities of the device. + :type capabilities: ~dps.models.DeviceCapabilities + :param iot_hub_host_name: The Iot Hub host name. + :type iot_hub_host_name: str + :param initial_twin: Initial device twin. + :type initial_twin: ~dps.models.InitialTwin + :param etag: The entity tag associated with the resource. + :type etag: str + :param provisioning_status: The provisioning status. Possible values + include: 'enabled', 'disabled'. Default value: "enabled" . + :type provisioning_status: str or ~dps.models.enum + :param reprovision_policy: The behavior when a device is re-provisioned to + an IoT hub. + :type reprovision_policy: ~dps.models.ReprovisionPolicy + :ivar created_date_time_utc: The DateTime this resource was created. + :vartype created_date_time_utc: datetime + :ivar last_updated_date_time_utc: The DateTime this resource was last + updated. + :vartype last_updated_date_time_utc: datetime + :param allocation_policy: The allocation policy of this resource. This + policy overrides the tenant level allocation policy for this individual + enrollment or enrollment group. Possible values include 'hashed': Linked + IoT hubs are equally likely to have devices provisioned to them, + 'geoLatency': Devices are provisioned to an IoT hub with the lowest + latency to the device.If multiple linked IoT hubs would provide the same + lowest latency, the provisioning service hashes devices across those hubs, + 'static' : Specification of the desired IoT hub in the enrollment list + takes priority over the service-level allocation policy, 'custom': Devices + are provisioned to an IoT hub based on your own custom logic. The + provisioning service passes information about the device to the logic, and + the logic returns the desired IoT hub as well as the desired initial + configuration. We recommend using Azure Functions to host your logic. + Possible values include: 'hashed', 'geoLatency', 'static', 'custom' + :type allocation_policy: str or ~dps.models.enum + :param iot_hubs: The list of names of IoT hubs the device(s) in this + resource can be allocated to. Must be a subset of tenant level list of IoT + hubs. + :type iot_hubs: list[str] + :param custom_allocation_definition: Custom allocation definition. + :type custom_allocation_definition: ~dps.models.CustomAllocationDefinition + """ + + _validation = { + 'registration_id': {'required': True}, + 'registration_state': {'readonly': True}, + 'attestation': {'required': True}, + 'created_date_time_utc': {'readonly': True}, + 'last_updated_date_time_utc': {'readonly': True}, + } + + _attribute_map = { + 'registration_id': {'key': 'registrationId', 'type': 'str'}, + 'device_id': {'key': 'deviceId', 'type': 'str'}, + 'registration_state': {'key': 'registrationState', 'type': 'DeviceRegistrationState'}, + 'attestation': {'key': 'attestation', 'type': 'AttestationMechanism'}, + 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, + 'iot_hub_host_name': {'key': 'iotHubHostName', 'type': 'str'}, + 'initial_twin': {'key': 'initialTwin', 'type': 'InitialTwin'}, + 'etag': {'key': 'etag', 'type': 'str'}, + 'provisioning_status': {'key': 'provisioningStatus', 'type': 'str'}, + 'reprovision_policy': {'key': 'reprovisionPolicy', 'type': 'ReprovisionPolicy'}, + 'created_date_time_utc': {'key': 'createdDateTimeUtc', 'type': 'iso-8601'}, + 'last_updated_date_time_utc': {'key': 'lastUpdatedDateTimeUtc', 'type': 'iso-8601'}, + 'allocation_policy': {'key': 'allocationPolicy', 'type': 'str'}, + 'iot_hubs': {'key': 'iotHubs', 'type': '[str]'}, + 'custom_allocation_definition': {'key': 'customAllocationDefinition', 'type': 'CustomAllocationDefinition'}, + } + + def __init__(self, **kwargs): + super(IndividualEnrollment, self).__init__(**kwargs) + self.registration_id = kwargs.get('registration_id', None) + self.device_id = kwargs.get('device_id', None) + self.registration_state = None + self.attestation = kwargs.get('attestation', None) + self.capabilities = kwargs.get('capabilities', None) + self.iot_hub_host_name = kwargs.get('iot_hub_host_name', None) + self.initial_twin = kwargs.get('initial_twin', None) + self.etag = kwargs.get('etag', None) + self.provisioning_status = kwargs.get('provisioning_status', "enabled") + self.reprovision_policy = kwargs.get('reprovision_policy', None) + self.created_date_time_utc = None + self.last_updated_date_time_utc = None + self.allocation_policy = kwargs.get('allocation_policy', None) + self.iot_hubs = kwargs.get('iot_hubs', None) + self.custom_allocation_definition = kwargs.get('custom_allocation_definition', None) diff --git a/azext_iot/sdk/dps/models/individual_enrollment.py b/azext_iot/sdk/dps/service/models/individual_enrollment_py3.py similarity index 76% rename from azext_iot/sdk/dps/models/individual_enrollment.py rename to azext_iot/sdk/dps/service/models/individual_enrollment_py3.py index bf4f4e6e2..aca250416 100644 --- a/azext_iot/sdk/dps/models/individual_enrollment.py +++ b/azext_iot/sdk/dps/service/models/individual_enrollment_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -14,35 +18,31 @@ class IndividualEnrollment(Model): Variables are only populated by the server, and will be ignored when sending a request. - :param capabilities: Capabilities of the device - :type capabilities: - ~microsoft.azure.management.provisioningservices.models.DeviceCapabilities - :param registration_id: The registration ID is alphanumeric, lowercase, - and may contain hyphens. + All required parameters must be populated in order to send to Azure. + + :param registration_id: Required. The registration ID is alphanumeric, + lowercase, and may contain hyphens. :type registration_id: str :param device_id: Desired IoT Hub device ID (optional). :type device_id: str :ivar registration_state: Current registration status. - :vartype registration_state: - ~microsoft.azure.management.provisioningservices.models.IndividualEnrollmentRegistrationState - :param attestation: Attestation method used by the device. - :type attestation: - ~microsoft.azure.management.provisioningservices.models.AttestationMechanism + :vartype registration_state: ~dps.models.DeviceRegistrationState + :param attestation: Required. Attestation method used by the device. + :type attestation: ~dps.models.AttestationMechanism + :param capabilities: Capabilities of the device. + :type capabilities: ~dps.models.DeviceCapabilities :param iot_hub_host_name: The Iot Hub host name. :type iot_hub_host_name: str :param initial_twin: Initial device twin. - :type initial_twin: - ~microsoft.azure.management.provisioningservices.models.InitialTwin + :type initial_twin: ~dps.models.InitialTwin :param etag: The entity tag associated with the resource. :type etag: str :param provisioning_status: The provisioning status. Possible values include: 'enabled', 'disabled'. Default value: "enabled" . - :type provisioning_status: str or - ~microsoft.azure.management.provisioningservices.models.enum + :type provisioning_status: str or ~dps.models.enum :param reprovision_policy: The behavior when a device is re-provisioned to an IoT hub. - :type reprovision_policy: - ~microsoft.azure.management.provisioningservices.models.ReprovisionPolicy + :type reprovision_policy: ~dps.models.ReprovisionPolicy :ivar created_date_time_utc: The DateTime this resource was created. :vartype created_date_time_utc: datetime :ivar last_updated_date_time_utc: The DateTime this resource was last @@ -62,15 +62,13 @@ class IndividualEnrollment(Model): the logic returns the desired IoT hub as well as the desired initial configuration. We recommend using Azure Functions to host your logic. Possible values include: 'hashed', 'geoLatency', 'static', 'custom' - :type allocation_policy: str or - ~microsoft.azure.management.provisioningservices.models.enum + :type allocation_policy: str or ~dps.models.enum :param iot_hubs: The list of names of IoT hubs the device(s) in this resource can be allocated to. Must be a subset of tenant level list of IoT hubs. :type iot_hubs: list[str] :param custom_allocation_definition: Custom allocation definition. - :type custom_allocation_definition: - ~microsoft.azure.management.provisioningservices.models.CustomAllocationDefinition + :type custom_allocation_definition: ~dps.models.CustomAllocationDefinition """ _validation = { @@ -82,11 +80,11 @@ class IndividualEnrollment(Model): } _attribute_map = { - 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, 'registration_id': {'key': 'registrationId', 'type': 'str'}, 'device_id': {'key': 'deviceId', 'type': 'str'}, - 'registration_state': {'key': 'registrationState', 'type': 'IndividualEnrollmentRegistrationState'}, + 'registration_state': {'key': 'registrationState', 'type': 'DeviceRegistrationState'}, 'attestation': {'key': 'attestation', 'type': 'AttestationMechanism'}, + 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, 'iot_hub_host_name': {'key': 'iotHubHostName', 'type': 'str'}, 'initial_twin': {'key': 'initialTwin', 'type': 'InitialTwin'}, 'etag': {'key': 'etag', 'type': 'str'}, @@ -99,13 +97,13 @@ class IndividualEnrollment(Model): 'custom_allocation_definition': {'key': 'customAllocationDefinition', 'type': 'CustomAllocationDefinition'}, } - def __init__(self, registration_id, attestation, capabilities=None, device_id=None, iot_hub_host_name=None, initial_twin=None, etag=None, provisioning_status="enabled", reprovision_policy=None, allocation_policy=None, iot_hubs=None, custom_allocation_definition=None): - super(IndividualEnrollment, self).__init__() - self.capabilities = capabilities + def __init__(self, *, registration_id: str, attestation, device_id: str=None, capabilities=None, iot_hub_host_name: str=None, initial_twin=None, etag: str=None, provisioning_status="enabled", reprovision_policy=None, allocation_policy=None, iot_hubs=None, custom_allocation_definition=None, **kwargs) -> None: + super(IndividualEnrollment, self).__init__(**kwargs) self.registration_id = registration_id self.device_id = device_id self.registration_state = None self.attestation = attestation + self.capabilities = capabilities self.iot_hub_host_name = iot_hub_host_name self.initial_twin = initial_twin self.etag = etag diff --git a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value.py b/azext_iot/sdk/dps/service/models/initial_twin.py similarity index 55% rename from azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value.py rename to azext_iot/sdk/dps/service/models/initial_twin.py index f710ec229..069b74593 100644 --- a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value.py +++ b/azext_iot/sdk/dps/service/models/initial_twin.py @@ -12,18 +12,21 @@ from msrest.serialization import Model -class DigitalTwinInterfacesPatchInterfacesValue(Model): - """DigitalTwinInterfacesPatchInterfacesValue. +class InitialTwin(Model): + """Initial device twin. Contains a subset of the properties of Twin. - :param properties: List of properties to update in an interface. - :type properties: dict[str, - ~service.models.DigitalTwinInterfacesPatchInterfacesValuePropertiesValue] + :param tags: Twin tags. + :type tags: ~dps.models.TwinCollection + :param properties: Twin desired properties. + :type properties: ~dps.models.InitialTwinProperties """ _attribute_map = { - 'properties': {'key': 'properties', 'type': '{DigitalTwinInterfacesPatchInterfacesValuePropertiesValue}'}, + 'tags': {'key': 'tags', 'type': 'TwinCollection'}, + 'properties': {'key': 'properties', 'type': 'InitialTwinProperties'}, } def __init__(self, **kwargs): - super(DigitalTwinInterfacesPatchInterfacesValue, self).__init__(**kwargs) + super(InitialTwin, self).__init__(**kwargs) + self.tags = kwargs.get('tags', None) self.properties = kwargs.get('properties', None) diff --git a/azext_iot/sdk/iothub/service/models/property.py b/azext_iot/sdk/dps/service/models/initial_twin_properties.py similarity index 62% rename from azext_iot/sdk/iothub/service/models/property.py rename to azext_iot/sdk/dps/service/models/initial_twin_properties.py index 7ad081cf2..dead116b8 100644 --- a/azext_iot/sdk/iothub/service/models/property.py +++ b/azext_iot/sdk/dps/service/models/initial_twin_properties.py @@ -12,21 +12,17 @@ from msrest.serialization import Model -class Property(Model): - """Property. +class InitialTwinProperties(Model): + """Represents the initial properties that will be set on the device twin. - :param reported: - :type reported: ~service.models.Reported - :param desired: - :type desired: ~service.models.Desired + :param desired: Gets and sets the InitialTwin desired properties. + :type desired: ~dps.models.TwinCollection """ _attribute_map = { - 'reported': {'key': 'reported', 'type': 'Reported'}, - 'desired': {'key': 'desired', 'type': 'Desired'}, + 'desired': {'key': 'desired', 'type': 'TwinCollection'}, } def __init__(self, **kwargs): - super(Property, self).__init__(**kwargs) - self.reported = kwargs.get('reported', None) + super(InitialTwinProperties, self).__init__(**kwargs) self.desired = kwargs.get('desired', None) diff --git a/azext_iot/sdk/dps/models/initial_twin_properties.py b/azext_iot/sdk/dps/service/models/initial_twin_properties_py3.py similarity index 59% rename from azext_iot/sdk/dps/models/initial_twin_properties.py rename to azext_iot/sdk/dps/service/models/initial_twin_properties_py3.py index 955e82d8a..b3b86445e 100644 --- a/azext_iot/sdk/dps/models/initial_twin_properties.py +++ b/azext_iot/sdk/dps/service/models/initial_twin_properties_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -9,17 +13,16 @@ class InitialTwinProperties(Model): - """InitialTwinProperties. + """Represents the initial properties that will be set on the device twin. :param desired: Gets and sets the InitialTwin desired properties. - :type desired: - ~microsoft.azure.management.provisioningservices.models.TwinCollection + :type desired: ~dps.models.TwinCollection """ _attribute_map = { 'desired': {'key': 'desired', 'type': 'TwinCollection'}, } - def __init__(self, desired=None): - super(InitialTwinProperties, self).__init__() + def __init__(self, *, desired=None, **kwargs) -> None: + super(InitialTwinProperties, self).__init__(**kwargs) self.desired = desired diff --git a/azext_iot/sdk/dps/models/initial_twin.py b/azext_iot/sdk/dps/service/models/initial_twin_py3.py similarity index 66% rename from azext_iot/sdk/dps/models/initial_twin.py rename to azext_iot/sdk/dps/service/models/initial_twin_py3.py index e059973e6..91019fb75 100644 --- a/azext_iot/sdk/dps/models/initial_twin.py +++ b/azext_iot/sdk/dps/service/models/initial_twin_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -12,11 +16,9 @@ class InitialTwin(Model): """Initial device twin. Contains a subset of the properties of Twin. :param tags: Twin tags. - :type tags: - ~microsoft.azure.management.provisioningservices.models.TwinCollection + :type tags: ~dps.models.TwinCollection :param properties: Twin desired properties. - :type properties: - ~microsoft.azure.management.provisioningservices.models.InitialTwinProperties + :type properties: ~dps.models.InitialTwinProperties """ _attribute_map = { @@ -24,7 +26,7 @@ class InitialTwin(Model): 'properties': {'key': 'properties', 'type': 'InitialTwinProperties'}, } - def __init__(self, tags=None, properties=None): - super(InitialTwin, self).__init__() + def __init__(self, *, tags=None, properties=None, **kwargs) -> None: + super(InitialTwin, self).__init__(**kwargs) self.tags = tags self.properties = properties diff --git a/azext_iot/sdk/dps/service/models/metadata.py b/azext_iot/sdk/dps/service/models/metadata.py new file mode 100644 index 000000000..560fdf217 --- /dev/null +++ b/azext_iot/sdk/dps/service/models/metadata.py @@ -0,0 +1,33 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class Metadata(Model): + """Metadata for the TwinCollection. + + :param last_updated: Last time the TwinCollection was updated + :type last_updated: datetime + :param last_updated_version: This SHOULD be null for Reported properties + metadata and MUST not be null for Desired properties metadata. + :type last_updated_version: long + """ + + _attribute_map = { + 'last_updated': {'key': 'lastUpdated', 'type': 'iso-8601'}, + 'last_updated_version': {'key': 'lastUpdatedVersion', 'type': 'long'}, + } + + def __init__(self, **kwargs): + super(Metadata, self).__init__(**kwargs) + self.last_updated = kwargs.get('last_updated', None) + self.last_updated_version = kwargs.get('last_updated_version', None) diff --git a/azext_iot/sdk/dps/models/metadata.py b/azext_iot/sdk/dps/service/models/metadata_py3.py similarity index 68% rename from azext_iot/sdk/dps/models/metadata.py rename to azext_iot/sdk/dps/service/models/metadata_py3.py index 5ade4a1ec..98715ac40 100644 --- a/azext_iot/sdk/dps/models/metadata.py +++ b/azext_iot/sdk/dps/service/models/metadata_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -9,9 +13,9 @@ class Metadata(Model): - """Metadata. + """Metadata for the TwinCollection. - :param last_updated: + :param last_updated: Last time the TwinCollection was updated :type last_updated: datetime :param last_updated_version: This SHOULD be null for Reported properties metadata and MUST not be null for Desired properties metadata. @@ -23,7 +27,7 @@ class Metadata(Model): 'last_updated_version': {'key': 'lastUpdatedVersion', 'type': 'long'}, } - def __init__(self, last_updated=None, last_updated_version=None): - super(Metadata, self).__init__() + def __init__(self, *, last_updated=None, last_updated_version: int=None, **kwargs) -> None: + super(Metadata, self).__init__(**kwargs) self.last_updated = last_updated self.last_updated_version = last_updated_version diff --git a/azext_iot/sdk/dps/service/models/provisioning_service_error_details.py b/azext_iot/sdk/dps/service/models/provisioning_service_error_details.py new file mode 100644 index 000000000..e58db7faf --- /dev/null +++ b/azext_iot/sdk/dps/service/models/provisioning_service_error_details.py @@ -0,0 +1,58 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model +from msrest.exceptions import HttpOperationError + + +class ProvisioningServiceErrorDetails(Model): + """Contains the properties of an error returned by the Azure IoT Hub + Provisioning Service. + + :param error_code: + :type error_code: int + :param tracking_id: + :type tracking_id: str + :param message: + :type message: str + :param info: + :type info: dict[str, str] + :param timestamp_utc: + :type timestamp_utc: datetime + """ + + _attribute_map = { + 'error_code': {'key': 'errorCode', 'type': 'int'}, + 'tracking_id': {'key': 'trackingId', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'info': {'key': 'info', 'type': '{str}'}, + 'timestamp_utc': {'key': 'timestampUtc', 'type': 'iso-8601'}, + } + + def __init__(self, **kwargs): + super(ProvisioningServiceErrorDetails, self).__init__(**kwargs) + self.error_code = kwargs.get('error_code', None) + self.tracking_id = kwargs.get('tracking_id', None) + self.message = kwargs.get('message', None) + self.info = kwargs.get('info', None) + self.timestamp_utc = kwargs.get('timestamp_utc', None) + + +class ProvisioningServiceErrorDetailsException(HttpOperationError): + """Server responsed with exception of type: 'ProvisioningServiceErrorDetails'. + + :param deserialize: A deserializer + :param response: Server response to be deserialized. + """ + + def __init__(self, deserialize, response, *args): + + super(ProvisioningServiceErrorDetailsException, self).__init__(deserialize, response, 'ProvisioningServiceErrorDetails', *args) diff --git a/azext_iot/sdk/dps/models/provisioning_service_error_details.py b/azext_iot/sdk/dps/service/models/provisioning_service_error_details_py3.py similarity index 77% rename from azext_iot/sdk/dps/models/provisioning_service_error_details.py rename to azext_iot/sdk/dps/service/models/provisioning_service_error_details_py3.py index d1e6743bd..2c1411312 100644 --- a/azext_iot/sdk/dps/models/provisioning_service_error_details.py +++ b/azext_iot/sdk/dps/service/models/provisioning_service_error_details_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -10,7 +14,8 @@ class ProvisioningServiceErrorDetails(Model): - """ProvisioningServiceErrorDetails. + """Contains the properties of an error returned by the Azure IoT Hub + Provisioning Service. :param error_code: :type error_code: int @@ -32,8 +37,8 @@ class ProvisioningServiceErrorDetails(Model): 'timestamp_utc': {'key': 'timestampUtc', 'type': 'iso-8601'}, } - def __init__(self, error_code=None, tracking_id=None, message=None, info=None, timestamp_utc=None): - super(ProvisioningServiceErrorDetails, self).__init__() + def __init__(self, *, error_code: int=None, tracking_id: str=None, message: str=None, info=None, timestamp_utc=None, **kwargs) -> None: + super(ProvisioningServiceErrorDetails, self).__init__(**kwargs) self.error_code = error_code self.tracking_id = tracking_id self.message = message diff --git a/azext_iot/sdk/dps/models/query_specification.py b/azext_iot/sdk/dps/service/models/query_specification.py similarity index 59% rename from azext_iot/sdk/dps/models/query_specification.py rename to azext_iot/sdk/dps/service/models/query_specification.py index 990dbf1e3..378002dd4 100644 --- a/azext_iot/sdk/dps/models/query_specification.py +++ b/azext_iot/sdk/dps/service/models/query_specification.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -11,7 +15,9 @@ class QuerySpecification(Model): """A Json query request. - :param query: The query. + All required parameters must be populated in order to send to Azure. + + :param query: Required. The query. :type query: str """ @@ -23,6 +29,6 @@ class QuerySpecification(Model): 'query': {'key': 'query', 'type': 'str'}, } - def __init__(self, query): - super(QuerySpecification, self).__init__() - self.query = query + def __init__(self, **kwargs): + super(QuerySpecification, self).__init__(**kwargs) + self.query = kwargs.get('query', None) diff --git a/azext_iot/sdk/service/models/query_specification.py b/azext_iot/sdk/dps/service/models/query_specification_py3.py similarity index 70% rename from azext_iot/sdk/service/models/query_specification.py rename to azext_iot/sdk/dps/service/models/query_specification_py3.py index 67f7ad02c..290bd23a2 100644 --- a/azext_iot/sdk/service/models/query_specification.py +++ b/azext_iot/sdk/dps/service/models/query_specification_py3.py @@ -15,14 +15,20 @@ class QuerySpecification(Model): """A Json query request. - :param query: The query. + All required parameters must be populated in order to send to Azure. + + :param query: Required. The query. :type query: str """ + _validation = { + 'query': {'required': True}, + } + _attribute_map = { 'query': {'key': 'query', 'type': 'str'}, } - def __init__(self, query=None): - super(QuerySpecification, self).__init__() + def __init__(self, *, query: str, **kwargs) -> None: + super(QuerySpecification, self).__init__(**kwargs) self.query = query diff --git a/azext_iot/sdk/dps/service/models/reprovision_policy.py b/azext_iot/sdk/dps/service/models/reprovision_policy.py new file mode 100644 index 000000000..92e777bf6 --- /dev/null +++ b/azext_iot/sdk/dps/service/models/reprovision_policy.py @@ -0,0 +1,48 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ReprovisionPolicy(Model): + """The behavior of the service when a device is re-provisioned to an IoT hub. + + All required parameters must be populated in order to send to Azure. + + :param update_hub_assignment: Required. When set to true (default), the + Device Provisioning Service will evaluate the device's IoT Hub assignment + and update it if necessary for any provisioning requests beyond the first + from a given device. If set to false, the device will stay assigned to its + current IoT hub. Default value: True . + :type update_hub_assignment: bool + :param migrate_device_data: Required. When set to true (default), the + Device Provisioning Service will migrate the device's data (twin, device + capabilities, and device ID) from one IoT hub to another during an IoT hub + assignment update. If set to false, the Device Provisioning Service will + reset the device's data to the initial desired configuration stored in the + corresponding enrollment list. Default value: True . + :type migrate_device_data: bool + """ + + _validation = { + 'update_hub_assignment': {'required': True}, + 'migrate_device_data': {'required': True}, + } + + _attribute_map = { + 'update_hub_assignment': {'key': 'updateHubAssignment', 'type': 'bool'}, + 'migrate_device_data': {'key': 'migrateDeviceData', 'type': 'bool'}, + } + + def __init__(self, **kwargs): + super(ReprovisionPolicy, self).__init__(**kwargs) + self.update_hub_assignment = kwargs.get('update_hub_assignment', True) + self.migrate_device_data = kwargs.get('migrate_device_data', True) diff --git a/azext_iot/sdk/dps/models/reprovision_policy.py b/azext_iot/sdk/dps/service/models/reprovision_policy_py3.py similarity index 60% rename from azext_iot/sdk/dps/models/reprovision_policy.py rename to azext_iot/sdk/dps/service/models/reprovision_policy_py3.py index dafaa1ee0..3a1403f2e 100644 --- a/azext_iot/sdk/dps/models/reprovision_policy.py +++ b/azext_iot/sdk/dps/service/models/reprovision_policy_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -11,14 +15,16 @@ class ReprovisionPolicy(Model): """The behavior of the service when a device is re-provisioned to an IoT hub. - :param update_hub_assignment: When set to true (default), the Device - Provisioning Service will evaluate the device's IoT Hub assignment and - update it if necessary for any provisioning requests beyond the first from - a given device. If set to false, the device will stay assigned to its + All required parameters must be populated in order to send to Azure. + + :param update_hub_assignment: Required. When set to true (default), the + Device Provisioning Service will evaluate the device's IoT Hub assignment + and update it if necessary for any provisioning requests beyond the first + from a given device. If set to false, the device will stay assigned to its current IoT hub. Default value: True . :type update_hub_assignment: bool - :param migrate_device_data: When set to true (default), the Device - Provisioning Service will migrate the device's data (twin, device + :param migrate_device_data: Required. When set to true (default), the + Device Provisioning Service will migrate the device's data (twin, device capabilities, and device ID) from one IoT hub to another during an IoT hub assignment update. If set to false, the Device Provisioning Service will reset the device's data to the initial desired configuration stored in the @@ -36,7 +42,7 @@ class ReprovisionPolicy(Model): 'migrate_device_data': {'key': 'migrateDeviceData', 'type': 'bool'}, } - def __init__(self, update_hub_assignment=True, migrate_device_data=True): - super(ReprovisionPolicy, self).__init__() + def __init__(self, *, update_hub_assignment: bool=True, migrate_device_data: bool=True, **kwargs) -> None: + super(ReprovisionPolicy, self).__init__(**kwargs) self.update_hub_assignment = update_hub_assignment self.migrate_device_data = migrate_device_data diff --git a/azext_iot/sdk/service/models/symmetric_key.py b/azext_iot/sdk/dps/service/models/symmetric_key_attestation.py similarity index 65% rename from azext_iot/sdk/service/models/symmetric_key.py rename to azext_iot/sdk/dps/service/models/symmetric_key_attestation.py index 6820dad37..6e94284c9 100644 --- a/azext_iot/sdk/service/models/symmetric_key.py +++ b/azext_iot/sdk/dps/service/models/symmetric_key_attestation.py @@ -12,12 +12,12 @@ from msrest.serialization import Model -class SymmetricKey(Model): - """SymmetricKey. +class SymmetricKeyAttestation(Model): + """Attestation via SymmetricKey. - :param primary_key: + :param primary_key: Primary symmetric key. :type primary_key: str - :param secondary_key: + :param secondary_key: Secondary symmetric key. :type secondary_key: str """ @@ -26,7 +26,7 @@ class SymmetricKey(Model): 'secondary_key': {'key': 'secondaryKey', 'type': 'str'}, } - def __init__(self, primary_key=None, secondary_key=None): - super(SymmetricKey, self).__init__() - self.primary_key = primary_key - self.secondary_key = secondary_key + def __init__(self, **kwargs): + super(SymmetricKeyAttestation, self).__init__(**kwargs) + self.primary_key = kwargs.get('primary_key', None) + self.secondary_key = kwargs.get('secondary_key', None) diff --git a/azext_iot/sdk/dps/models/symmetric_key_attestation.py b/azext_iot/sdk/dps/service/models/symmetric_key_attestation_py3.py similarity index 72% rename from azext_iot/sdk/dps/models/symmetric_key_attestation.py rename to azext_iot/sdk/dps/service/models/symmetric_key_attestation_py3.py index 28a1ef96a..b2efe3ce4 100644 --- a/azext_iot/sdk/dps/models/symmetric_key_attestation.py +++ b/azext_iot/sdk/dps/service/models/symmetric_key_attestation_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -22,7 +26,7 @@ class SymmetricKeyAttestation(Model): 'secondary_key': {'key': 'secondaryKey', 'type': 'str'}, } - def __init__(self, primary_key=None, secondary_key=None): - super(SymmetricKeyAttestation, self).__init__() + def __init__(self, *, primary_key: str=None, secondary_key: str=None, **kwargs) -> None: + super(SymmetricKeyAttestation, self).__init__(**kwargs) self.primary_key = primary_key self.secondary_key = secondary_key diff --git a/azext_iot/sdk/dps/service/models/tpm_attestation.py b/azext_iot/sdk/dps/service/models/tpm_attestation.py new file mode 100644 index 000000000..1643e80c9 --- /dev/null +++ b/azext_iot/sdk/dps/service/models/tpm_attestation.py @@ -0,0 +1,38 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TpmAttestation(Model): + """Attestation via TPM. + + All required parameters must be populated in order to send to Azure. + + :param endorsement_key: Required. + :type endorsement_key: str + :param storage_root_key: + :type storage_root_key: str + """ + + _validation = { + 'endorsement_key': {'required': True}, + } + + _attribute_map = { + 'endorsement_key': {'key': 'endorsementKey', 'type': 'str'}, + 'storage_root_key': {'key': 'storageRootKey', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(TpmAttestation, self).__init__(**kwargs) + self.endorsement_key = kwargs.get('endorsement_key', None) + self.storage_root_key = kwargs.get('storage_root_key', None) diff --git a/azext_iot/sdk/dps/models/tpm_attestation.py b/azext_iot/sdk/dps/service/models/tpm_attestation_py3.py similarity index 66% rename from azext_iot/sdk/dps/models/tpm_attestation.py rename to azext_iot/sdk/dps/service/models/tpm_attestation_py3.py index aaf7e8b9f..648b364cc 100644 --- a/azext_iot/sdk/dps/models/tpm_attestation.py +++ b/azext_iot/sdk/dps/service/models/tpm_attestation_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -11,7 +15,9 @@ class TpmAttestation(Model): """Attestation via TPM. - :param endorsement_key: + All required parameters must be populated in order to send to Azure. + + :param endorsement_key: Required. :type endorsement_key: str :param storage_root_key: :type storage_root_key: str @@ -26,7 +32,7 @@ class TpmAttestation(Model): 'storage_root_key': {'key': 'storageRootKey', 'type': 'str'}, } - def __init__(self, endorsement_key, storage_root_key=None): - super(TpmAttestation, self).__init__() + def __init__(self, *, endorsement_key: str, storage_root_key: str=None, **kwargs) -> None: + super(TpmAttestation, self).__init__(**kwargs) self.endorsement_key = endorsement_key self.storage_root_key = storage_root_key diff --git a/azext_iot/sdk/dps/service/models/twin_collection.py b/azext_iot/sdk/dps/service/models/twin_collection.py new file mode 100644 index 000000000..3ead028fc --- /dev/null +++ b/azext_iot/sdk/dps/service/models/twin_collection.py @@ -0,0 +1,41 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TwinCollection(Model): + """Represents a collection of properties within a Twin. + + :param additional_properties: Unmatched properties from the message are + deserialized this collection + :type additional_properties: dict[str, object] + :param version: Version of the TwinCollection + :type version: long + :param count: Number of properties in the TwinCollection + :type count: int + :param metadata: Metadata for the TwinCollection + :type metadata: ~dps.models.Metadata + """ + + _attribute_map = { + 'additional_properties': {'key': '', 'type': '{object}'}, + 'version': {'key': 'version', 'type': 'long'}, + 'count': {'key': 'count', 'type': 'int'}, + 'metadata': {'key': 'metadata', 'type': 'Metadata'}, + } + + def __init__(self, **kwargs): + super(TwinCollection, self).__init__(**kwargs) + self.additional_properties = kwargs.get('additional_properties', None) + self.version = kwargs.get('version', None) + self.count = kwargs.get('count', None) + self.metadata = kwargs.get('metadata', None) diff --git a/azext_iot/sdk/dps/models/twin_collection.py b/azext_iot/sdk/dps/service/models/twin_collection_py3.py similarity index 62% rename from azext_iot/sdk/dps/models/twin_collection.py rename to azext_iot/sdk/dps/service/models/twin_collection_py3.py index b3cf49987..232f7dedd 100644 --- a/azext_iot/sdk/dps/models/twin_collection.py +++ b/azext_iot/sdk/dps/service/models/twin_collection_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -9,18 +13,17 @@ class TwinCollection(Model): - """TwinCollection. + """Represents a collection of properties within a Twin. :param additional_properties: Unmatched properties from the message are deserialized this collection :type additional_properties: dict[str, object] - :param version: + :param version: Version of the TwinCollection :type version: long - :param count: + :param count: Number of properties in the TwinCollection :type count: int - :param metadata: - :type metadata: - ~microsoft.azure.management.provisioningservices.models.Metadata + :param metadata: Metadata for the TwinCollection + :type metadata: ~dps.models.Metadata """ _attribute_map = { @@ -30,8 +33,8 @@ class TwinCollection(Model): 'metadata': {'key': 'metadata', 'type': 'Metadata'}, } - def __init__(self, additional_properties=None, version=None, count=None, metadata=None): - super(TwinCollection, self).__init__() + def __init__(self, *, additional_properties=None, version: int=None, count: int=None, metadata=None, **kwargs) -> None: + super(TwinCollection, self).__init__(**kwargs) self.additional_properties = additional_properties self.version = version self.count = count diff --git a/azext_iot/sdk/dps/service/models/x509_attestation.py b/azext_iot/sdk/dps/service/models/x509_attestation.py new file mode 100644 index 000000000..2ffedb0cb --- /dev/null +++ b/azext_iot/sdk/dps/service/models/x509_attestation.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class X509Attestation(Model): + """Attestation via X509. + + :param client_certificates: + :type client_certificates: ~dps.models.X509Certificates + :param signing_certificates: + :type signing_certificates: ~dps.models.X509Certificates + :param ca_references: + :type ca_references: ~dps.models.X509CAReferences + """ + + _attribute_map = { + 'client_certificates': {'key': 'clientCertificates', 'type': 'X509Certificates'}, + 'signing_certificates': {'key': 'signingCertificates', 'type': 'X509Certificates'}, + 'ca_references': {'key': 'caReferences', 'type': 'X509CAReferences'}, + } + + def __init__(self, **kwargs): + super(X509Attestation, self).__init__(**kwargs) + self.client_certificates = kwargs.get('client_certificates', None) + self.signing_certificates = kwargs.get('signing_certificates', None) + self.ca_references = kwargs.get('ca_references', None) diff --git a/azext_iot/sdk/dps/models/x509_attestation.py b/azext_iot/sdk/dps/service/models/x509_attestation_py3.py similarity index 65% rename from azext_iot/sdk/dps/models/x509_attestation.py rename to azext_iot/sdk/dps/service/models/x509_attestation_py3.py index 56cd4d968..46184475c 100644 --- a/azext_iot/sdk/dps/models/x509_attestation.py +++ b/azext_iot/sdk/dps/service/models/x509_attestation_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -12,14 +16,11 @@ class X509Attestation(Model): """Attestation via X509. :param client_certificates: - :type client_certificates: - ~microsoft.azure.management.provisioningservices.models.X509Certificates + :type client_certificates: ~dps.models.X509Certificates :param signing_certificates: - :type signing_certificates: - ~microsoft.azure.management.provisioningservices.models.X509Certificates + :type signing_certificates: ~dps.models.X509Certificates :param ca_references: - :type ca_references: - ~microsoft.azure.management.provisioningservices.models.X509CAReferences + :type ca_references: ~dps.models.X509CAReferences """ _attribute_map = { @@ -28,8 +29,8 @@ class X509Attestation(Model): 'ca_references': {'key': 'caReferences', 'type': 'X509CAReferences'}, } - def __init__(self, client_certificates=None, signing_certificates=None, ca_references=None): - super(X509Attestation, self).__init__() + def __init__(self, *, client_certificates=None, signing_certificates=None, ca_references=None, **kwargs) -> None: + super(X509Attestation, self).__init__(**kwargs) self.client_certificates = client_certificates self.signing_certificates = signing_certificates self.ca_references = ca_references diff --git a/azext_iot/sdk/dps/service/models/x509_ca_references.py b/azext_iot/sdk/dps/service/models/x509_ca_references.py new file mode 100644 index 000000000..be2306bd8 --- /dev/null +++ b/azext_iot/sdk/dps/service/models/x509_ca_references.py @@ -0,0 +1,32 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class X509CAReferences(Model): + """Primary and secondary CA references. + + :param primary: + :type primary: str + :param secondary: + :type secondary: str + """ + + _attribute_map = { + 'primary': {'key': 'primary', 'type': 'str'}, + 'secondary': {'key': 'secondary', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(X509CAReferences, self).__init__(**kwargs) + self.primary = kwargs.get('primary', None) + self.secondary = kwargs.get('secondary', None) diff --git a/azext_iot/sdk/dps/models/x509_ca_references.py b/azext_iot/sdk/dps/service/models/x509_ca_references_py3.py similarity index 71% rename from azext_iot/sdk/dps/models/x509_ca_references.py rename to azext_iot/sdk/dps/service/models/x509_ca_references_py3.py index b08341f52..4a85172ae 100644 --- a/azext_iot/sdk/dps/models/x509_ca_references.py +++ b/azext_iot/sdk/dps/service/models/x509_ca_references_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -22,7 +26,7 @@ class X509CAReferences(Model): 'secondary': {'key': 'secondary', 'type': 'str'}, } - def __init__(self, primary=None, secondary=None): - super(X509CAReferences, self).__init__() + def __init__(self, *, primary: str=None, secondary: str=None, **kwargs) -> None: + super(X509CAReferences, self).__init__(**kwargs) self.primary = primary self.secondary = secondary diff --git a/azext_iot/sdk/dps/service/models/x509_certificate_info.py b/azext_iot/sdk/dps/service/models/x509_certificate_info.py new file mode 100644 index 000000000..2eb783782 --- /dev/null +++ b/azext_iot/sdk/dps/service/models/x509_certificate_info.py @@ -0,0 +1,69 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class X509CertificateInfo(Model): + """X509 certificate info. + + All required parameters must be populated in order to send to Azure. + + :param subject_name: Required. + :type subject_name: str + :param sha1_thumbprint: Required. + :type sha1_thumbprint: str + :param sha256_thumbprint: Required. + :type sha256_thumbprint: str + :param issuer_name: Required. + :type issuer_name: str + :param not_before_utc: Required. + :type not_before_utc: datetime + :param not_after_utc: Required. + :type not_after_utc: datetime + :param serial_number: Required. + :type serial_number: str + :param version: Required. + :type version: int + """ + + _validation = { + 'subject_name': {'required': True}, + 'sha1_thumbprint': {'required': True}, + 'sha256_thumbprint': {'required': True}, + 'issuer_name': {'required': True}, + 'not_before_utc': {'required': True}, + 'not_after_utc': {'required': True}, + 'serial_number': {'required': True}, + 'version': {'required': True}, + } + + _attribute_map = { + 'subject_name': {'key': 'subjectName', 'type': 'str'}, + 'sha1_thumbprint': {'key': 'sha1Thumbprint', 'type': 'str'}, + 'sha256_thumbprint': {'key': 'sha256Thumbprint', 'type': 'str'}, + 'issuer_name': {'key': 'issuerName', 'type': 'str'}, + 'not_before_utc': {'key': 'notBeforeUtc', 'type': 'iso-8601'}, + 'not_after_utc': {'key': 'notAfterUtc', 'type': 'iso-8601'}, + 'serial_number': {'key': 'serialNumber', 'type': 'str'}, + 'version': {'key': 'version', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(X509CertificateInfo, self).__init__(**kwargs) + self.subject_name = kwargs.get('subject_name', None) + self.sha1_thumbprint = kwargs.get('sha1_thumbprint', None) + self.sha256_thumbprint = kwargs.get('sha256_thumbprint', None) + self.issuer_name = kwargs.get('issuer_name', None) + self.not_before_utc = kwargs.get('not_before_utc', None) + self.not_after_utc = kwargs.get('not_after_utc', None) + self.serial_number = kwargs.get('serial_number', None) + self.version = kwargs.get('version', None) diff --git a/azext_iot/sdk/dps/models/x509_certificate_info.py b/azext_iot/sdk/dps/service/models/x509_certificate_info_py3.py similarity index 71% rename from azext_iot/sdk/dps/models/x509_certificate_info.py rename to azext_iot/sdk/dps/service/models/x509_certificate_info_py3.py index 1d0c92ab1..dc1ebb97f 100644 --- a/azext_iot/sdk/dps/models/x509_certificate_info.py +++ b/azext_iot/sdk/dps/service/models/x509_certificate_info_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -11,21 +15,23 @@ class X509CertificateInfo(Model): """X509 certificate info. - :param subject_name: + All required parameters must be populated in order to send to Azure. + + :param subject_name: Required. :type subject_name: str - :param sha1_thumbprint: + :param sha1_thumbprint: Required. :type sha1_thumbprint: str - :param sha256_thumbprint: + :param sha256_thumbprint: Required. :type sha256_thumbprint: str - :param issuer_name: + :param issuer_name: Required. :type issuer_name: str - :param not_before_utc: + :param not_before_utc: Required. :type not_before_utc: datetime - :param not_after_utc: + :param not_after_utc: Required. :type not_after_utc: datetime - :param serial_number: + :param serial_number: Required. :type serial_number: str - :param version: + :param version: Required. :type version: int """ @@ -51,8 +57,8 @@ class X509CertificateInfo(Model): 'version': {'key': 'version', 'type': 'int'}, } - def __init__(self, subject_name, sha1_thumbprint, sha256_thumbprint, issuer_name, not_before_utc, not_after_utc, serial_number, version): - super(X509CertificateInfo, self).__init__() + def __init__(self, *, subject_name: str, sha1_thumbprint: str, sha256_thumbprint: str, issuer_name: str, not_before_utc, not_after_utc, serial_number: str, version: int, **kwargs) -> None: + super(X509CertificateInfo, self).__init__(**kwargs) self.subject_name = subject_name self.sha1_thumbprint = sha1_thumbprint self.sha256_thumbprint = sha256_thumbprint diff --git a/azext_iot/sdk/dps/service/models/x509_certificate_with_info.py b/azext_iot/sdk/dps/service/models/x509_certificate_with_info.py new file mode 100644 index 000000000..36bc3d09e --- /dev/null +++ b/azext_iot/sdk/dps/service/models/x509_certificate_with_info.py @@ -0,0 +1,32 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class X509CertificateWithInfo(Model): + """Certificate and Certificate info. + + :param certificate: + :type certificate: str + :param info: + :type info: ~dps.models.X509CertificateInfo + """ + + _attribute_map = { + 'certificate': {'key': 'certificate', 'type': 'str'}, + 'info': {'key': 'info', 'type': 'X509CertificateInfo'}, + } + + def __init__(self, **kwargs): + super(X509CertificateWithInfo, self).__init__(**kwargs) + self.certificate = kwargs.get('certificate', None) + self.info = kwargs.get('info', None) diff --git a/azext_iot/sdk/dps/models/x509_certificate_with_info.py b/azext_iot/sdk/dps/service/models/x509_certificate_with_info_py3.py similarity index 67% rename from azext_iot/sdk/dps/models/x509_certificate_with_info.py rename to azext_iot/sdk/dps/service/models/x509_certificate_with_info_py3.py index 0704b6e4a..a54d66939 100644 --- a/azext_iot/sdk/dps/models/x509_certificate_with_info.py +++ b/azext_iot/sdk/dps/service/models/x509_certificate_with_info_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -14,8 +18,7 @@ class X509CertificateWithInfo(Model): :param certificate: :type certificate: str :param info: - :type info: - ~microsoft.azure.management.provisioningservices.models.X509CertificateInfo + :type info: ~dps.models.X509CertificateInfo """ _attribute_map = { @@ -23,7 +26,7 @@ class X509CertificateWithInfo(Model): 'info': {'key': 'info', 'type': 'X509CertificateInfo'}, } - def __init__(self, certificate=None, info=None): - super(X509CertificateWithInfo, self).__init__() + def __init__(self, *, certificate: str=None, info=None, **kwargs) -> None: + super(X509CertificateWithInfo, self).__init__(**kwargs) self.certificate = certificate self.info = info diff --git a/azext_iot/sdk/dps/service/models/x509_certificates.py b/azext_iot/sdk/dps/service/models/x509_certificates.py new file mode 100644 index 000000000..c549e0da2 --- /dev/null +++ b/azext_iot/sdk/dps/service/models/x509_certificates.py @@ -0,0 +1,32 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class X509Certificates(Model): + """Primary and secondary certificates. + + :param primary: + :type primary: ~dps.models.X509CertificateWithInfo + :param secondary: + :type secondary: ~dps.models.X509CertificateWithInfo + """ + + _attribute_map = { + 'primary': {'key': 'primary', 'type': 'X509CertificateWithInfo'}, + 'secondary': {'key': 'secondary', 'type': 'X509CertificateWithInfo'}, + } + + def __init__(self, **kwargs): + super(X509Certificates, self).__init__(**kwargs) + self.primary = kwargs.get('primary', None) + self.secondary = kwargs.get('secondary', None) diff --git a/azext_iot/sdk/dps/models/x509_certificates.py b/azext_iot/sdk/dps/service/models/x509_certificates_py3.py similarity index 64% rename from azext_iot/sdk/dps/models/x509_certificates.py rename to azext_iot/sdk/dps/service/models/x509_certificates_py3.py index 8845b060c..08d848954 100644 --- a/azext_iot/sdk/dps/models/x509_certificates.py +++ b/azext_iot/sdk/dps/service/models/x509_certificates_py3.py @@ -1,5 +1,9 @@ # coding=utf-8 # -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# # Code generated by Microsoft (R) AutoRest Code Generator. # Changes may cause incorrect behavior and will be lost if the code is # regenerated. @@ -12,11 +16,9 @@ class X509Certificates(Model): """Primary and secondary certificates. :param primary: - :type primary: - ~microsoft.azure.management.provisioningservices.models.X509CertificateWithInfo + :type primary: ~dps.models.X509CertificateWithInfo :param secondary: - :type secondary: - ~microsoft.azure.management.provisioningservices.models.X509CertificateWithInfo + :type secondary: ~dps.models.X509CertificateWithInfo """ _attribute_map = { @@ -24,7 +26,7 @@ class X509Certificates(Model): 'secondary': {'key': 'secondary', 'type': 'X509CertificateWithInfo'}, } - def __init__(self, primary=None, secondary=None): - super(X509Certificates, self).__init__() + def __init__(self, *, primary=None, secondary=None, **kwargs) -> None: + super(X509Certificates, self).__init__(**kwargs) self.primary = primary self.secondary = secondary diff --git a/azext_iot/sdk/dps/service/provisioning_service_client.py b/azext_iot/sdk/dps/service/provisioning_service_client.py new file mode 100644 index 000000000..d37d114d0 --- /dev/null +++ b/azext_iot/sdk/dps/service/provisioning_service_client.py @@ -0,0 +1,1021 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.service_client import SDKClient +from msrest import Serializer, Deserializer +from msrestazure import AzureConfiguration +from .version import VERSION +from msrest.pipeline import ClientRawResponse +import uuid +from . import models + + +class ProvisioningServiceClientConfiguration(AzureConfiguration): + """Configuration for ProvisioningServiceClient + Note that all parameters used to create this instance are saved as instance + attributes. + + :param credentials: Credentials needed for the client to connect to Azure. + :type credentials: :mod:`A msrestazure Credentials + object` + :param str base_url: Service URL + """ + + def __init__( + self, credentials, base_url=None): + + if credentials is None: + raise ValueError("Parameter 'credentials' must not be None.") + if not base_url: + base_url = 'https://your-dps.azure-devices-provisioning.net' + + super(ProvisioningServiceClientConfiguration, self).__init__(base_url) + + self.add_user_agent('provisioningserviceclient/{}'.format(VERSION)) + + self.credentials = credentials + + +class ProvisioningServiceClient(SDKClient): + """API for service operations with the Azure IoT Hub Device Provisioning Service + + :ivar config: Configuration for client. + :vartype config: ProvisioningServiceClientConfiguration + + :param credentials: Credentials needed for the client to connect to Azure. + :type credentials: :mod:`A msrestazure Credentials + object` + :param str base_url: Service URL + """ + + def __init__( + self, credentials, base_url=None): + + self.config = ProvisioningServiceClientConfiguration(credentials, base_url) + super(ProvisioningServiceClient, self).__init__(self.config.credentials, self.config) + + client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} + self.api_version = '2019-03-31' + self._serialize = Serializer(client_models) + self._deserialize = Deserializer(client_models) + + + def get_individual_enrollment( + self, id, custom_headers=None, raw=False, **operation_config): + """Get a device enrollment record. + + :param id: Registration ID. + :type id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: IndividualEnrollment or ClientRawResponse if raw=true + :rtype: ~dps.models.IndividualEnrollment or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + api_version = "2019-03-31" + + # Construct URL + url = self.get_individual_enrollment.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('IndividualEnrollment', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_individual_enrollment.metadata = {'url': '/enrollments/{id}'} + + def create_or_update_individual_enrollment( + self, id, enrollment, if_match=None, custom_headers=None, raw=False, **operation_config): + """Create or update a device enrollment record. + + :param id: The registration ID is alphanumeric, lowercase, and may + contain hyphens. + :type id: str + :param enrollment: The device enrollment record. + :type enrollment: ~dps.models.IndividualEnrollment + :param if_match: The ETag of the enrollment record. + :type if_match: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: IndividualEnrollment or ClientRawResponse if raw=true + :rtype: ~dps.models.IndividualEnrollment or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + api_version = "2019-03-31" + + # Construct URL + url = self.create_or_update_individual_enrollment.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + body_content = self._serialize.body(enrollment, 'IndividualEnrollment') + + # Construct and send request + request = self._client.put(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('IndividualEnrollment', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + create_or_update_individual_enrollment.metadata = {'url': '/enrollments/{id}'} + + def delete_individual_enrollment( + self, id, if_match=None, custom_headers=None, raw=False, **operation_config): + """Delete a device enrollment record. + + :param id: Registration ID. + :type id: str + :param if_match: The ETag of the enrollment record. + :type if_match: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + api_version = "2019-03-31" + + # Construct URL + url = self.delete_individual_enrollment.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + delete_individual_enrollment.metadata = {'url': '/enrollments/{id}'} + + def get_enrollment_group( + self, id, custom_headers=None, raw=False, **operation_config): + """Get a device enrollment group. + + :param id: Enrollment group ID. + :type id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: EnrollmentGroup or ClientRawResponse if raw=true + :rtype: ~dps.models.EnrollmentGroup or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + api_version = "2019-03-31" + + # Construct URL + url = self.get_enrollment_group.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('EnrollmentGroup', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_enrollment_group.metadata = {'url': '/enrollmentGroups/{id}'} + + def create_or_update_enrollment_group( + self, id, enrollment_group, if_match=None, custom_headers=None, raw=False, **operation_config): + """Create or update a device enrollment group. + + :param id: Enrollment group ID. + :type id: str + :param enrollment_group: The device enrollment group. + :type enrollment_group: ~dps.models.EnrollmentGroup + :param if_match: The ETag of the enrollment record. + :type if_match: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: EnrollmentGroup or ClientRawResponse if raw=true + :rtype: ~dps.models.EnrollmentGroup or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + api_version = "2019-03-31" + + # Construct URL + url = self.create_or_update_enrollment_group.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + body_content = self._serialize.body(enrollment_group, 'EnrollmentGroup') + + # Construct and send request + request = self._client.put(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('EnrollmentGroup', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + create_or_update_enrollment_group.metadata = {'url': '/enrollmentGroups/{id}'} + + def delete_enrollment_group( + self, id, if_match=None, custom_headers=None, raw=False, **operation_config): + """Delete a device enrollment group. + + :param id: Enrollment group ID. + :type id: str + :param if_match: The ETag of the enrollment group record. + :type if_match: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + api_version = "2019-03-31" + + # Construct URL + url = self.delete_enrollment_group.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + delete_enrollment_group.metadata = {'url': '/enrollmentGroups/{id}'} + + def get_device_registration_state( + self, id, custom_headers=None, raw=False, **operation_config): + """Gets the device registration state. + + :param id: Registration ID. + :type id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: DeviceRegistrationState or ClientRawResponse if raw=true + :rtype: ~dps.models.DeviceRegistrationState or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + api_version = "2019-03-31" + + # Construct URL + url = self.get_device_registration_state.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('DeviceRegistrationState', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_device_registration_state.metadata = {'url': '/registrations/{id}'} + + def delete_device_registration_state( + self, id, if_match=None, custom_headers=None, raw=False, **operation_config): + """Deletes the device registration. + + :param id: Registration ID. + :type id: str + :param if_match: The ETag of the registration status record. + :type if_match: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + api_version = "2019-03-31" + + # Construct URL + url = self.delete_device_registration_state.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + delete_device_registration_state.metadata = {'url': '/registrations/{id}'} + + def query_individual_enrollments( + self, query, x_ms_max_item_count=None, x_ms_continuation=None, custom_headers=None, raw=False, **operation_config): + """Query the device enrollment records. + + :param query: The query. + :type query: str + :param x_ms_max_item_count: Page size + :type x_ms_max_item_count: int + :param x_ms_continuation: Continuation token + :type x_ms_continuation: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: list or ClientRawResponse if raw=true + :rtype: list[~dps.models.IndividualEnrollment] or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + query_specification = models.QuerySpecification(query=query) + + api_version = "2019-03-31" + + # Construct URL + url = self.query_individual_enrollments.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if x_ms_max_item_count is not None: + header_parameters['x-ms-max-item-count'] = self._serialize.header("x_ms_max_item_count", x_ms_max_item_count, 'int') + if x_ms_continuation is not None: + header_parameters['x-ms-continuation'] = self._serialize.header("x_ms_continuation", x_ms_continuation, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + body_content = self._serialize.body(query_specification, 'QuerySpecification') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + header_dict = {} + + if response.status_code == 200: + deserialized = self._deserialize('[IndividualEnrollment]', response) + header_dict = { + 'x-ms-continuation': 'str', + 'x-ms-max-item-count': 'int', + 'x-ms-item-type': 'str', + } + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + client_raw_response.add_headers(header_dict) + return client_raw_response + + return deserialized + query_individual_enrollments.metadata = {'url': '/enrollments/query'} + + def get_individual_enrollment_attestation_mechanism( + self, id, custom_headers=None, raw=False, **operation_config): + """Get the attestation mechanism in the device enrollment record. + + :param id: Registration ID. + :type id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: AttestationMechanism or ClientRawResponse if raw=true + :rtype: ~dps.models.AttestationMechanism or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + api_version = "2019-03-31" + + # Construct URL + url = self.get_individual_enrollment_attestation_mechanism.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('AttestationMechanism', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_individual_enrollment_attestation_mechanism.metadata = {'url': '/enrollments/{id}/attestationmechanism'} + + def run_bulk_individual_enrollment_operation( + self, enrollments, mode, custom_headers=None, raw=False, **operation_config): + """Bulk device enrollment operation with maximum of 10 enrollments. + + :param enrollments: Enrollment items + :type enrollments: list[~dps.models.IndividualEnrollment] + :param mode: Operation mode. Possible values include: 'create', + 'update', 'updateIfMatchETag', 'delete' + :type mode: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: BulkEnrollmentOperationResult or ClientRawResponse if + raw=true + :rtype: ~dps.models.BulkEnrollmentOperationResult or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + bulk_operation = models.BulkEnrollmentOperation(enrollments=enrollments, mode=mode) + + api_version = "2019-03-31" + + # Construct URL + url = self.run_bulk_individual_enrollment_operation.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + body_content = self._serialize.body(bulk_operation, 'BulkEnrollmentOperation') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('BulkEnrollmentOperationResult', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + run_bulk_individual_enrollment_operation.metadata = {'url': '/enrollments'} + + def query_enrollment_groups( + self, query, x_ms_max_item_count=None, x_ms_continuation=None, custom_headers=None, raw=False, **operation_config): + """Query the device enrollment groups. + + :param query: The query. + :type query: str + :param x_ms_max_item_count: Page size + :type x_ms_max_item_count: int + :param x_ms_continuation: Continuation token + :type x_ms_continuation: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: list or ClientRawResponse if raw=true + :rtype: list[~dps.models.EnrollmentGroup] or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + query_specification = models.QuerySpecification(query=query) + + api_version = "2019-03-31" + + # Construct URL + url = self.query_enrollment_groups.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if x_ms_max_item_count is not None: + header_parameters['x-ms-max-item-count'] = self._serialize.header("x_ms_max_item_count", x_ms_max_item_count, 'int') + if x_ms_continuation is not None: + header_parameters['x-ms-continuation'] = self._serialize.header("x_ms_continuation", x_ms_continuation, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + body_content = self._serialize.body(query_specification, 'QuerySpecification') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + header_dict = {} + + if response.status_code == 200: + deserialized = self._deserialize('[EnrollmentGroup]', response) + header_dict = { + 'x-ms-continuation': 'str', + 'x-ms-max-item-count': 'int', + 'x-ms-item-type': 'str', + } + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + client_raw_response.add_headers(header_dict) + return client_raw_response + + return deserialized + query_enrollment_groups.metadata = {'url': '/enrollmentGroups/query'} + + def get_enrollment_group_attestation_mechanism( + self, id, custom_headers=None, raw=False, **operation_config): + """Get the attestation mechanism in the device enrollment group record. + + :param id: Enrollment group ID + :type id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: AttestationMechanism or ClientRawResponse if raw=true + :rtype: ~dps.models.AttestationMechanism or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + api_version = "2019-03-31" + + # Construct URL + url = self.get_enrollment_group_attestation_mechanism.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('AttestationMechanism', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_enrollment_group_attestation_mechanism.metadata = {'url': '/enrollmentGroups/{id}/attestationmechanism'} + + def run_bulk_enrollment_group_operation( + self, enrollment_groups, mode, custom_headers=None, raw=False, **operation_config): + """Bulk device enrollment group operation with maximum of 10 groups. + + :param enrollment_groups: Enrollment items + :type enrollment_groups: list[~dps.models.EnrollmentGroup] + :param mode: Operation mode. Possible values include: 'create', + 'update', 'updateIfMatchETag', 'delete' + :type mode: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: BulkEnrollmentGroupOperationResult or ClientRawResponse if + raw=true + :rtype: ~dps.models.BulkEnrollmentGroupOperationResult or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + bulk_operation = models.BulkEnrollmentGroupOperation(enrollment_groups=enrollment_groups, mode=mode) + + api_version = "2019-03-31" + + # Construct URL + url = self.run_bulk_enrollment_group_operation.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + body_content = self._serialize.body(bulk_operation, 'BulkEnrollmentGroupOperation') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('BulkEnrollmentGroupOperationResult', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + run_bulk_enrollment_group_operation.metadata = {'url': '/enrollmentGroups'} + + def query_device_registration_states( + self, id, x_ms_max_item_count=None, x_ms_continuation=None, custom_headers=None, raw=False, **operation_config): + """Gets the registration state of devices in this enrollmentGroup. + + :param id: Enrollment group ID. + :type id: str + :param x_ms_max_item_count: pageSize + :type x_ms_max_item_count: int + :param x_ms_continuation: continuation token + :type x_ms_continuation: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: list or ClientRawResponse if raw=true + :rtype: list[~dps.models.DeviceRegistrationState] or + ~msrest.pipeline.ClientRawResponse + :raises: + :class:`ProvisioningServiceErrorDetailsException` + """ + api_version = "2019-03-31" + + # Construct URL + url = self.query_device_registration_states.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if x_ms_max_item_count is not None: + header_parameters['x-ms-max-item-count'] = self._serialize.header("x_ms_max_item_count", x_ms_max_item_count, 'int') + if x_ms_continuation is not None: + header_parameters['x-ms-continuation'] = self._serialize.header("x_ms_continuation", x_ms_continuation, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + raise models.ProvisioningServiceErrorDetailsException(self._deserialize, response) + + deserialized = None + header_dict = {} + + if response.status_code == 200: + deserialized = self._deserialize('[DeviceRegistrationState]', response) + header_dict = { + 'x-ms-continuation': 'str', + 'x-ms-max-item-count': 'int', + 'x-ms-item-type': 'str', + } + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + client_raw_response.add_headers(header_dict) + return client_raw_response + + return deserialized + query_device_registration_states.metadata = {'url': '/registrations/{id}/query'} diff --git a/azext_iot/sdk/dps/service/version.py b/azext_iot/sdk/dps/service/version.py new file mode 100644 index 000000000..a2a4832f8 --- /dev/null +++ b/azext_iot/sdk/dps/service/version.py @@ -0,0 +1,13 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +VERSION = "2019-03-31" + diff --git a/azext_iot/sdk/dps/version.py b/azext_iot/sdk/dps/version.py deleted file mode 100644 index e6c13ab40..000000000 --- a/azext_iot/sdk/dps/version.py +++ /dev/null @@ -1,9 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -VERSION = "2018-09-01-preview" - diff --git a/azext_iot/sdk/iothub/device/iot_hub_gateway_device_ap_is.py b/azext_iot/sdk/iothub/device/iot_hub_gateway_device_ap_is.py index 3c1ebcf60..ee7cff7ca 100644 --- a/azext_iot/sdk/iothub/device/iot_hub_gateway_device_ap_is.py +++ b/azext_iot/sdk/iothub/device/iot_hub_gateway_device_ap_is.py @@ -15,7 +15,6 @@ from .version import VERSION from .operations.device_operations import DeviceOperations from . import models -from azext_iot.constants import USER_AGENT class IotHubGatewayDeviceAPIsConfiguration(AzureConfiguration): @@ -40,7 +39,6 @@ def __init__( super(IotHubGatewayDeviceAPIsConfiguration, self).__init__(base_url) self.add_user_agent('iothubgatewaydeviceapis/{}'.format(VERSION)) - self.add_user_agent(USER_AGENT) # @digimaun self.credentials = credentials diff --git a/azext_iot/sdk/iothub/service/iot_hub_gateway_service_ap_is.py b/azext_iot/sdk/iothub/service/iot_hub_gateway_service_ap_is.py index 29822f160..29b76a4b1 100644 --- a/azext_iot/sdk/iothub/service/iot_hub_gateway_service_ap_is.py +++ b/azext_iot/sdk/iothub/service/iot_hub_gateway_service_ap_is.py @@ -14,14 +14,15 @@ from msrestazure import AzureConfiguration from .version import VERSION from .operations.configuration_operations import ConfigurationOperations -from .operations.registry_manager_operations import RegistryManagerOperations -from .operations.job_client_operations import JobClientOperations -from .operations.fault_injection_operations import FaultInjectionOperations -from .operations.twin_operations import TwinOperations +from .operations.statistics_operations import StatisticsOperations +from .operations.devices_operations import DevicesOperations +from .operations.bulk_registry_operations import BulkRegistryOperations +from .operations.query_operations import QueryOperations +from .operations.jobs_operations import JobsOperations +from .operations.cloud_to_device_messages_operations import CloudToDeviceMessagesOperations +from .operations.modules_operations import ModulesOperations from .operations.digital_twin_operations import DigitalTwinOperations -from .operations.device_method_operations import DeviceMethodOperations from . import models -from azext_iot.constants import USER_AGENT class IotHubGatewayServiceAPIsConfiguration(AzureConfiguration): @@ -46,7 +47,6 @@ def __init__( super(IotHubGatewayServiceAPIsConfiguration, self).__init__(base_url) self.add_user_agent('iothubgatewayserviceapis/{}'.format(VERSION)) - self.add_user_agent(USER_AGENT) # @digimaun self.credentials = credentials @@ -59,18 +59,24 @@ class IotHubGatewayServiceAPIs(SDKClient): :ivar configuration: Configuration operations :vartype configuration: service.operations.ConfigurationOperations - :ivar registry_manager: RegistryManager operations - :vartype registry_manager: service.operations.RegistryManagerOperations - :ivar job_client: JobClient operations - :vartype job_client: service.operations.JobClientOperations - :ivar fault_injection: FaultInjection operations - :vartype fault_injection: service.operations.FaultInjectionOperations - :ivar twin: Twin operations - :vartype twin: service.operations.TwinOperations + :vartype configuration: azure.operations.ConfigurationOperations + :ivar statistics: Statistics operations + :vartype statistics: azure.operations.StatisticsOperations + :ivar devices: Devices operations + :vartype devices: azure.operations.DevicesOperations + :ivar bulk_registry: BulkRegistry operations + :vartype bulk_registry: azure.operations.BulkRegistryOperations + :ivar query: Query operations + :vartype query: azure.operations.QueryOperations + :ivar jobs: Jobs operations + :vartype jobs: azure.operations.JobsOperations + :ivar cloud_to_device_messages: CloudToDeviceMessages operations + :vartype cloud_to_device_messages: azure.operations.CloudToDeviceMessagesOperations + :ivar modules: Modules operations + :vartype modules: azure.operations.ModulesOperations :ivar digital_twin: DigitalTwin operations + :vartype digital_twin: azure.operations.DigitalTwinOperations :vartype digital_twin: service.operations.DigitalTwinOperations - :ivar device_method: DeviceMethod operations - :vartype device_method: service.operations.DeviceMethodOperations :param credentials: Credentials needed for the client to connect to Azure. :type credentials: :mod:`A msrestazure Credentials @@ -85,21 +91,25 @@ def __init__( super(IotHubGatewayServiceAPIs, self).__init__(self.config.credentials, self.config) client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} - self.api_version = '2019-10-01' + self.api_version = '2020-09-30' self._serialize = Serializer(client_models) self._deserialize = Deserializer(client_models) self.configuration = ConfigurationOperations( self._client, self.config, self._serialize, self._deserialize) - self.registry_manager = RegistryManagerOperations( + self.statistics = StatisticsOperations( self._client, self.config, self._serialize, self._deserialize) - self.job_client = JobClientOperations( + self.devices = DevicesOperations( self._client, self.config, self._serialize, self._deserialize) - self.fault_injection = FaultInjectionOperations( + self.bulk_registry = BulkRegistryOperations( self._client, self.config, self._serialize, self._deserialize) - self.twin = TwinOperations( + self.query = QueryOperations( self._client, self.config, self._serialize, self._deserialize) - self.digital_twin = DigitalTwinOperations( + self.jobs = JobsOperations( + self._client, self.config, self._serialize, self._deserialize) + self.cloud_to_device_messages = CloudToDeviceMessagesOperations( self._client, self.config, self._serialize, self._deserialize) - self.device_method = DeviceMethodOperations( + self.modules = ModulesOperations( + self._client, self.config, self._serialize, self._deserialize) + self.digital_twin = DigitalTwinOperations( self._client, self.config, self._serialize, self._deserialize) diff --git a/azext_iot/sdk/iothub/service/models/__init__.py b/azext_iot/sdk/iothub/service/models/__init__.py index 1c994117b..2f0a44266 100644 --- a/azext_iot/sdk/iothub/service/models/__init__.py +++ b/azext_iot/sdk/iothub/service/models/__init__.py @@ -32,14 +32,6 @@ from .twin_py3 import Twin from .job_properties_py3 import JobProperties from .purge_message_queue_result_py3 import PurgeMessageQueueResult - from .fault_injection_connection_properties_py3 import FaultInjectionConnectionProperties - from .fault_injection_properties_py3 import FaultInjectionProperties - from .desired_state_py3 import DesiredState - from .reported_py3 import Reported - from .desired_py3 import Desired - from .property_py3 import Property - from .interface_py3 import Interface - from .digital_twin_interfaces_py3 import DigitalTwinInterfaces from .cloud_to_device_method_py3 import CloudToDeviceMethod from .job_request_py3 import JobRequest from .device_job_statistics_py3 import DeviceJobStatistics @@ -47,10 +39,6 @@ from .query_result_py3 import QueryResult from .module_py3 import Module from .cloud_to_device_method_result_py3 import CloudToDeviceMethodResult - from .digital_twin_interfaces_patch_interfaces_value_properties_value_desired_py3 import DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired - from .digital_twin_interfaces_patch_interfaces_value_properties_value_py3 import DigitalTwinInterfacesPatchInterfacesValuePropertiesValue - from .digital_twin_interfaces_patch_interfaces_value_py3 import DigitalTwinInterfacesPatchInterfacesValue - from .digital_twin_interfaces_patch_py3 import DigitalTwinInterfacesPatch except (SyntaxError, ImportError): from .configuration_metrics import ConfigurationMetrics from .configuration_content import ConfigurationContent @@ -74,14 +62,6 @@ from .twin import Twin from .job_properties import JobProperties from .purge_message_queue_result import PurgeMessageQueueResult - from .fault_injection_connection_properties import FaultInjectionConnectionProperties - from .fault_injection_properties import FaultInjectionProperties - from .desired_state import DesiredState - from .reported import Reported - from .desired import Desired - from .property import Property - from .interface import Interface - from .digital_twin_interfaces import DigitalTwinInterfaces from .cloud_to_device_method import CloudToDeviceMethod from .job_request import JobRequest from .device_job_statistics import DeviceJobStatistics @@ -89,10 +69,6 @@ from .query_result import QueryResult from .module import Module from .cloud_to_device_method_result import CloudToDeviceMethodResult - from .digital_twin_interfaces_patch_interfaces_value_properties_value_desired import DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired - from .digital_twin_interfaces_patch_interfaces_value_properties_value import DigitalTwinInterfacesPatchInterfacesValuePropertiesValue - from .digital_twin_interfaces_patch_interfaces_value import DigitalTwinInterfacesPatchInterfacesValue - from .digital_twin_interfaces_patch import DigitalTwinInterfacesPatch __all__ = [ 'ConfigurationMetrics', @@ -117,14 +93,6 @@ 'Twin', 'JobProperties', 'PurgeMessageQueueResult', - 'FaultInjectionConnectionProperties', - 'FaultInjectionProperties', - 'DesiredState', - 'Reported', - 'Desired', - 'Property', - 'Interface', - 'DigitalTwinInterfaces', 'CloudToDeviceMethod', 'JobRequest', 'DeviceJobStatistics', @@ -132,8 +100,4 @@ 'QueryResult', 'Module', 'CloudToDeviceMethodResult', - 'DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired', - 'DigitalTwinInterfacesPatchInterfacesValuePropertiesValue', - 'DigitalTwinInterfacesPatchInterfacesValue', - 'DigitalTwinInterfacesPatch', ] diff --git a/azext_iot/sdk/iothub/service/models/authentication_mechanism.py b/azext_iot/sdk/iothub/service/models/authentication_mechanism.py index a255e5e34..c7fd092b2 100644 --- a/azext_iot/sdk/iothub/service/models/authentication_mechanism.py +++ b/azext_iot/sdk/iothub/service/models/authentication_mechanism.py @@ -15,12 +15,15 @@ class AuthenticationMechanism(Model): """AuthenticationMechanism. - :param symmetric_key: + :param symmetric_key: The primary and secondary keys used for SAS based + authentication. :type symmetric_key: ~service.models.SymmetricKey - :param x509_thumbprint: + :param x509_thumbprint: The primary and secondary x509 thumbprints used + for x509 based authentication. :type x509_thumbprint: ~service.models.X509Thumbprint - :param type: Possible values include: 'sas', 'selfSigned', - 'certificateAuthority', 'none' + :param type: The type of authentication used to connect to the service. + Possible values include: 'sas', 'selfSigned', 'certificateAuthority', + 'none' :type type: str or ~service.models.enum """ diff --git a/azext_iot/sdk/iothub/service/models/authentication_mechanism_py3.py b/azext_iot/sdk/iothub/service/models/authentication_mechanism_py3.py index 42df1f0ba..4be15d9c0 100644 --- a/azext_iot/sdk/iothub/service/models/authentication_mechanism_py3.py +++ b/azext_iot/sdk/iothub/service/models/authentication_mechanism_py3.py @@ -15,12 +15,15 @@ class AuthenticationMechanism(Model): """AuthenticationMechanism. - :param symmetric_key: + :param symmetric_key: The primary and secondary keys used for SAS based + authentication. :type symmetric_key: ~service.models.SymmetricKey - :param x509_thumbprint: + :param x509_thumbprint: The primary and secondary x509 thumbprints used + for x509 based authentication. :type x509_thumbprint: ~service.models.X509Thumbprint - :param type: Possible values include: 'sas', 'selfSigned', - 'certificateAuthority', 'none' + :param type: The type of authentication used to connect to the service. + Possible values include: 'sas', 'selfSigned', 'certificateAuthority', + 'none' :type type: str or ~service.models.enum """ diff --git a/azext_iot/sdk/iothub/service/models/bulk_registry_operation_result.py b/azext_iot/sdk/iothub/service/models/bulk_registry_operation_result.py index e3588851f..255d647b1 100644 --- a/azext_iot/sdk/iothub/service/models/bulk_registry_operation_result.py +++ b/azext_iot/sdk/iothub/service/models/bulk_registry_operation_result.py @@ -13,15 +13,13 @@ class BulkRegistryOperationResult(Model): - """Encapsulates the result of a bulk registry operation. + """The result of the bulk registry operation. - :param is_successful: Whether or not the operation was successful. + :param is_successful: The operation result. :type is_successful: bool - :param errors: If the operation was not successful, this contains an array - of DeviceRegistryOperationError objects. + :param errors: The device registry operation errors. :type errors: list[~service.models.DeviceRegistryOperationError] - :param warnings: If the operation was partially successful, this contains - an array of DeviceRegistryOperationWarning objects. + :param warnings: The device registry operation warnings. :type warnings: list[~service.models.DeviceRegistryOperationWarning] """ diff --git a/azext_iot/sdk/iothub/service/models/bulk_registry_operation_result_py3.py b/azext_iot/sdk/iothub/service/models/bulk_registry_operation_result_py3.py index 54bdfeda7..ee3921b12 100644 --- a/azext_iot/sdk/iothub/service/models/bulk_registry_operation_result_py3.py +++ b/azext_iot/sdk/iothub/service/models/bulk_registry_operation_result_py3.py @@ -13,15 +13,13 @@ class BulkRegistryOperationResult(Model): - """Encapsulates the result of a bulk registry operation. + """The result of the bulk registry operation. - :param is_successful: Whether or not the operation was successful. + :param is_successful: The operation result. :type is_successful: bool - :param errors: If the operation was not successful, this contains an array - of DeviceRegistryOperationError objects. + :param errors: The device registry operation errors. :type errors: list[~service.models.DeviceRegistryOperationError] - :param warnings: If the operation was partially successful, this contains - an array of DeviceRegistryOperationWarning objects. + :param warnings: The device registry operation warnings. :type warnings: list[~service.models.DeviceRegistryOperationWarning] """ diff --git a/azext_iot/sdk/iothub/service/models/cloud_to_device_method.py b/azext_iot/sdk/iothub/service/models/cloud_to_device_method.py index ef6e75604..3be40fa66 100644 --- a/azext_iot/sdk/iothub/service/models/cloud_to_device_method.py +++ b/azext_iot/sdk/iothub/service/models/cloud_to_device_method.py @@ -13,11 +13,12 @@ class CloudToDeviceMethod(Model): - """Parameters to execute a direct method on the device. + """The parameters to execute a direct method on the device. - :param method_name: Method to run + :param method_name: The name of the method to execute. :type method_name: str - :param payload: Payload + :param payload: The JSON-formatted direct method payload, up to 128kb in + size. :type payload: object :param response_timeout_in_seconds: :type response_timeout_in_seconds: int diff --git a/azext_iot/sdk/iothub/service/models/cloud_to_device_method_py3.py b/azext_iot/sdk/iothub/service/models/cloud_to_device_method_py3.py index 59227e897..a6cae3425 100644 --- a/azext_iot/sdk/iothub/service/models/cloud_to_device_method_py3.py +++ b/azext_iot/sdk/iothub/service/models/cloud_to_device_method_py3.py @@ -13,11 +13,12 @@ class CloudToDeviceMethod(Model): - """Parameters to execute a direct method on the device. + """The parameters to execute a direct method on the device. - :param method_name: Method to run + :param method_name: The name of the method to execute. :type method_name: str - :param payload: Payload + :param payload: The JSON-formatted direct method payload, up to 128kb in + size. :type payload: object :param response_timeout_in_seconds: :type response_timeout_in_seconds: int diff --git a/azext_iot/sdk/iothub/service/models/configuration.py b/azext_iot/sdk/iothub/service/models/configuration.py index 7ef56c1bf..0f417804f 100644 --- a/azext_iot/sdk/iothub/service/models/configuration.py +++ b/azext_iot/sdk/iothub/service/models/configuration.py @@ -13,30 +13,33 @@ class Configuration(Model): - """Configuration for IotHub devices and modules. + """The configuration for Iot Hub device and module twins. - :param id: Gets Identifier for the configuration + :param id: The unique identifier of the configuration. :type id: str - :param schema_version: Gets Schema version for the configuration + :param schema_version: The schema version of the configuration. :type schema_version: str - :param labels: Gets or sets labels for the configuration + :param labels: The key-value pairs used to describe the configuration. :type labels: dict[str, str] - :param content: Gets or sets Content for the configuration + :param content: The content of the configuration. :type content: ~service.models.ConfigurationContent - :param target_condition: Gets or sets Target Condition for the - configuration + :param target_condition: The query used to define the targeted devices or + modules. The query is based on twin tags and/or reported properties. :type target_condition: str - :param created_time_utc: Gets creation time for the configuration + :param created_time_utc: The creation date and time of the configuration. :type created_time_utc: datetime - :param last_updated_time_utc: Gets last update time for the configuration + :param last_updated_time_utc: The update date and time of the + configuration. :type last_updated_time_utc: datetime - :param priority: Gets or sets Priority for the configuration + :param priority: The priority number assigned to the configuration. :type priority: int - :param system_metrics: System Configuration Metrics + :param system_metrics: The system metrics computed by the IoT Hub that + cannot be customized. :type system_metrics: ~service.models.ConfigurationMetrics - :param metrics: Custom Configuration Metrics + :param metrics: The custom metrics specified by the developer as queries + against twin reported properties. :type metrics: ~service.models.ConfigurationMetrics - :param etag: Gets or sets configuration's ETag + :param etag: The ETag of the configuration. :type etag: str """ diff --git a/azext_iot/sdk/iothub/service/models/configuration_content.py b/azext_iot/sdk/iothub/service/models/configuration_content.py index f480f1487..14f947b84 100644 --- a/azext_iot/sdk/iothub/service/models/configuration_content.py +++ b/azext_iot/sdk/iothub/service/models/configuration_content.py @@ -13,13 +13,13 @@ class ConfigurationContent(Model): - """Configuration Content for Devices or Modules on Edge Devices. + """The configuration content for devices or modules on edge devices. - :param device_content: Gets or sets device Configurations + :param device_content: The device configuration content. :type device_content: dict[str, object] - :param modules_content: Gets or sets Modules Configurations + :param modules_content: The modules configuration content. :type modules_content: dict[str, dict[str, object]] - :param module_content: Gets or sets Module Configurations + :param module_content: The module configuration content. :type module_content: dict[str, object] """ diff --git a/azext_iot/sdk/iothub/service/models/configuration_content_py3.py b/azext_iot/sdk/iothub/service/models/configuration_content_py3.py index 18773bce8..f26eab075 100644 --- a/azext_iot/sdk/iothub/service/models/configuration_content_py3.py +++ b/azext_iot/sdk/iothub/service/models/configuration_content_py3.py @@ -13,13 +13,13 @@ class ConfigurationContent(Model): - """Configuration Content for Devices or Modules on Edge Devices. + """The configuration content for devices or modules on edge devices. - :param device_content: Gets or sets device Configurations + :param device_content: The device configuration content. :type device_content: dict[str, object] - :param modules_content: Gets or sets Modules Configurations + :param modules_content: The modules configuration content. :type modules_content: dict[str, dict[str, object]] - :param module_content: Gets or sets Module Configurations + :param module_content: The module configuration content. :type module_content: dict[str, object] """ diff --git a/azext_iot/sdk/iothub/service/models/configuration_metrics.py b/azext_iot/sdk/iothub/service/models/configuration_metrics.py index 428834007..f37b821d6 100644 --- a/azext_iot/sdk/iothub/service/models/configuration_metrics.py +++ b/azext_iot/sdk/iothub/service/models/configuration_metrics.py @@ -13,11 +13,11 @@ class ConfigurationMetrics(Model): - """Configuration Metrics. + """The configuration metrics for Iot Hub devices and modules. - :param results: + :param results: The results of the metrics collection queries. :type results: dict[str, long] - :param queries: + :param queries: The key-value pairs with queries and their identifier. :type queries: dict[str, str] """ diff --git a/azext_iot/sdk/iothub/service/models/configuration_metrics_py3.py b/azext_iot/sdk/iothub/service/models/configuration_metrics_py3.py index f5afd42a7..546cd3cf6 100644 --- a/azext_iot/sdk/iothub/service/models/configuration_metrics_py3.py +++ b/azext_iot/sdk/iothub/service/models/configuration_metrics_py3.py @@ -13,11 +13,11 @@ class ConfigurationMetrics(Model): - """Configuration Metrics. + """The configuration metrics for Iot Hub devices and modules. - :param results: + :param results: The results of the metrics collection queries. :type results: dict[str, long] - :param queries: + :param queries: The key-value pairs with queries and their identifier. :type queries: dict[str, str] """ diff --git a/azext_iot/sdk/iothub/service/models/configuration_py3.py b/azext_iot/sdk/iothub/service/models/configuration_py3.py index 8d7794a31..bd0424488 100644 --- a/azext_iot/sdk/iothub/service/models/configuration_py3.py +++ b/azext_iot/sdk/iothub/service/models/configuration_py3.py @@ -13,30 +13,33 @@ class Configuration(Model): - """Configuration for IotHub devices and modules. + """The configuration for Iot Hub device and module twins. - :param id: Gets Identifier for the configuration + :param id: The unique identifier of the configuration. :type id: str - :param schema_version: Gets Schema version for the configuration + :param schema_version: The schema version of the configuration. :type schema_version: str - :param labels: Gets or sets labels for the configuration + :param labels: The key-value pairs used to describe the configuration. :type labels: dict[str, str] - :param content: Gets or sets Content for the configuration + :param content: The content of the configuration. :type content: ~service.models.ConfigurationContent - :param target_condition: Gets or sets Target Condition for the - configuration + :param target_condition: The query used to define the targeted devices or + modules. The query is based on twin tags and/or reported properties. :type target_condition: str - :param created_time_utc: Gets creation time for the configuration + :param created_time_utc: The creation date and time of the configuration. :type created_time_utc: datetime - :param last_updated_time_utc: Gets last update time for the configuration + :param last_updated_time_utc: The update date and time of the + configuration. :type last_updated_time_utc: datetime - :param priority: Gets or sets Priority for the configuration + :param priority: The priority number assigned to the configuration. :type priority: int - :param system_metrics: System Configuration Metrics + :param system_metrics: The system metrics computed by the IoT Hub that + cannot be customized. :type system_metrics: ~service.models.ConfigurationMetrics - :param metrics: Custom Configuration Metrics + :param metrics: The custom metrics specified by the developer as queries + against twin reported properties. :type metrics: ~service.models.ConfigurationMetrics - :param etag: Gets or sets configuration's ETag + :param etag: The ETag of the configuration. :type etag: str """ diff --git a/azext_iot/sdk/iothub/service/models/configuration_queries_test_input.py b/azext_iot/sdk/iothub/service/models/configuration_queries_test_input.py index faaaa3c20..c0669c47a 100644 --- a/azext_iot/sdk/iothub/service/models/configuration_queries_test_input.py +++ b/azext_iot/sdk/iothub/service/models/configuration_queries_test_input.py @@ -15,9 +15,11 @@ class ConfigurationQueriesTestInput(Model): """ConfigurationQueriesTestInput. - :param target_condition: + :param target_condition: The query used to define targeted devices or + modules. The query is based on twin tags and/or reported properties. :type target_condition: str - :param custom_metric_queries: + :param custom_metric_queries: The key-value pairs with queries and their + identifier. :type custom_metric_queries: dict[str, str] """ diff --git a/azext_iot/sdk/iothub/service/models/configuration_queries_test_input_py3.py b/azext_iot/sdk/iothub/service/models/configuration_queries_test_input_py3.py index 9723b0ad5..ef88e79de 100644 --- a/azext_iot/sdk/iothub/service/models/configuration_queries_test_input_py3.py +++ b/azext_iot/sdk/iothub/service/models/configuration_queries_test_input_py3.py @@ -15,9 +15,11 @@ class ConfigurationQueriesTestInput(Model): """ConfigurationQueriesTestInput. - :param target_condition: + :param target_condition: The query used to define targeted devices or + modules. The query is based on twin tags and/or reported properties. :type target_condition: str - :param custom_metric_queries: + :param custom_metric_queries: The key-value pairs with queries and their + identifier. :type custom_metric_queries: dict[str, str] """ diff --git a/azext_iot/sdk/iothub/service/models/configuration_queries_test_response.py b/azext_iot/sdk/iothub/service/models/configuration_queries_test_response.py index 7914218ea..9fb4ef83c 100644 --- a/azext_iot/sdk/iothub/service/models/configuration_queries_test_response.py +++ b/azext_iot/sdk/iothub/service/models/configuration_queries_test_response.py @@ -15,9 +15,11 @@ class ConfigurationQueriesTestResponse(Model): """ConfigurationQueriesTestResponse. - :param target_condition_error: + :param target_condition_error: The errors from running the target + condition query. :type target_condition_error: str - :param custom_metric_query_errors: + :param custom_metric_query_errors: The errors from running the custom + metric query. :type custom_metric_query_errors: dict[str, str] """ diff --git a/azext_iot/sdk/iothub/service/models/configuration_queries_test_response_py3.py b/azext_iot/sdk/iothub/service/models/configuration_queries_test_response_py3.py index 462e36e3b..cbd8978d0 100644 --- a/azext_iot/sdk/iothub/service/models/configuration_queries_test_response_py3.py +++ b/azext_iot/sdk/iothub/service/models/configuration_queries_test_response_py3.py @@ -15,9 +15,11 @@ class ConfigurationQueriesTestResponse(Model): """ConfigurationQueriesTestResponse. - :param target_condition_error: + :param target_condition_error: The errors from running the target + condition query. :type target_condition_error: str - :param custom_metric_query_errors: + :param custom_metric_query_errors: The errors from running the custom + metric query. :type custom_metric_query_errors: dict[str, str] """ diff --git a/azext_iot/sdk/iothub/service/models/device.py b/azext_iot/sdk/iothub/service/models/device.py index 550a34a97..21b01b9b9 100644 --- a/azext_iot/sdk/iothub/service/models/device.py +++ b/azext_iot/sdk/iothub/service/models/device.py @@ -15,33 +15,49 @@ class Device(Model): """Device. - :param device_id: + :param device_id: The unique identifier of the device. :type device_id: str - :param generation_id: + :param generation_id: The IoT Hub-generated, case-sensitive string up to + 128 characters long. This value is used to distinguish devices with the + same deviceId, when they have been deleted and re-created. :type generation_id: str - :param etag: + :param etag: The string representing a weak ETag for the device identity, + as per RFC7232. :type etag: str - :param connection_state: Possible values include: 'Disconnected', - 'Connected' + :param connection_state: The state of the device. Possible values include: + 'Disconnected', 'Connected' :type connection_state: str or ~service.models.enum - :param status: Possible values include: 'enabled', 'disabled' + :param status: The status of the device. If the status disabled, a device + cannot connect to the service. Possible values include: 'enabled', + 'disabled' :type status: str or ~service.models.enum - :param status_reason: + :param status_reason: The 128 character-long string that stores the reason + for the device identity status. All UTF-8 characters are allowed. :type status_reason: str - :param connection_state_updated_time: + :param connection_state_updated_time: The date and time the connection + state was last updated. :type connection_state_updated_time: datetime - :param status_updated_time: + :param status_updated_time: The date and time when the status field was + last updated. :type status_updated_time: datetime - :param last_activity_time: + :param last_activity_time: The date and last time the device last + connected, received, or sent a message. :type last_activity_time: datetime - :param cloud_to_device_message_count: + :param cloud_to_device_message_count: The number of cloud-to-device + messages currently queued to be sent to the device. :type cloud_to_device_message_count: int - :param authentication: + :param authentication: The authentication mechanism used by the device. :type authentication: ~service.models.AuthenticationMechanism - :param capabilities: + :param capabilities: The set of capabilities of the device. For example, + if this device is an edge device or not. :type capabilities: ~service.models.DeviceCapabilities - :param device_scope: + :param device_scope: The scope of the device. Auto generated and immutable + for edge devices and modifiable in leaf devices to create child/parent + relationship. :type device_scope: str + :param parent_scopes: The scopes of the upper level edge devices if + applicable. Only available for edge devices. + :type parent_scopes: list[str] """ _attribute_map = { @@ -58,6 +74,7 @@ class Device(Model): 'authentication': {'key': 'authentication', 'type': 'AuthenticationMechanism'}, 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, 'device_scope': {'key': 'deviceScope', 'type': 'str'}, + 'parent_scopes': {'key': 'parentScopes', 'type': '[str]'}, } def __init__(self, **kwargs): @@ -75,3 +92,4 @@ def __init__(self, **kwargs): self.authentication = kwargs.get('authentication', None) self.capabilities = kwargs.get('capabilities', None) self.device_scope = kwargs.get('device_scope', None) + self.parent_scopes = kwargs.get('parent_scopes', None) diff --git a/azext_iot/sdk/iothub/service/models/device_capabilities.py b/azext_iot/sdk/iothub/service/models/device_capabilities.py index bfa503203..4b07f0986 100644 --- a/azext_iot/sdk/iothub/service/models/device_capabilities.py +++ b/azext_iot/sdk/iothub/service/models/device_capabilities.py @@ -13,9 +13,10 @@ class DeviceCapabilities(Model): - """Status of Capabilities enabled on the device. + """The status of capabilities enabled on the device. - :param iot_edge: + :param iot_edge: The property that determines if the device is an edge + device or not. :type iot_edge: bool """ diff --git a/azext_iot/sdk/iothub/service/models/device_capabilities_py3.py b/azext_iot/sdk/iothub/service/models/device_capabilities_py3.py index 3df4b2e26..3cbf0978a 100644 --- a/azext_iot/sdk/iothub/service/models/device_capabilities_py3.py +++ b/azext_iot/sdk/iothub/service/models/device_capabilities_py3.py @@ -13,9 +13,10 @@ class DeviceCapabilities(Model): - """Status of Capabilities enabled on the device. + """The status of capabilities enabled on the device. - :param iot_edge: + :param iot_edge: The property that determines if the device is an edge + device or not. :type iot_edge: bool """ diff --git a/azext_iot/sdk/iothub/service/models/device_job_statistics.py b/azext_iot/sdk/iothub/service/models/device_job_statistics.py index b5ed1fcf0..e8cae4be0 100644 --- a/azext_iot/sdk/iothub/service/models/device_job_statistics.py +++ b/azext_iot/sdk/iothub/service/models/device_job_statistics.py @@ -13,17 +13,17 @@ class DeviceJobStatistics(Model): - """The job counts, e.g., number of failed/succeeded devices. + """The job statistics regarding execution status. - :param device_count: Number of devices in the job + :param device_count: The number of devices targeted by the job. :type device_count: int - :param failed_count: The number of failed jobs + :param failed_count: The number of failed jobs. :type failed_count: int - :param succeeded_count: The number of Successed jobs + :param succeeded_count: The number of succeeded jobs. :type succeeded_count: int - :param running_count: The number of running jobs + :param running_count: The number of running jobs. :type running_count: int - :param pending_count: The number of pending (scheduled) jobs + :param pending_count: The number of pending (scheduled) jobs. :type pending_count: int """ diff --git a/azext_iot/sdk/iothub/service/models/device_job_statistics_py3.py b/azext_iot/sdk/iothub/service/models/device_job_statistics_py3.py index a477aae4c..9b553328e 100644 --- a/azext_iot/sdk/iothub/service/models/device_job_statistics_py3.py +++ b/azext_iot/sdk/iothub/service/models/device_job_statistics_py3.py @@ -13,17 +13,17 @@ class DeviceJobStatistics(Model): - """The job counts, e.g., number of failed/succeeded devices. + """The job statistics regarding execution status. - :param device_count: Number of devices in the job + :param device_count: The number of devices targeted by the job. :type device_count: int - :param failed_count: The number of failed jobs + :param failed_count: The number of failed jobs. :type failed_count: int - :param succeeded_count: The number of Successed jobs + :param succeeded_count: The number of succeeded jobs. :type succeeded_count: int - :param running_count: The number of running jobs + :param running_count: The number of running jobs. :type running_count: int - :param pending_count: The number of pending (scheduled) jobs + :param pending_count: The number of pending (scheduled) jobs. :type pending_count: int """ diff --git a/azext_iot/sdk/iothub/service/models/device_py3.py b/azext_iot/sdk/iothub/service/models/device_py3.py index 8e92917a9..5a677d583 100644 --- a/azext_iot/sdk/iothub/service/models/device_py3.py +++ b/azext_iot/sdk/iothub/service/models/device_py3.py @@ -15,33 +15,49 @@ class Device(Model): """Device. - :param device_id: + :param device_id: The unique identifier of the device. :type device_id: str - :param generation_id: + :param generation_id: The IoT Hub-generated, case-sensitive string up to + 128 characters long. This value is used to distinguish devices with the + same deviceId, when they have been deleted and re-created. :type generation_id: str - :param etag: + :param etag: The string representing a weak ETag for the device identity, + as per RFC7232. :type etag: str - :param connection_state: Possible values include: 'Disconnected', - 'Connected' + :param connection_state: The state of the device. Possible values include: + 'Disconnected', 'Connected' :type connection_state: str or ~service.models.enum - :param status: Possible values include: 'enabled', 'disabled' + :param status: The status of the device. If the status disabled, a device + cannot connect to the service. Possible values include: 'enabled', + 'disabled' :type status: str or ~service.models.enum - :param status_reason: + :param status_reason: The 128 character-long string that stores the reason + for the device identity status. All UTF-8 characters are allowed. :type status_reason: str - :param connection_state_updated_time: + :param connection_state_updated_time: The date and time the connection + state was last updated. :type connection_state_updated_time: datetime - :param status_updated_time: + :param status_updated_time: The date and time when the status field was + last updated. :type status_updated_time: datetime - :param last_activity_time: + :param last_activity_time: The date and last time the device last + connected, received, or sent a message. :type last_activity_time: datetime - :param cloud_to_device_message_count: + :param cloud_to_device_message_count: The number of cloud-to-device + messages currently queued to be sent to the device. :type cloud_to_device_message_count: int - :param authentication: + :param authentication: The authentication mechanism used by the device. :type authentication: ~service.models.AuthenticationMechanism - :param capabilities: + :param capabilities: The set of capabilities of the device. For example, + if this device is an edge device or not. :type capabilities: ~service.models.DeviceCapabilities - :param device_scope: + :param device_scope: The scope of the device. Auto generated and immutable + for edge devices and modifiable in leaf devices to create child/parent + relationship. :type device_scope: str + :param parent_scopes: The scopes of the upper level edge devices if + applicable. Only available for edge devices. + :type parent_scopes: list[str] """ _attribute_map = { @@ -58,9 +74,10 @@ class Device(Model): 'authentication': {'key': 'authentication', 'type': 'AuthenticationMechanism'}, 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, 'device_scope': {'key': 'deviceScope', 'type': 'str'}, + 'parent_scopes': {'key': 'parentScopes', 'type': '[str]'}, } - def __init__(self, *, device_id: str=None, generation_id: str=None, etag: str=None, connection_state=None, status=None, status_reason: str=None, connection_state_updated_time=None, status_updated_time=None, last_activity_time=None, cloud_to_device_message_count: int=None, authentication=None, capabilities=None, device_scope: str=None, **kwargs) -> None: + def __init__(self, *, device_id: str=None, generation_id: str=None, etag: str=None, connection_state=None, status=None, status_reason: str=None, connection_state_updated_time=None, status_updated_time=None, last_activity_time=None, cloud_to_device_message_count: int=None, authentication=None, capabilities=None, device_scope: str=None, parent_scopes=None, **kwargs) -> None: super(Device, self).__init__(**kwargs) self.device_id = device_id self.generation_id = generation_id @@ -75,3 +92,4 @@ def __init__(self, *, device_id: str=None, generation_id: str=None, etag: str=No self.authentication = authentication self.capabilities = capabilities self.device_scope = device_scope + self.parent_scopes = parent_scopes diff --git a/azext_iot/sdk/iothub/service/models/device_registry_operation_error.py b/azext_iot/sdk/iothub/service/models/device_registry_operation_error.py index bf4e128f2..b042e9947 100644 --- a/azext_iot/sdk/iothub/service/models/device_registry_operation_error.py +++ b/azext_iot/sdk/iothub/service/models/device_registry_operation_error.py @@ -13,14 +13,14 @@ class DeviceRegistryOperationError(Model): - """Encapsulates device registry operation error details. + """The device registry operation error details. - :param device_id: The ID of the device that indicated the error. + :param device_id: The unique identifier of the device. :type device_id: str - :param error_code: ErrorCode associated with the error. Possible values - include: 'InvalidErrorCode', 'GenericBadRequest', - 'InvalidProtocolVersion', 'DeviceInvalidResultCount', 'InvalidOperation', - 'ArgumentInvalid', 'ArgumentNull', 'IotHubFormatError', + :param error_code: The error code. Possible values include: + 'InvalidErrorCode', 'GenericBadRequest', 'InvalidProtocolVersion', + 'DeviceInvalidResultCount', 'InvalidOperation', 'ArgumentInvalid', + 'ArgumentNull', 'IotHubFormatError', 'DeviceStorageEntitySerializationError', 'BlobContainerValidationError', 'ImportWarningExistsError', 'InvalidSchemaVersion', 'DeviceDefinedMultipleTimes', 'DeserializationError', @@ -32,7 +32,8 @@ class DeviceRegistryOperationError(Model): 'RequestTimedOut', 'UnsupportedOperationOnReplica', 'NullMessage', 'ConnectionForcefullyClosedOnNewConnection', 'InvalidDeviceScope', 'ConnectionForcefullyClosedOnFaultInjection', - 'ConnectionRejectedOnFaultInjection', 'InvalidRouteTestInput', + 'ConnectionRejectedOnFaultInjection', 'InvalidEndpointAuthenticationType', + 'ManagedIdentityNotEnabled', 'InvalidRouteTestInput', 'InvalidSourceOnRoute', 'RoutingNotEnabled', 'InvalidContentEncodingOrType', 'InvalidEndorsementKey', 'InvalidRegistrationId', 'InvalidStorageRootKey', @@ -46,8 +47,9 @@ class DeviceRegistryOperationError(Model): 'InvalidConfigurationCustomMetricsQuery', 'InvalidPnPInterfaceDefinition', 'InvalidPnPDesiredProperties', 'InvalidPnPReportedProperties', 'InvalidPnPWritableReportedProperties', 'InvalidDigitalTwinJsonPatch', - 'GenericUnauthorized', 'IotHubNotFound', 'IotHubUnauthorizedAccess', - 'IotHubUnauthorized', 'ElasticPoolNotFound', + 'InvalidDigitalTwinPayload', 'InvalidDigitalTwinPatch', + 'InvalidDigitalTwinPatchPath', 'GenericUnauthorized', 'IotHubNotFound', + 'IotHubUnauthorizedAccess', 'IotHubUnauthorized', 'ElasticPoolNotFound', 'SystemModuleModifyUnauthorizedAccess', 'GenericForbidden', 'IotHubSuspended', 'IotHubQuotaExceeded', 'JobQuotaExceeded', 'DeviceMaximumQueueDepthExceeded', 'IotHubMaxCbsTokenExceeded', @@ -105,7 +107,9 @@ class DeviceRegistryOperationError(Model): 'UnexpectedPropertyValue', 'OrchestrationOperationFailed', 'ModelRepoEndpointError', 'ResolutionError', 'UnableToFetchCredentials', 'UnableToFetchTenantInfo', 'UnableToShareIdentity', - 'UnableToExpandDiscoveryInfo', 'GenericBadGateway', + 'UnableToExpandDiscoveryInfo', 'UnableToExpandComponentInfo', + 'UnableToCompressComponentInfo', 'UnableToCompressDiscoveryInfo', + 'OrphanDiscoveryDocument', 'GenericBadGateway', 'InvalidResponseWhileProxying', 'GenericServiceUnavailable', 'ServiceUnavailable', 'PartitionNotFound', 'IotHubActivationFailed', 'ServerBusy', 'IotHubRestoring', 'ReceiveLinkOpensThrottled', @@ -113,11 +117,11 @@ class DeviceRegistryOperationError(Model): 'GroupNotAvailable', 'HostingServiceNotAvailable', 'GenericGatewayTimeout', 'GatewayTimeout' :type error_code: str or ~service.models.enum - :param error_status: Additional details associated with the error. + :param error_status: The details of the error. :type error_status: str - :param module_id: + :param module_id: The unique identifier of the module, if applicable. :type module_id: str - :param operation: + :param operation: The type of the operation that failed. :type operation: str """ diff --git a/azext_iot/sdk/iothub/service/models/device_registry_operation_error_py3.py b/azext_iot/sdk/iothub/service/models/device_registry_operation_error_py3.py index 598439a79..2b5a95e19 100644 --- a/azext_iot/sdk/iothub/service/models/device_registry_operation_error_py3.py +++ b/azext_iot/sdk/iothub/service/models/device_registry_operation_error_py3.py @@ -13,14 +13,14 @@ class DeviceRegistryOperationError(Model): - """Encapsulates device registry operation error details. + """The device registry operation error details. - :param device_id: The ID of the device that indicated the error. + :param device_id: The unique identifier of the device. :type device_id: str - :param error_code: ErrorCode associated with the error. Possible values - include: 'InvalidErrorCode', 'GenericBadRequest', - 'InvalidProtocolVersion', 'DeviceInvalidResultCount', 'InvalidOperation', - 'ArgumentInvalid', 'ArgumentNull', 'IotHubFormatError', + :param error_code: The error code. Possible values include: + 'InvalidErrorCode', 'GenericBadRequest', 'InvalidProtocolVersion', + 'DeviceInvalidResultCount', 'InvalidOperation', 'ArgumentInvalid', + 'ArgumentNull', 'IotHubFormatError', 'DeviceStorageEntitySerializationError', 'BlobContainerValidationError', 'ImportWarningExistsError', 'InvalidSchemaVersion', 'DeviceDefinedMultipleTimes', 'DeserializationError', @@ -32,7 +32,8 @@ class DeviceRegistryOperationError(Model): 'RequestTimedOut', 'UnsupportedOperationOnReplica', 'NullMessage', 'ConnectionForcefullyClosedOnNewConnection', 'InvalidDeviceScope', 'ConnectionForcefullyClosedOnFaultInjection', - 'ConnectionRejectedOnFaultInjection', 'InvalidRouteTestInput', + 'ConnectionRejectedOnFaultInjection', 'InvalidEndpointAuthenticationType', + 'ManagedIdentityNotEnabled', 'InvalidRouteTestInput', 'InvalidSourceOnRoute', 'RoutingNotEnabled', 'InvalidContentEncodingOrType', 'InvalidEndorsementKey', 'InvalidRegistrationId', 'InvalidStorageRootKey', @@ -46,8 +47,9 @@ class DeviceRegistryOperationError(Model): 'InvalidConfigurationCustomMetricsQuery', 'InvalidPnPInterfaceDefinition', 'InvalidPnPDesiredProperties', 'InvalidPnPReportedProperties', 'InvalidPnPWritableReportedProperties', 'InvalidDigitalTwinJsonPatch', - 'GenericUnauthorized', 'IotHubNotFound', 'IotHubUnauthorizedAccess', - 'IotHubUnauthorized', 'ElasticPoolNotFound', + 'InvalidDigitalTwinPayload', 'InvalidDigitalTwinPatch', + 'InvalidDigitalTwinPatchPath', 'GenericUnauthorized', 'IotHubNotFound', + 'IotHubUnauthorizedAccess', 'IotHubUnauthorized', 'ElasticPoolNotFound', 'SystemModuleModifyUnauthorizedAccess', 'GenericForbidden', 'IotHubSuspended', 'IotHubQuotaExceeded', 'JobQuotaExceeded', 'DeviceMaximumQueueDepthExceeded', 'IotHubMaxCbsTokenExceeded', @@ -105,7 +107,9 @@ class DeviceRegistryOperationError(Model): 'UnexpectedPropertyValue', 'OrchestrationOperationFailed', 'ModelRepoEndpointError', 'ResolutionError', 'UnableToFetchCredentials', 'UnableToFetchTenantInfo', 'UnableToShareIdentity', - 'UnableToExpandDiscoveryInfo', 'GenericBadGateway', + 'UnableToExpandDiscoveryInfo', 'UnableToExpandComponentInfo', + 'UnableToCompressComponentInfo', 'UnableToCompressDiscoveryInfo', + 'OrphanDiscoveryDocument', 'GenericBadGateway', 'InvalidResponseWhileProxying', 'GenericServiceUnavailable', 'ServiceUnavailable', 'PartitionNotFound', 'IotHubActivationFailed', 'ServerBusy', 'IotHubRestoring', 'ReceiveLinkOpensThrottled', @@ -113,11 +117,11 @@ class DeviceRegistryOperationError(Model): 'GroupNotAvailable', 'HostingServiceNotAvailable', 'GenericGatewayTimeout', 'GatewayTimeout' :type error_code: str or ~service.models.enum - :param error_status: Additional details associated with the error. + :param error_status: The details of the error. :type error_status: str - :param module_id: + :param module_id: The unique identifier of the module, if applicable. :type module_id: str - :param operation: + :param operation: The type of the operation that failed. :type operation: str """ diff --git a/azext_iot/sdk/iothub/service/models/device_registry_operation_warning.py b/azext_iot/sdk/iothub/service/models/device_registry_operation_warning.py index 470907b68..e1cc3b556 100644 --- a/azext_iot/sdk/iothub/service/models/device_registry_operation_warning.py +++ b/azext_iot/sdk/iothub/service/models/device_registry_operation_warning.py @@ -13,14 +13,14 @@ class DeviceRegistryOperationWarning(Model): - """Encapsulates device registry operation error details. + """The device registry operation warning details. - :param device_id: The ID of the device that indicated the warning. + :param device_id: The unique identifier of the device. :type device_id: str - :param warning_code: Possible values include: + :param warning_code: The warning code. Possible values include: 'DeviceRegisteredWithoutTwin' :type warning_code: str or ~service.models.enum - :param warning_status: Additional details associated with the warning. + :param warning_status: The details of the warning. :type warning_status: str """ diff --git a/azext_iot/sdk/iothub/service/models/device_registry_operation_warning_py3.py b/azext_iot/sdk/iothub/service/models/device_registry_operation_warning_py3.py index 03ed5427a..1350e125d 100644 --- a/azext_iot/sdk/iothub/service/models/device_registry_operation_warning_py3.py +++ b/azext_iot/sdk/iothub/service/models/device_registry_operation_warning_py3.py @@ -13,14 +13,14 @@ class DeviceRegistryOperationWarning(Model): - """Encapsulates device registry operation error details. + """The device registry operation warning details. - :param device_id: The ID of the device that indicated the warning. + :param device_id: The unique identifier of the device. :type device_id: str - :param warning_code: Possible values include: + :param warning_code: The warning code. Possible values include: 'DeviceRegisteredWithoutTwin' :type warning_code: str or ~service.models.enum - :param warning_status: Additional details associated with the warning. + :param warning_status: The details of the warning. :type warning_status: str """ diff --git a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_py3.py b/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_py3.py deleted file mode 100644 index 3ca3eb190..000000000 --- a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_py3.py +++ /dev/null @@ -1,29 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DigitalTwinInterfacesPatchInterfacesValuePropertiesValue(Model): - """DigitalTwinInterfacesPatchInterfacesValuePropertiesValue. - - :param desired: - :type desired: - ~service.models.DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired - """ - - _attribute_map = { - 'desired': {'key': 'desired', 'type': 'DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired'}, - } - - def __init__(self, *, desired=None, **kwargs) -> None: - super(DigitalTwinInterfacesPatchInterfacesValuePropertiesValue, self).__init__(**kwargs) - self.desired = desired diff --git a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_py3.py b/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_py3.py deleted file mode 100644 index b0d87d685..000000000 --- a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_py3.py +++ /dev/null @@ -1,29 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DigitalTwinInterfacesPatchInterfacesValue(Model): - """DigitalTwinInterfacesPatchInterfacesValue. - - :param properties: List of properties to update in an interface. - :type properties: dict[str, - ~service.models.DigitalTwinInterfacesPatchInterfacesValuePropertiesValue] - """ - - _attribute_map = { - 'properties': {'key': 'properties', 'type': '{DigitalTwinInterfacesPatchInterfacesValuePropertiesValue}'}, - } - - def __init__(self, *, properties=None, **kwargs) -> None: - super(DigitalTwinInterfacesPatchInterfacesValue, self).__init__(**kwargs) - self.properties = properties diff --git a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_py3.py b/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_py3.py deleted file mode 100644 index 4529fd90f..000000000 --- a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_py3.py +++ /dev/null @@ -1,29 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DigitalTwinInterfacesPatch(Model): - """DigitalTwinInterfacesPatch. - - :param interfaces: Interface(s) data to patch in the digital twin. - :type interfaces: dict[str, - ~service.models.DigitalTwinInterfacesPatchInterfacesValue] - """ - - _attribute_map = { - 'interfaces': {'key': 'interfaces', 'type': '{DigitalTwinInterfacesPatchInterfacesValue}'}, - } - - def __init__(self, *, interfaces=None, **kwargs) -> None: - super(DigitalTwinInterfacesPatch, self).__init__(**kwargs) - self.interfaces = interfaces diff --git a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_py3.py b/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_py3.py deleted file mode 100644 index db386d3cd..000000000 --- a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_py3.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DigitalTwinInterfaces(Model): - """DigitalTwinInterfaces. - - :param interfaces: Interface(s) data on the digital twin. - :type interfaces: dict[str, ~service.models.Interface] - :param version: Version of digital twin. - :type version: long - """ - - _attribute_map = { - 'interfaces': {'key': 'interfaces', 'type': '{Interface}'}, - 'version': {'key': 'version', 'type': 'long'}, - } - - def __init__(self, *, interfaces=None, version: int=None, **kwargs) -> None: - super(DigitalTwinInterfaces, self).__init__(**kwargs) - self.interfaces = interfaces - self.version = version diff --git a/azext_iot/sdk/iothub/service/models/export_import_device.py b/azext_iot/sdk/iothub/service/models/export_import_device.py index 2d00b7cc2..335eecb8f 100644 --- a/azext_iot/sdk/iothub/service/models/export_import_device.py +++ b/azext_iot/sdk/iothub/service/models/export_import_device.py @@ -15,38 +15,46 @@ class ExportImportDevice(Model): """ExportImportDevice. - :param id: Device Id is always required + :param id: The unique identifier of the device. :type id: str - :param module_id: ModuleId is applicable to modules only + :param module_id: The unique identifier of the module, if applicable. :type module_id: str - :param e_tag: ETag parameter is only used for pre-conditioning the update - when importMode is updateIfMatchETag + :param e_tag: The string representing a weak ETag for the device RFC7232. + The value is only used if import mode is updateIfMatchETag, in that case + the import operation is performed only if this ETag matches the value + maintained by the server. :type e_tag: str - :param import_mode: Possible values include: 'create', 'update', - 'updateIfMatchETag', 'delete', 'deleteIfMatchETag', 'updateTwin', - 'updateTwinIfMatchETag' + :param import_mode: The type of registry operation and ETag preferences. + Possible values include: 'create', 'update', 'updateIfMatchETag', + 'delete', 'deleteIfMatchETag', 'updateTwin', 'updateTwinIfMatchETag' :type import_mode: str or ~service.models.enum - :param status: Status is optional and defaults to enabled. Possible values - include: 'enabled', 'disabled' + :param status: The status of the module. If disabled, the module cannot + connect to the service. Possible values include: 'enabled', 'disabled' :type status: str or ~service.models.enum - :param status_reason: + :param status_reason: The 128 character-long string that stores the reason + for the device identity status. All UTF-8 characters are allowed. :type status_reason: str - :param authentication: Authentication parameter is optional and defaults - to SAS if not provided. In that case, we auto-generate primary/secondary - access keys + :param authentication: The authentication mechanism used by the module. + This parameter is optional and defaults to SAS if not provided. In that + case, primary/secondary access keys are auto-generated. :type authentication: ~service.models.AuthenticationMechanism - :param twin_etag: twinETag parameter is only used for pre-conditioning the - update when importMode is updateTwinIfMatchETag + :param twin_etag: The string representing a weak ETag for the device twin + RFC7232. The value is only used if import mode is updateIfMatchETag, in + that case the import operation is performed only if this ETag matches the + value maintained by the server. :type twin_etag: str - :param tags: + :param tags: The JSON document read and written by the solution back end. + The tags are not visible to device apps. :type tags: dict[str, object] - :param properties: Properties are optional and defaults to empty object + :param properties: The desired and reported properties for the device. :type properties: ~service.models.PropertyContainer - :param capabilities: Capabilities param is optional and defaults to no - capability + :param capabilities: The status of capabilities enabled on the device. :type capabilities: ~service.models.DeviceCapabilities - :param device_scope: + :param device_scope: The scope of the device. :type device_scope: str + :param parent_scopes: The scopes of the upper level edge devices if + applicable. Only available for edge devices. + :type parent_scopes: list[str] """ _attribute_map = { @@ -62,6 +70,7 @@ class ExportImportDevice(Model): 'properties': {'key': 'properties', 'type': 'PropertyContainer'}, 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, 'device_scope': {'key': 'deviceScope', 'type': 'str'}, + 'parent_scopes': {'key': 'parentScopes', 'type': '[str]'}, } def __init__(self, **kwargs): @@ -78,3 +87,4 @@ def __init__(self, **kwargs): self.properties = kwargs.get('properties', None) self.capabilities = kwargs.get('capabilities', None) self.device_scope = kwargs.get('device_scope', None) + self.parent_scopes = kwargs.get('parent_scopes', None) diff --git a/azext_iot/sdk/iothub/service/models/export_import_device_py3.py b/azext_iot/sdk/iothub/service/models/export_import_device_py3.py index cb7da1440..39332be96 100644 --- a/azext_iot/sdk/iothub/service/models/export_import_device_py3.py +++ b/azext_iot/sdk/iothub/service/models/export_import_device_py3.py @@ -15,38 +15,46 @@ class ExportImportDevice(Model): """ExportImportDevice. - :param id: Device Id is always required + :param id: The unique identifier of the device. :type id: str - :param module_id: ModuleId is applicable to modules only + :param module_id: The unique identifier of the module, if applicable. :type module_id: str - :param e_tag: ETag parameter is only used for pre-conditioning the update - when importMode is updateIfMatchETag + :param e_tag: The string representing a weak ETag for the device RFC7232. + The value is only used if import mode is updateIfMatchETag, in that case + the import operation is performed only if this ETag matches the value + maintained by the server. :type e_tag: str - :param import_mode: Possible values include: 'create', 'update', - 'updateIfMatchETag', 'delete', 'deleteIfMatchETag', 'updateTwin', - 'updateTwinIfMatchETag' + :param import_mode: The type of registry operation and ETag preferences. + Possible values include: 'create', 'update', 'updateIfMatchETag', + 'delete', 'deleteIfMatchETag', 'updateTwin', 'updateTwinIfMatchETag' :type import_mode: str or ~service.models.enum - :param status: Status is optional and defaults to enabled. Possible values - include: 'enabled', 'disabled' + :param status: The status of the module. If disabled, the module cannot + connect to the service. Possible values include: 'enabled', 'disabled' :type status: str or ~service.models.enum - :param status_reason: + :param status_reason: The 128 character-long string that stores the reason + for the device identity status. All UTF-8 characters are allowed. :type status_reason: str - :param authentication: Authentication parameter is optional and defaults - to SAS if not provided. In that case, we auto-generate primary/secondary - access keys + :param authentication: The authentication mechanism used by the module. + This parameter is optional and defaults to SAS if not provided. In that + case, primary/secondary access keys are auto-generated. :type authentication: ~service.models.AuthenticationMechanism - :param twin_etag: twinETag parameter is only used for pre-conditioning the - update when importMode is updateTwinIfMatchETag + :param twin_etag: The string representing a weak ETag for the device twin + RFC7232. The value is only used if import mode is updateIfMatchETag, in + that case the import operation is performed only if this ETag matches the + value maintained by the server. :type twin_etag: str - :param tags: + :param tags: The JSON document read and written by the solution back end. + The tags are not visible to device apps. :type tags: dict[str, object] - :param properties: Properties are optional and defaults to empty object + :param properties: The desired and reported properties for the device. :type properties: ~service.models.PropertyContainer - :param capabilities: Capabilities param is optional and defaults to no - capability + :param capabilities: The status of capabilities enabled on the device. :type capabilities: ~service.models.DeviceCapabilities - :param device_scope: + :param device_scope: The scope of the device. :type device_scope: str + :param parent_scopes: The scopes of the upper level edge devices if + applicable. Only available for edge devices. + :type parent_scopes: list[str] """ _attribute_map = { @@ -62,9 +70,10 @@ class ExportImportDevice(Model): 'properties': {'key': 'properties', 'type': 'PropertyContainer'}, 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, 'device_scope': {'key': 'deviceScope', 'type': 'str'}, + 'parent_scopes': {'key': 'parentScopes', 'type': '[str]'}, } - def __init__(self, *, id: str=None, module_id: str=None, e_tag: str=None, import_mode=None, status=None, status_reason: str=None, authentication=None, twin_etag: str=None, tags=None, properties=None, capabilities=None, device_scope: str=None, **kwargs) -> None: + def __init__(self, *, id: str=None, module_id: str=None, e_tag: str=None, import_mode=None, status=None, status_reason: str=None, authentication=None, twin_etag: str=None, tags=None, properties=None, capabilities=None, device_scope: str=None, parent_scopes=None, **kwargs) -> None: super(ExportImportDevice, self).__init__(**kwargs) self.id = id self.module_id = module_id @@ -78,3 +87,4 @@ def __init__(self, *, id: str=None, module_id: str=None, e_tag: str=None, import self.properties = properties self.capabilities = capabilities self.device_scope = device_scope + self.parent_scopes = parent_scopes diff --git a/azext_iot/sdk/iothub/service/models/fault_injection_connection_properties.py b/azext_iot/sdk/iothub/service/models/fault_injection_connection_properties.py deleted file mode 100644 index 8c542d177..000000000 --- a/azext_iot/sdk/iothub/service/models/fault_injection_connection_properties.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class FaultInjectionConnectionProperties(Model): - """FaultInjectionConnectionProperties. - - :param action: Possible values include: 'None', 'CloseAll', 'Periodic' - :type action: str or ~service.models.enum - :param block_duration_in_minutes: - :type block_duration_in_minutes: int - """ - - _attribute_map = { - 'action': {'key': 'action', 'type': 'str'}, - 'block_duration_in_minutes': {'key': 'blockDurationInMinutes', 'type': 'int'}, - } - - def __init__(self, **kwargs): - super(FaultInjectionConnectionProperties, self).__init__(**kwargs) - self.action = kwargs.get('action', None) - self.block_duration_in_minutes = kwargs.get('block_duration_in_minutes', None) diff --git a/azext_iot/sdk/iothub/service/models/fault_injection_connection_properties_py3.py b/azext_iot/sdk/iothub/service/models/fault_injection_connection_properties_py3.py deleted file mode 100644 index 6fc2cca49..000000000 --- a/azext_iot/sdk/iothub/service/models/fault_injection_connection_properties_py3.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class FaultInjectionConnectionProperties(Model): - """FaultInjectionConnectionProperties. - - :param action: Possible values include: 'None', 'CloseAll', 'Periodic' - :type action: str or ~service.models.enum - :param block_duration_in_minutes: - :type block_duration_in_minutes: int - """ - - _attribute_map = { - 'action': {'key': 'action', 'type': 'str'}, - 'block_duration_in_minutes': {'key': 'blockDurationInMinutes', 'type': 'int'}, - } - - def __init__(self, *, action=None, block_duration_in_minutes: int=None, **kwargs) -> None: - super(FaultInjectionConnectionProperties, self).__init__(**kwargs) - self.action = action - self.block_duration_in_minutes = block_duration_in_minutes diff --git a/azext_iot/sdk/iothub/service/models/fault_injection_properties.py b/azext_iot/sdk/iothub/service/models/fault_injection_properties.py deleted file mode 100644 index be83309e5..000000000 --- a/azext_iot/sdk/iothub/service/models/fault_injection_properties.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class FaultInjectionProperties(Model): - """FaultInjectionProperties. - - :param iot_hub_name: - :type iot_hub_name: str - :param connection: - :type connection: ~service.models.FaultInjectionConnectionProperties - :param last_updated_time_utc: Service generated. - :type last_updated_time_utc: datetime - """ - - _attribute_map = { - 'iot_hub_name': {'key': 'IotHubName', 'type': 'str'}, - 'connection': {'key': 'connection', 'type': 'FaultInjectionConnectionProperties'}, - 'last_updated_time_utc': {'key': 'lastUpdatedTimeUtc', 'type': 'iso-8601'}, - } - - def __init__(self, **kwargs): - super(FaultInjectionProperties, self).__init__(**kwargs) - self.iot_hub_name = kwargs.get('iot_hub_name', None) - self.connection = kwargs.get('connection', None) - self.last_updated_time_utc = kwargs.get('last_updated_time_utc', None) diff --git a/azext_iot/sdk/iothub/service/models/fault_injection_properties_py3.py b/azext_iot/sdk/iothub/service/models/fault_injection_properties_py3.py deleted file mode 100644 index 6d3b840dc..000000000 --- a/azext_iot/sdk/iothub/service/models/fault_injection_properties_py3.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class FaultInjectionProperties(Model): - """FaultInjectionProperties. - - :param iot_hub_name: - :type iot_hub_name: str - :param connection: - :type connection: ~service.models.FaultInjectionConnectionProperties - :param last_updated_time_utc: Service generated. - :type last_updated_time_utc: datetime - """ - - _attribute_map = { - 'iot_hub_name': {'key': 'IotHubName', 'type': 'str'}, - 'connection': {'key': 'connection', 'type': 'FaultInjectionConnectionProperties'}, - 'last_updated_time_utc': {'key': 'lastUpdatedTimeUtc', 'type': 'iso-8601'}, - } - - def __init__(self, *, iot_hub_name: str=None, connection=None, last_updated_time_utc=None, **kwargs) -> None: - super(FaultInjectionProperties, self).__init__(**kwargs) - self.iot_hub_name = iot_hub_name - self.connection = connection - self.last_updated_time_utc = last_updated_time_utc diff --git a/azext_iot/sdk/iothub/service/models/interface.py b/azext_iot/sdk/iothub/service/models/interface.py deleted file mode 100644 index 4ea78c5c0..000000000 --- a/azext_iot/sdk/iothub/service/models/interface.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class Interface(Model): - """Interface. - - :param name: Full name of digital twin interface. - :type name: str - :param properties: List of all properties in an interface. - :type properties: dict[str, ~service.models.Property] - """ - - _attribute_map = { - 'name': {'key': 'name', 'type': 'str'}, - 'properties': {'key': 'properties', 'type': '{Property}'}, - } - - def __init__(self, **kwargs): - super(Interface, self).__init__(**kwargs) - self.name = kwargs.get('name', None) - self.properties = kwargs.get('properties', None) diff --git a/azext_iot/sdk/iothub/service/models/interface_py3.py b/azext_iot/sdk/iothub/service/models/interface_py3.py deleted file mode 100644 index d89cfab2b..000000000 --- a/azext_iot/sdk/iothub/service/models/interface_py3.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class Interface(Model): - """Interface. - - :param name: Full name of digital twin interface. - :type name: str - :param properties: List of all properties in an interface. - :type properties: dict[str, ~service.models.Property] - """ - - _attribute_map = { - 'name': {'key': 'name', 'type': 'str'}, - 'properties': {'key': 'properties', 'type': '{Property}'}, - } - - def __init__(self, *, name: str=None, properties=None, **kwargs) -> None: - super(Interface, self).__init__(**kwargs) - self.name = name - self.properties = properties diff --git a/azext_iot/sdk/iothub/service/models/job_properties.py b/azext_iot/sdk/iothub/service/models/job_properties.py index eab9a867e..1b52613a2 100644 --- a/azext_iot/sdk/iothub/service/models/job_properties.py +++ b/azext_iot/sdk/iothub/service/models/job_properties.py @@ -15,47 +15,58 @@ class JobProperties(Model): """JobProperties. - :param job_id: System generated. Ignored at creation. + :param job_id: The unique identifier of the job. :type job_id: str - :param start_time_utc: System generated. Ignored at creation. + :param start_time_utc: System generated. Ignored at creation. The start + date and time of the job in UTC. :type start_time_utc: datetime - :param end_time_utc: System generated. Ignored at creation. - Represents the time the job stopped processing. + :param end_time_utc: System generated. Ignored at creation. The end date + and time of the job in UTC. :type end_time_utc: datetime - :param type: Required. - The type of job to execute. Possible values include: 'unknown', 'export', + :param type: The job type. Possible values include: 'unknown', 'export', 'import', 'backup', 'readDeviceProperties', 'writeDeviceProperties', 'updateDeviceConfiguration', 'rebootDevice', 'factoryResetDevice', 'firmwareUpdate', 'scheduleDeviceMethod', 'scheduleUpdateTwin', 'restoreFromBackup', 'failoverDataCopy' :type type: str or ~service.models.enum - :param status: System generated. Ignored at creation. Possible values - include: 'unknown', 'enqueued', 'running', 'completed', 'failed', - 'cancelled', 'scheduled', 'queued' + :param status: System generated. Ignored at creation. The status of the + job. Possible values include: 'unknown', 'enqueued', 'running', + 'completed', 'failed', 'cancelled', 'scheduled', 'queued' :type status: str or ~service.models.enum - :param progress: System generated. Ignored at creation. - Represents the percentage of completion. + :param progress: System generated. Ignored at creation. The percentage of + job completion. :type progress: int - :param input_blob_container_uri: URI containing SAS token to a blob + :param input_blob_container_uri: The URI containing SAS token to a blob container that contains registry data to sync. :type input_blob_container_uri: str - :param input_blob_name: The blob name to be used when importing from the - provided input blob container. + :param input_blob_name: The blob name to use when importing from the input + blob container. :type input_blob_name: str - :param output_blob_container_uri: URI containing SAS token to a blob - container. This is used to output the status of the job and the results. + :param output_blob_container_uri: The SAS token to access the blob + container. This is used to output the status and results of the job. :type output_blob_container_uri: str - :param output_blob_name: The name of the blob that will be created in the - provided output blob container. This blob will contain - the exported device registry information for the IoT Hub. + :param output_blob_name: The blob name that will be created in the output + blob container. This blob will contain the exported device registry + information for the IoT Hub. :type output_blob_name: str :param exclude_keys_in_export: Optional for export jobs; ignored for other - jobs. Default: false. If false, authorization keys are included - in export output. Keys are exported as null otherwise. + jobs. If not specified, the service defaults to false. If false, + authorization keys are included in export output. Keys are exported as + null otherwise. :type exclude_keys_in_export: bool - :param failure_reason: System genereated. Ignored at creation. - If status == failure, this represents a string containing the reason. + :param storage_authentication_type: The authentication type used for + connecting to the storage account. Possible values include: 'keyBased', + 'identityBased' + :type storage_authentication_type: str or ~service.models.enum + :param failure_reason: System genereated. Ignored at creation. The reason + for failure, if a failure occurred. :type failure_reason: str + :param include_configurations: Defaults to false. If true, then + configurations are included in the data export/import. + :type include_configurations: bool + :param configurations_blob_name: Defaults to configurations.txt. Specifies + the name of the blob to use when exporting/importing configurations. + :type configurations_blob_name: str """ _attribute_map = { @@ -70,7 +81,10 @@ class JobProperties(Model): 'output_blob_container_uri': {'key': 'outputBlobContainerUri', 'type': 'str'}, 'output_blob_name': {'key': 'outputBlobName', 'type': 'str'}, 'exclude_keys_in_export': {'key': 'excludeKeysInExport', 'type': 'bool'}, + 'storage_authentication_type': {'key': 'storageAuthenticationType', 'type': 'str'}, 'failure_reason': {'key': 'failureReason', 'type': 'str'}, + 'include_configurations': {'key': 'includeConfigurations', 'type': 'bool'}, + 'configurations_blob_name': {'key': 'configurationsBlobName', 'type': 'str'}, } def __init__(self, **kwargs): @@ -86,4 +100,7 @@ def __init__(self, **kwargs): self.output_blob_container_uri = kwargs.get('output_blob_container_uri', None) self.output_blob_name = kwargs.get('output_blob_name', None) self.exclude_keys_in_export = kwargs.get('exclude_keys_in_export', None) + self.storage_authentication_type = kwargs.get('storage_authentication_type', None) self.failure_reason = kwargs.get('failure_reason', None) + self.include_configurations = kwargs.get('include_configurations', None) + self.configurations_blob_name = kwargs.get('configurations_blob_name', None) diff --git a/azext_iot/sdk/iothub/service/models/job_properties_py3.py b/azext_iot/sdk/iothub/service/models/job_properties_py3.py index 5712df5db..f944158ad 100644 --- a/azext_iot/sdk/iothub/service/models/job_properties_py3.py +++ b/azext_iot/sdk/iothub/service/models/job_properties_py3.py @@ -15,47 +15,58 @@ class JobProperties(Model): """JobProperties. - :param job_id: System generated. Ignored at creation. + :param job_id: The unique identifier of the job. :type job_id: str - :param start_time_utc: System generated. Ignored at creation. + :param start_time_utc: System generated. Ignored at creation. The start + date and time of the job in UTC. :type start_time_utc: datetime - :param end_time_utc: System generated. Ignored at creation. - Represents the time the job stopped processing. + :param end_time_utc: System generated. Ignored at creation. The end date + and time of the job in UTC. :type end_time_utc: datetime - :param type: Required. - The type of job to execute. Possible values include: 'unknown', 'export', + :param type: The job type. Possible values include: 'unknown', 'export', 'import', 'backup', 'readDeviceProperties', 'writeDeviceProperties', 'updateDeviceConfiguration', 'rebootDevice', 'factoryResetDevice', 'firmwareUpdate', 'scheduleDeviceMethod', 'scheduleUpdateTwin', 'restoreFromBackup', 'failoverDataCopy' :type type: str or ~service.models.enum - :param status: System generated. Ignored at creation. Possible values - include: 'unknown', 'enqueued', 'running', 'completed', 'failed', - 'cancelled', 'scheduled', 'queued' + :param status: System generated. Ignored at creation. The status of the + job. Possible values include: 'unknown', 'enqueued', 'running', + 'completed', 'failed', 'cancelled', 'scheduled', 'queued' :type status: str or ~service.models.enum - :param progress: System generated. Ignored at creation. - Represents the percentage of completion. + :param progress: System generated. Ignored at creation. The percentage of + job completion. :type progress: int - :param input_blob_container_uri: URI containing SAS token to a blob + :param input_blob_container_uri: The URI containing SAS token to a blob container that contains registry data to sync. :type input_blob_container_uri: str - :param input_blob_name: The blob name to be used when importing from the - provided input blob container. + :param input_blob_name: The blob name to use when importing from the input + blob container. :type input_blob_name: str - :param output_blob_container_uri: URI containing SAS token to a blob - container. This is used to output the status of the job and the results. + :param output_blob_container_uri: The SAS token to access the blob + container. This is used to output the status and results of the job. :type output_blob_container_uri: str - :param output_blob_name: The name of the blob that will be created in the - provided output blob container. This blob will contain - the exported device registry information for the IoT Hub. + :param output_blob_name: The blob name that will be created in the output + blob container. This blob will contain the exported device registry + information for the IoT Hub. :type output_blob_name: str :param exclude_keys_in_export: Optional for export jobs; ignored for other - jobs. Default: false. If false, authorization keys are included - in export output. Keys are exported as null otherwise. + jobs. If not specified, the service defaults to false. If false, + authorization keys are included in export output. Keys are exported as + null otherwise. :type exclude_keys_in_export: bool - :param failure_reason: System genereated. Ignored at creation. - If status == failure, this represents a string containing the reason. + :param storage_authentication_type: The authentication type used for + connecting to the storage account. Possible values include: 'keyBased', + 'identityBased' + :type storage_authentication_type: str or ~service.models.enum + :param failure_reason: System genereated. Ignored at creation. The reason + for failure, if a failure occurred. :type failure_reason: str + :param include_configurations: Defaults to false. If true, then + configurations are included in the data export/import. + :type include_configurations: bool + :param configurations_blob_name: Defaults to configurations.txt. Specifies + the name of the blob to use when exporting/importing configurations. + :type configurations_blob_name: str """ _attribute_map = { @@ -70,10 +81,13 @@ class JobProperties(Model): 'output_blob_container_uri': {'key': 'outputBlobContainerUri', 'type': 'str'}, 'output_blob_name': {'key': 'outputBlobName', 'type': 'str'}, 'exclude_keys_in_export': {'key': 'excludeKeysInExport', 'type': 'bool'}, + 'storage_authentication_type': {'key': 'storageAuthenticationType', 'type': 'str'}, 'failure_reason': {'key': 'failureReason', 'type': 'str'}, + 'include_configurations': {'key': 'includeConfigurations', 'type': 'bool'}, + 'configurations_blob_name': {'key': 'configurationsBlobName', 'type': 'str'}, } - def __init__(self, *, job_id: str=None, start_time_utc=None, end_time_utc=None, type=None, status=None, progress: int=None, input_blob_container_uri: str=None, input_blob_name: str=None, output_blob_container_uri: str=None, output_blob_name: str=None, exclude_keys_in_export: bool=None, failure_reason: str=None, **kwargs) -> None: + def __init__(self, *, job_id: str=None, start_time_utc=None, end_time_utc=None, type=None, status=None, progress: int=None, input_blob_container_uri: str=None, input_blob_name: str=None, output_blob_container_uri: str=None, output_blob_name: str=None, exclude_keys_in_export: bool=None, storage_authentication_type=None, failure_reason: str=None, include_configurations: bool=None, configurations_blob_name: str=None, **kwargs) -> None: super(JobProperties, self).__init__(**kwargs) self.job_id = job_id self.start_time_utc = start_time_utc @@ -86,4 +100,7 @@ def __init__(self, *, job_id: str=None, start_time_utc=None, end_time_utc=None, self.output_blob_container_uri = output_blob_container_uri self.output_blob_name = output_blob_name self.exclude_keys_in_export = exclude_keys_in_export + self.storage_authentication_type = storage_authentication_type self.failure_reason = failure_reason + self.include_configurations = include_configurations + self.configurations_blob_name = configurations_blob_name diff --git a/azext_iot/sdk/iothub/service/models/job_request.py b/azext_iot/sdk/iothub/service/models/job_request.py index 022d26c2b..990db418c 100644 --- a/azext_iot/sdk/iothub/service/models/job_request.py +++ b/azext_iot/sdk/iothub/service/models/job_request.py @@ -15,37 +15,35 @@ class JobRequest(Model): """JobRequest. - :param job_id: Job identifier + :param job_id: The unique identifier of the job. :type job_id: str - :param type: Required. - The type of job to execute. Possible values include: 'unknown', 'export', + :param type: The job type. Possible values include: 'unknown', 'export', 'import', 'backup', 'readDeviceProperties', 'writeDeviceProperties', 'updateDeviceConfiguration', 'rebootDevice', 'factoryResetDevice', 'firmwareUpdate', 'scheduleDeviceMethod', 'scheduleUpdateTwin', 'restoreFromBackup', 'failoverDataCopy' :type type: str or ~service.models.enum - :param cloud_to_device_method: Required if jobType is cloudToDeviceMethod. - The method type and parameters. + :param cloud_to_device_method: The method type and parameters. This is + required if the job type is cloudToDeviceMethod. :type cloud_to_device_method: ~service.models.CloudToDeviceMethod :param update_twin: :type update_twin: ~service.models.Twin - :param query_condition: Required if jobType is updateTwin or - cloudToDeviceMethod. - Condition for device query to get devices to execute the job on + :param query_condition: The condition for devices to execute the job. This + is required if the job type is updateTwin or cloudToDeviceMethod. :type query_condition: str - :param start_time: ISO 8601 date time to start the job + :param start_time: The start date and time of the job in ISO 8601 + standard. :type start_time: datetime - :param max_execution_time_in_seconds: Max execution time in secounds (ttl - duration) + :param max_execution_time_in_seconds: The maximum execution time in + secounds. :type max_execution_time_in_seconds: long """ - # @digimaun - altered update_twin type from Twin to {object} _attribute_map = { 'job_id': {'key': 'jobId', 'type': 'str'}, 'type': {'key': 'type', 'type': 'str'}, 'cloud_to_device_method': {'key': 'cloudToDeviceMethod', 'type': 'CloudToDeviceMethod'}, - 'update_twin': {'key': 'updateTwin', 'type': '{object}'}, + 'update_twin': {'key': 'updateTwin', 'type': 'Twin'}, 'query_condition': {'key': 'queryCondition', 'type': 'str'}, 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, 'max_execution_time_in_seconds': {'key': 'maxExecutionTimeInSeconds', 'type': 'long'}, diff --git a/azext_iot/sdk/iothub/service/models/job_request_py3.py b/azext_iot/sdk/iothub/service/models/job_request_py3.py index 3d5529711..91eadf6f0 100644 --- a/azext_iot/sdk/iothub/service/models/job_request_py3.py +++ b/azext_iot/sdk/iothub/service/models/job_request_py3.py @@ -15,37 +15,35 @@ class JobRequest(Model): """JobRequest. - :param job_id: Job identifier + :param job_id: The unique identifier of the job. :type job_id: str - :param type: Required. - The type of job to execute. Possible values include: 'unknown', 'export', + :param type: The job type. Possible values include: 'unknown', 'export', 'import', 'backup', 'readDeviceProperties', 'writeDeviceProperties', 'updateDeviceConfiguration', 'rebootDevice', 'factoryResetDevice', 'firmwareUpdate', 'scheduleDeviceMethod', 'scheduleUpdateTwin', 'restoreFromBackup', 'failoverDataCopy' :type type: str or ~service.models.enum - :param cloud_to_device_method: Required if jobType is cloudToDeviceMethod. - The method type and parameters. + :param cloud_to_device_method: The method type and parameters. This is + required if the job type is cloudToDeviceMethod. :type cloud_to_device_method: ~service.models.CloudToDeviceMethod :param update_twin: :type update_twin: ~service.models.Twin - :param query_condition: Required if jobType is updateTwin or - cloudToDeviceMethod. - Condition for device query to get devices to execute the job on + :param query_condition: The condition for devices to execute the job. This + is required if the job type is updateTwin or cloudToDeviceMethod. :type query_condition: str - :param start_time: ISO 8601 date time to start the job + :param start_time: The start date and time of the job in ISO 8601 + standard. :type start_time: datetime - :param max_execution_time_in_seconds: Max execution time in secounds (ttl - duration) + :param max_execution_time_in_seconds: The maximum execution time in + secounds. :type max_execution_time_in_seconds: long """ - # @digimaun - altered update_twin type from Twin to {object} _attribute_map = { 'job_id': {'key': 'jobId', 'type': 'str'}, 'type': {'key': 'type', 'type': 'str'}, 'cloud_to_device_method': {'key': 'cloudToDeviceMethod', 'type': 'CloudToDeviceMethod'}, - 'update_twin': {'key': 'updateTwin', 'type': '{object}'}, + 'update_twin': {'key': 'updateTwin', 'type': 'Twin'}, 'query_condition': {'key': 'queryCondition', 'type': 'str'}, 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, 'max_execution_time_in_seconds': {'key': 'maxExecutionTimeInSeconds', 'type': 'long'}, diff --git a/azext_iot/sdk/iothub/service/models/job_response.py b/azext_iot/sdk/iothub/service/models/job_response.py index b149ae290..f9fa79858 100644 --- a/azext_iot/sdk/iothub/service/models/job_response.py +++ b/azext_iot/sdk/iothub/service/models/job_response.py @@ -15,42 +15,42 @@ class JobResponse(Model): """JobResponse. - :param job_id: System generated. Ignored at creation. + :param job_id: System generated. Ignored at creation. The unique + identifier of the job. :type job_id: str - :param query_condition: Device query condition. + :param query_condition: The device query condition. :type query_condition: str - :param created_time: System generated. Ignored at creation. + :param created_time: System generated. Ignored at creation. The creation + date and time of the job. :type created_time: datetime - :param start_time: Scheduled job start time in UTC. + :param start_time: The start date and time of the scheduled job in UTC. :type start_time: datetime - :param end_time: System generated. Ignored at creation. - Represents the time the job stopped processing. + :param end_time: System generated. Ignored at creation. The end date and + time of the job in UTC. :type end_time: datetime - :param max_execution_time_in_seconds: Max execution time in secounds (ttl - duration) + :param max_execution_time_in_seconds: The maximum execution time in + secounds. :type max_execution_time_in_seconds: long - :param type: Required. - The type of job to execute. Possible values include: 'unknown', 'export', + :param type: The job type. Possible values include: 'unknown', 'export', 'import', 'backup', 'readDeviceProperties', 'writeDeviceProperties', 'updateDeviceConfiguration', 'rebootDevice', 'factoryResetDevice', 'firmwareUpdate', 'scheduleDeviceMethod', 'scheduleUpdateTwin', 'restoreFromBackup', 'failoverDataCopy' :type type: str or ~service.models.enum - :param cloud_to_device_method: Required if jobType is cloudToDeviceMethod. - The method type and parameters. + :param cloud_to_device_method: The method type and parameters. This is + required if job type is cloudToDeviceMethod. :type cloud_to_device_method: ~service.models.CloudToDeviceMethod :param update_twin: :type update_twin: ~service.models.Twin - :param status: System generated. Ignored at creation. Possible values - include: 'unknown', 'enqueued', 'running', 'completed', 'failed', - 'cancelled', 'scheduled', 'queued' + :param status: System generated. Ignored at creation. The status of the + job. Possible values include: 'unknown', 'enqueued', 'running', + 'completed', 'failed', 'cancelled', 'scheduled', 'queued' :type status: str or ~service.models.enum - :param failure_reason: System generated. Ignored at creation. - If status == failure, this represents a string containing the reason. + :param failure_reason: The reason for the failure, if a failure occurred. :type failure_reason: str - :param status_message: Status message for the job + :param status_message: The status message of the job. :type status_message: str - :param device_job_statistics: Job details + :param device_job_statistics: The details regarding job execution status. :type device_job_statistics: ~service.models.DeviceJobStatistics """ diff --git a/azext_iot/sdk/iothub/service/models/job_response_py3.py b/azext_iot/sdk/iothub/service/models/job_response_py3.py index 55571106a..365b18f6d 100644 --- a/azext_iot/sdk/iothub/service/models/job_response_py3.py +++ b/azext_iot/sdk/iothub/service/models/job_response_py3.py @@ -15,42 +15,42 @@ class JobResponse(Model): """JobResponse. - :param job_id: System generated. Ignored at creation. + :param job_id: System generated. Ignored at creation. The unique + identifier of the job. :type job_id: str - :param query_condition: Device query condition. + :param query_condition: The device query condition. :type query_condition: str - :param created_time: System generated. Ignored at creation. + :param created_time: System generated. Ignored at creation. The creation + date and time of the job. :type created_time: datetime - :param start_time: Scheduled job start time in UTC. + :param start_time: The start date and time of the scheduled job in UTC. :type start_time: datetime - :param end_time: System generated. Ignored at creation. - Represents the time the job stopped processing. + :param end_time: System generated. Ignored at creation. The end date and + time of the job in UTC. :type end_time: datetime - :param max_execution_time_in_seconds: Max execution time in secounds (ttl - duration) + :param max_execution_time_in_seconds: The maximum execution time in + secounds. :type max_execution_time_in_seconds: long - :param type: Required. - The type of job to execute. Possible values include: 'unknown', 'export', + :param type: The job type. Possible values include: 'unknown', 'export', 'import', 'backup', 'readDeviceProperties', 'writeDeviceProperties', 'updateDeviceConfiguration', 'rebootDevice', 'factoryResetDevice', 'firmwareUpdate', 'scheduleDeviceMethod', 'scheduleUpdateTwin', 'restoreFromBackup', 'failoverDataCopy' :type type: str or ~service.models.enum - :param cloud_to_device_method: Required if jobType is cloudToDeviceMethod. - The method type and parameters. + :param cloud_to_device_method: The method type and parameters. This is + required if job type is cloudToDeviceMethod. :type cloud_to_device_method: ~service.models.CloudToDeviceMethod :param update_twin: :type update_twin: ~service.models.Twin - :param status: System generated. Ignored at creation. Possible values - include: 'unknown', 'enqueued', 'running', 'completed', 'failed', - 'cancelled', 'scheduled', 'queued' + :param status: System generated. Ignored at creation. The status of the + job. Possible values include: 'unknown', 'enqueued', 'running', + 'completed', 'failed', 'cancelled', 'scheduled', 'queued' :type status: str or ~service.models.enum - :param failure_reason: System generated. Ignored at creation. - If status == failure, this represents a string containing the reason. + :param failure_reason: The reason for the failure, if a failure occurred. :type failure_reason: str - :param status_message: Status message for the job + :param status_message: The status message of the job. :type status_message: str - :param device_job_statistics: Job details + :param device_job_statistics: The details regarding job execution status. :type device_job_statistics: ~service.models.DeviceJobStatistics """ diff --git a/azext_iot/sdk/iothub/service/models/module.py b/azext_iot/sdk/iothub/service/models/module.py index 182a9f6f9..7a40a2a04 100644 --- a/azext_iot/sdk/iothub/service/models/module.py +++ b/azext_iot/sdk/iothub/service/models/module.py @@ -13,28 +13,36 @@ class Module(Model): - """Module identity on a device. + """The module identity on a device. - :param module_id: + :param module_id: The unique identifier of the module. :type module_id: str - :param managed_by: + :param managed_by: Identifies who manages this module. For instance, this + value is \\"IotEdge\\" if the edge runtime owns this module. :type managed_by: str - :param device_id: + :param device_id: The unique identifier of the device. :type device_id: str - :param generation_id: + :param generation_id: The IoT Hub generated, case-sensitive string up to + 128 characters long. This value is used to distinguish modules with the + same moduleId, when they have been deleted and re-created. :type generation_id: str - :param etag: + :param etag: The string representing a weak ETag for the module identity, + as per RFC7232. :type etag: str - :param connection_state: Possible values include: 'Disconnected', - 'Connected' + :param connection_state: The connection state of the device. Possible + values include: 'Disconnected', 'Connected' :type connection_state: str or ~service.models.enum - :param connection_state_updated_time: + :param connection_state_updated_time: The date and time the connection + state was last updated. :type connection_state_updated_time: datetime - :param last_activity_time: + :param last_activity_time: The date and time the device last connected, + received, or sent a message. :type last_activity_time: datetime - :param cloud_to_device_message_count: + :param cloud_to_device_message_count: The number of cloud-to-module + messages currently queued to be sent to the module. :type cloud_to_device_message_count: int - :param authentication: + :param authentication: The authentication mechanism used by the module + when connecting to the service and edge hub. :type authentication: ~service.models.AuthenticationMechanism """ diff --git a/azext_iot/sdk/iothub/service/models/module_py3.py b/azext_iot/sdk/iothub/service/models/module_py3.py index 33a4e93ae..2c766ad99 100644 --- a/azext_iot/sdk/iothub/service/models/module_py3.py +++ b/azext_iot/sdk/iothub/service/models/module_py3.py @@ -13,28 +13,36 @@ class Module(Model): - """Module identity on a device. + """The module identity on a device. - :param module_id: + :param module_id: The unique identifier of the module. :type module_id: str - :param managed_by: + :param managed_by: Identifies who manages this module. For instance, this + value is \\"IotEdge\\" if the edge runtime owns this module. :type managed_by: str - :param device_id: + :param device_id: The unique identifier of the device. :type device_id: str - :param generation_id: + :param generation_id: The IoT Hub generated, case-sensitive string up to + 128 characters long. This value is used to distinguish modules with the + same moduleId, when they have been deleted and re-created. :type generation_id: str - :param etag: + :param etag: The string representing a weak ETag for the module identity, + as per RFC7232. :type etag: str - :param connection_state: Possible values include: 'Disconnected', - 'Connected' + :param connection_state: The connection state of the device. Possible + values include: 'Disconnected', 'Connected' :type connection_state: str or ~service.models.enum - :param connection_state_updated_time: + :param connection_state_updated_time: The date and time the connection + state was last updated. :type connection_state_updated_time: datetime - :param last_activity_time: + :param last_activity_time: The date and time the device last connected, + received, or sent a message. :type last_activity_time: datetime - :param cloud_to_device_message_count: + :param cloud_to_device_message_count: The number of cloud-to-module + messages currently queued to be sent to the module. :type cloud_to_device_message_count: int - :param authentication: + :param authentication: The authentication mechanism used by the module + when connecting to the service and edge hub. :type authentication: ~service.models.AuthenticationMechanism """ diff --git a/azext_iot/sdk/iothub/service/models/property_container.py b/azext_iot/sdk/iothub/service/models/property_container.py index 345c535f6..ef0857dfa 100644 --- a/azext_iot/sdk/iothub/service/models/property_container.py +++ b/azext_iot/sdk/iothub/service/models/property_container.py @@ -13,18 +13,20 @@ class PropertyContainer(Model): - """Represents Twin properties. + """The desired and reported properties of the twin. The maximum depth of the + object is 10. - :param desired: Used in conjunction with reported properties to - synchronize device configuration or condition. Desired properties can only - be set by the solution back end and can be read by the device app. The - device app can also be notified in real time of changes on the desired - properties. + :param desired: The collection of desired property key-value pairs. The + keys are UTF-8 encoded, case-sensitive and up-to 1KB in length. Allowed + characters exclude UNICODE control characters (segments C0 and C1), '.', + '$' and space. The desired porperty values are JSON objects, up-to 4KB in + length. :type desired: dict[str, object] - :param reported: Used in conjunction with desired properties to - synchronize device configuration or condition. Reported properties can - only be set by the device app and can be read and queried by the solution - back end. + :param reported: The collection of reported property key-value pairs. The + keys are UTF-8 encoded, case-sensitive and up-to 1KB in length. Allowed + characters exclude UNICODE control characters (segments C0 and C1), '.', + '$' and space. The reported property values are JSON objects, up-to 4KB in + length. :type reported: dict[str, object] """ diff --git a/azext_iot/sdk/iothub/service/models/property_container_py3.py b/azext_iot/sdk/iothub/service/models/property_container_py3.py index 433224762..6ebf6ba35 100644 --- a/azext_iot/sdk/iothub/service/models/property_container_py3.py +++ b/azext_iot/sdk/iothub/service/models/property_container_py3.py @@ -13,18 +13,20 @@ class PropertyContainer(Model): - """Represents Twin properties. + """The desired and reported properties of the twin. The maximum depth of the + object is 10. - :param desired: Used in conjunction with reported properties to - synchronize device configuration or condition. Desired properties can only - be set by the solution back end and can be read by the device app. The - device app can also be notified in real time of changes on the desired - properties. + :param desired: The collection of desired property key-value pairs. The + keys are UTF-8 encoded, case-sensitive and up-to 1KB in length. Allowed + characters exclude UNICODE control characters (segments C0 and C1), '.', + '$' and space. The desired porperty values are JSON objects, up-to 4KB in + length. :type desired: dict[str, object] - :param reported: Used in conjunction with desired properties to - synchronize device configuration or condition. Reported properties can - only be set by the device app and can be read and queried by the solution - back end. + :param reported: The collection of reported property key-value pairs. The + keys are UTF-8 encoded, case-sensitive and up-to 1KB in length. Allowed + characters exclude UNICODE control characters (segments C0 and C1), '.', + '$' and space. The reported property values are JSON objects, up-to 4KB in + length. :type reported: dict[str, object] """ diff --git a/azext_iot/sdk/iothub/service/models/purge_message_queue_result.py b/azext_iot/sdk/iothub/service/models/purge_message_queue_result.py index 03a91f47a..08c75b9a4 100644 --- a/azext_iot/sdk/iothub/service/models/purge_message_queue_result.py +++ b/azext_iot/sdk/iothub/service/models/purge_message_queue_result.py @@ -13,13 +13,13 @@ class PurgeMessageQueueResult(Model): - """Result of a device message queue purge operation. + """The result of a device message queue purge operation. :param total_messages_purged: :type total_messages_purged: int - :param device_id: The ID of the device whose messages are being purged. + :param device_id: The unique identifier of the device. :type device_id: str - :param module_id: The ID of the device whose messages are being purged. + :param module_id: The unique identifier of the module. :type module_id: str """ diff --git a/azext_iot/sdk/iothub/service/models/purge_message_queue_result_py3.py b/azext_iot/sdk/iothub/service/models/purge_message_queue_result_py3.py index 66454f505..d46cddfeb 100644 --- a/azext_iot/sdk/iothub/service/models/purge_message_queue_result_py3.py +++ b/azext_iot/sdk/iothub/service/models/purge_message_queue_result_py3.py @@ -13,13 +13,13 @@ class PurgeMessageQueueResult(Model): - """Result of a device message queue purge operation. + """The result of a device message queue purge operation. :param total_messages_purged: :type total_messages_purged: int - :param device_id: The ID of the device whose messages are being purged. + :param device_id: The unique identifier of the device. :type device_id: str - :param module_id: The ID of the device whose messages are being purged. + :param module_id: The unique identifier of the module. :type module_id: str """ diff --git a/azext_iot/sdk/iothub/service/models/query_result.py b/azext_iot/sdk/iothub/service/models/query_result.py index 0dcf7da64..2fe0365d3 100644 --- a/azext_iot/sdk/iothub/service/models/query_result.py +++ b/azext_iot/sdk/iothub/service/models/query_result.py @@ -21,7 +21,7 @@ class QueryResult(Model): :type type: str or ~service.models.enum :param items: The query result items, as a collection. :type items: list[object] - :param continuation_token: Request continuation token. + :param continuation_token: The continuation token. :type continuation_token: str """ diff --git a/azext_iot/sdk/iothub/service/models/query_result_py3.py b/azext_iot/sdk/iothub/service/models/query_result_py3.py index 1e0a59f1f..ed93c2c0a 100644 --- a/azext_iot/sdk/iothub/service/models/query_result_py3.py +++ b/azext_iot/sdk/iothub/service/models/query_result_py3.py @@ -21,7 +21,7 @@ class QueryResult(Model): :type type: str or ~service.models.enum :param items: The query result items, as a collection. :type items: list[object] - :param continuation_token: Request continuation token. + :param continuation_token: The continuation token. :type continuation_token: str """ diff --git a/azext_iot/sdk/iothub/service/models/query_specification.py b/azext_iot/sdk/iothub/service/models/query_specification.py index d17a6c7f3..937c84ce9 100644 --- a/azext_iot/sdk/iothub/service/models/query_specification.py +++ b/azext_iot/sdk/iothub/service/models/query_specification.py @@ -13,9 +13,9 @@ class QuerySpecification(Model): - """A Json query request. + """The Json query request. - :param query: The query. + :param query: The query string. :type query: str """ diff --git a/azext_iot/sdk/iothub/service/models/query_specification_py3.py b/azext_iot/sdk/iothub/service/models/query_specification_py3.py index 08ee4d3eb..a2ac3a4eb 100644 --- a/azext_iot/sdk/iothub/service/models/query_specification_py3.py +++ b/azext_iot/sdk/iothub/service/models/query_specification_py3.py @@ -13,9 +13,9 @@ class QuerySpecification(Model): - """A Json query request. + """The Json query request. - :param query: The query. + :param query: The query string. :type query: str """ diff --git a/azext_iot/sdk/iothub/service/models/registry_statistics.py b/azext_iot/sdk/iothub/service/models/registry_statistics.py index 2ff4f1ae0..8b072bf0b 100644 --- a/azext_iot/sdk/iothub/service/models/registry_statistics.py +++ b/azext_iot/sdk/iothub/service/models/registry_statistics.py @@ -15,11 +15,12 @@ class RegistryStatistics(Model): """RegistryStatistics. - :param total_device_count: + :param total_device_count: The total number of devices registered for the + IoT Hub. :type total_device_count: long - :param enabled_device_count: + :param enabled_device_count: The number of currently enabled devices. :type enabled_device_count: long - :param disabled_device_count: + :param disabled_device_count: The number of currently disabled devices. :type disabled_device_count: long """ diff --git a/azext_iot/sdk/iothub/service/models/registry_statistics_py3.py b/azext_iot/sdk/iothub/service/models/registry_statistics_py3.py index be72cac00..ca24e948c 100644 --- a/azext_iot/sdk/iothub/service/models/registry_statistics_py3.py +++ b/azext_iot/sdk/iothub/service/models/registry_statistics_py3.py @@ -15,11 +15,12 @@ class RegistryStatistics(Model): """RegistryStatistics. - :param total_device_count: + :param total_device_count: The total number of devices registered for the + IoT Hub. :type total_device_count: long - :param enabled_device_count: + :param enabled_device_count: The number of currently enabled devices. :type enabled_device_count: long - :param disabled_device_count: + :param disabled_device_count: The number of currently disabled devices. :type disabled_device_count: long """ diff --git a/azext_iot/sdk/iothub/service/models/reported_py3.py b/azext_iot/sdk/iothub/service/models/reported_py3.py deleted file mode 100644 index 933e34420..000000000 --- a/azext_iot/sdk/iothub/service/models/reported_py3.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class Reported(Model): - """Reported. - - :param value: The current interface property value in a digitalTwin. - :type value: object - :param desired_state: - :type desired_state: ~service.models.DesiredState - """ - - _attribute_map = { - 'value': {'key': 'value', 'type': 'object'}, - 'desired_state': {'key': 'desiredState', 'type': 'DesiredState'}, - } - - def __init__(self, *, value=None, desired_state=None, **kwargs) -> None: - super(Reported, self).__init__(**kwargs) - self.value = value - self.desired_state = desired_state diff --git a/azext_iot/sdk/iothub/service/models/service_statistics.py b/azext_iot/sdk/iothub/service/models/service_statistics.py index aef69ff65..f2b6d9031 100644 --- a/azext_iot/sdk/iothub/service/models/service_statistics.py +++ b/azext_iot/sdk/iothub/service/models/service_statistics.py @@ -15,7 +15,7 @@ class ServiceStatistics(Model): """ServiceStatistics. - :param connected_device_count: + :param connected_device_count: The number of currently connected devices. :type connected_device_count: long """ diff --git a/azext_iot/sdk/iothub/service/models/service_statistics_py3.py b/azext_iot/sdk/iothub/service/models/service_statistics_py3.py index cecd023c8..360054e23 100644 --- a/azext_iot/sdk/iothub/service/models/service_statistics_py3.py +++ b/azext_iot/sdk/iothub/service/models/service_statistics_py3.py @@ -15,7 +15,7 @@ class ServiceStatistics(Model): """ServiceStatistics. - :param connected_device_count: + :param connected_device_count: The number of currently connected devices. :type connected_device_count: long """ diff --git a/azext_iot/sdk/iothub/service/models/symmetric_key.py b/azext_iot/sdk/iothub/service/models/symmetric_key.py index e1401d814..a4266245d 100644 --- a/azext_iot/sdk/iothub/service/models/symmetric_key.py +++ b/azext_iot/sdk/iothub/service/models/symmetric_key.py @@ -15,9 +15,9 @@ class SymmetricKey(Model): """SymmetricKey. - :param primary_key: + :param primary_key: The base64 encoded primary key of the device. :type primary_key: str - :param secondary_key: + :param secondary_key: The base64 encoded secondary key of the device. :type secondary_key: str """ diff --git a/azext_iot/sdk/iothub/service/models/symmetric_key_py3.py b/azext_iot/sdk/iothub/service/models/symmetric_key_py3.py index b34488d47..1fd90ab01 100644 --- a/azext_iot/sdk/iothub/service/models/symmetric_key_py3.py +++ b/azext_iot/sdk/iothub/service/models/symmetric_key_py3.py @@ -15,9 +15,9 @@ class SymmetricKey(Model): """SymmetricKey. - :param primary_key: + :param primary_key: The base64 encoded primary key of the device. :type primary_key: str - :param secondary_key: + :param secondary_key: The base64 encoded secondary key of the device. :type secondary_key: str """ diff --git a/azext_iot/sdk/iothub/service/models/twin.py b/azext_iot/sdk/iothub/service/models/twin.py index 9487c13ee..58cd9acb0 100644 --- a/azext_iot/sdk/iothub/service/models/twin.py +++ b/azext_iot/sdk/iothub/service/models/twin.py @@ -13,57 +13,74 @@ class Twin(Model): - """Twin Representation. + """The state information for a device or module. This is implicitly created + and deleted when the corresponding device/ module identity is created or + deleted in the IoT Hub. - :param device_id: The deviceId uniquely identifies the device in the IoT - hub's identity registry. A case-sensitive string (up to 128 char long) of - ASCII 7-bit alphanumeric chars + {'-', ':', '.', '+', '%', '_', '#', '*', - '?', '!', '(', ')', ',', '=', '@', ';', '$', '''}. + :param device_id: The unique identifier of the device in the identity + registry of the IoT Hub. It is a case-sensitive string (up to 128 char + long) of ASCII 7-bit alphanumeric chars, and the following special + characters {'-', ':', '.', '+', '%', '_', '#', '*', '?', '!', '(', ')', + ',', '=', '@', ';', '$', '''}. :type device_id: str - :param module_id: Gets and sets the Module Id. + :param module_id: The unique identifier of the module in the identity + registry of the IoT Hub. It is a case-sensitive string (up to 128 char + long) of ASCII 7-bit alphanumeric chars, and the following special + characters {'-', ':', '.', '+', '%', '_', '#', '*', '?', '!', '(', ')', + ',', '=', '@', ';', '$', '''}. :type module_id: str - :param tags: A JSON document read and written by the solution back end. - Tags are not visible to device apps. + :param tags: The collection of key-value pairs read and written by the + solution back end. They are not visible to device apps. They keys are + UTF-8 encoded, case-sensitive and up-to 1KB in length. Allowed characters + exclude UNICODE control characters (segments C0 and C1), '.', '$' and + space. The values are JSON objects, up-to 4KB in length. :type tags: dict[str, object] - :param properties: Gets and sets the Twin properties. + :param properties: The desired and reported properties of the twin. :type properties: ~service.models.TwinProperties - :param etag: Twin's ETag + :param etag: The string representing a ETag for the device twin, as per + RFC7232. :type etag: str - :param version: Version for device twin, including tags and desired + :param version: The version for the device twin including tags and desired properties :type version: long - :param device_etag: Device's ETag + :param device_etag: The string representing a ETag for the device, as per + RFC7232. :type device_etag: str - :param status: Gets the corresponding Device's Status. Possible values - include: 'enabled', 'disabled' + :param status: The enabled status of the device. If disabled, the device + cannot connect to the service. Possible values include: 'enabled', + 'disabled' :type status: str or ~service.models.enum - :param status_reason: Reason, if any, for the corresponding Device to be - in specified Status + :param status_reason: The reason for the current status of the device, if + any. :type status_reason: str - :param status_update_time: Time when the corresponding Device's Status was - last updated + :param status_update_time: The date and time when the status of the device + was last updated. :type status_update_time: datetime - :param connection_state: Corresponding Device's ConnectionState. Possible + :param connection_state: The connection state of the device. Possible values include: 'Disconnected', 'Connected' :type connection_state: str or ~service.models.enum - :param last_activity_time: The last time the device connected, received or - sent a message. In ISO8601 datetime format in UTC, for example, - 2015-01-28T16:24:48.789Z. This does not update if the device uses the - HTTP/1 protocol to perform messaging operations. + :param last_activity_time: The date and time when the device last + connected or received or sent a message. The date and time is sepecified + in ISO8601 datetime format in UTC, for example, 2015-01-28T16:24:48.789Z. + This value is not updated if the device uses the HTTP/1 protocol to + perform messaging operations. :type last_activity_time: datetime - :param cloud_to_device_message_count: Number of messages sent to the - corresponding Device from the Cloud + :param cloud_to_device_message_count: The number of cloud-to-device + messages sent. :type cloud_to_device_message_count: int - :param authentication_type: Corresponding Device's authentication type. + :param authentication_type: The authentication type used by the device. Possible values include: 'sas', 'selfSigned', 'certificateAuthority', 'none' :type authentication_type: str or ~service.models.enum - :param x509_thumbprint: Corresponding Device's X509 thumbprint + :param x509_thumbprint: The X509 thumbprint of the device. :type x509_thumbprint: ~service.models.X509Thumbprint :param capabilities: :type capabilities: ~service.models.DeviceCapabilities - :param device_scope: + :param device_scope: The scope of the device. :type device_scope: str + :param parent_scopes: The scopes of the upper level edge devices if + applicable. Only available for edge devices. + :type parent_scopes: list[str] """ _attribute_map = { @@ -84,6 +101,7 @@ class Twin(Model): 'x509_thumbprint': {'key': 'x509Thumbprint', 'type': 'X509Thumbprint'}, 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, 'device_scope': {'key': 'deviceScope', 'type': 'str'}, + 'parent_scopes': {'key': 'parentScopes', 'type': '[str]'}, } def __init__(self, **kwargs): @@ -105,3 +123,4 @@ def __init__(self, **kwargs): self.x509_thumbprint = kwargs.get('x509_thumbprint', None) self.capabilities = kwargs.get('capabilities', None) self.device_scope = kwargs.get('device_scope', None) + self.parent_scopes = kwargs.get('parent_scopes', None) diff --git a/azext_iot/sdk/iothub/service/models/twin_properties.py b/azext_iot/sdk/iothub/service/models/twin_properties.py index 36bf22333..b2aa76c26 100644 --- a/azext_iot/sdk/iothub/service/models/twin_properties.py +++ b/azext_iot/sdk/iothub/service/models/twin_properties.py @@ -13,18 +13,20 @@ class TwinProperties(Model): - """Represents Twin properties. + """The desired and reported properties of the twin. The maximum depth of the + object is 10. - :param desired: Used in conjunction with reported properties to - synchronize device configuration or condition. Desired properties can only - be set by the solution back end and can be read by the device app. The - device app can also be notified in real time of changes on the desired - properties. + :param desired: The collection of desired property key-value pairs. The + keys are UTF-8 encoded, case-sensitive and up-to 1KB in length. Allowed + characters exclude UNICODE control characters (segments C0 and C1), '.', + '$' and space. The desired porperty values are JSON objects, up-to 4KB in + length. :type desired: dict[str, object] - :param reported: Used in conjunction with desired properties to - synchronize device configuration or condition. Reported properties can - only be set by the device app and can be read and queried by the solution - back end. + :param reported: The collection of reported property key-value pairs. The + keys are UTF-8 encoded, case-sensitive and up-to 1KB in length. Allowed + characters exclude UNICODE control characters (segments C0 and C1), '.', + '$' and space. The reported property values are JSON objects, up-to 4KB in + length. :type reported: dict[str, object] """ diff --git a/azext_iot/sdk/iothub/service/models/twin_properties_py3.py b/azext_iot/sdk/iothub/service/models/twin_properties_py3.py index 8d7930b47..651b67394 100644 --- a/azext_iot/sdk/iothub/service/models/twin_properties_py3.py +++ b/azext_iot/sdk/iothub/service/models/twin_properties_py3.py @@ -13,18 +13,20 @@ class TwinProperties(Model): - """Represents Twin properties. + """The desired and reported properties of the twin. The maximum depth of the + object is 10. - :param desired: Used in conjunction with reported properties to - synchronize device configuration or condition. Desired properties can only - be set by the solution back end and can be read by the device app. The - device app can also be notified in real time of changes on the desired - properties. + :param desired: The collection of desired property key-value pairs. The + keys are UTF-8 encoded, case-sensitive and up-to 1KB in length. Allowed + characters exclude UNICODE control characters (segments C0 and C1), '.', + '$' and space. The desired porperty values are JSON objects, up-to 4KB in + length. :type desired: dict[str, object] - :param reported: Used in conjunction with desired properties to - synchronize device configuration or condition. Reported properties can - only be set by the device app and can be read and queried by the solution - back end. + :param reported: The collection of reported property key-value pairs. The + keys are UTF-8 encoded, case-sensitive and up-to 1KB in length. Allowed + characters exclude UNICODE control characters (segments C0 and C1), '.', + '$' and space. The reported property values are JSON objects, up-to 4KB in + length. :type reported: dict[str, object] """ diff --git a/azext_iot/sdk/iothub/service/models/twin_py3.py b/azext_iot/sdk/iothub/service/models/twin_py3.py index 701a1af5d..8328ba33a 100644 --- a/azext_iot/sdk/iothub/service/models/twin_py3.py +++ b/azext_iot/sdk/iothub/service/models/twin_py3.py @@ -13,57 +13,74 @@ class Twin(Model): - """Twin Representation. + """The state information for a device or module. This is implicitly created + and deleted when the corresponding device/ module identity is created or + deleted in the IoT Hub. - :param device_id: The deviceId uniquely identifies the device in the IoT - hub's identity registry. A case-sensitive string (up to 128 char long) of - ASCII 7-bit alphanumeric chars + {'-', ':', '.', '+', '%', '_', '#', '*', - '?', '!', '(', ')', ',', '=', '@', ';', '$', '''}. + :param device_id: The unique identifier of the device in the identity + registry of the IoT Hub. It is a case-sensitive string (up to 128 char + long) of ASCII 7-bit alphanumeric chars, and the following special + characters {'-', ':', '.', '+', '%', '_', '#', '*', '?', '!', '(', ')', + ',', '=', '@', ';', '$', '''}. :type device_id: str - :param module_id: Gets and sets the Module Id. + :param module_id: The unique identifier of the module in the identity + registry of the IoT Hub. It is a case-sensitive string (up to 128 char + long) of ASCII 7-bit alphanumeric chars, and the following special + characters {'-', ':', '.', '+', '%', '_', '#', '*', '?', '!', '(', ')', + ',', '=', '@', ';', '$', '''}. :type module_id: str - :param tags: A JSON document read and written by the solution back end. - Tags are not visible to device apps. + :param tags: The collection of key-value pairs read and written by the + solution back end. They are not visible to device apps. They keys are + UTF-8 encoded, case-sensitive and up-to 1KB in length. Allowed characters + exclude UNICODE control characters (segments C0 and C1), '.', '$' and + space. The values are JSON objects, up-to 4KB in length. :type tags: dict[str, object] - :param properties: Gets and sets the Twin properties. + :param properties: The desired and reported properties of the twin. :type properties: ~service.models.TwinProperties - :param etag: Twin's ETag + :param etag: The string representing a ETag for the device twin, as per + RFC7232. :type etag: str - :param version: Version for device twin, including tags and desired + :param version: The version for the device twin including tags and desired properties :type version: long - :param device_etag: Device's ETag + :param device_etag: The string representing a ETag for the device, as per + RFC7232. :type device_etag: str - :param status: Gets the corresponding Device's Status. Possible values - include: 'enabled', 'disabled' + :param status: The enabled status of the device. If disabled, the device + cannot connect to the service. Possible values include: 'enabled', + 'disabled' :type status: str or ~service.models.enum - :param status_reason: Reason, if any, for the corresponding Device to be - in specified Status + :param status_reason: The reason for the current status of the device, if + any. :type status_reason: str - :param status_update_time: Time when the corresponding Device's Status was - last updated + :param status_update_time: The date and time when the status of the device + was last updated. :type status_update_time: datetime - :param connection_state: Corresponding Device's ConnectionState. Possible + :param connection_state: The connection state of the device. Possible values include: 'Disconnected', 'Connected' :type connection_state: str or ~service.models.enum - :param last_activity_time: The last time the device connected, received or - sent a message. In ISO8601 datetime format in UTC, for example, - 2015-01-28T16:24:48.789Z. This does not update if the device uses the - HTTP/1 protocol to perform messaging operations. + :param last_activity_time: The date and time when the device last + connected or received or sent a message. The date and time is sepecified + in ISO8601 datetime format in UTC, for example, 2015-01-28T16:24:48.789Z. + This value is not updated if the device uses the HTTP/1 protocol to + perform messaging operations. :type last_activity_time: datetime - :param cloud_to_device_message_count: Number of messages sent to the - corresponding Device from the Cloud + :param cloud_to_device_message_count: The number of cloud-to-device + messages sent. :type cloud_to_device_message_count: int - :param authentication_type: Corresponding Device's authentication type. + :param authentication_type: The authentication type used by the device. Possible values include: 'sas', 'selfSigned', 'certificateAuthority', 'none' :type authentication_type: str or ~service.models.enum - :param x509_thumbprint: Corresponding Device's X509 thumbprint + :param x509_thumbprint: The X509 thumbprint of the device. :type x509_thumbprint: ~service.models.X509Thumbprint :param capabilities: :type capabilities: ~service.models.DeviceCapabilities - :param device_scope: + :param device_scope: The scope of the device. :type device_scope: str + :param parent_scopes: The scopes of the upper level edge devices if + applicable. Only available for edge devices. + :type parent_scopes: list[str] """ _attribute_map = { @@ -84,9 +101,10 @@ class Twin(Model): 'x509_thumbprint': {'key': 'x509Thumbprint', 'type': 'X509Thumbprint'}, 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, 'device_scope': {'key': 'deviceScope', 'type': 'str'}, + 'parent_scopes': {'key': 'parentScopes', 'type': '[str]'}, } - def __init__(self, *, device_id: str=None, module_id: str=None, tags=None, properties=None, etag: str=None, version: int=None, device_etag: str=None, status=None, status_reason: str=None, status_update_time=None, connection_state=None, last_activity_time=None, cloud_to_device_message_count: int=None, authentication_type=None, x509_thumbprint=None, capabilities=None, device_scope: str=None, **kwargs) -> None: + def __init__(self, *, device_id: str=None, module_id: str=None, tags=None, properties=None, etag: str=None, version: int=None, device_etag: str=None, status=None, status_reason: str=None, status_update_time=None, connection_state=None, last_activity_time=None, cloud_to_device_message_count: int=None, authentication_type=None, x509_thumbprint=None, capabilities=None, device_scope: str=None, parent_scopes=None, **kwargs) -> None: super(Twin, self).__init__(**kwargs) self.device_id = device_id self.module_id = module_id @@ -105,3 +123,4 @@ def __init__(self, *, device_id: str=None, module_id: str=None, tags=None, prope self.x509_thumbprint = x509_thumbprint self.capabilities = capabilities self.device_scope = device_scope + self.parent_scopes = parent_scopes diff --git a/azext_iot/sdk/iothub/service/models/x509_thumbprint.py b/azext_iot/sdk/iothub/service/models/x509_thumbprint.py index 450f74d3c..b95bdf380 100644 --- a/azext_iot/sdk/iothub/service/models/x509_thumbprint.py +++ b/azext_iot/sdk/iothub/service/models/x509_thumbprint.py @@ -15,9 +15,10 @@ class X509Thumbprint(Model): """X509Thumbprint. - :param primary_thumbprint: + :param primary_thumbprint: The X509 client certificate primary thumbprint. :type primary_thumbprint: str - :param secondary_thumbprint: + :param secondary_thumbprint: The X509 client certificate secondary + thumbprint. :type secondary_thumbprint: str """ diff --git a/azext_iot/sdk/iothub/service/models/x509_thumbprint_py3.py b/azext_iot/sdk/iothub/service/models/x509_thumbprint_py3.py index b2c70b507..037fc70da 100644 --- a/azext_iot/sdk/iothub/service/models/x509_thumbprint_py3.py +++ b/azext_iot/sdk/iothub/service/models/x509_thumbprint_py3.py @@ -15,9 +15,10 @@ class X509Thumbprint(Model): """X509Thumbprint. - :param primary_thumbprint: + :param primary_thumbprint: The X509 client certificate primary thumbprint. :type primary_thumbprint: str - :param secondary_thumbprint: + :param secondary_thumbprint: The X509 client certificate secondary + thumbprint. :type secondary_thumbprint: str """ diff --git a/azext_iot/sdk/iothub/service/operations/__init__.py b/azext_iot/sdk/iothub/service/operations/__init__.py index 727394b4d..caaa8e08a 100644 --- a/azext_iot/sdk/iothub/service/operations/__init__.py +++ b/azext_iot/sdk/iothub/service/operations/__init__.py @@ -10,19 +10,23 @@ # -------------------------------------------------------------------------- from .configuration_operations import ConfigurationOperations -from .registry_manager_operations import RegistryManagerOperations -from .job_client_operations import JobClientOperations -from .fault_injection_operations import FaultInjectionOperations -from .twin_operations import TwinOperations +from .statistics_operations import StatisticsOperations +from .devices_operations import DevicesOperations +from .bulk_registry_operations import BulkRegistryOperations +from .query_operations import QueryOperations +from .jobs_operations import JobsOperations +from .cloud_to_device_messages_operations import CloudToDeviceMessagesOperations +from .modules_operations import ModulesOperations from .digital_twin_operations import DigitalTwinOperations -from .device_method_operations import DeviceMethodOperations __all__ = [ 'ConfigurationOperations', - 'RegistryManagerOperations', - 'JobClientOperations', - 'FaultInjectionOperations', - 'TwinOperations', + 'StatisticsOperations', + 'DevicesOperations', + 'BulkRegistryOperations', + 'QueryOperations', + 'JobsOperations', + 'CloudToDeviceMessagesOperations', + 'ModulesOperations', 'DigitalTwinOperations', - 'DeviceMethodOperations', ] diff --git a/azext_iot/sdk/iothub/service/operations/bulk_registry_operations.py b/azext_iot/sdk/iothub/service/operations/bulk_registry_operations.py new file mode 100644 index 000000000..2a25ba1d6 --- /dev/null +++ b/azext_iot/sdk/iothub/service/operations/bulk_registry_operations.py @@ -0,0 +1,104 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse +from msrestazure.azure_exceptions import CloudError + +from .. import models + + +class BulkRegistryOperations(object): + """BulkRegistryOperations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: Version of the Api. Constant value: "2020-09-30". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-09-30" + + self.config = config + + def update_registry( + self, devices, custom_headers=None, raw=False, **operation_config): + """Creates, updates, or deletes the identities of multiple devices from + the IoT Hub identity registry. A device identity can be specified only + once in the list. Different operations (create, update, delete) on + different devices are allowed. A maximum of 100 devices can be + specified per invocation. For large scale operations, use the import + feature using blob storage + (https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities). + + :param devices: The registry operations to perform. + :type devices: list[~service.models.ExportImportDevice] + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: BulkRegistryOperationResult or ClientRawResponse if raw=true + :rtype: ~service.models.BulkRegistryOperationResult or + ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.update_registry.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + body_content = self._serialize.body(devices, '[ExportImportDevice]') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 400]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('BulkRegistryOperationResult', response) + if response.status_code == 400: + deserialized = self._deserialize('BulkRegistryOperationResult', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + update_registry.metadata = {'url': '/devices'} diff --git a/azext_iot/sdk/iothub/service/operations/cloud_to_device_messages_operations.py b/azext_iot/sdk/iothub/service/operations/cloud_to_device_messages_operations.py new file mode 100644 index 000000000..86b598e5c --- /dev/null +++ b/azext_iot/sdk/iothub/service/operations/cloud_to_device_messages_operations.py @@ -0,0 +1,249 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse +from msrestazure.azure_exceptions import CloudError + +from .. import models + + +class CloudToDeviceMessagesOperations(object): + """CloudToDeviceMessagesOperations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: Version of the Api. Constant value: "2020-09-30". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-09-30" + + self.config = config + + def purge_cloud_to_device_message_queue( + self, id, custom_headers=None, raw=False, **operation_config): + """Deletes all the pending commands for a device in the IoT Hub. + + :param id: The unique identifier of the device. + :type id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: PurgeMessageQueueResult or ClientRawResponse if raw=true + :rtype: ~service.models.PurgeMessageQueueResult or + ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.purge_cloud_to_device_message_queue.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('PurgeMessageQueueResult', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + purge_cloud_to_device_message_queue.metadata = {'url': '/devices/{id}/commands'} + + def receive_feedback_notification( + self, custom_headers=None, raw=False, **operation_config): + """Gets the feedback for cloud-to-device messages. See + https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging for + more information. This capability is only available in the standard + tier IoT Hub. For more information, see [Choose the right IoT Hub + tier](https://aka.ms/scaleyouriotsolution). + + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.receive_feedback_notification.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 204]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + receive_feedback_notification.metadata = {'url': '/messages/serviceBound/feedback'} + + def complete_feedback_notification( + self, lock_token, custom_headers=None, raw=False, **operation_config): + """Completes the cloud-to-device feedback message. A completed message is + deleted from the feedback queue of the service. See + https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging for + more information. + + :param lock_token: The lock token obtained when the cloud-to-device + message is received. This is used to resolve race conditions when + completing a feedback message. + :type lock_token: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.complete_feedback_notification.metadata['url'] + path_format_arguments = { + 'lockToken': self._serialize.url("lock_token", lock_token, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + complete_feedback_notification.metadata = {'url': '/messages/serviceBound/feedback/{lockToken}'} + + def abandon_feedback_notification( + self, lock_token, custom_headers=None, raw=False, **operation_config): + """Abandons the lock on a cloud-to-device feedback message. See + https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging for + more information. + + :param lock_token: The lock token obtained when the cloud-to-device + message is received. + :type lock_token: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.abandon_feedback_notification.metadata['url'] + path_format_arguments = { + 'lockToken': self._serialize.url("lock_token", lock_token, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + abandon_feedback_notification.metadata = {'url': '/messages/serviceBound/feedback/{lockToken}/abandon'} diff --git a/azext_iot/sdk/iothub/service/operations/configuration_operations.py b/azext_iot/sdk/iothub/service/operations/configuration_operations.py index f49d8ca18..a1f2bf195 100644 --- a/azext_iot/sdk/iothub/service/operations/configuration_operations.py +++ b/azext_iot/sdk/iothub/service/operations/configuration_operations.py @@ -23,7 +23,7 @@ class ConfigurationOperations(object): :param config: Configuration of service client. :param serializer: An object model serializer. :param deserializer: An object model deserializer. - :ivar api_version: Version of the Api. Constant value: "2019-10-01". + :ivar api_version: Version of the Api. Constant value: "2020-09-30". """ models = models @@ -33,16 +33,16 @@ def __init__(self, client, config, serializer, deserializer): self._client = client self._serialize = serializer self._deserialize = deserializer - self.api_version = "2019-10-01" + self.api_version = "2020-09-30" self.config = config def get( self, id, custom_headers=None, raw=False, **operation_config): - """Retrieve a configuration for Iot Hub devices and modules by it - identifier. + """Gets a configuration on the IoT Hub for automatic device/module + management. - :param id: + :param id: The unique identifier of the configuration. :type id: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -98,16 +98,17 @@ def get( def create_or_update( self, id, configuration, if_match=None, custom_headers=None, raw=False, **operation_config): - """Create or update the configuration for devices or modules of an IoT - hub. An ETag must not be specified for the create operation. An ETag - must be specified for the update operation. Note that configuration Id - and Content cannot be updated by the user. + """Creates or updates a configuration on the IoT Hub for automatic + device/module management. Configuration identifier and Content cannot + be updated. - :param id: + :param id: The unique identifier of the configuration. :type id: str - :param configuration: + :param configuration: The configuration to be created or updated. :type configuration: ~service.models.Configuration - :param if_match: + :param if_match: The string representing a weak ETag for the + configuration, as per RFC7232. This should not be set when creating a + configuration, but may be set when updating a configuration. :type if_match: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -171,19 +172,17 @@ def create_or_update( def delete( self, id, if_match=None, custom_headers=None, raw=False, **operation_config): - """Delete the configuration for devices or modules of an IoT hub. This - request requires the If-Match header. The client may specify the ETag - for the device identity on the request in order to compare to the ETag - maintained by the service for the purpose of optimistic concurrency. - The delete operation is performed only if the ETag sent by the client - matches the value maintained by the server, indicating that the device - identity has not been modified since it was retrieved by the client. To - force an unconditional delete, set If-Match to the wildcard character - (*). - - :param id: + """Deletes a configuration on the IoT Hub for automatic device/module + management. + + :param id: The unique identifier of the configuration. :type id: str - :param if_match: + :param if_match: The string representing a weak ETag for the + configuration, as per RFC7232. The delete operation is performed only + if this ETag matches the value maintained by the server, indicating + that the configuration has not been modified since it was last + retrieved. To force an unconditional delete, set If-Match to the + wildcard character (*). :type if_match: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -232,11 +231,12 @@ def delete( def get_configurations( self, top=None, custom_headers=None, raw=False, **operation_config): - """Get multiple configurations for devices or modules of an IoT Hub. - Returns the specified number of configurations for Iot Hub. Pagination - is not supported. + """Gets configurations on the IoT Hub for automatic device/module + management. Pagination is not supported. - :param top: + :param top: The number of configurations to retrieve. Value will be + overridden if greater than the maximum deployment count for the IoT + Hub. :type top: int :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -290,15 +290,14 @@ def get_configurations( def test_queries( self, target_condition=None, custom_metric_queries=None, custom_headers=None, raw=False, **operation_config): - """Validates the target condition query and custom metric queries for a - configuration. - - Validates the target condition query and custom metric queries for a - configuration. + """Validates target condition and custom metric queries for a + configuration on the IoT Hub. - :param target_condition: + :param target_condition: The query used to define targeted devices or + modules. The query is based on twin tags and/or reported properties. :type target_condition: str - :param custom_metric_queries: + :param custom_metric_queries: The key-value pairs with queries and + their identifier. :type custom_metric_queries: dict[str, str] :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -357,23 +356,19 @@ def test_queries( def apply_on_edge_device( self, id, content, custom_headers=None, raw=False, **operation_config): - """Applies the provided configuration content to the specified edge - device. - - Applies the provided configuration content to the specified edge - device. Configuration content must have modules content. + """Applies the configuration content to an edge device. - :param id: Device ID. + :param id: The unique identifier of the edge device. :type id: str - :param content: Configuration Content. + :param content: The configuration content. :type content: ~service.models.ConfigurationContent :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the deserialized response :param operation_config: :ref:`Operation configuration overrides`. - :return: object or ClientRawResponse if raw=true - :rtype: object or ~msrest.pipeline.ClientRawResponse + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse :raises: :class:`CloudError` """ # Construct URL @@ -389,7 +384,6 @@ def apply_on_edge_device( # Construct headers header_parameters = {} - header_parameters['Accept'] = 'application/json' header_parameters['Content-Type'] = 'application/json; charset=utf-8' if self.config.generate_client_request_id: header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) diff --git a/azext_iot/sdk/iothub/service/operations/twin_operations.py b/azext_iot/sdk/iothub/service/operations/devices_operations.py similarity index 60% rename from azext_iot/sdk/iothub/service/operations/twin_operations.py rename to azext_iot/sdk/iothub/service/operations/devices_operations.py index 532a395e5..086e7607e 100644 --- a/azext_iot/sdk/iothub/service/operations/twin_operations.py +++ b/azext_iot/sdk/iothub/service/operations/devices_operations.py @@ -16,14 +16,14 @@ from .. import models -class TwinOperations(object): - """TwinOperations operations. +class DevicesOperations(object): + """DevicesOperations operations. :param client: Client for service requests. :param config: Configuration of service client. :param serializer: An object model serializer. :param deserializer: An object model deserializer. - :ivar api_version: Version of the Api. Constant value: "2019-10-01". + :ivar api_version: Version of the Api. Constant value: "2020-09-30". """ models = models @@ -33,38 +33,40 @@ def __init__(self, client, config, serializer, deserializer): self._client = client self._serialize = serializer self._deserialize = deserializer - self.api_version = "2019-10-01" + self.api_version = "2020-09-30" self.config = config - def get_device_twin( - self, id, custom_headers=None, raw=False, **operation_config): - """Gets a device twin. - - Gets a device twin. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins + def get_devices( + self, top=None, custom_headers=None, raw=False, **operation_config): + """Gets the identities of multiple devices from the IoT Hub identity + registry. Not recommended. Use the IoT Hub query API to retrieve device + twin and device identity information. See + https://docs.microsoft.com/en-us/rest/api/iothub/service/queryiothub + and + https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-query-language for more information. - :param id: Device ID. - :type id: str + :param top: The maximum number of device identities returned by the + query. Any value outside the range of 1-1000 is considered to be 1000. + :type top: int :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the deserialized response :param operation_config: :ref:`Operation configuration overrides`. - :return: Twin or ClientRawResponse if raw=true - :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse + :return: list or ClientRawResponse if raw=true + :rtype: list[~service.models.Device] or + ~msrest.pipeline.ClientRawResponse :raises: :class:`CloudError` """ # Construct URL - url = self.get_device_twin.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) + url = self.get_devices.metadata['url'] # Construct parameters query_parameters = {} + if top is not None: + query_parameters['top'] = self._serialize.query("top", top, 'int') query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') # Construct headers @@ -89,40 +91,32 @@ def get_device_twin( deserialized = None if response.status_code == 200: - deserialized = self._deserialize('Twin', response) + deserialized = self._deserialize('[Device]', response) if raw: client_raw_response = ClientRawResponse(deserialized, response) return client_raw_response return deserialized - get_device_twin.metadata = {'url': '/twins/{id}'} + get_devices.metadata = {'url': '/devices'} - def replace_device_twin( - self, id, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): - """Replaces tags and desired properties of a device twin. - - Replaces a device twin. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins - for more information. + def get_identity( + self, id, custom_headers=None, raw=False, **operation_config): + """Gets a device from the identity registry of the IoT Hub. - :param id: Device ID. + :param id: The unique identifier of the device. :type id: str - :param device_twin_info: Device twin info - :type device_twin_info: ~service.models.Twin - :param if_match: - :type if_match: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the deserialized response :param operation_config: :ref:`Operation configuration overrides`. - :return: Twin or ClientRawResponse if raw=true - :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse + :return: Device or ClientRawResponse if raw=true + :rtype: ~service.models.Device or ~msrest.pipeline.ClientRawResponse :raises: :class:`CloudError` """ # Construct URL - url = self.replace_device_twin.metadata['url'] + url = self.get_identity.metadata['url'] path_format_arguments = { 'id': self._serialize.url("id", id, 'str') } @@ -135,22 +129,15 @@ def replace_device_twin( # Construct headers header_parameters = {} header_parameters['Accept'] = 'application/json' - header_parameters['Content-Type'] = 'application/json; charset=utf-8' if self.config.generate_client_request_id: header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) if custom_headers: header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') if self.config.accept_language is not None: header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - # Construct body - # @digimaun - Change deserialize type to {object} from Twin - body_content = self._serialize.body(device_twin_info, '{object}') - # Construct and send request - request = self._client.put(url, query_parameters, header_parameters, body_content) + request = self._client.get(url, query_parameters, header_parameters) response = self._client.send(request, stream=False, **operation_config) if response.status_code not in [200]: @@ -161,40 +148,39 @@ def replace_device_twin( deserialized = None if response.status_code == 200: - deserialized = self._deserialize('Twin', response) + deserialized = self._deserialize('Device', response) if raw: client_raw_response = ClientRawResponse(deserialized, response) return client_raw_response return deserialized - replace_device_twin.metadata = {'url': '/twins/{id}'} - - def update_device_twin( - self, id, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): - """Updates tags and desired properties of a device twin. + get_identity.metadata = {'url': '/devices/{id}'} - Updates a device twin. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins - for more information. + def create_or_update_identity( + self, id, device, if_match=None, custom_headers=None, raw=False, **operation_config): + """Creates or updates the identity of a device in the identity registry of + the IoT Hub. - :param id: Device ID. + :param id: The unique identifier of the device. :type id: str - :param device_twin_info: Device twin info - :type device_twin_info: ~service.models.Twin - :param if_match: + :param device: The contents of the device identity. + :type device: ~service.models.Device + :param if_match: The string representing a weak ETag for the device + identity, as per RFC7232. This should not be set when creating a + device, but may be set when updating a device. :type if_match: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the deserialized response :param operation_config: :ref:`Operation configuration overrides`. - :return: Twin or ClientRawResponse if raw=true - :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse + :return: Device or ClientRawResponse if raw=true + :rtype: ~service.models.Device or ~msrest.pipeline.ClientRawResponse :raises: :class:`CloudError` """ # Construct URL - url = self.update_device_twin.metadata['url'] + url = self.create_or_update_identity.metadata['url'] path_format_arguments = { 'id': self._serialize.url("id", id, 'str') } @@ -218,10 +204,10 @@ def update_device_twin( header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') # Construct body - body_content = self._serialize.body(device_twin_info, 'Twin') + body_content = self._serialize.body(device, 'Device') # Construct and send request - request = self._client.patch(url, query_parameters, header_parameters, body_content) + request = self._client.put(url, query_parameters, header_parameters, body_content) response = self._client.send(request, stream=False, **operation_config) if response.status_code not in [200]: @@ -232,27 +218,82 @@ def update_device_twin( deserialized = None if response.status_code == 200: - deserialized = self._deserialize('Twin', response) + deserialized = self._deserialize('Device', response) if raw: client_raw_response = ClientRawResponse(deserialized, response) return client_raw_response return deserialized - update_device_twin.metadata = {'url': '/twins/{id}'} + create_or_update_identity.metadata = {'url': '/devices/{id}'} + + def delete_identity( + self, id, if_match=None, custom_headers=None, raw=False, **operation_config): + """Deletes the identity of a device from the identity registry of the IoT + Hub. + + :param id: The unique identifier of the device. + :type id: str + :param if_match: The string representing a weak ETag for the device + identity, as per RFC7232. The delete operation is performed only if + this ETag matches the value maintained by the server, indicating that + the device identity has not been modified since it was last retrieved. + To force an unconditional delete, set If-Match to the wildcard + character (*). + :type if_match: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.delete_identity.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp - def get_module_twin( - self, id, mid, custom_headers=None, raw=False, **operation_config): - """Gets a module twin. + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + delete_identity.metadata = {'url': '/devices/{id}'} - Gets a module twin. See + def get_twin( + self, id, custom_headers=None, raw=False, **operation_config): + """Gets the device twin. See https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins for more information. - :param id: Device ID. + :param id: The unique identifier of the device. :type id: str - :param mid: Module ID. - :type mid: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the deserialized response @@ -263,10 +304,9 @@ def get_module_twin( :raises: :class:`CloudError` """ # Construct URL - url = self.get_module_twin.metadata['url'] + url = self.get_twin.metadata['url'] path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') + 'id': self._serialize.url("id", id, 'str') } url = self._client.format_url(url, **path_format_arguments) @@ -303,23 +343,22 @@ def get_module_twin( return client_raw_response return deserialized - get_module_twin.metadata = {'url': '/twins/{id}/modules/{mid}'} + get_twin.metadata = {'url': '/twins/{id}'} - def replace_module_twin( - self, id, mid, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): - """Replaces tags and desired properties of a module twin. - - Replaces a module twin. See + def replace_twin( + self, id, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): + """Replaces the tags and desired properties of a device twin. See https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins for more information. - :param id: Device ID. + :param id: The unique identifier of the device. :type id: str - :param mid: Module ID. - :type mid: str - :param device_twin_info: Device twin info + :param device_twin_info: The device twin info that will replace the + existing info. :type device_twin_info: ~service.models.Twin - :param if_match: + :param if_match: The string representing a weak ETag for the device + twin, as per RFC7232. It determines if the replace operation should be + carried out. :type if_match: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -331,10 +370,9 @@ def replace_module_twin( :raises: :class:`CloudError` """ # Construct URL - url = self.replace_module_twin.metadata['url'] + url = self.replace_twin.metadata['url'] path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') + 'id': self._serialize.url("id", id, 'str') } url = self._client.format_url(url, **path_format_arguments) @@ -378,23 +416,22 @@ def replace_module_twin( return client_raw_response return deserialized - replace_module_twin.metadata = {'url': '/twins/{id}/modules/{mid}'} - - def update_module_twin( - self, id, mid, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): - """Updates tags and desired properties of a module twin. + replace_twin.metadata = {'url': '/twins/{id}'} - Updates a module twin. See + def update_twin( + self, id, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): + """Updates the tags and desired properties of a device twin. See https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins for more information. - :param id: Device ID. + :param id: The unique identifier of the device. :type id: str - :param mid: Module ID. - :type mid: str - :param device_twin_info: Device twin information + :param device_twin_info: The device twin info containing the tags and + desired properties to be updated. :type device_twin_info: ~service.models.Twin - :param if_match: + :param if_match: The string representing a weak ETag for the device + twin, as per RFC7232. It determines if the update operation should be + carried out. :type if_match: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -406,10 +443,9 @@ def update_module_twin( :raises: :class:`CloudError` """ # Construct URL - url = self.update_module_twin.metadata['url'] + url = self.update_twin.metadata['url'] path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') + 'id': self._serialize.url("id", id, 'str') } url = self._client.format_url(url, **path_format_arguments) @@ -452,4 +488,73 @@ def update_module_twin( return client_raw_response return deserialized - update_module_twin.metadata = {'url': '/twins/{id}/modules/{mid}'} + update_twin.metadata = {'url': '/twins/{id}'} + + def invoke_method( + self, device_id, direct_method_request, custom_headers=None, raw=False, **operation_config): + """Invokes a direct method on a device. See + https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-direct-methods + for more information. + + :param device_id: The unique identifier of the device. + :type device_id: str + :param direct_method_request: The parameters to execute a direct + method on the device. + :type direct_method_request: ~service.models.CloudToDeviceMethod + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: CloudToDeviceMethodResult or ClientRawResponse if raw=true + :rtype: ~service.models.CloudToDeviceMethodResult or + ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.invoke_method.metadata['url'] + path_format_arguments = { + 'deviceId': self._serialize.url("device_id", device_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + # @digimaun - Originally 'CloudToDeviceMethod'. Model serialization forces a null payload property to be removed. + # TODO: Test model behavior in latest autorest generator. + body_content = self._serialize.body(direct_method_request, 'object') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('CloudToDeviceMethodResult', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + invoke_method.metadata = {'url': '/twins/{deviceId}/methods'} diff --git a/azext_iot/sdk/iothub/service/operations/digital_twin_operations.py b/azext_iot/sdk/iothub/service/operations/digital_twin_operations.py index ab7279092..9f08e9956 100644 --- a/azext_iot/sdk/iothub/service/operations/digital_twin_operations.py +++ b/azext_iot/sdk/iothub/service/operations/digital_twin_operations.py @@ -23,7 +23,7 @@ class DigitalTwinOperations(object): :param config: Configuration of service client. :param serializer: An object model serializer. :param deserializer: An object model deserializer. - :ivar api_version: Version of the Api. Constant value: "2019-10-01". + :ivar api_version: Version of the Api. Constant value: "2020-09-30". """ models = models @@ -33,31 +33,29 @@ def __init__(self, client, config, serializer, deserializer): self._client = client self._serialize = serializer self._deserialize = deserializer - self.api_version = "2019-10-01" + self.api_version = "2020-09-30" self.config = config - def get_components( - self, digital_twin_id, custom_headers=None, raw=False, **operation_config): - """Gets the list of interfaces. + def get_digital_twin( + self, id, custom_headers=None, raw=False, **operation_config): + """Gets a digital twin. - :param digital_twin_id: Digital Twin ID. Format of digitalTwinId is - DeviceId[~ModuleId]. ModuleId is optional. - :type digital_twin_id: str + :param id: Digital Twin ID. + :type id: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the deserialized response :param operation_config: :ref:`Operation configuration overrides`. - :return: DigitalTwinInterfaces or ClientRawResponse if raw=true - :rtype: ~service.models.DigitalTwinInterfaces or - ~msrest.pipeline.ClientRawResponse + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse :raises: :class:`CloudError` """ # Construct URL - url = self.get_components.metadata['url'] + url = self.get_digital_twin.metadata['url'] path_format_arguments = { - 'digitalTwinId': self._serialize.url("digital_twin_id", digital_twin_id, 'str') + 'id': self._serialize.url("id", id, 'str') } url = self._client.format_url(url, **path_format_arguments) @@ -88,7 +86,7 @@ def get_components( header_dict = {} if response.status_code == 200: - deserialized = self._deserialize('DigitalTwinInterfaces', response) + deserialized = self._deserialize('object', response) header_dict = { 'ETag': 'str', } @@ -99,37 +97,31 @@ def get_components( return client_raw_response return deserialized - get_components.metadata = {'url': '/digitalTwins/{digitalTwinId}/interfaces'} + get_digital_twin.metadata = {'url': '/digitaltwins/{id}'} - def update_component( - self, digital_twin_id, if_match=None, interfaces=None, custom_headers=None, raw=False, **operation_config): - """Updates desired properties of multiple interfaces. - Example URI: "digitalTwins/{digitalTwinId}/interfaces". + def update_digital_twin( + self, id, digital_twin_patch, if_match=None, custom_headers=None, raw=False, **operation_config): + """Updates a digital twin. - :param digital_twin_id: Digital Twin ID. Format of digitalTwinId is - DeviceId[~ModuleId]. ModuleId is optional. - :type digital_twin_id: str + :param id: Digital Twin ID. + :type id: str + :param digital_twin_patch: json-patch contents to update. + :type digital_twin_patch: list[object] :param if_match: :type if_match: str - :param interfaces: Interface(s) data to patch in the digital twin. - :type interfaces: dict[str, - ~service.models.DigitalTwinInterfacesPatchInterfacesValue] :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the deserialized response :param operation_config: :ref:`Operation configuration overrides`. - :return: DigitalTwinInterfaces or ClientRawResponse if raw=true - :rtype: ~service.models.DigitalTwinInterfaces or - ~msrest.pipeline.ClientRawResponse + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse :raises: :class:`CloudError` """ - interfaces_patch_info = models.DigitalTwinInterfacesPatch(interfaces=interfaces) - # Construct URL - url = self.update_component.metadata['url'] + url = self.update_digital_twin.metadata['url'] path_format_arguments = { - 'digitalTwinId': self._serialize.url("digital_twin_id", digital_twin_id, 'str') + 'id': self._serialize.url("id", id, 'str') } url = self._client.format_url(url, **path_format_arguments) @@ -139,7 +131,6 @@ def update_component( # Construct headers header_parameters = {} - header_parameters['Accept'] = 'application/json' header_parameters['Content-Type'] = 'application/json; charset=utf-8' if self.config.generate_client_request_id: header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) @@ -151,116 +142,44 @@ def update_component( header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') # Construct body - body_content = self._serialize.body(interfaces_patch_info, 'DigitalTwinInterfacesPatch') + body_content = self._serialize.body(digital_twin_patch, '[object]') # Construct and send request request = self._client.patch(url, query_parameters, header_parameters, body_content) response = self._client.send(request, stream=False, **operation_config) - if response.status_code not in [200]: + if response.status_code not in [202]: exp = CloudError(response) exp.request_id = response.headers.get('x-ms-request-id') raise exp - deserialized = None - header_dict = {} - - if response.status_code == 200: - deserialized = self._deserialize('DigitalTwinInterfaces', response) - header_dict = { - 'ETag': 'str', - } - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - client_raw_response.add_headers(header_dict) + client_raw_response = ClientRawResponse(None, response) + client_raw_response.add_headers({ + 'ETag': 'str', + 'Location': 'str', + }) return client_raw_response + update_digital_twin.metadata = {'url': '/digitaltwins/{id}'} - return deserialized - update_component.metadata = {'url': '/digitalTwins/{digitalTwinId}/interfaces'} - - def get_component( - self, digital_twin_id, interface_name, custom_headers=None, raw=False, **operation_config): - """Gets the interface of given interfaceId. - Example URI: "digitalTwins/{digitalTwinId}/interfaces/{interfaceName}". - - :param digital_twin_id: Digital Twin ID. Format of digitalTwinId is - DeviceId[~ModuleId]. ModuleId is optional. - :type digital_twin_id: str - :param interface_name: The interface name. - :type interface_name: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: DigitalTwinInterfaces or ClientRawResponse if raw=true - :rtype: ~service.models.DigitalTwinInterfaces or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_component.metadata['url'] - path_format_arguments = { - 'digitalTwinId': self._serialize.url("digital_twin_id", digital_twin_id, 'str'), - 'interfaceName': self._serialize.url("interface_name", interface_name, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - header_dict = {} - - if response.status_code == 200: - deserialized = self._deserialize('DigitalTwinInterfaces', response) - header_dict = { - 'ETag': 'str', - } + def invoke_root_level_command( + self, id, command_name, payload, connect_timeout_in_seconds=None, response_timeout_in_seconds=None, custom_headers=None, raw=False, **operation_config): + """Invoke a digital twin root level command. - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - client_raw_response.add_headers(header_dict) - return client_raw_response + Invoke a digital twin root level command. - return deserialized - get_component.metadata = {'url': '/digitalTwins/{digitalTwinId}/interfaces/{interfaceName}'} - - def get_digital_twin_model( - self, model_id, expand=None, custom_headers=None, raw=False, **operation_config): - """Returns a DigitalTwin model definition for the given id. - If "expand" is present in the query parameters and id is for a device - capability model then it returns - the capability metamodel with expanded interface definitions. - - :param model_id: Model id Ex: - urn:contoso:TemperatureSensor:1 - :type model_id: str - :param expand: Indicates whether to expand the device capability - model's interface definitions inline or not. - This query parameter ONLY applies to Capability model. - :type expand: bool + :param id: + :type id: str + :param command_name: + :type command_name: str + :param payload: + :type payload: object + :param connect_timeout_in_seconds: Maximum interval of time, in + seconds, that the digital twin command will wait for the answer. + :type connect_timeout_in_seconds: int + :param response_timeout_in_seconds: Maximum interval of time, in + seconds, that the digital twin command will wait for the answer. + :type response_timeout_in_seconds: int :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the deserialized response @@ -271,21 +190,25 @@ def get_digital_twin_model( :raises: :class:`CloudError` """ # Construct URL - url = self.get_digital_twin_model.metadata['url'] + url = self.invoke_root_level_command.metadata['url'] path_format_arguments = { - 'modelId': self._serialize.url("model_id", model_id, 'str') + 'id': self._serialize.url("id", id, 'str'), + 'commandName': self._serialize.url("command_name", command_name, 'str') } url = self._client.format_url(url, **path_format_arguments) # Construct parameters query_parameters = {} - if expand is not None: - query_parameters['expand'] = self._serialize.query("expand", expand, 'bool') query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + if connect_timeout_in_seconds is not None: + query_parameters['connectTimeoutInSeconds'] = self._serialize.query("connect_timeout_in_seconds", connect_timeout_in_seconds, 'int') + if response_timeout_in_seconds is not None: + query_parameters['responseTimeoutInSeconds'] = self._serialize.query("response_timeout_in_seconds", response_timeout_in_seconds, 'int') # Construct headers header_parameters = {} header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' if self.config.generate_client_request_id: header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) if custom_headers: @@ -293,11 +216,14 @@ def get_digital_twin_model( if self.config.accept_language is not None: header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + # Construct body + body_content = self._serialize.body(payload, 'object') + # Construct and send request - request = self._client.get(url, query_parameters, header_parameters) + request = self._client.post(url, query_parameters, header_parameters, body_content) response = self._client.send(request, stream=False, **operation_config) - if response.status_code not in [200, 204]: + if response.status_code not in [200]: exp = CloudError(response) exp.request_id = response.headers.get('x-ms-request-id') raise exp @@ -308,10 +234,8 @@ def get_digital_twin_model( if response.status_code == 200: deserialized = self._deserialize('object', response) header_dict = { - 'ETag': 'str', - 'x-ms-model-id': 'str', - 'x-ms-model-resolution-status': 'str', - 'x-ms-model-resolution-description': 'str', + 'x-ms-command-statuscode': 'int', + 'x-ms-request-id': 'str', } if raw: @@ -320,25 +244,27 @@ def get_digital_twin_model( return client_raw_response return deserialized - get_digital_twin_model.metadata = {'url': '/digitalTwins/models/{modelId}'} + invoke_root_level_command.metadata = {'url': '/digitaltwins/{id}/commands/{commandName}'} def invoke_component_command( - self, digital_twin_id, interface_name, command_name, payload, connect_timeout_in_seconds=None, response_timeout_in_seconds=None, custom_headers=None, raw=False, **operation_config): - """Invoke a digital twin interface command. + self, id, component_path, command_name, payload, connect_timeout_in_seconds=None, response_timeout_in_seconds=None, custom_headers=None, raw=False, **operation_config): + """Invoke a digital twin command. - Invoke a digital twin interface command. + Invoke a digital twin command. - :param digital_twin_id: - :type digital_twin_id: str - :param interface_name: - :type interface_name: str + :param id: + :type id: str + :param component_path: + :type component_path: str :param command_name: :type command_name: str :param payload: :type payload: object - :param connect_timeout_in_seconds: Connect timeout in seconds. + :param connect_timeout_in_seconds: Maximum interval of time, in + seconds, that the digital twin command will wait for the answer. :type connect_timeout_in_seconds: int - :param response_timeout_in_seconds: Response timeout in seconds. + :param response_timeout_in_seconds: Maximum interval of time, in + seconds, that the digital twin command will wait for the answer. :type response_timeout_in_seconds: int :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -352,8 +278,8 @@ def invoke_component_command( # Construct URL url = self.invoke_component_command.metadata['url'] path_format_arguments = { - 'digitalTwinId': self._serialize.url("digital_twin_id", digital_twin_id, 'str'), - 'interfaceName': self._serialize.url("interface_name", interface_name, 'str'), + 'id': self._serialize.url("id", id, 'str'), + 'componentPath': self._serialize.url("component_path", component_path, 'str'), 'commandName': self._serialize.url("command_name", command_name, 'str') } url = self._client.format_url(url, **path_format_arguments) @@ -405,4 +331,4 @@ def invoke_component_command( return client_raw_response return deserialized - invoke_component_command.metadata = {'url': '/digitalTwins/{digitalTwinId}/interfaces/{interfaceName}/commands/{commandName}'} + invoke_component_command.metadata = {'url': '/digitaltwins/{id}/components/{componentPath}/commands/{commandName}'} diff --git a/azext_iot/sdk/iothub/service/operations/job_client_operations.py b/azext_iot/sdk/iothub/service/operations/jobs_operations.py similarity index 89% rename from azext_iot/sdk/iothub/service/operations/job_client_operations.py rename to azext_iot/sdk/iothub/service/operations/jobs_operations.py index 215dd9d57..b6ddc14de 100644 --- a/azext_iot/sdk/iothub/service/operations/job_client_operations.py +++ b/azext_iot/sdk/iothub/service/operations/jobs_operations.py @@ -16,14 +16,14 @@ from .. import models -class JobClientOperations(object): - """JobClientOperations operations. +class JobsOperations(object): + """JobsOperations operations. :param client: Client for service requests. :param config: Configuration of service client. :param serializer: An object model serializer. :param deserializer: An object model deserializer. - :ivar api_version: Version of the Api. Constant value: "2019-10-01". + :ivar api_version: Version of the Api. Constant value: "2020-09-30". """ models = models @@ -33,19 +33,17 @@ def __init__(self, client, config, serializer, deserializer): self._client = client self._serialize = serializer self._deserialize = deserializer - self.api_version = "2019-10-01" + self.api_version = "2020-09-30" self.config = config def create_import_export_job( self, job_properties, custom_headers=None, raw=False, **operation_config): - """Create a new import/export job on an IoT hub. - - Create a new import/export job on an IoT hub. See + """Creates a new import or export job on the IoT Hub. See https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities for more information. - :param job_properties: + :param job_properties: The job specifications. :type job_properties: ~service.models.JobProperties :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -101,9 +99,7 @@ def create_import_export_job( def get_import_export_jobs( self, custom_headers=None, raw=False, **operation_config): - """Gets the status of all import/export jobs in an iot hub. - - Gets the status of all import/export jobs in an iot hub. See + """Gets the status of all import and export jobs in the IoT Hub. See https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities for more information. @@ -157,13 +153,11 @@ def get_import_export_jobs( def get_import_export_job( self, id, custom_headers=None, raw=False, **operation_config): - """Gets the status of an import or export job in an iot hub. - - Gets the status of an import or export job in an iot hub. See + """Gets the status of an import or export job in the IoT Hub. See https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities for more information. - :param id: Job ID. + :param id: The unique identifier of the job. :type id: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -219,13 +213,9 @@ def get_import_export_job( def cancel_import_export_job( self, id, custom_headers=None, raw=False, **operation_config): - """Cancels an import or export job in an IoT hub. - - Cancels an import or export job in an IoT hub. See - https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities - for more information. + """Cancels an import or export job in the IoT Hub. - :param id: Job ID. + :param id: The unique identifier of the job. :type id: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -278,15 +268,13 @@ def cancel_import_export_job( return deserialized cancel_import_export_job.metadata = {'url': '/jobs/{id}'} - def get_job( + def get_scheduled_job( self, id, custom_headers=None, raw=False, **operation_config): - """Retrieves details of a scheduled job from an IoT hub. - - Retrieves details of a scheduled job from an IoT hub. See + """Gets details of a scheduled job from the IoT Hub. See https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-jobs for more information. - :param id: Job ID. + :param id: The unique identifier of the job. :type id: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -299,7 +287,7 @@ def get_job( :raises: :class:`CloudError` """ # Construct URL - url = self.get_job.metadata['url'] + url = self.get_scheduled_job.metadata['url'] path_format_arguments = { 'id': self._serialize.url("id", id, 'str') } @@ -338,21 +326,18 @@ def get_job( return client_raw_response return deserialized - get_job.metadata = {'url': '/jobs/v2/{id}'} + get_scheduled_job.metadata = {'url': '/jobs/v2/{id}'} - def create_job( + def create_scheduled_job( self, id, job_request, custom_headers=None, raw=False, **operation_config): - """Creates a new job to schedule update twins or device direct methods on - an IoT hub at a scheduled time. - - Creates a new job to schedule update twins or device direct methods on - an IoT hub at a scheduled time. See + """Creates a new job to schedule twin updates or direct methods on the IoT + Hub at a scheduled time. See https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-jobs for more information. - :param id: Job ID. + :param id: The unique identifier of the job. :type id: str - :param job_request: + :param job_request: The job request info. :type job_request: ~service.models.JobRequest :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -365,7 +350,7 @@ def create_job( :raises: :class:`CloudError` """ # Construct URL - url = self.create_job.metadata['url'] + url = self.create_scheduled_job.metadata['url'] path_format_arguments = { 'id': self._serialize.url("id", id, 'str') } @@ -408,17 +393,15 @@ def create_job( return client_raw_response return deserialized - create_job.metadata = {'url': '/jobs/v2/{id}'} + create_scheduled_job.metadata = {'url': '/jobs/v2/{id}'} - def cancel_job( + def cancel_scheduled_job( self, id, custom_headers=None, raw=False, **operation_config): - """Cancels a scheduled job on an IoT hub. - - Cancels a scheduled job on an IoT hub. See + """Cancels a scheduled job on the IoT Hub. See https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-jobs for more information. - :param id: Job ID. + :param id: The unique identifier of the job. :type id: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -431,7 +414,7 @@ def cancel_job( :raises: :class:`CloudError` """ # Construct URL - url = self.cancel_job.metadata['url'] + url = self.cancel_scheduled_job.metadata['url'] path_format_arguments = { 'id': self._serialize.url("id", id, 'str') } @@ -470,22 +453,21 @@ def cancel_job( return client_raw_response return deserialized - cancel_job.metadata = {'url': '/jobs/v2/{id}/cancel'} + cancel_scheduled_job.metadata = {'url': '/jobs/v2/{id}/cancel'} - def query_jobs( + def query_scheduled_jobs( self, job_type=None, job_status=None, custom_headers=None, raw=False, **operation_config): - """Query an IoT hub to retrieve information regarding jobs using the IoT - Hub query language. - - Query an IoT hub to retrieve information regarding jobs using the IoT - Hub query language. See + """Gets the information about jobs using an IoT Hub query. See https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-query-language - for more information. Pagination of results is supported. This returns - information about jobs only. + for more information. - :param job_type: Job Type. + :param job_type: The job type. See + https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-jobs#querying-for-progress-on-jobs + for a list of possible job types. :type job_type: str - :param job_status: Job Status. + :param job_status: The job status. See + https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-jobs#querying-for-progress-on-jobs + for a list of possible statuses. :type job_status: str :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the @@ -498,7 +480,7 @@ def query_jobs( :raises: :class:`CloudError` """ # Construct URL - url = self.query_jobs.metadata['url'] + url = self.query_scheduled_jobs.metadata['url'] # Construct parameters query_parameters = {} @@ -548,4 +530,4 @@ def query_jobs( return client_raw_response return deserialized - query_jobs.metadata = {'url': '/jobs/v2/query'} + query_scheduled_jobs.metadata = {'url': '/jobs/v2/query'} diff --git a/azext_iot/sdk/iothub/service/operations/modules_operations.py b/azext_iot/sdk/iothub/service/operations/modules_operations.py new file mode 100644 index 000000000..2f4ecf663 --- /dev/null +++ b/azext_iot/sdk/iothub/service/operations/modules_operations.py @@ -0,0 +1,576 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse +from msrestazure.azure_exceptions import CloudError + +from .. import models + + +class ModulesOperations(object): + """ModulesOperations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: Version of the Api. Constant value: "2020-09-30". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-09-30" + + self.config = config + + def get_twin( + self, id, mid, custom_headers=None, raw=False, **operation_config): + """Gets the module twin. See + https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins + for more information. + + :param id: The unique identifier of the device. + :type id: str + :param mid: The unique identifier of the module. + :type mid: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: Twin or ClientRawResponse if raw=true + :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.get_twin.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'mid': self._serialize.url("mid", mid, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('Twin', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_twin.metadata = {'url': '/twins/{id}/modules/{mid}'} + + def replace_twin( + self, id, mid, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): + """Replaces the tags and desired properties of a module twin. See + https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins + for more information. + + :param id: The unique identifier of the device. + :type id: str + :param mid: The unique identifier of the module. + :type mid: str + :param device_twin_info: The module twin info that will replace the + existing info. + :type device_twin_info: ~service.models.Twin + :param if_match: The string representing a weak ETag for the device + twin, as per RFC7232. It determines if the replace operation should be + carried out. + :type if_match: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: Twin or ClientRawResponse if raw=true + :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.replace_twin.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'mid': self._serialize.url("mid", mid, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + # @digimaun - Change deserialize type to {object} from Twin + body_content = self._serialize.body(device_twin_info, '{object}') + + # Construct and send request + request = self._client.put(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('Twin', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + replace_twin.metadata = {'url': '/twins/{id}/modules/{mid}'} + + def update_twin( + self, id, mid, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): + """Updates the tags and desired properties of a module twin. See + https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins + for more information. + + :param id: The unique identifier of the device. + :type id: str + :param mid: The unique identifier of the module. + :type mid: str + :param device_twin_info: The module twin info containing the tags and + desired properties to be updated. + :type device_twin_info: ~service.models.Twin + :param if_match: The string representing a weak ETag for the device + twin, as per RFC7232. It determines if the update operation should be + carried out. + :type if_match: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: Twin or ClientRawResponse if raw=true + :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.update_twin.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'mid': self._serialize.url("mid", mid, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + body_content = self._serialize.body(device_twin_info, 'Twin') + + # Construct and send request + request = self._client.patch(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('Twin', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + update_twin.metadata = {'url': '/twins/{id}/modules/{mid}'} + + def get_modules_on_device( + self, id, custom_headers=None, raw=False, **operation_config): + """Gets all the module identities on the device. + + :param id: The unique identifier of the device. + :type id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: list or ClientRawResponse if raw=true + :rtype: list[~service.models.Module] or + ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.get_modules_on_device.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('[Module]', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_modules_on_device.metadata = {'url': '/devices/{id}/modules'} + + def get_identity( + self, id, mid, custom_headers=None, raw=False, **operation_config): + """Gets a module identity on the device. + + :param id: The unique identifier of the device. + :type id: str + :param mid: The unique identifier of the module. + :type mid: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: Module or ClientRawResponse if raw=true + :rtype: ~service.models.Module or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.get_identity.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'mid': self._serialize.url("mid", mid, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('Module', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_identity.metadata = {'url': '/devices/{id}/modules/{mid}'} + + def create_or_update_identity( + self, id, mid, module, if_match=None, custom_headers=None, raw=False, **operation_config): + """Creates or updates the module identity for a device in the IoT Hub. The + moduleId and generationId cannot be updated by the user. + + :param id: The unique identifier of the device. + :type id: str + :param mid: The unique identifier of the module. + :type mid: str + :param module: The module identity. + :type module: ~service.models.Module + :param if_match: The string representing a weak ETag for the module, + as per RFC7232. This should not be set when creating a module, but may + be set when updating a module. + :type if_match: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: Module or ClientRawResponse if raw=true + :rtype: ~service.models.Module or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.create_or_update_identity.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'mid': self._serialize.url("mid", mid, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + body_content = self._serialize.body(module, 'Module') + + # Construct and send request + request = self._client.put(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 201]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('Module', response) + if response.status_code == 201: + deserialized = self._deserialize('Module', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + create_or_update_identity.metadata = {'url': '/devices/{id}/modules/{mid}'} + + def delete_identity( + self, id, mid, if_match=None, custom_headers=None, raw=False, **operation_config): + """Deletes the module identity for a device in the IoT Hub. + + :param id: The unique identifier of the deivce. + :type id: str + :param mid: The unique identifier of the module. + :type mid: str + :param if_match: The string representing a weak ETag for the module, + as per RFC7232. The delete operation is performed only if this ETag + matches the value maintained by the server, indicating that the module + has not been modified since it was last retrieved. To force an + unconditional delete, set If-Match to the wildcard character (*). + :type if_match: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: None or ClientRawResponse if raw=true + :rtype: None or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.delete_identity.metadata['url'] + path_format_arguments = { + 'id': self._serialize.url("id", id, 'str'), + 'mid': self._serialize.url("mid", mid, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if if_match is not None: + header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + if raw: + client_raw_response = ClientRawResponse(None, response) + return client_raw_response + delete_identity.metadata = {'url': '/devices/{id}/modules/{mid}'} + + def invoke_method( + self, device_id, module_id, direct_method_request, custom_headers=None, raw=False, **operation_config): + """Invokes a direct method on a module of a device. See + https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-direct-methods + for more information. + + :param device_id: The unique identifier of the device. + :type device_id: str + :param module_id: The unique identifier of the module. + :type module_id: str + :param direct_method_request: The parameters to execute a direct + method on the module. + :type direct_method_request: ~service.models.CloudToDeviceMethod + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: CloudToDeviceMethodResult or ClientRawResponse if raw=true + :rtype: ~service.models.CloudToDeviceMethodResult or + ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + # Construct URL + url = self.invoke_method.metadata['url'] + path_format_arguments = { + 'deviceId': self._serialize.url("device_id", device_id, 'str'), + 'moduleId': self._serialize.url("module_id", module_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + # @digimaun - Originally 'CloudToDeviceMethod'. Model serialization forces a null payload property to be removed. + # TODO: Test model behavior in latest autorest generator. + body_content = self._serialize.body(direct_method_request, 'object') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('CloudToDeviceMethodResult', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + invoke_method.metadata = {'url': '/twins/{deviceId}/modules/{moduleId}/methods'} diff --git a/azext_iot/sdk/iothub/service/operations/query_operations.py b/azext_iot/sdk/iothub/service/operations/query_operations.py new file mode 100644 index 000000000..33a9f4543 --- /dev/null +++ b/azext_iot/sdk/iothub/service/operations/query_operations.py @@ -0,0 +1,106 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +import uuid +from msrest.pipeline import ClientRawResponse +from msrestazure.azure_exceptions import CloudError + +from .. import models + + +class QueryOperations(object): + """QueryOperations operations. + + :param client: Client for service requests. + :param config: Configuration of service client. + :param serializer: An object model serializer. + :param deserializer: An object model deserializer. + :ivar api_version: Version of the Api. Constant value: "2020-09-30". + """ + + models = models + + def __init__(self, client, config, serializer, deserializer): + + self._client = client + self._serialize = serializer + self._deserialize = deserializer + self.api_version = "2020-09-30" + + self.config = config + + def get_twins( + self, query=None, x_ms_continuation=None, x_ms_max_item_count=None, custom_headers=None, raw=False, **operation_config): + """Query an IoT Hub to retrieve information regarding device twins using a + SQL-like language. See + https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-query-language + for more information. Pagination is supported. This returns information + about device twins only. + + :param x_ms_continuation: The continuation token used to get the next + page of results. + :type x_ms_continuation: str + :param x_ms_max_item_count: The maximum number of items returned per + page. The service may use a different value if the value specified is + not acceptable. + :type x_ms_max_item_count: str + :param query: The query string. + :type query: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: list or ClientRawResponse if raw=true + :rtype: list[~service.models.Twin] or + ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + query_specification = models.QuerySpecification(query=query) + + # Construct URL + url = self.get_twins.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if x_ms_continuation is not None: + header_parameters['x-ms-continuation'] = self._serialize.header("x_ms_continuation", x_ms_continuation, 'str') + if x_ms_max_item_count is not None: + header_parameters['x-ms-max-item-count'] = self._serialize.header("x_ms_max_item_count", x_ms_max_item_count, 'str') + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + body_content = self._serialize.body(query_specification, 'QuerySpecification') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + # @digimaun - custom work, cut the fluff + return ClientRawResponse(None, response) + + get_twins.metadata = {'url': '/devices/query'} diff --git a/azext_iot/sdk/iothub/service/operations/registry_manager_operations.py b/azext_iot/sdk/iothub/service/operations/registry_manager_operations.py deleted file mode 100644 index 4ddea8358..000000000 --- a/azext_iot/sdk/iothub/service/operations/registry_manager_operations.py +++ /dev/null @@ -1,857 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -import uuid -from msrest.pipeline import ClientRawResponse -from msrestazure.azure_exceptions import CloudError - -from .. import models - - -class RegistryManagerOperations(object): - """RegistryManagerOperations operations. - - :param client: Client for service requests. - :param config: Configuration of service client. - :param serializer: An object model serializer. - :param deserializer: An object model deserializer. - :ivar api_version: Version of the Api. Constant value: "2019-10-01". - """ - - models = models - - def __init__(self, client, config, serializer, deserializer): - - self._client = client - self._serialize = serializer - self._deserialize = deserializer - self.api_version = "2019-10-01" - - self.config = config - - def get_device_statistics( - self, custom_headers=None, raw=False, **operation_config): - """Retrieves statistics about device identities in the IoT hub’s identity - registry. - - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: RegistryStatistics or ClientRawResponse if raw=true - :rtype: ~service.models.RegistryStatistics or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_device_statistics.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('RegistryStatistics', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_device_statistics.metadata = {'url': '/statistics/devices'} - - def get_service_statistics( - self, custom_headers=None, raw=False, **operation_config): - """Retrieves service statistics for this IoT hub’s identity registry. - - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: ServiceStatistics or ClientRawResponse if raw=true - :rtype: ~service.models.ServiceStatistics or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_service_statistics.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('ServiceStatistics', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_service_statistics.metadata = {'url': '/statistics/service'} - - def get_devices( - self, top=None, custom_headers=None, raw=False, **operation_config): - """Get the identities of multiple devices from the IoT hub identity - registry. Not recommended. Use the IoT Hub query language to retrieve - device twin and device identity information. See - https://docs.microsoft.com/en-us/rest/api/iothub/service/queryiothub - and - https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-query-language - for more information. - - :param top: This parameter when specified, defines the maximum number - of device identities that are returned. Any value outside the range of - 1-1000 is considered to be 1000. - :type top: int - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: list or ClientRawResponse if raw=true - :rtype: list[~service.models.Device] or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_devices.metadata['url'] - - # Construct parameters - query_parameters = {} - if top is not None: - query_parameters['top'] = self._serialize.query("top", top, 'int') - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('[Device]', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_devices.metadata = {'url': '/devices'} - - def bulk_device_crud( - self, devices, custom_headers=None, raw=False, **operation_config): - """Create, update, or delete the identities of multiple devices from the - IoT hub identity registry. - - Create, update, or delete the identiies of multiple devices from the - IoT hub identity registry. A device identity can be specified only once - in the list. Different operations (create, update, delete) on different - devices are allowed. A maximum of 100 devices can be specified per - invocation. For large scale operations, consider using the import - feature using blob - storage(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities). - - :param devices: - :type devices: list[~service.models.ExportImportDevice] - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: BulkRegistryOperationResult or ClientRawResponse if raw=true - :rtype: ~service.models.BulkRegistryOperationResult or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.bulk_device_crud.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(devices, '[ExportImportDevice]') - - # Construct and send request - request = self._client.post(url, query_parameters, header_parameters, body_content) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200, 400]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('BulkRegistryOperationResult', response) - if response.status_code == 400: - deserialized = self._deserialize('BulkRegistryOperationResult', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - bulk_device_crud.metadata = {'url': '/devices'} - - def query_iot_hub( - self, query=None, x_ms_continuation=None, x_ms_max_item_count=None, custom_headers=None, raw=False, **operation_config): - """Query an IoT hub to retrieve information regarding device twins using a - SQL-like language. - - Query an IoT hub to retrieve information regarding device twins using a - SQL-like language. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-query-language - for more information. Pagination of results is supported. This returns - information about device twins only. - - :param x_ms_continuation: - :type x_ms_continuation: str - :param x_ms_max_item_count: - :type x_ms_max_item_count: str - :param query: The query. - :type query: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: list or ClientRawResponse if raw=true - :rtype: list[~service.models.Twin] or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - query_specification = models.QuerySpecification(query=query) - - # Construct URL - url = self.query_iot_hub.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if x_ms_continuation is not None: - header_parameters['x-ms-continuation'] = self._serialize.header("x_ms_continuation", x_ms_continuation, 'str') - if x_ms_max_item_count is not None: - header_parameters['x-ms-max-item-count'] = self._serialize.header("x_ms_max_item_count", x_ms_max_item_count, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(query_specification, 'QuerySpecification') - - # Construct and send request - request = self._client.post(url, query_parameters, header_parameters, body_content) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - # @digimaun - custom work, cut the fluff - return ClientRawResponse(None, response) - - query_iot_hub.metadata = {'url': '/devices/query'} - - def get_device( - self, id, custom_headers=None, raw=False, **operation_config): - """Retrieve a device from the identity registry of an IoT hub. - - Retrieve a device from the identity registry of an IoT hub. - - :param id: Device ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Device or ClientRawResponse if raw=true - :rtype: ~service.models.Device or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_device.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('Device', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_device.metadata = {'url': '/devices/{id}'} - - def create_or_update_device( - self, id, device, if_match=None, custom_headers=None, raw=False, **operation_config): - """Create or update the identity of a device in the identity registry of - an IoT hub. - - Create or update the identity of a device in the identity registry of - an IoT hub. An ETag must not be specified for the create operation. An - ETag must be specified for the update operation. Note that generationId - and deviceId cannot be updated by the user. - - :param id: Device ID. - :type id: str - :param device: - :type device: ~service.models.Device - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Device or ClientRawResponse if raw=true - :rtype: ~service.models.Device or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.create_or_update_device.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(device, 'Device') - - # Construct and send request - request = self._client.put(url, query_parameters, header_parameters, body_content) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('Device', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - create_or_update_device.metadata = {'url': '/devices/{id}'} - - def delete_device( - self, id, if_match=None, custom_headers=None, raw=False, **operation_config): - """Delete the identity of a device from the identity registry of an IoT - hub. - - Delete the identity of a device from the identity registry of an IoT - hub. This request requires the If-Match header. The client may specify - the ETag for the device identity on the request in order to compare to - the ETag maintained by the service for the purpose of optimistic - concurrency. The delete operation is performed only if the ETag sent by - the client matches the value maintained by the server, indicating that - the device identity has not been modified since it was retrieved by the - client. To force an unconditional delete, set If-Match to the wildcard - character (*). - - :param id: Device ID. - :type id: str - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: None or ClientRawResponse if raw=true - :rtype: None or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.delete_device.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [204]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - if raw: - client_raw_response = ClientRawResponse(None, response) - return client_raw_response - delete_device.metadata = {'url': '/devices/{id}'} - - def purge_command_queue( - self, id, custom_headers=None, raw=False, **operation_config): - """Deletes all the pending commands for this device from the IoT hub. - - Deletes all the pending commands for this device from the IoT hub. - - :param id: Device ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: PurgeMessageQueueResult or ClientRawResponse if raw=true - :rtype: ~service.models.PurgeMessageQueueResult or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.purge_command_queue.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('PurgeMessageQueueResult', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - purge_command_queue.metadata = {'url': '/devices/{id}/commands'} - - def get_modules_on_device( - self, id, custom_headers=None, raw=False, **operation_config): - """Retrieve all the module identities on the device. - - :param id: Device ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: list or ClientRawResponse if raw=true - :rtype: list[~service.models.Module] or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_modules_on_device.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('[Module]', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_modules_on_device.metadata = {'url': '/devices/{id}/modules'} - - def get_module( - self, id, mid, custom_headers=None, raw=False, **operation_config): - """Retrieve the specified module identity on the device. - - :param id: Device ID. - :type id: str - :param mid: Module ID. - :type mid: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Module or ClientRawResponse if raw=true - :rtype: ~service.models.Module or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_module.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('Module', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_module.metadata = {'url': '/devices/{id}/modules/{mid}'} - - def create_or_update_module( - self, id, mid, module, if_match=None, custom_headers=None, raw=False, **operation_config): - """Create or update the module identity for device in IoT hub. An ETag - must not be specified for the create operation. An ETag must be - specified for the update operation. Note that moduleId and generation - cannot be updated by the user. - - :param id: Device ID. - :type id: str - :param mid: Module ID. - :type mid: str - :param module: - :type module: ~service.models.Module - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Module or ClientRawResponse if raw=true - :rtype: ~service.models.Module or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.create_or_update_module.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(module, 'Module') - - # Construct and send request - request = self._client.put(url, query_parameters, header_parameters, body_content) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200, 201]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('Module', response) - if response.status_code == 201: - deserialized = self._deserialize('Module', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - create_or_update_module.metadata = {'url': '/devices/{id}/modules/{mid}'} - - def delete_module( - self, id, mid, if_match=None, custom_headers=None, raw=False, **operation_config): - """Delete the module identity for device of an IoT hub. This request - requires the If-Match header. The client may specify the ETag for the - device identity on the request in order to compare to the ETag - maintained by the service for the purpose of optimistic concurrency. - The delete operation is performed only if the ETag sent by the client - matches the value maintained by the server, indicating that the device - identity has not been modified since it was retrieved by the client. To - force an unconditional delete, set If-Match to the wildcard character - (*). - - :param id: Device ID. - :type id: str - :param mid: Module ID. - :type mid: str - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: None or ClientRawResponse if raw=true - :rtype: None or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.delete_module.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [204]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - if raw: - client_raw_response = ClientRawResponse(None, response) - return client_raw_response - delete_module.metadata = {'url': '/devices/{id}/modules/{mid}'} diff --git a/azext_iot/sdk/iothub/service/operations/fault_injection_operations.py b/azext_iot/sdk/iothub/service/operations/statistics_operations.py similarity index 73% rename from azext_iot/sdk/iothub/service/operations/fault_injection_operations.py rename to azext_iot/sdk/iothub/service/operations/statistics_operations.py index 49ddc4d1f..b6060f1ec 100644 --- a/azext_iot/sdk/iothub/service/operations/fault_injection_operations.py +++ b/azext_iot/sdk/iothub/service/operations/statistics_operations.py @@ -16,14 +16,14 @@ from .. import models -class FaultInjectionOperations(object): - """FaultInjectionOperations operations. +class StatisticsOperations(object): + """StatisticsOperations operations. :param client: Client for service requests. :param config: Configuration of service client. :param serializer: An object model serializer. :param deserializer: An object model deserializer. - :ivar api_version: Version of the Api. Constant value: "2019-10-01". + :ivar api_version: Version of the Api. Constant value: "2020-09-30". """ models = models @@ -33,26 +33,27 @@ def __init__(self, client, config, serializer, deserializer): self._client = client self._serialize = serializer self._deserialize = deserializer - self.api_version = "2019-10-01" + self.api_version = "2020-09-30" self.config = config - def get( + def get_device_statistics( self, custom_headers=None, raw=False, **operation_config): - """Get FaultInjection entity. + """Gets device statistics of the IoT Hub identity registry, such as total + device count. :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the deserialized response :param operation_config: :ref:`Operation configuration overrides`. - :return: FaultInjectionProperties or ClientRawResponse if raw=true - :rtype: ~service.models.FaultInjectionProperties or + :return: RegistryStatistics or ClientRawResponse if raw=true + :rtype: ~service.models.RegistryStatistics or ~msrest.pipeline.ClientRawResponse :raises: :class:`CloudError` """ # Construct URL - url = self.get.metadata['url'] + url = self.get_device_statistics.metadata['url'] # Construct parameters query_parameters = {} @@ -80,32 +81,32 @@ def get( deserialized = None if response.status_code == 200: - deserialized = self._deserialize('FaultInjectionProperties', response) + deserialized = self._deserialize('RegistryStatistics', response) if raw: client_raw_response = ClientRawResponse(deserialized, response) return client_raw_response return deserialized - get.metadata = {'url': '/faultInjection'} + get_device_statistics.metadata = {'url': '/statistics/devices'} - def set( - self, value, custom_headers=None, raw=False, **operation_config): - """Create or update FaultInjection entity. + def get_service_statistics( + self, custom_headers=None, raw=False, **operation_config): + """Gets service statistics of the IoT Hub identity registry, such as + connected device count. - :param value: - :type value: ~service.models.FaultInjectionProperties :param dict custom_headers: headers that will be added to the request :param bool raw: returns the direct response alongside the deserialized response :param operation_config: :ref:`Operation configuration overrides`. - :return: None or ClientRawResponse if raw=true - :rtype: None or ~msrest.pipeline.ClientRawResponse + :return: ServiceStatistics or ClientRawResponse if raw=true + :rtype: ~service.models.ServiceStatistics or + ~msrest.pipeline.ClientRawResponse :raises: :class:`CloudError` """ # Construct URL - url = self.set.metadata['url'] + url = self.get_service_statistics.metadata['url'] # Construct parameters query_parameters = {} @@ -113,7 +114,7 @@ def set( # Construct headers header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' + header_parameters['Accept'] = 'application/json' if self.config.generate_client_request_id: header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) if custom_headers: @@ -121,11 +122,8 @@ def set( if self.config.accept_language is not None: header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - # Construct body - body_content = self._serialize.body(value, 'FaultInjectionProperties') - # Construct and send request - request = self._client.put(url, query_parameters, header_parameters, body_content) + request = self._client.get(url, query_parameters, header_parameters) response = self._client.send(request, stream=False, **operation_config) if response.status_code not in [200]: @@ -133,7 +131,14 @@ def set( exp.request_id = response.headers.get('x-ms-request-id') raise exp + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('ServiceStatistics', response) + if raw: - client_raw_response = ClientRawResponse(None, response) + client_raw_response = ClientRawResponse(deserialized, response) return client_raw_response - set.metadata = {'url': '/faultInjection'} + + return deserialized + get_service_statistics.metadata = {'url': '/statistics/service'} diff --git a/azext_iot/sdk/iothub/service/version.py b/azext_iot/sdk/iothub/service/version.py index f0880ef49..f4d73cc97 100644 --- a/azext_iot/sdk/iothub/service/version.py +++ b/azext_iot/sdk/iothub/service/version.py @@ -9,5 +9,5 @@ # regenerated. # -------------------------------------------------------------------------- -VERSION = "2019-10-01" +VERSION = "2020-09-30" diff --git a/azext_iot/sdk/pnp/digital_twin_repository_service.py b/azext_iot/sdk/pnp/digital_twin_repository_service.py deleted file mode 100644 index 9c1bf8e1f..000000000 --- a/azext_iot/sdk/pnp/digital_twin_repository_service.py +++ /dev/null @@ -1,361 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.service_client import SDKClient -from msrest import Configuration, Serializer, Deserializer -from .version import VERSION -from msrest.pipeline import ClientRawResponse -from msrest.exceptions import HttpOperationError -from . import models -from azext_iot.constants import USER_AGENT - - -class DigitalTwinRepositoryServiceConfiguration(Configuration): - """Configuration for DigitalTwinRepositoryService - Note that all parameters used to create this instance are saved as instance - attributes. - - :param str base_url: Service URL - """ - - def __init__( - self, base_url=None): - - if not base_url: - base_url = 'http://localhost' - - super(DigitalTwinRepositoryServiceConfiguration, self).__init__(base_url) - self.add_user_agent('digitaltwinrepositoryservice/{}'.format(VERSION)) - self.add_user_agent(USER_AGENT) - - -class DigitalTwinRepositoryService(SDKClient): - """DigitalTwin Model Repository Service. - - :ivar config: Configuration for client. - :vartype config: DigitalTwinRepositoryServiceConfiguration - - :param str base_url: Service URL - """ - - def __init__( - self, base_url=None): - - self.config = DigitalTwinRepositoryServiceConfiguration(base_url) - super(DigitalTwinRepositoryService, self).__init__(None, self.config) - - client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} - self.api_version = 'v1' - self._serialize = Serializer(client_models) - self._deserialize = Deserializer(client_models) - - - def get_model( - self, model_id, api_version, repository_id=None, x_ms_client_request_id=None, expand=False, custom_headers=None, raw=False, **operation_config): - """Returns a DigitalTwin model object for the given \"id\".\r\nIf - \"expand\" is present in the query parameters and \"id\" is for a - capability model then it returns\r\nthe capability model with expanded - interface definitions. - - :param model_id: Model id Ex: - urn:contoso:com:temparaturesensor:1 - :type model_id: str - :param api_version: Version of the Api. Must be 2019-07-01-preview - :type api_version: str - :param repository_id: To access private repo, repositoryId is the repo - id. To access global repo, caller should not specify this value. - :type repository_id: str - :param x_ms_client_request_id: Optional. Provides a client-generated - opaque value that is recorded in the logs. Using this header is highly - recommended for correlating client-side activities with requests - received by the server. - :type x_ms_client_request_id: str - :param expand: Indicates whether to expand the capability model's - interface definitions inline or not. This query parameter ONLY applies - to Capability model. - :type expand: bool - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: object or ClientRawResponse if raw=true - :rtype: object or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`HttpOperationError` - """ - # Construct URL - url = self.get_model.metadata['url'] - path_format_arguments = { - 'modelId': self._serialize.url("model_id", model_id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - if repository_id is not None: - query_parameters['repositoryId'] = self._serialize.query("repository_id", repository_id, 'str') - query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') - if expand is not None: - query_parameters['expand'] = self._serialize.query("expand", expand, 'bool') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/ld+json' - if custom_headers: - header_parameters.update(custom_headers) - if x_ms_client_request_id is not None: - header_parameters['x-ms-client-request-id'] = self._serialize.header("x_ms_client_request_id", x_ms_client_request_id, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - raise HttpOperationError(self._deserialize, response) - - deserialized = None - header_dict = {} - - if response.status_code == 200: - deserialized = self._deserialize('{object}', response) - header_dict = { - 'x-ms-request-id': 'str', - 'ETag': 'str', - 'x-ms-model-id': 'str', - 'x-ms-model-publisher-id': 'str', - 'x-ms-model-publisher-name': 'str', - 'x-ms-model-createdon': 'iso-8601', - 'x-ms-model-lastupdated': 'iso-8601', - } - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - client_raw_response.add_headers(header_dict) - return client_raw_response - - return deserialized - get_model.metadata = {'url': '/models/{modelId}'} - - def create_or_update_model( - self, model_id, api_version, content, repository_id=None, x_ms_client_request_id=None, if_match=None, custom_headers=None, raw=False, **operation_config): - """Creates or updates the DigitalTwin Model in the repository. - - :param model_id: Model id Ex: - urn:contoso:TemparatureSensor:1 - :type model_id: str - :param api_version: Version of the Api. Must be 2019-07-01-preview - :type api_version: str - :param content: Model definition in Digital Twin Definition Language - format. - :type content: object - :param repository_id: To access private repo, repositoryId is the repo - id\\r\\nTo access global repo, caller should not specify this value. - :type repository_id: str - :param x_ms_client_request_id: Optional. Provides a client-generated - opaque value that is recorded in the logs. Using this header is highly - recommended for correlating client-side activities with requests - received by the server. - :type x_ms_client_request_id: str - :param if_match: Used to make operation conditional for optimistic - concurrency. That is, the document is updated only if the specified - etag matches the current version in the database. The value should be - set to the etag value of the resource. - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: None or ClientRawResponse if raw=true - :rtype: None or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`HttpOperationError` - """ - # Construct URL - url = self.create_or_update_model.metadata['url'] - path_format_arguments = { - 'modelId': self._serialize.url("model_id", model_id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - if repository_id is not None: - query_parameters['repositoryId'] = self._serialize.query("repository_id", repository_id, 'str') - query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if custom_headers: - header_parameters.update(custom_headers) - if x_ms_client_request_id is not None: - header_parameters['x-ms-client-request-id'] = self._serialize.header("x_ms_client_request_id", x_ms_client_request_id, 'str') - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - - # Construct body - body_content = self._serialize.body(content, 'object') - # Construct and send request - request = self._client.put(url, query_parameters, header_parameters, body_content) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [201, 204, 412]: - raise HttpOperationError(self._deserialize, response) - - if raw: - client_raw_response = ClientRawResponse(None, response) - client_raw_response.add_headers({ - 'x-ms-request-id': 'str', - 'ETag': 'str', - }) - return client_raw_response - create_or_update_model.metadata = {'url': '/models/{modelId}'} - - def delete_model( - self, model_id, repository_id, api_version, x_ms_client_request_id=None, custom_headers=None, raw=False, **operation_config): - """Deletes a digital twin model from the repository. - - :param model_id: Model id Ex: - urn:contoso:com:temparaturesensor:1 - :type model_id: str - :param repository_id: To access private repo, repositoryId is the repo - id. Delete is not allowed for public repository. - :type repository_id: str - :param api_version: Version of the Api. Must be 2019-07-01-preview - :type api_version: str - :param x_ms_client_request_id: Optional. Provides a client-generated - opaque value that is recorded in the logs. Using this header is highly - recommended for correlating client-side activities with requests - received by the server. - :type x_ms_client_request_id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: None or ClientRawResponse if raw=true - :rtype: None or ~msrest.pipeline.ClientRawResponse - :raises: - :class:`HttpOperationError` - """ - # Construct URL - url = self.delete_model.metadata['url'] - path_format_arguments = { - 'modelId': self._serialize.url("model_id", model_id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['repositoryId'] = self._serialize.query("repository_id", repository_id, 'str') - query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') - - # Construct headers - header_parameters = {} - if custom_headers: - header_parameters.update(custom_headers) - if x_ms_client_request_id is not None: - header_parameters['x-ms-client-request-id'] = self._serialize.header("x_ms_client_request_id", x_ms_client_request_id, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters, header_parameters) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [204]: - raise HttpOperationError(self._deserialize, response) - - if raw: - client_raw_response = ClientRawResponse(None, response) - client_raw_response.add_headers({ - 'x-ms-request-id': 'str', - }) - return client_raw_response - delete_model.metadata = {'url': '/models/{modelId}'} - - def search( - self, search_options, api_version, repository_id=None, x_ms_client_request_id=None, custom_headers=None, raw=False, **operation_config): - """Searches pnp models for given search options. - It searches in the "Description, DisplayName, Comment and Id" metadata. - - :param search_options: Set SearchOption.searchKeyword to search models - with the keyword. - Set the "SearchOptions.modelFilterType" to restrict to a type of - DigitalTwin model (Ex: Interface or CapabilityModel). - Default it returns all the models. - :type search_options: - ~digitaltwinmodelrepositoryservice.models.SearchOptions - :param api_version: Version of the Api. Must be 2019-07-01-preview - :type api_version: str - :param repository_id: To access private repo, repositoryId is the repo - id.\\r\\nDelete is not allowed for public repository. - :type repository_id: str - :param x_ms_client_request_id: Optional. Provides a client-generated - opaque value that is recorded in the logs. Using this header is highly - recommended for correlating client-side activities with requests - received by the server.. - :type x_ms_client_request_id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: SearchResponse or ClientRawResponse if raw=true - :rtype: ~digitaltwinmodelrepositoryservice.models.SearchResponse or - ~msrest.pipeline.ClientRawResponse - :raises: - :class:`HttpOperationError` - """ - # Construct URL - url = self.search.metadata['url'] - - # Construct parameters - query_parameters = {} - if repository_id is not None: - query_parameters['repositoryId'] = self._serialize.query("repository_id", repository_id, 'str') - query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Accept'] = 'application/json' - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if custom_headers: - header_parameters.update(custom_headers) - if x_ms_client_request_id is not None: - header_parameters['x-ms-client-request-id'] = self._serialize.header("x_ms_client_request_id", x_ms_client_request_id, 'str') - - # Construct body - body_content = self._serialize.body(search_options, 'SearchOptions') - - # Construct and send request - request = self._client.post(url, query_parameters, header_parameters, body_content) - response = self._client.send(request, stream=False, **operation_config) - - if response.status_code not in [200]: - raise HttpOperationError(self._deserialize, response) - - deserialized = None - header_dict = {} - - if response.status_code == 200: - deserialized = self._deserialize('SearchResponse', response) - header_dict = { - 'x-ms-request-id': 'str', - } - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - client_raw_response.add_headers(header_dict) - return client_raw_response - - return deserialized - search.metadata = {'url': '/models/search'} diff --git a/azext_iot/sdk/pnp/models/__init__.py b/azext_iot/sdk/pnp/models/__init__.py deleted file mode 100644 index 21a611417..000000000 --- a/azext_iot/sdk/pnp/models/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -try: - from .search_options_py3 import SearchOptions - from .model_information_py3 import ModelInformation - from .search_response_py3 import SearchResponse -except (SyntaxError, ImportError): - from .search_options import SearchOptions - from .model_information import ModelInformation - from .search_response import SearchResponse - -__all__ = [ - 'SearchOptions', - 'ModelInformation', - 'SearchResponse', -] diff --git a/azext_iot/sdk/pnp/models/model_information.py b/azext_iot/sdk/pnp/models/model_information.py deleted file mode 100644 index c3f804945..000000000 --- a/azext_iot/sdk/pnp/models/model_information.py +++ /dev/null @@ -1,72 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class ModelInformation(Model): - """ModelInformation. - - :param comment: - :type comment: str - :param description: - :type description: str - :param display_name: - :type display_name: str - :param urn_id: - :type urn_id: str - :param model_name: - :type model_name: str - :param version: - :type version: int - :param type: Possible values include: 'interface', 'capabilityModel' - :type type: str or ~digitaltwinmodelrepositoryservice.models.enum - :param etag: - :type etag: str - :param publisher_id: - :type publisher_id: str - :param publisher_name: - :type publisher_name: str - :param created_on: - :type created_on: datetime - :param updated_on: - :type updated_on: datetime - """ - - _attribute_map = { - 'comment': {'key': 'comment', 'type': 'str'}, - 'description': {'key': 'description', 'type': 'str'}, - 'display_name': {'key': 'displayName', 'type': 'str'}, - 'urn_id': {'key': 'urnId', 'type': 'str'}, - 'model_name': {'key': 'modelName', 'type': 'str'}, - 'version': {'key': 'version', 'type': 'int'}, - 'type': {'key': 'type', 'type': 'str'}, - 'etag': {'key': 'etag', 'type': 'str'}, - 'publisher_id': {'key': 'publisherId', 'type': 'str'}, - 'publisher_name': {'key': 'publisherName', 'type': 'str'}, - 'created_on': {'key': 'createdOn', 'type': 'iso-8601'}, - 'updated_on': {'key': 'updatedOn', 'type': 'iso-8601'}, - } - - def __init__(self, **kwargs): - super(ModelInformation, self).__init__(**kwargs) - self.comment = kwargs.get('comment', None) - self.description = kwargs.get('description', None) - self.display_name = kwargs.get('display_name', None) - self.urn_id = kwargs.get('urn_id', None) - self.model_name = kwargs.get('model_name', None) - self.version = kwargs.get('version', None) - self.type = kwargs.get('type', None) - self.etag = kwargs.get('etag', None) - self.publisher_id = kwargs.get('publisher_id', None) - self.publisher_name = kwargs.get('publisher_name', None) - self.created_on = kwargs.get('created_on', None) - self.updated_on = kwargs.get('updated_on', None) diff --git a/azext_iot/sdk/pnp/models/model_information_py3.py b/azext_iot/sdk/pnp/models/model_information_py3.py deleted file mode 100644 index 4ec8acd27..000000000 --- a/azext_iot/sdk/pnp/models/model_information_py3.py +++ /dev/null @@ -1,72 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class ModelInformation(Model): - """ModelInformation. - - :param comment: - :type comment: str - :param description: - :type description: str - :param display_name: - :type display_name: str - :param urn_id: - :type urn_id: str - :param model_name: - :type model_name: str - :param version: - :type version: int - :param type: Possible values include: 'interface', 'capabilityModel' - :type type: str or ~digitaltwinmodelrepositoryservice.models.enum - :param etag: - :type etag: str - :param publisher_id: - :type publisher_id: str - :param publisher_name: - :type publisher_name: str - :param created_on: - :type created_on: datetime - :param updated_on: - :type updated_on: datetime - """ - - _attribute_map = { - 'comment': {'key': 'comment', 'type': 'str'}, - 'description': {'key': 'description', 'type': 'str'}, - 'display_name': {'key': 'displayName', 'type': 'str'}, - 'urn_id': {'key': 'urnId', 'type': 'str'}, - 'model_name': {'key': 'modelName', 'type': 'str'}, - 'version': {'key': 'version', 'type': 'int'}, - 'type': {'key': 'type', 'type': 'str'}, - 'etag': {'key': 'etag', 'type': 'str'}, - 'publisher_id': {'key': 'publisherId', 'type': 'str'}, - 'publisher_name': {'key': 'publisherName', 'type': 'str'}, - 'created_on': {'key': 'createdOn', 'type': 'iso-8601'}, - 'updated_on': {'key': 'updatedOn', 'type': 'iso-8601'}, - } - - def __init__(self, *, comment: str=None, description: str=None, display_name: str=None, urn_id: str=None, model_name: str=None, version: int=None, type=None, etag: str=None, publisher_id: str=None, publisher_name: str=None, created_on=None, updated_on=None, **kwargs) -> None: - super(ModelInformation, self).__init__(**kwargs) - self.comment = comment - self.description = description - self.display_name = display_name - self.urn_id = urn_id - self.model_name = model_name - self.version = version - self.type = type - self.etag = etag - self.publisher_id = publisher_id - self.publisher_name = publisher_name - self.created_on = created_on - self.updated_on = updated_on diff --git a/azext_iot/sdk/pnp/models/search_options.py b/azext_iot/sdk/pnp/models/search_options.py deleted file mode 100644 index 497740e04..000000000 --- a/azext_iot/sdk/pnp/models/search_options.py +++ /dev/null @@ -1,42 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class SearchOptions(Model): - """SearchOptions. - - :param search_keyword: - :type search_keyword: str - :param model_filter_type: Possible values include: 'interface', - 'capabilityModel' - :type model_filter_type: str or - ~digitaltwinmodelrepositoryservice.models.enum - :param continuation_token: - :type continuation_token: str - :param page_size: - :type page_size: int - """ - - _attribute_map = { - 'search_keyword': {'key': 'searchKeyword', 'type': 'str'}, - 'model_filter_type': {'key': 'modelFilterType', 'type': 'str'}, - 'continuation_token': {'key': 'continuationToken', 'type': 'str'}, - 'page_size': {'key': 'pageSize', 'type': 'int'}, - } - - def __init__(self, **kwargs): - super(SearchOptions, self).__init__(**kwargs) - self.search_keyword = kwargs.get('search_keyword', None) - self.model_filter_type = kwargs.get('model_filter_type', None) - self.continuation_token = kwargs.get('continuation_token', None) - self.page_size = kwargs.get('page_size', None) diff --git a/azext_iot/sdk/pnp/models/search_options_py3.py b/azext_iot/sdk/pnp/models/search_options_py3.py deleted file mode 100644 index f6da4fd89..000000000 --- a/azext_iot/sdk/pnp/models/search_options_py3.py +++ /dev/null @@ -1,42 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class SearchOptions(Model): - """SearchOptions. - - :param search_keyword: - :type search_keyword: str - :param model_filter_type: Possible values include: 'interface', - 'capabilityModel' - :type model_filter_type: str or - ~digitaltwinmodelrepositoryservice.models.enum - :param continuation_token: - :type continuation_token: str - :param page_size: - :type page_size: int - """ - - _attribute_map = { - 'search_keyword': {'key': 'searchKeyword', 'type': 'str'}, - 'model_filter_type': {'key': 'modelFilterType', 'type': 'str'}, - 'continuation_token': {'key': 'continuationToken', 'type': 'str'}, - 'page_size': {'key': 'pageSize', 'type': 'int'}, - } - - def __init__(self, *, search_keyword: str=None, model_filter_type=None, continuation_token: str=None, page_size: int=None, **kwargs) -> None: - super(SearchOptions, self).__init__(**kwargs) - self.search_keyword = search_keyword - self.model_filter_type = model_filter_type - self.continuation_token = continuation_token - self.page_size = page_size diff --git a/azext_iot/sdk/product/__init__.py b/azext_iot/sdk/product/__init__.py new file mode 100644 index 000000000..8c6b25599 --- /dev/null +++ b/azext_iot/sdk/product/__init__.py @@ -0,0 +1,18 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from .aicsapi import AICSAPI +from .version import VERSION + +__all__ = ['AICSAPI'] + +__version__ = VERSION + diff --git a/azext_iot/sdk/product/aicsapi.py b/azext_iot/sdk/product/aicsapi.py new file mode 100644 index 000000000..6533b8498 --- /dev/null +++ b/azext_iot/sdk/product/aicsapi.py @@ -0,0 +1,1032 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.service_client import SDKClient +from msrest import Serializer, Deserializer +from msrestazure import AzureConfiguration +from .version import VERSION +from msrest.pipeline import ClientRawResponse +from msrestazure.azure_exceptions import CloudError +import uuid +from . import models + + +class AICSAPIConfiguration(AzureConfiguration): + """Configuration for AICSAPI + Note that all parameters used to create this instance are saved as instance + attributes. + + :param credentials: Credentials needed for the client to connect to Azure. + :type credentials: :mod:`A msrestazure Credentials + object` + :param str base_url: Service URL + """ + + def __init__( + self, credentials, base_url=None): + + if credentials is None: + raise ValueError("Parameter 'credentials' must not be None.") + if not base_url: + base_url = 'http://localhost' + + super(AICSAPIConfiguration, self).__init__(base_url) + + self.credentials = credentials + + +class AICSAPI(SDKClient): + """AICSAPI + + :ivar config: Configuration for client. + :vartype config: AICSAPIConfiguration + + :param credentials: Credentials needed for the client to connect to Azure. + :type credentials: :mod:`A msrestazure Credentials + object` + :param str base_url: Service URL + """ + + def __init__( + self, credentials, base_url=None): + + self.config = AICSAPIConfiguration(credentials, base_url) + super(AICSAPI, self).__init__(self.config.credentials, self.config) + + client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} + self.api_version = '2020-05-01-preview' + self._serialize = Serializer(client_models) + self._deserialize = Deserializer(client_models) + + + def get_device_certification_requirements( + self, badge_type=None, custom_headers=None, raw=False, **operation_config): + """Get certification requirements. + + :param badge_type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type badge_type: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.get_device_certification_requirements.metadata['url'] + + # Construct parameters + query_parameters = {} + if badge_type is not None: + query_parameters['badgeType'] = self._serialize.query("badge_type", badge_type, 'str') + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 400]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('[DeviceCertificationRequirement]', response) + if response.status_code == 400: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_device_certification_requirements.metadata = {'url': '/certificationRequirements'} + + def create_device_test( + self, generate_provisioning_configuration=None, body=None, custom_headers=None, raw=False, **operation_config): + """Create a new Microsoft.Azure.IoT.TestKit.Models.DeviceTest. + + :param generate_provisioning_configuration: Whether to generate + ProvisioningConfiguration info from the server, + it only applies to + Microsoft.Azure.IoT.TestKit.Shared.Models.Provisioning.ProvisioningType.SymmetricKey + and + Microsoft.Azure.IoT.TestKit.Shared.Models.Provisioning.ProvisioningType.ConnectionString + provisioning type. + :type generate_provisioning_configuration: bool + :param body: + :type body: ~product.models.DeviceTest + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.create_device_test.metadata['url'] + + # Construct parameters + query_parameters = {} + if generate_provisioning_configuration is not None: + query_parameters['GenerateProvisioningConfiguration'] = self._serialize.query("generate_provisioning_configuration", generate_provisioning_configuration, 'bool') + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json-patch+json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + if body is not None: + body_content = self._serialize.body(body, 'DeviceTest') + else: + body_content = None + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 400]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('DeviceTest', response) + if response.status_code == 400: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + create_device_test.metadata = {'url': '/deviceTests'} + + def get_device_test( + self, device_test_id, custom_headers=None, raw=False, **operation_config): + """Get a DeviceTest by Id. + + :param device_test_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTest to retrieve. + :type device_test_id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.get_device_test.metadata['url'] + path_format_arguments = { + 'deviceTestId': self._serialize.url("device_test_id", device_test_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 400]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('DeviceTest', response) + if response.status_code == 400: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_device_test.metadata = {'url': '/deviceTests/{deviceTestId}'} + + def update_device_test( + self, device_test_id, generate_provisioning_configuration=None, body=None, custom_headers=None, raw=False, **operation_config): + """Update the DeviceTest with certain Id. + + :param device_test_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTest to retrieve. + :type device_test_id: str + :param generate_provisioning_configuration: Whether to generate + ProvisioningConfiguration info from the server, + it only applies to + Microsoft.Azure.IoT.TestKit.Shared.Models.Provisioning.ProvisioningType.SymmetricKey + and + Microsoft.Azure.IoT.TestKit.Shared.Models.Provisioning.ProvisioningType.ConnectionString + provisioning type. + :type generate_provisioning_configuration: bool + :param body: + :type body: ~product.models.DeviceTest + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.update_device_test.metadata['url'] + path_format_arguments = { + 'deviceTestId': self._serialize.url("device_test_id", device_test_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + if generate_provisioning_configuration is not None: + query_parameters['GenerateProvisioningConfiguration'] = self._serialize.query("generate_provisioning_configuration", generate_provisioning_configuration, 'bool') + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json-patch+json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + if body is not None: + body_content = self._serialize.body(body, 'DeviceTest') + else: + body_content = None + + # Construct and send request + request = self._client.put(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 400]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('DeviceTest', response) + if response.status_code == 400: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + update_device_test.metadata = {'url': '/deviceTests/{deviceTestId}'} + + def search_device_test( + self, body=None, custom_headers=None, raw=False, **operation_config): + """Search DeviceTest. + + :param body: + :type body: ~product.models.DeviceTestSearchOptions + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.search_device_test.metadata['url'] + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json-patch+json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + if body is not None: + body_content = self._serialize.body(body, 'DeviceTestSearchOptions') + else: + body_content = None + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 400]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('[DeviceTestSearchResult]', response) + if response.status_code == 400: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + search_device_test.metadata = {'url': '/deviceTests/search'} + + def create_device_test_task( + self, device_test_id, task_type=None, custom_headers=None, raw=False, **operation_config): + """Queue a new async + Microsoft.Azure.IoT.TestKit.Shared.Models.DeviceTestTaskType for a + DeviceTest. + The user can only have one running task for a DeviceTest. + + :param device_test_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTest to retrieve. + :type device_test_id: str + :param task_type: Possible values include: 'QueueTestRun', + 'GenerateTestCases' + :type task_type: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + body = None + if task_type is not None: + body = models.NewTaskPayload(task_type=task_type) + + api_version = "2020-05-01-preview" + + # Construct URL + url = self.create_device_test_task.metadata['url'] + path_format_arguments = { + 'deviceTestId': self._serialize.url("device_test_id", device_test_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json-patch+json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + if body is not None: + body_content = self._serialize.body(body, 'NewTaskPayload') + else: + body_content = None + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [202, 400, 404, 409]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 202: + deserialized = self._deserialize('DeviceTestTask', response) + if response.status_code == 400: + deserialized = self._deserialize('object', response) + if response.status_code == 404: + deserialized = self._deserialize('object', response) + if response.status_code == 409: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + create_device_test_task.metadata = {'url': '/deviceTests/{deviceTestId}/tasks'} + + def cancel_device_test_task( + self, task_id, device_test_id, custom_headers=None, raw=False, **operation_config): + """Cancel the running tasks of a DeviceTest. + + :param task_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTestTask to retrieve. + :type task_id: str + :param device_test_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTest to retrieve. + :type device_test_id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.cancel_device_test_task.metadata['url'] + path_format_arguments = { + 'taskId': self._serialize.url("task_id", task_id, 'str'), + 'deviceTestId': self._serialize.url("device_test_id", device_test_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.delete(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [202, 400, 404]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 400: + deserialized = self._deserialize('object', response) + if response.status_code == 404: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + cancel_device_test_task.metadata = {'url': '/deviceTests/{deviceTestId}/tasks/{taskId}'} + + def get_device_test_task( + self, task_id, device_test_id, custom_headers=None, raw=False, **operation_config): + """Get the task status of a DeviceTest. + + :param task_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTestTask to retrieve. + :type task_id: str + :param device_test_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTest to retrieve. + :type device_test_id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.get_device_test_task.metadata['url'] + path_format_arguments = { + 'taskId': self._serialize.url("task_id", task_id, 'str'), + 'deviceTestId': self._serialize.url("device_test_id", device_test_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 400, 404]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('DeviceTestTask', response) + if response.status_code == 400: + deserialized = self._deserialize('object', response) + if response.status_code == 404: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_device_test_task.metadata = {'url': '/deviceTests/{deviceTestId}/tasks/{taskId}'} + + def get_running_device_test_tasks( + self, device_test_id, custom_headers=None, raw=False, **operation_config): + """Get the running tasks of a DeviceTest. Current implementation only + allows one running task. + + :param device_test_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTest to retrieve. + :type device_test_id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.get_running_device_test_tasks.metadata['url'] + path_format_arguments = { + 'deviceTestId': self._serialize.url("device_test_id", device_test_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 400, 404]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('[DeviceTestTask]', response) + if response.status_code == 400: + deserialized = self._deserialize('object', response) + if response.status_code == 404: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_running_device_test_tasks.metadata = {'url': '/deviceTests/{deviceTestId}/tasks/running'} + + def get_test_cases( + self, device_test_id, custom_headers=None, raw=False, **operation_config): + """Get the testcases of a DeviceTest. They are generated through + Microsoft.Azure.IoT.TestKit.Shared.Models.DeviceTestTaskType.GenerateTestCases + task. + + :param device_test_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTest to retrieve. + :type device_test_id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.get_test_cases.metadata['url'] + path_format_arguments = { + 'deviceTestId': self._serialize.url("device_test_id", device_test_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 400, 404]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('TestCases', response) + if response.status_code == 400: + deserialized = self._deserialize('object', response) + if response.status_code == 404: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_test_cases.metadata = {'url': '/deviceTests/{deviceTestId}/TestCases'} + + def update_test_cases( + self, device_test_id, certification_badge_test_cases=None, custom_headers=None, raw=False, **operation_config): + """Update the testcases settings of a DeviceTest. The test cases cannot be + added or removed through this API. + + :param device_test_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTest to retrieve. + :type device_test_id: str + :param certification_badge_test_cases: + :type certification_badge_test_cases: list[object] + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + body = None + if certification_badge_test_cases is not None: + body = models.TestCases(certification_badge_test_cases=certification_badge_test_cases) + + api_version = "2020-05-01-preview" + + # Construct URL + url = self.update_test_cases.metadata['url'] + path_format_arguments = { + 'deviceTestId': self._serialize.url("device_test_id", device_test_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + header_parameters['Content-Type'] = 'application/json-patch+json; charset=utf-8' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct body + if body is not None: + body_content = self._serialize.body(body, 'TestCases') + else: + body_content = None + + # Construct and send request + request = self._client.patch(url, query_parameters, header_parameters, body_content) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204, 400, 404]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 400: + deserialized = self._deserialize('object', response) + if response.status_code == 404: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + update_test_cases.metadata = {'url': '/deviceTests/{deviceTestId}/TestCases'} + + def get_latest_test_run( + self, device_test_id, custom_headers=None, raw=False, **operation_config): + """Get the latest test run of the DeviceTest with the deviceTestId. + + :param device_test_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTest to retrieve. + :type device_test_id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.get_latest_test_run.metadata['url'] + path_format_arguments = { + 'deviceTestId': self._serialize.url("device_test_id", device_test_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 400, 404]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('TestRun', response) + if response.status_code == 400: + deserialized = self._deserialize('object', response) + if response.status_code == 404: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_latest_test_run.metadata = {'url': '/deviceTests/{deviceTestId}/testRuns/latest'} + + def get_test_run( + self, test_run_id, device_test_id, custom_headers=None, raw=False, **operation_config): + """Get the test run with testrunId of the DeviceTest with the + deviceTestId. + + :param test_run_id: The Id of a + Microsoft.Azure.IoT.TestKit.Models.TestRun. + :type test_run_id: str + :param device_test_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTest to retrieve. + :type device_test_id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.get_test_run.metadata['url'] + path_format_arguments = { + 'testRunId': self._serialize.url("test_run_id", test_run_id, 'str'), + 'deviceTestId': self._serialize.url("device_test_id", device_test_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.get(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [200, 400, 404]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 200: + deserialized = self._deserialize('TestRun', response) + if response.status_code == 400: + deserialized = self._deserialize('object', response) + if response.status_code == 404: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + get_test_run.metadata = {'url': '/deviceTests/{deviceTestId}/testRuns/{testRunId}'} + + def submit_test_run( + self, test_run_id, device_test_id, custom_headers=None, raw=False, **operation_config): + """Submit TestRun to Partner/Product service. + + :param test_run_id: The Id of a + Microsoft.Azure.IoT.TestKit.Models.TestRun. + :type test_run_id: str + :param device_test_id: The Id of the + Microsoft.Azure.IoT.TestKit.Models.DeviceTest to retrieve. + :type device_test_id: str + :param dict custom_headers: headers that will be added to the request + :param bool raw: returns the direct response alongside the + deserialized response + :param operation_config: :ref:`Operation configuration + overrides`. + :return: object or ClientRawResponse if raw=true + :rtype: object or ~msrest.pipeline.ClientRawResponse + :raises: :class:`CloudError` + """ + api_version = "2020-05-01-preview" + + # Construct URL + url = self.submit_test_run.metadata['url'] + path_format_arguments = { + 'testRunId': self._serialize.url("test_run_id", test_run_id, 'str'), + 'deviceTestId': self._serialize.url("device_test_id", device_test_id, 'str') + } + url = self._client.format_url(url, **path_format_arguments) + + # Construct parameters + query_parameters = {} + query_parameters['api-version'] = self._serialize.query("api_version", api_version, 'str') + + # Construct headers + header_parameters = {} + header_parameters['Accept'] = 'application/json' + if self.config.generate_client_request_id: + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if custom_headers: + header_parameters.update(custom_headers) + if self.config.accept_language is not None: + header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') + + # Construct and send request + request = self._client.post(url, query_parameters, header_parameters) + response = self._client.send(request, stream=False, **operation_config) + + if response.status_code not in [204, 400, 404, 500]: + exp = CloudError(response) + exp.request_id = response.headers.get('x-ms-request-id') + raise exp + + deserialized = None + + if response.status_code == 400: + deserialized = self._deserialize('object', response) + if response.status_code == 404: + deserialized = self._deserialize('object', response) + + if raw: + client_raw_response = ClientRawResponse(deserialized, response) + return client_raw_response + + return deserialized + submit_test_run.metadata = {'url': '/deviceTests/{deviceTestId}/testRuns/{testRunId}/submit'} diff --git a/azext_iot/sdk/product/models/__init__.py b/azext_iot/sdk/product/models/__init__.py new file mode 100644 index 000000000..609b4014d --- /dev/null +++ b/azext_iot/sdk/product/models/__init__.py @@ -0,0 +1,187 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +try: + from .device_certification_provisioning_requirement_py3 import DeviceCertificationProvisioningRequirement + from .device_certification_requirement_py3 import DeviceCertificationRequirement + from .badge_info_py3 import BadgeInfo + from .device_test_search_result_py3 import DeviceTestSearchResult + from .device_test_task_py3 import DeviceTestTask + from .validation_problem_details_py3 import ValidationProblemDetails + from .x509_enrollment_py3 import X509Enrollment + from .symmetric_key_enrollment_py3 import SymmetricKeyEnrollment + from .tpm_enrollment_py3 import TpmEnrollment + from .provisioning_configuration_py3 import ProvisioningConfiguration + from .iot_device_certification_badge_configuration_py3 import IotDeviceCertificationBadgeConfiguration + from .iot_edge_compatible_certification_badge_configuration_py3 import IotEdgeCompatibleCertificationBadgeConfiguration + from .model_resolution_source_py3 import ModelResolutionSource + from .pnp_certification_badge_configuration_py3 import PnpCertificationBadgeConfiguration + from .device_test_py3 import DeviceTest + from .device_test_search_options_py3 import DeviceTestSearchOptions + from .new_task_payload_py3 import NewTaskPayload + from .cannot_retrieve_model_reposistory_sas_token_error_py3 import CannotRetrieveModelReposistorySasTokenError + from .device_test_not_exist_error_py3 import DeviceTestNotExistError + from .existing_task_running_conflict_error_py3 import ExistingTaskRunningConflictError + from .fail_to_queue_task_error_py3 import FailToQueueTaskError + from .model_resolution_failure_error_py3 import ModelResolutionFailureError + from .system_error_py3 import SystemError + from .test_cases_not_exist_error_py3 import TestCasesNotExistError + from .test_run_not_exist_error_py3 import TestRunNotExistError + from .c2_dtest_py3 import C2DTest + from .d2_ctest_py3 import D2CTest + from .device_twin_test_py3 import DeviceTwinTest + from .direct_method_test_py3 import DirectMethodTest + from .iot_device_certification_badge_test_cases_py3 import IotDeviceCertificationBadgeTestCases + from .iot_edge_compatible_certification_badge_test_cases_py3 import IotEdgeCompatibleCertificationBadgeTestCases + from .interface_definition_snapshot_py3 import InterfaceDefinitionSnapshot + from .model_definition_snapshot_py3 import ModelDefinitionSnapshot + from .array_schema_py3 import ArraySchema + from .enum_value_py3 import EnumValue + from .enum_schema_py3 import EnumSchema + from .schema_field_py3 import SchemaField + from .map_schema_py3 import MapSchema + from .object_schema_py3 import ObjectSchema + from .property_model_py3 import PropertyModel + from .property_test_py3 import PropertyTest + from .command_model_py3 import CommandModel + from .command_test_py3 import CommandTest + from .telemetry_model_py3 import TelemetryModel + from .telemetry_test_py3 import TelemetryTest + from .interface_test_py3 import InterfaceTest + from .pnp_certification_badge_test_cases_py3 import PnpCertificationBadgeTestCases + from .test_cases_py3 import TestCases + from .certification_task_log_py3 import CertificationTaskLog + from .iot_device_validation_task_result_py3 import IotDeviceValidationTaskResult + from .iot_device_certification_badge_result_py3 import IotDeviceCertificationBadgeResult + from .edge_device_validation_task_result_py3 import EdgeDeviceValidationTaskResult + from .iot_edge_compatible_certification_badge_result_py3 import IotEdgeCompatibleCertificationBadgeResult + from .digital_twin_validation_task_result_py3 import DigitalTwinValidationTaskResult + from .pre_validation_task_result_py3 import PreValidationTaskResult + from .pnp_certification_badge_result_py3 import PnpCertificationBadgeResult + from .test_run_py3 import TestRun +except (SyntaxError, ImportError): + from .device_certification_provisioning_requirement import DeviceCertificationProvisioningRequirement + from .device_certification_requirement import DeviceCertificationRequirement + from .badge_info import BadgeInfo + from .device_test_search_result import DeviceTestSearchResult + from .device_test_task import DeviceTestTask + from .validation_problem_details import ValidationProblemDetails + from .x509_enrollment import X509Enrollment + from .symmetric_key_enrollment import SymmetricKeyEnrollment + from .tpm_enrollment import TpmEnrollment + from .provisioning_configuration import ProvisioningConfiguration + from .iot_device_certification_badge_configuration import IotDeviceCertificationBadgeConfiguration + from .iot_edge_compatible_certification_badge_configuration import IotEdgeCompatibleCertificationBadgeConfiguration + from .model_resolution_source import ModelResolutionSource + from .pnp_certification_badge_configuration import PnpCertificationBadgeConfiguration + from .device_test import DeviceTest + from .device_test_search_options import DeviceTestSearchOptions + from .new_task_payload import NewTaskPayload + from .cannot_retrieve_model_reposistory_sas_token_error import CannotRetrieveModelReposistorySasTokenError + from .device_test_not_exist_error import DeviceTestNotExistError + from .existing_task_running_conflict_error import ExistingTaskRunningConflictError + from .fail_to_queue_task_error import FailToQueueTaskError + from .model_resolution_failure_error import ModelResolutionFailureError + from .system_error import SystemError + from .test_cases_not_exist_error import TestCasesNotExistError + from .test_run_not_exist_error import TestRunNotExistError + from .c2_dtest import C2DTest + from .d2_ctest import D2CTest + from .device_twin_test import DeviceTwinTest + from .direct_method_test import DirectMethodTest + from .iot_device_certification_badge_test_cases import IotDeviceCertificationBadgeTestCases + from .iot_edge_compatible_certification_badge_test_cases import IotEdgeCompatibleCertificationBadgeTestCases + from .interface_definition_snapshot import InterfaceDefinitionSnapshot + from .model_definition_snapshot import ModelDefinitionSnapshot + from .array_schema import ArraySchema + from .enum_value import EnumValue + from .enum_schema import EnumSchema + from .schema_field import SchemaField + from .map_schema import MapSchema + from .object_schema import ObjectSchema + from .property_model import PropertyModel + from .property_test import PropertyTest + from .command_model import CommandModel + from .command_test import CommandTest + from .telemetry_model import TelemetryModel + from .telemetry_test import TelemetryTest + from .interface_test import InterfaceTest + from .pnp_certification_badge_test_cases import PnpCertificationBadgeTestCases + from .test_cases import TestCases + from .certification_task_log import CertificationTaskLog + from .iot_device_validation_task_result import IotDeviceValidationTaskResult + from .iot_device_certification_badge_result import IotDeviceCertificationBadgeResult + from .edge_device_validation_task_result import EdgeDeviceValidationTaskResult + from .iot_edge_compatible_certification_badge_result import IotEdgeCompatibleCertificationBadgeResult + from .digital_twin_validation_task_result import DigitalTwinValidationTaskResult + from .pre_validation_task_result import PreValidationTaskResult + from .pnp_certification_badge_result import PnpCertificationBadgeResult + from .test_run import TestRun + +__all__ = [ + 'DeviceCertificationProvisioningRequirement', + 'DeviceCertificationRequirement', + 'BadgeInfo', + 'DeviceTestSearchResult', + 'DeviceTestTask', + 'ValidationProblemDetails', + 'X509Enrollment', + 'SymmetricKeyEnrollment', + 'TpmEnrollment', + 'ProvisioningConfiguration', + 'IotDeviceCertificationBadgeConfiguration', + 'IotEdgeCompatibleCertificationBadgeConfiguration', + 'ModelResolutionSource', + 'PnpCertificationBadgeConfiguration', + 'DeviceTest', + 'DeviceTestSearchOptions', + 'NewTaskPayload', + 'CannotRetrieveModelReposistorySasTokenError', + 'DeviceTestNotExistError', + 'ExistingTaskRunningConflictError', + 'FailToQueueTaskError', + 'ModelResolutionFailureError', + 'SystemError', + 'TestCasesNotExistError', + 'TestRunNotExistError', + 'C2DTest', + 'D2CTest', + 'DeviceTwinTest', + 'DirectMethodTest', + 'IotDeviceCertificationBadgeTestCases', + 'IotEdgeCompatibleCertificationBadgeTestCases', + 'InterfaceDefinitionSnapshot', + 'ModelDefinitionSnapshot', + 'ArraySchema', + 'EnumValue', + 'EnumSchema', + 'SchemaField', + 'MapSchema', + 'ObjectSchema', + 'PropertyModel', + 'PropertyTest', + 'CommandModel', + 'CommandTest', + 'TelemetryModel', + 'TelemetryTest', + 'InterfaceTest', + 'PnpCertificationBadgeTestCases', + 'TestCases', + 'CertificationTaskLog', + 'IotDeviceValidationTaskResult', + 'IotDeviceCertificationBadgeResult', + 'EdgeDeviceValidationTaskResult', + 'IotEdgeCompatibleCertificationBadgeResult', + 'DigitalTwinValidationTaskResult', + 'PreValidationTaskResult', + 'PnpCertificationBadgeResult', + 'TestRun', +] diff --git a/azext_iot/sdk/product/models/array_schema.py b/azext_iot/sdk/product/models/array_schema.py new file mode 100644 index 000000000..cefd28d64 --- /dev/null +++ b/azext_iot/sdk/product/models/array_schema.py @@ -0,0 +1,58 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ArraySchema(Model): + """ArraySchema. + + :param element_schema: + :type element_schema: object + :param schema_type: Possible values include: 'Unknown', 'Boolean', 'Date', + 'DateTime', 'Double', 'Duration', 'Float', 'Integer', 'Long', 'String', + 'Time', 'Enum', 'Object', 'Map', 'Array' + :type schema_type: str or ~product.models.enum + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'element_schema': {'key': 'elementSchema', 'type': 'object'}, + 'schema_type': {'key': 'schemaType', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(ArraySchema, self).__init__(**kwargs) + self.element_schema = kwargs.get('element_schema', None) + self.schema_type = kwargs.get('schema_type', None) + self.name = kwargs.get('name', None) + self.comment = kwargs.get('comment', None) + self.display_name = kwargs.get('display_name', None) + self.id = kwargs.get('id', None) + self.description = kwargs.get('description', None) + self.language_version = kwargs.get('language_version', None) diff --git a/azext_iot/sdk/product/models/array_schema_py3.py b/azext_iot/sdk/product/models/array_schema_py3.py new file mode 100644 index 000000000..48bda58a1 --- /dev/null +++ b/azext_iot/sdk/product/models/array_schema_py3.py @@ -0,0 +1,58 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ArraySchema(Model): + """ArraySchema. + + :param element_schema: + :type element_schema: object + :param schema_type: Possible values include: 'Unknown', 'Boolean', 'Date', + 'DateTime', 'Double', 'Duration', 'Float', 'Integer', 'Long', 'String', + 'Time', 'Enum', 'Object', 'Map', 'Array' + :type schema_type: str or ~product.models.enum + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'element_schema': {'key': 'elementSchema', 'type': 'object'}, + 'schema_type': {'key': 'schemaType', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, *, element_schema=None, schema_type=None, name: str=None, comment: str=None, display_name=None, id: str=None, description=None, language_version: int=None, **kwargs) -> None: + super(ArraySchema, self).__init__(**kwargs) + self.element_schema = element_schema + self.schema_type = schema_type + self.name = name + self.comment = comment + self.display_name = display_name + self.id = id + self.description = description + self.language_version = language_version diff --git a/azext_iot/sdk/product/models/badge_info.py b/azext_iot/sdk/product/models/badge_info.py new file mode 100644 index 000000000..c1ada7c11 --- /dev/null +++ b/azext_iot/sdk/product/models/badge_info.py @@ -0,0 +1,33 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class BadgeInfo(Model): + """BadgeInfo. + + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + :param os: + :type os: str + """ + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'os': {'key': 'os', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(BadgeInfo, self).__init__(**kwargs) + self.type = kwargs.get('type', None) + self.os = kwargs.get('os', None) diff --git a/azext_iot/sdk/product/models/badge_info_py3.py b/azext_iot/sdk/product/models/badge_info_py3.py new file mode 100644 index 000000000..a62c838ca --- /dev/null +++ b/azext_iot/sdk/product/models/badge_info_py3.py @@ -0,0 +1,33 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class BadgeInfo(Model): + """BadgeInfo. + + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + :param os: + :type os: str + """ + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'os': {'key': 'os', 'type': 'str'}, + } + + def __init__(self, *, type=None, os: str=None, **kwargs) -> None: + super(BadgeInfo, self).__init__(**kwargs) + self.type = type + self.os = os diff --git a/azext_iot/sdk/product/models/c2_dtest.py b/azext_iot/sdk/product/models/c2_dtest.py new file mode 100644 index 000000000..d62ffad51 --- /dev/null +++ b/azext_iot/sdk/product/models/c2_dtest.py @@ -0,0 +1,40 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class C2DTest(Model): + """C2DTest. + + :param message_to_send: + :type message_to_send: str + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'message_to_send': {'key': 'messageToSend', 'type': 'str'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, **kwargs): + super(C2DTest, self).__init__(**kwargs) + self.message_to_send = kwargs.get('message_to_send', None) + self.validation_timeout = kwargs.get('validation_timeout', None) + self.is_mandatory = kwargs.get('is_mandatory', None) + self.should_validate = kwargs.get('should_validate', None) diff --git a/azext_iot/sdk/product/models/c2_dtest_py3.py b/azext_iot/sdk/product/models/c2_dtest_py3.py new file mode 100644 index 000000000..fcab04699 --- /dev/null +++ b/azext_iot/sdk/product/models/c2_dtest_py3.py @@ -0,0 +1,40 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class C2DTest(Model): + """C2DTest. + + :param message_to_send: + :type message_to_send: str + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'message_to_send': {'key': 'messageToSend', 'type': 'str'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, *, message_to_send: str=None, validation_timeout: int=None, is_mandatory: bool=None, should_validate: bool=None, **kwargs) -> None: + super(C2DTest, self).__init__(**kwargs) + self.message_to_send = message_to_send + self.validation_timeout = validation_timeout + self.is_mandatory = is_mandatory + self.should_validate = should_validate diff --git a/azext_iot/sdk/iothub/service/models/desired_state.py b/azext_iot/sdk/product/models/cannot_retrieve_model_reposistory_sas_token_error.py similarity index 56% rename from azext_iot/sdk/iothub/service/models/desired_state.py rename to azext_iot/sdk/product/models/cannot_retrieve_model_reposistory_sas_token_error.py index c44919521..1ac686b6f 100644 --- a/azext_iot/sdk/iothub/service/models/desired_state.py +++ b/azext_iot/sdk/product/models/cannot_retrieve_model_reposistory_sas_token_error.py @@ -12,25 +12,25 @@ from msrest.serialization import Model -class DesiredState(Model): - """DesiredState. +class CannotRetrieveModelReposistorySasTokenError(Model): + """CannotRetrieveModelReposistorySasTokenError. - :param code: Status code for the operation. + :param message: + :type message: str + :param code: :type code: int - :param version: Version of the desired value received. - :type version: long - :param description: Description of the status. - :type description: str + :param details: + :type details: list[object] """ _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, 'code': {'key': 'code', 'type': 'int'}, - 'version': {'key': 'version', 'type': 'long'}, - 'description': {'key': 'description', 'type': 'str'}, + 'details': {'key': 'details', 'type': '[object]'}, } def __init__(self, **kwargs): - super(DesiredState, self).__init__(**kwargs) + super(CannotRetrieveModelReposistorySasTokenError, self).__init__(**kwargs) + self.message = kwargs.get('message', None) self.code = kwargs.get('code', None) - self.version = kwargs.get('version', None) - self.description = kwargs.get('description', None) + self.details = kwargs.get('details', None) diff --git a/azext_iot/sdk/iothub/service/models/desired_state_py3.py b/azext_iot/sdk/product/models/cannot_retrieve_model_reposistory_sas_token_error_py3.py similarity index 51% rename from azext_iot/sdk/iothub/service/models/desired_state_py3.py rename to azext_iot/sdk/product/models/cannot_retrieve_model_reposistory_sas_token_error_py3.py index 4c788ea6a..8d27e0b0f 100644 --- a/azext_iot/sdk/iothub/service/models/desired_state_py3.py +++ b/azext_iot/sdk/product/models/cannot_retrieve_model_reposistory_sas_token_error_py3.py @@ -12,25 +12,25 @@ from msrest.serialization import Model -class DesiredState(Model): - """DesiredState. +class CannotRetrieveModelReposistorySasTokenError(Model): + """CannotRetrieveModelReposistorySasTokenError. - :param code: Status code for the operation. + :param message: + :type message: str + :param code: :type code: int - :param version: Version of the desired value received. - :type version: long - :param description: Description of the status. - :type description: str + :param details: + :type details: list[object] """ _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, 'code': {'key': 'code', 'type': 'int'}, - 'version': {'key': 'version', 'type': 'long'}, - 'description': {'key': 'description', 'type': 'str'}, + 'details': {'key': 'details', 'type': '[object]'}, } - def __init__(self, *, code: int=None, version: int=None, description: str=None, **kwargs) -> None: - super(DesiredState, self).__init__(**kwargs) + def __init__(self, *, message: str=None, code: int=None, details=None, **kwargs) -> None: + super(CannotRetrieveModelReposistorySasTokenError, self).__init__(**kwargs) + self.message = message self.code = code - self.version = version - self.description = description + self.details = details diff --git a/azext_iot/sdk/product/models/certification_task_log.py b/azext_iot/sdk/product/models/certification_task_log.py new file mode 100644 index 000000000..781e65141 --- /dev/null +++ b/azext_iot/sdk/product/models/certification_task_log.py @@ -0,0 +1,32 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class CertificationTaskLog(Model): + """CertificationTaskLog. + + :param time: + :type time: datetime + :param message: + :type message: str + """ + + _attribute_map = { + 'time': {'key': 'time', 'type': 'iso-8601'}, + 'message': {'key': 'message', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(CertificationTaskLog, self).__init__(**kwargs) + self.time = kwargs.get('time', None) + self.message = kwargs.get('message', None) diff --git a/azext_iot/sdk/product/models/certification_task_log_py3.py b/azext_iot/sdk/product/models/certification_task_log_py3.py new file mode 100644 index 000000000..0067a1d1b --- /dev/null +++ b/azext_iot/sdk/product/models/certification_task_log_py3.py @@ -0,0 +1,32 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class CertificationTaskLog(Model): + """CertificationTaskLog. + + :param time: + :type time: datetime + :param message: + :type message: str + """ + + _attribute_map = { + 'time': {'key': 'time', 'type': 'iso-8601'}, + 'message': {'key': 'message', 'type': 'str'}, + } + + def __init__(self, *, time=None, message: str=None, **kwargs) -> None: + super(CertificationTaskLog, self).__init__(**kwargs) + self.time = time + self.message = message diff --git a/azext_iot/sdk/product/models/command_model.py b/azext_iot/sdk/product/models/command_model.py new file mode 100644 index 000000000..e1b247a91 --- /dev/null +++ b/azext_iot/sdk/product/models/command_model.py @@ -0,0 +1,61 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class CommandModel(Model): + """CommandModel. + + :param request_schema: + :type request_schema: ~product.models.SchemaField + :param response_schema: + :type response_schema: ~product.models.SchemaField + :param command_execution_type: Possible values include: 'Synchronous', + 'Asynchronous' + :type command_execution_type: str or ~product.models.enum + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'request_schema': {'key': 'requestSchema', 'type': 'SchemaField'}, + 'response_schema': {'key': 'responseSchema', 'type': 'SchemaField'}, + 'command_execution_type': {'key': 'commandExecutionType', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(CommandModel, self).__init__(**kwargs) + self.request_schema = kwargs.get('request_schema', None) + self.response_schema = kwargs.get('response_schema', None) + self.command_execution_type = kwargs.get('command_execution_type', None) + self.name = kwargs.get('name', None) + self.comment = kwargs.get('comment', None) + self.display_name = kwargs.get('display_name', None) + self.id = kwargs.get('id', None) + self.description = kwargs.get('description', None) + self.language_version = kwargs.get('language_version', None) diff --git a/azext_iot/sdk/product/models/command_model_py3.py b/azext_iot/sdk/product/models/command_model_py3.py new file mode 100644 index 000000000..e14565990 --- /dev/null +++ b/azext_iot/sdk/product/models/command_model_py3.py @@ -0,0 +1,61 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class CommandModel(Model): + """CommandModel. + + :param request_schema: + :type request_schema: ~product.models.SchemaField + :param response_schema: + :type response_schema: ~product.models.SchemaField + :param command_execution_type: Possible values include: 'Synchronous', + 'Asynchronous' + :type command_execution_type: str or ~product.models.enum + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'request_schema': {'key': 'requestSchema', 'type': 'SchemaField'}, + 'response_schema': {'key': 'responseSchema', 'type': 'SchemaField'}, + 'command_execution_type': {'key': 'commandExecutionType', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, *, request_schema=None, response_schema=None, command_execution_type=None, name: str=None, comment: str=None, display_name=None, id: str=None, description=None, language_version: int=None, **kwargs) -> None: + super(CommandModel, self).__init__(**kwargs) + self.request_schema = request_schema + self.response_schema = response_schema + self.command_execution_type = command_execution_type + self.name = name + self.comment = comment + self.display_name = display_name + self.id = id + self.description = description + self.language_version = language_version diff --git a/azext_iot/sdk/product/models/command_test.py b/azext_iot/sdk/product/models/command_test.py new file mode 100644 index 000000000..c27ce1b02 --- /dev/null +++ b/azext_iot/sdk/product/models/command_test.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class CommandTest(Model): + """CommandTest. + + :param command: + :type command: ~product.models.CommandModel + :param payload: + :type payload: str + :param expected_result: + :type expected_result: str + :param priority: + :type priority: int + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'command': {'key': 'command', 'type': 'CommandModel'}, + 'payload': {'key': 'payload', 'type': 'str'}, + 'expected_result': {'key': 'expectedResult', 'type': 'str'}, + 'priority': {'key': 'priority', 'type': 'int'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, **kwargs): + super(CommandTest, self).__init__(**kwargs) + self.command = kwargs.get('command', None) + self.payload = kwargs.get('payload', None) + self.expected_result = kwargs.get('expected_result', None) + self.priority = kwargs.get('priority', None) + self.validation_timeout = kwargs.get('validation_timeout', None) + self.is_mandatory = kwargs.get('is_mandatory', None) + self.should_validate = kwargs.get('should_validate', None) diff --git a/azext_iot/sdk/product/models/command_test_py3.py b/azext_iot/sdk/product/models/command_test_py3.py new file mode 100644 index 000000000..af077e7d7 --- /dev/null +++ b/azext_iot/sdk/product/models/command_test_py3.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class CommandTest(Model): + """CommandTest. + + :param command: + :type command: ~product.models.CommandModel + :param payload: + :type payload: str + :param expected_result: + :type expected_result: str + :param priority: + :type priority: int + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'command': {'key': 'command', 'type': 'CommandModel'}, + 'payload': {'key': 'payload', 'type': 'str'}, + 'expected_result': {'key': 'expectedResult', 'type': 'str'}, + 'priority': {'key': 'priority', 'type': 'int'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, *, command=None, payload: str=None, expected_result: str=None, priority: int=None, validation_timeout: int=None, is_mandatory: bool=None, should_validate: bool=None, **kwargs) -> None: + super(CommandTest, self).__init__(**kwargs) + self.command = command + self.payload = payload + self.expected_result = expected_result + self.priority = priority + self.validation_timeout = validation_timeout + self.is_mandatory = is_mandatory + self.should_validate = should_validate diff --git a/azext_iot/sdk/product/models/d2_ctest.py b/azext_iot/sdk/product/models/d2_ctest.py new file mode 100644 index 000000000..044197111 --- /dev/null +++ b/azext_iot/sdk/product/models/d2_ctest.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class D2CTest(Model): + """D2CTest. + + :param expected_message: + :type expected_message: str + :param expected_message_count: + :type expected_message_count: int + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'expected_message': {'key': 'expectedMessage', 'type': 'str'}, + 'expected_message_count': {'key': 'expectedMessageCount', 'type': 'int'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, **kwargs): + super(D2CTest, self).__init__(**kwargs) + self.expected_message = kwargs.get('expected_message', None) + self.expected_message_count = kwargs.get('expected_message_count', None) + self.validation_timeout = kwargs.get('validation_timeout', None) + self.is_mandatory = kwargs.get('is_mandatory', None) + self.should_validate = kwargs.get('should_validate', None) diff --git a/azext_iot/sdk/product/models/d2_ctest_py3.py b/azext_iot/sdk/product/models/d2_ctest_py3.py new file mode 100644 index 000000000..5cc6f9d0b --- /dev/null +++ b/azext_iot/sdk/product/models/d2_ctest_py3.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class D2CTest(Model): + """D2CTest. + + :param expected_message: + :type expected_message: str + :param expected_message_count: + :type expected_message_count: int + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'expected_message': {'key': 'expectedMessage', 'type': 'str'}, + 'expected_message_count': {'key': 'expectedMessageCount', 'type': 'int'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, *, expected_message: str=None, expected_message_count: int=None, validation_timeout: int=None, is_mandatory: bool=None, should_validate: bool=None, **kwargs) -> None: + super(D2CTest, self).__init__(**kwargs) + self.expected_message = expected_message + self.expected_message_count = expected_message_count + self.validation_timeout = validation_timeout + self.is_mandatory = is_mandatory + self.should_validate = should_validate diff --git a/azext_iot/sdk/product/models/device_certification_provisioning_requirement.py b/azext_iot/sdk/product/models/device_certification_provisioning_requirement.py new file mode 100644 index 000000000..f08571dc8 --- /dev/null +++ b/azext_iot/sdk/product/models/device_certification_provisioning_requirement.py @@ -0,0 +1,28 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceCertificationProvisioningRequirement(Model): + """DeviceCertificationProvisioningRequirement. + + :param provisioning_types: + :type provisioning_types: list[str] + """ + + _attribute_map = { + 'provisioning_types': {'key': 'provisioningTypes', 'type': '[str]'}, + } + + def __init__(self, **kwargs): + super(DeviceCertificationProvisioningRequirement, self).__init__(**kwargs) + self.provisioning_types = kwargs.get('provisioning_types', None) diff --git a/azext_iot/sdk/product/models/device_certification_provisioning_requirement_py3.py b/azext_iot/sdk/product/models/device_certification_provisioning_requirement_py3.py new file mode 100644 index 000000000..1cc325e47 --- /dev/null +++ b/azext_iot/sdk/product/models/device_certification_provisioning_requirement_py3.py @@ -0,0 +1,28 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceCertificationProvisioningRequirement(Model): + """DeviceCertificationProvisioningRequirement. + + :param provisioning_types: + :type provisioning_types: list[str] + """ + + _attribute_map = { + 'provisioning_types': {'key': 'provisioningTypes', 'type': '[str]'}, + } + + def __init__(self, *, provisioning_types=None, **kwargs) -> None: + super(DeviceCertificationProvisioningRequirement, self).__init__(**kwargs) + self.provisioning_types = provisioning_types diff --git a/azext_iot/sdk/product/models/device_certification_requirement.py b/azext_iot/sdk/product/models/device_certification_requirement.py new file mode 100644 index 000000000..9dbd5b894 --- /dev/null +++ b/azext_iot/sdk/product/models/device_certification_requirement.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceCertificationRequirement(Model): + """DeviceCertificationRequirement. + + :param badge_type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type badge_type: str or ~product.models.enum + :param provisioning_requirement: + :type provisioning_requirement: + ~product.models.DeviceCertificationProvisioningRequirement + """ + + _attribute_map = { + 'badge_type': {'key': 'badgeType', 'type': 'str'}, + 'provisioning_requirement': {'key': 'provisioningRequirement', 'type': 'DeviceCertificationProvisioningRequirement'}, + } + + def __init__(self, **kwargs): + super(DeviceCertificationRequirement, self).__init__(**kwargs) + self.badge_type = kwargs.get('badge_type', None) + self.provisioning_requirement = kwargs.get('provisioning_requirement', None) diff --git a/azext_iot/sdk/product/models/device_certification_requirement_py3.py b/azext_iot/sdk/product/models/device_certification_requirement_py3.py new file mode 100644 index 000000000..a8f543813 --- /dev/null +++ b/azext_iot/sdk/product/models/device_certification_requirement_py3.py @@ -0,0 +1,34 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceCertificationRequirement(Model): + """DeviceCertificationRequirement. + + :param badge_type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type badge_type: str or ~product.models.enum + :param provisioning_requirement: + :type provisioning_requirement: + ~product.models.DeviceCertificationProvisioningRequirement + """ + + _attribute_map = { + 'badge_type': {'key': 'badgeType', 'type': 'str'}, + 'provisioning_requirement': {'key': 'provisioningRequirement', 'type': 'DeviceCertificationProvisioningRequirement'}, + } + + def __init__(self, *, badge_type=None, provisioning_requirement=None, **kwargs) -> None: + super(DeviceCertificationRequirement, self).__init__(**kwargs) + self.badge_type = badge_type + self.provisioning_requirement = provisioning_requirement diff --git a/azext_iot/sdk/product/models/device_test.py b/azext_iot/sdk/product/models/device_test.py new file mode 100644 index 000000000..ea38905f3 --- /dev/null +++ b/azext_iot/sdk/product/models/device_test.py @@ -0,0 +1,59 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTest(Model): + """A DeviceTest contains basic information of a testing device and the + provisioning + information of that device in AICS's managed IoT Hubs. + + :param id: Id of a Microsoft.Azure.IoT.TestKit.Models.DeviceTest. It is + generated by the service. + :type id: str + :param validation_type: + Microsoft.Azure.IoT.TestKit.Shared.Models.ValidationType of a + Microsoft.Azure.IoT.TestKit.Models.DeviceTest. Possible values include: + 'Certification', 'Test' + :type validation_type: str or ~product.models.enum + :param product_id: Product Id of the testing device in product service. In + CLI scenario, this can be null. + :type product_id: str + :param device_type: Microsoft.Azure.IoT.TestKit.Shared.Models.DeviceType + of the testing device. Possible values include: 'FinishedProduct', + 'DevKit' + :type device_type: str or ~product.models.enum + :param provisioning_configuration: + :type provisioning_configuration: + ~product.models.ProvisioningConfiguration + :param certification_badge_configurations: Certification badge + configurations + :type certification_badge_configurations: list[object] + """ + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'validation_type': {'key': 'validationType', 'type': 'str'}, + 'product_id': {'key': 'productId', 'type': 'str'}, + 'device_type': {'key': 'deviceType', 'type': 'str'}, + 'provisioning_configuration': {'key': 'provisioningConfiguration', 'type': 'ProvisioningConfiguration'}, + 'certification_badge_configurations': {'key': 'certificationBadgeConfigurations', 'type': '[object]'}, + } + + def __init__(self, **kwargs): + super(DeviceTest, self).__init__(**kwargs) + self.id = kwargs.get('id', None) + self.validation_type = kwargs.get('validation_type', None) + self.product_id = kwargs.get('product_id', None) + self.device_type = kwargs.get('device_type', None) + self.provisioning_configuration = kwargs.get('provisioning_configuration', None) + self.certification_badge_configurations = kwargs.get('certification_badge_configurations', None) diff --git a/azext_iot/sdk/product/models/device_test_not_exist_error.py b/azext_iot/sdk/product/models/device_test_not_exist_error.py new file mode 100644 index 000000000..c7672afd9 --- /dev/null +++ b/azext_iot/sdk/product/models/device_test_not_exist_error.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTestNotExistError(Model): + """DeviceTestNotExistError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, **kwargs): + super(DeviceTestNotExistError, self).__init__(**kwargs) + self.message = kwargs.get('message', None) + self.code = kwargs.get('code', None) + self.details = kwargs.get('details', None) diff --git a/azext_iot/sdk/product/models/device_test_not_exist_error_py3.py b/azext_iot/sdk/product/models/device_test_not_exist_error_py3.py new file mode 100644 index 000000000..4d1be9b1a --- /dev/null +++ b/azext_iot/sdk/product/models/device_test_not_exist_error_py3.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTestNotExistError(Model): + """DeviceTestNotExistError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, *, message: str=None, code: int=None, details=None, **kwargs) -> None: + super(DeviceTestNotExistError, self).__init__(**kwargs) + self.message = message + self.code = code + self.details = details diff --git a/azext_iot/sdk/product/models/device_test_py3.py b/azext_iot/sdk/product/models/device_test_py3.py new file mode 100644 index 000000000..7ebef29ac --- /dev/null +++ b/azext_iot/sdk/product/models/device_test_py3.py @@ -0,0 +1,59 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTest(Model): + """A DeviceTest contains basic information of a testing device and the + provisioning + information of that device in AICS's managed IoT Hubs. + + :param id: Id of a Microsoft.Azure.IoT.TestKit.Models.DeviceTest. It is + generated by the service. + :type id: str + :param validation_type: + Microsoft.Azure.IoT.TestKit.Shared.Models.ValidationType of a + Microsoft.Azure.IoT.TestKit.Models.DeviceTest. Possible values include: + 'Certification', 'Test' + :type validation_type: str or ~product.models.enum + :param product_id: Product Id of the testing device in product service. In + CLI scenario, this can be null. + :type product_id: str + :param device_type: Microsoft.Azure.IoT.TestKit.Shared.Models.DeviceType + of the testing device. Possible values include: 'FinishedProduct', + 'DevKit' + :type device_type: str or ~product.models.enum + :param provisioning_configuration: + :type provisioning_configuration: + ~product.models.ProvisioningConfiguration + :param certification_badge_configurations: Certification badge + configurations + :type certification_badge_configurations: list[object] + """ + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'validation_type': {'key': 'validationType', 'type': 'str'}, + 'product_id': {'key': 'productId', 'type': 'str'}, + 'device_type': {'key': 'deviceType', 'type': 'str'}, + 'provisioning_configuration': {'key': 'provisioningConfiguration', 'type': 'ProvisioningConfiguration'}, + 'certification_badge_configurations': {'key': 'certificationBadgeConfigurations', 'type': '[object]'}, + } + + def __init__(self, *, id: str=None, validation_type=None, product_id: str=None, device_type=None, provisioning_configuration=None, certification_badge_configurations=None, **kwargs) -> None: + super(DeviceTest, self).__init__(**kwargs) + self.id = id + self.validation_type = validation_type + self.product_id = product_id + self.device_type = device_type + self.provisioning_configuration = provisioning_configuration + self.certification_badge_configurations = certification_badge_configurations diff --git a/azext_iot/sdk/product/models/device_test_search_options.py b/azext_iot/sdk/product/models/device_test_search_options.py new file mode 100644 index 000000000..510620ef6 --- /dev/null +++ b/azext_iot/sdk/product/models/device_test_search_options.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTestSearchOptions(Model): + """DeviceTestSearchOptions. + + :param product_id: + :type product_id: str + :param dps_registration_id: + :type dps_registration_id: str + :param dps_x509_certificate_common_name: + :type dps_x509_certificate_common_name: str + """ + + _attribute_map = { + 'product_id': {'key': 'productId', 'type': 'str'}, + 'dps_registration_id': {'key': 'dpsRegistrationId', 'type': 'str'}, + 'dps_x509_certificate_common_name': {'key': 'dpsX509CertificateCommonName', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(DeviceTestSearchOptions, self).__init__(**kwargs) + self.product_id = kwargs.get('product_id', None) + self.dps_registration_id = kwargs.get('dps_registration_id', None) + self.dps_x509_certificate_common_name = kwargs.get('dps_x509_certificate_common_name', None) diff --git a/azext_iot/sdk/product/models/device_test_search_options_py3.py b/azext_iot/sdk/product/models/device_test_search_options_py3.py new file mode 100644 index 000000000..ffdecb882 --- /dev/null +++ b/azext_iot/sdk/product/models/device_test_search_options_py3.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTestSearchOptions(Model): + """DeviceTestSearchOptions. + + :param product_id: + :type product_id: str + :param dps_registration_id: + :type dps_registration_id: str + :param dps_x509_certificate_common_name: + :type dps_x509_certificate_common_name: str + """ + + _attribute_map = { + 'product_id': {'key': 'productId', 'type': 'str'}, + 'dps_registration_id': {'key': 'dpsRegistrationId', 'type': 'str'}, + 'dps_x509_certificate_common_name': {'key': 'dpsX509CertificateCommonName', 'type': 'str'}, + } + + def __init__(self, *, product_id: str=None, dps_registration_id: str=None, dps_x509_certificate_common_name: str=None, **kwargs) -> None: + super(DeviceTestSearchOptions, self).__init__(**kwargs) + self.product_id = product_id + self.dps_registration_id = dps_registration_id + self.dps_x509_certificate_common_name = dps_x509_certificate_common_name diff --git a/azext_iot/sdk/product/models/device_test_search_result.py b/azext_iot/sdk/product/models/device_test_search_result.py new file mode 100644 index 000000000..b85ba3f9b --- /dev/null +++ b/azext_iot/sdk/product/models/device_test_search_result.py @@ -0,0 +1,46 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTestSearchResult(Model): + """DeviceTest search result. + + :param device_test_link: Gets or sets DeviceTest resource link. + :type device_test_link: str + :param product_id: Gets or sets product Id. + :type product_id: str + :param dps_registration_id: Gets or sets DPS registration Id. + :type dps_registration_id: str + :param dps_x509_certificate_common_name: Gets or sets dPS x509 enrollment + certificate common name. + :type dps_x509_certificate_common_name: str + :param badge_info: Gets or sets list of Badge related info which can + identify a DeviceTest + :type badge_info: list[~product.models.BadgeInfo] + """ + + _attribute_map = { + 'device_test_link': {'key': 'deviceTestLink', 'type': 'str'}, + 'product_id': {'key': 'productId', 'type': 'str'}, + 'dps_registration_id': {'key': 'dpsRegistrationId', 'type': 'str'}, + 'dps_x509_certificate_common_name': {'key': 'dpsX509CertificateCommonName', 'type': 'str'}, + 'badge_info': {'key': 'badgeInfo', 'type': '[BadgeInfo]'}, + } + + def __init__(self, **kwargs): + super(DeviceTestSearchResult, self).__init__(**kwargs) + self.device_test_link = kwargs.get('device_test_link', None) + self.product_id = kwargs.get('product_id', None) + self.dps_registration_id = kwargs.get('dps_registration_id', None) + self.dps_x509_certificate_common_name = kwargs.get('dps_x509_certificate_common_name', None) + self.badge_info = kwargs.get('badge_info', None) diff --git a/azext_iot/sdk/product/models/device_test_search_result_py3.py b/azext_iot/sdk/product/models/device_test_search_result_py3.py new file mode 100644 index 000000000..cb2d3bf1b --- /dev/null +++ b/azext_iot/sdk/product/models/device_test_search_result_py3.py @@ -0,0 +1,46 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTestSearchResult(Model): + """DeviceTest search result. + + :param device_test_link: Gets or sets DeviceTest resource link. + :type device_test_link: str + :param product_id: Gets or sets product Id. + :type product_id: str + :param dps_registration_id: Gets or sets DPS registration Id. + :type dps_registration_id: str + :param dps_x509_certificate_common_name: Gets or sets dPS x509 enrollment + certificate common name. + :type dps_x509_certificate_common_name: str + :param badge_info: Gets or sets list of Badge related info which can + identify a DeviceTest + :type badge_info: list[~product.models.BadgeInfo] + """ + + _attribute_map = { + 'device_test_link': {'key': 'deviceTestLink', 'type': 'str'}, + 'product_id': {'key': 'productId', 'type': 'str'}, + 'dps_registration_id': {'key': 'dpsRegistrationId', 'type': 'str'}, + 'dps_x509_certificate_common_name': {'key': 'dpsX509CertificateCommonName', 'type': 'str'}, + 'badge_info': {'key': 'badgeInfo', 'type': '[BadgeInfo]'}, + } + + def __init__(self, *, device_test_link: str=None, product_id: str=None, dps_registration_id: str=None, dps_x509_certificate_common_name: str=None, badge_info=None, **kwargs) -> None: + super(DeviceTestSearchResult, self).__init__(**kwargs) + self.device_test_link = device_test_link + self.product_id = product_id + self.dps_registration_id = dps_registration_id + self.dps_x509_certificate_common_name = dps_x509_certificate_common_name + self.badge_info = badge_info diff --git a/azext_iot/sdk/product/models/device_test_task.py b/azext_iot/sdk/product/models/device_test_task.py new file mode 100644 index 000000000..c5a9402ea --- /dev/null +++ b/azext_iot/sdk/product/models/device_test_task.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTestTask(Model): + """It represents an async operation associated with a + Microsoft.Azure.IoT.TestKit.Models.DeviceTest. + + :param id: Id of a Microsoft.Azure.IoT.TestKit.Models.DeviceTestTask. + :type id: str + :param status: Task status a + Microsoft.Azure.IoT.TestKit.Models.DeviceTestTask. Possible values + include: 'Queued', 'Started', 'Running', 'Completed', 'Failed', + 'Cancelled' + :type status: str or ~product.models.enum + :param type: Task type. Possible values include: 'QueueTestRun', + 'GenerateTestCases' + :type type: str or ~product.models.enum + :param device_test_id: Id of Microsoft.Azure.IoT.TestKit.Models.DeviceTest + :type device_test_id: str + :param result_link: Task result link(relative url). + :type result_link: str + :param error: + :type error: object + """ + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'status': {'key': 'status', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + 'device_test_id': {'key': 'deviceTestId', 'type': 'str'}, + 'result_link': {'key': 'resultLink', 'type': 'str'}, + 'error': {'key': 'error', 'type': 'object'}, + } + + def __init__(self, **kwargs): + super(DeviceTestTask, self).__init__(**kwargs) + self.id = kwargs.get('id', None) + self.status = kwargs.get('status', None) + self.type = kwargs.get('type', None) + self.device_test_id = kwargs.get('device_test_id', None) + self.result_link = kwargs.get('result_link', None) + self.error = kwargs.get('error', None) diff --git a/azext_iot/sdk/product/models/device_test_task_py3.py b/azext_iot/sdk/product/models/device_test_task_py3.py new file mode 100644 index 000000000..2fb4bf134 --- /dev/null +++ b/azext_iot/sdk/product/models/device_test_task_py3.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTestTask(Model): + """It represents an async operation associated with a + Microsoft.Azure.IoT.TestKit.Models.DeviceTest. + + :param id: Id of a Microsoft.Azure.IoT.TestKit.Models.DeviceTestTask. + :type id: str + :param status: Task status a + Microsoft.Azure.IoT.TestKit.Models.DeviceTestTask. Possible values + include: 'Queued', 'Started', 'Running', 'Completed', 'Failed', + 'Cancelled' + :type status: str or ~product.models.enum + :param type: Task type. Possible values include: 'QueueTestRun', + 'GenerateTestCases' + :type type: str or ~product.models.enum + :param device_test_id: Id of Microsoft.Azure.IoT.TestKit.Models.DeviceTest + :type device_test_id: str + :param result_link: Task result link(relative url). + :type result_link: str + :param error: + :type error: object + """ + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'status': {'key': 'status', 'type': 'str'}, + 'type': {'key': 'type', 'type': 'str'}, + 'device_test_id': {'key': 'deviceTestId', 'type': 'str'}, + 'result_link': {'key': 'resultLink', 'type': 'str'}, + 'error': {'key': 'error', 'type': 'object'}, + } + + def __init__(self, *, id: str=None, status=None, type=None, device_test_id: str=None, result_link: str=None, error=None, **kwargs) -> None: + super(DeviceTestTask, self).__init__(**kwargs) + self.id = id + self.status = status + self.type = type + self.device_test_id = device_test_id + self.result_link = result_link + self.error = error diff --git a/azext_iot/sdk/product/models/device_twin_test.py b/azext_iot/sdk/product/models/device_twin_test.py new file mode 100644 index 000000000..ce7f47c31 --- /dev/null +++ b/azext_iot/sdk/product/models/device_twin_test.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTwinTest(Model): + """DeviceTwinTest. + + :param desired_properties: + :type desired_properties: str + :param reported_properties: + :type reported_properties: str + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'desired_properties': {'key': 'desiredProperties', 'type': 'str'}, + 'reported_properties': {'key': 'reportedProperties', 'type': 'str'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, **kwargs): + super(DeviceTwinTest, self).__init__(**kwargs) + self.desired_properties = kwargs.get('desired_properties', None) + self.reported_properties = kwargs.get('reported_properties', None) + self.validation_timeout = kwargs.get('validation_timeout', None) + self.is_mandatory = kwargs.get('is_mandatory', None) + self.should_validate = kwargs.get('should_validate', None) diff --git a/azext_iot/sdk/product/models/device_twin_test_py3.py b/azext_iot/sdk/product/models/device_twin_test_py3.py new file mode 100644 index 000000000..7fece51c5 --- /dev/null +++ b/azext_iot/sdk/product/models/device_twin_test_py3.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DeviceTwinTest(Model): + """DeviceTwinTest. + + :param desired_properties: + :type desired_properties: str + :param reported_properties: + :type reported_properties: str + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'desired_properties': {'key': 'desiredProperties', 'type': 'str'}, + 'reported_properties': {'key': 'reportedProperties', 'type': 'str'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, *, desired_properties: str=None, reported_properties: str=None, validation_timeout: int=None, is_mandatory: bool=None, should_validate: bool=None, **kwargs) -> None: + super(DeviceTwinTest, self).__init__(**kwargs) + self.desired_properties = desired_properties + self.reported_properties = reported_properties + self.validation_timeout = validation_timeout + self.is_mandatory = is_mandatory + self.should_validate = should_validate diff --git a/azext_iot/sdk/product/models/digital_twin_validation_task_result.py b/azext_iot/sdk/product/models/digital_twin_validation_task_result.py new file mode 100644 index 000000000..281c0c2cb --- /dev/null +++ b/azext_iot/sdk/product/models/digital_twin_validation_task_result.py @@ -0,0 +1,74 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinValidationTaskResult(Model): + """DigitalTwinValidationTaskResult. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param interface_id: + :type interface_id: str + :param component_name: + :type component_name: str + :param name: + :type name: str + :param timeout: + :type timeout: int + :param start_time: + :type start_time: datetime + :param end_time: + :type end_time: datetime + :param status: Possible values include: 'Created', 'Running', 'Failed', + 'Passed', 'Cancelled', 'Aborted' + :type status: str or ~product.models.enum + :ivar message: + :vartype message: str + :ivar logs: + :vartype logs: list[~product.models.CertificationTaskLog] + :ivar raw_data: + :vartype raw_data: list[str] + """ + + _validation = { + 'message': {'readonly': True}, + 'logs': {'readonly': True}, + 'raw_data': {'readonly': True}, + } + + _attribute_map = { + 'interface_id': {'key': 'interfaceId', 'type': 'str'}, + 'component_name': {'key': 'componentName', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'timeout': {'key': 'timeout', 'type': 'int'}, + 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, + 'end_time': {'key': 'endTime', 'type': 'iso-8601'}, + 'status': {'key': 'status', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'logs': {'key': 'logs', 'type': '[CertificationTaskLog]'}, + 'raw_data': {'key': 'rawData', 'type': '[str]'}, + } + + def __init__(self, **kwargs): + super(DigitalTwinValidationTaskResult, self).__init__(**kwargs) + self.interface_id = kwargs.get('interface_id', None) + self.component_name = kwargs.get('component_name', None) + self.name = kwargs.get('name', None) + self.timeout = kwargs.get('timeout', None) + self.start_time = kwargs.get('start_time', None) + self.end_time = kwargs.get('end_time', None) + self.status = kwargs.get('status', None) + self.message = None + self.logs = None + self.raw_data = None diff --git a/azext_iot/sdk/product/models/digital_twin_validation_task_result_py3.py b/azext_iot/sdk/product/models/digital_twin_validation_task_result_py3.py new file mode 100644 index 000000000..ffb439a8b --- /dev/null +++ b/azext_iot/sdk/product/models/digital_twin_validation_task_result_py3.py @@ -0,0 +1,74 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DigitalTwinValidationTaskResult(Model): + """DigitalTwinValidationTaskResult. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param interface_id: + :type interface_id: str + :param component_name: + :type component_name: str + :param name: + :type name: str + :param timeout: + :type timeout: int + :param start_time: + :type start_time: datetime + :param end_time: + :type end_time: datetime + :param status: Possible values include: 'Created', 'Running', 'Failed', + 'Passed', 'Cancelled', 'Aborted' + :type status: str or ~product.models.enum + :ivar message: + :vartype message: str + :ivar logs: + :vartype logs: list[~product.models.CertificationTaskLog] + :ivar raw_data: + :vartype raw_data: list[str] + """ + + _validation = { + 'message': {'readonly': True}, + 'logs': {'readonly': True}, + 'raw_data': {'readonly': True}, + } + + _attribute_map = { + 'interface_id': {'key': 'interfaceId', 'type': 'str'}, + 'component_name': {'key': 'componentName', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'timeout': {'key': 'timeout', 'type': 'int'}, + 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, + 'end_time': {'key': 'endTime', 'type': 'iso-8601'}, + 'status': {'key': 'status', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'logs': {'key': 'logs', 'type': '[CertificationTaskLog]'}, + 'raw_data': {'key': 'rawData', 'type': '[str]'}, + } + + def __init__(self, *, interface_id: str=None, component_name: str=None, name: str=None, timeout: int=None, start_time=None, end_time=None, status=None, **kwargs) -> None: + super(DigitalTwinValidationTaskResult, self).__init__(**kwargs) + self.interface_id = interface_id + self.component_name = component_name + self.name = name + self.timeout = timeout + self.start_time = start_time + self.end_time = end_time + self.status = status + self.message = None + self.logs = None + self.raw_data = None diff --git a/azext_iot/sdk/product/models/direct_method_test.py b/azext_iot/sdk/product/models/direct_method_test.py new file mode 100644 index 000000000..1e10ee3fb --- /dev/null +++ b/azext_iot/sdk/product/models/direct_method_test.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DirectMethodTest(Model): + """DirectMethodTest. + + :param method_name: + :type method_name: str + :param payload: + :type payload: str + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'method_name': {'key': 'methodName', 'type': 'str'}, + 'payload': {'key': 'payload', 'type': 'str'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, **kwargs): + super(DirectMethodTest, self).__init__(**kwargs) + self.method_name = kwargs.get('method_name', None) + self.payload = kwargs.get('payload', None) + self.validation_timeout = kwargs.get('validation_timeout', None) + self.is_mandatory = kwargs.get('is_mandatory', None) + self.should_validate = kwargs.get('should_validate', None) diff --git a/azext_iot/sdk/product/models/direct_method_test_py3.py b/azext_iot/sdk/product/models/direct_method_test_py3.py new file mode 100644 index 000000000..71f611c4c --- /dev/null +++ b/azext_iot/sdk/product/models/direct_method_test_py3.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class DirectMethodTest(Model): + """DirectMethodTest. + + :param method_name: + :type method_name: str + :param payload: + :type payload: str + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'method_name': {'key': 'methodName', 'type': 'str'}, + 'payload': {'key': 'payload', 'type': 'str'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, *, method_name: str=None, payload: str=None, validation_timeout: int=None, is_mandatory: bool=None, should_validate: bool=None, **kwargs) -> None: + super(DirectMethodTest, self).__init__(**kwargs) + self.method_name = method_name + self.payload = payload + self.validation_timeout = validation_timeout + self.is_mandatory = is_mandatory + self.should_validate = should_validate diff --git a/azext_iot/sdk/product/models/edge_device_validation_task_result.py b/azext_iot/sdk/product/models/edge_device_validation_task_result.py new file mode 100644 index 000000000..b15b30de5 --- /dev/null +++ b/azext_iot/sdk/product/models/edge_device_validation_task_result.py @@ -0,0 +1,66 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EdgeDeviceValidationTaskResult(Model): + """EdgeDeviceValidationTaskResult. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param name: + :type name: str + :param timeout: + :type timeout: int + :param start_time: + :type start_time: datetime + :param end_time: + :type end_time: datetime + :param status: Possible values include: 'Created', 'Running', 'Failed', + 'Passed', 'Cancelled', 'Aborted' + :type status: str or ~product.models.enum + :ivar message: + :vartype message: str + :ivar logs: + :vartype logs: list[~product.models.CertificationTaskLog] + :ivar raw_data: + :vartype raw_data: list[str] + """ + + _validation = { + 'message': {'readonly': True}, + 'logs': {'readonly': True}, + 'raw_data': {'readonly': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'timeout': {'key': 'timeout', 'type': 'int'}, + 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, + 'end_time': {'key': 'endTime', 'type': 'iso-8601'}, + 'status': {'key': 'status', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'logs': {'key': 'logs', 'type': '[CertificationTaskLog]'}, + 'raw_data': {'key': 'rawData', 'type': '[str]'}, + } + + def __init__(self, **kwargs): + super(EdgeDeviceValidationTaskResult, self).__init__(**kwargs) + self.name = kwargs.get('name', None) + self.timeout = kwargs.get('timeout', None) + self.start_time = kwargs.get('start_time', None) + self.end_time = kwargs.get('end_time', None) + self.status = kwargs.get('status', None) + self.message = None + self.logs = None + self.raw_data = None diff --git a/azext_iot/sdk/product/models/edge_device_validation_task_result_py3.py b/azext_iot/sdk/product/models/edge_device_validation_task_result_py3.py new file mode 100644 index 000000000..bd4535c5b --- /dev/null +++ b/azext_iot/sdk/product/models/edge_device_validation_task_result_py3.py @@ -0,0 +1,66 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EdgeDeviceValidationTaskResult(Model): + """EdgeDeviceValidationTaskResult. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param name: + :type name: str + :param timeout: + :type timeout: int + :param start_time: + :type start_time: datetime + :param end_time: + :type end_time: datetime + :param status: Possible values include: 'Created', 'Running', 'Failed', + 'Passed', 'Cancelled', 'Aborted' + :type status: str or ~product.models.enum + :ivar message: + :vartype message: str + :ivar logs: + :vartype logs: list[~product.models.CertificationTaskLog] + :ivar raw_data: + :vartype raw_data: list[str] + """ + + _validation = { + 'message': {'readonly': True}, + 'logs': {'readonly': True}, + 'raw_data': {'readonly': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'timeout': {'key': 'timeout', 'type': 'int'}, + 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, + 'end_time': {'key': 'endTime', 'type': 'iso-8601'}, + 'status': {'key': 'status', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'logs': {'key': 'logs', 'type': '[CertificationTaskLog]'}, + 'raw_data': {'key': 'rawData', 'type': '[str]'}, + } + + def __init__(self, *, name: str=None, timeout: int=None, start_time=None, end_time=None, status=None, **kwargs) -> None: + super(EdgeDeviceValidationTaskResult, self).__init__(**kwargs) + self.name = name + self.timeout = timeout + self.start_time = start_time + self.end_time = end_time + self.status = status + self.message = None + self.logs = None + self.raw_data = None diff --git a/azext_iot/sdk/product/models/enum_schema.py b/azext_iot/sdk/product/models/enum_schema.py new file mode 100644 index 000000000..73745e360 --- /dev/null +++ b/azext_iot/sdk/product/models/enum_schema.py @@ -0,0 +1,64 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EnumSchema(Model): + """EnumSchema. + + :param enum_values: + :type enum_values: list[~product.models.EnumValue] + :param value_schema: Possible values include: 'Unknown', 'Boolean', + 'Date', 'DateTime', 'Double', 'Duration', 'Float', 'Integer', 'Long', + 'String', 'Time', 'Enum', 'Object', 'Map', 'Array' + :type value_schema: str or ~product.models.enum + :param schema_type: Possible values include: 'Unknown', 'Boolean', 'Date', + 'DateTime', 'Double', 'Duration', 'Float', 'Integer', 'Long', 'String', + 'Time', 'Enum', 'Object', 'Map', 'Array' + :type schema_type: str or ~product.models.enum + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'enum_values': {'key': 'enumValues', 'type': '[EnumValue]'}, + 'value_schema': {'key': 'valueSchema', 'type': 'str'}, + 'schema_type': {'key': 'schemaType', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(EnumSchema, self).__init__(**kwargs) + self.enum_values = kwargs.get('enum_values', None) + self.value_schema = kwargs.get('value_schema', None) + self.schema_type = kwargs.get('schema_type', None) + self.name = kwargs.get('name', None) + self.comment = kwargs.get('comment', None) + self.display_name = kwargs.get('display_name', None) + self.id = kwargs.get('id', None) + self.description = kwargs.get('description', None) + self.language_version = kwargs.get('language_version', None) diff --git a/azext_iot/sdk/product/models/enum_schema_py3.py b/azext_iot/sdk/product/models/enum_schema_py3.py new file mode 100644 index 000000000..a3e2b7c5f --- /dev/null +++ b/azext_iot/sdk/product/models/enum_schema_py3.py @@ -0,0 +1,64 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EnumSchema(Model): + """EnumSchema. + + :param enum_values: + :type enum_values: list[~product.models.EnumValue] + :param value_schema: Possible values include: 'Unknown', 'Boolean', + 'Date', 'DateTime', 'Double', 'Duration', 'Float', 'Integer', 'Long', + 'String', 'Time', 'Enum', 'Object', 'Map', 'Array' + :type value_schema: str or ~product.models.enum + :param schema_type: Possible values include: 'Unknown', 'Boolean', 'Date', + 'DateTime', 'Double', 'Duration', 'Float', 'Integer', 'Long', 'String', + 'Time', 'Enum', 'Object', 'Map', 'Array' + :type schema_type: str or ~product.models.enum + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'enum_values': {'key': 'enumValues', 'type': '[EnumValue]'}, + 'value_schema': {'key': 'valueSchema', 'type': 'str'}, + 'schema_type': {'key': 'schemaType', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, *, enum_values=None, value_schema=None, schema_type=None, name: str=None, comment: str=None, display_name=None, id: str=None, description=None, language_version: int=None, **kwargs) -> None: + super(EnumSchema, self).__init__(**kwargs) + self.enum_values = enum_values + self.value_schema = value_schema + self.schema_type = schema_type + self.name = name + self.comment = comment + self.display_name = display_name + self.id = id + self.description = description + self.language_version = language_version diff --git a/azext_iot/sdk/product/models/enum_value.py b/azext_iot/sdk/product/models/enum_value.py new file mode 100644 index 000000000..cec0a3814 --- /dev/null +++ b/azext_iot/sdk/product/models/enum_value.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EnumValue(Model): + """EnumValue. + + :param value: + :type value: object + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'value': {'key': 'value', 'type': 'object'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(EnumValue, self).__init__(**kwargs) + self.value = kwargs.get('value', None) + self.name = kwargs.get('name', None) + self.comment = kwargs.get('comment', None) + self.display_name = kwargs.get('display_name', None) + self.id = kwargs.get('id', None) + self.description = kwargs.get('description', None) + self.language_version = kwargs.get('language_version', None) diff --git a/azext_iot/sdk/product/models/enum_value_py3.py b/azext_iot/sdk/product/models/enum_value_py3.py new file mode 100644 index 000000000..266d3fa87 --- /dev/null +++ b/azext_iot/sdk/product/models/enum_value_py3.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class EnumValue(Model): + """EnumValue. + + :param value: + :type value: object + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'value': {'key': 'value', 'type': 'object'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, *, value=None, name: str=None, comment: str=None, display_name=None, id: str=None, description=None, language_version: int=None, **kwargs) -> None: + super(EnumValue, self).__init__(**kwargs) + self.value = value + self.name = name + self.comment = comment + self.display_name = display_name + self.id = id + self.description = description + self.language_version = language_version diff --git a/azext_iot/sdk/product/models/existing_task_running_conflict_error.py b/azext_iot/sdk/product/models/existing_task_running_conflict_error.py new file mode 100644 index 000000000..1b0bcc535 --- /dev/null +++ b/azext_iot/sdk/product/models/existing_task_running_conflict_error.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ExistingTaskRunningConflictError(Model): + """ExistingTaskRunningConflictError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, **kwargs): + super(ExistingTaskRunningConflictError, self).__init__(**kwargs) + self.message = kwargs.get('message', None) + self.code = kwargs.get('code', None) + self.details = kwargs.get('details', None) diff --git a/azext_iot/sdk/product/models/existing_task_running_conflict_error_py3.py b/azext_iot/sdk/product/models/existing_task_running_conflict_error_py3.py new file mode 100644 index 000000000..c09f32b9a --- /dev/null +++ b/azext_iot/sdk/product/models/existing_task_running_conflict_error_py3.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ExistingTaskRunningConflictError(Model): + """ExistingTaskRunningConflictError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, *, message: str=None, code: int=None, details=None, **kwargs) -> None: + super(ExistingTaskRunningConflictError, self).__init__(**kwargs) + self.message = message + self.code = code + self.details = details diff --git a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces.py b/azext_iot/sdk/product/models/fail_to_queue_task_error.py similarity index 51% rename from azext_iot/sdk/iothub/service/models/digital_twin_interfaces.py rename to azext_iot/sdk/product/models/fail_to_queue_task_error.py index 09a6e4abb..e1fcaa373 100644 --- a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces.py +++ b/azext_iot/sdk/product/models/fail_to_queue_task_error.py @@ -12,21 +12,25 @@ from msrest.serialization import Model -class DigitalTwinInterfaces(Model): - """DigitalTwinInterfaces. +class FailToQueueTaskError(Model): + """FailToQueueTaskError. - :param interfaces: Interface(s) data on the digital twin. - :type interfaces: dict[str, ~service.models.Interface] - :param version: Version of digital twin. - :type version: long + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] """ _attribute_map = { - 'interfaces': {'key': 'interfaces', 'type': '{Interface}'}, - 'version': {'key': 'version', 'type': 'long'}, + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, } def __init__(self, **kwargs): - super(DigitalTwinInterfaces, self).__init__(**kwargs) - self.interfaces = kwargs.get('interfaces', None) - self.version = kwargs.get('version', None) + super(FailToQueueTaskError, self).__init__(**kwargs) + self.message = kwargs.get('message', None) + self.code = kwargs.get('code', None) + self.details = kwargs.get('details', None) diff --git a/azext_iot/sdk/product/models/fail_to_queue_task_error_py3.py b/azext_iot/sdk/product/models/fail_to_queue_task_error_py3.py new file mode 100644 index 000000000..b81c17a14 --- /dev/null +++ b/azext_iot/sdk/product/models/fail_to_queue_task_error_py3.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class FailToQueueTaskError(Model): + """FailToQueueTaskError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, *, message: str=None, code: int=None, details=None, **kwargs) -> None: + super(FailToQueueTaskError, self).__init__(**kwargs) + self.message = message + self.code = code + self.details = details diff --git a/azext_iot/sdk/product/models/interface_definition_snapshot.py b/azext_iot/sdk/product/models/interface_definition_snapshot.py new file mode 100644 index 000000000..3d9b9affb --- /dev/null +++ b/azext_iot/sdk/product/models/interface_definition_snapshot.py @@ -0,0 +1,37 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class InterfaceDefinitionSnapshot(Model): + """InterfaceDefinitionSnapshot. + + :param interface_id: + :type interface_id: str + :param content: + :type content: str + :param resolution_source: Possible values include: 'Unknown', + 'GlobalRepository', 'PrivateRepository', 'UserUploads' + :type resolution_source: str or ~product.models.enum + """ + + _attribute_map = { + 'interface_id': {'key': 'interfaceId', 'type': 'str'}, + 'content': {'key': 'content', 'type': 'str'}, + 'resolution_source': {'key': 'resolutionSource', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(InterfaceDefinitionSnapshot, self).__init__(**kwargs) + self.interface_id = kwargs.get('interface_id', None) + self.content = kwargs.get('content', None) + self.resolution_source = kwargs.get('resolution_source', None) diff --git a/azext_iot/sdk/product/models/interface_definition_snapshot_py3.py b/azext_iot/sdk/product/models/interface_definition_snapshot_py3.py new file mode 100644 index 000000000..25f1b3527 --- /dev/null +++ b/azext_iot/sdk/product/models/interface_definition_snapshot_py3.py @@ -0,0 +1,37 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class InterfaceDefinitionSnapshot(Model): + """InterfaceDefinitionSnapshot. + + :param interface_id: + :type interface_id: str + :param content: + :type content: str + :param resolution_source: Possible values include: 'Unknown', + 'GlobalRepository', 'PrivateRepository', 'UserUploads' + :type resolution_source: str or ~product.models.enum + """ + + _attribute_map = { + 'interface_id': {'key': 'interfaceId', 'type': 'str'}, + 'content': {'key': 'content', 'type': 'str'}, + 'resolution_source': {'key': 'resolutionSource', 'type': 'str'}, + } + + def __init__(self, *, interface_id: str=None, content: str=None, resolution_source=None, **kwargs) -> None: + super(InterfaceDefinitionSnapshot, self).__init__(**kwargs) + self.interface_id = interface_id + self.content = content + self.resolution_source = resolution_source diff --git a/azext_iot/sdk/product/models/interface_test.py b/azext_iot/sdk/product/models/interface_test.py new file mode 100644 index 000000000..f9c9d8cda --- /dev/null +++ b/azext_iot/sdk/product/models/interface_test.py @@ -0,0 +1,61 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class InterfaceTest(Model): + """InterfaceTest. + + :param id: + :type id: str + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + :param component_name: + :type component_name: str + :param display_name: + :type display_name: str + :param resolution_source: Possible values include: 'Unknown', + 'GlobalRepository', 'PrivateRepository', 'UserUploads' + :type resolution_source: str or ~product.models.enum + :param property_tests: + :type property_tests: list[~product.models.PropertyTest] + :param command_tests: + :type command_tests: list[~product.models.CommandTest] + :param telemetry_tests: + :type telemetry_tests: list[~product.models.TelemetryTest] + """ + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + 'component_name': {'key': 'componentName', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': 'str'}, + 'resolution_source': {'key': 'resolutionSource', 'type': 'str'}, + 'property_tests': {'key': 'propertyTests', 'type': '[PropertyTest]'}, + 'command_tests': {'key': 'commandTests', 'type': '[CommandTest]'}, + 'telemetry_tests': {'key': 'telemetryTests', 'type': '[TelemetryTest]'}, + } + + def __init__(self, **kwargs): + super(InterfaceTest, self).__init__(**kwargs) + self.id = kwargs.get('id', None) + self.is_mandatory = kwargs.get('is_mandatory', None) + self.should_validate = kwargs.get('should_validate', None) + self.component_name = kwargs.get('component_name', None) + self.display_name = kwargs.get('display_name', None) + self.resolution_source = kwargs.get('resolution_source', None) + self.property_tests = kwargs.get('property_tests', None) + self.command_tests = kwargs.get('command_tests', None) + self.telemetry_tests = kwargs.get('telemetry_tests', None) diff --git a/azext_iot/sdk/product/models/interface_test_py3.py b/azext_iot/sdk/product/models/interface_test_py3.py new file mode 100644 index 000000000..c2d7c2d39 --- /dev/null +++ b/azext_iot/sdk/product/models/interface_test_py3.py @@ -0,0 +1,61 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class InterfaceTest(Model): + """InterfaceTest. + + :param id: + :type id: str + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + :param component_name: + :type component_name: str + :param display_name: + :type display_name: str + :param resolution_source: Possible values include: 'Unknown', + 'GlobalRepository', 'PrivateRepository', 'UserUploads' + :type resolution_source: str or ~product.models.enum + :param property_tests: + :type property_tests: list[~product.models.PropertyTest] + :param command_tests: + :type command_tests: list[~product.models.CommandTest] + :param telemetry_tests: + :type telemetry_tests: list[~product.models.TelemetryTest] + """ + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + 'component_name': {'key': 'componentName', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': 'str'}, + 'resolution_source': {'key': 'resolutionSource', 'type': 'str'}, + 'property_tests': {'key': 'propertyTests', 'type': '[PropertyTest]'}, + 'command_tests': {'key': 'commandTests', 'type': '[CommandTest]'}, + 'telemetry_tests': {'key': 'telemetryTests', 'type': '[TelemetryTest]'}, + } + + def __init__(self, *, id: str=None, is_mandatory: bool=None, should_validate: bool=None, component_name: str=None, display_name: str=None, resolution_source=None, property_tests=None, command_tests=None, telemetry_tests=None, **kwargs) -> None: + super(InterfaceTest, self).__init__(**kwargs) + self.id = id + self.is_mandatory = is_mandatory + self.should_validate = should_validate + self.component_name = component_name + self.display_name = display_name + self.resolution_source = resolution_source + self.property_tests = property_tests + self.command_tests = command_tests + self.telemetry_tests = telemetry_tests diff --git a/azext_iot/sdk/product/models/iot_device_certification_badge_configuration.py b/azext_iot/sdk/product/models/iot_device_certification_badge_configuration.py new file mode 100644 index 000000000..4569d0967 --- /dev/null +++ b/azext_iot/sdk/product/models/iot_device_certification_badge_configuration.py @@ -0,0 +1,29 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotDeviceCertificationBadgeConfiguration(Model): + """IotDeviceCertificationBadgeConfiguration. + + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + """ + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(IotDeviceCertificationBadgeConfiguration, self).__init__(**kwargs) + self.type = kwargs.get('type', None) diff --git a/azext_iot/sdk/product/models/iot_device_certification_badge_configuration_py3.py b/azext_iot/sdk/product/models/iot_device_certification_badge_configuration_py3.py new file mode 100644 index 000000000..747847fdb --- /dev/null +++ b/azext_iot/sdk/product/models/iot_device_certification_badge_configuration_py3.py @@ -0,0 +1,29 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotDeviceCertificationBadgeConfiguration(Model): + """IotDeviceCertificationBadgeConfiguration. + + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + """ + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, *, type=None, **kwargs) -> None: + super(IotDeviceCertificationBadgeConfiguration, self).__init__(**kwargs) + self.type = type diff --git a/azext_iot/sdk/product/models/iot_device_certification_badge_result.py b/azext_iot/sdk/product/models/iot_device_certification_badge_result.py new file mode 100644 index 000000000..4e2f18ff9 --- /dev/null +++ b/azext_iot/sdk/product/models/iot_device_certification_badge_result.py @@ -0,0 +1,42 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotDeviceCertificationBadgeResult(Model): + """IotDevice badge certification result. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :vartype type: str or ~product.models.enum + :ivar validation_tasks: + :vartype validation_tasks: + list[~product.models.IotDeviceValidationTaskResult] + """ + + _validation = { + 'type': {'readonly': True}, + 'validation_tasks': {'readonly': True}, + } + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'validation_tasks': {'key': 'validationTasks', 'type': '[IotDeviceValidationTaskResult]'}, + } + + def __init__(self, **kwargs): + super(IotDeviceCertificationBadgeResult, self).__init__(**kwargs) + self.type = None + self.validation_tasks = None diff --git a/azext_iot/sdk/product/models/iot_device_certification_badge_result_py3.py b/azext_iot/sdk/product/models/iot_device_certification_badge_result_py3.py new file mode 100644 index 000000000..f2d4769a3 --- /dev/null +++ b/azext_iot/sdk/product/models/iot_device_certification_badge_result_py3.py @@ -0,0 +1,42 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotDeviceCertificationBadgeResult(Model): + """IotDevice badge certification result. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :vartype type: str or ~product.models.enum + :ivar validation_tasks: + :vartype validation_tasks: + list[~product.models.IotDeviceValidationTaskResult] + """ + + _validation = { + 'type': {'readonly': True}, + 'validation_tasks': {'readonly': True}, + } + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'validation_tasks': {'key': 'validationTasks', 'type': '[IotDeviceValidationTaskResult]'}, + } + + def __init__(self, **kwargs) -> None: + super(IotDeviceCertificationBadgeResult, self).__init__(**kwargs) + self.type = None + self.validation_tasks = None diff --git a/azext_iot/sdk/product/models/iot_device_certification_badge_test_cases.py b/azext_iot/sdk/product/models/iot_device_certification_badge_test_cases.py new file mode 100644 index 000000000..bb587269c --- /dev/null +++ b/azext_iot/sdk/product/models/iot_device_certification_badge_test_cases.py @@ -0,0 +1,45 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotDeviceCertificationBadgeTestCases(Model): + """IotDeviceCertificationBadgeTestCases. + + :param c2_dtests: + :type c2_dtests: list[~product.models.C2DTest] + :param d2_ctests: + :type d2_ctests: list[~product.models.D2CTest] + :param device_twin_tests: + :type device_twin_tests: list[~product.models.DeviceTwinTest] + :param direct_method_tests: + :type direct_method_tests: list[~product.models.DirectMethodTest] + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + """ + + _attribute_map = { + 'c2_dtests': {'key': 'c2DTests', 'type': '[C2DTest]'}, + 'd2_ctests': {'key': 'd2CTests', 'type': '[D2CTest]'}, + 'device_twin_tests': {'key': 'deviceTwinTests', 'type': '[DeviceTwinTest]'}, + 'direct_method_tests': {'key': 'directMethodTests', 'type': '[DirectMethodTest]'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(IotDeviceCertificationBadgeTestCases, self).__init__(**kwargs) + self.c2_dtests = kwargs.get('c2_dtests', None) + self.d2_ctests = kwargs.get('d2_ctests', None) + self.device_twin_tests = kwargs.get('device_twin_tests', None) + self.direct_method_tests = kwargs.get('direct_method_tests', None) + self.type = kwargs.get('type', None) diff --git a/azext_iot/sdk/product/models/iot_device_certification_badge_test_cases_py3.py b/azext_iot/sdk/product/models/iot_device_certification_badge_test_cases_py3.py new file mode 100644 index 000000000..ee3f4fcee --- /dev/null +++ b/azext_iot/sdk/product/models/iot_device_certification_badge_test_cases_py3.py @@ -0,0 +1,45 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotDeviceCertificationBadgeTestCases(Model): + """IotDeviceCertificationBadgeTestCases. + + :param c2_dtests: + :type c2_dtests: list[~product.models.C2DTest] + :param d2_ctests: + :type d2_ctests: list[~product.models.D2CTest] + :param device_twin_tests: + :type device_twin_tests: list[~product.models.DeviceTwinTest] + :param direct_method_tests: + :type direct_method_tests: list[~product.models.DirectMethodTest] + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + """ + + _attribute_map = { + 'c2_dtests': {'key': 'c2DTests', 'type': '[C2DTest]'}, + 'd2_ctests': {'key': 'd2CTests', 'type': '[D2CTest]'}, + 'device_twin_tests': {'key': 'deviceTwinTests', 'type': '[DeviceTwinTest]'}, + 'direct_method_tests': {'key': 'directMethodTests', 'type': '[DirectMethodTest]'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, *, c2_dtests=None, d2_ctests=None, device_twin_tests=None, direct_method_tests=None, type=None, **kwargs) -> None: + super(IotDeviceCertificationBadgeTestCases, self).__init__(**kwargs) + self.c2_dtests = c2_dtests + self.d2_ctests = d2_ctests + self.device_twin_tests = device_twin_tests + self.direct_method_tests = direct_method_tests + self.type = type diff --git a/azext_iot/sdk/product/models/iot_device_validation_task_result.py b/azext_iot/sdk/product/models/iot_device_validation_task_result.py new file mode 100644 index 000000000..6edf3fe29 --- /dev/null +++ b/azext_iot/sdk/product/models/iot_device_validation_task_result.py @@ -0,0 +1,66 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotDeviceValidationTaskResult(Model): + """IotDeviceValidationTaskResult. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param name: + :type name: str + :param timeout: + :type timeout: int + :param start_time: + :type start_time: datetime + :param end_time: + :type end_time: datetime + :param status: Possible values include: 'Created', 'Running', 'Failed', + 'Passed', 'Cancelled', 'Aborted' + :type status: str or ~product.models.enum + :ivar message: + :vartype message: str + :ivar logs: + :vartype logs: list[~product.models.CertificationTaskLog] + :ivar raw_data: + :vartype raw_data: list[str] + """ + + _validation = { + 'message': {'readonly': True}, + 'logs': {'readonly': True}, + 'raw_data': {'readonly': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'timeout': {'key': 'timeout', 'type': 'int'}, + 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, + 'end_time': {'key': 'endTime', 'type': 'iso-8601'}, + 'status': {'key': 'status', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'logs': {'key': 'logs', 'type': '[CertificationTaskLog]'}, + 'raw_data': {'key': 'rawData', 'type': '[str]'}, + } + + def __init__(self, **kwargs): + super(IotDeviceValidationTaskResult, self).__init__(**kwargs) + self.name = kwargs.get('name', None) + self.timeout = kwargs.get('timeout', None) + self.start_time = kwargs.get('start_time', None) + self.end_time = kwargs.get('end_time', None) + self.status = kwargs.get('status', None) + self.message = None + self.logs = None + self.raw_data = None diff --git a/azext_iot/sdk/product/models/iot_device_validation_task_result_py3.py b/azext_iot/sdk/product/models/iot_device_validation_task_result_py3.py new file mode 100644 index 000000000..f1d979640 --- /dev/null +++ b/azext_iot/sdk/product/models/iot_device_validation_task_result_py3.py @@ -0,0 +1,66 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotDeviceValidationTaskResult(Model): + """IotDeviceValidationTaskResult. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param name: + :type name: str + :param timeout: + :type timeout: int + :param start_time: + :type start_time: datetime + :param end_time: + :type end_time: datetime + :param status: Possible values include: 'Created', 'Running', 'Failed', + 'Passed', 'Cancelled', 'Aborted' + :type status: str or ~product.models.enum + :ivar message: + :vartype message: str + :ivar logs: + :vartype logs: list[~product.models.CertificationTaskLog] + :ivar raw_data: + :vartype raw_data: list[str] + """ + + _validation = { + 'message': {'readonly': True}, + 'logs': {'readonly': True}, + 'raw_data': {'readonly': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'timeout': {'key': 'timeout', 'type': 'int'}, + 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, + 'end_time': {'key': 'endTime', 'type': 'iso-8601'}, + 'status': {'key': 'status', 'type': 'str'}, + 'message': {'key': 'message', 'type': 'str'}, + 'logs': {'key': 'logs', 'type': '[CertificationTaskLog]'}, + 'raw_data': {'key': 'rawData', 'type': '[str]'}, + } + + def __init__(self, *, name: str=None, timeout: int=None, start_time=None, end_time=None, status=None, **kwargs) -> None: + super(IotDeviceValidationTaskResult, self).__init__(**kwargs) + self.name = name + self.timeout = timeout + self.start_time = start_time + self.end_time = end_time + self.status = status + self.message = None + self.logs = None + self.raw_data = None diff --git a/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_configuration.py b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_configuration.py new file mode 100644 index 000000000..d815245dc --- /dev/null +++ b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_configuration.py @@ -0,0 +1,29 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotEdgeCompatibleCertificationBadgeConfiguration(Model): + """IotEdgeCompatibleCertificationBadgeConfiguration. + + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + """ + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(IotEdgeCompatibleCertificationBadgeConfiguration, self).__init__(**kwargs) + self.type = kwargs.get('type', None) diff --git a/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_configuration_py3.py b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_configuration_py3.py new file mode 100644 index 000000000..9e6003a5c --- /dev/null +++ b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_configuration_py3.py @@ -0,0 +1,29 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotEdgeCompatibleCertificationBadgeConfiguration(Model): + """IotEdgeCompatibleCertificationBadgeConfiguration. + + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + """ + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, *, type=None, **kwargs) -> None: + super(IotEdgeCompatibleCertificationBadgeConfiguration, self).__init__(**kwargs) + self.type = type diff --git a/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_result.py b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_result.py new file mode 100644 index 000000000..7648fd326 --- /dev/null +++ b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_result.py @@ -0,0 +1,42 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotEdgeCompatibleCertificationBadgeResult(Model): + """IotDevice badge certification result. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :vartype type: str or ~product.models.enum + :ivar validation_tasks: + :vartype validation_tasks: + list[~product.models.EdgeDeviceValidationTaskResult] + """ + + _validation = { + 'type': {'readonly': True}, + 'validation_tasks': {'readonly': True}, + } + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'validation_tasks': {'key': 'validationTasks', 'type': '[EdgeDeviceValidationTaskResult]'}, + } + + def __init__(self, **kwargs): + super(IotEdgeCompatibleCertificationBadgeResult, self).__init__(**kwargs) + self.type = None + self.validation_tasks = None diff --git a/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_result_py3.py b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_result_py3.py new file mode 100644 index 000000000..7c2c1580b --- /dev/null +++ b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_result_py3.py @@ -0,0 +1,42 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotEdgeCompatibleCertificationBadgeResult(Model): + """IotDevice badge certification result. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :vartype type: str or ~product.models.enum + :ivar validation_tasks: + :vartype validation_tasks: + list[~product.models.EdgeDeviceValidationTaskResult] + """ + + _validation = { + 'type': {'readonly': True}, + 'validation_tasks': {'readonly': True}, + } + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'validation_tasks': {'key': 'validationTasks', 'type': '[EdgeDeviceValidationTaskResult]'}, + } + + def __init__(self, **kwargs) -> None: + super(IotEdgeCompatibleCertificationBadgeResult, self).__init__(**kwargs) + self.type = None + self.validation_tasks = None diff --git a/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_test_cases.py b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_test_cases.py new file mode 100644 index 000000000..4e2a3cf27 --- /dev/null +++ b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_test_cases.py @@ -0,0 +1,29 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class IotEdgeCompatibleCertificationBadgeTestCases(Model): + """IotEdgeCompatibleCertificationBadgeTestCases. + + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + """ + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(IotEdgeCompatibleCertificationBadgeTestCases, self).__init__(**kwargs) + self.type = kwargs.get('type', None) diff --git a/azext_iot/sdk/service/models/configuration_metrics.py b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_test_cases_py3.py similarity index 54% rename from azext_iot/sdk/service/models/configuration_metrics.py rename to azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_test_cases_py3.py index 332bb17d7..ecff69557 100644 --- a/azext_iot/sdk/service/models/configuration_metrics.py +++ b/azext_iot/sdk/product/models/iot_edge_compatible_certification_badge_test_cases_py3.py @@ -12,21 +12,18 @@ from msrest.serialization import Model -class ConfigurationMetrics(Model): - """Configuration Metrics. +class IotEdgeCompatibleCertificationBadgeTestCases(Model): + """IotEdgeCompatibleCertificationBadgeTestCases. - :param results: - :type results: dict[str, long] - :param queries: - :type queries: dict[str, str] + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum """ _attribute_map = { - 'results': {'key': 'results', 'type': '{long}'}, - 'queries': {'key': 'queries', 'type': '{str}'}, + 'type': {'key': 'type', 'type': 'str'}, } - def __init__(self, results=None, queries=None): - super(ConfigurationMetrics, self).__init__() - self.results = results - self.queries = queries + def __init__(self, *, type=None, **kwargs) -> None: + super(IotEdgeCompatibleCertificationBadgeTestCases, self).__init__(**kwargs) + self.type = type diff --git a/azext_iot/sdk/product/models/map_schema.py b/azext_iot/sdk/product/models/map_schema.py new file mode 100644 index 000000000..f9697fdbd --- /dev/null +++ b/azext_iot/sdk/product/models/map_schema.py @@ -0,0 +1,62 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class MapSchema(Model): + """MapSchema. + + :param key_schema: + :type key_schema: ~product.models.SchemaField + :param value_schema: + :type value_schema: ~product.models.SchemaField + :param schema_type: Possible values include: 'Unknown', 'Boolean', 'Date', + 'DateTime', 'Double', 'Duration', 'Float', 'Integer', 'Long', 'String', + 'Time', 'Enum', 'Object', 'Map', 'Array' + :type schema_type: str or ~product.models.enum + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'key_schema': {'key': 'keySchema', 'type': 'SchemaField'}, + 'value_schema': {'key': 'valueSchema', 'type': 'SchemaField'}, + 'schema_type': {'key': 'schemaType', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(MapSchema, self).__init__(**kwargs) + self.key_schema = kwargs.get('key_schema', None) + self.value_schema = kwargs.get('value_schema', None) + self.schema_type = kwargs.get('schema_type', None) + self.name = kwargs.get('name', None) + self.comment = kwargs.get('comment', None) + self.display_name = kwargs.get('display_name', None) + self.id = kwargs.get('id', None) + self.description = kwargs.get('description', None) + self.language_version = kwargs.get('language_version', None) diff --git a/azext_iot/sdk/product/models/map_schema_py3.py b/azext_iot/sdk/product/models/map_schema_py3.py new file mode 100644 index 000000000..4875fba99 --- /dev/null +++ b/azext_iot/sdk/product/models/map_schema_py3.py @@ -0,0 +1,62 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class MapSchema(Model): + """MapSchema. + + :param key_schema: + :type key_schema: ~product.models.SchemaField + :param value_schema: + :type value_schema: ~product.models.SchemaField + :param schema_type: Possible values include: 'Unknown', 'Boolean', 'Date', + 'DateTime', 'Double', 'Duration', 'Float', 'Integer', 'Long', 'String', + 'Time', 'Enum', 'Object', 'Map', 'Array' + :type schema_type: str or ~product.models.enum + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'key_schema': {'key': 'keySchema', 'type': 'SchemaField'}, + 'value_schema': {'key': 'valueSchema', 'type': 'SchemaField'}, + 'schema_type': {'key': 'schemaType', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, *, key_schema=None, value_schema=None, schema_type=None, name: str=None, comment: str=None, display_name=None, id: str=None, description=None, language_version: int=None, **kwargs) -> None: + super(MapSchema, self).__init__(**kwargs) + self.key_schema = key_schema + self.value_schema = value_schema + self.schema_type = schema_type + self.name = name + self.comment = comment + self.display_name = display_name + self.id = id + self.description = description + self.language_version = language_version diff --git a/azext_iot/sdk/product/models/model_definition_snapshot.py b/azext_iot/sdk/product/models/model_definition_snapshot.py new file mode 100644 index 000000000..cd259a63e --- /dev/null +++ b/azext_iot/sdk/product/models/model_definition_snapshot.py @@ -0,0 +1,42 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ModelDefinitionSnapshot(Model): + """ModelDefinitionSnapshot. + + :param id: + :type id: str + :param content: + :type content: str + :param resolution_source: Possible values include: 'Unknown', + 'GlobalRepository', 'PrivateRepository', 'UserUploads' + :type resolution_source: str or ~product.models.enum + :param interface_snapshots: + :type interface_snapshots: + list[~product.models.InterfaceDefinitionSnapshot] + """ + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'content': {'key': 'content', 'type': 'str'}, + 'resolution_source': {'key': 'resolutionSource', 'type': 'str'}, + 'interface_snapshots': {'key': 'interfaceSnapshots', 'type': '[InterfaceDefinitionSnapshot]'}, + } + + def __init__(self, **kwargs): + super(ModelDefinitionSnapshot, self).__init__(**kwargs) + self.id = kwargs.get('id', None) + self.content = kwargs.get('content', None) + self.resolution_source = kwargs.get('resolution_source', None) + self.interface_snapshots = kwargs.get('interface_snapshots', None) diff --git a/azext_iot/sdk/product/models/model_definition_snapshot_py3.py b/azext_iot/sdk/product/models/model_definition_snapshot_py3.py new file mode 100644 index 000000000..bc3b18a26 --- /dev/null +++ b/azext_iot/sdk/product/models/model_definition_snapshot_py3.py @@ -0,0 +1,42 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ModelDefinitionSnapshot(Model): + """ModelDefinitionSnapshot. + + :param id: + :type id: str + :param content: + :type content: str + :param resolution_source: Possible values include: 'Unknown', + 'GlobalRepository', 'PrivateRepository', 'UserUploads' + :type resolution_source: str or ~product.models.enum + :param interface_snapshots: + :type interface_snapshots: + list[~product.models.InterfaceDefinitionSnapshot] + """ + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'content': {'key': 'content', 'type': 'str'}, + 'resolution_source': {'key': 'resolutionSource', 'type': 'str'}, + 'interface_snapshots': {'key': 'interfaceSnapshots', 'type': '[InterfaceDefinitionSnapshot]'}, + } + + def __init__(self, *, id: str=None, content: str=None, resolution_source=None, interface_snapshots=None, **kwargs) -> None: + super(ModelDefinitionSnapshot, self).__init__(**kwargs) + self.id = id + self.content = content + self.resolution_source = resolution_source + self.interface_snapshots = interface_snapshots diff --git a/azext_iot/sdk/product/models/model_resolution_failure_error.py b/azext_iot/sdk/product/models/model_resolution_failure_error.py new file mode 100644 index 000000000..cf6738ad6 --- /dev/null +++ b/azext_iot/sdk/product/models/model_resolution_failure_error.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ModelResolutionFailureError(Model): + """ModelResolutionFailureError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, **kwargs): + super(ModelResolutionFailureError, self).__init__(**kwargs) + self.message = kwargs.get('message', None) + self.code = kwargs.get('code', None) + self.details = kwargs.get('details', None) diff --git a/azext_iot/sdk/product/models/model_resolution_failure_error_py3.py b/azext_iot/sdk/product/models/model_resolution_failure_error_py3.py new file mode 100644 index 000000000..7ed5cfc5b --- /dev/null +++ b/azext_iot/sdk/product/models/model_resolution_failure_error_py3.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ModelResolutionFailureError(Model): + """ModelResolutionFailureError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, *, message: str=None, code: int=None, details=None, **kwargs) -> None: + super(ModelResolutionFailureError, self).__init__(**kwargs) + self.message = message + self.code = code + self.details = details diff --git a/azext_iot/sdk/product/models/model_resolution_source.py b/azext_iot/sdk/product/models/model_resolution_source.py new file mode 100644 index 000000000..a2ace37c4 --- /dev/null +++ b/azext_iot/sdk/product/models/model_resolution_source.py @@ -0,0 +1,33 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ModelResolutionSource(Model): + """ModelResolutionSource. + + :param priority: + :type priority: int + :param source_type: Possible values include: 'Unknown', + 'GlobalRepository', 'PrivateRepository', 'UserUploads' + :type source_type: str or ~product.models.enum + """ + + _attribute_map = { + 'priority': {'key': 'priority', 'type': 'int'}, + 'source_type': {'key': 'sourceType', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(ModelResolutionSource, self).__init__(**kwargs) + self.priority = kwargs.get('priority', None) + self.source_type = kwargs.get('source_type', None) diff --git a/azext_iot/sdk/product/models/model_resolution_source_py3.py b/azext_iot/sdk/product/models/model_resolution_source_py3.py new file mode 100644 index 000000000..e556ef5af --- /dev/null +++ b/azext_iot/sdk/product/models/model_resolution_source_py3.py @@ -0,0 +1,33 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ModelResolutionSource(Model): + """ModelResolutionSource. + + :param priority: + :type priority: int + :param source_type: Possible values include: 'Unknown', + 'GlobalRepository', 'PrivateRepository', 'UserUploads' + :type source_type: str or ~product.models.enum + """ + + _attribute_map = { + 'priority': {'key': 'priority', 'type': 'int'}, + 'source_type': {'key': 'sourceType', 'type': 'str'}, + } + + def __init__(self, *, priority: int=None, source_type=None, **kwargs) -> None: + super(ModelResolutionSource, self).__init__(**kwargs) + self.priority = priority + self.source_type = source_type diff --git a/azext_iot/sdk/iothub/service/models/desired.py b/azext_iot/sdk/product/models/new_task_payload.py similarity index 62% rename from azext_iot/sdk/iothub/service/models/desired.py rename to azext_iot/sdk/product/models/new_task_payload.py index 5bc2a8dd8..6027b9c79 100644 --- a/azext_iot/sdk/iothub/service/models/desired.py +++ b/azext_iot/sdk/product/models/new_task_payload.py @@ -12,18 +12,18 @@ from msrest.serialization import Model -class Desired(Model): - """Desired. +class NewTaskPayload(Model): + """NewTaskPayload. - :param value: The desired value of the interface property to set in a - digitalTwin. - :type value: object + :param task_type: Possible values include: 'QueueTestRun', + 'GenerateTestCases' + :type task_type: str or ~product.models.enum """ _attribute_map = { - 'value': {'key': 'value', 'type': 'object'}, + 'task_type': {'key': 'taskType', 'type': 'str'}, } def __init__(self, **kwargs): - super(Desired, self).__init__(**kwargs) - self.value = kwargs.get('value', None) + super(NewTaskPayload, self).__init__(**kwargs) + self.task_type = kwargs.get('task_type', None) diff --git a/azext_iot/sdk/product/models/new_task_payload_py3.py b/azext_iot/sdk/product/models/new_task_payload_py3.py new file mode 100644 index 000000000..43a4bdb9b --- /dev/null +++ b/azext_iot/sdk/product/models/new_task_payload_py3.py @@ -0,0 +1,29 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class NewTaskPayload(Model): + """NewTaskPayload. + + :param task_type: Possible values include: 'QueueTestRun', + 'GenerateTestCases' + :type task_type: str or ~product.models.enum + """ + + _attribute_map = { + 'task_type': {'key': 'taskType', 'type': 'str'}, + } + + def __init__(self, *, task_type=None, **kwargs) -> None: + super(NewTaskPayload, self).__init__(**kwargs) + self.task_type = task_type diff --git a/azext_iot/sdk/product/models/object_schema.py b/azext_iot/sdk/product/models/object_schema.py new file mode 100644 index 000000000..44328637b --- /dev/null +++ b/azext_iot/sdk/product/models/object_schema.py @@ -0,0 +1,58 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ObjectSchema(Model): + """ObjectSchema. + + :param fields: + :type fields: list[~product.models.SchemaField] + :param schema_type: Possible values include: 'Unknown', 'Boolean', 'Date', + 'DateTime', 'Double', 'Duration', 'Float', 'Integer', 'Long', 'String', + 'Time', 'Enum', 'Object', 'Map', 'Array' + :type schema_type: str or ~product.models.enum + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'fields': {'key': 'fields', 'type': '[SchemaField]'}, + 'schema_type': {'key': 'schemaType', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(ObjectSchema, self).__init__(**kwargs) + self.fields = kwargs.get('fields', None) + self.schema_type = kwargs.get('schema_type', None) + self.name = kwargs.get('name', None) + self.comment = kwargs.get('comment', None) + self.display_name = kwargs.get('display_name', None) + self.id = kwargs.get('id', None) + self.description = kwargs.get('description', None) + self.language_version = kwargs.get('language_version', None) diff --git a/azext_iot/sdk/product/models/object_schema_py3.py b/azext_iot/sdk/product/models/object_schema_py3.py new file mode 100644 index 000000000..af02fbc61 --- /dev/null +++ b/azext_iot/sdk/product/models/object_schema_py3.py @@ -0,0 +1,58 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ObjectSchema(Model): + """ObjectSchema. + + :param fields: + :type fields: list[~product.models.SchemaField] + :param schema_type: Possible values include: 'Unknown', 'Boolean', 'Date', + 'DateTime', 'Double', 'Duration', 'Float', 'Integer', 'Long', 'String', + 'Time', 'Enum', 'Object', 'Map', 'Array' + :type schema_type: str or ~product.models.enum + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'fields': {'key': 'fields', 'type': '[SchemaField]'}, + 'schema_type': {'key': 'schemaType', 'type': 'str'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, *, fields=None, schema_type=None, name: str=None, comment: str=None, display_name=None, id: str=None, description=None, language_version: int=None, **kwargs) -> None: + super(ObjectSchema, self).__init__(**kwargs) + self.fields = fields + self.schema_type = schema_type + self.name = name + self.comment = comment + self.display_name = display_name + self.id = id + self.description = description + self.language_version = language_version diff --git a/azext_iot/sdk/product/models/pnp_certification_badge_configuration.py b/azext_iot/sdk/product/models/pnp_certification_badge_configuration.py new file mode 100644 index 000000000..a8b3b7087 --- /dev/null +++ b/azext_iot/sdk/product/models/pnp_certification_badge_configuration.py @@ -0,0 +1,37 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PnpCertificationBadgeConfiguration(Model): + """PnpCertificationBadgeConfiguration. + + :param digital_twin_model_definitions: + :type digital_twin_model_definitions: list[str] + :param resolution_sources: + :type resolution_sources: list[~product.models.ModelResolutionSource] + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + """ + + _attribute_map = { + 'digital_twin_model_definitions': {'key': 'digitalTwinModelDefinitions', 'type': '[str]'}, + 'resolution_sources': {'key': 'resolutionSources', 'type': '[ModelResolutionSource]'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(PnpCertificationBadgeConfiguration, self).__init__(**kwargs) + self.digital_twin_model_definitions = kwargs.get('digital_twin_model_definitions', None) + self.resolution_sources = kwargs.get('resolution_sources', None) + self.type = kwargs.get('type', None) diff --git a/azext_iot/sdk/product/models/pnp_certification_badge_configuration_py3.py b/azext_iot/sdk/product/models/pnp_certification_badge_configuration_py3.py new file mode 100644 index 000000000..b18fad109 --- /dev/null +++ b/azext_iot/sdk/product/models/pnp_certification_badge_configuration_py3.py @@ -0,0 +1,37 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PnpCertificationBadgeConfiguration(Model): + """PnpCertificationBadgeConfiguration. + + :param digital_twin_model_definitions: + :type digital_twin_model_definitions: list[str] + :param resolution_sources: + :type resolution_sources: list[~product.models.ModelResolutionSource] + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + """ + + _attribute_map = { + 'digital_twin_model_definitions': {'key': 'digitalTwinModelDefinitions', 'type': '[str]'}, + 'resolution_sources': {'key': 'resolutionSources', 'type': '[ModelResolutionSource]'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, *, digital_twin_model_definitions=None, resolution_sources=None, type=None, **kwargs) -> None: + super(PnpCertificationBadgeConfiguration, self).__init__(**kwargs) + self.digital_twin_model_definitions = digital_twin_model_definitions + self.resolution_sources = resolution_sources + self.type = type diff --git a/azext_iot/sdk/product/models/pnp_certification_badge_result.py b/azext_iot/sdk/product/models/pnp_certification_badge_result.py new file mode 100644 index 000000000..941ed3180 --- /dev/null +++ b/azext_iot/sdk/product/models/pnp_certification_badge_result.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PnpCertificationBadgeResult(Model): + """Pnp badge certification result. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar validation_tasks: + :vartype validation_tasks: + list[~product.models.DigitalTwinValidationTaskResult] + :ivar pre_validation_tasks: + :vartype pre_validation_tasks: + list[~product.models.PreValidationTaskResult] + :ivar type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :vartype type: str or ~product.models.enum + :param resolution_source: Possible values include: 'Unknown', + 'GlobalRepository', 'PrivateRepository', 'UserUploads' + :type resolution_source: str or ~product.models.enum + """ + + _validation = { + 'validation_tasks': {'readonly': True}, + 'pre_validation_tasks': {'readonly': True}, + 'type': {'readonly': True}, + } + + _attribute_map = { + 'validation_tasks': {'key': 'validationTasks', 'type': '[DigitalTwinValidationTaskResult]'}, + 'pre_validation_tasks': {'key': 'preValidationTasks', 'type': '[PreValidationTaskResult]'}, + 'type': {'key': 'type', 'type': 'str'}, + 'resolution_source': {'key': 'resolutionSource', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(PnpCertificationBadgeResult, self).__init__(**kwargs) + self.validation_tasks = None + self.pre_validation_tasks = None + self.type = None + self.resolution_source = kwargs.get('resolution_source', None) diff --git a/azext_iot/sdk/product/models/pnp_certification_badge_result_py3.py b/azext_iot/sdk/product/models/pnp_certification_badge_result_py3.py new file mode 100644 index 000000000..cf6668ecd --- /dev/null +++ b/azext_iot/sdk/product/models/pnp_certification_badge_result_py3.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PnpCertificationBadgeResult(Model): + """Pnp badge certification result. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :ivar validation_tasks: + :vartype validation_tasks: + list[~product.models.DigitalTwinValidationTaskResult] + :ivar pre_validation_tasks: + :vartype pre_validation_tasks: + list[~product.models.PreValidationTaskResult] + :ivar type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :vartype type: str or ~product.models.enum + :param resolution_source: Possible values include: 'Unknown', + 'GlobalRepository', 'PrivateRepository', 'UserUploads' + :type resolution_source: str or ~product.models.enum + """ + + _validation = { + 'validation_tasks': {'readonly': True}, + 'pre_validation_tasks': {'readonly': True}, + 'type': {'readonly': True}, + } + + _attribute_map = { + 'validation_tasks': {'key': 'validationTasks', 'type': '[DigitalTwinValidationTaskResult]'}, + 'pre_validation_tasks': {'key': 'preValidationTasks', 'type': '[PreValidationTaskResult]'}, + 'type': {'key': 'type', 'type': 'str'}, + 'resolution_source': {'key': 'resolutionSource', 'type': 'str'}, + } + + def __init__(self, *, resolution_source=None, **kwargs) -> None: + super(PnpCertificationBadgeResult, self).__init__(**kwargs) + self.validation_tasks = None + self.pre_validation_tasks = None + self.type = None + self.resolution_source = resolution_source diff --git a/azext_iot/sdk/product/models/pnp_certification_badge_test_cases.py b/azext_iot/sdk/product/models/pnp_certification_badge_test_cases.py new file mode 100644 index 000000000..0f0f0aa27 --- /dev/null +++ b/azext_iot/sdk/product/models/pnp_certification_badge_test_cases.py @@ -0,0 +1,37 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PnpCertificationBadgeTestCases(Model): + """PnpCertificationBadgeTestCases. + + :param model_definition_snapshot: + :type model_definition_snapshot: ~product.models.ModelDefinitionSnapshot + :param interface_tests: + :type interface_tests: list[~product.models.InterfaceTest] + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + """ + + _attribute_map = { + 'model_definition_snapshot': {'key': 'modelDefinitionSnapshot', 'type': 'ModelDefinitionSnapshot'}, + 'interface_tests': {'key': 'interfaceTests', 'type': '[InterfaceTest]'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(PnpCertificationBadgeTestCases, self).__init__(**kwargs) + self.model_definition_snapshot = kwargs.get('model_definition_snapshot', None) + self.interface_tests = kwargs.get('interface_tests', None) + self.type = kwargs.get('type', None) diff --git a/azext_iot/sdk/product/models/pnp_certification_badge_test_cases_py3.py b/azext_iot/sdk/product/models/pnp_certification_badge_test_cases_py3.py new file mode 100644 index 000000000..984f21e58 --- /dev/null +++ b/azext_iot/sdk/product/models/pnp_certification_badge_test_cases_py3.py @@ -0,0 +1,37 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PnpCertificationBadgeTestCases(Model): + """PnpCertificationBadgeTestCases. + + :param model_definition_snapshot: + :type model_definition_snapshot: ~product.models.ModelDefinitionSnapshot + :param interface_tests: + :type interface_tests: list[~product.models.InterfaceTest] + :param type: Possible values include: 'IotDevice', 'Pnp', + 'IotEdgeCompatible' + :type type: str or ~product.models.enum + """ + + _attribute_map = { + 'model_definition_snapshot': {'key': 'modelDefinitionSnapshot', 'type': 'ModelDefinitionSnapshot'}, + 'interface_tests': {'key': 'interfaceTests', 'type': '[InterfaceTest]'}, + 'type': {'key': 'type', 'type': 'str'}, + } + + def __init__(self, *, model_definition_snapshot=None, interface_tests=None, type=None, **kwargs) -> None: + super(PnpCertificationBadgeTestCases, self).__init__(**kwargs) + self.model_definition_snapshot = model_definition_snapshot + self.interface_tests = interface_tests + self.type = type diff --git a/azext_iot/sdk/product/models/pre_validation_task_result.py b/azext_iot/sdk/product/models/pre_validation_task_result.py new file mode 100644 index 000000000..d0f87dd10 --- /dev/null +++ b/azext_iot/sdk/product/models/pre_validation_task_result.py @@ -0,0 +1,57 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PreValidationTaskResult(Model): + """PreValidationTaskResult. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param name: + :type name: str + :param start_time: + :type start_time: datetime + :param end_time: + :type end_time: datetime + :param status: Possible values include: 'Created', 'Running', 'Failed', + 'Passed', 'Cancelled', 'Aborted' + :type status: str or ~product.models.enum + :ivar logs: + :vartype logs: list[~product.models.CertificationTaskLog] + :ivar raw_data: + :vartype raw_data: list[str] + """ + + _validation = { + 'logs': {'readonly': True}, + 'raw_data': {'readonly': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, + 'end_time': {'key': 'endTime', 'type': 'iso-8601'}, + 'status': {'key': 'status', 'type': 'str'}, + 'logs': {'key': 'logs', 'type': '[CertificationTaskLog]'}, + 'raw_data': {'key': 'rawData', 'type': '[str]'}, + } + + def __init__(self, **kwargs): + super(PreValidationTaskResult, self).__init__(**kwargs) + self.name = kwargs.get('name', None) + self.start_time = kwargs.get('start_time', None) + self.end_time = kwargs.get('end_time', None) + self.status = kwargs.get('status', None) + self.logs = None + self.raw_data = None diff --git a/azext_iot/sdk/product/models/pre_validation_task_result_py3.py b/azext_iot/sdk/product/models/pre_validation_task_result_py3.py new file mode 100644 index 000000000..2dd376ba8 --- /dev/null +++ b/azext_iot/sdk/product/models/pre_validation_task_result_py3.py @@ -0,0 +1,57 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PreValidationTaskResult(Model): + """PreValidationTaskResult. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param name: + :type name: str + :param start_time: + :type start_time: datetime + :param end_time: + :type end_time: datetime + :param status: Possible values include: 'Created', 'Running', 'Failed', + 'Passed', 'Cancelled', 'Aborted' + :type status: str or ~product.models.enum + :ivar logs: + :vartype logs: list[~product.models.CertificationTaskLog] + :ivar raw_data: + :vartype raw_data: list[str] + """ + + _validation = { + 'logs': {'readonly': True}, + 'raw_data': {'readonly': True}, + } + + _attribute_map = { + 'name': {'key': 'name', 'type': 'str'}, + 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, + 'end_time': {'key': 'endTime', 'type': 'iso-8601'}, + 'status': {'key': 'status', 'type': 'str'}, + 'logs': {'key': 'logs', 'type': '[CertificationTaskLog]'}, + 'raw_data': {'key': 'rawData', 'type': '[str]'}, + } + + def __init__(self, *, name: str=None, start_time=None, end_time=None, status=None, **kwargs) -> None: + super(PreValidationTaskResult, self).__init__(**kwargs) + self.name = name + self.start_time = start_time + self.end_time = end_time + self.status = status + self.logs = None + self.raw_data = None diff --git a/azext_iot/sdk/product/models/property_model.py b/azext_iot/sdk/product/models/property_model.py new file mode 100644 index 000000000..3f06422df --- /dev/null +++ b/azext_iot/sdk/product/models/property_model.py @@ -0,0 +1,64 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PropertyModel(Model): + """PropertyModel. + + :param supplemental_types: + :type supplemental_types: list[str] + :param schema: + :type schema: object + :param writable: + :type writable: bool + :param supplemental_properties: + :type supplemental_properties: dict[str, object] + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'supplemental_types': {'key': 'supplementalTypes', 'type': '[str]'}, + 'schema': {'key': 'schema', 'type': 'object'}, + 'writable': {'key': 'writable', 'type': 'bool'}, + 'supplemental_properties': {'key': 'supplementalProperties', 'type': '{object}'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(PropertyModel, self).__init__(**kwargs) + self.supplemental_types = kwargs.get('supplemental_types', None) + self.schema = kwargs.get('schema', None) + self.writable = kwargs.get('writable', None) + self.supplemental_properties = kwargs.get('supplemental_properties', None) + self.name = kwargs.get('name', None) + self.comment = kwargs.get('comment', None) + self.display_name = kwargs.get('display_name', None) + self.id = kwargs.get('id', None) + self.description = kwargs.get('description', None) + self.language_version = kwargs.get('language_version', None) diff --git a/azext_iot/sdk/product/models/property_model_py3.py b/azext_iot/sdk/product/models/property_model_py3.py new file mode 100644 index 000000000..7c981a5d5 --- /dev/null +++ b/azext_iot/sdk/product/models/property_model_py3.py @@ -0,0 +1,64 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PropertyModel(Model): + """PropertyModel. + + :param supplemental_types: + :type supplemental_types: list[str] + :param schema: + :type schema: object + :param writable: + :type writable: bool + :param supplemental_properties: + :type supplemental_properties: dict[str, object] + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'supplemental_types': {'key': 'supplementalTypes', 'type': '[str]'}, + 'schema': {'key': 'schema', 'type': 'object'}, + 'writable': {'key': 'writable', 'type': 'bool'}, + 'supplemental_properties': {'key': 'supplementalProperties', 'type': '{object}'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, *, supplemental_types=None, schema=None, writable: bool=None, supplemental_properties=None, name: str=None, comment: str=None, display_name=None, id: str=None, description=None, language_version: int=None, **kwargs) -> None: + super(PropertyModel, self).__init__(**kwargs) + self.supplemental_types = supplemental_types + self.schema = schema + self.writable = writable + self.supplemental_properties = supplemental_properties + self.name = name + self.comment = comment + self.display_name = display_name + self.id = id + self.description = description + self.language_version = language_version diff --git a/azext_iot/sdk/product/models/property_test.py b/azext_iot/sdk/product/models/property_test.py new file mode 100644 index 000000000..8e96a1d24 --- /dev/null +++ b/azext_iot/sdk/product/models/property_test.py @@ -0,0 +1,48 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PropertyTest(Model): + """PropertyTest. + + :param property: + :type property: ~product.models.PropertyModel + :param value_to_write: + :type value_to_write: str + :param value_to_report: + :type value_to_report: str + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'property': {'key': 'property', 'type': 'PropertyModel'}, + 'value_to_write': {'key': 'valueToWrite', 'type': 'str'}, + 'value_to_report': {'key': 'valueToReport', 'type': 'str'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, **kwargs): + super(PropertyTest, self).__init__(**kwargs) + self.property = kwargs.get('property', None) + self.value_to_write = kwargs.get('value_to_write', None) + self.value_to_report = kwargs.get('value_to_report', None) + self.validation_timeout = kwargs.get('validation_timeout', None) + self.is_mandatory = kwargs.get('is_mandatory', None) + self.should_validate = kwargs.get('should_validate', None) diff --git a/azext_iot/sdk/product/models/property_test_py3.py b/azext_iot/sdk/product/models/property_test_py3.py new file mode 100644 index 000000000..1143d1e51 --- /dev/null +++ b/azext_iot/sdk/product/models/property_test_py3.py @@ -0,0 +1,48 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class PropertyTest(Model): + """PropertyTest. + + :param property: + :type property: ~product.models.PropertyModel + :param value_to_write: + :type value_to_write: str + :param value_to_report: + :type value_to_report: str + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'property': {'key': 'property', 'type': 'PropertyModel'}, + 'value_to_write': {'key': 'valueToWrite', 'type': 'str'}, + 'value_to_report': {'key': 'valueToReport', 'type': 'str'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, *, property=None, value_to_write: str=None, value_to_report: str=None, validation_timeout: int=None, is_mandatory: bool=None, should_validate: bool=None, **kwargs) -> None: + super(PropertyTest, self).__init__(**kwargs) + self.property = property + self.value_to_write = value_to_write + self.value_to_report = value_to_report + self.validation_timeout = validation_timeout + self.is_mandatory = is_mandatory + self.should_validate = should_validate diff --git a/azext_iot/sdk/product/models/provisioning_configuration.py b/azext_iot/sdk/product/models/provisioning_configuration.py new file mode 100644 index 000000000..e11c3c326 --- /dev/null +++ b/azext_iot/sdk/product/models/provisioning_configuration.py @@ -0,0 +1,60 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ProvisioningConfiguration(Model): + """ProvisioningConfiguration. + + :param type: Possible values include: 'ConnectionString', 'X509', 'TPM', + 'SymmetricKey' + :type type: str or ~product.models.enum + :param device_id: + :type device_id: str + :param dps_registration_id: + :type dps_registration_id: str + :param region: Possible values include: 'Unknown', 'CentralUS', + 'CentralUSEUAP', 'EastUS', 'EastUS2EUAP', 'JapanEast', 'NorthEurope', + 'WestUS', 'WestUS2' + :type region: str or ~product.models.enum + :param device_connection_string: + :type device_connection_string: str + :param x509_enrollment_information: + :type x509_enrollment_information: ~product.models.X509Enrollment + :param symmetric_key_enrollment_information: + :type symmetric_key_enrollment_information: + ~product.models.SymmetricKeyEnrollment + :param tpm_enrollment_information: + :type tpm_enrollment_information: ~product.models.TpmEnrollment + """ + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'device_id': {'key': 'deviceId', 'type': 'str'}, + 'dps_registration_id': {'key': 'dpsRegistrationId', 'type': 'str'}, + 'region': {'key': 'region', 'type': 'str'}, + 'device_connection_string': {'key': 'deviceConnectionString', 'type': 'str'}, + 'x509_enrollment_information': {'key': 'x509EnrollmentInformation', 'type': 'X509Enrollment'}, + 'symmetric_key_enrollment_information': {'key': 'symmetricKeyEnrollmentInformation', 'type': 'SymmetricKeyEnrollment'}, + 'tpm_enrollment_information': {'key': 'tpmEnrollmentInformation', 'type': 'TpmEnrollment'}, + } + + def __init__(self, **kwargs): + super(ProvisioningConfiguration, self).__init__(**kwargs) + self.type = kwargs.get('type', None) + self.device_id = kwargs.get('device_id', None) + self.dps_registration_id = kwargs.get('dps_registration_id', None) + self.region = kwargs.get('region', None) + self.device_connection_string = kwargs.get('device_connection_string', None) + self.x509_enrollment_information = kwargs.get('x509_enrollment_information', None) + self.symmetric_key_enrollment_information = kwargs.get('symmetric_key_enrollment_information', None) + self.tpm_enrollment_information = kwargs.get('tpm_enrollment_information', None) diff --git a/azext_iot/sdk/product/models/provisioning_configuration_py3.py b/azext_iot/sdk/product/models/provisioning_configuration_py3.py new file mode 100644 index 000000000..6482c4209 --- /dev/null +++ b/azext_iot/sdk/product/models/provisioning_configuration_py3.py @@ -0,0 +1,60 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ProvisioningConfiguration(Model): + """ProvisioningConfiguration. + + :param type: Possible values include: 'ConnectionString', 'X509', 'TPM', + 'SymmetricKey' + :type type: str or ~product.models.enum + :param device_id: + :type device_id: str + :param dps_registration_id: + :type dps_registration_id: str + :param region: Possible values include: 'Unknown', 'CentralUS', + 'CentralUSEUAP', 'EastUS', 'EastUS2EUAP', 'JapanEast', 'NorthEurope', + 'WestUS', 'WestUS2' + :type region: str or ~product.models.enum + :param device_connection_string: + :type device_connection_string: str + :param x509_enrollment_information: + :type x509_enrollment_information: ~product.models.X509Enrollment + :param symmetric_key_enrollment_information: + :type symmetric_key_enrollment_information: + ~product.models.SymmetricKeyEnrollment + :param tpm_enrollment_information: + :type tpm_enrollment_information: ~product.models.TpmEnrollment + """ + + _attribute_map = { + 'type': {'key': 'type', 'type': 'str'}, + 'device_id': {'key': 'deviceId', 'type': 'str'}, + 'dps_registration_id': {'key': 'dpsRegistrationId', 'type': 'str'}, + 'region': {'key': 'region', 'type': 'str'}, + 'device_connection_string': {'key': 'deviceConnectionString', 'type': 'str'}, + 'x509_enrollment_information': {'key': 'x509EnrollmentInformation', 'type': 'X509Enrollment'}, + 'symmetric_key_enrollment_information': {'key': 'symmetricKeyEnrollmentInformation', 'type': 'SymmetricKeyEnrollment'}, + 'tpm_enrollment_information': {'key': 'tpmEnrollmentInformation', 'type': 'TpmEnrollment'}, + } + + def __init__(self, *, type=None, device_id: str=None, dps_registration_id: str=None, region=None, device_connection_string: str=None, x509_enrollment_information=None, symmetric_key_enrollment_information=None, tpm_enrollment_information=None, **kwargs) -> None: + super(ProvisioningConfiguration, self).__init__(**kwargs) + self.type = type + self.device_id = device_id + self.dps_registration_id = dps_registration_id + self.region = region + self.device_connection_string = device_connection_string + self.x509_enrollment_information = x509_enrollment_information + self.symmetric_key_enrollment_information = symmetric_key_enrollment_information + self.tpm_enrollment_information = tpm_enrollment_information diff --git a/azext_iot/sdk/product/models/schema_field.py b/azext_iot/sdk/product/models/schema_field.py new file mode 100644 index 000000000..efc98f092 --- /dev/null +++ b/azext_iot/sdk/product/models/schema_field.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class SchemaField(Model): + """SchemaField. + + :param schema: + :type schema: object + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'schema': {'key': 'schema', 'type': 'object'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(SchemaField, self).__init__(**kwargs) + self.schema = kwargs.get('schema', None) + self.name = kwargs.get('name', None) + self.comment = kwargs.get('comment', None) + self.display_name = kwargs.get('display_name', None) + self.id = kwargs.get('id', None) + self.description = kwargs.get('description', None) + self.language_version = kwargs.get('language_version', None) diff --git a/azext_iot/sdk/product/models/schema_field_py3.py b/azext_iot/sdk/product/models/schema_field_py3.py new file mode 100644 index 000000000..cf559baa0 --- /dev/null +++ b/azext_iot/sdk/product/models/schema_field_py3.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class SchemaField(Model): + """SchemaField. + + :param schema: + :type schema: object + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'schema': {'key': 'schema', 'type': 'object'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, *, schema=None, name: str=None, comment: str=None, display_name=None, id: str=None, description=None, language_version: int=None, **kwargs) -> None: + super(SchemaField, self).__init__(**kwargs) + self.schema = schema + self.name = name + self.comment = comment + self.display_name = display_name + self.id = id + self.description = description + self.language_version = language_version diff --git a/azext_iot/sdk/product/models/symmetric_key_enrollment.py b/azext_iot/sdk/product/models/symmetric_key_enrollment.py new file mode 100644 index 000000000..31cd3ba78 --- /dev/null +++ b/azext_iot/sdk/product/models/symmetric_key_enrollment.py @@ -0,0 +1,40 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class SymmetricKeyEnrollment(Model): + """SymmetricKeyEnrollment. + + :param registration_id: + :type registration_id: str + :param primary_key: + :type primary_key: str + :param secondary_key: + :type secondary_key: str + :param scope_id: + :type scope_id: str + """ + + _attribute_map = { + 'registration_id': {'key': 'registrationId', 'type': 'str'}, + 'primary_key': {'key': 'primaryKey', 'type': 'str'}, + 'secondary_key': {'key': 'secondaryKey', 'type': 'str'}, + 'scope_id': {'key': 'scopeId', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(SymmetricKeyEnrollment, self).__init__(**kwargs) + self.registration_id = kwargs.get('registration_id', None) + self.primary_key = kwargs.get('primary_key', None) + self.secondary_key = kwargs.get('secondary_key', None) + self.scope_id = kwargs.get('scope_id', None) diff --git a/azext_iot/sdk/product/models/symmetric_key_enrollment_py3.py b/azext_iot/sdk/product/models/symmetric_key_enrollment_py3.py new file mode 100644 index 000000000..599c6c203 --- /dev/null +++ b/azext_iot/sdk/product/models/symmetric_key_enrollment_py3.py @@ -0,0 +1,40 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class SymmetricKeyEnrollment(Model): + """SymmetricKeyEnrollment. + + :param registration_id: + :type registration_id: str + :param primary_key: + :type primary_key: str + :param secondary_key: + :type secondary_key: str + :param scope_id: + :type scope_id: str + """ + + _attribute_map = { + 'registration_id': {'key': 'registrationId', 'type': 'str'}, + 'primary_key': {'key': 'primaryKey', 'type': 'str'}, + 'secondary_key': {'key': 'secondaryKey', 'type': 'str'}, + 'scope_id': {'key': 'scopeId', 'type': 'str'}, + } + + def __init__(self, *, registration_id: str=None, primary_key: str=None, secondary_key: str=None, scope_id: str=None, **kwargs) -> None: + super(SymmetricKeyEnrollment, self).__init__(**kwargs) + self.registration_id = registration_id + self.primary_key = primary_key + self.secondary_key = secondary_key + self.scope_id = scope_id diff --git a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value.py b/azext_iot/sdk/product/models/system_error.py similarity index 52% rename from azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value.py rename to azext_iot/sdk/product/models/system_error.py index 40a4c9e43..774f0ba92 100644 --- a/azext_iot/sdk/iothub/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value.py +++ b/azext_iot/sdk/product/models/system_error.py @@ -12,18 +12,25 @@ from msrest.serialization import Model -class DigitalTwinInterfacesPatchInterfacesValuePropertiesValue(Model): - """DigitalTwinInterfacesPatchInterfacesValuePropertiesValue. +class SystemError(Model): + """SystemError. - :param desired: - :type desired: - ~service.models.DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] """ _attribute_map = { - 'desired': {'key': 'desired', 'type': 'DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired'}, + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, } def __init__(self, **kwargs): - super(DigitalTwinInterfacesPatchInterfacesValuePropertiesValue, self).__init__(**kwargs) - self.desired = kwargs.get('desired', None) + super(SystemError, self).__init__(**kwargs) + self.message = kwargs.get('message', None) + self.code = kwargs.get('code', None) + self.details = kwargs.get('details', None) diff --git a/azext_iot/sdk/product/models/system_error_py3.py b/azext_iot/sdk/product/models/system_error_py3.py new file mode 100644 index 000000000..7b9a097ba --- /dev/null +++ b/azext_iot/sdk/product/models/system_error_py3.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class SystemError(Model): + """SystemError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, *, message: str=None, code: int=None, details=None, **kwargs) -> None: + super(SystemError, self).__init__(**kwargs) + self.message = message + self.code = code + self.details = details diff --git a/azext_iot/sdk/product/models/telemetry_model.py b/azext_iot/sdk/product/models/telemetry_model.py new file mode 100644 index 000000000..d16e5e977 --- /dev/null +++ b/azext_iot/sdk/product/models/telemetry_model.py @@ -0,0 +1,60 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TelemetryModel(Model): + """TelemetryModel. + + :param supplemental_types: + :type supplemental_types: list[str] + :param schema: + :type schema: object + :param supplemental_properties: + :type supplemental_properties: dict[str, object] + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'supplemental_types': {'key': 'supplementalTypes', 'type': '[str]'}, + 'schema': {'key': 'schema', 'type': 'object'}, + 'supplemental_properties': {'key': 'supplementalProperties', 'type': '{object}'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, **kwargs): + super(TelemetryModel, self).__init__(**kwargs) + self.supplemental_types = kwargs.get('supplemental_types', None) + self.schema = kwargs.get('schema', None) + self.supplemental_properties = kwargs.get('supplemental_properties', None) + self.name = kwargs.get('name', None) + self.comment = kwargs.get('comment', None) + self.display_name = kwargs.get('display_name', None) + self.id = kwargs.get('id', None) + self.description = kwargs.get('description', None) + self.language_version = kwargs.get('language_version', None) diff --git a/azext_iot/sdk/product/models/telemetry_model_py3.py b/azext_iot/sdk/product/models/telemetry_model_py3.py new file mode 100644 index 000000000..208f9d7d6 --- /dev/null +++ b/azext_iot/sdk/product/models/telemetry_model_py3.py @@ -0,0 +1,60 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TelemetryModel(Model): + """TelemetryModel. + + :param supplemental_types: + :type supplemental_types: list[str] + :param schema: + :type schema: object + :param supplemental_properties: + :type supplemental_properties: dict[str, object] + :param name: + :type name: str + :param comment: + :type comment: str + :param display_name: + :type display_name: dict[str, str] + :param id: + :type id: str + :param description: + :type description: dict[str, str] + :param language_version: + :type language_version: int + """ + + _attribute_map = { + 'supplemental_types': {'key': 'supplementalTypes', 'type': '[str]'}, + 'schema': {'key': 'schema', 'type': 'object'}, + 'supplemental_properties': {'key': 'supplementalProperties', 'type': '{object}'}, + 'name': {'key': 'name', 'type': 'str'}, + 'comment': {'key': 'comment', 'type': 'str'}, + 'display_name': {'key': 'displayName', 'type': '{str}'}, + 'id': {'key': 'id', 'type': 'str'}, + 'description': {'key': 'description', 'type': '{str}'}, + 'language_version': {'key': 'languageVersion', 'type': 'int'}, + } + + def __init__(self, *, supplemental_types=None, schema=None, supplemental_properties=None, name: str=None, comment: str=None, display_name=None, id: str=None, description=None, language_version: int=None, **kwargs) -> None: + super(TelemetryModel, self).__init__(**kwargs) + self.supplemental_types = supplemental_types + self.schema = schema + self.supplemental_properties = supplemental_properties + self.name = name + self.comment = comment + self.display_name = display_name + self.id = id + self.description = description + self.language_version = language_version diff --git a/azext_iot/sdk/product/models/telemetry_test.py b/azext_iot/sdk/product/models/telemetry_test.py new file mode 100644 index 000000000..1ebb62942 --- /dev/null +++ b/azext_iot/sdk/product/models/telemetry_test.py @@ -0,0 +1,48 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TelemetryTest(Model): + """TelemetryTest. + + :param telemetry: + :type telemetry: ~product.models.TelemetryModel + :param expected_result: + :type expected_result: str + :param message_count: + :type message_count: int + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'telemetry': {'key': 'telemetry', 'type': 'TelemetryModel'}, + 'expected_result': {'key': 'expectedResult', 'type': 'str'}, + 'message_count': {'key': 'messageCount', 'type': 'int'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, **kwargs): + super(TelemetryTest, self).__init__(**kwargs) + self.telemetry = kwargs.get('telemetry', None) + self.expected_result = kwargs.get('expected_result', None) + self.message_count = kwargs.get('message_count', None) + self.validation_timeout = kwargs.get('validation_timeout', None) + self.is_mandatory = kwargs.get('is_mandatory', None) + self.should_validate = kwargs.get('should_validate', None) diff --git a/azext_iot/sdk/product/models/telemetry_test_py3.py b/azext_iot/sdk/product/models/telemetry_test_py3.py new file mode 100644 index 000000000..53c611bd3 --- /dev/null +++ b/azext_iot/sdk/product/models/telemetry_test_py3.py @@ -0,0 +1,48 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TelemetryTest(Model): + """TelemetryTest. + + :param telemetry: + :type telemetry: ~product.models.TelemetryModel + :param expected_result: + :type expected_result: str + :param message_count: + :type message_count: int + :param validation_timeout: + :type validation_timeout: int + :param is_mandatory: + :type is_mandatory: bool + :param should_validate: + :type should_validate: bool + """ + + _attribute_map = { + 'telemetry': {'key': 'telemetry', 'type': 'TelemetryModel'}, + 'expected_result': {'key': 'expectedResult', 'type': 'str'}, + 'message_count': {'key': 'messageCount', 'type': 'int'}, + 'validation_timeout': {'key': 'validationTimeout', 'type': 'int'}, + 'is_mandatory': {'key': 'isMandatory', 'type': 'bool'}, + 'should_validate': {'key': 'shouldValidate', 'type': 'bool'}, + } + + def __init__(self, *, telemetry=None, expected_result: str=None, message_count: int=None, validation_timeout: int=None, is_mandatory: bool=None, should_validate: bool=None, **kwargs) -> None: + super(TelemetryTest, self).__init__(**kwargs) + self.telemetry = telemetry + self.expected_result = expected_result + self.message_count = message_count + self.validation_timeout = validation_timeout + self.is_mandatory = is_mandatory + self.should_validate = should_validate diff --git a/azext_iot/sdk/product/models/test_cases.py b/azext_iot/sdk/product/models/test_cases.py new file mode 100644 index 000000000..682e14dcb --- /dev/null +++ b/azext_iot/sdk/product/models/test_cases.py @@ -0,0 +1,28 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TestCases(Model): + """Test cases of a DigitTwin test. + + :param certification_badge_test_cases: + :type certification_badge_test_cases: list[object] + """ + + _attribute_map = { + 'certification_badge_test_cases': {'key': 'certificationBadgeTestCases', 'type': '[object]'}, + } + + def __init__(self, **kwargs): + super(TestCases, self).__init__(**kwargs) + self.certification_badge_test_cases = kwargs.get('certification_badge_test_cases', None) diff --git a/azext_iot/sdk/product/models/test_cases_not_exist_error.py b/azext_iot/sdk/product/models/test_cases_not_exist_error.py new file mode 100644 index 000000000..67ae42a9a --- /dev/null +++ b/azext_iot/sdk/product/models/test_cases_not_exist_error.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TestCasesNotExistError(Model): + """TestCasesNotExistError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, **kwargs): + super(TestCasesNotExistError, self).__init__(**kwargs) + self.message = kwargs.get('message', None) + self.code = kwargs.get('code', None) + self.details = kwargs.get('details', None) diff --git a/azext_iot/sdk/product/models/test_cases_not_exist_error_py3.py b/azext_iot/sdk/product/models/test_cases_not_exist_error_py3.py new file mode 100644 index 000000000..f032f60a2 --- /dev/null +++ b/azext_iot/sdk/product/models/test_cases_not_exist_error_py3.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TestCasesNotExistError(Model): + """TestCasesNotExistError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, *, message: str=None, code: int=None, details=None, **kwargs) -> None: + super(TestCasesNotExistError, self).__init__(**kwargs) + self.message = message + self.code = code + self.details = details diff --git a/azext_iot/sdk/product/models/test_cases_py3.py b/azext_iot/sdk/product/models/test_cases_py3.py new file mode 100644 index 000000000..465cc189d --- /dev/null +++ b/azext_iot/sdk/product/models/test_cases_py3.py @@ -0,0 +1,28 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TestCases(Model): + """Test cases of a DigitTwin test. + + :param certification_badge_test_cases: + :type certification_badge_test_cases: list[object] + """ + + _attribute_map = { + 'certification_badge_test_cases': {'key': 'certificationBadgeTestCases', 'type': '[object]'}, + } + + def __init__(self, *, certification_badge_test_cases=None, **kwargs) -> None: + super(TestCases, self).__init__(**kwargs) + self.certification_badge_test_cases = certification_badge_test_cases diff --git a/azext_iot/sdk/product/models/test_run.py b/azext_iot/sdk/product/models/test_run.py new file mode 100644 index 000000000..052f54b8a --- /dev/null +++ b/azext_iot/sdk/product/models/test_run.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TestRun(Model): + """Test run result for API. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param id: + :type id: str + :param start_time: + :type start_time: datetime + :param end_time: + :type end_time: datetime + :param status: Possible values include: 'Queued', 'Started', 'Running', + 'Failed', 'Completed', 'Cancelled' + :type status: str or ~product.models.enum + :ivar certification_badge_results: + :vartype certification_badge_results: list[object] + """ + + _validation = { + 'certification_badge_results': {'readonly': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, + 'end_time': {'key': 'endTime', 'type': 'iso-8601'}, + 'status': {'key': 'status', 'type': 'str'}, + 'certification_badge_results': {'key': 'certificationBadgeResults', 'type': '[object]'}, + } + + def __init__(self, **kwargs): + super(TestRun, self).__init__(**kwargs) + self.id = kwargs.get('id', None) + self.start_time = kwargs.get('start_time', None) + self.end_time = kwargs.get('end_time', None) + self.status = kwargs.get('status', None) + self.certification_badge_results = None diff --git a/azext_iot/sdk/product/models/test_run_not_exist_error.py b/azext_iot/sdk/product/models/test_run_not_exist_error.py new file mode 100644 index 000000000..d8412548d --- /dev/null +++ b/azext_iot/sdk/product/models/test_run_not_exist_error.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TestRunNotExistError(Model): + """TestRunNotExistError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, **kwargs): + super(TestRunNotExistError, self).__init__(**kwargs) + self.message = kwargs.get('message', None) + self.code = kwargs.get('code', None) + self.details = kwargs.get('details', None) diff --git a/azext_iot/sdk/product/models/test_run_not_exist_error_py3.py b/azext_iot/sdk/product/models/test_run_not_exist_error_py3.py new file mode 100644 index 000000000..476ba5421 --- /dev/null +++ b/azext_iot/sdk/product/models/test_run_not_exist_error_py3.py @@ -0,0 +1,36 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TestRunNotExistError(Model): + """TestRunNotExistError. + + :param message: + :type message: str + :param code: + :type code: int + :param details: + :type details: list[object] + """ + + _attribute_map = { + 'message': {'key': 'message', 'type': 'str'}, + 'code': {'key': 'code', 'type': 'int'}, + 'details': {'key': 'details', 'type': '[object]'}, + } + + def __init__(self, *, message: str=None, code: int=None, details=None, **kwargs) -> None: + super(TestRunNotExistError, self).__init__(**kwargs) + self.message = message + self.code = code + self.details = details diff --git a/azext_iot/sdk/product/models/test_run_py3.py b/azext_iot/sdk/product/models/test_run_py3.py new file mode 100644 index 000000000..f1c71888a --- /dev/null +++ b/azext_iot/sdk/product/models/test_run_py3.py @@ -0,0 +1,52 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TestRun(Model): + """Test run result for API. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param id: + :type id: str + :param start_time: + :type start_time: datetime + :param end_time: + :type end_time: datetime + :param status: Possible values include: 'Queued', 'Started', 'Running', + 'Failed', 'Completed', 'Cancelled' + :type status: str or ~product.models.enum + :ivar certification_badge_results: + :vartype certification_badge_results: list[object] + """ + + _validation = { + 'certification_badge_results': {'readonly': True}, + } + + _attribute_map = { + 'id': {'key': 'id', 'type': 'str'}, + 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, + 'end_time': {'key': 'endTime', 'type': 'iso-8601'}, + 'status': {'key': 'status', 'type': 'str'}, + 'certification_badge_results': {'key': 'certificationBadgeResults', 'type': '[object]'}, + } + + def __init__(self, *, id: str=None, start_time=None, end_time=None, status=None, **kwargs) -> None: + super(TestRun, self).__init__(**kwargs) + self.id = id + self.start_time = start_time + self.end_time = end_time + self.status = status + self.certification_badge_results = None diff --git a/azext_iot/sdk/product/models/tpm_enrollment.py b/azext_iot/sdk/product/models/tpm_enrollment.py new file mode 100644 index 000000000..6380f643b --- /dev/null +++ b/azext_iot/sdk/product/models/tpm_enrollment.py @@ -0,0 +1,40 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TpmEnrollment(Model): + """TpmEnrollment. + + :param scope_id: + :type scope_id: str + :param registration_id: + :type registration_id: str + :param endorsement_key: + :type endorsement_key: str + :param storage_root_key: + :type storage_root_key: str + """ + + _attribute_map = { + 'scope_id': {'key': 'scopeId', 'type': 'str'}, + 'registration_id': {'key': 'registrationId', 'type': 'str'}, + 'endorsement_key': {'key': 'endorsementKey', 'type': 'str'}, + 'storage_root_key': {'key': 'storageRootKey', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(TpmEnrollment, self).__init__(**kwargs) + self.scope_id = kwargs.get('scope_id', None) + self.registration_id = kwargs.get('registration_id', None) + self.endorsement_key = kwargs.get('endorsement_key', None) + self.storage_root_key = kwargs.get('storage_root_key', None) diff --git a/azext_iot/sdk/product/models/tpm_enrollment_py3.py b/azext_iot/sdk/product/models/tpm_enrollment_py3.py new file mode 100644 index 000000000..97ab836ef --- /dev/null +++ b/azext_iot/sdk/product/models/tpm_enrollment_py3.py @@ -0,0 +1,40 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class TpmEnrollment(Model): + """TpmEnrollment. + + :param scope_id: + :type scope_id: str + :param registration_id: + :type registration_id: str + :param endorsement_key: + :type endorsement_key: str + :param storage_root_key: + :type storage_root_key: str + """ + + _attribute_map = { + 'scope_id': {'key': 'scopeId', 'type': 'str'}, + 'registration_id': {'key': 'registrationId', 'type': 'str'}, + 'endorsement_key': {'key': 'endorsementKey', 'type': 'str'}, + 'storage_root_key': {'key': 'storageRootKey', 'type': 'str'}, + } + + def __init__(self, *, scope_id: str=None, registration_id: str=None, endorsement_key: str=None, storage_root_key: str=None, **kwargs) -> None: + super(TpmEnrollment, self).__init__(**kwargs) + self.scope_id = scope_id + self.registration_id = registration_id + self.endorsement_key = endorsement_key + self.storage_root_key = storage_root_key diff --git a/azext_iot/sdk/product/models/validation_problem_details.py b/azext_iot/sdk/product/models/validation_problem_details.py new file mode 100644 index 000000000..b31ec2d2c --- /dev/null +++ b/azext_iot/sdk/product/models/validation_problem_details.py @@ -0,0 +1,60 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ValidationProblemDetails(Model): + """ValidationProblemDetails. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param additional_properties: Unmatched properties from the message are + deserialized this collection + :type additional_properties: dict[str, object] + :ivar errors: + :vartype errors: dict[str, list[str]] + :param type: + :type type: str + :param title: + :type title: str + :param status: + :type status: int + :param detail: + :type detail: str + :param instance: + :type instance: str + """ + + _validation = { + 'errors': {'readonly': True}, + } + + _attribute_map = { + 'additional_properties': {'key': '', 'type': '{object}'}, + 'errors': {'key': 'errors', 'type': '{[str]}'}, + 'type': {'key': 'type', 'type': 'str'}, + 'title': {'key': 'title', 'type': 'str'}, + 'status': {'key': 'status', 'type': 'int'}, + 'detail': {'key': 'detail', 'type': 'str'}, + 'instance': {'key': 'instance', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(ValidationProblemDetails, self).__init__(**kwargs) + self.additional_properties = kwargs.get('additional_properties', None) + self.errors = None + self.type = kwargs.get('type', None) + self.title = kwargs.get('title', None) + self.status = kwargs.get('status', None) + self.detail = kwargs.get('detail', None) + self.instance = kwargs.get('instance', None) diff --git a/azext_iot/sdk/product/models/validation_problem_details_py3.py b/azext_iot/sdk/product/models/validation_problem_details_py3.py new file mode 100644 index 000000000..395e3ae14 --- /dev/null +++ b/azext_iot/sdk/product/models/validation_problem_details_py3.py @@ -0,0 +1,60 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class ValidationProblemDetails(Model): + """ValidationProblemDetails. + + Variables are only populated by the server, and will be ignored when + sending a request. + + :param additional_properties: Unmatched properties from the message are + deserialized this collection + :type additional_properties: dict[str, object] + :ivar errors: + :vartype errors: dict[str, list[str]] + :param type: + :type type: str + :param title: + :type title: str + :param status: + :type status: int + :param detail: + :type detail: str + :param instance: + :type instance: str + """ + + _validation = { + 'errors': {'readonly': True}, + } + + _attribute_map = { + 'additional_properties': {'key': '', 'type': '{object}'}, + 'errors': {'key': 'errors', 'type': '{[str]}'}, + 'type': {'key': 'type', 'type': 'str'}, + 'title': {'key': 'title', 'type': 'str'}, + 'status': {'key': 'status', 'type': 'int'}, + 'detail': {'key': 'detail', 'type': 'str'}, + 'instance': {'key': 'instance', 'type': 'str'}, + } + + def __init__(self, *, additional_properties=None, type: str=None, title: str=None, status: int=None, detail: str=None, instance: str=None, **kwargs) -> None: + super(ValidationProblemDetails, self).__init__(**kwargs) + self.additional_properties = additional_properties + self.errors = None + self.type = type + self.title = title + self.status = status + self.detail = detail + self.instance = instance diff --git a/azext_iot/sdk/product/models/x509_enrollment.py b/azext_iot/sdk/product/models/x509_enrollment.py new file mode 100644 index 000000000..f3f400c72 --- /dev/null +++ b/azext_iot/sdk/product/models/x509_enrollment.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class X509Enrollment(Model): + """X509Enrollment. + + :param scope_id: + :type scope_id: str + :param subject: + :type subject: str + :param thumbprint: + :type thumbprint: str + :param registration_id: + :type registration_id: str + :param base64_encoded_x509_certificate: + :type base64_encoded_x509_certificate: str + """ + + _attribute_map = { + 'scope_id': {'key': 'scopeId', 'type': 'str'}, + 'subject': {'key': 'subject', 'type': 'str'}, + 'thumbprint': {'key': 'thumbprint', 'type': 'str'}, + 'registration_id': {'key': 'registrationId', 'type': 'str'}, + 'base64_encoded_x509_certificate': {'key': 'base64EncodedX509Certificate', 'type': 'str'}, + } + + def __init__(self, **kwargs): + super(X509Enrollment, self).__init__(**kwargs) + self.scope_id = kwargs.get('scope_id', None) + self.subject = kwargs.get('subject', None) + self.thumbprint = kwargs.get('thumbprint', None) + self.registration_id = kwargs.get('registration_id', None) + self.base64_encoded_x509_certificate = kwargs.get('base64_encoded_x509_certificate', None) diff --git a/azext_iot/sdk/product/models/x509_enrollment_py3.py b/azext_iot/sdk/product/models/x509_enrollment_py3.py new file mode 100644 index 000000000..e2ac30081 --- /dev/null +++ b/azext_iot/sdk/product/models/x509_enrollment_py3.py @@ -0,0 +1,44 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +from msrest.serialization import Model + + +class X509Enrollment(Model): + """X509Enrollment. + + :param scope_id: + :type scope_id: str + :param subject: + :type subject: str + :param thumbprint: + :type thumbprint: str + :param registration_id: + :type registration_id: str + :param base64_encoded_x509_certificate: + :type base64_encoded_x509_certificate: str + """ + + _attribute_map = { + 'scope_id': {'key': 'scopeId', 'type': 'str'}, + 'subject': {'key': 'subject', 'type': 'str'}, + 'thumbprint': {'key': 'thumbprint', 'type': 'str'}, + 'registration_id': {'key': 'registrationId', 'type': 'str'}, + 'base64_encoded_x509_certificate': {'key': 'base64EncodedX509Certificate', 'type': 'str'}, + } + + def __init__(self, *, scope_id: str=None, subject: str=None, thumbprint: str=None, registration_id: str=None, base64_encoded_x509_certificate: str=None, **kwargs) -> None: + super(X509Enrollment, self).__init__(**kwargs) + self.scope_id = scope_id + self.subject = subject + self.thumbprint = thumbprint + self.registration_id = registration_id + self.base64_encoded_x509_certificate = base64_encoded_x509_certificate diff --git a/azext_iot/sdk/product/version.py b/azext_iot/sdk/product/version.py new file mode 100644 index 000000000..fc729cd31 --- /dev/null +++ b/azext_iot/sdk/product/version.py @@ -0,0 +1,13 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# +# Code generated by Microsoft (R) AutoRest Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is +# regenerated. +# -------------------------------------------------------------------------- + +VERSION = "2020-05-01-preview" + diff --git a/azext_iot/sdk/service/iot_hub_gateway_service_apis.py b/azext_iot/sdk/service/iot_hub_gateway_service_apis.py deleted file mode 100644 index 0c29ba0ff..000000000 --- a/azext_iot/sdk/service/iot_hub_gateway_service_apis.py +++ /dev/null @@ -1,2738 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator 2.3.33.0 -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.service_client import ServiceClient -from msrest import Serializer, Deserializer -from msrestazure import AzureConfiguration -from msrest.pipeline import ClientRawResponse -from msrestazure.azure_exceptions import CloudError -import uuid -from . import models -from azext_iot.constants import USER_AGENT -from azext_iot.constants import BASE_API_VERSION, PNP_API_VERSION - - -class IotHubGatewayServiceAPIsConfiguration(AzureConfiguration): - """Configuration for IotHubGatewayServiceAPIs - Note that all parameters used to create this instance are saved as instance - attributes. - - :param credentials: Credentials needed for the client to connect to Azure. - :type credentials: :mod:`A msrestazure Credentials - object` - :param str base_url: Service URL - """ - - def __init__( - self, credentials, base_url=None): - - if credentials is None: - raise ValueError("Parameter 'credentials' must not be None.") - if not base_url: - base_url = 'https://localhost' - - super(IotHubGatewayServiceAPIsConfiguration, self).__init__(base_url) - self.add_user_agent('iothubgatewayserviceapi/{}'.format(BASE_API_VERSION)) - self.add_user_agent(USER_AGENT) - - self.credentials = credentials - - -class IotHubGatewayServiceAPIs(object): - """IotHubGatewayServiceAPIs - - :ivar config: Configuration for client. - :vartype config: IotHubGatewayServiceAPIs - - :param credentials: Credentials needed for the client to connect to Azure. - :type credentials: :mod:`A msrestazure Credentials - object` - :param str base_url: Service URL - """ - - def __init__( - self, credentials, base_url=None): - - self.config = IotHubGatewayServiceAPIsConfiguration(credentials, base_url) - self._client = ServiceClient(self.config.credentials, self.config) - - client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} - self.api_version = BASE_API_VERSION - self._serialize = Serializer(client_models) - self._deserialize = Deserializer(client_models) - - - def get_configuration( - self, id, custom_headers=None, raw=False, **operation_config): - """Retrieve a configuration for Iot Hub devices and modules by it - identifier. - - :param id: - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Configuration or ClientRawResponse if raw=true - :rtype: ~service.models.Configuration or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_configuration.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - # @digimaun - deserialize as {object} from Configuration - if response.status_code == 200: - deserialized = self._deserialize('{object}', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_configuration.metadata = {'url': '/configurations/{id}'} - - def create_or_update_configuration( - self, id, configuration, if_match=None, custom_headers=None, raw=False, **operation_config): - """Create or update the configuration for devices or modules of an IoT - hub. An ETag must not be specified for the create operation. An ETag - must be specified for the update operation. Note that configuration Id - and Content cannot be updated by the user. - - :param id: - :type id: str - :param configuration: - :type configuration: ~service.models.Configuration - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Configuration or ClientRawResponse if raw=true - :rtype: ~service.models.Configuration or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.create_or_update_configuration.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(configuration, 'Configuration') - - # Construct and send request - request = self._client.put(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200, 201]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('Configuration', response) - if response.status_code == 201: - deserialized = self._deserialize('Configuration', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - create_or_update_configuration.metadata = {'url': '/configurations/{id}'} - - def delete_configuration( - self, id, if_match=None, custom_headers=None, raw=False, **operation_config): - """Delete the configuration for devices or modules of an IoT hub. This - request requires the If-Match header. The client may specify the ETag - for the device identity on the request in order to compare to the ETag - maintained by the service for the purpose of optimistic concurrency. - The delete operation is performed only if the ETag sent by the client - matches the value maintained by the server, indicating that the device - identity has not been modified since it was retrieved by the client. To - force an unconditional delete, set If-Match to the wildcard character - (*). - - :param id: - :type id: str - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: None or ClientRawResponse if raw=true - :rtype: None or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.delete_configuration.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [204]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - if raw: - client_raw_response = ClientRawResponse(None, response) - return client_raw_response - delete_configuration.metadata = {'url': '/configurations/{id}'} - - def get_configurations( - self, top=None, custom_headers=None, raw=False, **operation_config): - """Get multiple configurations for devices or modules of an IoT Hub. - Returns the specified number of configurations for Iot Hub. Pagination - is not supported. - - :param top: - :type top: int - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: list or ClientRawResponse if raw=true - :rtype: list[~service.models.Configuration] or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_configurations.metadata['url'] - - # Construct parameters - query_parameters = {} - if top is not None: - query_parameters['top'] = self._serialize.query("top", top, 'int') - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('[object]', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_configurations.metadata = {'url': '/configurations'} - - def test_configuration_queries( - self, input, custom_headers=None, raw=False, **operation_config): - """ - - :param input: - :type input: ~service.models.ConfigurationQueriesTestInput - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: ConfigurationQueriesTestResponse or ClientRawResponse if - raw=true - :rtype: ~service.models.ConfigurationQueriesTestResponse or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.test_configuration_queries.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(input, 'ConfigurationQueriesTestInput') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - # @digimaun - ConfigurationQueriesTestResponse - deserialized = self._deserialize('object', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - test_configuration_queries.metadata = {'url': '/configurations/testQueries'} - - def get_device_registry_statistics( - self, custom_headers=None, raw=False, **operation_config): - """Retrieves statistics about device identities in the IoT hub’s identity - registry. - - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: RegistryStatistics or ClientRawResponse if raw=true - :rtype: ~service.models.RegistryStatistics or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_device_registry_statistics.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('RegistryStatistics', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_device_registry_statistics.metadata = {'url': '/statistics/devices'} - - def get_service_statistics( - self, custom_headers=None, raw=False, **operation_config): - """Retrieves service statistics for this IoT hub’s identity registry. - - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: ServiceStatistics or ClientRawResponse if raw=true - :rtype: ~service.models.ServiceStatistics or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_service_statistics.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('ServiceStatistics', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_service_statistics.metadata = {'url': '/statistics/service'} - - def get_devices( - self, top=None, custom_headers=None, raw=False, **operation_config): - """Get the identities of multiple devices from the IoT hub identity - registry. Not recommended. Use the IoT Hub query language to retrieve - device twin and device identity information. See - https://docs.microsoft.com/en-us/rest/api/iothub/deviceapi/querydevices. - - :param top: This parameter when specified, defines the maximum number - of device identities that are returned. Any value outside the range of - 1-1000 is considered to be 1000. - :type top: int - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: list or ClientRawResponse if raw=true - :rtype: list[~service.models.Device] or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_devices.metadata['url'] - - # Construct parameters - query_parameters = {} - if top is not None: - query_parameters['top'] = self._serialize.query("top", top, 'int') - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('[Device]', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_devices.metadata = {'url': '/devices'} - - def bulk_create_or_update_devices( - self, devices, custom_headers=None, raw=False, **operation_config): - """Create, update, or delete the identities of multiple devices from the - IoT hub identity registry. - - Create, update, or delete the identiies of multiple devices from the - IoT hub identity registry. A device identity can be specified only once - in the list. Different operations (create, update, delete) on different - devices are allowed. A maximum of 100 devices can be specified per - invocation. For large scale operations, consider using the import - feature using blob - storage(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities). - - :param devices: - :type devices: list[~service.models.ExportImportDevice] - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: BulkRegistryOperationResult or ClientRawResponse if raw=true - :rtype: ~service.models.BulkRegistryOperationResult or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.bulk_create_or_update_devices.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(devices, '[ExportImportDevice]') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200, 400]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('BulkRegistryOperationResult', response) - if response.status_code == 400: - deserialized = self._deserialize('BulkRegistryOperationResult', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - bulk_create_or_update_devices.metadata = {'url': '/devices'} - - def query_iot_hub( - self, query_specification, custom_headers=None, raw=False, **operation_config): - """Query an IoT hub to retrieve information regarding device twins using a - SQL-like language. - - :param query_specification: - :type query_specification: ~service.models.QuerySpecification - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: QueryResult or ClientRawResponse if raw=true - :rtype: ~service.models.QueryResult or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.query_iot_hub.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(query_specification, 'QuerySpecification') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - # @digimaun - Changed from 'QueryResult' to [object] - if response.status_code == 200: - deserialized = self._deserialize('[object]', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - # @digimaun - Added Custom - continuation = response.headers.get('x-ms-continuation') - return deserialized, continuation - - query_iot_hub.metadata = {'url': '/devices/query'} - - def get_device( - self, id, custom_headers=None, raw=False, **operation_config): - """Retrieve a device from the identity registry of an IoT hub. - - :param id: Device ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Device or ClientRawResponse if raw=true - :rtype: ~service.models.Device or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_device.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - # @digimaun - Change deserialize type to object from Device - deserialized = self._deserialize('object', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_device.metadata = {'url': '/devices/{id}'} - - def create_or_update_device( - self, id, device, if_match=None, custom_headers=None, raw=False, **operation_config): - """Create or update the identity of a device in the identity registry of - an IoT hub. - - Create or update the identity of a device in the identity registry of - an IoT hub. An ETag must not be specified for the create operation. An - ETag must be specified for the update operation. Note that generationId - and deviceId cannot be updated by the user. - - :param id: Device ID. - :type id: str - :param device: - :type device: ~service.models.Device - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Device or ClientRawResponse if raw=true - :rtype: ~service.models.Device or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.create_or_update_device.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(device, 'Device') - - # Construct and send request - request = self._client.put(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('Device', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - create_or_update_device.metadata = {'url': '/devices/{id}'} - - def delete_device( - self, id, if_match=None, custom_headers=None, raw=False, **operation_config): - """Delete the identity of a device from the identity registry of an IoT - hub. - - Delete the identity of a device from the identity registry of an IoT - hub. This request requires the If-Match header. The client may specify - the ETag for the device identity on the request in order to compare to - the ETag maintained by the service for the purpose of optimistic - concurrency. The delete operation is performed only if the ETag sent by - the client matches the value maintained by the server, indicating that - the device identity has not been modified since it was retrieved by the - client. To force an unconditional delete, set If-Match to the wildcard - character (*). - - :param id: Device ID. - :type id: str - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: None or ClientRawResponse if raw=true - :rtype: None or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.delete_device.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [204]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - if raw: - client_raw_response = ClientRawResponse(None, response) - return client_raw_response - delete_device.metadata = {'url': '/devices/{id}'} - - def apply_configuration_on_device( - self, id, content, custom_headers=None, raw=False, **operation_config): - """Applies the provided configuration content to the specified device. - - Applies the provided configuration content to the specified device. - Configuration content must have modules content. - - :param id: Device ID. - :type id: str - :param content: - :type content: ~service.models.ConfigurationContent - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: object or ClientRawResponse if raw=true - :rtype: object or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.apply_configuration_on_device.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(content, 'ConfigurationContent') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200, 204]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('object', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - apply_configuration_on_device.metadata = {'url': '/devices/{id}/applyConfigurationContent'} - - def create_job( - self, job_properties, custom_headers=None, raw=False, **operation_config): - """Create a new job on an IoT hub. - - Create a new job on an IoT hub. See - https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities - for more information. - - :param job_properties: - :type job_properties: ~service.models.JobProperties - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: JobProperties or ClientRawResponse if raw=true - :rtype: ~service.models.JobProperties or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.create_job.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(job_properties, 'JobProperties') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('JobProperties', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - create_job.metadata = {'url': '/jobs/create'} - - def get_jobs( - self, custom_headers=None, raw=False, **operation_config): - """Gets the status of all jobs in an iot hub. - - Gets the status of all jobs in an iot hub. See - https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities - for more information. - - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: list or ClientRawResponse if raw=true - :rtype: list[~service.models.JobProperties] or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_jobs.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('[JobProperties]', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_jobs.metadata = {'url': '/jobs'} - - def get_job( - self, id, custom_headers=None, raw=False, **operation_config): - """Gets the status of job in an iot hub. - - Gets the status of job in an iot hub. See - https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities - for more information. - - :param id: Job ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: JobProperties or ClientRawResponse if raw=true - :rtype: ~service.models.JobProperties or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_job.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('JobProperties', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_job.metadata = {'url': '/jobs/{id}'} - - def cancel_job( - self, id, custom_headers=None, raw=False, **operation_config): - """Cancels job in an IoT hub. - - Cancels job in an IoT hub. See - https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-identity-registry#import-and-export-device-identities - for more information. - - :param id: Job ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: object or ClientRawResponse if raw=true - :rtype: object or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.cancel_job.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200, 204]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('object', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - cancel_job.metadata = {'url': '/jobs/{id}'} - - def purge_command_queue( - self, id, custom_headers=None, raw=False, **operation_config): - """Delete all the pending commands for this device from the IoT hub. - - :param id: Device ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: PurgeMessageQueueResult or ClientRawResponse if raw=true - :rtype: ~service.models.PurgeMessageQueueResult or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.purge_command_queue.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('PurgeMessageQueueResult', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - purge_command_queue.metadata = {'url': '/devices/{id}/commands'} - - def get_twin( - self, id, custom_headers=None, raw=False, **operation_config): - """Get a device twin. - - Get a device twin. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins - for more information. - - :param id: Device ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Twin or ClientRawResponse if raw=true - :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_twin.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - # @digimaun - change device param from 'Twin' to {object} - if response.status_code == 200: - deserialized = self._deserialize('{object}', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_twin.metadata = {'url': '/twins/{id}'} - - def replace_twin( - self, id, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): - """Replaces tags and desired properties of a device twin. - - Replaces a device twin. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins - for more information. - - :param id: Device ID. - :type id: str - :param device_twin_info: Device twin info - :type device_twin_info: ~service.models.Twin - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Twin or ClientRawResponse if raw=true - :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.replace_twin.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - # @digimaun - Change deserialize type to {object} from Twin - body_content = self._serialize.body(device_twin_info, '{object}') - - # Construct and send request - request = self._client.put(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('Twin', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - replace_twin.metadata = {'url': '/twins/{id}'} - - def update_twin( - self, id, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): - """Updates tags and desired properties of a device twin. - - Updates a device twin. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins - for more information. - - :param id: Device ID. - :type id: str - :param device_twin_info: Device twin info - :type device_twin_info: ~service.models.Twin - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Twin or ClientRawResponse if raw=true - :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.update_twin.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(device_twin_info, 'Twin') - - # Construct and send request - request = self._client.patch(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('Twin', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - update_twin.metadata = {'url': '/twins/{id}'} - - def get_module_twin( - self, id, mid, custom_headers=None, raw=False, **operation_config): - """Gets a module twin. - - Gets a module twin. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins - for more information. - - :param id: Device ID. - :type id: str - :param mid: Module ID. - :type mid: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Twin or ClientRawResponse if raw=true - :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_module_twin.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - # @digimaun - Change deserialize type to {object} from Twin - deserialized = self._deserialize('{object}', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_module_twin.metadata = {'url': '/twins/{id}/modules/{mid}'} - - def replace_module_twin( - self, id, mid, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): - """Replaces tags and desired properties of a module twin. - - Replaces a module twin. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins - for more information. - - :param id: Device ID. - :type id: str - :param mid: Module ID. - :type mid: str - :param device_twin_info: Device twin info - :type device_twin_info: ~service.models.Twin - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Twin or ClientRawResponse if raw=true - :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.replace_module_twin.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - # @digimaun - Change deserialize type to {object} from Twin - body_content = self._serialize.body(device_twin_info, '{object}') - - # Construct and send request - request = self._client.put(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('Twin', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - replace_module_twin.metadata = {'url': '/twins/{id}/modules/{mid}'} - - def update_module_twin( - self, id, mid, device_twin_info, if_match=None, custom_headers=None, raw=False, **operation_config): - """Updates tags and desired properties of a module twin. - - Updates a module twin. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-device-twins - for more information. - - :param id: Device ID. - :type id: str - :param mid: Module ID. - :type mid: str - :param device_twin_info: Device twin information - :type device_twin_info: ~service.models.Twin - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Twin or ClientRawResponse if raw=true - :rtype: ~service.models.Twin or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.update_module_twin.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(device_twin_info, 'Twin') - - # Construct and send request - request = self._client.patch(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('Twin', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - update_module_twin.metadata = {'url': '/twins/{id}/modules/{mid}'} - - def send_device_command( - self, custom_headers=None, raw=False, **operation_config): - """ - - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: object or ClientRawResponse if raw=true - :rtype: object or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.send_device_command.metadata['url'] - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('object', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - send_device_command.metadata = {'url': '/messages/deviceBound'} - - def get_job1( - self, id, custom_headers=None, raw=False, **operation_config): - """Retrieve details of an existing job from an IoT hub. - - :param id: Job ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: JobResponse or ClientRawResponse if raw=true - :rtype: ~service.models.JobResponse or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_job1.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - # @digimaun - altered from "JobResponse" to "{object}" - deserialized = self._deserialize('{object}', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_job1.metadata = {'url': '/jobs/v2/{id}'} - - def create_job1( - self, id, job_request, custom_headers=None, raw=False, **operation_config): - """Create a new job on an IoT hub. - - :param id: Job ID. - :type id: str - :param job_request: - :type job_request: ~service.models.JobRequest - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: JobResponse or ClientRawResponse if raw=true - :rtype: ~service.models.JobResponse or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.create_job1.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(job_request, 'JobRequest') - - # Construct and send request - request = self._client.put(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - # @digimaun - changed from "JobResponse" to {object} - if response.status_code == 200: - deserialized = self._deserialize('{object}', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - create_job1.metadata = {'url': '/jobs/v2/{id}'} - - def cancel_job1( - self, id, custom_headers=None, raw=False, **operation_config): - """Cancel an existing job on an IoT hub. - - :param id: Job ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: JobResponse or ClientRawResponse if raw=true - :rtype: ~service.models.JobResponse or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.cancel_job1.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - # @digimaun - changed from "JobResponse" to {object} - if response.status_code == 200: - deserialized = self._deserialize('{object}', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - cancel_job1.metadata = {'url': '/jobs/v2/{id}/cancel'} - - def query_jobs( - self, job_type=None, job_status=None, custom_headers=None, raw=False, **operation_config): - """Query an IoT hub to retrieve information regarding jobs using the IoT - Hub query language. - - :param job_type: Job Type. - :type job_type: str - :param job_status: Job Status. - :type job_status: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: QueryResult or ClientRawResponse if raw=true - :rtype: ~service.models.QueryResult or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.query_jobs.metadata['url'] - - # Construct parameters - query_parameters = {} - if job_type is not None: - query_parameters['jobType'] = self._serialize.query("job_type", job_type, 'str') - if job_status is not None: - query_parameters['jobStatus'] = self._serialize.query("job_status", job_status, 'str') - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - # @digimaun - changed from 'QueryResult' to [object] - if response.status_code == 200: - deserialized = self._deserialize('[object]', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - # @digimaun - Added Custom - continuation = response.headers.get('x-ms-continuation') - return deserialized, continuation - query_jobs.metadata = {'url': '/jobs/v2/query'} - - def get_modules_on_device( - self, id, custom_headers=None, raw=False, **operation_config): - """Retrieve all the module identities on the device. - - :param id: Device ID. - :type id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: list or ClientRawResponse if raw=true - :rtype: list[~service.models.Module] or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_modules_on_device.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('[Module]', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_modules_on_device.metadata = {'url': '/devices/{id}/modules'} - - def get_module( - self, id, mid, custom_headers=None, raw=False, **operation_config): - """Retrieve the specified module identity on the device. - - :param id: Device ID. - :type id: str - :param mid: Module ID. - :type mid: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Module or ClientRawResponse if raw=true - :rtype: ~service.models.Module or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.get_module.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - # @digimaun - Change deserialize type to {object} from Module - deserialized = self._deserialize('{object}', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - get_module.metadata = {'url': '/devices/{id}/modules/{mid}'} - - def create_or_update_module( - self, id, mid, module, if_match=None, custom_headers=None, raw=False, **operation_config): - """Create or update the module identity for device in IoT hub. An ETag - must not be specified for the create operation. An ETag must be - specified for the update operation. Note that moduleId and generation - cannot be updated by the user. - - :param id: Device ID. - :type id: str - :param mid: Module ID. - :type mid: str - :param module: - :type module: ~service.models.Module - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: Module or ClientRawResponse if raw=true - :rtype: ~service.models.Module or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.create_or_update_module.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(module, 'Module') - - # Construct and send request - request = self._client.put(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200, 201]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('Module', response) - if response.status_code == 201: - deserialized = self._deserialize('Module', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - create_or_update_module.metadata = {'url': '/devices/{id}/modules/{mid}'} - - def delete_module( - self, id, mid, if_match=None, custom_headers=None, raw=False, **operation_config): - """Delete the module identity for device of an IoT hub. This request - requires the If-Match header. The client may specify the ETag for the - device identity on the request in order to compare to the ETag - maintained by the service for the purpose of optimistic concurrency. - The delete operation is performed only if the ETag sent by the client - matches the value maintained by the server, indicating that the device - identity has not been modified since it was retrieved by the client. To - force an unconditional delete, set If-Match to the wildcard character - (*). - - :param id: Device ID. - :type id: str - :param mid: Module ID. - :type mid: str - :param if_match: - :type if_match: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: None or ClientRawResponse if raw=true - :rtype: None or ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.delete_module.metadata['url'] - path_format_arguments = { - 'id': self._serialize.url("id", id, 'str'), - 'mid': self._serialize.url("mid", mid, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.delete(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [204]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - if raw: - client_raw_response = ClientRawResponse(None, response) - return client_raw_response - delete_module.metadata = {'url': '/devices/{id}/modules/{mid}'} - - def invoke_device_method( - self, deviceid, direct_method_request, custom_headers=None, raw=False, **operation_config): - """Invoke a direct method on a device. - - Invoke a direct method on a device. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-direct-methods - for more information. - - :param deviceid: - :type deviceid: str - :param direct_method_request: - :type direct_method_request: ~service.models.CloudToDeviceMethod - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: CloudToDeviceMethodResult or ClientRawResponse if raw=true - :rtype: ~service.models.CloudToDeviceMethodResult or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.invoke_device_method.metadata['url'] - # digimaun - change device param from {id} to {deviceId} - - path_format_arguments = { - 'deviceId': self._serialize.url("deviceId", deviceid, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(direct_method_request, 'CloudToDeviceMethod') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('CloudToDeviceMethodResult', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - - # @digimaun - change device param from {id} to {deviceId} - invoke_device_method.metadata = {'url': '/twins/{deviceId}/methods'} - - # @digimaun - invoke_module_method - def invoke_device_method1( - self, device_id, module_id, direct_method_request, custom_headers=None, raw=False, **operation_config): - """Invoke a direct method on a module of a device. - - Invoke a direct method on a device. See - https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-direct-methods - for more information. - - :param device_id: - :type device_id: str - :param module_id: - :type module_id: str - :param direct_method_request: - :type direct_method_request: ~service.models.CloudToDeviceMethod - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: CloudToDeviceMethodResult or ClientRawResponse if raw=true - :rtype: ~service.models.CloudToDeviceMethodResult or - ~msrest.pipeline.ClientRawResponse - :raises: :class:`CloudError` - """ - # Construct URL - url = self.invoke_device_method1.metadata['url'] - path_format_arguments = { - 'deviceId': self._serialize.url("device_id", device_id, 'str'), - 'moduleId': self._serialize.url("module_id", module_id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - query_parameters['api-version'] = self._serialize.query("self.api_version", self.api_version, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(direct_method_request, 'CloudToDeviceMethod') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send( - request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - - if response.status_code == 200: - deserialized = self._deserialize('CloudToDeviceMethodResult', response) - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - return client_raw_response - - return deserialized - - # @digimaun - change device param from {id} to {deviceId} - invoke_device_method1.metadata = {'url': '/twins/{deviceId}/modules/{moduleId}/methods'} - - def get_interfaces( - self, digital_twin_id, custom_headers=None, raw=False, **operation_config): - """Gets the list of interfaces. - - :param digital_twin_id: Digital Twin ID. Format of digitalTwinId is - DeviceId[~ModuleId]. ModuleId is optional. - :type digital_twin_id: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: DigitalTwinInterfaces or ClientRawResponse if raw=true - :rtype: ~service.models.DigitalTwinInterfaces or - ~msrest.pipeline.ClientRawResponse - :raises: class:`CloudError` - """ - # Construct URL - url = self.get_interfaces.metadata['url'] - path_format_arguments = { - 'digitalTwinId': self._serialize.url("digital_twin_id", digital_twin_id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - # @anusapan - Changed self.api_version to PNP_API_VERSION until new version get released - query_parameters['api-version'] = self._serialize.query("self.api_version", PNP_API_VERSION, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - header_dict = {} - - # @anusapan - deserialize as {object} from DigitalTwinInterfaces - if response.status_code == 200: - deserialized = self._deserialize('{object}', response) - header_dict = { - 'ETag': 'str', - } - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - client_raw_response.add_headers(header_dict) - return client_raw_response - - return deserialized - get_interfaces.metadata = {'url': '/digitalTwins/{digitalTwinId}/interfaces'} - - def update_interfaces( - self, digital_twin_id, if_match=None, interfaces=None, custom_headers=None, raw=False, **operation_config): - """Updates desired properties of multiple interfaces. - Example URI: "digitalTwins/{digitalTwinId}/interfaces". - - :param digital_twin_id: Digital Twin ID. Format of digitalTwinId is - DeviceId[~ModuleId]. ModuleId is optional. - :type digital_twin_id: str - :param if_match: - :type if_match: str - :param interfaces: Interface(s) data to patch in the digital twin. - :type interfaces: dict[str, - ~service.models.DigitalTwinInterfacesPatchInterfacesValue] - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: DigitalTwinInterfaces or ClientRawResponse if raw=true - :rtype: ~service.models.DigitalTwinInterfaces or - ~msrest.pipeline.ClientRawResponse - :raises: class:`CloudError` - """ - interfaces_patch_info = models.DigitalTwinInterfacesPatch(interfaces=interfaces) - - # Construct URL - url = self.update_interfaces.metadata['url'] - path_format_arguments = { - 'digitalTwinId': self._serialize.url("digital_twin_id", digital_twin_id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - # @anusapan - Changed self.api_version to PNP_API_VERSION until new version get released - query_parameters['api-version'] = self._serialize.query("self.api_version", PNP_API_VERSION, 'str') - - # Construct headers - header_parameters = {} - header_parameters['Content-Type'] = 'application/json; charset=utf-8' - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - if if_match is not None: - header_parameters['If-Match'] = self._serialize.header("if_match", if_match, 'str') - - # Construct body - body_content = self._serialize.body(interfaces_patch_info, 'DigitalTwinInterfacesPatch') - - # Construct and send request - request = self._client.patch(url, query_parameters) - response = self._client.send(request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - - deserialized = None - header_dict = {} - - # @anusapan - deserialize as {object} from DigitalTwinInterfaces - if response.status_code == 200: - deserialized = self._deserialize('{object}', response) - header_dict = { - 'ETag': 'str', - } - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - client_raw_response.add_headers(header_dict) - return client_raw_response - - return deserialized - update_interfaces.metadata = {'url': '/digitalTwins/{digitalTwinId}/interfaces'} - - def get_interface( - self, digital_twin_id, interface_name, custom_headers=None, raw=False, **operation_config): - """Gets the interface of given interfaceId. - Example URI: "digitalTwins/{digitalTwinId}/interfaces/{interfaceName}". - - :param digital_twin_id: Digital Twin ID. Format of digitalTwinId is - DeviceId[~ModuleId]. ModuleId is optional. - :type digital_twin_id: str - :param interface_name: The interface name. - :type interface_name: str - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: DigitalTwinInterfaces or ClientRawResponse if raw=true - :rtype: ~service.models.DigitalTwinInterfaces or - ~msrest.pipeline.ClientRawResponse - :raises: class:`CloudError` - """ - # Construct URL - url = self.get_interface.metadata['url'] - path_format_arguments = { - 'digitalTwinId': self._serialize.url("digital_twin_id", digital_twin_id, 'str'), - 'interfaceName': self._serialize.url("interface_name", interface_name, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - # @anusapan - Changed self.api_version to PNP_API_VERSION until new version get released - query_parameters['api-version'] = self._serialize.query("self.api_version", PNP_API_VERSION, 'str') - - # Construct headers - header_parameters = {} - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - header_dict = {} - - # @anusapan - deserialize as {object} from DigitalTwinInterfaces - if response.status_code == 200: - deserialized = self._deserialize('{object}', response) - header_dict = { - 'ETag': 'str', - } - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - client_raw_response.add_headers(header_dict) - return client_raw_response - - return deserialized - get_interface.metadata = {'url': '/digitalTwins/{digitalTwinId}/interfaces/{interfaceName}'} - - def get_digital_twin_model( - self, model_id, expand=None, custom_headers=None, raw=False, **operation_config): - """Returns a DigitalTwin model definition for the given id. - If "expand" is present in the query parameters and id is for a device - capability model then it returns - the capability metamodel with expanded interface definitions. - - :param model_id: Model id Ex: - urn:contoso:TemperatureSensor:1 - :type model_id: str - :param expand: Indicates whether to expand the device capability - model's interface definitions inline or not. - This query parameter ONLY applies to Capability model. - :type expand: bool - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: object or ClientRawResponse if raw=true - :rtype: object or ~msrest.pipeline.ClientRawResponse - :raises: class:`CloudError` - """ - # Construct URL - url = self.get_digital_twin_model.metadata['url'] - path_format_arguments = { - 'modelId': self._serialize.url("model_id", model_id, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - if expand is not None: - query_parameters['expand'] = self._serialize.query("expand", expand, 'bool') - # @anusapan - Changed self.api_version to PNP_API_VERSION until new version get released - query_parameters['api-version'] = self._serialize.query("self.api_version", PNP_API_VERSION, 'str') - - # Construct headers - header_parameters = {} - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct and send request - request = self._client.get(url, query_parameters) - response = self._client.send(request, header_parameters, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - header_dict = {} - - if response.status_code == 200: - deserialized = self._deserialize('object', response) - header_dict = { - 'ETag': 'str', - 'x-ms-model-id': 'str', - } - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - client_raw_response.add_headers(header_dict) - return client_raw_response - - return deserialized - get_digital_twin_model.metadata = {'url': '/digitalTwins/models/{modelId}'} - - def invoke_interface_command( - self, digital_twin_id, interface_name, command_name, payload, connect_timeout_in_seconds=None, response_timeout_in_seconds=None, custom_headers=None, raw=False, **operation_config): - """Invoke a digital twin interface command. - - Invoke a digital twin interface command. - - :param digital_twin_id: - :type digital_twin_id: str - :param interface_name: - :type interface_name: str - :param command_name: - :type command_name: str - :param payload: - :type payload: object - :param connect_timeout_in_seconds: Connect timeout in seconds. - :type connect_timeout_in_seconds: int - :param response_timeout_in_seconds: Response timeout in seconds. - :type response_timeout_in_seconds: int - :param dict custom_headers: headers that will be added to the request - :param bool raw: returns the direct response alongside the - deserialized response - :param operation_config: :ref:`Operation configuration - overrides`. - :return: object or ClientRawResponse if raw=true - :rtype: object or ~msrest.pipeline.ClientRawResponse - :raises: class:`CloudError` - """ - # Construct URL - url = self.invoke_interface_command.metadata['url'] - path_format_arguments = { - 'digitalTwinId': self._serialize.url("digital_twin_id", digital_twin_id, 'str'), - 'interfaceName': self._serialize.url("interface_name", interface_name, 'str'), - 'commandName': self._serialize.url("command_name", command_name, 'str') - } - url = self._client.format_url(url, **path_format_arguments) - - # Construct parameters - query_parameters = {} - # @anusapan - Changed self.api_version to PNP_API_VERSION until new version get released - query_parameters['api-version'] = self._serialize.query("self.api_version", PNP_API_VERSION, 'str') - if connect_timeout_in_seconds is not None: - query_parameters['connectTimeoutInSeconds'] = self._serialize.query("connect_timeout_in_seconds", connect_timeout_in_seconds, 'int') - if response_timeout_in_seconds is not None: - query_parameters['responseTimeoutInSeconds'] = self._serialize.query("response_timeout_in_seconds", response_timeout_in_seconds, 'int') - - # Construct headers - header_parameters = {} - if self.config.generate_client_request_id: - header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) - if custom_headers: - header_parameters.update(custom_headers) - if self.config.accept_language is not None: - header_parameters['accept-language'] = self._serialize.header("self.config.accept_language", self.config.accept_language, 'str') - - # Construct body - body_content = self._serialize.body(payload, 'object') - - # Construct and send request - request = self._client.post(url, query_parameters) - response = self._client.send(request, header_parameters, body_content, stream=False, **operation_config) - - if response.status_code not in [200]: - exp = CloudError(response) - exp.request_id = response.headers.get('x-ms-request-id') - raise exp - - deserialized = None - header_dict = {} - - if response.status_code == 200: - deserialized = self._deserialize('object', response) - header_dict = { - 'x-ms-command-statuscode': 'int', - 'x-ms-request-id': 'str', - } - - if raw: - client_raw_response = ClientRawResponse(deserialized, response) - client_raw_response.add_headers(header_dict) - return client_raw_response - - return deserialized - invoke_interface_command.metadata = {'url': '/digitalTwins/{digitalTwinId}/interfaces/{interfaceName}/commands/{commandName}'} diff --git a/azext_iot/sdk/service/models/__init__.py b/azext_iot/sdk/service/models/__init__.py deleted file mode 100644 index aa0335a88..000000000 --- a/azext_iot/sdk/service/models/__init__.py +++ /dev/null @@ -1,92 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from .configuration_metrics import ConfigurationMetrics -from .configuration_content import ConfigurationContent -from .configuration import Configuration -from .configuration_queries_test_input import ConfigurationQueriesTestInput -from .configuration_queries_test_response import ConfigurationQueriesTestResponse -from .registry_statistics import RegistryStatistics -from .service_statistics import ServiceStatistics -from .symmetric_key import SymmetricKey -from .x509_thumbprint import X509Thumbprint -from .authentication_mechanism import AuthenticationMechanism -from .device_capabilities import DeviceCapabilities -from .device import Device -from .property_container import PropertyContainer -from .export_import_device import ExportImportDevice -from .device_registry_operation_error import DeviceRegistryOperationError -from .device_registry_operation_warning import DeviceRegistryOperationWarning -from .bulk_registry_operation_result import BulkRegistryOperationResult -from .query_specification import QuerySpecification -from .query_result import QueryResult -from .job_properties import JobProperties -from .purge_message_queue_result import PurgeMessageQueueResult -from .twin_properties import TwinProperties -from .twin import Twin -from .cloud_to_device_method import CloudToDeviceMethod -from .job_request import JobRequest -from .device_job_statistics import DeviceJobStatistics -from .job_response import JobResponse -from .module import Module -from .cloud_to_device_method_result import CloudToDeviceMethodResult -from .desired import Desired -from .desired_state import DesiredState -from .digital_twin_interfaces import DigitalTwinInterfaces -from .digital_twin_interfaces_patch import DigitalTwinInterfacesPatch -from .digital_twin_interfaces_patch_interfaces_value import DigitalTwinInterfacesPatchInterfacesValue -from .digital_twin_interfaces_patch_interfaces_value_properties_value import DigitalTwinInterfacesPatchInterfacesValuePropertiesValue -from .digital_twin_interfaces_patch_interfaces_value_properties_value_desired import DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired -from .interface import Interface -from .property import Property -from .reported import Reported - -__all__ = [ - 'ConfigurationMetrics', - 'ConfigurationContent', - 'Configuration', - 'ConfigurationQueriesTestInput', - 'ConfigurationQueriesTestResponse', - 'RegistryStatistics', - 'ServiceStatistics', - 'SymmetricKey', - 'X509Thumbprint', - 'AuthenticationMechanism', - 'DeviceCapabilities', - 'Device', - 'PropertyContainer', - 'ExportImportDevice', - 'DeviceRegistryOperationError', - 'DeviceRegistryOperationWarning', - 'BulkRegistryOperationResult', - 'QuerySpecification', - 'QueryResult', - 'JobProperties', - 'PurgeMessageQueueResult', - 'TwinProperties', - 'Twin', - 'CloudToDeviceMethod', - 'JobRequest', - 'DeviceJobStatistics', - 'JobResponse', - 'Module', - 'CloudToDeviceMethodResult', - 'Desired', - 'DigitalTwinInterfaces', - 'DigitalTwinInterfacesPatch', - 'DigitalTwinInterfacesPatchInterfacesValue', - 'DigitalTwinInterfacesPatchInterfacesValuePropertiesValue', - 'DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired', - 'DesiredState', - 'Interface', - 'Property', - 'Reported', -] diff --git a/azext_iot/sdk/service/models/authentication_mechanism.py b/azext_iot/sdk/service/models/authentication_mechanism.py deleted file mode 100644 index 5344b5f17..000000000 --- a/azext_iot/sdk/service/models/authentication_mechanism.py +++ /dev/null @@ -1,37 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class AuthenticationMechanism(Model): - """AuthenticationMechanism. - - :param symmetric_key: - :type symmetric_key: ~service.models.SymmetricKey - :param x509_thumbprint: - :type x509_thumbprint: ~service.models.X509Thumbprint - :param type: Possible values include: 'sas', 'selfSigned', - 'certificateAuthority', 'none' - :type type: str or ~service.models.enum - """ - - _attribute_map = { - 'symmetric_key': {'key': 'symmetricKey', 'type': 'SymmetricKey'}, - 'x509_thumbprint': {'key': 'x509Thumbprint', 'type': 'X509Thumbprint'}, - 'type': {'key': 'type', 'type': 'str'}, - } - - def __init__(self, symmetric_key=None, x509_thumbprint=None, type=None): - super(AuthenticationMechanism, self).__init__() - self.symmetric_key = symmetric_key - self.x509_thumbprint = x509_thumbprint - self.type = type diff --git a/azext_iot/sdk/service/models/bulk_registry_operation_result.py b/azext_iot/sdk/service/models/bulk_registry_operation_result.py deleted file mode 100644 index 1651a0ce7..000000000 --- a/azext_iot/sdk/service/models/bulk_registry_operation_result.py +++ /dev/null @@ -1,38 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class BulkRegistryOperationResult(Model): - """Encapsulates the result of a bulk registry operation. - - :param is_successful: Whether or not the operation was successful. - :type is_successful: bool - :param errors: If the operation was not successful, this contains an array - of DeviceRegistryOperationError objects. - :type errors: list[~service.models.DeviceRegistryOperationError] - :param warnings: If the operation was partially successful, this contains - an array of DeviceRegistryOperationWarning objects. - :type warnings: list[~service.models.DeviceRegistryOperationWarning] - """ - - _attribute_map = { - 'is_successful': {'key': 'isSuccessful', 'type': 'bool'}, - 'errors': {'key': 'errors', 'type': '[DeviceRegistryOperationError]'}, - 'warnings': {'key': 'warnings', 'type': '[DeviceRegistryOperationWarning]'}, - } - - def __init__(self, is_successful=None, errors=None, warnings=None): - super(BulkRegistryOperationResult, self).__init__() - self.is_successful = is_successful - self.errors = errors - self.warnings = warnings diff --git a/azext_iot/sdk/service/models/cloud_to_device_method.py b/azext_iot/sdk/service/models/cloud_to_device_method.py deleted file mode 100644 index 8b74b4b7e..000000000 --- a/azext_iot/sdk/service/models/cloud_to_device_method.py +++ /dev/null @@ -1,50 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class CloudToDeviceMethod(Model): - """Parameters to execute a direct method on the device. - - Variables are only populated by the server, and will be ignored when - sending a request. - - :param method_name: Method to run - :type method_name: str - :ivar payload: Payload - :vartype payload: object - :param response_timeout_in_seconds: - :type response_timeout_in_seconds: int - :param connect_timeout_in_seconds: - :type connect_timeout_in_seconds: int - """ - - # @digimaun - does not serialize payload if payload.readonly = True - # _validation = { - # 'payload': {'readonly': True}, - # } - - # @digimaun - payload altered from '{object}' to 'object' since primitives can be passed in. - _attribute_map = { - 'method_name': {'key': 'methodName', 'type': 'str'}, - 'payload': {'key': 'payload', 'type': 'object'}, - 'response_timeout_in_seconds': {'key': 'responseTimeoutInSeconds', 'type': 'int'}, - 'connect_timeout_in_seconds': {'key': 'connectTimeoutInSeconds', 'type': 'int'}, - } - - # @digimaun - added payload - def __init__(self, method_name=None, response_timeout_in_seconds=None, connect_timeout_in_seconds=None, payload=None): - super(CloudToDeviceMethod, self).__init__() - self.method_name = method_name - self.payload = payload - self.response_timeout_in_seconds = response_timeout_in_seconds - self.connect_timeout_in_seconds = connect_timeout_in_seconds diff --git a/azext_iot/sdk/service/models/cloud_to_device_method_result.py b/azext_iot/sdk/service/models/cloud_to_device_method_result.py deleted file mode 100644 index d6a3969c7..000000000 --- a/azext_iot/sdk/service/models/cloud_to_device_method_result.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class CloudToDeviceMethodResult(Model): - """Represents the Device Method Invocation Results. - - :param status: Method invocation result status. - :type status: int - :param payload: Method invocation result payload. - :type payload: object - """ - - _attribute_map = { - 'status': {'key': 'status', 'type': 'int'}, - 'payload': {'key': 'payload', 'type': 'object'}, - } - - def __init__(self, status=None, payload=None): - super(CloudToDeviceMethodResult, self).__init__() - self.status = status - self.payload = payload diff --git a/azext_iot/sdk/service/models/configuration.py b/azext_iot/sdk/service/models/configuration.py deleted file mode 100644 index 3da1e54c8..000000000 --- a/azext_iot/sdk/service/models/configuration.py +++ /dev/null @@ -1,72 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class Configuration(Model): - """Configuration for IotHub devices and modules. - - :param id: Gets Identifier for the configuration - :type id: str - :param schema_version: Gets Schema version for the configuration - :type schema_version: str - :param labels: Gets or sets labels for the configuration - :type labels: dict[str, str] - :param content: Gets or sets Content for the configuration - :type content: ~service.models.ConfigurationContent - :param target_condition: Gets or sets Target Condition for the - configuration - :type target_condition: str - :param created_time_utc: Gets creation time for the configuration - :type created_time_utc: datetime - :param last_updated_time_utc: Gets last update time for the configuration - :type last_updated_time_utc: datetime - :param priority: Gets or sets Priority for the configuration - :type priority: int - :param system_metrics: System Configuration Metrics - :type system_metrics: ~service.models.ConfigurationMetrics - :param metrics: Custom Configuration Metrics - :type metrics: ~service.models.ConfigurationMetrics - :param etag: Gets or sets configuration's ETag - :type etag: str - """ - - # @digimaun - added missing contentType property - _attribute_map = { - 'id': {'key': 'id', 'type': 'str'}, - 'schema_version': {'key': 'schemaVersion', 'type': 'str'}, - 'labels': {'key': 'labels', 'type': '{str}'}, - 'content': {'key': 'content', 'type': 'ConfigurationContent'}, - 'target_condition': {'key': 'targetCondition', 'type': 'str'}, - 'created_time_utc': {'key': 'createdTimeUtc', 'type': 'iso-8601'}, - 'last_updated_time_utc': {'key': 'lastUpdatedTimeUtc', 'type': 'iso-8601'}, - 'priority': {'key': 'priority', 'type': 'int'}, - 'system_metrics': {'key': 'systemMetrics', 'type': 'ConfigurationMetrics'}, - 'metrics': {'key': 'metrics', 'type': 'ConfigurationMetrics'}, - 'etag': {'key': 'etag', 'type': 'str'}, - 'content_type': {'key': 'contentType', 'type': 'str'}, - } - - def __init__(self, id=None, schema_version=None, labels=None, content=None, target_condition=None, created_time_utc=None, last_updated_time_utc=None, priority=None, system_metrics=None, metrics=None, etag=None, content_type=None): - super(Configuration, self).__init__() - self.id = id - self.schema_version = schema_version - self.labels = labels - self.content = content - self.target_condition = target_condition - self.created_time_utc = created_time_utc - self.last_updated_time_utc = last_updated_time_utc - self.priority = priority - self.system_metrics = system_metrics - self.metrics = metrics - self.etag = etag - self.content_type = content_type diff --git a/azext_iot/sdk/service/models/configuration_content.py b/azext_iot/sdk/service/models/configuration_content.py deleted file mode 100644 index 6445ef122..000000000 --- a/azext_iot/sdk/service/models/configuration_content.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class ConfigurationContent(Model): - """Configuration Content for Devices or Modules on Edge Devices. - - :param device_content: Gets or sets device Configurations - :type device_content: dict[str, object] - :param modules_content: Gets or sets Module Configurations - :type modules_content: dict[str, dict[str, object]] - """ - # @digimaun altered modulesContent type from {{object}} to {object} - # @digimaun added moduleContent - - _attribute_map = { - 'device_content': {'key': 'deviceContent', 'type': '{object}'}, - 'modules_content': {'key': 'modulesContent', 'type': '{object}'}, - 'module_content': {'key': 'moduleContent', 'type': '{object}'} - } - - def __init__(self, device_content=None, modules_content=None, module_content=None): - super(ConfigurationContent, self).__init__() - self.device_content = device_content - self.modules_content = modules_content - self.module_content = module_content diff --git a/azext_iot/sdk/service/models/configuration_queries_test_input.py b/azext_iot/sdk/service/models/configuration_queries_test_input.py deleted file mode 100644 index 978df2228..000000000 --- a/azext_iot/sdk/service/models/configuration_queries_test_input.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class ConfigurationQueriesTestInput(Model): - """ConfigurationQueriesTestInput. - - :param target_condition: - :type target_condition: str - :param custom_metric_queries: - :type custom_metric_queries: dict[str, str] - """ - - _attribute_map = { - 'target_condition': {'key': 'targetCondition', 'type': 'str'}, - 'custom_metric_queries': {'key': 'customMetricQueries', 'type': '{str}'}, - } - - def __init__(self, target_condition=None, custom_metric_queries=None): - super(ConfigurationQueriesTestInput, self).__init__() - self.target_condition = target_condition - self.custom_metric_queries = custom_metric_queries diff --git a/azext_iot/sdk/service/models/configuration_queries_test_response.py b/azext_iot/sdk/service/models/configuration_queries_test_response.py deleted file mode 100644 index 43f1d5ae5..000000000 --- a/azext_iot/sdk/service/models/configuration_queries_test_response.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class ConfigurationQueriesTestResponse(Model): - """ConfigurationQueriesTestResponse. - - :param target_condition_error: - :type target_condition_error: str - :param custom_metric_query_errors: - :type custom_metric_query_errors: dict[str, str] - """ - - _attribute_map = { - 'target_condition_error': {'key': 'targetConditionError', 'type': 'str'}, - 'custom_metric_query_errors': {'key': 'customMetricQueryErrors', 'type': '{str}'}, - } - - def __init__(self, target_condition_error=None, custom_metric_query_errors=None): - super(ConfigurationQueriesTestResponse, self).__init__() - self.target_condition_error = target_condition_error - self.custom_metric_query_errors = custom_metric_query_errors diff --git a/azext_iot/sdk/service/models/desired_state.py b/azext_iot/sdk/service/models/desired_state.py deleted file mode 100644 index 5c29a0445..000000000 --- a/azext_iot/sdk/service/models/desired_state.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DesiredState(Model): - """DesiredState. - - :param code: Status code for the operation. - :type code: int - :param sub_code: Sub status code for the status. - :type sub_code: int - :param version: Version of the desired value received. - :type version: long - :param description: Description of the status. - :type description: str - """ - - _attribute_map = { - 'code': {'key': 'code', 'type': 'int'}, - 'sub_code': {'key': 'subCode', 'type': 'int'}, - 'version': {'key': 'version', 'type': 'long'}, - 'description': {'key': 'description', 'type': 'str'}, - } - - def __init__(self, code=None, sub_code=None, version=None, description=None): - super(DesiredState, self).__init__() - self.code = code - self.sub_code = sub_code - self.version = version - self.description = description diff --git a/azext_iot/sdk/service/models/device.py b/azext_iot/sdk/service/models/device.py deleted file mode 100644 index d6a74d016..000000000 --- a/azext_iot/sdk/service/models/device.py +++ /dev/null @@ -1,77 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class Device(Model): - """Device. - - :param device_id: - :type device_id: str - :param generation_id: - :type generation_id: str - :param etag: - :type etag: str - :param connection_state: Possible values include: 'Disconnected', - 'Connected' - :type connection_state: str or ~service.models.enum - :param status: Possible values include: 'enabled', 'disabled' - :type status: str or ~service.models.enum - :param status_reason: - :type status_reason: str - :param connection_state_updated_time: - :type connection_state_updated_time: datetime - :param status_updated_time: - :type status_updated_time: datetime - :param last_activity_time: - :type last_activity_time: datetime - :param cloud_to_device_message_count: - :type cloud_to_device_message_count: int - :param authentication: - :type authentication: ~service.models.AuthenticationMechanism - :param capabilities: - :type capabilities: ~service.models.DeviceCapabilities - :param device_scope: - :type device_scope: str - """ - - _attribute_map = { - 'device_id': {'key': 'deviceId', 'type': 'str'}, - 'generation_id': {'key': 'generationId', 'type': 'str'}, - 'etag': {'key': 'etag', 'type': 'str'}, - 'connection_state': {'key': 'connectionState', 'type': 'str'}, - 'status': {'key': 'status', 'type': 'str'}, - 'status_reason': {'key': 'statusReason', 'type': 'str'}, - 'connection_state_updated_time': {'key': 'connectionStateUpdatedTime', 'type': 'iso-8601'}, - 'status_updated_time': {'key': 'statusUpdatedTime', 'type': 'iso-8601'}, - 'last_activity_time': {'key': 'lastActivityTime', 'type': 'iso-8601'}, - 'cloud_to_device_message_count': {'key': 'cloudToDeviceMessageCount', 'type': 'int'}, - 'authentication': {'key': 'authentication', 'type': 'AuthenticationMechanism'}, - 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, - 'device_scope': {'key': 'deviceScope', 'type': 'str'}, - } - - def __init__(self, device_id=None, generation_id=None, etag=None, connection_state=None, status=None, status_reason=None, connection_state_updated_time=None, status_updated_time=None, last_activity_time=None, cloud_to_device_message_count=None, authentication=None, capabilities=None, device_scope=None): - super(Device, self).__init__() - self.device_id = device_id - self.generation_id = generation_id - self.etag = etag - self.connection_state = connection_state - self.status = status - self.status_reason = status_reason - self.connection_state_updated_time = connection_state_updated_time - self.status_updated_time = status_updated_time - self.last_activity_time = last_activity_time - self.cloud_to_device_message_count = cloud_to_device_message_count - self.authentication = authentication - self.capabilities = capabilities - self.device_scope = device_scope diff --git a/azext_iot/sdk/service/models/device_job_statistics.py b/azext_iot/sdk/service/models/device_job_statistics.py deleted file mode 100644 index 5bb46680d..000000000 --- a/azext_iot/sdk/service/models/device_job_statistics.py +++ /dev/null @@ -1,44 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DeviceJobStatistics(Model): - """The job counts, e.g., number of failed/succeeded devices. - - :param device_count: Number of devices in the job - :type device_count: int - :param failed_count: The number of failed jobs - :type failed_count: int - :param succeeded_count: The number of Successed jobs - :type succeeded_count: int - :param running_count: The number of running jobs - :type running_count: int - :param pending_count: The number of pending (scheduled) jobs - :type pending_count: int - """ - - _attribute_map = { - 'device_count': {'key': 'deviceCount', 'type': 'int'}, - 'failed_count': {'key': 'failedCount', 'type': 'int'}, - 'succeeded_count': {'key': 'succeededCount', 'type': 'int'}, - 'running_count': {'key': 'runningCount', 'type': 'int'}, - 'pending_count': {'key': 'pendingCount', 'type': 'int'}, - } - - def __init__(self, device_count=None, failed_count=None, succeeded_count=None, running_count=None, pending_count=None): - super(DeviceJobStatistics, self).__init__() - self.device_count = device_count - self.failed_count = failed_count - self.succeeded_count = succeeded_count - self.running_count = running_count - self.pending_count = pending_count diff --git a/azext_iot/sdk/service/models/device_registry_operation_error.py b/azext_iot/sdk/service/models/device_registry_operation_error.py deleted file mode 100644 index af658192e..000000000 --- a/azext_iot/sdk/service/models/device_registry_operation_error.py +++ /dev/null @@ -1,119 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DeviceRegistryOperationError(Model): - """Encapsulates device registry operation error details. - - :param device_id: The ID of the device that indicated the error. - :type device_id: str - :param error_code: ErrorCode associated with the error. Possible values - include: 'InvalidErrorCode', 'GenericBadRequest', - 'InvalidProtocolVersion', 'DeviceInvalidResultCount', 'InvalidOperation', - 'ArgumentInvalid', 'ArgumentNull', 'IotHubFormatError', - 'DeviceStorageEntitySerializationError', 'BlobContainerValidationError', - 'ImportWarningExistsError', 'InvalidSchemaVersion', - 'DeviceDefinedMultipleTimes', 'DeserializationError', - 'BulkRegistryOperationFailure', 'DefaultStorageEndpointNotConfigured', - 'InvalidFileUploadCorrelationId', 'ExpiredFileUploadCorrelationId', - 'InvalidStorageEndpoint', 'InvalidMessagingEndpoint', - 'InvalidFileUploadCompletionStatus', 'InvalidStorageEndpointOrBlob', - 'RequestCanceled', 'InvalidStorageEndpointProperty', 'EtagDoesNotMatch', - 'RequestTimedOut', 'UnsupportedOperationOnReplica', 'NullMessage', - 'ConnectionForcefullyClosedOnNewConnection', 'InvalidDeviceScope', - 'InvalidRouteTestInput', 'InvalidSourceOnRoute', 'RoutingNotEnabled', - 'InvalidEndorsementKey', 'InvalidRegistrationId', 'InvalidStorageRootKey', - 'InvalidEnrollmentGroupId', 'TooManyEnrollments', - 'RegistrationIdDefinedMultipleTimes', 'CustomAllocationFailed', - 'CustomAllocationIotHubNotSpecified', - 'CustomAllocationUnauthorizedAccess', 'CannotRegisterModuleToModule', - 'TenantHubRoutingNotEnabled', 'InvalidConfigurationTargetCondition', - 'InvalidConfigurationContent', - 'CannotModifyImmutableConfigurationContent', - 'InvalidConfigurationCustomMetricsQuery', 'InvalidPnPInterfaceDefinition', - 'InvalidPnPDesiredProperties', 'InvalidPnPReportedProperties', - 'InvalidPnPWritableReportedProperties', 'GenericUnauthorized', - 'IotHubNotFound', 'IotHubUnauthorizedAccess', 'IotHubUnauthorized', - 'ElasticPoolNotFound', 'SystemModuleModifyUnauthorizedAccess', - 'GenericForbidden', 'IotHubSuspended', 'IotHubQuotaExceeded', - 'JobQuotaExceeded', 'DeviceMaximumQueueDepthExceeded', - 'IotHubMaxCbsTokenExceeded', 'DeviceMaximumActiveFileUploadLimitExceeded', - 'DeviceMaximumQueueSizeExceeded', 'RoutingEndpointResponseForbidden', - 'InvalidMessageExpiryTime', 'OperationNotAvailableInCurrentTier', - 'DeviceModelMaxPropertiesExceeded', - 'DeviceModelMaxIndexablePropertiesExceeded', 'IotDpsSuspended', - 'IotDpsSuspending', 'GenericNotFound', 'DeviceNotFound', 'JobNotFound', - 'QuotaMetricNotFound', 'SystemPropertyNotFound', 'AmqpAddressNotFound', - 'RoutingEndpointResponseNotFound', 'CertificateNotFound', - 'ElasticPoolTenantHubNotFound', 'ModuleNotFound', - 'AzureTableStoreNotFound', 'IotHubFailingOver', 'FeatureNotSupported', - 'QueryStoreClusterNotFound', 'DeviceNotOnline', - 'DeviceConnectionClosedRemotely', 'EnrollmentNotFound', - 'DeviceRegistrationNotFound', 'AsyncOperationNotFound', - 'EnrollmentGroupNotFound', 'ConfigurationNotFound', 'GroupNotFound', - 'GenericMethodNotAllowed', 'OperationNotAllowedInCurrentState', - 'ImportDevicesNotSupported', 'BulkAddDevicesNotSupported', - 'GenericConflict', 'DeviceAlreadyExists', 'LinkCreationConflict', - 'CallbackSubscriptionConflict', 'ModelAlreadyExists', 'DeviceLocked', - 'DeviceJobAlreadyExists', 'JobAlreadyExists', 'EnrollmentConflict', - 'EnrollmentGroupConflict', 'RegistrationStatusConflict', - 'ModuleAlreadyExistsOnDevice', 'ConfigurationAlreadyExists', - 'ApplyConfigurationAlreadyInProgressOnDevice', - 'GenericPreconditionFailed', 'PreconditionFailed', - 'DeviceMessageLockLost', 'JobRunPreconditionFailed', - 'InflightMessagesInLink', 'GenericRequestEntityTooLarge', - 'MessageTooLarge', 'TooManyDevices', 'TooManyModulesOnDevice', - 'ConfigurationCountLimitExceeded', 'GenericUnsupportedMediaType', - 'IncompatibleDataType', 'GenericTooManyRequests', 'ThrottlingException', - 'ThrottleBacklogLimitExceeded', 'ThrottlingBacklogTimeout', - 'ThrottlingMaxActiveJobCountExceeded', 'GenericServerError', - 'ServerError', 'JobCancelled', 'StatisticsRetrievalError', - 'ConnectionForcefullyClosed', 'InvalidBlobState', 'BackupTimedOut', - 'AzureStorageTimeout', 'GenericTimeout', 'InvalidThrottleParameter', - 'EventHubLinkAlreadyClosed', 'ReliableBlobStoreError', - 'RetryAttemptsExhausted', 'AzureTableStoreError', - 'CheckpointStoreNotFound', 'DocumentDbInvalidReturnValue', - 'ReliableDocDbStoreStoreError', 'ReliableBlobStoreTimeoutError', - 'ConfigReadFailed', 'InvalidContainerReceiveLink', - 'InvalidPartitionEpoch', 'RestoreTimedOut', 'StreamReservationFailure', - 'UnexpectedPropertyValue', 'OrchestrationOperationFailed', - 'GenericBadGateway', 'InvalidResponseWhileProxying', - 'GenericServiceUnavailable', 'ServiceUnavailable', 'PartitionNotFound', - 'IotHubActivationFailed', 'ServerBusy', 'IotHubRestoring', - 'ReceiveLinkOpensThrottled', 'ConnectionUnavailable', 'DeviceUnavailable', - 'ConfigurationNotAvailable', 'GroupNotAvailable', 'GenericGatewayTimeout', - 'GatewayTimeout' - :type error_code: str or ~service.models.enum - :param error_status: Additional details associated with the error. - :type error_status: str - :param module_id: - :type module_id: str - :param operation: - :type operation: str - """ - - _attribute_map = { - 'device_id': {'key': 'deviceId', 'type': 'str'}, - 'error_code': {'key': 'errorCode', 'type': 'str'}, - 'error_status': {'key': 'errorStatus', 'type': 'str'}, - 'module_id': {'key': 'moduleId', 'type': 'str'}, - 'operation': {'key': 'operation', 'type': 'str'}, - } - - def __init__(self, device_id=None, error_code=None, error_status=None, module_id=None, operation=None): - super(DeviceRegistryOperationError, self).__init__() - self.device_id = device_id - self.error_code = error_code - self.error_status = error_status - self.module_id = module_id - self.operation = operation diff --git a/azext_iot/sdk/service/models/device_registry_operation_warning.py b/azext_iot/sdk/service/models/device_registry_operation_warning.py deleted file mode 100644 index c3c0d47ee..000000000 --- a/azext_iot/sdk/service/models/device_registry_operation_warning.py +++ /dev/null @@ -1,37 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DeviceRegistryOperationWarning(Model): - """Encapsulates device registry operation error details. - - :param device_id: The ID of the device that indicated the warning. - :type device_id: str - :param warning_code: Possible values include: - 'DeviceRegisteredWithoutTwin' - :type warning_code: str or ~service.models.enum - :param warning_status: Additional details associated with the warning. - :type warning_status: str - """ - - _attribute_map = { - 'device_id': {'key': 'deviceId', 'type': 'str'}, - 'warning_code': {'key': 'warningCode', 'type': 'str'}, - 'warning_status': {'key': 'warningStatus', 'type': 'str'}, - } - - def __init__(self, device_id=None, warning_code=None, warning_status=None): - super(DeviceRegistryOperationWarning, self).__init__() - self.device_id = device_id - self.warning_code = warning_code - self.warning_status = warning_status diff --git a/azext_iot/sdk/service/models/digital_twin_interfaces.py b/azext_iot/sdk/service/models/digital_twin_interfaces.py deleted file mode 100644 index cac98248a..000000000 --- a/azext_iot/sdk/service/models/digital_twin_interfaces.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DigitalTwinInterfaces(Model): - """DigitalTwinInterfaces. - - :param interfaces: Interface(s) data on the digital twin. - :type interfaces: dict[str, ~service.models.Interface] - :param version: Version of digital twin. - :type version: long - """ - - _attribute_map = { - 'interfaces': {'key': 'interfaces', 'type': '{Interface}'}, - 'version': {'key': 'version', 'type': 'long'}, - } - - def __init__(self, interfaces=None, version=None): - super(DigitalTwinInterfaces, self).__init__() - self.interfaces = interfaces - self.version = version diff --git a/azext_iot/sdk/service/models/digital_twin_interfaces_patch.py b/azext_iot/sdk/service/models/digital_twin_interfaces_patch.py deleted file mode 100644 index 512ff1729..000000000 --- a/azext_iot/sdk/service/models/digital_twin_interfaces_patch.py +++ /dev/null @@ -1,28 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DigitalTwinInterfacesPatch(Model): - """DigitalTwinInterfacesPatch. - - :param interfaces: Interface(s) data to patch in the digital twin. - :type interfaces: dict[str, ~service.models.DigitalTwinInterfacesPatchInterfacesValue] - """ - - _attribute_map = { - 'interfaces': {'key': 'interfaces', 'type': '{DigitalTwinInterfacesPatchInterfacesValue}'}, - } - - def __init__(self, interfaces=None): - super(DigitalTwinInterfacesPatch, self).__init__() - self.interfaces = interfaces diff --git a/azext_iot/sdk/service/models/digital_twin_interfaces_patch_interfaces_value.py b/azext_iot/sdk/service/models/digital_twin_interfaces_patch_interfaces_value.py deleted file mode 100644 index 1ce39941b..000000000 --- a/azext_iot/sdk/service/models/digital_twin_interfaces_patch_interfaces_value.py +++ /dev/null @@ -1,28 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DigitalTwinInterfacesPatchInterfacesValue(Model): - """DigitalTwinInterfacesPatchInterfacesValue. - - :param properties: List of properties to update in an interface. - :type properties: dict[str, ~service.models.DigitalTwinInterfacesPatchInterfacesValuePropertiesValue] - """ - - _attribute_map = { - 'properties': {'key': 'properties', 'type': '{DigitalTwinInterfacesPatchInterfacesValuePropertiesValue}'}, - } - - def __init__(self, properties=None): - super(DigitalTwinInterfacesPatchInterfacesValue, self).__init__() - self.properties = properties diff --git a/azext_iot/sdk/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value.py b/azext_iot/sdk/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value.py deleted file mode 100644 index c60a512bd..000000000 --- a/azext_iot/sdk/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value.py +++ /dev/null @@ -1,28 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DigitalTwinInterfacesPatchInterfacesValuePropertiesValue(Model): - """DigitalTwinInterfacesPatchInterfacesValuePropertiesValue. - - :param desired: - :type desired: ~service.models.DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired - """ - - _attribute_map = { - 'desired': {'key': 'desired', 'type': 'DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired'}, - } - - def __init__(self, desired=None): - super(DigitalTwinInterfacesPatchInterfacesValuePropertiesValue, self).__init__() - self.desired = desired diff --git a/azext_iot/sdk/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_desired.py b/azext_iot/sdk/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_desired.py deleted file mode 100644 index 1e90b4098..000000000 --- a/azext_iot/sdk/service/models/digital_twin_interfaces_patch_interfaces_value_properties_value_desired.py +++ /dev/null @@ -1,29 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired(Model): - """DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired. - - :param value: The desired value of the interface property to set in a - digitalTwin. - :type value: object - """ - - _attribute_map = { - 'value': {'key': 'value', 'type': 'object'}, - } - - def __init__(self, value=None): - super(DigitalTwinInterfacesPatchInterfacesValuePropertiesValueDesired, self).__init__() - self.value = value diff --git a/azext_iot/sdk/service/models/export_import_device.py b/azext_iot/sdk/service/models/export_import_device.py deleted file mode 100644 index afa4cb693..000000000 --- a/azext_iot/sdk/service/models/export_import_device.py +++ /dev/null @@ -1,70 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class ExportImportDevice(Model): - """ExportImportDevice. - - :param id: - :type id: str - :param module_id: - :type module_id: str - :param e_tag: - :type e_tag: str - :param import_mode: Possible values include: 'createOrUpdate', 'create', - 'update', 'updateIfMatchETag', 'createOrUpdateIfMatchETag', 'delete', - 'deleteIfMatchETag', 'updateTwin', 'updateTwinIfMatchETag' - :type import_mode: str or ~service.models.enum - :param status: Possible values include: 'enabled', 'disabled' - :type status: str or ~service.models.enum - :param status_reason: - :type status_reason: str - :param authentication: - :type authentication: ~service.models.AuthenticationMechanism - :param twin_etag: - :type twin_etag: str - :param tags: - :type tags: dict[str, object] - :param properties: - :type properties: ~service.models.PropertyContainer - :param capabilities: - :type capabilities: ~service.models.DeviceCapabilities - """ - - _attribute_map = { - 'id': {'key': 'id', 'type': 'str'}, - 'module_id': {'key': 'moduleId', 'type': 'str'}, - 'e_tag': {'key': 'eTag', 'type': 'str'}, - 'import_mode': {'key': 'importMode', 'type': 'str'}, - 'status': {'key': 'status', 'type': 'str'}, - 'status_reason': {'key': 'statusReason', 'type': 'str'}, - 'authentication': {'key': 'authentication', 'type': 'AuthenticationMechanism'}, - 'twin_etag': {'key': 'twinETag', 'type': 'str'}, - 'tags': {'key': 'tags', 'type': '{object}'}, - 'properties': {'key': 'properties', 'type': 'PropertyContainer'}, - 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, - } - - def __init__(self, id=None, module_id=None, e_tag=None, import_mode=None, status=None, status_reason=None, authentication=None, twin_etag=None, tags=None, properties=None, capabilities=None): - super(ExportImportDevice, self).__init__() - self.id = id - self.module_id = module_id - self.e_tag = e_tag - self.import_mode = import_mode - self.status = status - self.status_reason = status_reason - self.authentication = authentication - self.twin_etag = twin_etag - self.tags = tags - self.properties = properties - self.capabilities = capabilities diff --git a/azext_iot/sdk/service/models/interface.py b/azext_iot/sdk/service/models/interface.py deleted file mode 100644 index b81ccd6cc..000000000 --- a/azext_iot/sdk/service/models/interface.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class Interface(Model): - """Interface. - - :param name: Full name of digital twin interface. - :type name: str - :param properties: List of all properties in an interface. - :type properties: dict[str, ~service.models.Property] - """ - - _attribute_map = { - 'name': {'key': 'name', 'type': 'str'}, - 'properties': {'key': 'properties', 'type': '{Property}'}, - } - - def __init__(self, name=None, properties=None): - super(Interface, self).__init__() - self.name = name - self.properties = properties diff --git a/azext_iot/sdk/service/models/job_properties.py b/azext_iot/sdk/service/models/job_properties.py deleted file mode 100644 index 26b693563..000000000 --- a/azext_iot/sdk/service/models/job_properties.py +++ /dev/null @@ -1,89 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class JobProperties(Model): - """JobProperties. - - :param job_id: System generated. Ignored at creation. - :type job_id: str - :param start_time_utc: System generated. Ignored at creation. - :type start_time_utc: datetime - :param end_time_utc: System generated. Ignored at creation. - Represents the time the job stopped processing. - :type end_time_utc: datetime - :param type: Required. - The type of job to execute. Possible values include: 'unknown', 'export', - 'import', 'backup', 'readDeviceProperties', 'writeDeviceProperties', - 'updateDeviceConfiguration', 'rebootDevice', 'factoryResetDevice', - 'firmwareUpdate', 'scheduleDeviceMethod', 'scheduleUpdateTwin', - 'restoreFromBackup', 'failoverDataCopy' - :type type: str or ~service.models.enum - :param status: System generated. Ignored at creation. Possible values - include: 'unknown', 'enqueued', 'running', 'completed', 'failed', - 'cancelled', 'scheduled', 'queued' - :type status: str or ~service.models.enum - :param progress: System generated. Ignored at creation. - Represents the percentage of completion. - :type progress: int - :param input_blob_container_uri: URI containing SAS token to a blob - container that contains registry data to sync. - :type input_blob_container_uri: str - :param input_blob_name: The blob name to be used when importing from the - provided input blob container. - :type input_blob_name: str - :param output_blob_container_uri: URI containing SAS token to a blob - container. This is used to output the status of the job and the results. - :type output_blob_container_uri: str - :param output_blob_name: The name of the blob that will be created in the - provided output blob container. This blob will contain - the exported device registry information for the IoT Hub. - :type output_blob_name: str - :param exclude_keys_in_export: Optional for export jobs; ignored for other - jobs. Default: false. If false, authorization keys are included - in export output. Keys are exported as null otherwise. - :type exclude_keys_in_export: bool - :param failure_reason: System genereated. Ignored at creation. - If status == failure, this represents a string containing the reason. - :type failure_reason: str - """ - - _attribute_map = { - 'job_id': {'key': 'jobId', 'type': 'str'}, - 'start_time_utc': {'key': 'startTimeUtc', 'type': 'iso-8601'}, - 'end_time_utc': {'key': 'endTimeUtc', 'type': 'iso-8601'}, - 'type': {'key': 'type', 'type': 'str'}, - 'status': {'key': 'status', 'type': 'str'}, - 'progress': {'key': 'progress', 'type': 'int'}, - 'input_blob_container_uri': {'key': 'inputBlobContainerUri', 'type': 'str'}, - 'input_blob_name': {'key': 'inputBlobName', 'type': 'str'}, - 'output_blob_container_uri': {'key': 'outputBlobContainerUri', 'type': 'str'}, - 'output_blob_name': {'key': 'outputBlobName', 'type': 'str'}, - 'exclude_keys_in_export': {'key': 'excludeKeysInExport', 'type': 'bool'}, - 'failure_reason': {'key': 'failureReason', 'type': 'str'}, - } - - def __init__(self, job_id=None, start_time_utc=None, end_time_utc=None, type=None, status=None, progress=None, input_blob_container_uri=None, input_blob_name=None, output_blob_container_uri=None, output_blob_name=None, exclude_keys_in_export=None, failure_reason=None): - super(JobProperties, self).__init__() - self.job_id = job_id - self.start_time_utc = start_time_utc - self.end_time_utc = end_time_utc - self.type = type - self.status = status - self.progress = progress - self.input_blob_container_uri = input_blob_container_uri - self.input_blob_name = input_blob_name - self.output_blob_container_uri = output_blob_container_uri - self.output_blob_name = output_blob_name - self.exclude_keys_in_export = exclude_keys_in_export - self.failure_reason = failure_reason diff --git a/azext_iot/sdk/service/models/job_request.py b/azext_iot/sdk/service/models/job_request.py deleted file mode 100644 index 8d091c2ec..000000000 --- a/azext_iot/sdk/service/models/job_request.py +++ /dev/null @@ -1,62 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class JobRequest(Model): - """JobRequest. - - :param job_id: Job identifier - :type job_id: str - :param type: Required. - The type of job to execute. Possible values include: 'unknown', 'export', - 'import', 'backup', 'readDeviceProperties', 'writeDeviceProperties', - 'updateDeviceConfiguration', 'rebootDevice', 'factoryResetDevice', - 'firmwareUpdate', 'scheduleDeviceMethod', 'scheduleUpdateTwin', - 'restoreFromBackup', 'failoverDataCopy' - :type type: str or ~service.models.enum - :param cloud_to_device_method: Required if jobType is cloudToDeviceMethod. - The method type and parameters. - :type cloud_to_device_method: ~service.models.CloudToDeviceMethod - :param update_twin: - :type update_twin: ~service.models.Twin - :param query_condition: Required if jobType is updateTwin or - cloudToDeviceMethod. - Condition for device query to get devices to execute the job on - :type query_condition: str - :param start_time: ISO 8601 date time to start the job - :type start_time: datetime - :param max_execution_time_in_seconds: Max execution time in secounds (ttl - duration) - :type max_execution_time_in_seconds: long - """ - - # @digimaun - altered update_twin type from Twin to {object} - _attribute_map = { - 'job_id': {'key': 'jobId', 'type': 'str'}, - 'type': {'key': 'type', 'type': 'str'}, - 'cloud_to_device_method': {'key': 'cloudToDeviceMethod', 'type': 'CloudToDeviceMethod'}, - 'update_twin': {'key': 'updateTwin', 'type': '{object}'}, - 'query_condition': {'key': 'queryCondition', 'type': 'str'}, - 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, - 'max_execution_time_in_seconds': {'key': 'maxExecutionTimeInSeconds', 'type': 'long'}, - } - - def __init__(self, job_id=None, type=None, cloud_to_device_method=None, update_twin=None, query_condition=None, start_time=None, max_execution_time_in_seconds=None): - super(JobRequest, self).__init__() - self.job_id = job_id - self.type = type - self.cloud_to_device_method = cloud_to_device_method - self.update_twin = update_twin - self.query_condition = query_condition - self.start_time = start_time - self.max_execution_time_in_seconds = max_execution_time_in_seconds diff --git a/azext_iot/sdk/service/models/job_response.py b/azext_iot/sdk/service/models/job_response.py deleted file mode 100644 index 68852dfc8..000000000 --- a/azext_iot/sdk/service/models/job_response.py +++ /dev/null @@ -1,87 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class JobResponse(Model): - """JobResponse. - - :param job_id: System generated. Ignored at creation. - :type job_id: str - :param query_condition: Device query condition. - :type query_condition: str - :param created_time: System generated. Ignored at creation. - :type created_time: datetime - :param start_time: Scheduled job start time in UTC. - :type start_time: datetime - :param end_time: System generated. Ignored at creation. - Represents the time the job stopped processing. - :type end_time: datetime - :param max_execution_time_in_seconds: Max execution time in secounds (ttl - duration) - :type max_execution_time_in_seconds: long - :param type: Required. - The type of job to execute. Possible values include: 'unknown', 'export', - 'import', 'backup', 'readDeviceProperties', 'writeDeviceProperties', - 'updateDeviceConfiguration', 'rebootDevice', 'factoryResetDevice', - 'firmwareUpdate', 'scheduleDeviceMethod', 'scheduleUpdateTwin', - 'restoreFromBackup', 'failoverDataCopy' - :type type: str or ~service.models.enum - :param cloud_to_device_method: Required if jobType is cloudToDeviceMethod. - The method type and parameters. - :type cloud_to_device_method: ~service.models.CloudToDeviceMethod - :param update_twin: - :type update_twin: ~service.models.Twin - :param status: System generated. Ignored at creation. Possible values - include: 'unknown', 'enqueued', 'running', 'completed', 'failed', - 'cancelled', 'scheduled', 'queued' - :type status: str or ~service.models.enum - :param failure_reason: System generated. Ignored at creation. - If status == failure, this represents a string containing the reason. - :type failure_reason: str - :param status_message: Status message for the job - :type status_message: str - :param device_job_statistics: Job details - :type device_job_statistics: ~service.models.DeviceJobStatistics - """ - - _attribute_map = { - 'job_id': {'key': 'jobId', 'type': 'str'}, - 'query_condition': {'key': 'queryCondition', 'type': 'str'}, - 'created_time': {'key': 'createdTime', 'type': 'iso-8601'}, - 'start_time': {'key': 'startTime', 'type': 'iso-8601'}, - 'end_time': {'key': 'endTime', 'type': 'iso-8601'}, - 'max_execution_time_in_seconds': {'key': 'maxExecutionTimeInSeconds', 'type': 'long'}, - 'type': {'key': 'type', 'type': 'str'}, - 'cloud_to_device_method': {'key': 'cloudToDeviceMethod', 'type': 'CloudToDeviceMethod'}, - 'update_twin': {'key': 'updateTwin', 'type': 'Twin'}, - 'status': {'key': 'status', 'type': 'str'}, - 'failure_reason': {'key': 'failureReason', 'type': 'str'}, - 'status_message': {'key': 'statusMessage', 'type': 'str'}, - 'device_job_statistics': {'key': 'deviceJobStatistics', 'type': 'DeviceJobStatistics'}, - } - - def __init__(self, job_id=None, query_condition=None, created_time=None, start_time=None, end_time=None, max_execution_time_in_seconds=None, type=None, cloud_to_device_method=None, update_twin=None, status=None, failure_reason=None, status_message=None, device_job_statistics=None): - super(JobResponse, self).__init__() - self.job_id = job_id - self.query_condition = query_condition - self.created_time = created_time - self.start_time = start_time - self.end_time = end_time - self.max_execution_time_in_seconds = max_execution_time_in_seconds - self.type = type - self.cloud_to_device_method = cloud_to_device_method - self.update_twin = update_twin - self.status = status - self.failure_reason = failure_reason - self.status_message = status_message - self.device_job_statistics = device_job_statistics diff --git a/azext_iot/sdk/service/models/module.py b/azext_iot/sdk/service/models/module.py deleted file mode 100644 index a32a455c5..000000000 --- a/azext_iot/sdk/service/models/module.py +++ /dev/null @@ -1,65 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class Module(Model): - """Module identity on a device. - - :param module_id: - :type module_id: str - :param managed_by: - :type managed_by: str - :param device_id: - :type device_id: str - :param generation_id: - :type generation_id: str - :param etag: - :type etag: str - :param connection_state: Possible values include: 'Disconnected', - 'Connected' - :type connection_state: str or ~service.models.enum - :param connection_state_updated_time: - :type connection_state_updated_time: datetime - :param last_activity_time: - :type last_activity_time: datetime - :param cloud_to_device_message_count: - :type cloud_to_device_message_count: int - :param authentication: - :type authentication: ~service.models.AuthenticationMechanism - """ - - _attribute_map = { - 'module_id': {'key': 'moduleId', 'type': 'str'}, - 'managed_by': {'key': 'managedBy', 'type': 'str'}, - 'device_id': {'key': 'deviceId', 'type': 'str'}, - 'generation_id': {'key': 'generationId', 'type': 'str'}, - 'etag': {'key': 'etag', 'type': 'str'}, - 'connection_state': {'key': 'connectionState', 'type': 'str'}, - 'connection_state_updated_time': {'key': 'connectionStateUpdatedTime', 'type': 'iso-8601'}, - 'last_activity_time': {'key': 'lastActivityTime', 'type': 'iso-8601'}, - 'cloud_to_device_message_count': {'key': 'cloudToDeviceMessageCount', 'type': 'int'}, - 'authentication': {'key': 'authentication', 'type': 'AuthenticationMechanism'}, - } - - def __init__(self, module_id=None, managed_by=None, device_id=None, generation_id=None, etag=None, connection_state=None, connection_state_updated_time=None, last_activity_time=None, cloud_to_device_message_count=None, authentication=None): - super(Module, self).__init__() - self.module_id = module_id - self.managed_by = managed_by - self.device_id = device_id - self.generation_id = generation_id - self.etag = etag - self.connection_state = connection_state - self.connection_state_updated_time = connection_state_updated_time - self.last_activity_time = last_activity_time - self.cloud_to_device_message_count = cloud_to_device_message_count - self.authentication = authentication diff --git a/azext_iot/sdk/service/models/property.py b/azext_iot/sdk/service/models/property.py deleted file mode 100644 index c950c11db..000000000 --- a/azext_iot/sdk/service/models/property.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class Property(Model): - """Property. - - :param reported: - :type reported: ~service.models.Reported - :param desired: - :type desired: ~service.models.Desired - """ - - _attribute_map = { - 'reported': {'key': 'reported', 'type': 'Reported'}, - 'desired': {'key': 'desired', 'type': 'Desired'}, - } - - def __init__(self, reported=None, desired=None): - super(Property, self).__init__() - self.reported = reported - self.desired = desired \ No newline at end of file diff --git a/azext_iot/sdk/service/models/property_container.py b/azext_iot/sdk/service/models/property_container.py deleted file mode 100644 index caa6ae42f..000000000 --- a/azext_iot/sdk/service/models/property_container.py +++ /dev/null @@ -1,39 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class PropertyContainer(Model): - """Represents Twin properties. - - :param desired: Used in conjunction with reported properties to - synchronize device configuration or condition. Desired properties can only - be set by the solution back end and can be read by the device app. The - device app can also be notified in real time of changes on the desired - properties. - :type desired: dict[str, object] - :param reported: Used in conjunction with desired properties to - synchronize device configuration or condition. Reported properties can - only be set by the device app and can be read and queried by the solution - back end. - :type reported: dict[str, object] - """ - - _attribute_map = { - 'desired': {'key': 'desired', 'type': '{object}'}, - 'reported': {'key': 'reported', 'type': '{object}'}, - } - - def __init__(self, desired=None, reported=None): - super(PropertyContainer, self).__init__() - self.desired = desired - self.reported = reported diff --git a/azext_iot/sdk/service/models/purge_message_queue_result.py b/azext_iot/sdk/service/models/purge_message_queue_result.py deleted file mode 100644 index 1b9896f7c..000000000 --- a/azext_iot/sdk/service/models/purge_message_queue_result.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class PurgeMessageQueueResult(Model): - """Result of a device message queue purge operation. - - :param total_messages_purged: - :type total_messages_purged: int - :param device_id: The ID of the device whose messages are being purged. - :type device_id: str - :param module_id: The ID of the device whose messages are being purged. - :type module_id: str - """ - - _attribute_map = { - 'total_messages_purged': {'key': 'totalMessagesPurged', 'type': 'int'}, - 'device_id': {'key': 'deviceId', 'type': 'str'}, - 'module_id': {'key': 'moduleId', 'type': 'str'}, - } - - def __init__(self, total_messages_purged=None, device_id=None, module_id=None): - super(PurgeMessageQueueResult, self).__init__() - self.total_messages_purged = total_messages_purged - self.device_id = device_id - self.module_id = module_id diff --git a/azext_iot/sdk/service/models/registry_statistics.py b/azext_iot/sdk/service/models/registry_statistics.py deleted file mode 100644 index f917cd871..000000000 --- a/azext_iot/sdk/service/models/registry_statistics.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class RegistryStatistics(Model): - """RegistryStatistics. - - :param total_device_count: - :type total_device_count: long - :param enabled_device_count: - :type enabled_device_count: long - :param disabled_device_count: - :type disabled_device_count: long - """ - - _attribute_map = { - 'total_device_count': {'key': 'totalDeviceCount', 'type': 'long'}, - 'enabled_device_count': {'key': 'enabledDeviceCount', 'type': 'long'}, - 'disabled_device_count': {'key': 'disabledDeviceCount', 'type': 'long'}, - } - - def __init__(self, total_device_count=None, enabled_device_count=None, disabled_device_count=None): - super(RegistryStatistics, self).__init__() - self.total_device_count = total_device_count - self.enabled_device_count = enabled_device_count - self.disabled_device_count = disabled_device_count diff --git a/azext_iot/sdk/service/models/reported.py b/azext_iot/sdk/service/models/reported.py deleted file mode 100644 index 1dc58b366..000000000 --- a/azext_iot/sdk/service/models/reported.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class Reported(Model): - """Reported. - - :param value: The current interface property value in a digitalTwin. - :type value: object - :param desired_state: - :type desired_state: ~service.models.DesiredState - """ - - _attribute_map = { - 'value': {'key': 'value', 'type': 'object'}, - 'desired_state': {'key': 'desiredState', 'type': 'DesiredState'}, - } - - def __init__(self, value=None, desired_state=None): - super(Reported, self).__init__() - self.value = value - self.desired_state = desired_state diff --git a/azext_iot/sdk/service/models/service_statistics.py b/azext_iot/sdk/service/models/service_statistics.py deleted file mode 100644 index 9c3ee8cf3..000000000 --- a/azext_iot/sdk/service/models/service_statistics.py +++ /dev/null @@ -1,28 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class ServiceStatistics(Model): - """ServiceStatistics. - - :param connected_device_count: - :type connected_device_count: long - """ - - _attribute_map = { - 'connected_device_count': {'key': 'connectedDeviceCount', 'type': 'long'}, - } - - def __init__(self, connected_device_count=None): - super(ServiceStatistics, self).__init__() - self.connected_device_count = connected_device_count diff --git a/azext_iot/sdk/service/models/twin.py b/azext_iot/sdk/service/models/twin.py deleted file mode 100644 index dfffb8723..000000000 --- a/azext_iot/sdk/service/models/twin.py +++ /dev/null @@ -1,107 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class Twin(Model): - """Twin Representation. - - :param device_id: The deviceId uniquely identifies the device in the IoT - hub's identity registry. A case-sensitive string (up to 128 char long) of - ASCII 7-bit alphanumeric chars + {'-', ':', '.', '+', '%', '_', '#', '*', - '?', '!', '(', ')', ',', '=', '@', ';', '$', '''}. - :type device_id: str - :param module_id: Gets and sets the Module Id. - :type module_id: str - :param tags: A JSON document read and written by the solution back end. - Tags are not visible to device apps. - :type tags: dict[str, object] - :param properties: Gets and sets the Twin properties. - :type properties: ~service.models.TwinProperties - :param etag: Twin's ETag - :type etag: str - :param version: Version for device twin, including tags and desired - properties - :type version: long - :param device_etag: Device's ETag - :type device_etag: str - :param status: Gets the corresponding Device's Status. Possible values - include: 'enabled', 'disabled' - :type status: str or ~service.models.enum - :param status_reason: Reason, if any, for the corresponding Device to be - in specified Status - :type status_reason: str - :param status_update_time: Time when the corresponding Device's Status was - last updated - :type status_update_time: datetime - :param connection_state: Corresponding Device's ConnectionState. Possible - values include: 'Disconnected', 'Connected' - :type connection_state: str or ~service.models.enum - :param last_activity_time: The last time the device connected, received or - sent a message. In ISO8601 datetime format in UTC, for example, - 2015-01-28T16:24:48.789Z. This does not update if the device uses the - HTTP/1 protocol to perform messaging operations. - :type last_activity_time: datetime - :param cloud_to_device_message_count: Number of messages sent to the - corresponding Device from the Cloud - :type cloud_to_device_message_count: int - :param authentication_type: Corresponding Device's authentication type. - Possible values include: 'sas', 'selfSigned', 'certificateAuthority', - 'none' - :type authentication_type: str or ~service.models.enum - :param x509_thumbprint: Corresponding Device's X509 thumbprint - :type x509_thumbprint: ~service.models.X509Thumbprint - :param capabilities: - :type capabilities: ~service.models.DeviceCapabilities - :param device_scope: - :type device_scope: str - """ - - _attribute_map = { - 'device_id': {'key': 'deviceId', 'type': 'str'}, - 'module_id': {'key': 'moduleId', 'type': 'str'}, - 'tags': {'key': 'tags', 'type': '{object}'}, - 'properties': {'key': 'properties', 'type': 'TwinProperties'}, - 'etag': {'key': 'etag', 'type': 'str'}, - 'version': {'key': 'version', 'type': 'long'}, - 'device_etag': {'key': 'deviceEtag', 'type': 'str'}, - 'status': {'key': 'status', 'type': 'str'}, - 'status_reason': {'key': 'statusReason', 'type': 'str'}, - 'status_update_time': {'key': 'statusUpdateTime', 'type': 'iso-8601'}, - 'connection_state': {'key': 'connectionState', 'type': 'str'}, - 'last_activity_time': {'key': 'lastActivityTime', 'type': 'iso-8601'}, - 'cloud_to_device_message_count': {'key': 'cloudToDeviceMessageCount', 'type': 'int'}, - 'authentication_type': {'key': 'authenticationType', 'type': 'str'}, - 'x509_thumbprint': {'key': 'x509Thumbprint', 'type': 'X509Thumbprint'}, - 'capabilities': {'key': 'capabilities', 'type': 'DeviceCapabilities'}, - 'device_scope': {'key': 'deviceScope', 'type': 'str'}, - } - - def __init__(self, device_id=None, module_id=None, tags=None, properties=None, etag=None, version=None, device_etag=None, status=None, status_reason=None, status_update_time=None, connection_state=None, last_activity_time=None, cloud_to_device_message_count=None, authentication_type=None, x509_thumbprint=None, capabilities=None, device_scope=None): - super(Twin, self).__init__() - self.device_id = device_id - self.module_id = module_id - self.tags = tags - self.properties = properties - self.etag = etag - self.version = version - self.device_etag = device_etag - self.status = status - self.status_reason = status_reason - self.status_update_time = status_update_time - self.connection_state = connection_state - self.last_activity_time = last_activity_time - self.cloud_to_device_message_count = cloud_to_device_message_count - self.authentication_type = authentication_type - self.x509_thumbprint = x509_thumbprint - self.capabilities = capabilities - self.device_scope = device_scope diff --git a/azext_iot/sdk/service/models/twin_properties.py b/azext_iot/sdk/service/models/twin_properties.py deleted file mode 100644 index fc8184ac1..000000000 --- a/azext_iot/sdk/service/models/twin_properties.py +++ /dev/null @@ -1,39 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class TwinProperties(Model): - """Represents Twin properties. - - :param desired: Used in conjunction with reported properties to - synchronize device configuration or condition. Desired properties can only - be set by the solution back end and can be read by the device app. The - device app can also be notified in real time of changes on the desired - properties. - :type desired: dict[str, object] - :param reported: Used in conjunction with desired properties to - synchronize device configuration or condition. Reported properties can - only be set by the device app and can be read and queried by the solution - back end. - :type reported: dict[str, object] - """ - - _attribute_map = { - 'desired': {'key': 'desired', 'type': '{object}'}, - 'reported': {'key': 'reported', 'type': '{object}'}, - } - - def __init__(self, desired=None, reported=None): - super(TwinProperties, self).__init__() - self.desired = desired - self.reported = reported diff --git a/azext_iot/sdk/service/models/x509_thumbprint.py b/azext_iot/sdk/service/models/x509_thumbprint.py deleted file mode 100644 index 43e6360ba..000000000 --- a/azext_iot/sdk/service/models/x509_thumbprint.py +++ /dev/null @@ -1,32 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -# Code generated by Microsoft (R) AutoRest Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is -# regenerated. -# -------------------------------------------------------------------------- - -from msrest.serialization import Model - - -class X509Thumbprint(Model): - """X509Thumbprint. - - :param primary_thumbprint: - :type primary_thumbprint: str - :param secondary_thumbprint: - :type secondary_thumbprint: str - """ - - _attribute_map = { - 'primary_thumbprint': {'key': 'primaryThumbprint', 'type': 'str'}, - 'secondary_thumbprint': {'key': 'secondaryThumbprint', 'type': 'str'}, - } - - def __init__(self, primary_thumbprint=None, secondary_thumbprint=None): - super(X509Thumbprint, self).__init__() - self.primary_thumbprint = primary_thumbprint - self.secondary_thumbprint = secondary_thumbprint diff --git a/azext_iot/tests/__init__.py b/azext_iot/tests/__init__.py index f2e50768e..f7cf926d2 100644 --- a/azext_iot/tests/__init__.py +++ b/azext_iot/tests/__init__.py @@ -50,22 +50,40 @@ def close(self): buffer_tee.close() -class IoTLiveScenarioTest(LiveScenarioTest): - def __init__(self, test_scenario, entity_name, entity_rg, entity_cs): +class CaptureOutputLiveScenarioTest(LiveScenarioTest): + def __init__(self, test_scenario): + super(CaptureOutputLiveScenarioTest, self).__init__(test_scenario) + + # TODO: @digimaun - Maybe put a helper like this in the shared lib, when you create it? + def command_execute_assert(self, command, asserts): + from . import capture_output + + with capture_output() as buffer: + self.cmd(command, checks=None) + output = buffer.get_output() + + for a in asserts: + assert a in output + + return output + + +class IoTLiveScenarioTest(CaptureOutputLiveScenarioTest): + def __init__(self, test_scenario, entity_name, entity_rg): assert test_scenario assert entity_name assert entity_rg - assert entity_cs self.entity_name = entity_name self.entity_rg = entity_rg - self.entity_cs = entity_cs self.device_ids = [] self.config_ids = [] os.environ["AZURE_CORE_COLLECT_TELEMETRY"] = "no" + super(IoTLiveScenarioTest, self).__init__(test_scenario) self.region = self.get_region() + self.connection_string = self.get_hub_cstring() def generate_device_names(self, count=1, edge=False): names = [ @@ -95,27 +113,15 @@ def generate_config_names(self, count=1, edge=False): def generate_job_names(self, count=1): return [ - self.create_random_name(prefix=PREFIX_JOB, length=32) - for i in range(count) + self.create_random_name(prefix=PREFIX_JOB, length=32) for i in range(count) ] - # TODO: @digimaun - Maybe put a helper like this in the shared lib, when you create it? - def command_execute_assert(self, command, asserts): - from . import capture_output - - with capture_output() as buffer: - self.cmd(command, checks=None) - output = buffer.get_output() - - for a in asserts: - assert a in output - def tearDown(self): if self.device_ids: device = self.device_ids.pop() self.cmd( "iot hub device-identity delete -d {} --login {}".format( - device, self.entity_cs + device, self.connection_string ), checks=self.is_empty(), ) @@ -132,7 +138,7 @@ def tearDown(self): config = self.config_ids.pop() self.cmd( "iot hub configuration delete -c {} --login {}".format( - config, self.entity_cs + config, self.connection_string ), checks=self.is_empty(), ) @@ -154,6 +160,13 @@ def get_region(self): if loc["role"] == "primary": return loc["location"] + def get_hub_cstring(self, policy="iothubowner"): + return self.cmd( + "iot hub show-connection-string -n {} -g {} --policy-name {}".format( + self.entity_name, self.entity_rg, policy + ) + ).get_output_in_json()["connectionString"] + def disable_telemetry(test_function): def wrapper(*args, **kwargs): diff --git a/azext_iot/tests/central/json/deeply_nested_template.json b/azext_iot/tests/central/json/deeply_nested_template.json new file mode 100644 index 000000000..7231d657d Binary files /dev/null and b/azext_iot/tests/central/json/deeply_nested_template.json differ diff --git a/azext_iot/tests/central/json/device.json b/azext_iot/tests/central/json/device.json new file mode 100644 index 000000000..0b138571a --- /dev/null +++ b/azext_iot/tests/central/json/device.json @@ -0,0 +1,9 @@ +{ + "id": "device-id", + "etag": "some-etag", + "displayName": "device-id", + "instanceOf": "urn:d9cltbeus:tvj4oal1a0", + "simulated": false, + "provisioned": true, + "approved": true +} \ No newline at end of file diff --git a/azext_iot/tests/central/json/device_template.json b/azext_iot/tests/central/json/device_template.json new file mode 100644 index 000000000..41cedaf66 --- /dev/null +++ b/azext_iot/tests/central/json/device_template.json @@ -0,0 +1,285 @@ +{ + "id": "urn:d9cltbeus:tvj4oal1a0", + "etag": "\"~WgqHZmg+d95gTA53P8AnqBsDLGgj2wa0msOL7xozC9Y=\"", + "types": [ + "DeviceModel" + ], + "displayName": "duplicate-field-name", + "capabilityModel": { + "@id": "urn:sampleApp:modelOne_bz:2", + "@type": "CapabilityModel", + "implements": [ + { + "@id": "urn:sampleApp:modelOne_bz:_rpgcmdpo:1", + "@type": [ + "InterfaceInstance" + ], + "displayName": "Interface", + "name": "modelOne_g4", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:1", + "@type": [ + "Interface" + ], + "displayName": "Interface", + "contents": [ + { + "@id": "urn:sampleApp:modelOne_g4:Bool:1", + "@type": [ + "Telemetry" + ], + "displayName": "Bool", + "name": "Bool", + "schema": "boolean" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Date:1", + "@type": [ + "Telemetry" + ], + "displayName": "Date", + "name": "Date", + "schema": "date" + }, + { + "@id": "urn:sampleApp:modelOne_g4:DateTime:1", + "@type": [ + "Telemetry" + ], + "displayName": "DateTime", + "name": "DateTime", + "schema": "dateTime" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Double:1", + "@type": [ + "Telemetry" + ], + "displayName": "Double", + "name": "Double", + "schema": "double" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Duration:1", + "@type": [ + "Telemetry" + ], + "displayName": "Duration", + "name": "Duration", + "schema": "duration" + }, + { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:1", + "@type": [ + "Telemetry" + ], + "displayName": "IntEnum", + "name": "IntEnum", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:pgkbdhard:1", + "@type": [ + "Enum" + ], + "displayName": "Enum", + "valueSchema": "integer", + "enumValues": [ + { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:pgkbdhard:Enum1:1", + "@type": [ + "EnumValue" + ], + "displayName": "Enum1", + "enumValue": 1, + "name": "Enum1" + }, + { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:pgkbdhard:Enum2:1", + "@type": [ + "EnumValue" + ], + "displayName": "Enum2", + "enumValue": 2, + "name": "Enum2" + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:1", + "@type": [ + "Telemetry" + ], + "displayName": "StringEnum", + "name": "StringEnum", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:kyesuinpsx:1", + "@type": [ + "Enum" + ], + "displayName": "Enum", + "valueSchema": "string", + "enumValues": [ + { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:kyesuinpsx:EnumA:1", + "@type": [ + "EnumValue" + ], + "displayName": "EnumA", + "enumValue": "A", + "name": "EnumA" + }, + { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:kyesuinpsx:EnumB:1", + "@type": [ + "EnumValue" + ], + "displayName": "EnumB", + "enumValue": "B", + "name": "EnumB" + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_g4:Float:1", + "@type": [ + "Telemetry" + ], + "displayName": "Float", + "name": "Float", + "schema": "float" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Geopoint:1", + "@type": [ + "Telemetry" + ], + "displayName": "Geopoint", + "name": "Geopoint", + "schema": "geopoint" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Int:1", + "@type": [ + "Telemetry" + ], + "displayName": "Int", + "name": "Int", + "schema": "integer" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Long:1", + "@type": [ + "Telemetry" + ], + "displayName": "Long", + "name": "Long", + "schema": "long" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Object:1", + "@type": [ + "Telemetry" + ], + "displayName": "Object", + "name": "Object", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:Object:8ot2x5whp8:1", + "@type": [ + "Object" + ], + "displayName": "Object", + "fields": [ + { + "@id": "urn:sampleApp:modelOne_g4:Object:8ot2x5whp8:Double:1", + "@type": [ + "SchemaField" + ], + "displayName": "Double", + "name": "Double", + "schema": "double" + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_g4:String:1", + "@type": [ + "Telemetry" + ], + "displayName": "String", + "name": "String", + "schema": "string" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Time:1", + "@type": [ + "Telemetry" + ], + "displayName": "Time", + "name": "Time", + "schema": "time" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Vector:1", + "@type": [ + "Telemetry" + ], + "displayName": "Vector", + "name": "Vector", + "schema": "vector" + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_bz:myxqftpsr:2", + "@type": [ + "InterfaceInstance" + ], + "displayName": "Interface", + "name": "modelTwo_ed", + "schema": { + "@id": "urn:sampleApp:modelTwo_ed:1", + "@type": [ + "Interface" + ], + "displayName": "Interface", + "contents": [ + { + "@id": "urn:sampleApp:modelTwo_ed:Bool:1", + "@type": [ + "Telemetry" + ], + "displayName": "Bool", + "name": "Bool", + "schema": "boolean" + }, + { + "@id": "urn:sampleApp:modelTwo_ed:bool:1", + "@type": [ + "Telemetry" + ], + "displayName": "bool", + "name": "bool", + "schema": "boolean" + } + ] + } + } + ], + "displayName": "larger-telemetry-device", + "@context": [ + "http://azureiot.com/v1/contexts/IoTModel.json" + ] + }, + "solutionModel": { + "@id": "urn:d9cltbeus:lz1tl4a_jz", + "@type": [ + "SolutionModel" + ], + "cloudProperties": [], + "initialValues": [], + "overrides": [] + } +} \ No newline at end of file diff --git a/azext_iot/tests/central/json/device_template_int_test.json b/azext_iot/tests/central/json/device_template_int_test.json new file mode 100644 index 000000000..689656bf8 --- /dev/null +++ b/azext_iot/tests/central/json/device_template_int_test.json @@ -0,0 +1,320 @@ +{ + "types": [ + "DeviceModel" + ], + "displayName": "int-test-device-template", + "capabilityModel": { + "@id": "urn:sampleApp:modelOne_bz:2", + "@type": "CapabilityModel", + "contents": [ + { + "@id": "urn:testazuresphere:AzureSphereSampleDevice_614:testDefaultCapability:2", + "@type": "Telemetry", + "displayName": "testDefaultCapability", + "name": "testDefaultCapability", + "schema": "double" + } + ], + "implements": [ + { + "@id": "urn:sampleApp:modelOne_bz:_rpgcmdpo:1", + "@type": [ + "InterfaceInstance" + ], + "displayName": "Interface", + "name": "modelOne_g4", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:1", + "@type": [ + "Interface" + ], + "displayName": "Interface", + "contents": [ + { + "@id": "urn:sampleApp:modelOne_g4:Bool:1", + "@type": [ + "Telemetry" + ], + "displayName": "Bool", + "name": "Bool", + "schema": "boolean" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Date:1", + "@type": [ + "Telemetry" + ], + "displayName": "Date", + "name": "Date", + "schema": "date" + }, + { + "@id": "urn:sampleApp:modelOne_g4:DateTime:1", + "@type": [ + "Telemetry" + ], + "displayName": "DateTime", + "name": "DateTime", + "schema": "dateTime" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Double:1", + "@type": [ + "Telemetry" + ], + "displayName": "Double", + "name": "Double", + "schema": "double" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Duration:1", + "@type": [ + "Telemetry" + ], + "displayName": "Duration", + "name": "Duration", + "schema": "duration" + }, + { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:1", + "@type": [ + "Telemetry" + ], + "displayName": "IntEnum", + "name": "IntEnum", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:pgkbdhard:1", + "@type": [ + "Enum" + ], + "displayName": "Enum", + "valueSchema": "integer", + "enumValues": [ + { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:pgkbdhard:Enum1:1", + "@type": [ + "EnumValue" + ], + "displayName": "Enum1", + "enumValue": 1, + "name": "Enum1" + }, + { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:pgkbdhard:Enum2:1", + "@type": [ + "EnumValue" + ], + "displayName": "Enum2", + "enumValue": 2, + "name": "Enum2" + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:1", + "@type": [ + "Telemetry" + ], + "displayName": "StringEnum", + "name": "StringEnum", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:kyesuinpsx:1", + "@type": [ + "Enum" + ], + "displayName": "Enum", + "valueSchema": "string", + "enumValues": [ + { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:kyesuinpsx:EnumA:1", + "@type": [ + "EnumValue" + ], + "displayName": "EnumA", + "enumValue": "A", + "name": "EnumA" + }, + { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:kyesuinpsx:EnumB:1", + "@type": [ + "EnumValue" + ], + "displayName": "EnumB", + "enumValue": "B", + "name": "EnumB" + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_g4:Float:1", + "@type": [ + "Telemetry" + ], + "displayName": "Float", + "name": "Float", + "schema": "float" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Geopoint:1", + "@type": [ + "Telemetry" + ], + "displayName": "Geopoint", + "name": "Geopoint", + "schema": "geopoint" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Int:1", + "@type": [ + "Telemetry" + ], + "displayName": "Int", + "name": "Int", + "schema": "integer" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Long:1", + "@type": [ + "Telemetry" + ], + "displayName": "Long", + "name": "Long", + "schema": "long" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Object:1", + "@type": [ + "Telemetry" + ], + "displayName": "Object", + "name": "Object", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:Object:8ot2x5whp8:1", + "@type": [ + "Object" + ], + "displayName": "Object", + "fields": [ + { + "@id": "urn:sampleApp:modelOne_g4:Object:8ot2x5whp8:Double:1", + "@type": [ + "SchemaField" + ], + "displayName": "Double", + "name": "Double", + "schema": "double" + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_g4:String:1", + "@type": [ + "Telemetry" + ], + "displayName": "String", + "name": "String", + "schema": "string" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Time:1", + "@type": [ + "Telemetry" + ], + "displayName": "Time", + "name": "Time", + "schema": "time" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Vector:1", + "@type": [ + "Telemetry" + ], + "displayName": "Vector", + "name": "Vector", + "schema": "vector" + }, + { + "@id": "urn:sampleApp:modelOne_g4:sync_cmd:1", + "@type": [ + "Command" + ], + "commandType": "synchronous", + "displayName": "sync_cmd", + "durable": false, + "name": "sync_cmd", + "request": { + "@id": "urn:sampleApp:modelOne_g4:sync_cmd:argument:1", + "@type": [ + "SchemaField" + ], + "displayName": "argument", + "name": "argument", + "schema": "string" + }, + "response": { + "@id": "urn:sampleApp:modelOne_g4:sync_cmd:status:1", + "@type": [ + "SchemaField" + ], + "displayName": "status", + "name": "status", + "schema": "double" + } + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_bz:myxqftpsr:2", + "@type": [ + "InterfaceInstance" + ], + "displayName": "Interface", + "name": "modelTwo_ed", + "schema": { + "@id": "urn:sampleApp:modelTwo_ed:1", + "@type": [ + "Interface" + ], + "displayName": "Interface", + "contents": [ + { + "@id": "urn:sampleApp:modelTwo_ed:Bool:1", + "@type": [ + "Telemetry" + ], + "displayName": "Bool", + "name": "Bool", + "schema": "boolean" + }, + { + "@id": "urn:sampleApp:modelTwo_ed:bool:1", + "@type": [ + "Telemetry" + ], + "displayName": "bool", + "name": "bool", + "schema": "boolean" + } + ] + } + } + ], + "displayName": "larger-telemetry-device", + "@context": [ + "http://azureiot.com/v1/contexts/IoTModel.json" + ] + }, + "solutionModel": { + "@id": "urn:d9cltbeus:lz1tl4a_jz", + "@type": [ + "SolutionModel" + ], + "cloudProperties": [], + "initialValues": [], + "overrides": [] + } +} \ No newline at end of file diff --git a/azext_iot/tests/central/json/device_twin.json b/azext_iot/tests/central/json/device_twin.json new file mode 100644 index 000000000..1a112aff9 --- /dev/null +++ b/azext_iot/tests/central/json/device_twin.json @@ -0,0 +1,186 @@ +{ + "deviceId": "testDpsSetup", + "etag": "AAAAAAAAAIA=", + "deviceEtag": "NTMwMzM3ODQ4", + "status": "enabled", + "statusUpdateTime": "0001-01-01T00: 00: 00Z", + "connectionState": "Connected", + "lastActivityTime": "2020-06-25T06: 36: 07.5744727Z", + "cloudToDeviceMessageCount": 0, + "authenticationType": "sas", + "x509Thumbprint": { + "primaryThumbprint": "None", + "secondaryThumbprint": "None" + }, + "version": 2856, + "properties": { + "desired": { + "$iotin:settings": { + "fanSpeed": { + "value": 120 + } + }, + "$metadata": { + "$lastUpdated": "current_time", + "$lastUpdatedVersion": 128, + "$iotin:settings": { + "$lastUpdated": "current_time", + "$lastUpdatedVersion": 128, + "fanSpeed": { + "$lastUpdated": "current_time", + "$lastUpdatedVersion": 128, + "value": { + "$lastUpdated": "current_time", + "$lastUpdatedVersion": 128 + } + } + } + }, + "$version": 128 + }, + "reported": { + "$iotin:urn_azureiot_Client_SDKInformation": { + "language": { + "value": "C" + }, + "version": { + "value": "0.9.0" + }, + "vendor": { + "value": "Microsoft" + } + }, + "$iotin:deviceinfo": { + "manufacturer": { + "value": "MXChip" + }, + "model": { + "value": "AZ3166" + }, + "osName": { + "value": "Arm Mbed OS v5.2" + }, + "processorArchitecture": { + "value": "Arm Cortex M4" + }, + "swVersion": { + "value": "1.0.0" + }, + "processorManufacturer": { + "value": "STMicro" + }, + "totalStorage": { + "value": 2048 + }, + "totalMemory": { + "value": 256 + } + }, + "$iotin:settings": { + "fanSpeed": { + "value": 120, + "sc": 200, + "sd": "fanSpeed property is updated successfully", + "sv": 128 + } + }, + "$metadata": { + "$lastUpdated": "current_time", + "$iotin:urn_azureiot_Client_SDKInformation": { + "$lastUpdated": "current_time", + "language": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + } + }, + "version": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + } + }, + "vendor": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + } + } + }, + "$iotin:deviceinfo": { + "$lastUpdated": "current_time", + "manufacturer": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + } + }, + "model": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + } + }, + "osName": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + } + }, + "processorArchitecture": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + } + }, + "swVersion": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + } + }, + "processorManufacturer": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + } + }, + "totalStorage": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + } + }, + "totalMemory": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + } + } + }, + "$iotin:settings": { + "$lastUpdated": "current_time", + "fanSpeed": { + "$lastUpdated": "current_time", + "value": { + "$lastUpdated": "current_time" + }, + "sc": { + "$lastUpdated": "current_time" + }, + "sd": { + "$lastUpdated": "current_time" + }, + "sv": { + "$lastUpdated": "current_time" + } + } + } + }, + "$version": 2728 + } + }, + "capabilities": { + "iotEdge": "False" + } +} \ No newline at end of file diff --git a/azext_iot/tests/central/json/property_validation_template.json b/azext_iot/tests/central/json/property_validation_template.json new file mode 100644 index 000000000..ce0bee64b --- /dev/null +++ b/azext_iot/tests/central/json/property_validation_template.json @@ -0,0 +1,282 @@ +{ + "id": "urn:d9cltbeus:tvj4oal1a0", + "etag": "\"~WgqHZmg+d95gTA53P8AnqBsDLGgj2wa0msOL7xozC9Y=\"", + "types": [ + "DeviceModel" + ], + "displayName": "property-validation", + "capabilityModel": { + "@id": "urn:sampleApp:groupOne_bz:2", + "@type": "CapabilityModel", + "contents": [ + { + "@id": "urn:sampleApp:root_bz:_rpgcmdpo:1", + "@type": "Property", + "displayName": "addRootProperty", + "name": "addRootProperty", + "schema": "boolean", + "writable": true + }, + { + "@id": "urn:sampleApp:componentOne_bz:_rpgcmdpo:1", + "@type": "Component", + "displayName": "Component", + "name": "_rpgcmdpo", + "schema": { + "@id": "dtmi:sampleApp:_rpgcmdpo;4", + "@type": "Interface", + "contents": [ + { + "@id": "dtmi:sampleApp:_rpgcmdpo:component1Prop;1", + "@type": "Property", + "displayName": "component1Prop", + "name": "component1Prop", + "schema": "boolean", + "writable": true + }, + { + "@id": "dtmi:sampleApp:_rpgcmdpo:testComponent;1", + "@type": "Property", + "displayName": "testComponent", + "name": "testComponent", + "schema": "boolean", + "writable": true + }, + { + "@id": "dtmi:sampleApp:_rpgcmdpo:component1PropReadonly;1", + "@type": "Property", + "displayName": "component1PropReadonly", + "name": "component1PropReadonly", + "schema": "boolean", + "writable": false + }, + { + "@id": "dtmi:sampleApp:_rpgcmdpo:component1Prop2;1", + "@type": "Property", + "displayName": "component1Prop2", + "name": "component1Prop2", + "schema": "boolean", + "writable": true + } + ], + "displayName": "Component" + } + }, + { + "@id": "urn:rigado:RS40_Occupancy_Sensor:RS40OccupancySensorV36fy:3", + "@type": "Component", + "displayName": "Component", + "name": "RS40OccupancySensorV36fy", + "schema": { + "@id": "dtmi:cliIntegrationtestApp:RS40OccupancySensorV36fy;4", + "@type": "Interface", + "contents": [ + { + "@id": "dtmi:cliIntegrationtestApp:RS40OccupancySensorV36fy:component2prop;1", + "@type": "Property", + "displayName": "component2prop", + "name": "component2prop", + "schema": "boolean", + "writable": true + }, + { + "@id": "dtmi:cliIntegrationtestApp:RS40OccupancySensorV36fy:testComponent;2", + "@type": "Property", + "displayName": "testComponent", + "name": "testComponent", + "schema": "boolean", + "writable": true + }, + { + "@id": "dtmi:cliIntegrationtestApp:RS40OccupancySensorV36fy:component2PropReadonly;3", + "@type": "Property", + "displayName": "component2PropReadonly", + "name": "component2PropReadonly", + "schema": "boolean", + "writable": false + }, + { + "@id": "dtmi:cliIntegrationtestApp:RS40OccupancySensorV36fy:component2Prop2;4", + "@type": "Property", + "displayName": "component2Prop2", + "name": "component2Prop2", + "schema": "boolean", + "writable": true + }, + { + "@id": "dtmi:cliIntegrationtestApp:RS40OccupancySensorV36fy:component1Telemetry;5", + "@type": "Telemetry", + "displayName": "component1Telemetry", + "name": "component1Telemetry", + "schema": "double" + } + ], + "displayName": "Component" + } + }, + { + "@id": "urn:rigado:RS40_Occupancy_Sensor:addRootPropertyReadOnly:5", + "@type": "Property", + "displayName": "addRootPropertyReadOnly", + "name": "addRootPropertyReadOnly", + "schema": "boolean", + "writable": false + }, + { + "@id": "urn:rigado:RS40_Occupancy_Sensor:addRootProperty2:7", + "@type": "Property", + "displayName": "addRootProperty2", + "name": "addRootProperty2", + "schema": "boolean", + "writable": true + } + ], + "implements": [ + { + "@id": "urn:sampleApp:groupOne_bz:_rpgcmdpo:1", + "@type": [ + "InterfaceInstance" + ], + "displayName": "Interface", + "name": "groupOne_g4", + "schema": { + "@id": "urn:sampleApp:groupOne_g4:1", + "@type": [ + "Interface" + ], + "displayName": "Interface", + "contents": [ + { + "@id": "urn:sampleApp:groupOne_g4:Model:1", + "@type": [ + "Property" + ], + "displayName": "Model", + "name": "Model", + "schema": "string" + }, + { + "@id": "urn:sampleApp:groupOne_g4:Version:1", + "@type": [ + "Property" + ], + "displayName": "Version", + "name": "Version", + "schema": "string" + }, + { + "@id": "urn:sampleApp:groupOne_g4:TotalStorage:1", + "@type": [ + "Property" + ], + "displayName": "TotalStorage", + "name": "TotalStorage", + "schema": "string" + } + ] + } + }, + { + "@id": "urn:sampleApp:groupTwo_bz:myxqftpsr:2", + "@type": [ + "InterfaceInstance" + ], + "displayName": "Interface", + "name": "groupTwo_ed", + "schema": { + "@id": "urn:sampleApp:groupTwo_ed:1", + "@type": [ + "Interface" + ], + "displayName": "Interface", + "contents": [ + { + "@id": "urn:sampleApp:groupTwo_ed:Model:1", + "@type": [ + "Property" + ], + "displayName": "Model", + "name": "Model", + "schema": "string" + }, + { + "@id": "urn:sampleApp:groupThree_ed:Manufacturer:1", + "@type": [ + "Property" + ], + "displayName": "Manufacturer", + "name": "Manufacturer", + "schema": "string" + } + ] + } + }, + { + "@id": "urn:sampleApp:groupThree_bz:myxqftpsr:2", + "@type": [ + "InterfaceInstance" + ], + "displayName": "Interface", + "name": "groupThree_ed", + "schema": { + "@id": "urn:sampleApp:groupThree_ed:1", + "@type": [ + "Interface" + ], + "displayName": "Interface", + "contents": [ + { + "@id": "urn:sampleApp:groupThree_ed:Manufacturer:1", + "@type": [ + "Property" + ], + "displayName": "Manufacturer", + "name": "Manufacturer", + "schema": "string" + }, + { + "@id": "urn:sampleApp:groupThree_g4:Version:1", + "@type": [ + "Property" + ], + "displayName": "Version", + "name": "Version", + "schema": "string" + }, + { + "@id": "urn:sampleApp:groupThree_ed:Model:1", + "@type": [ + "Property" + ], + "displayName": "Model", + "name": "Model", + "schema": "string" + }, + { + "@id": "urn:sampleApp:groupThree_ed:OsName:1", + "@type": [ + "Property" + ], + "displayName": "OsName", + "name": "OsName", + "schema": "string" + } + ] + } + } + ], + "displayName": "property_validation", + "@context": [ + "http://azureiot.com/v1/contexts/IoTModel.json" + ] + }, + "solutionModel": { + "@id": "urn:d9cltbeus:lz1tl4a_jz", + "@type": [ + "SolutionModel" + ], + "cloudProperties": [], + "initialValues": [], + "overrides": [] + } +} \ No newline at end of file diff --git a/azext_iot/tests/central/json/sync_command_args.json b/azext_iot/tests/central/json/sync_command_args.json new file mode 100644 index 000000000..e47e4024e --- /dev/null +++ b/azext_iot/tests/central/json/sync_command_args.json @@ -0,0 +1,5 @@ +{ + "request": { + "argument": "value" + } +} \ No newline at end of file diff --git a/azext_iot/tests/central/test_iot_central_int.py b/azext_iot/tests/central/test_iot_central_int.py new file mode 100644 index 000000000..fba2f3fe5 --- /dev/null +++ b/azext_iot/tests/central/test_iot_central_int.py @@ -0,0 +1,649 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +import json +import os +import time +import pytest + +from azext_iot.tests.conftest import get_context_path + +from azure.iot.device import Message +from azext_iot.common import utility +from azext_iot.central.models.enum import DeviceStatus, Role +from azext_iot.monitor.parsers import strings + +from azext_iot.tests import CaptureOutputLiveScenarioTest, helpers + +APP_ID = os.environ.get("azext_iot_central_app_id") +APP_PRIMARY_KEY = os.environ.get("azext_iot_central_primarykey") +APP_SCOPE_ID = os.environ.get("azext_iot_central_scope_id") +device_template_path = get_context_path( + __file__, "json/device_template_int_test.json" +) +sync_command_params = get_context_path(__file__, "json/sync_command_args.json") + +if not all([APP_ID]): + raise ValueError("Set azext_iot_central_app_id to run central integration tests.") + + +class TestIotCentral(CaptureOutputLiveScenarioTest): + def __init__(self, test_scenario): + super(TestIotCentral, self).__init__(test_scenario=test_scenario) + + def test_central_device_twin_show_fail(self): + (device_id, _) = self._create_device() + + # Verify incorrect app-id throws error + self.cmd( + "iot central device twin show --app-id incorrect-app --device-id {}".format( + device_id + ), + expect_failure=True, + ) + # Verify incorrect device-id throws error + self.cmd( + "iot central device twin show --app-id {} --device-id incorrect-device".format( + APP_ID + ), + expect_failure=True, + ) + + # Verify incorrect app-id throws error + self.cmd( + "iot central device twin show --app-id incorrect-app --device-id {}".format( + device_id + ), + expect_failure=True, + ) + + # Verify incorrect device-id throws error + self.cmd( + "iot central device twin show --app-id {} --device-id incorrect-device".format( + APP_ID + ), + expect_failure=True, + ) + + self._delete_device(device_id) + + def test_central_device_twin_show_success(self): + (template_id, _) = self._create_device_template() + (device_id, _) = self._create_device(instance_of=template_id, simulated=True) + + # wait about a few seconds for simulator to kick in so that provisioning completes + time.sleep(60) + + self.cmd( + "iot central device twin show --app-id {} --device-id {}".format( + APP_ID, device_id + ), + checks=[self.check("deviceId", device_id)], + ) + + self.cmd( + "iot central device twin show --app-id {} --device-id {}".format( + APP_ID, device_id + ), + checks=[self.check("deviceId", device_id)], + ) + + self._delete_device(device_id) + self._delete_device_template(template_id) + + def test_central_monitor_events(self): + (template_id, _) = self._create_device_template() + (device_id, _) = self._create_device(instance_of=template_id) + credentials = self._get_credentials(device_id) + + device_client = helpers.dps_connect_device(device_id, credentials) + + enqueued_time = utility.calculate_millisec_since_unix_epoch_utc() - 10000 + + payload = {"Bool": True} + msg = Message( + data=json.dumps(payload), + content_encoding="utf-8", + content_type="application/json", + ) + device_client.send_message(msg) + + # Test with invalid app-id + self.cmd( + "iot central diagnostics monitor-events --app-id {} -y".format( + APP_ID + "zzz" + ), + expect_failure=True, + ) + + # Ensure no failure + output = self._get_monitor_events_output(device_id, enqueued_time) + + self._delete_device(device_id) + self._delete_device_template(template_id) + assert '"Bool": true' in output + assert device_id in output + + def test_central_validate_messages_success(self): + (template_id, _) = self._create_device_template() + (device_id, _) = self._create_device(instance_of=template_id) + credentials = self._get_credentials(device_id) + + device_client = helpers.dps_connect_device(device_id, credentials) + + enqueued_time = utility.calculate_millisec_since_unix_epoch_utc() - 10000 + + payload = {"Bool": True} + msg = Message( + data=json.dumps(payload), + content_encoding="utf-8", + content_type="application/json", + ) + device_client.send_message(msg) + + # Validate the messages + output = self._get_validate_messages_output(device_id, enqueued_time) + + self._delete_device(device_id) + self._delete_device_template(template_id) + + assert output + assert "Successfully parsed 1 message(s)" in output + assert "No errors detected" in output + + @pytest.mark.skipif( + not APP_SCOPE_ID, reason="empty azext_iot_central_scope_id env var" + ) + @pytest.mark.skipif( + not APP_PRIMARY_KEY, reason="empty azext_iot_central_primarykey env var" + ) + def test_device_connect(self): + device_id = "testDevice" + + device_primary_key = self.cmd( + "iot central device compute-device-key --pk {} -d {}".format( + APP_PRIMARY_KEY, device_id + ), + ).get_output_in_json() + + credentials = { + "idScope": APP_SCOPE_ID, + "symmetricKey": {"primaryKey": device_primary_key}, + } + device_client = helpers.dps_connect_device(device_id, credentials) + + self.cmd( + "iot central device show --app-id {} -d {}".format(APP_ID, device_id), + checks=[self.check("id", device_id)], + ) + + self._delete_device(device_id) + + assert device_client.connected + + def test_central_validate_messages_issues_detected(self): + expected_messages = [] + (template_id, _) = self._create_device_template() + (device_id, _) = self._create_device(instance_of=template_id) + credentials = self._get_credentials(device_id) + + device_client = helpers.dps_connect_device(device_id, credentials) + + enqueued_time = utility.calculate_millisec_since_unix_epoch_utc() - 10000 + + # Invalid encoding + payload = {"Bool": True} + msg = Message(data=json.dumps(payload), content_type="application/json") + device_client.send_message(msg) + expected_messages.append(strings.invalid_encoding("")) + + # Content type mismatch (e.g. non application/json) + payload = {"Bool": True} + msg = Message(data=json.dumps(payload), content_encoding="utf-8") + device_client.send_message(msg) + expected_messages.append(strings.content_type_mismatch("", "application/json")) + + # Invalid type + payload = {"Bool": 123} + msg = Message( + data=json.dumps(payload), + content_encoding="utf-8", + content_type="application/json", + ) + device_client.send_message(msg) + expected_messages.append( + strings.invalid_primitive_schema_mismatch_template("Bool", "boolean", 123) + ) + + # Telemetry not defined + payload = {"NotPresentInTemplate": True} + msg = Message( + data=json.dumps(payload), + content_encoding="utf-8", + content_type="application/json", + ) + device_client.send_message(msg) + # this error is harder to build from strings because we have to construct a whole template for it + expected_messages.append( + "Following capabilities have NOT been defined in the device template '['NotPresentInTemplate']'" + ) + + # Invalid JSON + payload = '{"asd":"def}' + msg = Message( + data=payload, content_encoding="utf-8", content_type="application/json", + ) + device_client.send_message(msg) + expected_messages.append(strings.invalid_json()) + + # Validate the messages + output = self._get_validate_messages_output( + device_id, enqueued_time, max_messages=len(expected_messages) + ) + + self._delete_device(device_id) + self._delete_device_template(template_id) + + assert output + + expected_issues = [ + "No encoding found. Expected encoding 'utf-8' to be present in message header.", + "Content type '' is not supported. Expected Content type is 'application/json'.", + "Datatype of telemetry field 'Bool' does not match the datatype boolean.", + "Data sent by the device : 123.", + "For more information, see: https://aka.ms/iotcentral-payloads", + "Following capabilities have NOT been defined in the device template '['NotPresentInTemplate']'", + "Invalid JSON format", + ] + for issue in expected_issues: + assert issue in output + + def test_central_device_methods_CRD(self): + (device_id, device_name) = self._create_device() + + self.cmd( + "iot central device show --app-id {} -d {}".format(APP_ID, device_id), + checks=[ + self.check("approved", True), + self.check("displayName", device_name), + self.check("id", device_id), + self.check("simulated", False), + ], + ) + + self._delete_device(device_id) + + def test_central_user_methods_CRD(self): + users = self._create_users() + + self.cmd( + "iot central user show --app-id {} --user-id {}".format( + APP_ID, users[0].get("id") + ), + ) + + result = self.cmd( + "iot central user list --app-id {}".format(APP_ID,), + ).get_output_in_json() + + user_list = result.get("value") + + for user in users: + self._delete_user(user.get("id")) + + for user in users: + assert user in user_list + + def test_central_api_token_methods_CRD(self): + tokens = self._create_api_tokens() + + self.cmd( + "iot central api-token show --app-id {} --token-id {}".format( + APP_ID, tokens[0].get("id") + ), + ) + + result = self.cmd( + "iot central api-token list --app-id {}".format(APP_ID,), + ).get_output_in_json() + + token_list = result.get("value") + + for token in tokens: + self._delete_api_token(token.get("id")) + + for token in tokens: + token_info_basic = { + "expiry": token.get("expiry"), + "id": token.get("id"), + "roles": token.get("roles"), + } + assert token_info_basic in token_list + + def test_central_device_template_methods_CRD(self): + # currently: create, show, list, delete + (template_id, template_name) = self._create_device_template() + + self.cmd( + "iot central device-template show --app-id {} --device-template-id {}".format( + APP_ID, template_id + ), + checks=[ + self.check("displayName", template_name), + self.check("id", template_id), + ], + ) + + self._delete_device_template(template_id) + + def test_central_device_registration_info_registered(self): + (template_id, _) = self._create_device_template() + (device_id, device_name) = self._create_device( + instance_of=template_id, simulated=False + ) + + result = self.cmd( + "iot central device registration-info --app-id {} -d {}".format( + APP_ID, device_id + ) + ) + + self._delete_device(device_id) + self._delete_device_template(template_id) + + json_result = result.get_output_in_json() + + assert json_result["@device_id"] == device_id + + # since time taken for provisioning to complete is not known + # we can only assert that the payload is populated, not anything specific beyond that + assert json_result["device_registration_info"] is not None + assert json_result["dps_state"] is not None + + # Validation - device registration. + device_registration_info = json_result["device_registration_info"] + assert len(device_registration_info) == 5 + assert device_registration_info.get("device_status") == "registered" + assert device_registration_info.get("id") == device_id + assert device_registration_info.get("display_name") == device_name + assert device_registration_info.get("instance_of") == template_id + assert not device_registration_info.get("simulated") + + # Validation - dps state + dps_state = json_result["dps_state"] + assert len(dps_state) == 2 + assert device_registration_info.get("status") is None + assert dps_state.get("error") == "Device is not yet provisioned." + + def test_central_run_command(self): + interface_id = "modelOne_g4" + command_name = "sync_cmd" + (template_id, _) = self._create_device_template() + (device_id, _) = self._create_device(instance_of=template_id, simulated=True) + + self._wait_for_provisioned(device_id) + + run_command_result = self.cmd( + "iot central device command run" + " -n {}" + " -d {}" + " -i {}" + " --cn {}" + " -k '{}'" + "".format( + APP_ID, device_id, interface_id, command_name, sync_command_params + ) + ) + + show_command_result = self.cmd( + "iot central device command history" + " -n {}" + " -d {}" + " -i {}" + " --cn {}" + "".format(APP_ID, device_id, interface_id, command_name) + ) + + self._delete_device(device_id) + self._delete_device_template(template_id) + + run_result = run_command_result.get_output_in_json() + show_result = show_command_result.get_output_in_json() + + # from file indicated by `sync_command_params` + assert run_result["request"] == {"argument": "value"} + + # check that run result and show result indeed match + assert run_result["response"] == show_result["value"][0]["response"] + + def test_central_device_registration_info_unassociated(self): + + (device_id, device_name) = self._create_device() + + result = self.cmd( + "iot central device registration-info --app-id {} -d {}".format( + APP_ID, device_id + ) + ) + + self._delete_device(device_id) + + json_result = result.get_output_in_json() + + assert json_result["@device_id"] == device_id + + # since time taken for provisioning to complete is not known + # we can only assert that the payload is populated, not anything specific beyond that + assert json_result["device_registration_info"] is not None + assert json_result["dps_state"] is not None + + # Validation - device registration. + device_registration_info = json_result["device_registration_info"] + assert len(device_registration_info) == 5 + assert device_registration_info.get("device_status") == "unassociated" + assert device_registration_info.get("id") == device_id + assert device_registration_info.get("display_name") == device_name + assert device_registration_info.get("instance_of") is None + assert not device_registration_info.get("simulated") + + # Validation - dps state + dps_state = json_result["dps_state"] + assert len(dps_state) == 2 + assert device_registration_info.get("status") is None + assert ( + dps_state.get("error") + == "Device does not have a valid template associated with it." + ) + + def test_central_device_registration_summary(self): + + result = self.cmd( + "iot central diagnostics registration-summary --app-id {}".format(APP_ID) + ) + + json_result = result.get_output_in_json() + assert json_result[DeviceStatus.provisioned.value] is not None + assert json_result[DeviceStatus.registered.value] is not None + assert json_result[DeviceStatus.unassociated.value] is not None + assert json_result[DeviceStatus.blocked.value] is not None + assert len(json_result) == 4 + + def _create_device(self, **kwargs) -> (str, str): + """ + kwargs: + instance_of: template_id (str) + simulated: if the device is to be simulated (bool) + """ + device_id = self.create_random_name(prefix="aztest", length=24) + device_name = self.create_random_name(prefix="aztest", length=24) + + command = "iot central device create --app-id {} -d {} --device-name {}".format( + APP_ID, device_id, device_name + ) + checks = [ + self.check("approved", True), + self.check("displayName", device_name), + self.check("id", device_id), + ] + + instance_of = kwargs.get("instance_of") + if instance_of: + command = command + " --instance-of {}".format(instance_of) + checks.append(self.check("instanceOf", instance_of)) + + simulated = bool(kwargs.get("simulated")) + if simulated: + command = command + " --simulated" + + checks.append(self.check("simulated", simulated)) + + self.cmd(command, checks=checks) + return (device_id, device_name) + + def _create_users(self,): + + users = [] + for role in Role: + user_id = self.create_random_name(prefix="aztest", length=24) + email = user_id + "@microsoft.com" + command = "iot central user create --app-id {} --user-id {} -r {} --email {}".format( + APP_ID, user_id, role.name, email, + ) + + checks = [ + self.check("id", user_id), + self.check("email", email), + self.check("type", "EmailUser"), + self.check("roles[0].role", role.value), + ] + users.append(self.cmd(command, checks=checks).get_output_in_json()) + + return users + + def _delete_user(self, user_id) -> None: + self.cmd( + "iot central user delete --app-id {} --user-id {}".format(APP_ID, user_id), + checks=[self.check("result", "success")], + ) + + def _create_api_tokens(self,): + + tokens = [] + for role in Role: + token_id = self.create_random_name(prefix="aztest", length=24) + command = "iot central api-token create --app-id {} --token-id {} -r {}".format( + APP_ID, token_id, role.name, + ) + + checks = [ + self.check("id", token_id), + self.check("roles[0].role", role.value), + ] + + tokens.append(self.cmd(command, checks=checks).get_output_in_json()) + return tokens + + def _delete_api_token(self, token_id) -> None: + self.cmd( + "iot central api-token delete --app-id {} --token-id {}".format( + APP_ID, token_id + ), + checks=[self.check("result", "success")], + ) + + def _wait_for_provisioned(self, device_id): + command = "iot central device show --app-id {} -d {}".format(APP_ID, device_id) + while True: + result = self.cmd(command) + device = result.get_output_in_json() + + # return when its provisioned + if device.get("provisioned"): + return + + # wait 10 seconds for provisioning to complete + time.sleep(10) + + def _delete_device(self, device_id) -> None: + self.cmd( + "iot central device delete --app-id {} -d {}".format(APP_ID, device_id), + checks=[self.check("result", "success")], + ) + + def _create_device_template(self): + template = utility.process_json_arg( + device_template_path, argument_name="device_template_path" + ) + template_name = template["displayName"] + template_id = template_name + "id" + + self.cmd( + "iot central device-template create --app-id {} --device-template-id {} -k '{}'".format( + APP_ID, template_id, device_template_path + ), + checks=[ + self.check("displayName", template_name), + self.check("id", template_id), + ], + ) + + return (template_id, template_name) + + def _delete_device_template(self, template_id): + attempts = range(0, 10) + command = "iot central device-template delete --app-id {} --device-template-id {}".format( + APP_ID, template_id + ) + + # retry logic to delete the template + for _ in attempts: + try: + self.cmd(command, checks=[self.check("result", "success")]) + return + except: + time.sleep(10) + + def _get_credentials(self, device_id): + return self.cmd( + "iot central device show-credentials --app-id {} -d {}".format( + APP_ID, device_id + ) + ).get_output_in_json() + + def _get_validate_messages_output( + self, device_id, enqueued_time, duration=60, max_messages=1, asserts=None + ): + if not asserts: + asserts = [] + + output = self.command_execute_assert( + "iot central diagnostics validate-messages --app-id {} -d {} --et {} --duration {} --mm {} -y --style json".format( + APP_ID, device_id, enqueued_time, duration, max_messages + ), + asserts, + ) + + if not output: + output = "" + + return output + + def _get_monitor_events_output(self, device_id, enqueued_time, asserts=None): + if not asserts: + asserts = [] + + output = self.command_execute_assert( + "iot central diagnostics monitor-events -n {} -d {} --et {} --to 1 -y".format( + APP_ID, device_id, enqueued_time + ), + asserts, + ) + + if not output: + output = "" + + return output diff --git a/azext_iot/tests/central/test_iot_central_unit.py b/azext_iot/tests/central/test_iot_central_unit.py new file mode 100644 index 000000000..cd09b11bd --- /dev/null +++ b/azext_iot/tests/central/test_iot_central_unit.py @@ -0,0 +1,436 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import mock +import pytest +import json +import responses +import ast +from datetime import datetime +from knack.util import CLIError +from azure.cli.core.mock import DummyCli +from azext_iot.central import commands_device_twin +from azext_iot.central import commands_monitor +from azext_iot.central.providers import ( + CentralDeviceProvider, + CentralDeviceTemplateProvider, +) +from azext_iot.central.models.devicetwin import DeviceTwin +from azext_iot.central.models.template import Template +from azext_iot.monitor.property import PropertyMonitor +from azext_iot.monitor.models.enum import Severity +from azext_iot.tests.helpers import load_json +from azext_iot.tests.test_constants import FileNames +from azext_iot.constants import PNP_DTDLV2_COMPONENT_MARKER + +device_id = "mydevice" +app_id = "myapp" +device_twin_result = {"deviceId": "{}".format(device_id)} +resource = "shared_resource" + + +@pytest.fixture() +def fixture_cmd(mocker): + # Placeholder for later use + cmd = mock.MagicMock() + cmd.cli_ctx = DummyCli() + return cmd + + +@pytest.fixture() +def fixture_requests_post(mocker): + class MockJsonObject: + def get(self, _value): + return "" + + def value(self): + return "fixture_requests_post value" + + class ReturnObject: + def json(self): + return MockJsonObject() + + mock = mocker.patch("requests.post") + mock.return_value = ReturnObject() + + +@pytest.fixture() +def fixture_azure_profile(mocker): + mock = mocker.patch("azure.cli.core._profile.Profile.__init__") + mock.return_value = None + + mock_method = mocker.patch("azure.cli.core._profile.Profile.get_raw_token") + + class MockTokenWithGet: + def get(self, _value, _default): + return "value" + + mock_method.return_value = [ + ["raw token 0 - A", "raw token 0 -b", MockTokenWithGet()], + "raw token 1", + "raw token 2", + ] + + +@pytest.fixture() +def fixture_get_iot_central_tokens(mocker): + mock = mocker.patch("azext_iot.common._azure.get_iot_central_tokens") + + mock.return_value = { + "id": { + "eventhubSasToken": { + "hostname": "part1/part2/part3", + "entityPath": "entityPath", + "sasToken": "sasToken", + }, + "expiry": "0000", + "iothubTenantSasToken": { + "sasToken": "SharedAccessSignature sr=shared_resource&sig=" + }, + } + } + + +class TestCentralHelpers: + def test_get_iot_central_tokens(self, fixture_requests_post, fixture_azure_profile): + from azext_iot.common._azure import get_iot_central_tokens + + class Cmd: + cli_ctx = "" + + # Test to ensure get_iot_central_tokens calls requests.post and tokens are returned + assert ( + get_iot_central_tokens(Cmd(), "app_id", "", "api-uri").value() + == "fixture_requests_post value" + ) + + def test_get_aad_token(self, fixture_azure_profile): + from azext_iot.common.auth import get_aad_token + + class Cmd: + cli_ctx = "" + + # Test to ensure _get_aad_token is called and returns the right values based on profile.get_raw_tokens + assert get_aad_token(Cmd(), "resource") == { + "accessToken": "raw token 0 -b", + "expiresOn": "value", + "subscription": "raw token 1", + "tenant": "raw token 2", + "tokenType": "raw token 0 - A", + } + + +class TestDeviceTwinShow: + @pytest.fixture + def service_client( + self, mocked_response, fixture_cmd, fixture_get_iot_central_tokens + ): + mocked_response.add( + method=responses.GET, + url="https://{}/twins/{}".format(resource, device_id), + body=json.dumps(device_twin_result), + status=200, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_device_twin_show_calls_get_twin(self, service_client): + result = commands_device_twin.device_twin_show(fixture_cmd, device_id, app_id) + assert result == device_twin_result + + +class TestMonitorEvents: + @pytest.mark.parametrize("timeout, exception", [(-1, CLIError)]) + def test_monitor_events_invalid_args(self, timeout, exception, fixture_cmd): + with pytest.raises(exception): + commands_monitor.monitor_events(fixture_cmd, app_id, timeout=timeout) + + +class TestCentralDeviceProvider: + _device = load_json(FileNames.central_device_file) + _device_template = load_json(FileNames.central_device_template_file) + + @mock.patch("azext_iot.central.services.device_template") + @mock.patch("azext_iot.central.services.device") + def test_should_return_device(self, mock_device_svc, mock_device_template_svc): + # setup + provider = CentralDeviceProvider(cmd=None, app_id=app_id) + mock_device_svc.get_device.return_value = self._device + mock_device_template_svc.get_device_template.return_value = ( + self._device_template + ) + + # act + device = provider.get_device("someDeviceId") + # check that caching is working + device = provider.get_device("someDeviceId") + + # verify + # call counts should be at most 1 since the provider has a cache + assert mock_device_svc.get_device.call_count == 1 + assert mock_device_svc.get_device_template.call_count == 0 + assert device == self._device + + @mock.patch("azext_iot.central.services.device_template") + @mock.patch("azext_iot.central.services.device") + def test_should_return_device_template( + self, mock_device_svc, mock_device_template_svc + ): + # setup + provider = CentralDeviceTemplateProvider(cmd=None, app_id=app_id) + mock_device_svc.get_device.return_value = self._device + mock_device_template_svc.get_device_template.return_value = ( + self._device_template + ) + + # act + template = provider.get_device_template("someDeviceTemplate") + # check that caching is working + template = provider.get_device_template("someDeviceTemplate") + + # verify + # call counts should be at most 1 since the provider has a cache + assert mock_device_template_svc.get_device_template.call_count == 1 + assert template == self._device_template + + +class TestCentralPropertyMonitor: + _device_twin = load_json(FileNames.central_device_twin_file) + _duplicate_property_template = load_json( + FileNames.central_property_validation_template_file + ) + + @mock.patch("azext_iot.central.services.device_template") + @mock.patch("azext_iot.central.services.device") + def test_should_return_updated_properties( + self, mock_device_svc, mock_device_template_svc + ): + # setup + device_twin_data = json.dumps(self._device_twin) + raw_twin = ast.literal_eval( + device_twin_data.replace("current_time", datetime.now().isoformat()) + ) + + twin = DeviceTwin(raw_twin) + twin_next = DeviceTwin(raw_twin) + twin_next.reported_property.version = twin.reported_property.version + 1 + monitor = PropertyMonitor( + cmd=None, + app_id=app_id, + device_id=device_id, + token=None, + central_dns_suffix=None, + ) + result = monitor._compare_properties( + twin_next.reported_property, twin.reported_property + ) + assert len(result) == 3 + assert len(result["$iotin:urn_azureiot_Client_SDKInformation"]) == 3 + assert result["$iotin:urn_azureiot_Client_SDKInformation"]["language"] + assert result["$iotin:urn_azureiot_Client_SDKInformation"]["version"] + assert result["$iotin:urn_azureiot_Client_SDKInformation"]["vendor"] + + assert len(result["$iotin:deviceinfo"]) == 8 + assert result["$iotin:deviceinfo"]["manufacturer"] + assert result["$iotin:deviceinfo"]["model"] + assert result["$iotin:deviceinfo"]["osName"] + assert result["$iotin:deviceinfo"]["processorArchitecture"] + assert result["$iotin:deviceinfo"]["swVersion"] + assert result["$iotin:deviceinfo"]["processorManufacturer"] + assert result["$iotin:deviceinfo"]["totalStorage"] + assert result["$iotin:deviceinfo"]["totalMemory"] + + assert len(result["$iotin:settings"]) == 1 + assert result["$iotin:settings"]["fanSpeed"] + + @mock.patch("azext_iot.central.services.device_template") + @mock.patch("azext_iot.central.services.device") + def test_should_return_no_properties( + self, mock_device_svc, mock_device_template_svc + ): + # test to check that no property updates are reported when version is not upadted + # setup + device_twin_data = json.dumps(self._device_twin) + raw_twin = ast.literal_eval( + device_twin_data.replace("current_time", datetime.now().isoformat()) + ) + twin = DeviceTwin(raw_twin) + twin_next = DeviceTwin(raw_twin) + monitor = PropertyMonitor( + cmd=None, + app_id=app_id, + device_id=device_id, + token=None, + central_dns_suffix=None, + ) + result = monitor._compare_properties( + twin_next.reported_property, twin.reported_property + ) + assert result is None + + @mock.patch("azext_iot.central.services.device_template") + @mock.patch("azext_iot.central.services.device") + def test_validate_properties_declared_multiple_interfaces( + self, mock_device_svc, mock_device_template_svc + ): + + # setup + mock_device_template_svc.get_device_template.return_value = Template( + self._duplicate_property_template + ) + + monitor = PropertyMonitor( + cmd=None, + app_id=app_id, + device_id=device_id, + token=None, + central_dns_suffix=None, + ) + + model = {"Model": "test_model"} + + issues = monitor._validate_payload_against_entities( + model, list(model.keys())[0], Severity.warning, + ) + + assert ( + issues[0].details == "Duplicate property: 'Model' found under following " + "interfaces ['urn:sampleApp:groupOne_bz:_rpgcmdpo:1', 'urn:sampleApp:groupTwo_bz:myxqftpsr:2', " + "'urn:sampleApp:groupThree_bz:myxqftpsr:2'] " + "in the device model. Either provide the interface name as part " + "of the device payload or make the propery name unique in the device model" + ) + + version = {"OsName": "test_osName"} + + issues = monitor._validate_payload_against_entities( + version, list(version.keys())[0], Severity.warning, + ) + + assert len(issues) == 0 + + @mock.patch("azext_iot.central.services.device_template") + @mock.patch("azext_iot.central.services.device") + def test_validate_properties_name_miss_under_interface( + self, mock_device_svc, mock_device_template_svc + ): + + # setup + mock_device_template_svc.get_device_template.return_value = Template( + self._duplicate_property_template + ) + + monitor = PropertyMonitor( + cmd=None, + app_id=app_id, + device_id=device_id, + token=None, + central_dns_suffix=None, + ) + + # invalid interface / property + definition = {"definition": "test_definition"} + + issues = monitor._validate_payload_against_entities( + definition, list(definition.keys())[0], Severity.warning, + ) + + assert ( + issues[0].details + == "Device is sending data that has not been defined in the device template." + " Following capabilities have NOT been defined in the device template '['definition']'." + " Following capabilities have been defined in the device template (grouped by interface)" + " '{'urn:sampleApp:groupOne_bz:2': ['addRootProperty', 'addRootPropertyReadOnly', 'addRootProperty2']," + " 'urn:sampleApp:groupOne_bz:_rpgcmdpo:1': ['Model', 'Version', 'TotalStorage']," + " 'urn:sampleApp:groupTwo_bz:myxqftpsr:2': ['Model', 'Manufacturer']," + " 'urn:sampleApp:groupThree_bz:myxqftpsr:2': ['Manufacturer', 'Version', 'Model', 'OsName']}'. " + ) + + @mock.patch("azext_iot.central.services.device_template") + @mock.patch("azext_iot.central.services.device") + def test_validate_properties_severity_level( + self, mock_device_svc, mock_device_template_svc + ): + + # setup + mock_device_template_svc.get_device_template.return_value = Template( + self._duplicate_property_template + ) + + monitor = PropertyMonitor( + cmd=None, + app_id=app_id, + device_id=device_id, + token=None, + central_dns_suffix=None, + ) + + # severity level info + definition = {"definition": "test_definition"} + + issues = monitor._validate_payload_against_entities( + definition, list(definition.keys())[0], Severity.info, + ) + + assert ( + issues[0].details + == "Device is sending data that has not been defined in the device template. " + "Following capabilities have NOT been defined in the device template " + "'['definition']'. Following capabilities have been defined in the device template " + "(grouped by interface) '{'urn:sampleApp:groupOne_bz:2': " + "['addRootProperty', 'addRootPropertyReadOnly', 'addRootProperty2'], " + "'urn:sampleApp:groupOne_bz:_rpgcmdpo:1': ['Model', 'Version', 'TotalStorage'], " + "'urn:sampleApp:groupTwo_bz:myxqftpsr:2': ['Model', 'Manufacturer'], " + "'urn:sampleApp:groupThree_bz:myxqftpsr:2': ['Manufacturer', 'Version', 'Model', 'OsName']}'. " + ) + + # severity level error + issues = monitor._validate_payload_against_entities( + definition, list(definition.keys())[0], Severity.error, + ) + + assert len(issues) == 0 + + @mock.patch("azext_iot.central.services.device_template") + @mock.patch("azext_iot.central.services.device") + def test_validate_properties_name_miss_under_component( + self, mock_device_svc, mock_device_template_svc + ): + + # setup + mock_device_template_svc.get_device_template.return_value = Template( + self._duplicate_property_template + ) + + monitor = PropertyMonitor( + cmd=None, + app_id=app_id, + device_id=device_id, + token=None, + central_dns_suffix=None, + ) + + # invalid component property + definition = { + PNP_DTDLV2_COMPONENT_MARKER: "c", + "data": {"definition": "test_definition"}, + } + + issues = monitor._validate_payload_against_entities( + definition, list(definition.keys())[0], Severity.warning, + ) + + assert ( + issues[0].details + == "Device is sending data that has not been defined in the device template. " + "Following capabilities have NOT been defined in the device template '['data']'. " + "Following capabilities have been defined in the device template (grouped by components) " + "'{'_rpgcmdpo': ['component1Prop', 'testComponent', 'component1PropReadonly', 'component1Prop2'], " + "'RS40OccupancySensorV36fy': ['component2prop', 'testComponent', 'component2PropReadonly', " + "'component2Prop2', 'component1Telemetry']}'. " + ) diff --git a/azext_iot/tests/central/test_iot_central_validator_unit.py b/azext_iot/tests/central/test_iot_central_validator_unit.py new file mode 100644 index 000000000..75bafe4bc --- /dev/null +++ b/azext_iot/tests/central/test_iot_central_validator_unit.py @@ -0,0 +1,368 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import pytest +import collections + +from azext_iot.central.models.template import Template +from azext_iot.monitor.central_validator import validate, extract_schema_type + +from azext_iot.tests.helpers import load_json +from azext_iot.tests.test_constants import FileNames + + +class TestTemplateValidations: + def test_template_interface_list(self): + expected_interface_list = [ + "urn:sampleApp:groupOne_bz:_rpgcmdpo:1", + "urn:sampleApp:groupTwo_bz:myxqftpsr:2", + "urn:sampleApp:groupThree_bz:myxqftpsr:2", + "urn:sampleApp:groupOne_bz:2", + ] + template = Template( + load_json(FileNames.central_property_validation_template_file) + ) + + assert collections.Counter(template.interfaces.keys()) == collections.Counter( + expected_interface_list + ) + + def test_template_component_list(self): + expected_component_list = [ + "_rpgcmdpo", + "RS40OccupancySensorV36fy", + ] + template = Template( + load_json(FileNames.central_property_validation_template_file) + ) + + assert collections.Counter(template.components.keys()) == collections.Counter( + expected_component_list + ) + + +class TestExtractSchemaType: + def test_extract_schema_type_component(self): + expected_mapping = { + "component1Prop": "boolean", + "testComponent": "boolean", + "component1PropReadonly": "boolean", + "component1Prop2": "boolean", + } + template = Template( + load_json(FileNames.central_property_validation_template_file) + ) + for key, val in expected_mapping.items(): + schema = template.get_schema(key, is_component=True) + schema_type = extract_schema_type(schema) + assert schema_type == val + + def test_extract_schema_type_component_identifier(self): + expected_mapping = { + "component2prop": "boolean", + "component2Prop2": "boolean", + "testComponent": "boolean", + "component2PropReadonly": "boolean", + } + template = Template( + load_json(FileNames.central_property_validation_template_file) + ) + for key, val in expected_mapping.items(): + schema = template.get_schema( + key, is_component=True, identifier="RS40OccupancySensorV36fy" + ) + schema_type = extract_schema_type(schema) + assert schema_type == val + + def test_extract_schema_type(self): + expected_mapping = { + "Bool": "boolean", + "Date": "date", + "DateTime": "dateTime", + "Double": "double", + "Duration": "duration", + "IntEnum": "Enum", + "StringEnum": "Enum", + "Float": "float", + "Geopoint": "geopoint", + "Long": "long", + "Object": "Object", + "String": "string", + "Time": "time", + "Vector": "vector", + } + template = Template(load_json(FileNames.central_device_template_file)) + for key, val in expected_mapping.items(): + schema = template.get_schema(key) + schema_type = extract_schema_type(schema) + assert schema_type == val + + +class TestPrimitiveValidations: + @pytest.mark.parametrize( + "value, expected_result", + [ + (True, True), + (False, True), + ("False", False), + ("True", False), + (1, False), + (0, False), + ], + ) + def test_boolean(self, value, expected_result): + assert validate({"schema": "boolean"}, value) == expected_result + + @pytest.mark.parametrize( + "value, expected_result", + [(1, True), (-1, True), (1.1, True), ("1", False), ("1.1", False)], + ) + def test_double_float_long(self, value, expected_result): + assert validate({"schema": "double"}, value) == expected_result + assert validate({"schema": "float"}, value) == expected_result + assert validate({"schema": "long"}, value) == expected_result + + @pytest.mark.parametrize( + "value, expected_result", + [(1, True), (-1, True), (1.1, False), ("1", False), ("1.1", False)], + ) + def test_int(self, value, expected_result): + assert validate({"schema": "integer"}, value) == expected_result + + @pytest.mark.parametrize( + "value, expected_result", + [("a", True), ("asd", True), (1, False), (True, False)], + ) + def test_str(self, value, expected_result): + assert validate({"schema": "string"}, value) == expected_result + + # by convention we have stated that an empty payload is valid + def test_empty(self): + assert validate(None, None) + + +# none of these are valid anything in ISO 8601 +BAD_ARRAY = ["asd", "", 123.4, 123, True, False] + + +class TestDateTimeValidations: + # Success suite + @pytest.mark.parametrize( + "to_validate", ["20200101", "20200101Z", "2020-01-01", "2020-01-01Z"] + ) + def test_is_iso8601_date_pass(self, to_validate): + assert validate({"schema": "date"}, to_validate) + + @pytest.mark.parametrize( + "to_validate", + [ + "20200101T00:00:00", + "20200101T000000", + "2020-01-01T00:00:00", + "2020-01-01T00:00:00Z", + "2020-01-01T00:00:00.00", + "2020-01-01T00:00:00.00Z", + "2020-01-01T00:00:00.00+08:30", + ], + ) + def test_is_iso8601_datetime_pass(self, to_validate): + assert validate({"schema": "dateTime"}, to_validate) + + @pytest.mark.parametrize("to_validate", ["P32DT7.592380349524318S", "P32DT7S"]) + def test_is_iso8601_duration_pass(self, to_validate): + assert validate({"schema": "duration"}, to_validate) + + @pytest.mark.parametrize( + "to_validate", ["00:00:00+08:30", "00:00:00Z", "00:00:00.123Z"] + ) + def test_is_iso8601_time_pass(self, to_validate): + assert validate({"schema": "time"}, to_validate) + + # Failure suite + @pytest.mark.parametrize( + "to_validate", ["2020-13-35", *BAD_ARRAY], + ) + def test_is_iso8601_date_fail(self, to_validate): + assert not validate({"schema": "date"}, to_validate) + + @pytest.mark.parametrize("to_validate", ["2020-13-35", "2020-00-00T", *BAD_ARRAY]) + def test_is_iso8601_datetime_fail(self, to_validate): + assert not validate({"schema": "dateTime"}, to_validate) + + @pytest.mark.parametrize("to_validate", ["2020-01", *BAD_ARRAY]) + def test_is_iso8601_duration_fail(self, to_validate): + assert not validate({"schema": "duration"}, to_validate) + + @pytest.mark.parametrize("to_validate", [*BAD_ARRAY]) + def test_is_iso8601_time_fail(self, to_validate): + assert not validate({"schema": "time"}, to_validate) + + +class TestPredefinedComplexType: + @pytest.mark.parametrize( + "value, expected_result", + [ + ({"lat": 123, "lon": 123, "alt": 123}, True), + ({"lat": 123.123, "lon": 123.123, "alt": 123.123}, True), + ({"lat": 123, "lon": 123}, True), + ({"lat": 123}, False), + ({"lon": 123}, False), + ({"alt": 123}, False), + ({"lat": "123.123", "lon": "123.123", "alt": "123.123"}, False), + ({"x": 123, "y": 123, "z": 123}, False), + ({"x": 123.123, "y": 123.123, "z": 123.123}, False), + ], + ) + def test_geopoint(self, value, expected_result): + assert validate({"schema": "geopoint"}, value) == expected_result + + @pytest.mark.parametrize( + "value, expected_result", + [ + ({"x": 123, "y": 123, "z": 123}, True), + ({"x": 123.123, "y": 123.123, "z": 123.123}, True), + ({"lat": 123, "lon": 123, "alt": 123}, False), + ({"lat": 123.123, "lon": 123.123, "alt": 123.123}, False), + ({"lat": 123, "lon": 123}, False), + ({"x": "123", "y": "123", "z": "123"}, False), + ({"x": 123.123, "y": 123.123}, False), + ({"x": 123.123, "z": 123.123}, False), + ({"y": 123.123, "z": 123.123}, False), + ], + ) + def test_vector(self, value, expected_result): + assert validate({"schema": "vector"}, value) == expected_result + + +class TestComplexType: + @pytest.mark.parametrize( + "value, expected_result", + [(1, True), (2, True), (3, False), ("1", False), ("2", False)], + ) + def test_int_enum(self, value, expected_result): + template = Template(load_json(FileNames.central_device_template_file)) + schema = template.get_schema("IntEnum") + assert validate(schema, value) == expected_result + + @pytest.mark.parametrize( + "value, expected_result", + [("A", True), ("B", True), ("C", False), (1, False), (2, False)], + ) + def test_str_enum(self, value, expected_result): + template = Template(load_json(FileNames.central_device_template_file)) + schema = template.get_schema("StringEnum") + assert validate(schema, value) == expected_result + + @pytest.mark.parametrize( + "value, expected_result", + [ + ({"Double": 123}, True), + ({"Double": "123"}, False), + ({"double": 123}, False), + ({"asd": 123}, False), + ], + ) + def test_object_simple(self, value, expected_result): + template = Template(load_json(FileNames.central_device_template_file)) + schema = template.get_schema("Object") + assert validate(schema, value) == expected_result + + @pytest.mark.parametrize( + "value, expected_result", + [ + ({"LayerC": {"Depth1C": {"SomeTelemetry": 100}}}, True,), + ({"LayerC": {"Depth1C": {"SomeTelemetry": 100.001}}}, True,), + ({"LayerC": {"Depth1C": {"SomeTelemetry": "100"}}}, False,), + ({"LayerC": {"Depth1C": {"sometelemetry": 100.001}}}, False,), + ({"LayerC": {"depth1c": {"SomeTelemetry": 100.001}}}, False,), + ({"layerc": {"Depth1C": {"SomeTelemetry": 100.001}}}, False,), + ], + ) + def test_object_medium(self, value, expected_result): + template = Template( + load_json(FileNames.central_deeply_nested_device_template_file) + ) + schema = template.get_schema("RidiculousObject") + assert validate(schema, value) == expected_result + + @pytest.mark.parametrize( + "value, expected_result", + [ + ( + { + "LayerA": { + "Depth1A": { + "Depth2": { + "Depth3": { + "Depth4": { + "DeepestComplexEnum": 1, + "DeepestVector": {"x": 1, "y": 2, "z": 3}, + "DeepestGeopoint": { + "lat": 1, + "lon": 2, + "alt": 3, + }, + "Depth5": {"Depth6Double": 123}, + } + } + } + } + } + }, + True, + ), + ( + { + "LayerA": { + "Depth1A": { + "Depth2": { + "Depth3": { + "Depth4": { + "DeepestComplexEnum": 1, + "DeepestVector": {"x": 1, "y": 2, "z": 3}, + "DeepestGeopoint": { + "lat": "1", + "lon": 2, + "alt": 3, + }, + } + } + } + } + } + }, + False, + ), + ( + { + "LayerA": { + "Depth1A": { + "Depth2": { + "Depth3": { + "Depth3": { + "DeepestComplexEnum": 1, + "DeepestVector": {"x": 1, "y": 2, "z": 3}, + "DeepestGeopoint": { + "lat": 1, + "lon": 2, + "alt": 3, + }, + } + } + } + } + } + }, + False, + ), + ], + ) + def test_object_deep(self, value, expected_result): + template = Template( + load_json(FileNames.central_deeply_nested_device_template_file) + ) + schema = template.get_schema("RidiculousObject") + assert validate(schema, value) == expected_result diff --git a/azext_iot/tests/conftest.py b/azext_iot/tests/conftest.py index 47de77eeb..6860b4c6c 100644 --- a/azext_iot/tests/conftest.py +++ b/azext_iot/tests/conftest.py @@ -14,9 +14,12 @@ from azure.cli.core.commands import AzCliCommand from azure.cli.core.mock import DummyCli -path_iot_hub_service_factory = "azext_iot.common._azure.iot_hub_service_factory" +path_iot_hub_service_factory = "azext_iot._factory.iot_hub_service_factory" path_service_client = "msrest.service_client.ServiceClient.send" -path_ghcs = "azext_iot.operations.hub.get_iot_hub_connection_string" +path_ghcs = "azext_iot.iothub.providers.discovery.IotHubDiscovery.get_target" +path_discovery_init = ( + "azext_iot.iothub.providers.discovery.IotHubDiscovery._initialize_client" +) path_sas = "azext_iot._factory.SasTokenAuthentication" path_mqtt_client = "azext_iot.operations._mqtt.mqtt.Client" path_iot_hub_monitor_events_entrypoint = ( @@ -48,18 +51,6 @@ def generate_cs( return result.lower() if lower_case else result -@pytest.fixture() -def fixture_cmd2(mocker): - cli = DummyCli() - cli.loader = mocker.MagicMock() - cli.loader.cli_ctx = cli - - def test_handler1(): - pass - - return AzCliCommand(cli.loader, 'iot-extension command', test_handler1) - - # Sets current working directory to the directory of the executing file @pytest.fixture() def set_cwd(request): @@ -68,16 +59,14 @@ def set_cwd(request): @pytest.fixture() def fixture_cmd(mocker): - # Placeholder for later use - mocker.patch(path_iot_hub_service_factory) - cmd = mocker.MagicMock(name="cli cmd context") - return cmd + cli = DummyCli() + cli.loader = mocker.MagicMock() + cli.loader.cli_ctx = cli + def test_handler1(): + pass -@pytest.fixture() -def fixture_events_uamqp_sendclient(mocker): - from azext_iot.operations.events3._events import uamqp - return mocker.patch.object(uamqp, "SendClient", autospec=True) + return AzCliCommand(cli.loader, "iot-extension command", test_handler1) @pytest.fixture() @@ -99,6 +88,9 @@ def serviceclient_generic_error(mocker, fixture_ghcs, fixture_sas, request): def fixture_ghcs(mocker): ghcs = mocker.patch(path_ghcs) ghcs.return_value = mock_target + mocker.patch(path_iot_hub_service_factory) + mocker.patch(path_discovery_init) + return ghcs @@ -142,13 +134,17 @@ def fixture_monitor_events_entrypoint(mocker): # TODO: To be deprecated asap. Leverage mocked_response fixture for this functionality. -def build_mock_response(mocker=None, status_code=200, payload=None, headers=None, **kwargs): +def build_mock_response( + mocker=None, status_code=200, payload=None, headers=None, **kwargs +): try: from unittest.mock import MagicMock except: from mock import MagicMock - response = mocker.MagicMock(name="response") if mocker else MagicMock(name="response") + response = ( + mocker.MagicMock(name="response") if mocker else MagicMock(name="response") + ) response.status_code = status_code del response.context del response._attribute_map @@ -161,8 +157,8 @@ def build_mock_response(mocker=None, status_code=200, payload=None, headers=None response.text = _payload_str response.internal_response.json.return_value = json.loads(_payload_str) else: - response.text.return_value = '' - response.text = '' + response.text.return_value = "" + response.text = "" headers_get_side_effect = kwargs.get("headers_get_side_effect") if headers_get_side_effect: @@ -183,6 +179,28 @@ def get_context_path(base_path, *paths): return base_path +''' TODO: Possibly expand for future use +fake_oauth_response = responses.Response( + method=responses.POST, + url=re.compile("https://login.microsoftonline.com/(.+)/oauth2/token"), + body=json.dumps({ + "token_type": "Bearer", + "scope": "user_impersonation", + "expires_in": "90000", + "ext_expires_in": "90000", + "expires_on": "979778250", + "not_before": "979739250", + "resource": "localhost", + "access_token": "totally_fake_access_token", + "refresh_token": "totally_fake_refresh_token", + "foci": "1" + }), + status=200, + content_type="application/json", +) +''' + + @pytest.fixture def mocked_response(): with responses.RequestsMock() as rsps: @@ -192,13 +210,40 @@ def mocked_response(): @pytest.fixture(params=[400, 401, 500]) def service_client_generic_errors(mocked_response, fixture_ghcs, request): def error_callback(_): - return (request.param, {'Content-Type': "application/json; charset=utf-8"}, json.dumps({"error": "something failed"})) + return ( + request.param, + {"Content-Type": "application/json; charset=utf-8"}, + json.dumps({"error": "something failed"}), + ) any_endpoint = r"^https:\/\/.+" with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: - rsps.add_callback(callback=error_callback, method=responses.GET, url=re.compile(any_endpoint)) - rsps.add_callback(callback=error_callback, method=responses.PUT, url=re.compile(any_endpoint)) - rsps.add_callback(callback=error_callback, method=responses.POST, url=re.compile(any_endpoint)) - rsps.add_callback(callback=error_callback, method=responses.DELETE, url=re.compile(any_endpoint)) - rsps.add_callback(callback=error_callback, method=responses.PATCH, url=re.compile(any_endpoint)) + rsps.add_callback( + callback=error_callback, method=responses.GET, url=re.compile(any_endpoint) + ) + rsps.add_callback( + callback=error_callback, method=responses.PUT, url=re.compile(any_endpoint) + ) + rsps.add_callback( + callback=error_callback, method=responses.POST, url=re.compile(any_endpoint) + ) + rsps.add_callback( + callback=error_callback, + method=responses.DELETE, + url=re.compile(any_endpoint), + ) + rsps.add_callback( + callback=error_callback, + method=responses.PATCH, + url=re.compile(any_endpoint), + ) yield rsps + + +@pytest.fixture() +def fixture_mock_aics_token(mocker): + patch = mocker.patch( + "azext_iot.product.providers.auth.AICSAuthentication.generate_token" + ) + patch.return_value = "Bearer token" + return patch diff --git a/azext_iot/tests/digitaltwins/__init__.py b/azext_iot/tests/digitaltwins/__init__.py new file mode 100644 index 000000000..e36950edb --- /dev/null +++ b/azext_iot/tests/digitaltwins/__init__.py @@ -0,0 +1,182 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import pytest +from azext_iot.tests.generators import generate_generic_id +from azext_iot.tests.settings import DynamoSettings +from azure.cli.testsdk import LiveScenarioTest +from azext_iot.common.embedded_cli import EmbeddedCLI +from knack.log import get_logger + +logger = get_logger(__name__) + + +MOCK_RESOURCE_TAGS = "a=b c=d" +MOCK_RESOURCE_TAGS_DICT = {"a": "b", "c": "d"} +MOCK_DEAD_LETTER_ENDPOINT = "https://accountname.blob.core.windows.net/containerName" +MOCK_DEAD_LETTER_SECRET = "{}?sasToken".format(MOCK_DEAD_LETTER_ENDPOINT) +REGION_RESOURCE_LIMIT = 10 +REGION_LIST = ["westus2", "westcentralus", "eastus2", "eastus", "eastus2euap"] + + +def generate_resource_id(): + return "dtcli-{}".format(generate_generic_id()) + + +class DTLiveScenarioTest(LiveScenarioTest): + role_map = { + "owner": "Azure Digital Twins Data Owner", + "reader": "Azure Digital Twins Data Reader", + } + + def __init__(self, test_scenario): + assert test_scenario + + super(DTLiveScenarioTest, self).__init__(test_scenario) + self.settings = DynamoSettings( + opt_env_set=["azext_iot_testrg", "azext_dt_region"] + ) + self.embedded_cli = EmbeddedCLI() + self._bootup_scenario() + + def _bootup_scenario(self): + self._is_provider_registered() + self._init_basic_env_vars() + self.tracked_instances = [] + + def _is_provider_registered(self): + result = self.cmd( + "provider show --namespace 'Microsoft.DigitalTwins' --query 'registrationState'" + ) + if '"registered"' in result.output.lower(): + return + + pytest.skip( + "Microsoft.DigitalTwins provider not registered. " + "Run 'az provider register --namespace Microsoft.DigitalTwins'" + ) + + def _init_basic_env_vars(self): + self._force_region = self.settings.env.azext_dt_region + if self._force_region and not self.is_region_available(self._force_region): + raise RuntimeError( + "Forced region: {} does not have capacity.".format(self._force_region) + ) + + self.region = ( + self._force_region if self._force_region else self.get_available_region() + ) + self.rg = self.settings.env.azext_iot_testrg + if not self.rg: + pytest.skip( + "Digital Twins CLI tests requires at least 'azext_iot_testrg' for resource deployment." + ) + self.rg_region = self.embedded_cli.invoke( + "group show --name {}".format(self.rg) + ).as_json()["location"] + + @property + def current_user(self): + return self.embedded_cli.invoke("account show").as_json()["user"]["name"] + + @property + def current_subscription(self): + return self.embedded_cli.invoke("account show").as_json()["id"] + + def wait_for_capacity( + self, region=None, capacity: int = 1, wait_in_sec: int = 10, interval: int = 3 + ): + from time import sleep + + target_region = region + if not target_region: + target_region = self.region + + if self.is_region_available(region=target_region, capacity=capacity): + return + + while interval >= 1: + logger.info("Waiting :{} (sec) for capacity.") + sleep(wait_in_sec) + if self.is_region_available(region=target_region, capacity=capacity): + return + interval = interval - 1 + + raise RuntimeError( + "Unavailable region DT capacity. wait(sec): {}, interval: {}, region: {}, capacity: {}".format( + wait_in_sec, interval, target_region, capacity + ) + ) + + def is_region_available(self, region, capacity: int = 1): + region_capacity = self.calculate_region_capacity + return (region_capacity.get(region, 0) + capacity) <= REGION_RESOURCE_LIMIT + + @property + def calculate_region_capacity(self) -> dict: + instances = self.instances = self.embedded_cli.invoke("dt list").as_json() + capacity_map = {} + for instance in instances: + cap_val = capacity_map.get(instance["location"], 0) + cap_val = cap_val + 1 + capacity_map[instance["location"]] = cap_val + + for region in REGION_LIST: + if region not in capacity_map: + capacity_map[region] = 0 + + return capacity_map + + def get_available_region(self, capacity: int = 1, skip_regions: list = None) -> str: + if not skip_regions: + skip_regions = [] + + region_capacity = self.calculate_region_capacity + + while region_capacity: + region = min(region_capacity, key=region_capacity.get) + if region not in skip_regions: + if region_capacity[region] + capacity <= REGION_RESOURCE_LIMIT: + return region + region_capacity.pop(region, None) + + raise RuntimeError( + "There are no available regions with capacity: {} for provision DT instances in subscription: {}".format( + capacity, self.current_subscription + ) + ) + + def track_instance(self, instance: dict): + self.tracked_instances.append((instance["name"], instance["resourceGroup"])) + + def tearDown(self): + for instance in self.tracked_instances: + self.embedded_cli.invoke( + "dt delete -n {} -g {} -y --no-wait".format(instance[0], instance[1]) + ) + + # Needed because the DT service will indicate provisioning is finished before it actually is. + def wait_for_hostname( + self, instance: dict, wait_in_sec: int = 10, interval: int = 4 + ): + from time import sleep + + while interval >= 1: + logger.info( + "Waiting :{} (sec) for provisioning to complete.".format(wait_in_sec) + ) + sleep(wait_in_sec) + interval = interval - 1 + refereshed_instance = self.embedded_cli.invoke( + "dt show -n {} -g {}".format( + instance["name"], instance["resourceGroup"] + ) + ).as_json() + + if refereshed_instance.get("hostName") and refereshed_instance["provisioningState"] == "Succeeded": + return refereshed_instance + + return instance diff --git a/azext_iot/tests/digitaltwins/models/Floor.json b/azext_iot/tests/digitaltwins/models/Floor.json new file mode 100644 index 000000000..2f1c00aa4 --- /dev/null +++ b/azext_iot/tests/digitaltwins/models/Floor.json @@ -0,0 +1,22 @@ +{ + "@id": "dtmi:com:example:Floor;1", + "@type": "Interface", + "@context": "dtmi:dtdl:context;2", + "displayName": "Floor", + "contents": [{ + "@type": "Relationship", + "target": "dtmi:com:example:Room;1", + "name": "contains", + "properties": [{ + "@type": "Property", + "name": "ownershipUser", + "schema": "string" + }, + { + "@type": "Property", + "name": "ownershipDepartment", + "schema": "string" + } + ] + }] +} \ No newline at end of file diff --git a/azext_iot/tests/digitaltwins/models/nested/Room.json b/azext_iot/tests/digitaltwins/models/nested/Room.json new file mode 100644 index 000000000..1df6992e2 --- /dev/null +++ b/azext_iot/tests/digitaltwins/models/nested/Room.json @@ -0,0 +1,22 @@ +{ + "@id": "dtmi:com:example:Room;1", + "@type": "Interface", + "@context": "dtmi:dtdl:context;2", + "displayName": "Room", + "contents": [{ + "@type": "Property", + "name": "Temperature", + "schema": "double" + }, + { + "@type": "Property", + "name": "Humidity", + "schema": "double" + }, + { + "@type": "Component", + "name": "Thermostat", + "schema": "dtmi:com:example:Thermostat;1" + } + ] +} \ No newline at end of file diff --git a/azext_iot/tests/digitaltwins/models/nested/deeper/Thermostat.json b/azext_iot/tests/digitaltwins/models/nested/deeper/Thermostat.json new file mode 100644 index 000000000..803dc5e84 --- /dev/null +++ b/azext_iot/tests/digitaltwins/models/nested/deeper/Thermostat.json @@ -0,0 +1,18 @@ +{ + "@id": "dtmi:com:example:Thermostat;1", + "@type": "Interface", + "displayName": "Thermostat", + "contents": [{ + "@type": "Telemetry", + "name": "temp", + "schema": "double" + }, + { + "@type": "Property", + "name": "setPointTemp", + "writable": true, + "schema": "double" + } + ], + "@context": "dtmi:dtdl:context;2" +} \ No newline at end of file diff --git a/azext_iot/tests/digitaltwins/test_dt_model_lifecycle_int.py b/azext_iot/tests/digitaltwins/test_dt_model_lifecycle_int.py new file mode 100644 index 000000000..9fce3100f --- /dev/null +++ b/azext_iot/tests/digitaltwins/test_dt_model_lifecycle_int.py @@ -0,0 +1,197 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import pytest +import json +from time import sleep +from knack.log import get_logger +from azext_iot.common.utility import ( + scantree, + process_json_arg, +) +from . import DTLiveScenarioTest +from . import generate_resource_id + +logger = get_logger(__name__) + + +@pytest.mark.usefixtures("set_cwd") +class TestDTModelLifecycle(DTLiveScenarioTest): + def __init__(self, test_case): + super(TestDTModelLifecycle, self).__init__(test_case) + + def test_dt_models(self): + self.wait_for_capacity() + + instance_name = generate_resource_id() + models_directory = "./models" + inline_model = "./models/Floor.json" + component_dtmi = "dtmi:com:example:Thermostat;1" + room_dtmi = "dtmi:com:example:Room;1" + + create_output = self.cmd( + "dt create -n {} -g {} -l {}".format(instance_name, self.rg, self.region) + ).get_output_in_json() + self.track_instance(create_output) + self.wait_for_hostname(create_output) + + self.cmd( + "dt role-assignment create -n {} -g {} --assignee {} --role '{}'".format( + instance_name, self.rg, self.current_user, self.role_map["owner"] + ) + ) + + # Wait for RBAC to catch-up + sleep(60) + + create_models_output = self.cmd( + "dt model create -n {} --from-directory '{}'".format( + instance_name, models_directory + ) + ).get_output_in_json() + + assert_create_models_attributes( + create_models_output, directory_path=models_directory + ) + + list_models_output = self.cmd( + "dt model list -n {}".format(instance_name) + ).get_output_in_json() + assert len(list_models_output) == len(create_models_output) + for model in list_models_output: + assert model["id"] + + list_models_output = self.cmd( + "dt model list -n {} -g {} --definition".format(instance_name, self.rg) + ).get_output_in_json() + assert len(list_models_output) == len(create_models_output) + for model in list_models_output: + assert model["id"] + assert model["model"] + + model_dependencies_output = self.cmd( + "dt model list -n {} -g {} --dependencies-for '{}'".format( + instance_name, + self.rg, + room_dtmi, + ) + ).get_output_in_json() + assert len(model_dependencies_output) == 2 + + for model in create_models_output: + model_show_output = self.cmd( + "dt model show -n {} --dtmi '{}'".format(instance_name, model["id"]) + ).get_output_in_json() + assert model_show_output["id"] == model["id"] + + model_show_def_output = self.cmd( + "dt model show -n {} -g {} --dtmi '{}' --definition".format( + instance_name, self.rg, model["id"] + ) + ).get_output_in_json() + + assert model_show_def_output["id"] == model["id"] + assert model_show_def_output["model"] + assert model_show_def_output["model"]["@id"] == model["id"] + + model_json = process_json_arg(inline_model, "models") + model_id = model_json["@id"] + inc_model_id = _increment_model_id(model_id) + model_json["@id"] = inc_model_id + self.kwargs["modelJson"] = json.dumps(model_json) + create_models_inline_output = self.cmd( + "dt model create -n {} --models '{}'".format(instance_name, "{modelJson}") + ).get_output_in_json() + assert create_models_inline_output[0]["id"] == inc_model_id + + update_model_output = self.cmd( + "dt model update -n {} --dtmi '{}' --decommission".format( + instance_name, inc_model_id + ) + ).get_output_in_json() + assert update_model_output["id"] == inc_model_id + assert update_model_output["decommissioned"] is True + + list_models_output = self.cmd( + "dt model list -n {}".format(instance_name) + ).get_output_in_json() + + # Delete non-referenced models first + for model in list_models_output: + if model["id"] != component_dtmi: + self.cmd( + "dt model delete -n {} --dtmi {}".format(instance_name, model["id"]) + ) + + # Now referenced component + self.cmd( + "dt model delete -n {} --dtmi {}".format(instance_name, component_dtmi) + ) + + assert ( + len( + self.cmd( + "dt model list -n {}".format(instance_name) + ).get_output_in_json() + ) + == 0 + ) + + +def assert_create_models_attributes(result, directory_path=None, models=None): + if not any([directory_path, models]): + raise ValueError("Provide directory_path or models.") + + local_test_models = [] + if directory_path: + local_test_models = _get_models_from_directory(directory_path) + + assert len(result) == len(local_test_models) + + for m in result: + local_model = [model for model in local_test_models if model["id"] == m["id"]] + assert len(local_model) == 1 + assert m["id"] == local_model[0]["id"] + + +def _get_models_from_directory(from_directory): + payload = [] + for entry in scantree(from_directory): + if not entry.name.endswith(".json"): + logger.debug( + "Skipping {} - model file must end with .json".format(entry.path) + ) + continue + entry_json = process_json_arg(content=entry.path, argument_name=entry.name) + payload.append(entry_json) + + return _get_models_metadata(payload) + + +def _get_models_metadata(models): + models_metadata = [] + for model in models: + metadata = { + "id": model["@id"], + "decommissioned": False, + "displayName": model.get("displayName", ""), + "resolveSource": "$devloper", # Currently no other resolveSource + "serviceOrigin": "ADT", # Currently no other serviceOrigin + } + models_metadata.append(metadata) + + return models_metadata + + +def _increment_model_id(model_id): + # This block is to increment model version for + # executing model create of a different style + model_ver = int(model_id.split(";")[-1]) + model_ver = model_ver + 1 + model_id_chars = list(model_id) + model_id_chars[-1] = str(model_ver) + inc_model_id = "".join(model_id_chars) + return inc_model_id diff --git a/azext_iot/tests/digitaltwins/test_dt_privatelinks_lifecycle_int.py b/azext_iot/tests/digitaltwins/test_dt_privatelinks_lifecycle_int.py new file mode 100644 index 000000000..706919f40 --- /dev/null +++ b/azext_iot/tests/digitaltwins/test_dt_privatelinks_lifecycle_int.py @@ -0,0 +1,187 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.log import get_logger +from . import DTLiveScenarioTest +from . import generate_resource_id, generate_generic_id + +logger = get_logger(__name__) + + +class TestDTPrivateLinksLifecycle(DTLiveScenarioTest): + def __init__(self, test_case): + super(TestDTPrivateLinksLifecycle, self).__init__(test_case) + + def test_dt_privatelinks(self): + self.wait_for_capacity() + + instance_name = generate_resource_id() + group_id = "API" + create_output = self.cmd( + "dt create -n {} -g {} -l {}".format( + instance_name, + self.rg, + self.region, + ) + ).get_output_in_json() + self.track_instance(create_output) + create_output = self.wait_for_hostname(create_output) + + # Fail test if hostName missing + assert create_output.get( + "hostName" + ), "Service failed to provision DT instance: {}.".format(instance_name) + assert create_output["publicNetworkAccess"] == "Enabled" + + update_output = self.cmd( + "dt create -n {} -g {} -l {} --public-network-access Disabled".format( + instance_name, + self.rg, + self.region, + ) + ).get_output_in_json() + assert update_output["publicNetworkAccess"] == "Disabled" + + list_priv_links = self.cmd( + "dt network private-link list -n {} -g {}".format( + instance_name, + self.rg, + ) + ).get_output_in_json() + assert len(list_priv_links) > 0 + + show_api_priv_link = self.cmd( + "dt network private-link show -n {} -g {} --ln {}".format( + instance_name, self.rg, group_id + ) + ).get_output_in_json() + assert show_api_priv_link["name"] == group_id + assert ( + show_api_priv_link["type"] + == "Microsoft.DigitalTwins/digitalTwinsInstances/privateLinkResources" + ) + + connection_name = generate_generic_id() + endpoint_name = generate_generic_id() + dt_instance_id = create_output["id"] + vnet_name = generate_generic_id() + subnet_name = generate_generic_id() + + # Create VNET + self.cmd( + "network vnet create -n {} -g {} --subnet-name {}".format( + vnet_name, self.rg, subnet_name + ), + checks=self.check("length(newVNet.subnets)", 1), + ) + self.cmd( + "network vnet subnet update -n {} --vnet-name {} -g {} " + "--disable-private-endpoint-network-policies true".format( + subnet_name, vnet_name, self.rg + ), + checks=self.check("privateEndpointNetworkPolicies", "Disabled"), + ) + + create_priv_endpoint_result = self.embedded_cli.invoke( + "network private-endpoint create --connection-name {} -n {} --private-connection-resource-id '{}'" + " --group-id {} -g {} --vnet-name {} --subnet {} --manual-request".format( + connection_name, + endpoint_name, + dt_instance_id, + group_id, + self.rg, + vnet_name, + subnet_name, + ) + ) + + if not create_priv_endpoint_result.success(): + raise RuntimeError( + "Failed to configure private-endpoint for DT instance: {}".format( + instance_name + ) + ) + + list_priv_endpoints = self.cmd( + "dt network private-endpoint connection list -n {} -g {}".format( + instance_name, + self.rg, + ) + ).get_output_in_json() + assert len(list_priv_endpoints) > 0 + + instance_connection_id = list_priv_endpoints[-1]["name"] + + show_priv_endpoint = self.cmd( + "dt network private-endpoint connection show -n {} -g {} --cn {}".format( + instance_name, self.rg, instance_connection_id + ) + ).get_output_in_json() + assert show_priv_endpoint["name"] == instance_connection_id + assert ( + show_priv_endpoint["type"] + == "Microsoft.DigitalTwins/digitalTwinsInstances/privateEndpointConnections" + ) + assert show_priv_endpoint["properties"]["provisioningState"] == "Succeeded" + + # Force manual approval + assert ( + show_priv_endpoint["properties"]["privateLinkServiceConnectionState"]["status"] + == "Pending" + ) + + random_desc_approval = "{} {}".format( + generate_generic_id(), generate_generic_id() + ) + set_connection_output = self.cmd( + "dt network private-endpoint connection set -n {} -g {} --cn {} --status Approved --desc '{}'".format( + instance_name, self.rg, instance_connection_id, random_desc_approval + ) + ).get_output_in_json() + assert ( + set_connection_output["properties"]["privateLinkServiceConnectionState"]["status"] + == "Approved" + ) + assert ( + set_connection_output["properties"]["privateLinkServiceConnectionState"]["description"] + == random_desc_approval + ) + + random_desc_rejected = "{} {}".format( + generate_generic_id(), generate_generic_id() + ) + set_connection_output = self.cmd( + "dt network private-endpoint connection set -n {} -g {} --cn {} --status Rejected --desc '{}'".format( + instance_name, self.rg, instance_connection_id, random_desc_rejected + ) + ).get_output_in_json() + assert ( + set_connection_output["properties"]["privateLinkServiceConnectionState"]["status"] + == "Rejected" + ) + assert ( + set_connection_output["properties"]["privateLinkServiceConnectionState"]["description"] + == random_desc_rejected + ) + + self.cmd( + "dt network private-endpoint connection delete -n {} -g {} --cn {} -y".format( + instance_name, self.rg, instance_connection_id + ) + ) + + list_priv_endpoints = self.cmd( + "dt network private-endpoint connection list -n {} -g {}".format( + instance_name, + self.rg, + ) + ).get_output_in_json() + assert len(list_priv_endpoints) == 0 + + # TODO clean-up optimization + + self.cmd("network private-endpoint delete -n {} -g {} ".format(endpoint_name, self.rg)) + self.cmd("network vnet delete -n {} -g {} ".format(vnet_name, self.rg)) diff --git a/azext_iot/tests/digitaltwins/test_dt_provider_unit.py b/azext_iot/tests/digitaltwins/test_dt_provider_unit.py new file mode 100644 index 000000000..58a9e5ffd --- /dev/null +++ b/azext_iot/tests/digitaltwins/test_dt_provider_unit.py @@ -0,0 +1,87 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import re +import pytest +import responses +import json +from azext_iot.digitaltwins.providers.base import DigitalTwinsProvider +from azext_iot.tests.generators import generate_generic_id + + +resource_group = generate_generic_id() +instance_name = generate_generic_id() +qualified_hostname = "{}.subdomain.domain".format(instance_name) + + +@pytest.fixture +def get_mgmt_client(mocker, fixture_cmd): + from azext_iot.sdk.digitaltwins.controlplane import AzureDigitalTwinsManagementClient + from azext_iot.digitaltwins.providers.auth import DigitalTwinAuthentication + + patched_get_raw_token = mocker.patch( + "azure.cli.core._profile.Profile.get_raw_token" + ) + patched_get_raw_token.return_value = ( + mocker.MagicMock(name="creds"), + mocker.MagicMock(name="subscription"), + mocker.MagicMock(name="tenant"), + ) + + patch = mocker.patch( + "azext_iot.digitaltwins.providers.digitaltwins_service_factory" + ) + patch.return_value = AzureDigitalTwinsManagementClient( + credentials=DigitalTwinAuthentication( + fixture_cmd, "00000000-0000-0000-0000-000000000000" + ), + subscription_id="00000000-0000-0000-0000-000000000000", + ) + + return patch + + +class TestDigitalTwinsProvider: + @pytest.fixture + def service_client(self, mocked_response, get_mgmt_client): + mocked_response.assert_all_requests_are_fired = False + + mocked_response.add( + method=responses.GET, + content_type="application/json", + url=re.compile( + "https://management.azure.com/subscriptions/(.*)/resourceGroups/{}/" + "providers/Microsoft.DigitalTwins/digitalTwinsInstances/{}".format( + resource_group, instance_name + ) + ), + status=200, + match_querystring=False, + body=json.dumps({"hostName": qualified_hostname}), + ) + + yield mocked_response + + @pytest.mark.parametrize( + "name, expected", + [ + (instance_name, "https://{}".format(qualified_hostname)), + (qualified_hostname, "https://{}".format(qualified_hostname)), + ( + "https://{}.domain".format(instance_name), + "https://{}.domain".format(instance_name), + ), + ( + "http://{}.domain".format(instance_name), + "https://{}.domain".format(instance_name), + ), + ], + ) + def test_get_endpoint(self, fixture_cmd, name, expected, service_client): + subject = DigitalTwinsProvider(cmd=fixture_cmd, name=name, rg=resource_group) + endpoint = subject._get_endpoint() + + assert endpoint == expected diff --git a/azext_iot/tests/digitaltwins/test_dt_resource_lifecycle_int.py b/azext_iot/tests/digitaltwins/test_dt_resource_lifecycle_int.py new file mode 100644 index 000000000..48d50b0ea --- /dev/null +++ b/azext_iot/tests/digitaltwins/test_dt_resource_lifecycle_int.py @@ -0,0 +1,732 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import pytest +from typing import List +from collections import namedtuple +from time import sleep +from knack.log import get_logger +from azext_iot.digitaltwins.common import ADTEndpointAuthType, ADTEndpointType +from azext_iot.tests.settings import DynamoSettings +from . import DTLiveScenarioTest +from . import ( + MOCK_RESOURCE_TAGS, + MOCK_RESOURCE_TAGS_DICT, + MOCK_DEAD_LETTER_SECRET, + MOCK_DEAD_LETTER_ENDPOINT, + generate_resource_id, +) + +logger = get_logger(__name__) + +resource_test_env_vars = [ + "azext_dt_ep_eventhub_namespace", + "azext_dt_ep_eventhub_policy", + "azext_dt_ep_eventhub_topic", + "azext_dt_ep_servicebus_namespace", + "azext_dt_ep_servicebus_policy", + "azext_dt_ep_servicebus_topic", + "azext_dt_ep_eventgrid_topic", + "azext_dt_ep_rg", +] + +settings = DynamoSettings(opt_env_set=resource_test_env_vars) +run_resource_tests = False +run_endpoint_route_tests = False + + +if all( + [ + settings.env.azext_dt_ep_eventhub_namespace, + settings.env.azext_dt_ep_eventhub_policy, + settings.env.azext_dt_ep_eventhub_topic, + settings.env.azext_dt_ep_servicebus_namespace, + settings.env.azext_dt_ep_servicebus_policy, + settings.env.azext_dt_ep_servicebus_topic, + settings.env.azext_dt_ep_eventgrid_topic, + settings.env.azext_dt_ep_rg, + ] +): + run_endpoint_route_tests = True + + +class TestDTResourceLifecycle(DTLiveScenarioTest): + def __init__(self, test_case): + super(TestDTResourceLifecycle, self).__init__(test_case) + + @pytest.mark.skipif( + not all( + [ + settings.env.azext_dt_ep_rg, + settings.env.azext_dt_ep_eventgrid_topic, + settings.env.azext_dt_ep_servicebus_topic, + settings.env.azext_dt_ep_servicebus_namespace, + ] + ), + reason="Required env vars missing.", + ) + def test_dt_resource(self): + self.wait_for_capacity(capacity=3) + + eventgrid_topic_id = self.cmd( + "eventgrid topic show -g {} -n {}".format( + settings.env.azext_dt_ep_rg, settings.env.azext_dt_ep_eventgrid_topic + ) + ).get_output_in_json()["id"] + + servicebus_topic_id = self.cmd( + "servicebus topic show -g {} -n {} --namespace-name {}".format( + settings.env.azext_dt_ep_rg, + settings.env.azext_dt_ep_servicebus_topic, + settings.env.azext_dt_ep_servicebus_namespace, + ) + ).get_output_in_json()["id"] + + scope_ids = [eventgrid_topic_id, servicebus_topic_id] + + instance_names = [generate_resource_id(), generate_resource_id()] + create_output = self.cmd( + "dt create -n {} -g {} -l {} --tags {}".format( + instance_names[0], + self.rg, + self.region, + MOCK_RESOURCE_TAGS, + ) + ).get_output_in_json() + self.track_instance(create_output) + + assert_common_resource_attributes( + self.wait_for_hostname(create_output), + instance_names[0], + self.rg, + self.region, + MOCK_RESOURCE_TAGS_DICT, + ) + + show_output = self.cmd( + "dt show -n {}".format(instance_names[0]) + ).get_output_in_json() + + assert_common_resource_attributes( + show_output, + instance_names[0], + self.rg, + self.region, + MOCK_RESOURCE_TAGS_DICT, + ) + + # Explictly assert create prevents provisioning on a name conflict (across regions) + self.cmd( + "dt create -n {} -g {} -l {} --tags {}".format( + instance_names[0], + self.rg, + self.get_available_region(1, skip_regions=[self.region]), + MOCK_RESOURCE_TAGS, + ), + expect_failure=True, + ) + + # No location specified. Use the resource group location. + create_msi_output = self.cmd( + "dt create -n {} -g {} --assign-identity --scopes {}".format( + instance_names[1], self.rg, " ".join(scope_ids) + ) + ).get_output_in_json() + self.track_instance(create_msi_output) + + assert_common_resource_attributes( + self.wait_for_hostname(create_msi_output), + instance_names[1], + self.rg, + self.rg_region, + tags=None, + assign_identity=True, + ) + + show_msi_output = self.cmd( + "dt show -n {} -g {}".format(instance_names[1], self.rg) + ).get_output_in_json() + + assert_common_resource_attributes( + show_msi_output, + instance_names[1], + self.rg, + self.rg_region, + tags=None, + assign_identity=True, + ) + + role_assignment_egt_list = self.cmd( + "role assignment list --scope {} --assignee {}".format( + eventgrid_topic_id, show_msi_output["identity"]["principalId"] + ) + ).get_output_in_json() + assert len(role_assignment_egt_list) == 1 + + role_assignment_sbt_list = self.cmd( + "role assignment list --scope {} --assignee {}".format( + servicebus_topic_id, show_msi_output["identity"]["principalId"] + ) + ).get_output_in_json() + assert len(role_assignment_sbt_list) == 1 + + # Update tags and disable MSI + updated_tags = "env=test tier=premium" + updated_tags_dict = {"env": "test", "tier": "premium"} + remove_msi_output = self.cmd( + "dt create -n {} -g {} --assign-identity false --tags {}".format( + instance_names[1], self.rg, updated_tags + ) + ).get_output_in_json() + + assert_common_resource_attributes( + self.wait_for_hostname(remove_msi_output), + instance_names[1], + self.rg, + self.rg_region, + tags=updated_tags_dict, + assign_identity=False, + ) + + list_output = self.cmd("dt list").get_output_in_json() + filtered_list = filter_dt_list(list_output, instance_names) + assert len(filtered_list) == len(instance_names) + + list_group_output = self.cmd( + "dt list -g {}".format(self.rg) + ).get_output_in_json() + filtered_group_list = filter_dt_list(list_group_output, instance_names) + assert len(filtered_group_list) == len(instance_names) + + # Delete does not currently return output + # Delete no blocking + self.cmd( + "dt delete -n {} -g {} -y --no-wait".format(instance_names[1], self.rg) + ) + + # Delete while blocking + self.cmd("dt delete -n {} -y".format(instance_names[0])) + + def test_dt_rbac(self): + self.wait_for_capacity() + + rbac_assignee_owner = self.current_user + rbac_assignee_reader = self.current_user + + rbac_instance_name = generate_resource_id() + rbac_instance = self.cmd( + "dt create -n {} -g {} -l {}".format( + rbac_instance_name, + self.rg, + self.region, + ) + ).get_output_in_json() + self.track_instance(rbac_instance) + + assert ( + len( + self.cmd( + "dt role-assignment list -n {}".format(rbac_instance_name) + ).get_output_in_json() + ) + == 0 + ) + + assign_output = self.cmd( + "dt role-assignment create -n {} --assignee {} --role '{}'".format( + rbac_instance_name, rbac_assignee_owner, self.role_map["owner"] + ) + ).get_output_in_json() + + assert_common_rbac_attributes( + assign_output, + rbac_instance_name, + "owner", + rbac_assignee_owner, + ) + + assign_output = self.cmd( + "dt role-assignment create -n {} --assignee {} --role '{}' -g {}".format( + rbac_instance_name, + rbac_assignee_reader, + self.role_map["reader"], + self.rg, + ) + ).get_output_in_json() + + assert_common_rbac_attributes( + assign_output, + rbac_instance_name, + "reader", + rbac_assignee_reader, + ) + + list_assigned_output = self.cmd( + "dt role-assignment list -n {}".format(rbac_instance_name) + ).get_output_in_json() + + assert len(list_assigned_output) == 2 + + # Remove specific role assignment (reader) for assignee + # Role-assignment delete does not currently return output + self.cmd( + "dt role-assignment delete -n {} --assignee {} --role '{}'".format( + rbac_instance_name, + rbac_assignee_owner, + self.role_map["reader"], + ) + ) + + list_assigned_output = self.cmd( + "dt role-assignment list -n {} -g {}".format(rbac_instance_name, self.rg) + ).get_output_in_json() + + assert len(list_assigned_output) == 1 + + # Remove all role assignments for assignee + self.cmd( + "dt role-assignment delete -n {} --assignee {}".format( + rbac_instance_name, rbac_assignee_reader + ) + ) + + list_assigned_output = self.cmd( + "dt role-assignment list -n {} -g {}".format(rbac_instance_name, self.rg) + ).get_output_in_json() + + assert len(list_assigned_output) == 0 + + @pytest.mark.skipif( + not run_endpoint_route_tests, + reason="All azext_dt_ep_* env vars are required for endpoint and route tests.", + ) + def test_dt_endpoints_routes(self): + self.wait_for_capacity() + endpoints_instance_name = generate_resource_id() + target_scope_role = "Contributor" + + sb_topic_resource_id = self.embedded_cli.invoke( + "servicebus topic show --namespace-name {} -n {} -g {}".format( + settings.env.azext_dt_ep_servicebus_namespace, + settings.env.azext_dt_ep_servicebus_topic, + settings.env.azext_dt_ep_rg, + ) + ).as_json()["id"] + + eh_resource_id = self.embedded_cli.invoke( + "eventhubs eventhub show --namespace-name {} -n {} -g {}".format( + settings.env.azext_dt_ep_eventhub_namespace, + settings.env.azext_dt_ep_eventhub_topic, + settings.env.azext_dt_ep_rg, + ) + ).as_json()["id"] + + endpoint_instance = self.cmd( + "dt create -n {} -g {} -l {} --assign-identity --scopes {} {} --role {}".format( + endpoints_instance_name, + self.rg, + self.region, + sb_topic_resource_id, + eh_resource_id, + target_scope_role, + ) + ).get_output_in_json() + self.track_instance(endpoint_instance) + + # Setup RBAC so we can interact with routes + self.cmd( + "dt role-assignment create -n {} --assignee {} --role '{}' -g {}".format( + endpoints_instance_name, + self.current_user, + self.role_map["owner"], + self.rg, + ) + ) + + sleep(30) # Wait for service to catch-up + + EndpointTuple = namedtuple( + "endpoint_tuple", ["endpoint_name", "endpoint_type", "auth_type"] + ) + endpoint_tuple_collection: List[EndpointTuple] = [] + list_ep_output = self.cmd( + "dt endpoint list -n {}".format(endpoints_instance_name) + ).get_output_in_json() + assert len(list_ep_output) == 0 + + eventgrid_rg = settings.env.azext_dt_ep_rg + eventgrid_topic = settings.env.azext_dt_ep_eventgrid_topic + eventgrid_endpoint = "myeventgridendpoint" + + logger.debug("Adding key based eventgrid endpoint...") + add_ep_output = self.cmd( + "dt endpoint create eventgrid -n {} -g {} --egg {} --egt {} --en {} --dsu {}".format( + endpoints_instance_name, + self.rg, + eventgrid_rg, + eventgrid_topic, + eventgrid_endpoint, + MOCK_DEAD_LETTER_SECRET, + ) + ).get_output_in_json() + assert_common_endpoint_attributes( + add_ep_output, + eventgrid_endpoint, + ADTEndpointType.eventgridtopic, + dead_letter_secret=MOCK_DEAD_LETTER_SECRET, + ) + + endpoint_tuple_collection.append( + EndpointTuple( + eventgrid_endpoint, + ADTEndpointType.eventgridtopic, + ADTEndpointAuthType.keybased, + ) + ) + + servicebus_rg = settings.env.azext_dt_ep_rg + servicebus_namespace = settings.env.azext_dt_ep_servicebus_namespace + servicebus_policy = settings.env.azext_dt_ep_servicebus_policy + servicebus_topic = settings.env.azext_dt_ep_servicebus_topic + servicebus_endpoint = "myservicebusendpoint" + servicebus_endpoint_msi = "{}identity".format(servicebus_endpoint) + + logger.debug("Adding key based servicebus topic endpoint...") + add_ep_sb_key_output = self.cmd( + "dt endpoint create servicebus -n {} --sbg {} --sbn {} --sbp {} --sbt {} --en {} --dsu {}".format( + endpoints_instance_name, + servicebus_rg, + servicebus_namespace, + servicebus_policy, + servicebus_topic, + servicebus_endpoint, + MOCK_DEAD_LETTER_SECRET, + ) + ).get_output_in_json() + + assert_common_endpoint_attributes( + add_ep_sb_key_output, + servicebus_endpoint, + endpoint_type=ADTEndpointType.servicebus, + auth_type=ADTEndpointAuthType.keybased, + dead_letter_secret=MOCK_DEAD_LETTER_SECRET, + ) + endpoint_tuple_collection.append( + EndpointTuple( + servicebus_endpoint, + ADTEndpointType.servicebus, + ADTEndpointAuthType.keybased, + ) + ) + + logger.debug("Adding identity based servicebus topic endpoint...") + add_ep_sb_identity_output = self.cmd( + "dt endpoint create servicebus -n {} --sbg {} --sbn {} --sbt {} --en {} --du {} --auth-type IdentityBased".format( + endpoints_instance_name, + servicebus_rg, + servicebus_namespace, + servicebus_topic, + servicebus_endpoint_msi, + MOCK_DEAD_LETTER_ENDPOINT, + ) + ).get_output_in_json() + + assert_common_endpoint_attributes( + add_ep_sb_identity_output, + servicebus_endpoint_msi, + endpoint_type=ADTEndpointType.servicebus, + auth_type=ADTEndpointAuthType.identitybased, + dead_letter_endpoint=MOCK_DEAD_LETTER_ENDPOINT, + ) + endpoint_tuple_collection.append( + EndpointTuple( + servicebus_endpoint_msi, + ADTEndpointType.servicebus, + ADTEndpointAuthType.identitybased, + ) + ) + + eventhub_rg = settings.env.azext_dt_ep_rg + eventhub_namespace = settings.env.azext_dt_ep_eventhub_namespace + eventhub_policy = settings.env.azext_dt_ep_eventhub_policy + eventhub_topic = settings.env.azext_dt_ep_eventhub_topic + eventhub_endpoint = "myeventhubendpoint" + eventhub_endpoint_msi = "{}identity".format(eventhub_endpoint) + + logger.debug("Adding key based eventhub endpoint...") + add_ep_output = self.cmd( + "dt endpoint create eventhub -n {} --ehg {} --ehn {} --ehp {} --eh {} --ehs {} --en {} --dsu '{}'".format( + endpoints_instance_name, + eventhub_rg, + eventhub_namespace, + eventhub_policy, + eventhub_topic, + self.current_subscription, + eventhub_endpoint, + MOCK_DEAD_LETTER_SECRET, + ) + ).get_output_in_json() + + assert_common_endpoint_attributes( + add_ep_output, + eventhub_endpoint, + ADTEndpointType.eventhub, + auth_type=ADTEndpointAuthType.keybased, + dead_letter_secret=MOCK_DEAD_LETTER_SECRET, + ) + endpoint_tuple_collection.append( + EndpointTuple( + eventhub_endpoint, + ADTEndpointType.eventhub, + ADTEndpointAuthType.keybased, + ) + ) + + logger.debug("Adding identity based eventhub endpoint...") + add_ep_output = self.cmd( + "dt endpoint create eventhub -n {} --ehg {} --ehn {} --eh {} --ehs {} --en {} --du {} " + "--auth-type IdentityBased".format( + endpoints_instance_name, + eventhub_rg, + eventhub_namespace, + eventhub_topic, + self.current_subscription, + eventhub_endpoint_msi, + MOCK_DEAD_LETTER_ENDPOINT, + ) + ).get_output_in_json() + + assert_common_endpoint_attributes( + add_ep_output, + eventhub_endpoint_msi, + endpoint_type=ADTEndpointType.eventhub, + auth_type=ADTEndpointAuthType.identitybased, + dead_letter_endpoint=MOCK_DEAD_LETTER_ENDPOINT, + ) + endpoint_tuple_collection.append( + EndpointTuple( + eventhub_endpoint_msi, + ADTEndpointType.eventhub, + ADTEndpointAuthType.identitybased, + ) + ) + + for ep in endpoint_tuple_collection: + is_last = ep.endpoint_name == endpoint_tuple_collection[-1].endpoint_name + show_ep_output = self.cmd( + "dt endpoint show -n {} --en {} {}".format( + endpoints_instance_name, + ep.endpoint_name, + "-g {}".format(self.rg) if is_last else "", + ) + ).get_output_in_json() + + assert_common_endpoint_attributes( + show_ep_output, + ep.endpoint_name, + endpoint_type=ep.endpoint_type, + auth_type=ep.auth_type, + ) + + list_ep_output = self.cmd( + "dt endpoint list -n {} -g {}".format(endpoints_instance_name, self.rg) + ).get_output_in_json() + assert len(list_ep_output) == 5 + + endpoint_names = [eventgrid_endpoint, servicebus_endpoint, eventhub_endpoint] + filter_values = ["", "false", "type = Microsoft.DigitalTwins.Twin.Create"] + + # Test Routes + list_routes_output = self.cmd( + "dt route list -n {}".format(endpoints_instance_name) + ).get_output_in_json() + assert len(list_routes_output) == 0 + + for endpoint_name in endpoint_names: + is_last = endpoint_name == endpoint_names[-1] + route_name = "routefor{}".format(endpoint_name) + filter_value = filter_values.pop() + add_route_output = self.cmd( + "dt route create -n {} --rn {} --en {} --filter '{}' {}".format( + endpoints_instance_name, + route_name, + endpoint_name, + filter_value, + "-g {}".format(self.rg) if is_last else "", + ) + ).get_output_in_json() + + assert_common_route_attributes( + add_route_output, route_name, endpoint_name, filter_value + ) + + show_route_output = self.cmd( + "dt route show -n {} --rn {} {}".format( + endpoints_instance_name, + route_name, + "-g {}".format(self.rg) if is_last else "", + ) + ).get_output_in_json() + + assert_common_route_attributes( + show_route_output, route_name, endpoint_name, filter_value + ) + + list_routes_output = self.cmd( + "dt route list -n {} -g {}".format(endpoints_instance_name, self.rg) + ).get_output_in_json() + assert len(list_routes_output) == 3 + + for endpoint_name in endpoint_names: + is_last = endpoint_name == endpoint_names[-1] + route_name = "routefor{}".format(endpoint_name) + self.cmd( + "dt route delete -n {} --rn {} {}".format( + endpoints_instance_name, + route_name, + "-g {}".format(self.rg) if is_last else "", + ) + ) + + list_routes_output = self.cmd( + "dt route list -n {} -g {}".format(endpoints_instance_name, self.rg) + ).get_output_in_json() + assert len(list_routes_output) == 0 + + for ep in endpoint_tuple_collection: + logger.debug("Deleting endpoint {}...".format(ep.endpoint_name)) + is_last = ep.endpoint_name == endpoint_tuple_collection[-1].endpoint_name + self.cmd( + "dt endpoint delete -y -n {} --en {} {}".format( + endpoints_instance_name, + ep.endpoint_name, + "-g {} --no-wait".format(self.rg) if is_last else "", + ) + ) + + list_endpoint_output = self.cmd( + "dt endpoint list -n {} -g {}".format(endpoints_instance_name, self.rg) + ).get_output_in_json() + assert ( + len(list_endpoint_output) == 0 + or len( + [ + ep + for ep in list_endpoint_output + if ep["properties"]["provisioningState"].lower() != "deleting" + ] + ) + == 0 + ) + + +def assert_common_resource_attributes( + instance_output, resource_id, group_id, location, tags=None, assign_identity=False +): + assert instance_output["createdTime"] + hostname = instance_output.get("hostName") + + assert hostname, "Provisioned instance is missing hostName." + assert hostname.startswith(resource_id) + assert instance_output["location"] == location + assert instance_output["id"].endswith(resource_id) + assert instance_output["lastUpdatedTime"] + assert instance_output["name"] == resource_id + assert instance_output["provisioningState"] == "Succeeded" + assert instance_output["resourceGroup"] == group_id + assert instance_output["type"] == "Microsoft.DigitalTwins/digitalTwinsInstances" + + if tags: + assert instance_output["tags"] == tags + + if not assign_identity: + assert instance_output["identity"] is None + return + + assert instance_output["identity"]["principalId"] + assert instance_output["identity"]["tenantId"] + # Currently only SystemAssigned identity is supported. + assert instance_output["identity"]["type"] == "SystemAssigned" + + +def assert_common_route_attributes( + route_output, route_name, endpoint_name, filter_value +): + assert route_output["endpointName"] == endpoint_name + assert route_output["id"] == route_name + assert route_output["filter"] == filter_value if filter_value else "true" + + +def assert_common_endpoint_attributes( + endpoint_output, + endpoint_name, + endpoint_type, + dead_letter_secret=None, + dead_letter_endpoint=None, + auth_type=ADTEndpointAuthType.keybased, +): + assert endpoint_output["id"].endswith("/{}".format(endpoint_name)) + assert ( + endpoint_output["type"] + == "Microsoft.DigitalTwins/digitalTwinsInstances/endpoints" + ) + assert endpoint_output["resourceGroup"] + assert endpoint_output["properties"]["provisioningState"] + assert endpoint_output["properties"]["createdTime"] + + if dead_letter_secret: + pass + # TODO: This appears to be flaky. + # assert endpoint_output["properties"][ + # "deadLetterSecret" + # ], "Expected deadletter secret." + + if dead_letter_endpoint: + assert endpoint_output["properties"][ + "deadLetterUri" + ], "Expected deadletter Uri." + + # Currently DT -> EventGrid is only key based. + if endpoint_type == ADTEndpointType.eventgridtopic: + assert endpoint_output["properties"]["topicEndpoint"] + assert endpoint_output["properties"]["accessKey1"] + assert endpoint_output["properties"]["accessKey2"] + assert endpoint_output["properties"]["endpointType"] == "EventGrid" + return + if endpoint_type == ADTEndpointType.servicebus: + if auth_type == ADTEndpointAuthType.keybased: + assert endpoint_output["properties"]["primaryConnectionString"] + assert endpoint_output["properties"]["secondaryConnectionString"] + if auth_type == ADTEndpointAuthType.identitybased: + assert endpoint_output["properties"]["endpointUri"] + assert endpoint_output["properties"]["entityPath"] + assert endpoint_output["properties"]["endpointType"] == "ServiceBus" + return + if endpoint_type == ADTEndpointType.eventhub: + if auth_type == ADTEndpointAuthType.keybased: + assert endpoint_output["properties"]["connectionStringPrimaryKey"] + assert endpoint_output["properties"]["connectionStringSecondaryKey"] + if auth_type == ADTEndpointAuthType.identitybased: + assert endpoint_output["properties"]["endpointUri"] + assert endpoint_output["properties"]["entityPath"] + assert endpoint_output["properties"]["endpointType"] == "EventHub" + return + + +def assert_common_rbac_attributes(rbac_output, instance_name, role_name, assignee): + role_def_id = None + if role_name == "owner": + role_def_id = "/bcd981a7-7f74-457b-83e1-cceb9e632ffe" + elif role_name == "reader": + role_def_id = "/d57506d4-4c8d-48b1-8587-93c323f6a5a3" + + assert rbac_output["roleDefinitionId"].endswith(role_def_id) + assert rbac_output["type"] == "Microsoft.Authorization/roleAssignments" + assert rbac_output["scope"].endswith("/{}".format(instance_name)) + + +def filter_dt_list(list_output, valid_names): + return [inst for inst in list_output if inst["name"] in valid_names] diff --git a/azext_iot/tests/digitaltwins/test_dt_twin_lifecycle_int.py b/azext_iot/tests/digitaltwins/test_dt_twin_lifecycle_int.py new file mode 100644 index 000000000..e7f0fc38b --- /dev/null +++ b/azext_iot/tests/digitaltwins/test_dt_twin_lifecycle_int.py @@ -0,0 +1,724 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import pytest +import json +from time import sleep +from knack.log import get_logger +from . import DTLiveScenarioTest +from . import ( + generate_resource_id, +) + +logger = get_logger(__name__) + + +@pytest.mark.usefixtures("set_cwd") +class TestDTTwinLifecycle(DTLiveScenarioTest): + def __init__(self, test_case): + super(TestDTTwinLifecycle, self).__init__(test_case) + + def test_dt_twin(self): + self.wait_for_capacity() + instance_name = generate_resource_id() + models_directory = "./models" + floor_dtmi = "dtmi:com:example:Floor;1" + floor_twin_id = "myfloor" + room_dtmi = "dtmi:com:example:Room;1" + room_twin_id = "myroom" + thermostat_component_id = "Thermostat" + etag = 'AAAA==' + + create_output = self.cmd( + "dt create -n {} -g {} -l {}".format(instance_name, self.rg, self.region) + ).get_output_in_json() + self.track_instance(create_output) + self.wait_for_hostname(create_output) + + self.cmd( + "dt role-assignment create -n {} -g {} --assignee {} --role '{}'".format( + instance_name, self.rg, self.current_user, self.role_map["owner"] + ) + ) + # Wait for RBAC to catch-up + sleep(60) + + self.cmd( + "dt model create -n {} --from-directory '{}'".format( + instance_name, models_directory + ) + ) + + twin_query_result = self.cmd( + "dt twin query -n {} -q 'select * from digitaltwins'".format(instance_name) + ).get_output_in_json() + assert len(twin_query_result["result"]) == 0 + + self.kwargs["tempAndThermostatComponentJson"] = json.dumps( + { + "Temperature": 10.2, + "Thermostat": { + "$metadata": {}, + "setPointTemp": 23.12, + }, + } + ) + + self.kwargs["emptyThermostatComponentJson"] = json.dumps( + {"Thermostat": {"$metadata": {}}} + ) + + floor_twin = self.cmd( + "dt twin create -n {} --dtmi {} --twin-id {}".format( + instance_name, floor_dtmi, floor_twin_id + ) + ).get_output_in_json() + + assert_twin_attributes( + twin=floor_twin, + expected_twin_id=floor_twin_id, + expected_dtmi=floor_dtmi, + ) + + # twin create with component - example of bare minimum --properties + + # create twin will fail without --properties + self.cmd( + "dt twin create -n {} -g {} --dtmi {} --twin-id {}".format( + instance_name, self.rg, room_dtmi, room_twin_id + ), + expect_failure=True, + ) + + # minimum component object with empty $metadata object + min_room_twin = self.cmd( + "dt twin create -n {} -g {} --dtmi {} --twin-id {} --properties '{}'".format( + instance_name, + self.rg, + room_dtmi, + room_twin_id, + "{emptyThermostatComponentJson}", + ) + ).get_output_in_json() + + assert_twin_attributes( + twin=min_room_twin, + expected_twin_id=room_twin_id, + expected_dtmi=room_dtmi, + properties=self.kwargs["emptyThermostatComponentJson"], + component_name=thermostat_component_id, + ) + + replaced_room_twin = self.cmd( + "dt twin create -n {} -g {} --dtmi {} --twin-id {} --properties '{}'".format( + instance_name, + self.rg, + room_dtmi, + room_twin_id, + "{tempAndThermostatComponentJson}", + ) + ).get_output_in_json() + + assert_twin_attributes( + twin=replaced_room_twin, + expected_twin_id=room_twin_id, + expected_dtmi=room_dtmi, + properties=self.kwargs["tempAndThermostatComponentJson"], + component_name=thermostat_component_id, + ) + + # new twin cannot be created with same twin_id if if-none-match provided + self.cmd( + "dt twin create -n {} -g {} --dtmi {} --twin-id {} --if-none-match --properties '{}'".format( + instance_name, + self.rg, + room_dtmi, + room_twin_id, + "{emptyThermostatComponentJson}", + ), + expect_failure=True + ) + + # delete command should fail if etag is different + self.cmd( + "dt twin delete -n {} -g {} --twin-id {} --etag '{}'".format( + instance_name, + self.rg, + room_twin_id, + etag + ), + expect_failure=True + ) + + self.cmd( + "dt twin delete -n {} -g {} --twin-id {}".format( + instance_name, + self.rg, + room_twin_id, + ) + ) + + room_twin = self.cmd( + "dt twin create -n {} -g {} --dtmi {} --twin-id {} --properties '{}'".format( + instance_name, + self.rg, + room_dtmi, + room_twin_id, + "{tempAndThermostatComponentJson}", + ) + ).get_output_in_json() + + assert_twin_attributes( + twin=room_twin, + expected_twin_id=room_twin_id, + expected_dtmi=room_dtmi, + properties=self.kwargs["tempAndThermostatComponentJson"], + component_name=thermostat_component_id, + ) + + # Component + + thermostat_component = self.cmd( + "dt twin component show -n {} -g {} --twin-id {} --component {}".format( + instance_name, + self.rg, + room_twin_id, + thermostat_component_id, + ) + ).get_output_in_json() + + self.kwargs["thermostatJsonPatch"] = json.dumps( + [{"op": "replace", "path": "/setPointTemp", "value": 50.5}] + ) + + # Currently component update does not return value + self.cmd( + "dt twin component update -n {} -g {} --twin-id {} --component {} --json-patch '{}'".format( + instance_name, + self.rg, + room_twin_id, + thermostat_component_id, + "{thermostatJsonPatch}", + ) + ) + + thermostat_component = self.cmd( + "dt twin component show -n {} -g {} --twin-id {} --component {}".format( + instance_name, + self.rg, + room_twin_id, + thermostat_component_id, + ) + ).get_output_in_json() + + assert ( + thermostat_component["setPointTemp"] + == json.loads(self.kwargs["thermostatJsonPatch"])[0]["value"] + ) + + twins_id_list = [ + (floor_twin_id, floor_dtmi), + (room_twin_id, room_dtmi), + ] + + for twin_tuple in twins_id_list: + twin = self.cmd( + "dt twin show -n {} --twin-id {} {}".format( + instance_name, + twin_tuple[0], + "-g {}".format(self.rg) if twins_id_list[-1] == twin_tuple else "", + ) + ).get_output_in_json() + assert_twin_attributes( + twin=twin, expected_twin_id=twin_tuple[0], expected_dtmi=twin_tuple[1] + ) + + self.kwargs["temperatureJsonPatch"] = json.dumps( + {"op": "replace", "path": "/Temperature", "value": 20.2} + ) + + update_twin_result = self.cmd( + "dt twin update -n {} --twin-id {} --json-patch '{}'".format( + instance_name, + room_twin_id, + "{temperatureJsonPatch}", + ) + ).get_output_in_json() + + assert ( + update_twin_result["Temperature"] + == json.loads(self.kwargs["temperatureJsonPatch"])["value"] + ) + + self.cmd( + "dt twin update -n {} --twin-id {} --json-patch '{}' --etag '{}'".format( + instance_name, + room_twin_id, + "{temperatureJsonPatch}", + etag + ), + expect_failure=True + ) + + update_twin_result = self.cmd( + "dt twin update -n {} --twin-id {} --json-patch '{}' --etag '{}'".format( + instance_name, + room_twin_id, + "{temperatureJsonPatch}", + update_twin_result["$etag"] + ) + ).get_output_in_json() + + assert ( + update_twin_result["Temperature"] + == json.loads(self.kwargs["temperatureJsonPatch"])["value"] + ) + + twin_query_result = self.cmd( + "dt twin query -n {} -g {} -q 'select * from digitaltwins'".format( + instance_name, self.rg + ) + ).get_output_in_json() + assert len(twin_query_result["result"]) == 2 + + # Relationship Tests + relationship_id = "myedge" + relationship = "contains" + self.kwargs["relationshipJson"] = json.dumps( + {"ownershipUser": "me", "ownershipDepartment": "mydepartment"} + ) + self.kwargs["relationshipJsonPatch"] = json.dumps( + {"op": "replace", "path": "/ownershipUser", "value": "meme"} + ) + + twin_relationship_create_result = self.cmd( + "dt twin relationship create -n {} -g {} --relationship-id {} --relationship {} --twin-id {} " + "--target-twin-id {}".format( + instance_name, + self.rg, + relationship_id, + relationship, + floor_twin_id, + room_twin_id, + ) + ).get_output_in_json() + + twin_relationship_create_result = self.cmd( + "dt twin relationship create -n {} -g {} --relationship-id {} --relationship {} --twin-id {} " + "--target-twin-id {} --properties '{}'".format( + instance_name, + self.rg, + relationship_id, + relationship, + floor_twin_id, + room_twin_id, + "{relationshipJson}", + ) + ).get_output_in_json() + + assert_twin_relationship_attributes( + twin_relationship_obj=twin_relationship_create_result, + expected_relationship=relationship, + relationship_id=relationship_id, + source_id=floor_twin_id, + target_id=room_twin_id, + properties=self.kwargs["relationshipJson"], + ) + + # new twin cannot be created with same twin_id if if-none-match provided + twin_relationship_create_result = self.cmd( + "dt twin relationship create -n {} -g {} --relationship-id {} --relationship {} --twin-id {} " + "--target-twin-id {} --if-none-match --properties '{}'".format( + instance_name, + self.rg, + relationship_id, + relationship, + floor_twin_id, + room_twin_id, + "{relationshipJson}", + ), + expect_failure=True + ) + + twin_relationship_show_result = self.cmd( + "dt twin relationship show -n {} -g {} --twin-id {} --relationship-id {}".format( + instance_name, + self.rg, + floor_twin_id, + relationship_id, + ) + ).get_output_in_json() + + assert_twin_relationship_attributes( + twin_relationship_obj=twin_relationship_show_result, + expected_relationship=relationship, + relationship_id=relationship_id, + source_id=floor_twin_id, + target_id=room_twin_id, + properties=self.kwargs["relationshipJson"], + ) + + twin_edge_update_result = self.cmd( + "dt twin relationship update -n {} -g {} --relationship-id {} --twin-id {} " + "--json-patch '{}'".format( + instance_name, + self.rg, + relationship_id, + floor_twin_id, + "{relationshipJsonPatch}", + ) + ).get_output_in_json() + + assert ( + twin_edge_update_result["ownershipUser"] + == json.loads(self.kwargs["relationshipJsonPatch"])["value"] + ) + + # Fail to update if the etag if different + self.cmd( + "dt twin relationship update -n {} -g {} --relationship-id {} --twin-id {} " + "--json-patch '{}' --etag '{}'".format( + instance_name, + self.rg, + relationship_id, + floor_twin_id, + "{relationshipJsonPatch}", + etag + ), + expect_failure=True + ) + + twin_edge_update_result = self.cmd( + "dt twin relationship update -n {} -g {} --relationship-id {} --twin-id {} " + "--json-patch '{}' --etag '{}'".format( + instance_name, + self.rg, + relationship_id, + floor_twin_id, + "{relationshipJsonPatch}", + twin_edge_update_result["$etag"] + ) + ).get_output_in_json() + + assert ( + twin_edge_update_result["ownershipUser"] + == json.loads(self.kwargs["relationshipJsonPatch"])["value"] + ) + + twin_relationship_list_result = self.cmd( + "dt twin relationship list -n {} --twin-id {}".format( + instance_name, + floor_twin_id, + ) + ).get_output_in_json() + assert len(twin_relationship_list_result) == 1 + + twin_relationship_list_result = self.cmd( + "dt twin relationship list -n {} -g {} --twin-id {} --relationship {}".format( + instance_name, + self.rg, + floor_twin_id, + relationship, + ) + ).get_output_in_json() + assert len(twin_relationship_list_result) == 1 + + twin_relationship_list_result = self.cmd( + "dt twin relationship list -n {} --twin-id {}".format( + instance_name, + room_twin_id, + ) + ).get_output_in_json() + assert len(twin_relationship_list_result) == 0 + + twin_relationship_list_result = self.cmd( + "dt twin relationship list -n {} --twin-id {} --incoming".format( + instance_name, + room_twin_id, + ) + ).get_output_in_json() + assert len(twin_relationship_list_result) == 1 + + twin_relationship_list_result = self.cmd( + "dt twin relationship list -n {} --twin-id {} --kind {} --incoming".format( + instance_name, room_twin_id, relationship + ) + ).get_output_in_json() + assert len(twin_relationship_list_result) == 1 + + self.cmd( + "dt twin relationship delete -n {} --twin-id {} -r {} --etag '{}'".format( + instance_name, + floor_twin_id, + relationship_id, + etag + ), + expect_failure=True + ) + + # No output from API for delete edge + self.cmd( + "dt twin relationship delete -n {} --twin-id {} -r {}".format( + instance_name, + floor_twin_id, + relationship_id, + ) + ) + + twin_relationship_list_result = self.cmd( + "dt twin relationship list -n {} -g {} --twin-id {} --kind {}".format( + instance_name, + self.rg, + floor_twin_id, + relationship, + ) + ).get_output_in_json() + assert len(twin_relationship_list_result) == 0 + + # Twin + Component Telemetry. Neither returns data. Only 204 status code. + + self.kwargs["telemetryJson"] = json.dumps({"data": generate_resource_id()}) + + self.cmd( + "dt twin telemetry send -n {} -g {} --twin-id {} --telemetry '{}'".format( + instance_name, + self.rg, + room_twin_id, + "{telemetryJson}", + ) + ) + + self.cmd( + "dt twin telemetry send -n {} -g {} --twin-id {} --component {} --telemetry '{}'".format( + instance_name, + self.rg, + room_twin_id, + thermostat_component_id, + "{telemetryJson}", + ) + ) + + self.cmd( + "dt twin delete-all -n {} --yes".format( + instance_name, + ) + ) + sleep(5) # Wait for API to catch up + + twin_query_result = self.cmd( + "dt twin query -n {} -g {} -q 'select * from digitaltwins' --cost".format( + instance_name, self.rg + ) + ).get_output_in_json() + assert len(twin_query_result["result"]) == 0 + assert twin_query_result["cost"] + + def test_dt_twin_delete(self): + self.wait_for_capacity() + instance_name = generate_resource_id() + models_directory = "./models" + floor_dtmi = "dtmi:com:example:Floor;1" + floor_twin_id = "myfloor" + room_dtmi = "dtmi:com:example:Room;1" + room_twin_id = "myroom" + thermostat_component_id = "Thermostat" + + create_output = self.cmd( + "dt create -n {} -g {} -l {}".format(instance_name, self.rg, self.region) + ).get_output_in_json() + self.track_instance(create_output) + self.wait_for_hostname(create_output) + + self.cmd( + "dt role-assignment create -n {} -g {} --assignee {} --role '{}'".format( + instance_name, self.rg, self.current_user, self.role_map["owner"] + ) + ) + # Wait for RBAC to catch-up + sleep(60) + + self.cmd( + "dt model create -n {} --from-directory '{}'".format( + instance_name, models_directory + ) + ) + + self.kwargs["tempAndThermostatComponentJson"] = json.dumps( + { + "Temperature": 10.2, + "Thermostat": { + "$metadata": {}, + "setPointTemp": 23.12, + }, + } + ) + + floor_twin = self.cmd( + "dt twin create -n {} --dtmi {} --twin-id {}".format( + instance_name, floor_dtmi, floor_twin_id + ) + ).get_output_in_json() + + assert_twin_attributes( + twin=floor_twin, + expected_twin_id=floor_twin_id, + expected_dtmi=floor_dtmi, + ) + + room_twin = self.cmd( + "dt twin create -n {} -g {} --dtmi {} --twin-id {} --properties '{}'".format( + instance_name, + self.rg, + room_dtmi, + room_twin_id, + "{tempAndThermostatComponentJson}", + ) + ).get_output_in_json() + + assert_twin_attributes( + twin=room_twin, + expected_twin_id=room_twin_id, + expected_dtmi=room_dtmi, + properties=self.kwargs["tempAndThermostatComponentJson"], + component_name=thermostat_component_id, + ) + + sleep(5) # Wait for API to catch up + twin_query_result = self.cmd( + "dt twin query -n {} -g {} -q 'select * from digitaltwins'".format( + instance_name, self.rg + ) + ).get_output_in_json() + assert len(twin_query_result["result"]) == 2 + + # Relationship Tests + relationship_id = "myedge" + relationship = "contains" + self.kwargs["relationshipJson"] = json.dumps( + {"ownershipUser": "me", "ownershipDepartment": "mydepartment"} + ) + + twin_relationship_create_result = self.cmd( + "dt twin relationship create -n {} -g {} --relationship-id {} --relationship {} --twin-id {} " + "--target-twin-id {} --properties '{}'".format( + instance_name, + self.rg, + relationship_id, + relationship, + floor_twin_id, + room_twin_id, + "{relationshipJson}", + ) + ).get_output_in_json() + + assert_twin_relationship_attributes( + twin_relationship_obj=twin_relationship_create_result, + expected_relationship=relationship, + relationship_id=relationship_id, + source_id=floor_twin_id, + target_id=room_twin_id, + properties=self.kwargs["relationshipJson"], + ) + + twin_relationship_list_result = self.cmd( + "dt twin relationship list -n {} --twin-id {}".format( + instance_name, + floor_twin_id, + ) + ).get_output_in_json() + assert len(twin_relationship_list_result) == 1 + + # Delete all relationships + self.cmd( + "dt twin relationship delete-all -n {} --twin-id {} --yes".format( + instance_name, + floor_twin_id, + ) + ) + + twin_relationship_list_result = self.cmd( + "dt twin relationship list -n {} --twin-id {}".format( + instance_name, + floor_twin_id, + ) + ).get_output_in_json() + assert len(twin_relationship_list_result) == 0 + + # Recreate relationship for delete all twins + twin_relationship_create_result = self.cmd( + "dt twin relationship create -n {} -g {} --relationship-id {} --relationship {} --twin-id {} " + "--target-twin-id {} --properties '{}'".format( + instance_name, + self.rg, + relationship_id, + relationship, + floor_twin_id, + room_twin_id, + "{relationshipJson}", + ) + ).get_output_in_json() + + assert_twin_relationship_attributes( + twin_relationship_obj=twin_relationship_create_result, + expected_relationship=relationship, + relationship_id=relationship_id, + source_id=floor_twin_id, + target_id=room_twin_id, + properties=self.kwargs["relationshipJson"], + ) + + self.cmd( + "dt twin delete-all -n {} --yes".format( + instance_name, + ) + ) + sleep(5) # Wait for API to catch up + + twin_query_result = self.cmd( + "dt twin query -n {} -g {} -q 'select * from digitaltwins' --cost".format( + instance_name, self.rg + ) + ).get_output_in_json() + assert len(twin_query_result["result"]) == 0 + assert twin_query_result["cost"] + + +# TODO: Refactor - limited interface +def assert_twin_attributes( + twin, expected_twin_id, expected_dtmi, properties=None, component_name=None +): + assert twin["$dtId"] == expected_twin_id + assert twin["$etag"] + + metadata = twin["$metadata"] + metadata["$model"] == expected_dtmi + + if properties: + properties = json.loads(properties) + assert properties + + for key in properties: + if key != component_name: + assert properties[key] == twin[key] + + +def assert_twin_relationship_attributes( + twin_relationship_obj, + expected_relationship, + relationship_id, + source_id, + target_id, + properties=None, +): + assert twin_relationship_obj["$relationshipId"] == relationship_id + assert twin_relationship_obj["$relationshipName"] == expected_relationship + assert twin_relationship_obj["$sourceId"] == source_id + assert twin_relationship_obj["$targetId"] == target_id + + if properties: + properties = json.loads(properties) + for key in properties: + assert twin_relationship_obj[key] == properties[key] diff --git a/azext_iot/tests/digitaltwins/test_dt_twins_unit.py b/azext_iot/tests/digitaltwins/test_dt_twins_unit.py new file mode 100644 index 000000000..dfaada441 --- /dev/null +++ b/azext_iot/tests/digitaltwins/test_dt_twins_unit.py @@ -0,0 +1,1583 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import re +import pytest +import responses +import json +from knack.cli import CLIError +from azext_iot.digitaltwins import commands_twins as subject +from azext_iot.tests.generators import generate_generic_id +from msrest.paging import Paged + +instance_name = generate_generic_id() +hostname = "{}.subdomain.domain".format(instance_name) +etag = 'AAAA==' +resource_group = 'myrg' + +model_id = generate_generic_id() +twin_id = generate_generic_id() +target_twin_id = generate_generic_id() +relationship_id = generate_generic_id() +component_path = generate_generic_id() + +generic_result = json.dumps({"result": generate_generic_id()}) +generic_query = "select * from digitaltwins" +models_result = json.dumps({"id": model_id}) +generic_patch_1 = json.dumps({"a" : "b"}) +generic_patch_2 = json.dumps({"a" : "b", "c" : "d"}) + + +def generate_twin_result(randomized=False): + return { + "$dtId": generate_generic_id() if randomized else twin_id, + "$etag": generate_generic_id() if randomized else etag, + "$metadata": { + "$model": generate_generic_id() if randomized else model_id + } + } + + +def create_relationship(relationship_name=None): + return { + "$relationshipId": generate_generic_id(), + "$relationshipName": relationship_name, + "$sourceId": generate_generic_id() + } + + +@pytest.fixture +def control_and_data_plane_client(mocker, fixture_cmd): + from azext_iot.sdk.digitaltwins.controlplane import AzureDigitalTwinsManagementClient + from azext_iot.sdk.digitaltwins.dataplane import AzureDigitalTwinsAPI + from azext_iot.digitaltwins.providers.auth import DigitalTwinAuthentication + + patched_get_raw_token = mocker.patch( + "azure.cli.core._profile.Profile.get_raw_token" + ) + patched_get_raw_token.return_value = ( + mocker.MagicMock(name="creds"), + mocker.MagicMock(name="subscription"), + mocker.MagicMock(name="tenant"), + ) + + control_plane_patch = mocker.patch( + "azext_iot.digitaltwins.providers.digitaltwins_service_factory" + ) + control_plane_patch.return_value = AzureDigitalTwinsManagementClient( + credentials=DigitalTwinAuthentication( + fixture_cmd, "00000000-0000-0000-0000-000000000000" + ), + subscription_id="00000000-0000-0000-0000-000000000000", + ) + + data_plane_patch = mocker.patch( + "azext_iot.digitaltwins.providers.base.DigitalTwinsProvider.get_sdk" + ) + + data_plane_patch.return_value = AzureDigitalTwinsAPI( + credentials=DigitalTwinAuthentication( + fixture_cmd, "00000000-0000-0000-0000-000000000000" + ), + base_url="https://{}/".format(hostname) + ) + + return control_plane_patch, data_plane_patch + + +@pytest.fixture +def start_twin_response(mocked_response, control_and_data_plane_client): + mocked_response.assert_all_requests_are_fired = False + + mocked_response.add( + method=responses.GET, + content_type="application/json", + url=re.compile( + "https://management.azure.com/subscriptions/(.*)/" + "providers/Microsoft.DigitalTwins/digitalTwinsInstances" + ), + status=200, + match_querystring=False, + body=json.dumps({"hostName": hostname}), + ) + + yield mocked_response + + +def check_resource_group_name_call(service_client, resource_group_input): + if ("management.azure.com/subscriptions" in service_client.calls[0].request.url): + return 1 + return 0 + + +class TestTwinQueryTwins(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + yield mocked_response + + @pytest.mark.parametrize( + "query_command, show_cost, servresult, numresultsperpage", + [ + (generic_query, True, [], 1), + (generic_query, False, [], 1), + (generic_query, True, [generate_twin_result()], 1), + (generic_query, False, [generate_twin_result()], 1), + (generic_query, True, [generate_twin_result(), generate_twin_result()], 2), + (generic_query, True, [generate_twin_result(), generate_twin_result(), generate_twin_result()], 2) + ] + ) + def test_query_twins( + self, fixture_cmd, service_client, query_command, show_cost, servresult, numresultsperpage + ): + # Set up number of pages, setting it to 1 if result is [] + numpages = int(len(servresult) / numresultsperpage) + if numpages == 0: + numpages += 1 + cost = 0 + + # Create and add the mocked responses + for i in range(numpages): + if i == 0: + url = "https://{}/query".format(hostname) + else: + url = "https://{}/query?{}".format(hostname, str(i)) + + if numpages - i == 1: + contToken = None + value = servresult[i * numresultsperpage:] + else: + contToken = "https://{}/query?{}".format(hostname, str(i + 1)) + value = servresult[i * numresultsperpage:(i + 1) * numresultsperpage] + + cost += 0.5 + i + service_client.add( + method=responses.POST, + url=url, + body=json.dumps({ + "value" : value, + "continuationToken" : contToken + }), + status=200, + content_type="application/json", + match_querystring=False, + headers={ + "Query-Charge": str(0.5 + i) + } + ) + + # Create expected result + query_result = { + "result": servresult + } + if show_cost: + query_result["cost"] = cost + + result = subject.query_twins( + cmd=fixture_cmd, + name_or_hostname=hostname, + query_command=query_command, + show_cost=show_cost, + resource_group_name=None + ) + + assert result == query_result + + @pytest.fixture(params=[(400, 200), (401, 200), (500, 200), (200, 400), (200, 401), (200, 500)]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.POST, + url="https://{}/query".format( + hostname + ), + body=json.dumps({ + "value": [generate_twin_result()], + "continuationToken": "https://{}/query?2".format(hostname) + }), + status=request.param[0], + content_type="application/json", + match_querystring=False, + headers={ + "Query-Charge": "1.0" + } + ) + + mocked_response.add( + method=responses.POST, + url="https://{}/query?2".format( + hostname + ), + body=json.dumps({"value": [generate_twin_result()]}), + status=request.param[1], + content_type="application/json", + match_querystring=False, + headers={ + "Query-Charge": "1.0" + } + ) + + yield mocked_response + + def test_list_relationship_error(self, fixture_cmd, service_client_error): + with pytest.raises(CLIError): + subject.query_twins( + cmd=fixture_cmd, + name_or_hostname=hostname, + query_command=generic_query, + show_cost=False, + resource_group_name=None + ) + + +class TestTwinCreateTwin(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + mocked_response.add( + method=responses.PUT, + url="https://{}/digitaltwins/{}".format( + hostname, twin_id + ), + body=generic_result, + status=200, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "if_none_match, properties, resource_group_name", + [ + (False, None, None), + (True, None, None), + (False, None, resource_group), + (False, generic_patch_1, None), + (False, generic_patch_2, None) + ] + ) + def test_create_twin(self, fixture_cmd, service_client, if_none_match, properties, resource_group_name): + result = subject.create_twin( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + model_id=model_id, + if_none_match=if_none_match, + properties=properties, + resource_group_name=resource_group_name + ) + + start = check_resource_group_name_call(service_client, resource_group_name) + + twin_request = service_client.calls[start].request + assert twin_request.method == "PUT" + assert "{}/digitaltwins/{}".format(hostname, twin_id) in twin_request.url + + twin_request_body = json.loads(twin_request.body) + assert twin_request_body["$dtId"] == twin_id + assert twin_request_body["$metadata"]["$model"] == model_id + + if if_none_match: + assert twin_request.headers["If-None-Match"] == "*" + + if properties: + for (key, value) in json.loads(properties).items(): + assert twin_request_body[key] == value + + assert result == json.loads(generic_result) + + @pytest.fixture(params=[400, 401, 500]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.PUT, + url="https://{}/digitaltwins/{}".format( + hostname, twin_id + ), + body=generic_result, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_create_twin_error(self, fixture_cmd, service_client_error): + with pytest.raises(CLIError): + subject.create_twin( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + model_id=model_id, + properties=None + ) + + +class TestTwinShowTwin(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}".format( + hostname, twin_id + ), + body=generic_result, + status=200, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "resource_group_name", + [None, resource_group] + ) + def test_show_twin(self, fixture_cmd, service_client, resource_group_name): + result = subject.show_twin( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + resource_group_name=resource_group_name + ) + + assert result == json.loads(generic_result) + + @pytest.fixture(params=[400, 401, 500]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}".format( + hostname, twin_id + ), + body=generic_result, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "resource_group_name", + [None, resource_group] + ) + def test_show_twin_error(self, fixture_cmd, service_client_error, resource_group_name): + with pytest.raises(CLIError): + subject.show_twin( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + resource_group_name=resource_group_name + ) + + +class TestTwinUpdateTwin(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + mocked_response.add( + method=responses.PATCH, + url="https://{}/digitaltwins/{}".format( + hostname, twin_id + ), + body=generic_result, + status=202, + content_type="application/json", + match_querystring=False, + ) + + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}".format( + hostname, twin_id + ), + body=json.dumps(generate_twin_result()), + status=200, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "json_patch, resource_group_name, etag", + [ + (json.dumps({}), None, None), + (json.dumps({}), resource_group, None), + (json.dumps({}), None, etag), + (generic_patch_1, None, None), + (generic_patch_2, None, None) + ] + ) + def test_update_twin(self, fixture_cmd, service_client, json_patch, resource_group_name, etag): + result = subject.update_twin( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + json_patch=json_patch, + resource_group_name=resource_group_name, + etag=etag + ) + + start = check_resource_group_name_call(service_client, resource_group_name) + + # check patch request + patch_request = service_client.calls[start].request + assert patch_request.method == "PATCH" + + expected_request_body = [json.loads(json_patch)] + assert json.loads(patch_request.body) == expected_request_body + + assert patch_request.headers["If-Match"] == etag if etag else "*" + + # check get request + get_request = service_client.calls[start + 1].request + assert get_request.method == "GET" + + assert result == generate_twin_result() + + def test_update_twin_invalid_patch(self, fixture_cmd, service_client): + # CLIError is raised when --json-patch is not dict or list + with pytest.raises(CLIError) as e: + subject.update_twin( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + json_patch="'{op:add,path:/setPointTemp,value:50.2}'", + resource_group_name=None, + etag=None + ) + assert str(e.value) == "--json-patch content must be an object or array. Actual type was: str" + + @pytest.fixture(params=[(400, 200), (401, 200), (500, 200), (202, 400), (202, 401), (202, 500)]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.PATCH, + url="https://{}/digitaltwins/{}".format( + hostname, twin_id + ), + body=generic_result, + status=request.param[0], + content_type="application/json", + match_querystring=False, + ) + + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}".format( + hostname, twin_id + ), + body=generic_result, + status=request.param[1], + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_update_twin_error(self, fixture_cmd, service_client_error): + with pytest.raises(CLIError): + subject.update_twin( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + json_patch=generic_patch_1, + resource_group_name=None, + etag=None + ) + + +class TestTwinDeleteTwin(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + mocked_response.add( + method=responses.DELETE, + url="https://{}/digitaltwins/{}".format( + hostname, twin_id + ), + body=generic_result, + status=204, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "resource_group_name, etag", + [(None, None), (resource_group, None), (None, etag)] + ) + def test_delete_twin(self, fixture_cmd, service_client, resource_group_name, etag): + result = subject.delete_twin( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + resource_group_name=resource_group_name, + etag=etag + ) + + start = check_resource_group_name_call(service_client, resource_group_name) + + delete_request = service_client.calls[start].request + assert delete_request.method == "DELETE" + assert delete_request.headers["If-Match"] == etag if etag else "*" + + assert result is None + + @pytest.fixture(params=[400, 401, 500]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.DELETE, + url="https://{}/digitaltwins/{}".format( + hostname, twin_id + ), + body=generic_result, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "resource_group_name, etag", + [(None, None), (resource_group, None), (None, etag)] + ) + def test_delete_twin_error(self, fixture_cmd, service_client_error, resource_group_name, etag): + with pytest.raises(CLIError): + subject.delete_twin( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + resource_group_name=resource_group_name, + etag=etag + ) + + +class TestTwinDeleteAllTwin(object): + @pytest.fixture + def service_client_all(self, mocked_response, start_twin_response): + yield mocked_response + + @pytest.mark.parametrize( + "number_twins", [0, 1, 3] + ) + def test_delete_twin_all(self, mocker, fixture_cmd, service_client_all, number_twins): + + # Create query call and delete calls + query_result = [] + for i in range(number_twins): + twin = generate_twin_result(randomized=True) + query_result.append(twin) + # Query calls to check if there are any relationships + service_client_all.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/incomingrelationships".format( + hostname, twin["$dtId"] + ), + body=json.dumps({ + "value" : [], + "nextLink" : None + }), + status=200, + content_type="application/json", + match_querystring=False + ) + service_client_all.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/relationships".format( + hostname, twin["$dtId"] + ), + body=json.dumps({ + "value" : [], + "nextLink" : None + }), + status=200, + content_type="application/json", + match_querystring=False + ) + # Delete call + service_client_all.add( + method=responses.DELETE, + url="https://{}/digitaltwins/{}".format( + hostname, twin["$dtId"] + ), + body=generic_result, + status=204 if i % 2 == 0 else 400, + content_type="application/json", + match_querystring=False, + ) + # Query call for twins to delete + service_client_all.add( + method=responses.POST, + url="https://{}/query".format( + hostname + ), + body=json.dumps({ + "value": query_result, + "continuationToken": None + }), + status=200, + content_type="application/json", + match_querystring=False, + headers={ + "Query-Charge": "1.0" + } + ) + + # Call the delete all command + result = subject.delete_all_twin( + cmd=fixture_cmd, + name_or_hostname=hostname, + ) + + start = check_resource_group_name_call(service_client_all, resource_group_input=None) + + delete_request = service_client_all.calls[start].request + assert delete_request.method == "POST" + + # Check delete calls + for i in range(number_twins): + query1_request = service_client_all.calls[start + 1 + 3 * i].request + assert query1_request.method == "GET" + assert query_result[i]["$dtId"] in query1_request.url + + query2_request = service_client_all.calls[start + 2 + 3 * i].request + assert query2_request.method == "GET" + assert query_result[i]["$dtId"] in query2_request.url + + delete_request = service_client_all.calls[start + 3 + 3 * i].request + assert delete_request.method == "DELETE" + assert query_result[i]["$dtId"] in delete_request.url + + assert result is None + + +class TestTwinCreateRelationship(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + mocked_response.add( + method=responses.PUT, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, twin_id, relationship_id + ), + body=generic_result, + status=200, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "relationship, if_none_match, properties, resource_group_name", + [ + ("contains", False, None, None), + ("", False, None, None), + ("contains", True, None, None), + ("contains", False, generic_patch_1, None), + ("contains", False, generic_patch_2, None), + ("contains", False, None, resource_group) + ] + ) + def test_create_relationship(self, fixture_cmd, service_client, relationship, if_none_match, properties, resource_group_name): + result = subject.create_relationship( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + target_twin_id=target_twin_id, + relationship_id=relationship_id, + relationship=relationship, + if_none_match=if_none_match, + properties=properties, + resource_group_name=resource_group_name + ) + + start = check_resource_group_name_call(service_client, resource_group_name) + + # check body + put_request = service_client.calls[start].request + assert put_request.method == "PUT" + assert "{}/digitaltwins/{}/relationships/{}".format(hostname, twin_id, relationship_id) in put_request.url + result_request_body = json.loads(put_request.body) + + assert result_request_body["$targetId"] == target_twin_id + assert result_request_body["$relationshipName"] == relationship + + if if_none_match: + assert put_request.headers["If-None-Match"] == "*" + + if properties: + for (key, value) in json.loads(properties).items(): + assert result_request_body[key] == value + + assert result == json.loads(generic_result) + + @pytest.fixture(params=[400, 401, 500]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.PUT, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, twin_id, relationship_id + ), + body=generic_result, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_create_relationship_error(self, fixture_cmd, service_client_error): + with pytest.raises(CLIError): + subject.create_relationship( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + target_twin_id=target_twin_id, + relationship_id=relationship_id, + relationship="" + ) + + +class TestTwinShowRelationship(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, twin_id, relationship_id + ), + body=generic_result, + status=200, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize("resource_group_name", [None, resource_group]) + def test_show_relationship(self, fixture_cmd, service_client, resource_group_name): + result = subject.show_relationship( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + relationship_id=relationship_id, + resource_group_name=resource_group_name + ) + + assert result == json.loads(generic_result) + + @pytest.fixture(params=[400, 401, 500]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, twin_id, relationship_id + ), + body=generic_result, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_show_relationship_error(self, fixture_cmd, service_client_error): + with pytest.raises(CLIError): + subject.show_relationship( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + relationship_id=relationship_id, + resource_group_name=None + ) + + +class TestTwinUpdateRelationship(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, twin_id, relationship_id + ), + body=generic_result, + status=200, + content_type="application/json", + match_querystring=False, + ) + + mocked_response.add( + method=responses.PATCH, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, twin_id, relationship_id + ), + body=generic_result, + status=204, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "json_patch, resource_group_name, etag", + [ + (json.dumps({}), None, None), + (generic_patch_1, None, None), + (generic_patch_1, resource_group, None), + (generic_patch_1, None, etag), + (generic_patch_2, None, None) + ] + ) + def test_update_relationship(self, fixture_cmd, service_client, json_patch, resource_group_name, etag): + result = subject.update_relationship( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + relationship_id=relationship_id, + json_patch=json_patch, + resource_group_name=resource_group_name, + etag=etag + ) + + start = check_resource_group_name_call(service_client, resource_group_name) + # check patch request + patch_request = service_client.calls[start].request + assert patch_request.method == "PATCH" + + expected_request_body = [json.loads(json_patch)] + assert json.loads(patch_request.body) == expected_request_body + + assert patch_request.headers["If-Match"] == etag if etag else "*" + + # check get request + get_request = service_client.calls[start + 1].request + assert get_request.method == "GET" + + assert result == json.loads(generic_result) + + def test_update_relationship_invalid_patch(self, fixture_cmd, service_client): + # CLIError is raised when --json-patch is not dict or list + with pytest.raises(CLIError) as e: + subject.update_relationship( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + relationship_id=relationship_id, + json_patch="'{op:add,path:/setPointTemp,value:50.2}'", + resource_group_name=None, + etag=None + ) + assert str(e.value) == "--json-patch content must be an object or array. Actual type was: str" + + @pytest.fixture(params=[(400, 200), (401, 200), (500, 200), (204, 400), (204, 401), (204, 500)]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, twin_id, relationship_id + ), + body=generic_result, + status=request.param[0], + content_type="application/json", + match_querystring=False, + ) + + mocked_response.add( + method=responses.PATCH, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, twin_id, relationship_id + ), + body=generic_result, + status=request.param[1], + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_update_relationship_error(self, fixture_cmd, service_client_error): + with pytest.raises(CLIError): + subject.update_relationship( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + relationship_id=relationship_id, + json_patch=generic_patch_1, + resource_group_name=None, + etag=None + ) + + +class TestTwinListRelationship(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + yield mocked_response + + @pytest.mark.parametrize( + "incoming_relationships, relationship, resource_group_name, servresult, numresultsperpage", + [ + (False, None, None, [], 1), + (True, None, None, [], 1), + (False, "", None, [], 1), + (True, "", None, [], 1), + (False, "", None, [create_relationship("")], 1), + (False, "contains", None, [], 1), + (True, "contains", None, [], 1), + (False, "contains", None, [create_relationship("contains")], 1), + (True, "contains", None, [create_relationship("contains")], 1), + (False, "contains", None, [create_relationship("other")], 1), + (True, "contains", None, [create_relationship("other")], 2), + (False, "contains", None, [create_relationship("other"), + create_relationship("contains"), + create_relationship("contains"), + create_relationship("other")], 2), + (True, "contains", None, [create_relationship("other"), + create_relationship("contains"), + create_relationship("contains"), + create_relationship("other")], 1), + (False, None, resource_group, [], 1) + ] + ) + def test_list_relationship( + self, + fixture_cmd, + service_client, + incoming_relationships, + relationship, + resource_group_name, + servresult, + numresultsperpage + ): + # Set up number of pages, setting it to 1 if result is [] + numpages = int(len(servresult) / numresultsperpage) + if numpages == 0: + numpages += 1 + relationship = "incomingrelationships" if incoming_relationships else "relationships" + + # Create and add the mocked responses + for i in range(numpages): + if i == 0: + url = "https://{}/digitaltwins/{}/{}".format(hostname, twin_id, relationship) + else: + url = "https://{}/digitaltwins/{}/{}?{}".format(hostname, twin_id, relationship, str(i)) + + if numpages - i == 1: + contToken = None + value = servresult[i * numresultsperpage:] + else: + contToken = "https://{}/digitaltwins/{}/{}?{}".format(hostname, twin_id, relationship, str(i + 1)) + value = servresult[i * numresultsperpage:(i + 1) * numresultsperpage] + + service_client.add( + method=responses.GET, + url=url, + body=json.dumps({ + "value" : value, + "nextLink" : contToken + }), + status=200, + content_type="application/json", + match_querystring=False + ) + + result = subject.list_relationships( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + incoming_relationships=incoming_relationships, + relationship=relationship, + resource_group_name=resource_group_name + ) + + # Check result, unpack if it is a Paged object + if incoming_relationships: + expected_result = [ + x + for x in servresult + if x["$relationshipName"] and x["$relationshipName"] == relationship + ] + assert result == expected_result + else: + assert isinstance(result, Paged) + unpacked_result = [] + try: + while True: + unpacked_result.extend(result.advance_page()) + except StopIteration: + pass + + assert unpacked_result == servresult + + @pytest.fixture(params=[(400, 200), (401, 200), (500, 200), (200, 400), (200, 401), (200, 500)]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/incomingrelationships".format( + hostname, twin_id + ), + body=json.dumps({ + "value": [create_relationship("contains")], + "nextLink": "https://{}/digitaltwins/{}/incomingrelationships?2".format(hostname, twin_id) + }), + status=request.param[0], + content_type="application/json", + match_querystring=False, + ) + + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/incomingrelationships?2".format( + hostname, twin_id + ), + body=json.dumps({"value": [create_relationship("contains")]}), + status=request.param[1], + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_list_relationship_error(self, fixture_cmd, service_client_error, ): + with pytest.raises(CLIError): + subject.list_relationships( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + incoming_relationships=True, + relationship=None, + resource_group_name=None + ) + + +class TestTwinDeleteRelationship(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + mocked_response.add( + method=responses.DELETE, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, twin_id, relationship_id + ), + body=generic_result, + status=204, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "resource_group_name, etag", + [(None, None), (resource_group, None), (None, etag)] + ) + def test_delete_relationship(self, fixture_cmd, service_client, resource_group_name, etag): + result = subject.delete_relationship( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + relationship_id=relationship_id, + resource_group_name=resource_group_name, + etag=etag + ) + + start = check_resource_group_name_call(service_client, resource_group_name) + + delete_request = service_client.calls[start].request + assert delete_request.method == "DELETE" + assert delete_request.headers["If-Match"] == etag if etag else "*" + + assert result is None + + @pytest.fixture(params=[400, 401, 500]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.DELETE, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, twin_id, relationship_id + ), + body=generic_result, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_delete_relationship_error(self, fixture_cmd, service_client_error): + with pytest.raises(CLIError): + subject.delete_relationship( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + relationship_id=relationship_id, + resource_group_name=None, + etag=None + ) + + +class TestTwinDeleteAllRelationship(object): + @pytest.fixture + def service_client_all(self, mocked_response, start_twin_response): + yield mocked_response + + @pytest.mark.parametrize( + "incoming, outcoming", + [ + (0, 0), + (1, 0), + (0, 1), + (1, 1), + (3, 0), + (0, 3), + (3, 3), + ] + ) + def test_delete_relationship_all(self, mocker, fixture_cmd, service_client_all, incoming, outcoming): + # Create query call with incoming_relationships=True + incoming_query = [] + for i in range(incoming): + relationship = create_relationship("contains") + incoming_query.append(relationship) + service_client_all.add( + method=responses.DELETE, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, relationship["$sourceId"], relationship["$relationshipId"] + ), + body=generic_result, + status=204 if i % 2 == 0 else 400, + content_type="application/json", + match_querystring=False, + ) + service_client_all.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/incomingrelationships".format( + hostname, twin_id + ), + body=json.dumps({ + "value" : incoming_query, + "nextLink" : None + }), + status=200, + content_type="application/json", + match_querystring=False + ) + + # Create query call with incoming_relationships=False + outcoming_query = [] + for i in range(incoming): + relationship = create_relationship("contains") + outcoming_query.append(relationship) + service_client_all.add( + method=responses.DELETE, + url="https://{}/digitaltwins/{}/relationships/{}".format( + hostname, twin_id, relationship["$relationshipId"] + ), + body=generic_result, + status=204 if i % 2 == 0 else 400, + content_type="application/json", + match_querystring=False, + ) + service_client_all.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/relationships".format( + hostname, twin_id + ), + body=json.dumps({ + "value" : outcoming_query, + "nextLink" : None + }), + status=200, + content_type="application/json", + match_querystring=False + ) + + # Run the delete all command + result = subject.delete_all_relationship( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + ) + + start = check_resource_group_name_call(service_client_all, resource_group_input=None) + + # First two calls should be the query calls + assert service_client_all.calls[start].request.method == "GET" + assert service_client_all.calls[start + 1].request.method == "GET" + + call_num = start + 2 + for i in range(len(incoming_query)): + delete_request = service_client_all.calls[call_num + i].request + assert delete_request.method == "DELETE" + assert incoming_query[i]["$relationshipId"] in delete_request.url + + call_num += len(incoming_query) + for i in range(len(outcoming_query)): + delete_request = service_client_all.calls[call_num + i].request + assert delete_request.method == "DELETE" + assert outcoming_query[i]["$relationshipId"] in delete_request.url + assert len(service_client_all.calls) == call_num + len(outcoming_query) + + assert result is None + + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + yield mocked_response + + @pytest.mark.parametrize( + "number_twins", [0, 1, 3] + ) + def test_delete_relationships_all_twins(self, mocker, fixture_cmd, service_client, number_twins): + # Create query call and delete calls + query_result = [] + for i in range(number_twins): + twin = generate_twin_result(randomized=True) + query_result.append(twin) + # Query calls to check if there are any relationships + service_client.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/incomingrelationships".format( + hostname, twin["$dtId"] + ), + body=json.dumps({ + "value" : [], + "nextLink" : None + }), + status=200, + content_type="application/json", + match_querystring=False + ) + service_client.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/relationships".format( + hostname, twin["$dtId"] + ), + body=json.dumps({ + "value" : [], + "nextLink" : None + }), + status=200, + content_type="application/json", + match_querystring=False + ) + # the only difference between this and delete_all_twins is no twin delete call + # Query call for twins to delete + service_client.add( + method=responses.POST, + url="https://{}/query".format( + hostname + ), + body=json.dumps({ + "value": query_result, + "continuationToken": None + }), + status=200, + content_type="application/json", + match_querystring=False, + headers={ + "Query-Charge": "1.0" + } + ) + + # Call the delete all command + result = subject.delete_all_relationship( + cmd=fixture_cmd, + name_or_hostname=hostname, + ) + + start = check_resource_group_name_call(service_client, resource_group_input=None) + + delete_request = service_client.calls[start].request + assert delete_request.method == "POST" + + # Check delete calls + for i in range(number_twins): + query1_request = service_client.calls[start + 1 + 2 * i].request + assert query1_request.method == "GET" + assert query_result[i]["$dtId"] in query1_request.url + + query2_request = service_client.calls[start + 2 + 2 * i].request + assert query2_request.method == "GET" + assert query_result[i]["$dtId"] in query2_request.url + + assert result is None + + +class TestTwinSendTelemetry(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + mocked_response.add( + method=responses.POST, + url="https://{}/digitaltwins/{}/telemetry".format( + hostname, twin_id + ), + body=generic_result, + status=204, + content_type="application/json", + match_querystring=False, + ) + + mocked_response.add( + method=responses.POST, + url="https://{}/digitaltwins/{}/components/{}/telemetry".format( + hostname, twin_id, component_path + ), + body=generic_result, + status=204, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "dt_id, component_path, telemetry, resource_group_name", + [ + (None, None, json.dumps({}), None), + ("DT_ID", None, json.dumps({}), None), + (None, component_path, json.dumps({}), None), + (None, None, generic_patch_1, None), + (None, None, generic_patch_2, None), + (None, None, json.dumps({}), resource_group) + ] + ) + def test_send_telemetry(self, fixture_cmd, service_client, dt_id, component_path, telemetry, resource_group_name): + result = subject.send_telemetry( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + dt_id=dt_id, + component_path=component_path, + telemetry=telemetry, + resource_group_name=resource_group_name, + ) + + start = check_resource_group_name_call(service_client, resource_group_name) + + if component_path: + component_telemetry_request = service_client.calls[start].request + assert component_telemetry_request.method == "POST" + assert ( + "{}/digitaltwins/{}/components/{}/telemetry".format(hostname, twin_id, component_path) + in component_telemetry_request.url + ) + + expected_request_body = json.loads(telemetry) + assert json.loads(component_telemetry_request.body) == expected_request_body + + if dt_id: + component_telemetry_request.headers["Message-Id"] == dt_id + + start += 1 + + # Check POST telemetry + twin_telemetry_request = service_client.calls[start].request + assert twin_telemetry_request.method == "POST" + assert "{}/digitaltwins/{}/telemetry".format(hostname, twin_id) in twin_telemetry_request.url + + expected_request_body = json.loads(telemetry) + assert json.loads(twin_telemetry_request.body) == expected_request_body + + if dt_id: + twin_telemetry_request.headers["Message-Id"] == dt_id + + assert result is None + + @pytest.fixture(params=[(400, 204), (401, 204), (500, 204), (204, 400), (204, 401), (204, 500)]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.POST, + url="https://{}/digitaltwins/{}/telemetry".format( + hostname, twin_id + ), + body=generic_result, + status=request.param[0], + content_type="application/json", + match_querystring=False, + ) + + mocked_response.add( + method=responses.POST, + url="https://{}/digitaltwins/{}/components/{}/telemetry".format( + hostname, twin_id, component_path + ), + body=generic_result, + status=request.param[1], + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_send_telemetry_error(self, fixture_cmd, service_client_error): + with pytest.raises(CLIError): + subject.send_telemetry( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + dt_id=None, + component_path=component_path, + telemetry=json.dumps({}), + resource_group_name=None, + ) + + +class TestTwinShowComponent(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/components/{}".format( + hostname, twin_id, component_path + ), + body=generic_result, + status=200, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize("resource_group_name", [None, resource_group]) + def test_show_component(self, fixture_cmd, service_client, resource_group_name): + result = subject.show_component( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + component_path=component_path, + resource_group_name=resource_group_name, + ) + + assert result == json.loads(generic_result) + + @pytest.fixture(params=[400, 401, 500]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/components/{}".format( + hostname, twin_id, component_path + ), + body=generic_result, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_show_component_error(self, fixture_cmd, service_client_error): + with pytest.raises(CLIError): + subject.show_component( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + component_path=component_path, + resource_group_name=None, + ) + + +class TestTwinUpdateComponent(object): + @pytest.fixture + def service_client(self, mocked_response, start_twin_response): + mocked_response.add( + method=responses.PATCH, + url="https://{}/digitaltwins/{}/components/{}".format( + hostname, twin_id, component_path + ), + body=generic_result, + status=204, + content_type="application/json", + match_querystring=False, + ) + + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}/components/{}".format( + hostname, twin_id, component_path + ), + body=generic_result, + status=200, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "json_patch, resource_group_name, etag", + [ + (json.dumps({}), None, None), + (generic_patch_1, None, None), + (generic_patch_2, None, None), + (generic_patch_1, resource_group, None), + (generic_patch_1, None, etag) + ] + ) + def test_update_component(self, fixture_cmd, service_client, json_patch, resource_group_name, etag): + result = subject.update_component( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + component_path=component_path, + json_patch=json_patch, + resource_group_name=resource_group_name, + etag=etag + ) + + start = check_resource_group_name_call(service_client, resource_group_name) + # check patch request + patch_request = service_client.calls[start].request + assert patch_request.method == "PATCH" + + expected_request_body = [json.loads(json_patch)] + assert json.loads(patch_request.body) == expected_request_body + + assert patch_request.headers["If-Match"] == etag if etag else "*" + + # check get request + get_request = service_client.calls[start + 1].request + assert get_request.method == "GET" + + assert result == json.loads(generic_result) + + def test_update_component_invalid_patch(self, fixture_cmd, service_client): + # CLIError is raised when --json-patch is not dict or list + with pytest.raises(CLIError) as e: + subject.update_component( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + component_path=component_path, + json_patch="'{op:add,path:/setPointTemp,value:50.2}'", + resource_group_name=None, + etag=None + ) + assert str(e.value) == "--json-patch content must be an object or array. Actual type was: str" + + @pytest.fixture(params=[400, 401, 500]) + def service_client_error(self, mocked_response, start_twin_response, request): + mocked_response.add( + method=responses.PATCH, + url="https://{}/digitaltwins/{}/components/{}".format( + hostname, twin_id, component_path + ), + body=generic_result, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_update_component_error(self, fixture_cmd, service_client_error): + with pytest.raises(CLIError): + subject.update_component( + cmd=fixture_cmd, + name_or_hostname=hostname, + twin_id=twin_id, + component_path=component_path, + json_patch=generic_patch_1, + resource_group_name=None, + etag=None + ) diff --git a/azext_iot/tests/dps/test_iot_dps_int.py b/azext_iot/tests/dps/test_iot_dps_int.py new file mode 100644 index 000000000..4cff8cd72 --- /dev/null +++ b/azext_iot/tests/dps/test_iot_dps_int.py @@ -0,0 +1,650 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +from azure.cli.testsdk import LiveScenarioTest +from azext_iot.common.shared import EntityStatusType, AttestationType, AllocationType +from azext_iot.common.certops import create_self_signed_certificate +from azext_iot.common import embedded_cli +from azext_iot.common.utility import generate_key +from azext_iot.iothub.providers.discovery import IotHubDiscovery +from azext_iot.tests.settings import Setting + +# Set these to the proper IoT Hub DPS, IoT Hub and Resource Group for Integration Tests. +dps = os.environ.get("azext_iot_testdps") +rg = os.environ.get("azext_iot_testrg") +hub = os.environ.get("azext_iot_testhub") + +if not all([dps, rg, hub]): + raise ValueError( + "Set azext_iot_testhub, azext_iot_testdps " + "and azext_iot_testrg to run integration tests." + ) + +cert_name = "test" +cert_path = cert_name + "-cert.pem" +test_endorsement_key = ( + "AToAAQALAAMAsgAgg3GXZ0SEs/gakMyNRqXXJP1S124GUgtk8qHaGzMUaaoABgCAAEMAEAgAAAAAAAEAibym9HQP9vxCGF5dVc1Q" + "QsAGe021aUGJzNol1/gycBx3jFsTpwmWbISRwnFvflWd0w2Mc44FAAZNaJOAAxwZvG8GvyLlHh6fGKdh+mSBL4iLH2bZ4Ry22cB3" + "CJVjXmdGoz9Y/j3/NwLndBxQC+baNvzvyVQZ4/A2YL7vzIIj2ik4y+ve9ir7U0GbNdnxskqK1KFIITVVtkTIYyyFTIR0BySjPrRI" + "Dj7r7Mh5uF9HBppGKQCBoVSVV8dI91lNazmSdpGWyqCkO7iM4VvUMv2HT/ym53aYlUrau+Qq87Tu+uQipWYgRdF11KDfcpMHqqzB" + "QQ1NpOJVhrsTrhyJzO7KNw==" +) +provisioning_status = EntityStatusType.enabled.value +provisioning_status_new = EntityStatusType.disabled.value + + +def _cleanup_enrollments(self, dps, rg): + enrollments = self.cmd( + "iot dps enrollment list --dps-name {} -g {}".format(dps, rg) + ).get_output_in_json() + if len(enrollments) > 0: + enrollment_ids = list(map(lambda x: x["registrationId"], enrollments)) + for id in enrollment_ids: + self.cmd( + "iot dps enrollment delete --dps-name {} -g {} --enrollment-id {}".format( + dps, rg, id + ) + ) + + enrollment_groups = self.cmd( + "iot dps enrollment-group list --dps-name {} -g {}".format(dps, rg) + ).get_output_in_json() + if len(enrollment_groups) > 0: + enrollment_ids = list(map(lambda x: x["enrollmentGroupId"], enrollment_groups)) + for id in enrollment_ids: + self.cmd( + "iot dps enrollment-group delete --dps-name {} -g {} --enrollment-id {}".format( + dps, rg, id + ) + ) + + self.cmd( + "iot dps enrollment list --dps-name {} -g {}".format(dps, rg), + checks=self.is_empty(), + ) + self.cmd( + "iot dps enrollment-group list --dps-name {} -g {}".format(dps, rg), + checks=self.is_empty(), + ) + + +def _ensure_dps_hub_link(self, dps, rg, hub): + cli = embedded_cli.EmbeddedCLI() + hubs = cli.invoke( + "iot dps linked-hub list --dps-name {} -g {}".format(dps, rg) + ).as_json() + if not len(hubs) or not len( + list( + filter( + lambda linked_hub: linked_hub["name"] + == "{}.azure-devices.net".format(hub), + hubs, + ) + ) + ): + discovery = IotHubDiscovery(self.cmd_shell) + target_hub = discovery.get_target(hub, rg) + cli.invoke( + "iot dps linked-hub create --dps-name {} -g {} --connection-string {} --location {}".format( + dps, rg, target_hub.get("cs"), target_hub.get("location") + ) + ) + + +class TestDPSEnrollments(LiveScenarioTest): + def __init__(self, test_method): + super(TestDPSEnrollments, self).__init__(test_method) + self.cmd_shell = Setting() + setattr(self.cmd_shell, "cli_ctx", self.cli_ctx) + + _ensure_dps_hub_link(self, dps, rg, hub) + + output_dir = os.getcwd() + create_self_signed_certificate(cert_name, 200, output_dir, True) + base_enrollment_props = { + "count": None, + "metadata": None, + "version": None, + } + self.kwargs["generic_dict"] = { + **base_enrollment_props, + "key": "value", + } + self.kwargs["twin_array_dict"] = { + **base_enrollment_props, + "values": [{"key1": "value1"}, {"key2": "value2"}], + } + + _cleanup_enrollments(self, dps, rg) + + def __del__(self): + if os.path.exists(cert_path): + os.remove(cert_path) + + def test_dps_compute_device_key(self): + device_key = self.cmd( + 'az iot dps compute-device-key --key "{}" ' + "--registration-id myarbitrarydeviceId".format(test_endorsement_key) + ).output + device_key = device_key.strip("\"'\n") + assert device_key == "cT/EXZvsplPEpT//p98Pc6sKh8mY3kYgSxavHwMkl7w=" + + def test_dps_enrollment_tpm_lifecycle(self): + enrollment_id = self.create_random_name("enrollment-for-test", length=48) + device_id = self.create_random_name("device-id-for-test", length=48) + attestation_type = AttestationType.tpm.value + hub_host_name = "{}.azure-devices.net".format(hub) + + enrollment = self.cmd( + "iot dps enrollment create --enrollment-id {} --attestation-type {}" + " -g {} --dps-name {} --endorsement-key {}" + " --provisioning-status {} --device-id {} --initial-twin-tags {}" + " --initial-twin-properties {} --allocation-policy {} --iot-hubs {}".format( + enrollment_id, + attestation_type, + rg, + dps, + test_endorsement_key, + provisioning_status, + device_id, + '"{generic_dict}"', + '"{generic_dict}"', + AllocationType.static.value, + hub_host_name, + ), + checks=[ + self.check("attestation.type", attestation_type), + self.check("registrationId", enrollment_id), + self.check("provisioningStatus", provisioning_status), + self.check("deviceId", device_id), + self.check("allocationPolicy", AllocationType.static.value), + self.check("iotHubs", hub_host_name.split()), + self.check("initialTwin.tags", self.kwargs["generic_dict"]), + self.check( + "initialTwin.properties.desired", self.kwargs["generic_dict"] + ), + self.exists("reprovisionPolicy"), + self.check("reprovisionPolicy.migrateDeviceData", True), + self.check("reprovisionPolicy.updateHubAssignment", True), + ], + ).get_output_in_json() + etag = enrollment["etag"] + + self.cmd( + "iot dps enrollment list -g {} --dps-name {}".format(rg, dps), + checks=[ + self.check("length(@)", 1), + self.check("[0].registrationId", enrollment_id), + ], + ) + + self.cmd( + "iot dps enrollment show -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_id + ), + checks=[self.check("registrationId", enrollment_id)], + ) + + self.cmd( + "iot dps enrollment show -g {} --dps-name {} --enrollment-id {} --show-keys".format( + rg, dps, enrollment_id + ), + checks=[ + self.check("registrationId", enrollment_id), + self.check("attestation.type", attestation_type), + self.exists("attestation.{}".format(attestation_type)), + ], + ) + + self.cmd( + "iot dps enrollment update -g {} --dps-name {} --enrollment-id {}" + " --provisioning-status {} --etag {}".format( + rg, dps, enrollment_id, provisioning_status_new, etag + ), + checks=[ + self.check("attestation.type", attestation_type), + self.check("registrationId", enrollment_id), + self.check("provisioningStatus", provisioning_status_new), + self.check("deviceId", device_id), + self.check("allocationPolicy", AllocationType.static.value), + self.check("iotHubs", hub_host_name.split()), + self.exists("initialTwin.tags"), + self.exists("initialTwin.properties.desired"), + ], + ) + + self.cmd( + "iot dps enrollment delete -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_id + ) + ) + + def test_dps_enrollment_x509_lifecycle(self): + enrollment_id = self.create_random_name("enrollment-for-test", length=48) + attestation_type = AttestationType.x509.value + device_id = self.create_random_name("device-id-for-test", length=48) + hub_host_name = "{}.azure-devices.net".format(hub) + + etag = self.cmd( + "iot dps enrollment create --enrollment-id {} --attestation-type {}" + " -g {} --dps-name {} --cp {} --scp {}" + " --provisioning-status {} --device-id {}" + " --initial-twin-tags {} --initial-twin-properties {}" + " --allocation-policy {} --iot-hubs {}".format( + enrollment_id, + attestation_type, + rg, + dps, + cert_path, + cert_path, + provisioning_status, + device_id, + '"{generic_dict}"', + '"{generic_dict}"', + AllocationType.hashed.value, + hub_host_name, + ), + checks=[ + self.check("attestation.type", attestation_type), + self.check("registrationId", enrollment_id), + self.check("provisioningStatus", provisioning_status), + self.check("deviceId", device_id), + self.check("allocationPolicy", AllocationType.hashed.value), + self.check("iotHubs", hub_host_name.split()), + self.check("initialTwin.tags", self.kwargs["generic_dict"]), + self.check( + "initialTwin.properties.desired", self.kwargs["generic_dict"] + ), + self.exists("reprovisionPolicy"), + self.check("reprovisionPolicy.migrateDeviceData", True), + self.check("reprovisionPolicy.updateHubAssignment", True), + ], + ).get_output_in_json()["etag"] + + self.cmd( + "iot dps enrollment list -g {} --dps-name {}".format(rg, dps), + checks=[ + self.check("length(@)", 1), + self.check("[0].registrationId", enrollment_id), + ], + ) + + self.cmd( + "iot dps enrollment show -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_id + ), + checks=[self.check("registrationId", enrollment_id)], + ) + + self.cmd( + "iot dps enrollment update -g {} --dps-name {} --enrollment-id {}" + " --provisioning-status {} --etag {} --rc".format( + rg, dps, enrollment_id, provisioning_status_new, etag + ), + checks=[ + self.check("attestation.type", attestation_type), + self.check("registrationId", enrollment_id), + self.check("provisioningStatus", provisioning_status_new), + self.check("deviceId", device_id), + self.check("allocationPolicy", AllocationType.hashed.value), + self.check("iotHubs", hub_host_name.split()), + self.exists("initialTwin.tags"), + self.exists("initialTwin.properties.desired"), + self.check("attestation.type.x509.clientCertificates.primary", None), + ], + ) + + self.cmd( + "iot dps enrollment delete -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_id + ) + ) + + def test_dps_enrollment_symmetrickey_lifecycle(self): + enrollment_id = self.create_random_name("enrollment-for-test", length=48) + enrollment_id2 = self.create_random_name("enrollment-for-test", length=48) + attestation_type = AttestationType.symmetricKey.value + primary_key = generate_key() + secondary_key = generate_key() + device_id = self.create_random_name("device-id-for-test", length=48) + reprovisionPolicy_reprovisionandresetdata = "reprovisionandresetdata" + hub_host_name = "{}.azure-devices.net".format(hub) + webhook_url = "https://www.test.test" + api_version = "2019-03-31" + + etag = self.cmd( + "iot dps enrollment create --enrollment-id {} --attestation-type {}" + " -g {} --dps-name {} --pk {} --sk {}" + " --provisioning-status {} --device-id {}" + " --initial-twin-tags {} --initial-twin-properties {}" + " --allocation-policy {} --rp {} --iot-hubs {} --edge-enabled".format( + enrollment_id, + attestation_type, + rg, + dps, + primary_key, + secondary_key, + provisioning_status, + device_id, + '"{generic_dict}"', + '"{generic_dict}"', + AllocationType.geolatency.value, + reprovisionPolicy_reprovisionandresetdata, + hub_host_name, + ), + checks=[ + self.check("attestation.type", attestation_type), + self.check("registrationId", enrollment_id), + self.check("provisioningStatus", provisioning_status), + self.check("deviceId", device_id), + self.check("allocationPolicy", "geoLatency"), + # self.check("iotHubs", hub_host_name.split()), + self.check("initialTwin.tags", self.kwargs["generic_dict"]), + self.check( + "initialTwin.properties.desired", self.kwargs["generic_dict"] + ), + self.exists("reprovisionPolicy"), + self.check("reprovisionPolicy.migrateDeviceData", False), + self.check("reprovisionPolicy.updateHubAssignment", True), + self.check("capabilities.iotEdge", True), + ], + ).get_output_in_json()["etag"] + + self.cmd( + "iot dps enrollment list -g {} --dps-name {}".format(rg, dps), + checks=[ + self.check("length(@)", 1), + self.check("[0].registrationId", enrollment_id), + ], + ) + + self.cmd( + "iot dps enrollment show -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_id + ), + checks=[self.check("registrationId", enrollment_id)], + ) + + self.cmd( + "iot dps enrollment update -g {} --dps-name {} --enrollment-id {}" + " --provisioning-status {} --etag {} --edge-enabled False" + " --allocation-policy {} --webhook-url {} --api-version {}".format( + rg, + dps, + enrollment_id, + provisioning_status_new, + etag, + AllocationType.custom.value, + webhook_url, + api_version, + ), + checks=[ + self.check("attestation.type", attestation_type), + self.check("registrationId", enrollment_id), + self.check("provisioningStatus", provisioning_status_new), + self.check("deviceId", device_id), + self.check("allocationPolicy", "custom"), + self.check("customAllocationDefinition.webhookUrl", webhook_url), + self.check("customAllocationDefinition.apiVersion", api_version), + # self.check("iotHubs", hub_host_name.split()), + self.exists("initialTwin.tags"), + self.exists("initialTwin.properties.desired"), + self.check("attestation.symmetricKey.primaryKey", primary_key), + self.check("capabilities.iotEdge", False), + ], + ) + + self.cmd( + "iot dps enrollment create --enrollment-id {} --attestation-type {}" + " -g {} --dps-name {} --allocation-policy {} --webhook-url {} --api-version {}".format( + enrollment_id2, + attestation_type, + rg, + dps, + AllocationType.custom.value, + webhook_url, + api_version, + ), + checks=[ + self.check("attestation.type", attestation_type), + self.check("registrationId", enrollment_id2), + self.check("allocationPolicy", "custom"), + self.check("customAllocationDefinition.webhookUrl", webhook_url), + self.check("customAllocationDefinition.apiVersion", api_version), + ], + ) + + self.cmd( + "iot dps enrollment delete -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_id + ) + ) + self.cmd( + "iot dps enrollment delete -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_id2 + ) + ) + + def test_dps_enrollment_group_lifecycle(self): + enrollment_id = self.create_random_name("enrollment-for-test", length=48) + reprovisionPolicy_never = "never" + hub_host_name = "{}.azure-devices.net".format(hub) + webhook_url = "https://www.test.test" + api_version = "2019-03-31" + etag = self.cmd( + "iot dps enrollment-group create --enrollment-id {} -g {} --dps-name {}" + " --cp {} --scp {} --provisioning-status {} --allocation-policy {}" + " --iot-hubs {} --edge-enabled".format( + enrollment_id, + rg, + dps, + cert_path, + cert_path, + provisioning_status, + "geoLatency", + hub_host_name, + ), + checks=[ + self.check("enrollmentGroupId", enrollment_id), + self.check("provisioningStatus", provisioning_status), + self.exists("reprovisionPolicy"), + self.check("allocationPolicy", "geoLatency"), + self.check("iotHubs", hub_host_name.split()), + self.check("reprovisionPolicy.migrateDeviceData", True), + self.check("reprovisionPolicy.updateHubAssignment", True), + self.check("capabilities.iotEdge", True), + ], + ).get_output_in_json()["etag"] + + self.cmd( + "iot dps enrollment-group list -g {} --dps-name {}".format(rg, dps), + checks=[ + self.check("length(@)", 1), + self.check("[0].enrollmentGroupId", enrollment_id), + ], + ) + + self.cmd( + "iot dps enrollment-group show -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_id + ), + checks=[self.check("enrollmentGroupId", enrollment_id)], + ) + + self.cmd( + "iot dps enrollment-group show -g {} --dps-name {} --enrollment-id {} --show-keys".format( + rg, dps, enrollment_id + ), + checks=[ + self.check("enrollmentGroupId", enrollment_id), + self.exists("attestation.x509"), + ], + ) + + etag = self.cmd( + "iot dps enrollment-group update -g {} --dps-name {} --enrollment-id {}" + " --provisioning-status {} --rsc --etag {} --rp {} --allocation-policy {}" + " --edge-enabled False --scp {}".format( + rg, + dps, + enrollment_id, + provisioning_status_new, + etag, + reprovisionPolicy_never, + AllocationType.hashed.value, + cert_path, + ), + checks=[ + self.check("attestation.type", AttestationType.x509.value), + self.check("enrollmentGroupId", enrollment_id), + self.check("provisioningStatus", provisioning_status_new), + self.check("attestation.type.x509.clientCertificates.secondary", None), + self.exists("reprovisionPolicy"), + self.check("allocationPolicy", AllocationType.hashed.value), + self.check("reprovisionPolicy.migrateDeviceData", False), + self.check("reprovisionPolicy.updateHubAssignment", False), + self.check("capabilities.iotEdge", False), + ], + ).get_output_in_json()["etag"] + + self.cmd( + "iot dps registration list -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_id + ), + checks=[self.check("length(@)", 0)], + ) + + cert_name = self.create_random_name("certificate-for-test", length=48) + cert_etag = self.cmd( + "iot dps certificate create -g {} --dps-name {} --name {} --p {}".format( + rg, dps, cert_name, cert_path + ), + checks=[self.check("name", cert_name)], + ).get_output_in_json()["etag"] + + self.cmd( + "iot dps enrollment-group update -g {} --dps-name {} --enrollment-id {}" + " --cn {} --etag {} --allocation-policy {} --webhook-url {} --api-version {}".format( + rg, + dps, + enrollment_id, + cert_name, + etag, + AllocationType.custom.value, + webhook_url, + api_version, + ), + checks=[ + self.check("attestation.type", AttestationType.x509.value), + self.check("enrollmentGroupId", enrollment_id), + self.check("allocationPolicy", "custom"), + self.check("customAllocationDefinition.webhookUrl", webhook_url), + self.check("customAllocationDefinition.apiVersion", api_version), + self.check("attestation.x509.caReferences.primary", cert_name), + self.check("attestation.x509.caReferences.secondary", None), + ], + ) + + self.cmd( + "iot dps enrollment-group delete -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_id + ) + ) + + self.cmd( + "iot dps certificate delete -g {} --dps-name {} --name {} --etag {}".format( + rg, dps, cert_name, cert_etag + ) + ) + + def test_dps_enrollment_twin_array(self): + hub_host_name = "{}.azure-devices.net".format(hub) + + # test twin array in enrollment + attestation_type = AttestationType.x509.value + device_id = self.create_random_name("device-id-for-test", length=48) + enrollment_id = self.create_random_name("enrollment-for-test", length=48) + + self.cmd( + "iot dps enrollment create --enrollment-id {} --attestation-type {}" + " -g {} --dps-name {} --cp {} --scp {}" + " --provisioning-status {} --device-id {}" + " --initial-twin-tags {} --initial-twin-properties {}" + " --allocation-policy {} --iot-hubs {}".format( + enrollment_id, + attestation_type, + rg, + dps, + cert_path, + cert_path, + provisioning_status, + device_id, + '"{generic_dict}"', + '"{twin_array_dict}"', + AllocationType.hashed.value, + hub_host_name, + ), + checks=[ + self.check("attestation.type", attestation_type), + self.check("registrationId", enrollment_id), + self.check("provisioningStatus", provisioning_status), + self.check("deviceId", device_id), + self.check("allocationPolicy", AllocationType.hashed.value), + self.check("iotHubs", hub_host_name.split()), + self.check("initialTwin.tags", self.kwargs["generic_dict"]), + self.check( + "initialTwin.properties.desired", self.kwargs["twin_array_dict"] + ), + self.exists("reprovisionPolicy"), + self.check("reprovisionPolicy.migrateDeviceData", True), + self.check("reprovisionPolicy.updateHubAssignment", True), + ], + ) + + self.cmd( + "iot dps enrollment delete -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_id + ) + ) + + # test twin array in enrollment group + enrollment_group_id = self.create_random_name("enrollment-for-test", length=48) + + self.cmd( + "iot dps enrollment-group create --enrollment-id {} -g {} --dps-name {}" + " --cp {} --scp {} --provisioning-status {} --allocation-policy {}" + " --iot-hubs {} --edge-enabled --props {}".format( + enrollment_group_id, + rg, + dps, + cert_path, + cert_path, + provisioning_status, + "geoLatency", + hub_host_name, + '"{twin_array_dict}"', + ), + checks=[ + self.check("enrollmentGroupId", enrollment_group_id), + self.check("provisioningStatus", provisioning_status), + self.exists("reprovisionPolicy"), + self.check("allocationPolicy", "geoLatency"), + self.check("iotHubs", hub_host_name.split()), + self.check( + "initialTwin.properties.desired", self.kwargs["twin_array_dict"] + ), + self.check("reprovisionPolicy.migrateDeviceData", True), + self.check("reprovisionPolicy.updateHubAssignment", True), + self.check("capabilities.iotEdge", True), + ], + ) + + self.cmd( + "iot dps enrollment-group delete -g {} --dps-name {} --enrollment-id {}".format( + rg, dps, enrollment_group_id + ) + ) diff --git a/azext_iot/tests/test_iot_dps_unit.py b/azext_iot/tests/dps/test_iot_dps_unit.py similarity index 72% rename from azext_iot/tests/test_iot_dps_unit.py rename to azext_iot/tests/dps/test_iot_dps_unit.py index ab378eaec..91fde8687 100644 --- a/azext_iot/tests/test_iot_dps_unit.py +++ b/azext_iot/tests/dps/test_iot_dps_unit.py @@ -13,10 +13,10 @@ import pytest import json +import responses from azext_iot.operations import dps as subject from knack.util import CLIError from azext_iot.common.sas_token_auth import SasTokenAuthentication -from .conftest import build_mock_response enrollment_id = 'myenrollment' resource_group = 'myrg' @@ -31,6 +31,13 @@ mock_target['policy'] = 'provisioningserviceowner' mock_target['subscription'] = "5952cff8-bcd1-4235-9554-af2c0348bf23" +mock_symmetric_key_attestation = { + "type": "symmetricKey", + "symmetricKey": { + "primaryKey": "primary_key", + "secondaryKey": "secondary_key" + }, +} # Patch Paths # path_service_client = 'msrest.service_client.ServiceClient.send' @@ -40,8 +47,8 @@ @pytest.fixture() def fixture_gdcs(mocker): - ghcs = mocker.patch(path_gdcs) - ghcs.return_value = mock_target + gdcs = mocker.patch(path_gdcs) + gdcs.return_value = mock_target @pytest.fixture() @@ -53,20 +60,13 @@ def fixture_sas(mocker): sas.return_value = r -@pytest.fixture(params=[400, 401, 500]) -def serviceclient_generic_error(mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {'error': 'something failed'}) - return service_client - - def generate_enrollment_create_req(attestation_type=None, endorsement_key=None, certificate_path=None, secondary_certificate_path=None, device_Id=None, iot_hub_host_name=None, initial_twin_tags=None, initial_twin_properties=None, provisioning_status=None, reprovision_policy=None, primary_key=None, secondary_key=None, allocation_policy=None, - iot_hubs=None, edge_enabled=False): + iot_hubs=None, edge_enabled=False, webhook_url=None, api_version=None): return {'client': None, 'enrollment_id': enrollment_id, 'rg': resource_group, @@ -85,15 +85,35 @@ def generate_enrollment_create_req(attestation_type=None, endorsement_key=None, 'secondary_key': secondary_key, 'allocation_policy': allocation_policy, 'iot_hubs': iot_hubs, - 'edge_enabled': edge_enabled} + 'edge_enabled': edge_enabled, + 'webhook_url': webhook_url, + 'api_version': api_version} class TestEnrollmentCreate(): - @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {}) - return service_client + @pytest.fixture() + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas): + mocked_response.add( + method=responses.PUT, + url="https://{}/enrollments/{}".format(mock_target['entity'], enrollment_id), + body='{}', + status=200, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response + + @pytest.fixture(params=[400, 401, 500]) + def serviceclient_generic_error(self, mocked_response, fixture_gdcs, fixture_sas, request): + mocked_response.add( + method=responses.PUT, + url="https://{}/enrollments/{}".format(mock_target['entity'], enrollment_id), + body='{}', + status=request.param, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response @pytest.mark.parametrize("req", [ (generate_enrollment_create_req(attestation_type='tpm', @@ -151,13 +171,29 @@ def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): secondary_key='secondarykey', reprovision_policy='never', allocation_policy='geolatency')), + (generate_enrollment_create_req(attestation_type='symmetricKey', + primary_key='primarykey', + secondary_key='secondarykey', + reprovision_policy='never', + allocation_policy='custom', + webhook_url="https://www.test.test", + api_version="2019-03-31")), (generate_enrollment_create_req(attestation_type='symmetricKey', primary_key='primarykey', secondary_key='secondarykey', edge_enabled=True)), + (generate_enrollment_create_req(attestation_type='symmetricKey', + primary_key='primarykey', + secondary_key='secondarykey', + edge_enabled=True, + initial_twin_properties={'key': ['value1', 'value2']})), + (generate_enrollment_create_req(attestation_type='tpm', + endorsement_key='mykey', + provisioning_status='enabled', + initial_twin_properties={'key': ['value1', 'value2']})) ]) - def test_enrollment_create(self, serviceclient, req): - subject.iot_dps_device_enrollment_create(None, + def test_enrollment_create(self, serviceclient, fixture_cmd, req): + subject.iot_dps_device_enrollment_create(fixture_cmd, req['enrollment_id'], req['attestation_type'], req['dps_name'], req['rg'], @@ -174,13 +210,15 @@ def test_enrollment_create(self, serviceclient, req): req['reprovision_policy'], req['allocation_policy'], req['iot_hubs'], - req['edge_enabled']) - args = serviceclient.call_args - url = args[0][0].url + req['edge_enabled'], + req['webhook_url'], + req['api_version']) + request = serviceclient.calls[0].request + url = request.url assert "{}/enrollments/{}?".format(mock_target['entity'], enrollment_id) in url - assert args[0][0].method == 'PUT' + assert request.method == 'PUT' - body = args[0][2] + body = json.loads(request.body) assert body['registrationId'] == req['enrollment_id'] if req['attestation_type'] == 'tpm': assert body['attestation']['type'] == req['attestation_type'] @@ -225,6 +263,9 @@ def test_enrollment_create(self, serviceclient, req): assert not body['reprovisionPolicy']['updateHubAssignment'] if req['allocation_policy']: assert body['allocationPolicy'] == req['allocation_policy'] + if body['allocationPolicy'] == 'custom': + assert body['customAllocationDefinition']['webhookUrl'] == req['webhook_url'] + assert body['customAllocationDefinition']['apiVersion'] == req['api_version'] if req['iot_hubs']: assert body['iotHubs'] == req['iot_hubs'].split() if req['edge_enabled']: @@ -238,13 +279,15 @@ def test_enrollment_create(self, serviceclient, req): (generate_enrollment_create_req(reprovision_policy='invalid')), (generate_enrollment_create_req(allocation_policy='invalid')), (generate_enrollment_create_req(allocation_policy='static')), + (generate_enrollment_create_req(allocation_policy='custom')), + (generate_enrollment_create_req(allocation_policy='custom', webhook_url="https://www.test.test")), (generate_enrollment_create_req(allocation_policy='static', iot_hubs='hub1 hub2')), (generate_enrollment_create_req(allocation_policy='static', iot_hub_host_name='hubname')), (generate_enrollment_create_req(iot_hubs='hub1 hub2')) ]) - def test_enrollment_create_invalid_args(self, serviceclient, req): + def test_enrollment_create_invalid_args(self, fixture_gdcs, fixture_cmd, req): with pytest.raises(CLIError): - subject.iot_dps_device_enrollment_create(None, req['enrollment_id'], + subject.iot_dps_device_enrollment_create(fixture_cmd, req['enrollment_id'], req['attestation_type'], req['dps_name'], req['rg'], req['endorsement_key'], @@ -259,7 +302,8 @@ def test_enrollment_create_invalid_args(self, serviceclient, req): None, req['reprovision_policy'], req['allocation_policy'], - req['iot_hubs']) + req['iot_hubs'], + req['webhook_url']) @pytest.mark.parametrize("req", [ (generate_enrollment_create_req(attestation_type='tpm', endorsement_key='mykey')) @@ -308,7 +352,7 @@ def generate_enrollment_update_req(certificate_path=None, iot_hub_host_name=None device_id=None, etag=None, reprovision_policy=None, allocation_policy=None, iot_hubs=None, - edge_enabled=None): + edge_enabled=None, webhook_url=None, api_version=None): return {'client': None, 'enrollment_id': enrollment_id, 'rg': resource_group, @@ -326,19 +370,34 @@ def generate_enrollment_update_req(certificate_path=None, iot_hub_host_name=None 'reprovision_policy': reprovision_policy, 'allocation_policy': allocation_policy, 'iot_hubs': iot_hubs, - 'edge_enabled': edge_enabled} + 'edge_enabled': edge_enabled, + 'webhook_url': webhook_url, + 'api_version': api_version} class TestEnrollmentUpdate(): - @pytest.fixture(params=[(200, generate_enrollment_show(), 200)]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - test_side_effect = [ - build_mock_response(mocker, request.param[0], request.param[1]), - build_mock_response(mocker, request.param[2]) - ] - service_client.side_effect = test_side_effect - return service_client + @pytest.fixture() + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + # Initial GET + mocked_response.add( + method=responses.GET, + url="https://{}/enrollments/{}".format(mock_target['entity'], enrollment_id), + body=json.dumps(generate_enrollment_show()), + status=200, + content_type="application/json", + match_querystring=False, + ) + + # Update PUT + mocked_response.add( + method=responses.PUT, + url="https://{}/enrollments/{}".format(mock_target['entity'], enrollment_id), + body=json.dumps(generate_enrollment_show()), + status=200, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response @pytest.mark.parametrize("req", [ (generate_enrollment_update_req(etag=etag, secondary_certificate_path='someOtherCertPath')), @@ -355,6 +414,9 @@ def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): (generate_enrollment_update_req(allocation_policy='static', iot_hubs='hub1')), (generate_enrollment_update_req(allocation_policy='hashed', iot_hubs='hub1 hub2')), (generate_enrollment_update_req(allocation_policy='geolatency')), + (generate_enrollment_update_req(allocation_policy='custom', + webhook_url="https://www.test.test", + api_version="2019-03-31")), (generate_enrollment_update_req(edge_enabled=True)), (generate_enrollment_update_req(edge_enabled=False)) ]) @@ -379,15 +441,22 @@ def test_enrollment_update(self, serviceclient, req): req['reprovision_policy'], req['allocation_policy'], req['iot_hubs'], - edge_enabled=req['edge_enabled']) - # Index 1 is the update args - args = serviceclient.call_args_list[1] - url = args[0][0].url + req['edge_enabled'], + req['webhook_url'], + req['api_version']) + get_request = serviceclient.calls[0].request + assert get_request.method == 'GET' + assert "{}/enrollments/{}?".format(mock_target['entity'], enrollment_id) in get_request.url + + update_request = serviceclient.calls[1].request + url = update_request.url assert "{}/enrollments/{}?".format(mock_target['entity'], enrollment_id) in url - assert args[0][0].method == 'PUT' + assert update_request.method == 'PUT' + + assert update_request.headers["If-Match"] == req['etag'] if req['etag'] else "*" - body = args[0][2] + body = json.loads(update_request.body) if not req['certificate_path']: if req['remove_certificate_path']: assert body['attestation']['x509']['clientCertificates'].get('primary') is None @@ -419,6 +488,9 @@ def test_enrollment_update(self, serviceclient, req): assert not body['reprovisionPolicy']['updateHubAssignment'] if req['allocation_policy']: assert body['allocationPolicy'] == req['allocation_policy'] + if body['allocationPolicy'] == 'custom': + assert body['customAllocationDefinition']['webhookUrl'] == req['webhook_url'] + assert body['customAllocationDefinition']['apiVersion'] == req['api_version'] if req['iot_hubs']: assert body['iotHubs'] == req['iot_hubs'].split() if req['edge_enabled'] is not None: @@ -427,22 +499,73 @@ def test_enrollment_update(self, serviceclient, req): class TestEnrollmentShow(): @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, generate_enrollment_show()) - return service_client + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + mocked_response.add( + method=responses.GET, + url="https://{}/enrollments/{}".format(mock_target['entity'], enrollment_id), + body=json.dumps(generate_enrollment_show()), + status=request.param, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.fixture() + def serviceclient_attestation(self, mocked_response, fixture_gdcs, fixture_sas): + mocked_response.add( + method=responses.GET, + url="https://{}/enrollments/{}".format(mock_target['entity'], enrollment_id), + body=json.dumps(generate_enrollment_show(attestation=mock_symmetric_key_attestation)), + status=200, + content_type="application/json", + match_querystring=False, + ) + + mocked_response.add( + method=responses.POST, + url="https://{}/enrollments/{}/attestationmechanism".format(mock_target['entity'], enrollment_id), + body=json.dumps(mock_symmetric_key_attestation), + status=200, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response def test_enrollment_show(self, serviceclient): result = subject.iot_dps_device_enrollment_get(None, enrollment_id, mock_target['entity'], resource_group) - assert result.registration_id == enrollment_id - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method + assert result['registrationId'] == enrollment_id + + request = serviceclient.calls[0].request + url = request.url + method = request.method + + assert "{}/enrollments/{}?".format(mock_target['entity'], enrollment_id) in url + assert method == 'GET' + + def test_enrollment_show_with_keys(self, serviceclient_attestation): + result = subject.iot_dps_device_enrollment_get(None, enrollment_id, + mock_target['entity'], resource_group, show_keys=True) + + assert result['registrationId'] == enrollment_id + assert result['attestation'] + + request = serviceclient_attestation.calls[0].request + url = request.url + method = request.method + assert "{}/enrollments/{}?".format(mock_target['entity'], enrollment_id) in url assert method == 'GET' + request = serviceclient_attestation.calls[1].request + url = request.url + method = request.method + + assert "{}/enrollments/{}/attestationmechanism?".format(mock_target['entity'], enrollment_id) in url + assert method == 'POST' + def test_enrollment_show_error(self, serviceclient_generic_error): with pytest.raises(CLIError): subject.iot_dps_device_enrollment_get(None, enrollment_id, @@ -451,18 +574,24 @@ def test_enrollment_show_error(self, serviceclient_generic_error): class TestEnrollmentList(): @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, [generate_enrollment_show()]) - return service_client + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + mocked_response.add( + method=responses.POST, + url="https://{}/enrollments/query?".format(mock_target['entity']), + body=json.dumps([generate_enrollment_show()]), + status=200, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response @pytest.mark.parametrize("top", [3, None]) def test_enrollment_list(self, serviceclient, top): result = subject.iot_dps_device_enrollment_list(None, mock_target['entity'], resource_group, top) - args = serviceclient.call_args_list[0] - headers = args[0][1] - url = args[0][0].url - method = args[0][0].method + request = serviceclient.calls[0].request + headers = request.headers + url = request.url + method = request.method assert str(headers.get("x-ms-max-item-count")) == str(top) assert "{}/enrollments/query?".format(mock_target['entity']) in url @@ -476,22 +605,30 @@ def test_enrollment_list_error(self, serviceclient_generic_error): class TestEnrollmentDelete(): @pytest.fixture(params=[204]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - response = mocker.MagicMock(name='response') - del response._attribute_map - response.status_code = request.param - service_client.return_value = response - return service_client - - def test_enrollment_delete(self, serviceclient): - subject.iot_dps_device_enrollment_delete(None, enrollment_id, - mock_target['entity'], resource_group) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + mocked_response.add( + method=responses.DELETE, + url="https://{}/enrollments/{}".format(mock_target['entity'], enrollment_id), + body='{}', + status=request.param, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response + + @pytest.mark.parametrize( + "etag", + [None, etag] + ) + def test_enrollment_delete(self, serviceclient, etag): + subject.iot_dps_device_enrollment_delete(serviceclient, enrollment_id, + mock_target['entity'], resource_group, etag=etag) + request = serviceclient.calls[0].request + url = request.url + method = request.method assert "{}/enrollments/{}?".format(mock_target['entity'], enrollment_id) in url assert method == 'DELETE' + assert request.headers["If-Match"] == etag if etag else "*" def test_enrollment_delete_error(self, serviceclient_generic_error): with pytest.raises(CLIError): @@ -512,7 +649,9 @@ def generate_enrollment_group_create_req(iot_hub_host_name=None, reprovision_policy=None, allocation_policy=None, iot_hubs=None, - edge_enabled=False): + edge_enabled=False, + webhook_url=None, + api_version=None): return {'client': None, 'enrollment_id': enrollment_id, 'rg': resource_group, @@ -530,15 +669,23 @@ def generate_enrollment_group_create_req(iot_hub_host_name=None, 'reprovision_policy': reprovision_policy, 'allocation_policy': allocation_policy, 'iot_hubs': iot_hubs, - 'edge_enabled': edge_enabled} + 'edge_enabled': edge_enabled, + 'webhook_url': webhook_url, + 'api_version': api_version} class TestEnrollmentGroupCreate(): @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {}) - return service_client + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + mocked_response.add( + method=responses.PUT, + url="https://{}/enrollmentGroups/{}".format(mock_target['entity'], enrollment_id), + body='{}', + status=200, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response @pytest.mark.parametrize("req", [ (generate_enrollment_group_create_req(primary_key='primarykey', @@ -567,9 +714,21 @@ def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): iot_hubs='hub1 hub2')), (generate_enrollment_group_create_req(certificate_path='myCert', allocation_policy='geolatency')), + (generate_enrollment_group_create_req(certificate_path='myCert', + allocation_policy='custom', + webhook_url="https://www.test.test", + api_version="2019-03-31")), (generate_enrollment_group_create_req(primary_key='primarykey', secondary_key='secondarykey', edge_enabled=True)), + (generate_enrollment_group_create_req(primary_key='primarykey', + secondary_key='secondarykey', + edge_enabled=True, + initial_twin_properties={'key': ['value1', 'value2']})), + (generate_enrollment_group_create_req(primary_key='primarykey', + secondary_key='secondarykey', + edge_enabled=True, + initial_twin_properties={'key': ['value1', 'value2']})), ]) def test_enrollment_group_create(self, serviceclient, req): subject.iot_dps_device_enrollment_group_create(None, @@ -589,13 +748,15 @@ def test_enrollment_group_create(self, serviceclient, req): req['reprovision_policy'], req['allocation_policy'], req['iot_hubs'], - edge_enabled=req['edge_enabled']) - args = serviceclient.call_args - url = args[0][0].url + req['edge_enabled'], + req['webhook_url'], + req['api_version']) + request = serviceclient.calls[0].request + url = request.url assert "{}/enrollmentGroups/{}?".format(mock_target['entity'], enrollment_id) in url - assert args[0][0].method == 'PUT' + assert request.method == 'PUT' - body = args[0][2] + body = json.loads(request.body) assert body['enrollmentGroupId'] == req['enrollment_id'] if req['certificate_path']: assert body['attestation']['type'] == 'x509' @@ -639,6 +800,9 @@ def test_enrollment_group_create(self, serviceclient, req): assert not body['reprovisionPolicy']['updateHubAssignment'] if req['allocation_policy']: assert body['allocationPolicy'] == req['allocation_policy'] + if body['allocationPolicy'] == 'custom': + assert body['customAllocationDefinition']['webhookUrl'] == req['webhook_url'] + assert body['customAllocationDefinition']['apiVersion'] == req['api_version'] if req['iot_hubs']: assert body['iotHubs'] == req['iot_hubs'].split() if req['edge_enabled']: @@ -654,11 +818,13 @@ def test_enrollment_group_create(self, serviceclient, req): secondary_certificate_path='myCert2')), (generate_enrollment_group_create_req(reprovision_policy='invalid')), (generate_enrollment_group_create_req(allocation_policy='invalid')), + (generate_enrollment_group_create_req(allocation_policy='custom')), + (generate_enrollment_group_create_req(allocation_policy='custom', webhook_url="https://www.test.test")), (generate_enrollment_group_create_req(allocation_policy='static', iot_hub_host_name='hub')), (generate_enrollment_group_create_req(allocation_policy='static', iot_hubs='hub1 hub2')), (generate_enrollment_group_create_req(iot_hubs='hub1 hub2')) ]) - def test_enrollment_group_create_invalid_args(self, serviceclient, req): + def test_enrollment_group_create_invalid_args(self, req): with pytest.raises(CLIError): subject.iot_dps_device_enrollment_group_create(None, req['enrollment_id'], @@ -676,7 +842,8 @@ def test_enrollment_group_create_invalid_args(self, serviceclient, req): req['provisioning_status'], req['reprovision_policy'], req['allocation_policy'], - req['iot_hubs']) + req['iot_hubs'], + webhook_url=req['webhook_url']) @pytest.mark.parametrize("req", [ (generate_enrollment_group_create_req(certificate_path='myCert')) @@ -735,7 +902,9 @@ def generate_enrollment_group_update_req(iot_hub_host_name=None, reprovision_policy=None, allocation_policy=None, iot_hubs=None, - edge_enabled=None): + edge_enabled=None, + webhook_url=None, + api_version=None): return {'client': None, 'enrollment_id': enrollment_id, 'rg': resource_group, @@ -756,19 +925,33 @@ def generate_enrollment_group_update_req(iot_hub_host_name=None, 'reprovision_policy': reprovision_policy, 'allocation_policy': allocation_policy, 'iot_hubs': iot_hubs, - 'edge_enabled': edge_enabled} + 'edge_enabled': edge_enabled, + 'webhook_url': webhook_url, + 'api_version': api_version} class TestEnrollmentGroupUpdate(): @pytest.fixture(params=[(200, generate_enrollment_group_show(), 200)]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - test_side_effect = [ - build_mock_response(mocker, request.param[0], request.param[1]), - build_mock_response(mocker, request.param[2]) - ] - service_client.side_effect = test_side_effect - return service_client + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + # Initial GET + mocked_response.add( + method=responses.GET, + url="https://{}/enrollmentGroups/{}".format(mock_target['entity'], enrollment_id), + body=json.dumps(generate_enrollment_group_show()), + status=200, + content_type="application/json", + match_querystring=False, + ) + # Update PUT + mocked_response.add( + method=responses.PUT, + url="https://{}/enrollmentGroups/{}".format(mock_target['entity'], enrollment_id), + body=json.dumps(generate_enrollment_group_show()), + status=200, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response @pytest.mark.parametrize("req", [ (generate_enrollment_group_update_req(etag=etag, secondary_certificate_path='someOtherCertPath')), @@ -784,6 +967,9 @@ def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): (generate_enrollment_group_update_req(reprovision_policy='reprovisionandresetdata')), (generate_enrollment_group_update_req(reprovision_policy='never')), (generate_enrollment_group_update_req(allocation_policy='static', iot_hubs='hub1')), + (generate_enrollment_group_update_req(allocation_policy='custom', + webhook_url="https://www.test.test", + api_version="2019-03-31")), (generate_enrollment_group_update_req(allocation_policy='hashed', iot_hubs='hub1 hub2')), (generate_enrollment_group_update_req(allocation_policy='geolatency')), (generate_enrollment_group_update_req(iot_hub_host_name='hub1')), @@ -811,15 +997,23 @@ def test_enrollment_group_update(self, serviceclient, req): req['reprovision_policy'], req['allocation_policy'], req['iot_hubs'], - edge_enabled=req['edge_enabled']) - # Index 1 is the update args - args = serviceclient.call_args_list[1] - url = args[0][0].url + req['edge_enabled'], + req['webhook_url'], + req['api_version']) + # test initial GET + request = serviceclient.calls[0].request + url = request.url + assert "{}/enrollmentGroups/{}?".format(mock_target['entity'], enrollment_id) in url + assert request.method == 'GET' + + request = serviceclient.calls[1].request + url = request.url assert "{}/enrollmentGroups/{}?".format(mock_target['entity'], enrollment_id) in url - assert args[0][0].method == 'PUT' + assert request.method == 'PUT' + assert request.headers["If-Match"] == req['etag'] if req['etag'] else "*" - body = args[0][2] + body = json.loads(request.body) if not req['certificate_path']: if not req['root_ca_name'] and not req['secondary_root_ca_name']: assert body['attestation']['x509']['signingCertificates']['primary']['info'] is not None @@ -854,6 +1048,9 @@ def test_enrollment_group_update(self, serviceclient, req): assert not body['reprovisionPolicy']['updateHubAssignment'] if req['allocation_policy']: assert body['allocationPolicy'] == req['allocation_policy'] + if body['allocationPolicy'] == 'custom': + assert body['customAllocationDefinition']['webhookUrl'] == req['webhook_url'] + assert body['customAllocationDefinition']['apiVersion'] == req['api_version'] if req['iot_hubs']: assert body['iotHubs'] == req['iot_hubs'].split() if req['edge_enabled'] is not None: @@ -872,11 +1069,13 @@ def test_enrollment_group_update(self, serviceclient, req): (generate_enrollment_group_update_req(remove_certificate='true')), (generate_enrollment_group_update_req(reprovision_policy='invalid')), (generate_enrollment_group_update_req(allocation_policy='invalid')), + (generate_enrollment_group_update_req(allocation_policy='custom')), + (generate_enrollment_group_update_req(allocation_policy='custom', webhook_url="https://www.test.test")), (generate_enrollment_group_update_req(allocation_policy='static', iot_hub_host_name='hub')), (generate_enrollment_group_update_req(allocation_policy='static', iot_hubs='hub1 hub2')), (generate_enrollment_group_update_req(iot_hubs='hub1 hub2')) ]) - def test_enrollment_group_update_invalid_args(self, serviceclient, req): + def test_enrollment_group_update_invalid_args(self, req): with pytest.raises(CLIError): subject.iot_dps_device_enrollment_group_update(None, req['enrollment_id'], @@ -897,26 +1096,77 @@ def test_enrollment_group_update_invalid_args(self, serviceclient, req): req['provisioning_status'], req['reprovision_policy'], req['allocation_policy'], - req['iot_hubs']) + req['iot_hubs'], + None, + req['webhook_url']) class TestEnrollmentGroupShow(): @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, generate_enrollment_group_show()) - return service_client + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + mocked_response.add( + method=responses.GET, + url="https://{}/enrollmentGroups/{}".format(mock_target['entity'], enrollment_id), + body=json.dumps(generate_enrollment_group_show()), + status=200, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response + + @pytest.fixture() + def serviceclient_attestation(self, mocked_response, fixture_gdcs, fixture_sas): + mocked_response.add( + method=responses.GET, + url="https://{}/enrollmentGroups/{}".format(mock_target['entity'], enrollment_id), + body=json.dumps(generate_enrollment_group_show(attestation=mock_symmetric_key_attestation)), + status=200, + content_type="application/json", + match_querystring=False, + ) + + mocked_response.add( + method=responses.POST, + url="https://{}/enrollmentGroups/{}/attestationmechanism".format(mock_target['entity'], enrollment_id), + body=json.dumps(mock_symmetric_key_attestation), + status=200, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response def test_enrollment_group_show(self, serviceclient): result = subject.iot_dps_device_enrollment_group_get(None, enrollment_id, mock_target['entity'], resource_group) - assert result.enrollment_group_id == enrollment_id - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method + assert result['enrollmentGroupId'] == enrollment_id + assert result['attestation'] + request = serviceclient.calls[0].request + url = request.url + method = request.method + assert "{}/enrollmentGroups/{}?".format(mock_target['entity'], enrollment_id) in url assert method == 'GET' + def test_enrollment_group_show_with_keys(self, serviceclient_attestation): + result = subject.iot_dps_device_enrollment_group_get(None, enrollment_id, + mock_target['entity'], resource_group, show_keys=True) + assert result['enrollmentGroupId'] == enrollment_id + assert result['attestation'] + + request = serviceclient_attestation.calls[0].request + url = request.url + method = request.method + + assert "{}/enrollmentGroups/{}?".format(mock_target['entity'], enrollment_id) in url + assert method == 'GET' + + request = serviceclient_attestation.calls[1].request + url = request.url + method = request.method + + assert "{}/enrollmentGroups/{}/attestationmechanism?".format(mock_target['entity'], enrollment_id) in url + assert method == 'POST' + def test_enrollment_group_show_error(self, serviceclient_generic_error): with pytest.raises(CLIError): subject.iot_dps_device_enrollment_group_get(None, enrollment_id, @@ -925,25 +1175,31 @@ def test_enrollment_group_show_error(self, serviceclient_generic_error): class TestEnrollmentGroupList(): @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, [generate_enrollment_group_show()]) - return service_client + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + mocked_response.add( + method=responses.POST, + url="https://{}/enrollmentGroups/query?".format(mock_target['entity']), + body=json.dumps([generate_enrollment_group_show()]), + status=200, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response @pytest.mark.parametrize("top", [5, None]) def test_enrollment_group_list(self, serviceclient, top): result = subject.iot_dps_device_enrollment_group_list(None, mock_target['entity'], resource_group, top) - args = serviceclient.call_args_list[0] - headers = args[0][1] - url = args[0][0].url - method = args[0][0].method + request = serviceclient.calls[0].request + headers = request.headers + url = request.url + method = request.method assert "{}/enrollmentGroups/query?".format(mock_target['entity']) in url assert method == 'POST' assert json.dumps(result) assert str(headers.get("x-ms-max-item-count")) == str(top) - def test_enrollment_group_list_error(self, serviceclient_generic_error): + def test_enrollment_group_list_error(self): with pytest.raises(CLIError): subject.iot_dps_device_enrollment_group_list(None, mock_target['entity'], @@ -952,21 +1208,32 @@ def test_enrollment_group_list_error(self, serviceclient_generic_error): class TestEnrollmentGroupDelete(): @pytest.fixture(params=[204]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {}) - return service_client - - def test_enrollment_group_delete(self, serviceclient): + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + mocked_response.add( + method=responses.DELETE, + url="https://{}/enrollmentGroups/{}".format(mock_target['entity'], enrollment_id), + body='{}', + status=request.param, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response + + @pytest.mark.parametrize( + "etag", + [None, etag] + ) + def test_enrollment_group_delete(self, serviceclient, etag): subject.iot_dps_device_enrollment_group_delete(None, enrollment_id, - mock_target['entity'], resource_group) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method + mock_target['entity'], resource_group, etag=etag) + request = serviceclient.calls[0].request + url = request.url + method = request.method assert "{}/enrollmentGroups/{}?".format(mock_target['entity'], enrollment_id) in url assert method == 'DELETE' + assert request.headers["If-Match"] == etag if etag else "*" - def test_enrollment_group_delete_error(self, serviceclient_generic_error): + def test_enrollment_group_delete_error(self): with pytest.raises(CLIError): subject.iot_dps_device_enrollment_group_delete(None, enrollment_id, mock_target['entity'], resource_group) @@ -980,22 +1247,28 @@ def generate_registration_state_show(): class TestRegistrationShow(): @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, generate_registration_state_show()) - return service_client + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + mocked_response.add( + method=responses.GET, + url="https://{}/registrations/{}?".format(mock_target['entity'], registration_id), + body=json.dumps(generate_registration_state_show()), + status=request.param, + content_type="application/json", + match_querystring=False + ) + yield mocked_response def test_registration_show(self, serviceclient): result = subject.iot_dps_registration_get(None, mock_target['entity'], resource_group, registration_id) - assert result.registration_id == registration_id - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method + assert result['registrationId'] == registration_id + request = serviceclient.calls[0].request + url = request.url + method = request.method assert "{}/registrations/{}?".format(mock_target['entity'], registration_id) in url assert method == 'GET' - def test_registration_show_error(self, serviceclient_generic_error): + def test_registration_show_error(self): with pytest.raises(CLIError): subject.iot_dps_registration_get(None, registration_id, mock_target['entity'], resource_group) @@ -1003,23 +1276,27 @@ def test_registration_show_error(self, serviceclient_generic_error): class TestRegistrationList(): @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - result = [] - result.append(generate_registration_state_show()) - service_client.return_value = build_mock_response(mocker, request.param, result) - return service_client + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + mocked_response.add( + method=responses.POST, + url="https://{}/registrations/{}/query?".format(mock_target['entity'], enrollment_id), + body=json.dumps([generate_registration_state_show()]), + status=request.param, + content_type="application/json", + match_querystring=False + ) + yield mocked_response def test_registration_list(self, serviceclient): subject.iot_dps_registration_list(None, mock_target['entity'], resource_group, enrollment_id) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method + request = serviceclient.calls[0].request + url = request.url + method = request.method assert "{}/registrations/{}/query?".format(mock_target['entity'], enrollment_id) in url assert method == 'POST' - def test_registration_list_error(self, serviceclient_generic_error): + def test_registration_list_error(self): with pytest.raises(CLIError): subject.iot_dps_registration_list(None, mock_target['entity'], resource_group, enrollment_id) @@ -1027,21 +1304,32 @@ def test_registration_list_error(self, serviceclient_generic_error): class TestRegistrationDelete(): @pytest.fixture(params=[204]) - def serviceclient(self, mocker, fixture_gdcs, fixture_sas, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {}) - return service_client - - def test_registration_delete(self, serviceclient): + def serviceclient(self, mocked_response, fixture_gdcs, fixture_sas, request): + mocked_response.add( + method=responses.DELETE, + url="https://{}/registrations/{}".format(mock_target['entity'], registration_id), + body='{}', + status=request.param, + content_type="application/json", + match_querystring=False + ) + yield mocked_response + + @pytest.mark.parametrize( + "etag", + [None, etag] + ) + def test_registration_delete(self, serviceclient, etag): subject.iot_dps_registration_delete(None, mock_target['entity'], - resource_group, registration_id) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method + resource_group, registration_id, etag=etag) + request = serviceclient.calls[0].request + url = request.url + method = request.method assert "{}/registrations/{}?".format(mock_target['entity'], registration_id) in url assert method == 'DELETE' + assert request.headers["If-Match"] == etag if etag else "*" - def test_registration_delete_error(self, serviceclient_generic_error): + def test_registration_delete_error(self): with pytest.raises(CLIError): subject.iot_dps_registration_delete(None, registration_id, mock_target['entity'], resource_group) diff --git a/azext_iot/tests/helpers.py b/azext_iot/tests/helpers.py new file mode 100644 index 000000000..e500c8bd6 --- /dev/null +++ b/azext_iot/tests/helpers.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +import os + +from inspect import getsourcefile +from azure.iot.device import ProvisioningDeviceClient, IoTHubDeviceClient + +from azext_iot.common.utility import read_file_content + +GLOBAL_PROVISIONING_HOST = "global.azure-devices-provisioning.net" + + +def load_json(filename): + os.chdir(os.path.dirname(os.path.abspath(getsourcefile(lambda: 0)))) + return json.loads(read_file_content(filename)) + + +def dps_connect_device(device_id: str, credentials: dict) -> IoTHubDeviceClient: + id_scope = credentials["idScope"] + key = credentials["symmetricKey"]["primaryKey"] + + provisioning_device_client = ProvisioningDeviceClient.create_from_symmetric_key( + provisioning_host=GLOBAL_PROVISIONING_HOST, + registration_id=device_id, + id_scope=id_scope, + symmetric_key=key, + ) + + registration_result = provisioning_device_client.register() + if registration_result.status == "assigned": + device_client = IoTHubDeviceClient.create_from_symmetric_key( + symmetric_key=key, + hostname=registration_result.registration_state.assigned_hub, + device_id=registration_result.registration_state.device_id, + ) + device_client.connect() + return device_client + + +class MockLogger: + def info(self, msg): + print(msg) + + def warn(self, msg): + print(msg) + + def error(self, msg): + print(msg) diff --git a/azext_iot/tests/iothub/configurations/test_edge_deployment_v11.json b/azext_iot/tests/iothub/configurations/test_edge_deployment_v11.json new file mode 100644 index 000000000..8b7df56b4 --- /dev/null +++ b/azext_iot/tests/iothub/configurations/test_edge_deployment_v11.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.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_int.py b/azext_iot/tests/iothub/configurations/test_iot_config_int.py index 7085c0300..8c4997e96 100644 --- a/azext_iot/tests/iothub/configurations/test_iot_config_int.py +++ b/azext_iot/tests/iothub/configurations/test_iot_config_int.py @@ -7,20 +7,20 @@ import random import json -from ... import IoTLiveScenarioTest -from ...conftest import get_context_path -from ...settings import DynamoSettings, ENV_SET_TEST_IOTHUB_BASIC +from azext_iot.tests import IoTLiveScenarioTest +from azext_iot.tests.conftest import get_context_path +from azext_iot.tests.settings import DynamoSettings, ENV_SET_TEST_IOTHUB_BASIC from azext_iot.common.utility import read_file_content settings = DynamoSettings(ENV_SET_TEST_IOTHUB_BASIC) LIVE_HUB = settings.env.azext_iot_testhub LIVE_RG = settings.env.azext_iot_testrg -LIVE_HUB_CS = settings.env.azext_iot_testhub_cs edge_content_path = get_context_path(__file__, "test_edge_deployment.json") edge_content_layered_path = get_context_path( __file__, "test_edge_deployment_layered.json" ) +edge_content_v11_path = get_context_path(__file__, "test_edge_deployment_v11.json") edge_content_v1_path = get_context_path(__file__, "test_edge_deployment_v1.json") edge_content_malformed_path = get_context_path( __file__, "test_edge_deployment_malformed.json" @@ -33,7 +33,7 @@ class TestIoTEdgeSetModules(IoTLiveScenarioTest): def __init__(self, test_case): super(TestIoTEdgeSetModules, self).__init__( - test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS + test_case, LIVE_HUB, LIVE_RG ) def test_edge_set_modules(self): @@ -67,7 +67,7 @@ def test_edge_set_modules(self): # Using connection string - content from file self.cmd( "iot edge set-modules -d {} --login {} -k '{}'".format( - edge_device_ids[0], LIVE_HUB_CS, edge_content_v1_path + edge_device_ids[0], self.connection_string, edge_content_v1_path ), checks=[self.check("length([*])", 4)], ) @@ -84,11 +84,11 @@ def test_edge_set_modules(self): class TestIoTEdgeDeployments(IoTLiveScenarioTest): def __init__(self, test_case): super(TestIoTEdgeDeployments, self).__init__( - test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS + test_case, LIVE_HUB, LIVE_RG ) def test_edge_deployments(self): - config_count = 4 + config_count = 5 config_ids = self.generate_config_names(config_count) self.kwargs["generic_metrics"] = read_file_content(generic_metrics_path) @@ -138,7 +138,7 @@ def test_edge_deployments(self): self.cmd( "iot edge deployment create -d {} --login {} --pri {} --tc \"{}\" --lab '{}' -k '{}' --metrics '{}'".format( config_ids[1].upper(), - LIVE_HUB_CS, + self.connection_string, priority, condition, "{labels}", @@ -168,7 +168,7 @@ def test_edge_deployments(self): self.cmd( "iot edge deployment create -d {} --login {} -k '{}' --metrics '{}' --layered".format( config_ids[2].upper(), - LIVE_HUB_CS, + self.connection_string, edge_content_layered_path, generic_metrics_path, ), @@ -251,6 +251,31 @@ def test_edge_deployments(self): expect_failure=True, ) + # Uses IoT Edge hub schema version 1.1 + self.cmd( + """iot edge deployment create --deployment-id {} --hub-name {} --resource-group {} --priority {} + --target-condition \"{}\" --labels '{}' --content '{}'""".format( + config_ids[4], + LIVE_HUB, + LIVE_RG, + priority, + condition, + "{labels}", + edge_content_v11_path, + ), + checks=[ + self.check("id", config_ids[4]), + self.check("priority", priority), + self.check("targetCondition", condition), + self.check("labels", json.loads(self.kwargs["labels"])), + self.check( + "content.modulesContent", + json.loads(read_file_content(edge_content_v11_path))["modulesContent"], + ), + self.check("metrics.queries", {}), + ], + ) + # Show deployment self.cmd( "iot edge deployment show --deployment-id {} --hub-name {} --resource-group {}".format( @@ -267,7 +292,7 @@ def test_edge_deployments(self): # Show deployment - using connection string self.cmd( "iot edge deployment show -d {} --login {}".format( - config_ids[1], LIVE_HUB_CS + config_ids[1], self.connection_string ), checks=[ self.check("id", config_ids[1]), @@ -324,7 +349,7 @@ def test_edge_deployments(self): system_metric_name = "appliedCount" config_output = self.cmd( "iot edge deployment show --login {} --deployment-id {}".format( - LIVE_HUB_CS, config_ids[1] + self.connection_string, config_ids[1] ) ).get_output_in_json() @@ -344,7 +369,7 @@ def test_edge_deployments(self): # System metric - using connection string self.cmd( "iot edge deployment show-metric --metric-id {} --login '{}' --deployment-id {} --metric-type {}".format( - system_metric_name, LIVE_HUB_CS, config_ids[1], "system" + system_metric_name, self.connection_string, config_ids[1], "system" ), checks=[ self.check("metric", system_metric_name), @@ -358,13 +383,13 @@ def test_edge_deployments(self): # Error - metric does not exist, using connection string self.cmd( "iot edge deployment show-metric -m {} --login {} -d {}".format( - "doesnotexist", LIVE_HUB_CS, config_ids[0] + "doesnotexist", self.connection_string, config_ids[0] ), expect_failure=True, ) config_list_check = [ - self.check("length([*])", 4), + self.check("length([*])", config_count), self.exists("[?id=='{}']".format(config_ids[0])), self.exists("[?id=='{}']".format(config_ids[1])), self.exists("[?id=='{}']".format(config_ids[2])), @@ -379,22 +404,10 @@ def test_edge_deployments(self): # List all edge deployments - using connection string self.cmd( - "iot edge deployment list --login {}".format(LIVE_HUB_CS), + "iot edge deployment list --login {}".format(self.connection_string), checks=config_list_check, ) - # Error top of -1 does not work with configurations - self.cmd( - "iot edge deployment list -n {} -g {} --top -1".format(LIVE_HUB, LIVE_RG), - expect_failure=True, - ) - - # Error max top of 100 with configurations - self.cmd( - "iot edge deployment list -n {} -g {} --top 101".format(LIVE_HUB, LIVE_RG), - expect_failure=True, - ) - # Explicitly delete an edge deployment self.cmd( "iot edge deployment delete -d {} -n {} -g {}".format( @@ -406,7 +419,7 @@ def test_edge_deployments(self): # Explicitly delete an edge deployment - using connection string self.cmd( "iot edge deployment delete -d {} --login {}".format( - config_ids[1], LIVE_HUB_CS + config_ids[1], self.connection_string ) ) del self.config_ids[0] @@ -415,7 +428,7 @@ def test_edge_deployments(self): class TestIoTHubConfigurations(IoTLiveScenarioTest): def __init__(self, test_case): super(TestIoTHubConfigurations, self).__init__( - test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS + test_case, LIVE_HUB, LIVE_RG ) def test_device_configurations(self): @@ -466,7 +479,7 @@ def test_device_configurations(self): self.cmd( "iot hub configuration create -c {} --login {} --pri {} --tc \"{}\" --lab '{}' -k '{}' --metrics '{}'".format( config_ids[1].upper(), - LIVE_HUB_CS, + self.connection_string, priority, module_condition, "{labels}", @@ -496,7 +509,7 @@ def test_device_configurations(self): self.cmd( "iot hub configuration create -c {} --login {} -k '{}' --metrics '{}'".format( config_ids[2].upper(), - LIVE_HUB_CS, + self.connection_string, adm_content_device_path, generic_metrics_path, ), @@ -539,7 +552,7 @@ def test_device_configurations(self): self.cmd( "iot hub configuration create -c {} --login {} -k '{}'".format( config_ids[1].upper(), - LIVE_HUB_CS, + self.connection_string, adm_content_module_path, ), expect_failure=True, @@ -561,7 +574,7 @@ def test_device_configurations(self): # Show ADM configuration - using connection string self.cmd( "iot hub configuration show -c {} --login {}".format( - config_ids[1], LIVE_HUB_CS + config_ids[1], self.connection_string ), checks=[ self.check("id", config_ids[1]), @@ -618,7 +631,7 @@ def test_device_configurations(self): system_metric_name = "appliedCount" config_output = self.cmd( "iot hub configuration show --login {} --config-id {}".format( - LIVE_HUB_CS, config_ids[1] + self.connection_string, config_ids[1] ) ).get_output_in_json() @@ -638,7 +651,7 @@ def test_device_configurations(self): # System metric - using connection string self.cmd( "iot hub configuration show-metric --metric-id {} --login '{}' --config-id {} --metric-type {}".format( - system_metric_name, LIVE_HUB_CS, config_ids[1], "system" + system_metric_name, self.connection_string, config_ids[1], "system" ), checks=[ self.check("metric", system_metric_name), @@ -652,7 +665,7 @@ def test_device_configurations(self): # Error - metric does not exist, using connection string self.cmd( "iot hub configuration show-metric -m {} --login {} -c {}".format( - "doesnotexist", LIVE_HUB_CS, config_ids[0] + "doesnotexist", self.connection_string, config_ids[0] ), expect_failure=True, ) @@ -668,7 +681,7 @@ def test_device_configurations(self): ) config_list_check = [ - self.check("length([*])", 3), + self.check("length([*])", config_count), self.exists("[?id=='{}']".format(config_ids[0])), self.exists("[?id=='{}']".format(config_ids[1])), self.exists("[?id=='{}']".format(config_ids[2])) @@ -682,22 +695,10 @@ def test_device_configurations(self): # List all ADM configurations - using connection string self.cmd( - "iot hub configuration list --login {}".format(LIVE_HUB_CS), + "iot hub configuration list --login {}".format(self.connection_string), checks=config_list_check, ) - # Error top of -1 does not work with configurations - self.cmd( - "iot hub configuration list -n {} -g {} --top -1".format(LIVE_HUB, LIVE_RG), - expect_failure=True, - ) - - # Error max top of 100 with configurations - self.cmd( - "iot hub configuration list -n {} -g {} --top 101".format(LIVE_HUB, LIVE_RG), - expect_failure=True, - ) - # Explicitly delete an ADM configuration self.cmd( "iot hub configuration delete -c {} -n {} -g {}".format( @@ -709,7 +710,7 @@ def test_device_configurations(self): # Explicitly delete an ADM configuration - using connection string self.cmd( "iot hub configuration delete -c {} --login {}".format( - config_ids[1], LIVE_HUB_CS + config_ids[1], self.connection_string ) ) del self.config_ids[0] 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 0264715cd..cb54909c2 100644 --- a/azext_iot/tests/iothub/configurations/test_iot_config_unit.py +++ b/azext_iot/tests/iothub/configurations/test_iot_config_unit.py @@ -5,6 +5,7 @@ # -------------------------------------------------------------------------------------------- +from azext_iot.tests.generators import generate_generic_id import pytest import responses import json @@ -13,7 +14,7 @@ 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 ...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("-", "")) @@ -32,11 +33,12 @@ def sample_config_edge_malformed(set_cwd): return result -@pytest.fixture(params=["file", "inlineA", "inlineB", "layered", "v1"]) +@pytest.fixture(params=["file", "inlineA", "inlineB", "layered", "v1", "v11"]) 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" payload = None if request.param == "inlineA": @@ -49,6 +51,8 @@ def sample_config_edge(set_cwd, request): payload = json.dumps(json.loads(read_file_content(layered_path))) elif request.param == "v1": payload = json.dumps(json.loads(read_file_content(v1_path))) + elif request.param == "v11": + payload = json.dumps(json.loads(read_file_content(v11_path))) return (request.param, payload) @@ -126,7 +130,7 @@ def service_client( ) def test_config_metric_show( self, - fixture_cmd2, + fixture_cmd, service_client, metric_id, content_type, @@ -141,7 +145,7 @@ def test_config_metric_show( else partial(subject.iot_hub_configuration_metric_show) ) result = target_method( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, metric_type=metric_type, metric_id=metric_id, @@ -174,7 +178,7 @@ def test_config_metric_show( ], ) def test_config_metric_show_invalid_args( - self, fixture_cmd2, service_client, metric_id, content_type, metric_type + self, fixture_cmd, service_client, metric_id, content_type, metric_type ): from functools import partial service_client.assert_all_requests_are_fired = False @@ -187,7 +191,7 @@ def test_config_metric_show_invalid_args( ) target_method( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, metric_type=metric_type, metric_id=metric_id, @@ -206,9 +210,9 @@ def serviceclient( ) return service_client - def test_config_show(self, serviceclient, fixture_cmd2): + def test_config_show(self, serviceclient, fixture_cmd): result = subject.iot_hub_configuration_show( - fixture_cmd2, config_id=config_id, hub_name=mock_target["entity"] + fixture_cmd, config_id=config_id, hub_name=mock_target["entity"] ) args = serviceclient.call_args @@ -219,10 +223,10 @@ def test_config_show(self, serviceclient, fixture_cmd2): assert method == "GET" assert isinstance(result, dict) - def test_config_show_error(self, serviceclient_generic_error, fixture_cmd2): + def test_config_show_error(self, serviceclient_generic_error, fixture_cmd): with pytest.raises(CLIError): subject.iot_hub_configuration_show( - fixture_cmd2, config_id=config_id, hub_name=mock_target["entity"] + fixture_cmd, config_id=config_id, hub_name=mock_target["entity"] ) @@ -255,7 +259,7 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): ) def test_config_create_edge( self, - fixture_cmd2, + fixture_cmd, serviceclient, sample_config_edge, sample_config_metrics, @@ -266,7 +270,7 @@ def test_config_create_edge( labels, ): subject.iot_edge_deployment_create( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, hub_name=hub_name, content=sample_config_edge[1], @@ -285,12 +289,11 @@ def test_config_create_edge( assert "{}/configurations/{}?".format(hub_name, config_id.lower()) in url assert method == "PUT" assert body["id"] == config_id.lower() - assert body["contentType"] == "assignment" assert body.get("targetCondition") == target_condition assert body.get("priority") == priority assert body.get("labels") == evaluate_literal(labels, dict) - if sample_config_edge[0] == "inlineB": + if sample_config_edge[0] == "inlineB" or sample_config_edge[0] == "v11": assert ( body["content"]["modulesContent"] == json.loads(sample_config_edge[1])["modulesContent"] @@ -329,7 +332,7 @@ def test_config_create_edge( ) def test_config_create_edge_malformed( self, - fixture_cmd2, + fixture_cmd, serviceclient, sample_config_edge_malformed, config_id, @@ -340,7 +343,7 @@ def test_config_create_edge_malformed( ): with pytest.raises(CLIError) as exc: subject.iot_edge_deployment_create( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, hub_name=hub_name, content=sample_config_edge_malformed, @@ -378,7 +381,7 @@ def test_config_create_edge_malformed( ) def test_config_create_adm( self, - fixture_cmd2, + fixture_cmd, serviceclient, sample_config_adm, sample_config_metrics, @@ -400,7 +403,7 @@ def test_config_create_adm( target_condition = "FROM devices.modules WHERE {}".format(target_condition) subject.iot_hub_configuration_create( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, hub_name=hub_name, content=sample_config_adm[1], @@ -418,7 +421,6 @@ def test_config_create_adm( assert "{}/configurations/{}?".format(hub_name, config_id.lower()) in url assert method == "PUT" assert body["id"] == config_id.lower() - assert body["contentType"] == "assignment" assert body.get("targetCondition") == target_condition assert body.get("priority") == priority assert body.get("labels") == evaluate_literal(labels, dict) @@ -470,7 +472,7 @@ def _assert_config_metrics_request(self, sample_config_metrics, body): ) def test_config_create_adm_invalid( self, - fixture_cmd2, + fixture_cmd, serviceclient, config_id, hub_name, @@ -480,7 +482,7 @@ def test_config_create_adm_invalid( ): with pytest.raises(CLIError) as exc1: subject.iot_hub_configuration_create( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, hub_name=hub_name, content=get_context_path(__file__, "test_edge_deployment.json"), @@ -493,7 +495,7 @@ def test_config_create_adm_invalid( content = json.dumps({"deviceContent": {}, "moduleContent": {}}) with pytest.raises(CLIError) as exc2: subject.iot_hub_configuration_create( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, hub_name=hub_name, content=content, @@ -512,7 +514,7 @@ def test_config_create_adm_invalid( content = json.dumps({"moduleContent": {"key": "value"}}) with pytest.raises(CLIError) as exc3: subject.iot_hub_configuration_create( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, hub_name=hub_name, content=content, @@ -540,7 +542,7 @@ def test_config_create_adm_invalid( ) def test_config_create_error( self, - fixture_cmd2, + fixture_cmd, serviceclient_generic_error, sample_config_edge, config_id, @@ -551,7 +553,7 @@ def test_config_create_error( ): with pytest.raises(CLIError): subject.iot_edge_deployment_create( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, hub_name=hub_name, content=sample_config_edge[1], @@ -562,21 +564,21 @@ def test_config_create_error( class TestConfigDelete: - @pytest.fixture(params=[(200, 204)]) + @pytest.fixture(params=[204]) def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - etag = str(uuid4()) - service_client.expected_etag = etag side_effect = [ - build_mock_response(mocker, request.param[0], {"etag": etag}), - build_mock_response(mocker, request.param[1]), + build_mock_response(mocker, request.param), ] service_client.side_effect = side_effect return service_client - def test_config_delete(self, serviceclient, fixture_cmd2): + @pytest.mark.parametrize( + "etag", [generate_generic_id(), None], + ) + def test_config_delete(self, serviceclient, fixture_cmd, etag): subject.iot_hub_configuration_delete( - fixture_cmd2, config_id=config_id, hub_name=mock_target["entity"] + fixture_cmd, config_id=config_id, hub_name=mock_target["entity"], etag=etag ) args = serviceclient.call_args url = args[0][0].url @@ -585,24 +587,12 @@ def test_config_delete(self, serviceclient, fixture_cmd2): assert method == "DELETE" assert "{}/configurations/{}?".format(mock_target["entity"], config_id) in url - assert headers["If-Match"] == '"{}"'.format(serviceclient.expected_etag) + assert headers["If-Match"] == '"{}"'.format(etag if etag else "*") - @pytest.mark.parametrize("expected_error", [CLIError]) - def test_config_delete_invalid_args( - self, - fixture_cmd2, - serviceclient_generic_invalid_or_missing_etag, - expected_error, - ): - with pytest.raises(expected_error): - subject.iot_hub_configuration_delete( - fixture_cmd2, config_id=config_id, hub_name=mock_target["entity"] - ) - - def test_config_delete_error(self, fixture_cmd2, serviceclient_generic_error): + def test_config_delete_error(self, fixture_cmd, serviceclient_generic_error): with pytest.raises(CLIError): subject.iot_hub_configuration_delete( - fixture_cmd2, config_id=config_id, hub_name=mock_target["entity"] + fixture_cmd, config_id=config_id, hub_name=mock_target["entity"] ) @@ -613,12 +603,16 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): service_client.return_value = build_mock_response(mocker, request.param, {}) return service_client - def test_config_update(self, fixture_cmd2, serviceclient, sample_config_show): + @pytest.mark.parametrize( + "etag", [generate_generic_id(), None], + ) + def test_config_update(self, fixture_cmd, serviceclient, sample_config_show, etag): subject.iot_hub_configuration_update( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, hub_name=mock_target["entity"], parameters=sample_config_show, + etag=etag ) args = serviceclient.call_args url = args[0][0].url @@ -629,37 +623,26 @@ def test_config_update(self, fixture_cmd2, serviceclient, sample_config_show): assert "{}/configurations/{}?".format(mock_target["entity"], config_id) in url assert method == "PUT" - assert headers["If-Match"] == '"{}"'.format(sample_config_show["etag"]) - assert body["id"] == sample_config_show["id"] - assert body["contentType"] == "assignment" assert body.get("metrics") == sample_config_show.get("metrics") assert body.get("targetCondition") == sample_config_show.get("targetCondition") assert body.get("priority") == sample_config_show.get("priority") assert body.get("labels") == sample_config_show.get("labels") + headers = args[0][0].headers + assert headers["If-Match"] == '"{}"'.format(etag if etag else "*") + def test_config_update_invalid_args( - self, fixture_cmd2, serviceclient, sample_config_show + self, fixture_cmd, serviceclient, sample_config_show ): from copy import deepcopy - request = deepcopy(sample_config_show) - request["etag"] = None - - with pytest.raises(CLIError): - subject.iot_hub_configuration_update( - cmd=fixture_cmd2, - config_id=config_id, - hub_name=mock_target["entity"], - parameters=request, - ) - request = deepcopy(sample_config_show) request["labels"] = "not a dictionary" with pytest.raises(CLIError) as exc_label: subject.iot_hub_configuration_update( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, hub_name=mock_target["entity"], parameters=request, @@ -670,10 +653,10 @@ def test_config_update_invalid_args( "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_cmd2, serviceclient_generic_error): + def test_config_update_error(self, fixture_cmd, serviceclient_generic_error): with pytest.raises(CLIError): subject.iot_hub_configuration_update( - cmd=fixture_cmd2, + cmd=fixture_cmd, config_id=config_id, hub_name=mock_target["entity"], parameters={}, @@ -681,7 +664,7 @@ def test_config_update_error(self, fixture_cmd2, serviceclient_generic_error): class TestConfigList: - @pytest.fixture(params=[10, 0, 20]) + @pytest.fixture(params=[10, 0, 1000]) def service_client(self, mocked_response, fixture_ghcs, request): result = [] size = request.param @@ -714,10 +697,10 @@ def service_client(self, mocked_response, fixture_ghcs, request): mocked_response.expected_size = size yield mocked_response - @pytest.mark.parametrize("top", [1, 100]) - def test_config_list(self, fixture_cmd2, service_client, top): + @pytest.mark.parametrize("top", [1, 100, 1000, None]) + def test_config_list(self, fixture_cmd, service_client, top): result = subject.iot_hub_configuration_list( - cmd=fixture_cmd2, hub_name=mock_target["entity"], top=top + cmd=fixture_cmd, hub_name=mock_target["entity"], top=top ) assert json.dumps(result) @@ -725,32 +708,32 @@ def test_config_list(self, fixture_cmd2, service_client, top): assert len(result) == top or len(result) == service_client.expected_size * 2 list_request = service_client.calls[0].request - assert "top={}".format(top) in list_request.url + assert "top=" not in list_request.url - @pytest.mark.parametrize("top", [1, 10]) - def test_deployment_list(self, fixture_cmd2, service_client, top): + @pytest.mark.parametrize("top", [1, 100, 1000, None]) + def test_deployment_list(self, fixture_cmd, service_client, top): result = subject.iot_edge_deployment_list( - cmd=fixture_cmd2, hub_name=mock_target["entity"], top=top + cmd=fixture_cmd, hub_name=mock_target["entity"], top=top ) assert json.dumps(result) assert len(result) == top or len(result) == service_client.expected_size list_request = service_client.calls[0].request - assert "top={}".format(top) in list_request.url + assert "top=" not in list_request.url @pytest.mark.parametrize("top", [-1, 0, 101]) - def test_config_list_invalid_args(self, fixture_cmd2, top): + def test_config_list_invalid_args(self, fixture_cmd, top): with pytest.raises(CLIError): subject.iot_hub_configuration_list( - cmd=fixture_cmd2, hub_name=mock_target["entity"], top=top + cmd=fixture_cmd, hub_name=mock_target["entity"], top=top ) - def test_config_list_error(self, fixture_cmd2, service_client_generic_errors): + def test_config_list_error(self, fixture_cmd, service_client_generic_errors): service_client_generic_errors.assert_all_requests_are_fired = False with pytest.raises(CLIError): subject.iot_hub_configuration_list( - cmd=fixture_cmd2, hub_name=mock_target["entity"] + cmd=fixture_cmd, hub_name=mock_target["entity"] ) @@ -768,14 +751,14 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): "device_id, hub_name", [("test-device-01", mock_target["entity"])] ) def test_config_apply_edge( - self, fixture_cmd2, serviceclient, device_id, hub_name, sample_config_edge + self, fixture_cmd, serviceclient, device_id, hub_name, sample_config_edge ): # Not yet supporting layered deployments if sample_config_edge[0] == "layered": return result = subject.iot_edge_set_modules( - cmd=fixture_cmd2, + cmd=fixture_cmd, device_id=device_id, hub_name=mock_target["entity"], content=sample_config_edge[1], @@ -795,7 +778,7 @@ def test_config_apply_edge( in url ) - if sample_config_edge[0] == "inlineB": + if sample_config_edge[0] == "inlineB" or sample_config_edge[0] == "v11": assert ( body["modulesContent"] == json.loads(sample_config_edge[1])["modulesContent"] @@ -832,7 +815,7 @@ def test_config_apply_edge( ) def test_config_apply_edge_malformed( self, - fixture_cmd2, + fixture_cmd, serviceclient, device_id, hub_name, @@ -840,7 +823,7 @@ def test_config_apply_edge_malformed( ): with pytest.raises(CLIError) as exc: subject.iot_edge_set_modules( - cmd=fixture_cmd2, + cmd=fixture_cmd, device_id=device_id, hub_name=mock_target["entity"], content=sample_config_edge_malformed, @@ -858,7 +841,7 @@ def test_config_apply_edge_malformed( ) def test_config_apply_edge_error( self, - fixture_cmd2, + fixture_cmd, serviceclient_generic_error, device_id, hub_name, @@ -866,7 +849,7 @@ def test_config_apply_edge_error( ): with pytest.raises(CLIError): subject.iot_edge_set_modules( - cmd=fixture_cmd2, + cmd=fixture_cmd, device_id=device_id, hub_name=mock_target["entity"], content=sample_config_edge_malformed, diff --git a/azext_iot/tests/iothub/device_identity/test_iot_device_identity_int.py b/azext_iot/tests/iothub/device_identity/test_iot_device_identity_int.py deleted file mode 100644 index de33127d2..000000000 --- a/azext_iot/tests/iothub/device_identity/test_iot_device_identity_int.py +++ /dev/null @@ -1,22 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -from ... import IoTLiveScenarioTest -from ...settings import DynamoSettings, ENV_SET_TEST_IOTHUB_BASIC - -settings = DynamoSettings(ENV_SET_TEST_IOTHUB_BASIC) -LIVE_HUB = settings.env.azext_iot_testhub -LIVE_RG = settings.env.azext_iot_testrg -LIVE_HUB_CS = settings.env.azext_iot_testhub_cs - - -class TestIoTDeviceIdentity(IoTLiveScenarioTest): - def __init__(self, test_case): - super(TestIoTDeviceIdentity, self).__init__( - test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS - ) - - pass diff --git a/azext_iot/tests/iothub/jobs/test_iothub_jobs_int.py b/azext_iot/tests/iothub/jobs/test_iothub_jobs_int.py index e48e30125..902adcfd9 100644 --- a/azext_iot/tests/iothub/jobs/test_iothub_jobs_int.py +++ b/azext_iot/tests/iothub/jobs/test_iothub_jobs_int.py @@ -6,19 +6,18 @@ import json from datetime import datetime, timedelta -from ... import IoTLiveScenarioTest -from ...settings import DynamoSettings, ENV_SET_TEST_IOTHUB_BASIC +from azext_iot.tests import IoTLiveScenarioTest +from azext_iot.tests.settings import DynamoSettings, ENV_SET_TEST_IOTHUB_BASIC settings = DynamoSettings(ENV_SET_TEST_IOTHUB_BASIC) LIVE_HUB = settings.env.azext_iot_testhub LIVE_RG = settings.env.azext_iot_testrg -LIVE_HUB_CS = settings.env.azext_iot_testhub_cs class TestIoTHubJobs(IoTLiveScenarioTest): def __init__(self, test_case): - super(TestIoTHubJobs, self).__init__(test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS) + super(TestIoTHubJobs, self).__init__(test_case, LIVE_HUB, LIVE_RG) job_count = 3 self.job_ids = self.generate_job_names(job_count) @@ -89,7 +88,7 @@ def test_jobs(self): "scheduleUpdateTwin", query_condition, "{twin_patch_props}", - LIVE_HUB_CS, + self.connection_string, ), checks=[ self.check("jobId", self.job_ids[1]), @@ -150,7 +149,7 @@ def test_jobs(self): # With connection string self.cmd( "iot hub job show --job-id {} --login {}".format( - self.job_ids[1], LIVE_HUB_CS + self.job_ids[1], self.connection_string ), checks=[ self.check("jobId", self.job_ids[1]), @@ -168,7 +167,7 @@ def test_jobs(self): # Cancel Job test # Create job to be cancelled - scheduled +7 days from now. - scheduled_time_iso = (datetime.utcnow() + timedelta(days=7)).isoformat() + scheduled_time_iso = (datetime.utcnow() + timedelta(days=6)).isoformat() self.cmd( "iot hub job create --job-id {} --job-type {} -q \"{}\" --twin-patch '{}' --start '{}' -n {} -g {}".format( @@ -183,6 +182,21 @@ def test_jobs(self): checks=[self.check("jobId", self.job_ids[2])], ) + # Allow time for job to transfer to scheduled state (cannot cancel job in running state) + from time import sleep + sleep(5) + + self.cmd( + "iot hub job show --job-id {} -n {} -g {}".format( + self.job_ids[2], LIVE_HUB, LIVE_RG + ), + checks=[ + self.check("jobId", self.job_ids[2]), + self.check("status", "scheduled"), + ], + ) + + # Cancel job self.cmd( "iot hub job cancel --job-id {} -n {} -g {}".format( self.job_ids[2], LIVE_HUB, LIVE_RG @@ -211,7 +225,7 @@ def test_jobs(self): # List Jobs - with connection string job_result_set_cs = self.cmd( - "iot hub job list --login {}".format(LIVE_HUB_CS) + "iot hub job list --login {}".format(self.connection_string) ).get_output_in_json() self.validate_job_list(jobs_set=job_result_set_cs) diff --git a/azext_iot/tests/iothub/jobs/test_iothub_jobs_unit.py b/azext_iot/tests/iothub/jobs/test_iothub_jobs_unit.py index 30a28110d..491e5ec26 100644 --- a/azext_iot/tests/iothub/jobs/test_iothub_jobs_unit.py +++ b/azext_iot/tests/iothub/jobs/test_iothub_jobs_unit.py @@ -11,9 +11,9 @@ from functools import partial from uuid import uuid4 from knack.cli import CLIError -from azext_iot.iothub import job_commands as subject +from azext_iot.iothub import commands_job as subject from azext_iot.common.shared import JobStatusType, JobType -from ...conftest import build_mock_response, path_service_client, mock_target +from azext_iot.tests.conftest import build_mock_response, path_service_client, mock_target def generate_job_id(): @@ -214,7 +214,7 @@ def serviceclient_test_wait(self, mocker, fixture_ghcs, fixture_sas, request): ) def test_job_create( self, - fixture_cmd2, + fixture_cmd, serviceclient, job_id, job_type, @@ -229,7 +229,7 @@ def test_job_create( ): job_create = partial( subject.job_create, - cmd=fixture_cmd2, + cmd=fixture_cmd, job_id=job_id, job_type=job_type, hub_name=hub_name, @@ -264,8 +264,7 @@ def test_job_create( if job_type == JobType.scheduleUpdateTwin.value: assert body["type"] == JobType.scheduleUpdateTwin.value - payload["etag"] = "*" - assert body["updateTwin"] == payload + assert body["updateTwin"] == {'etag': '*'} elif job_type == JobType.scheduleDeviceMethod.value: assert body["type"] == JobType.scheduleDeviceMethod.value assert body["cloudToDeviceMethod"]["methodName"] == method_name @@ -304,7 +303,7 @@ def test_job_create( ) def test_job_create_wait( self, - fixture_cmd2, + fixture_cmd, serviceclient_test_wait, job_id, job_type, @@ -319,7 +318,7 @@ def test_job_create_wait( ): job_create = partial( subject.job_create, - cmd=fixture_cmd2, + cmd=fixture_cmd, job_id=job_id, job_type=job_type, hub_name=hub_name, @@ -453,7 +452,7 @@ def test_job_create_wait( ) def test_job_create_malformed( self, - fixture_cmd2, + fixture_cmd, serviceclient, job_id, job_type, @@ -472,7 +471,7 @@ def test_job_create_malformed( with pytest.raises(CLIError) as exc: job_create = partial( subject.job_create, - cmd=fixture_cmd2, + cmd=fixture_cmd, job_id=job_id, job_type=job_type, hub_name=hub_name, @@ -528,7 +527,7 @@ def test_job_create_malformed( ) def test_job_create_error( self, - fixture_cmd2, + fixture_cmd, serviceclient_generic_error, job_id, job_type, @@ -544,7 +543,7 @@ def test_job_create_error( with pytest.raises(CLIError): job_create = partial( subject.job_create, - cmd=fixture_cmd2, + cmd=fixture_cmd, job_id=job_id, job_type=job_type, hub_name=hub_name, @@ -601,10 +600,10 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): return service_client - def test_job_show(self, fixture_cmd2, serviceclient): + def test_job_show(self, fixture_cmd, serviceclient): target_job_id = generate_job_id() result = subject.job_show( - cmd=fixture_cmd2, job_id=target_job_id, hub_name=mock_target["entity"] + cmd=fixture_cmd, job_id=target_job_id, hub_name=mock_target["entity"] ) args_list = serviceclient.call_args_list @@ -630,12 +629,12 @@ def test_job_show(self, fixture_cmd2, serviceclient): assert result["startTime"] assert result["endTime"] - def test_job_show_error(self, fixture_cmd2, serviceclient_generic_error): + def test_job_show_error(self, fixture_cmd, serviceclient_generic_error): target_job_id = generate_job_id() with pytest.raises(CLIError): subject.job_show( - cmd=fixture_cmd2, job_id=target_job_id, hub_name=mock_target["entity"] + cmd=fixture_cmd, job_id=target_job_id, hub_name=mock_target["entity"] ) @@ -686,10 +685,10 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): return service_client - def test_job_cancel(self, fixture_cmd2, serviceclient): + def test_job_cancel(self, fixture_cmd, serviceclient): target_job_id = generate_job_id() result = subject.job_cancel( - cmd=fixture_cmd2, job_id=target_job_id, hub_name=mock_target["entity"] + cmd=fixture_cmd, job_id=target_job_id, hub_name=mock_target["entity"] ) args_list = serviceclient.call_args_list @@ -727,12 +726,12 @@ def test_job_cancel(self, fixture_cmd2, serviceclient): ) assert args_list[2][0][0].method == "DELETE" - def test_job_cancel_error(self, fixture_cmd2, serviceclient_generic_error): + def test_job_cancel_error(self, fixture_cmd, serviceclient_generic_error): target_job_id = generate_job_id() with pytest.raises(CLIError): subject.job_cancel( - cmd=fixture_cmd2, job_id=target_job_id, hub_name=mock_target["entity"] + cmd=fixture_cmd, job_id=target_job_id, hub_name=mock_target["entity"] ) @@ -870,9 +869,9 @@ def handle_calls(*args, **kwargs): (None, None, 5), ], ) - def test_job_list(self, fixture_cmd2, serviceclient, job_type, job_status, top): + def test_job_list(self, fixture_cmd, serviceclient, job_type, job_status, top): result = subject.job_list( - cmd=fixture_cmd2, + cmd=fixture_cmd, job_type=job_type, job_status=job_status, top=top, @@ -955,6 +954,6 @@ def _in_criteria(job_collection, job_status, job_type): return result - def test_job_list_error(self, fixture_cmd2, serviceclient_generic_error): + def test_job_list_error(self, fixture_cmd, serviceclient_generic_error): with pytest.raises(CLIError): - subject.job_list(cmd=fixture_cmd2, hub_name=mock_target["entity"]) + subject.job_list(cmd=fixture_cmd, hub_name=mock_target["entity"]) diff --git a/azext_iot/tests/iothub/pnp_runtime/__init__.py b/azext_iot/tests/iothub/pnp_runtime/__init__.py new file mode 100644 index 000000000..55614acbf --- /dev/null +++ b/azext_iot/tests/iothub/pnp_runtime/__init__.py @@ -0,0 +1,5 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/azext_iot/tests/iothub/pnp_runtime/test_iot_pnp_runtime_unit.py b/azext_iot/tests/iothub/pnp_runtime/test_iot_pnp_runtime_unit.py new file mode 100644 index 000000000..8011e48f5 --- /dev/null +++ b/azext_iot/tests/iothub/pnp_runtime/test_iot_pnp_runtime_unit.py @@ -0,0 +1,273 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +import pytest +import responses +import json +from random import randint +from knack.cli import CLIError +from azext_iot.iothub import commands_pnp_runtime as subject +from azext_iot.tests.conftest import mock_target +from azext_iot.tests.generators import generate_generic_id + + +device_id = generate_generic_id() +command_id = generate_generic_id() +component_id = generate_generic_id() +generic_result = json.dumps({"result": generate_generic_id()}) + + +class TestPnPRuntimeInvokeCommand(object): + @pytest.fixture(params=[201]) + def service_client_root(self, mocked_response, fixture_ghcs, request): + # Root level device command mock + mocked_response.add( + method=responses.POST, + url="https://{}/digitaltwins/{}/commands/{}".format( + mock_target["entity"], device_id, command_id + ), + body=generic_result, + headers={"x-ms-command-statuscode": str(request.param)}, + status=200, + content_type="application/json", + match_querystring=False, + ) + + mocked_response.metadata = request.param + yield mocked_response + + @pytest.fixture(params=[201]) + def service_client_component(self, mocked_response, fixture_ghcs, request): + # Component level device command mock + mocked_response.add( + method=responses.POST, + url="https://{}/digitaltwins/{}/components/{}/commands/{}".format( + mock_target["entity"], device_id, component_id, command_id + ), + body=generic_result, + headers={"x-ms-command-statuscode": str(request.param)}, + status=200, + content_type="application/json", + match_querystring=False, + ) + + mocked_response.metadata = request.param + yield mocked_response + + @pytest.mark.parametrize( + "request_payload, arbitrary_timeout", + [ + ("{}", randint(10, 30)), + (json.dumps({"key": str(generate_generic_id())}), None), + ], + ) + def test_pnp_runtime_invoke_root_command( + self, fixture_cmd, service_client_root, request_payload, arbitrary_timeout + ): + result = subject.invoke_device_command( + cmd=fixture_cmd, + device_id=device_id, + command_name=command_id, + connect_timeout=arbitrary_timeout, + response_timeout=arbitrary_timeout, + payload=request_payload, + hub_name=mock_target["entity"], + ) + + self._assert_common_attributes( + request_payload=request_payload, + executed_client=service_client_root, + result=result, + connect_timeout=arbitrary_timeout, + response_timeout=arbitrary_timeout, + ) + + @pytest.mark.parametrize( + "request_payload, arbitrary_timeout", + [ + ("{}", randint(10, 30)), + (json.dumps({"key": str(generate_generic_id())}), None), + ], + ) + def test_pnp_runtime_invoke_component_command( + self, fixture_cmd, service_client_component, request_payload, arbitrary_timeout + ): + result = subject.invoke_device_command( + cmd=fixture_cmd, + device_id=device_id, + command_name=command_id, + connect_timeout=arbitrary_timeout, + response_timeout=arbitrary_timeout, + payload=request_payload, + hub_name=mock_target["entity"], + component_path=component_id, + ) + + self._assert_common_attributes( + request_payload=request_payload, + executed_client=service_client_component, + result=result, + connect_timeout=arbitrary_timeout, + response_timeout=arbitrary_timeout, + ) + + def test_pnp_runtime_invoke_command_error( + self, + fixture_cmd, + service_client_generic_errors, + ): + with pytest.raises(CLIError): + subject.invoke_device_command( + cmd=fixture_cmd, + device_id=device_id, + command_name=command_id, + payload=json.dumps({}), + hub_name=mock_target["entity"], + ) + + with pytest.raises(CLIError): + subject.invoke_device_command( + cmd=fixture_cmd, + device_id=device_id, + command_name=command_id, + payload=json.dumps({}), + hub_name=mock_target["entity"], + component_path=component_id, + ) + + def _assert_common_attributes( + self, + request_payload, + executed_client, + result, + connect_timeout=None, + response_timeout=None, + ): + if connect_timeout: + assert ( + "connectTimeoutInSeconds={}".format(connect_timeout) + in executed_client.calls[0].request.url + ) + + if response_timeout: + assert ( + "responseTimeoutInSeconds={}".format(response_timeout) + in executed_client.calls[0].request.url + ) + + assert request_payload == executed_client.calls[0].request.body + assert ( + executed_client.calls[0].request.headers["Content-Type"] + == "application/json; charset=utf-8" + ) + + assert result["payload"] == json.loads(generic_result) + assert result["status"] == str(executed_client.metadata) + + +class TestPnPRuntimeShowDigitalTwin(object): + @pytest.fixture + def service_client(self, mocked_response, fixture_ghcs, request): + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}".format( + mock_target["entity"], + device_id, + ), + body=generic_result, + status=200, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + def test_pnp_runtime_show_digital_twin(self, fixture_cmd, service_client): + result = subject.get_digital_twin( + cmd=fixture_cmd, + device_id=device_id, + hub_name=mock_target["entity"], + ) + + # Validates simple endpoint behavior. + # The request pattern is validated via responses mock. + assert result == json.loads(generic_result) + + +class TestPnPRuntimeUpdateDigitalTwin(object): + @pytest.fixture + def service_client(self, mocked_response, fixture_ghcs, request): + mocked_response.add( + method=responses.PATCH, + url="https://{}/digitaltwins/{}".format( + mock_target["entity"], + device_id, + ), + body=None, + status=202, + content_type="application/json", + match_querystring=False, + ) + + # The command currently will GET a fresh view of the digital twin + # because the update operation does not return anything. + mocked_response.add( + method=responses.GET, + url="https://{}/digitaltwins/{}".format( + mock_target["entity"], + device_id, + ), + body=generic_result, + status=200, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + @pytest.mark.parametrize( + "request_json_patch, etag", + [ + ( + '[{"op":"remove", "path":"/thermostat1/targetTemperature"}, ' + '{"op":"add", "path":"/thermostat2/targetTemperature", "value": 22}]', + generate_generic_id(), + ), + ( + '{"op":"add", "path":"/thermostat1/targetTemperature", "value": 54}', + None, + ), + ], + ) + def test_pnp_runtime_update_digital_twin( + self, fixture_cmd, service_client, request_json_patch, etag + ): + json_patch = json.loads(request_json_patch) + + json_patch_collection = [] + if isinstance(json_patch, dict): + json_patch_collection.append(json_patch) + if isinstance(json_patch, list): + json_patch_collection.extend(json_patch) + + expected_request_body = json.dumps(json_patch_collection) + + result = subject.patch_digital_twin( + cmd=fixture_cmd, + device_id=device_id, + hub_name=mock_target["entity"], + json_patch=request_json_patch, + etag=etag, + ) + + # First call for patch + patch_request = service_client.calls[0].request + assert patch_request.body == expected_request_body + assert patch_request.headers["If-Match"] == etag if etag else "*" + + # Result from get twin + assert result == json.loads(generic_result) diff --git a/azext_iot/tests/test_generic_replace.json b/azext_iot/tests/iothub/test_generic_replace.json similarity index 100% rename from azext_iot/tests/test_generic_replace.json rename to azext_iot/tests/iothub/test_generic_replace.json diff --git a/azext_iot/tests/test_generic_twin.json b/azext_iot/tests/iothub/test_generic_twin.json similarity index 100% rename from azext_iot/tests/test_generic_twin.json rename to azext_iot/tests/iothub/test_generic_twin.json diff --git a/azext_iot/tests/test_iot_ext_int.py b/azext_iot/tests/iothub/test_iot_ext_int.py similarity index 71% rename from azext_iot/tests/test_iot_ext_int.py rename to azext_iot/tests/iothub/test_iot_ext_int.py index bd196988e..81b67f0f5 100644 --- a/azext_iot/tests/test_iot_ext_int.py +++ b/azext_iot/tests/iothub/test_iot_ext_int.py @@ -9,22 +9,27 @@ import warnings from azext_iot.common.utility import read_file_content -from . import IoTLiveScenarioTest -from .settings import DynamoSettings, ENV_SET_TEST_IOTHUB_BASIC +from azext_iot.tests import IoTLiveScenarioTest +from azext_iot.tests.settings import DynamoSettings, ENV_SET_TEST_IOTHUB_BASIC from azext_iot.constants import DEVICE_DEVICESCOPE_PREFIX -opt_env_set = ["azext_iot_teststorageuri"] +opt_env_set = ["azext_iot_teststorageuri", "azext_iot_identity_teststorageid"] -settings = DynamoSettings(req_env_set=ENV_SET_TEST_IOTHUB_BASIC, opt_env_set=opt_env_set) +settings = DynamoSettings( + req_env_set=ENV_SET_TEST_IOTHUB_BASIC, opt_env_set=opt_env_set +) LIVE_HUB = settings.env.azext_iot_testhub LIVE_RG = settings.env.azext_iot_testrg -LIVE_HUB_CS = settings.env.azext_iot_testhub_cs -LIVE_HUB_MIXED_CASE_CS = LIVE_HUB_CS.replace("HostName", "hostname", 1) # Set this environment variable to your empty blob container sas uri to test device export and enable file upload test. # For file upload, you will need to have configured your IoT Hub before running. LIVE_STORAGE = settings.env.azext_iot_teststorageuri + +# Set this environment variable to enable identity-based integration tests +# You will need permissions to add and remove role assignments for this storage account +LIVE_STORAGE_ID = settings.env.azext_iot_identity_teststorageid + LIVE_CONSUMER_GROUPS = ["test1", "test2", "test3"] CWD = os.path.dirname(os.path.abspath(__file__)) @@ -35,7 +40,7 @@ class TestIoTHub(IoTLiveScenarioTest): def __init__(self, test_case): - super(TestIoTHub, self).__init__(test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS) + super(TestIoTHub, self).__init__(test_case, LIVE_HUB, LIVE_RG) def test_hub(self): self.cmd( @@ -55,22 +60,71 @@ def test_hub(self): # With connection string self.cmd( - "az iot hub generate-sas-token --login {}".format(LIVE_HUB_CS), + "az iot hub generate-sas-token --login {}".format(self.connection_string), checks=[self.exists("sas")], ) self.cmd( "az iot hub generate-sas-token --login {} --pn somepolicy".format( - LIVE_HUB_CS + self.connection_string ), expect_failure=True, ) + # Test 'az iot hub connection-string show' + conn_str_pattern = r'^HostName={0}.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey='.format( + LIVE_HUB) + conn_str_eventhub_pattern = (r'^Endpoint=sb://(.+?)servicebus.windows.net/;SharedAccessKeyName=' + r'iothubowner;SharedAccessKey=(.+?);EntityPath=') + defaultpolicy = "iothubowner" + nonexistantpolicy = "badpolicy" + + hubs_in_sub = self.cmd('iot hub connection-string show').get_output_in_json() + hubs_in_rg = self.cmd('iot hub connection-string show -g {}'.format(LIVE_RG)).get_output_in_json() + assert len(hubs_in_sub) >= len(hubs_in_rg) + + self.cmd('iot hub connection-string show -n {0}'.format(LIVE_HUB), checks=[ + self.check_pattern('connectionString', conn_str_pattern) + ]) + + self.cmd('iot hub connection-string show -n {0} --pn {1}'.format(LIVE_HUB, defaultpolicy), checks=[ + self.check_pattern('connectionString', conn_str_pattern) + ]) + + self.cmd( + 'iot hub connection-string show -n {0} --pn {1}'.format(LIVE_HUB, nonexistantpolicy), + expect_failure=True, + ) + + self.cmd( + 'iot hub connection-string show --pn {0}'.format(nonexistantpolicy), + checks=[self.check('length(@)', 0)] + ) + + self.cmd('iot hub connection-string show -n {0} --eh'.format(LIVE_HUB), checks=[ + self.check_pattern('connectionString', conn_str_eventhub_pattern) + ]) + + self.cmd('iot hub connection-string show -n {0} -g {1}'.format(LIVE_HUB, LIVE_RG), checks=[ + self.check('length(@)', 1), + self.check_pattern('connectionString', conn_str_pattern) + ]) + + self.cmd('iot hub connection-string show -n {0} -g {1} --all'.format(LIVE_HUB, LIVE_RG), checks=[ + self.greater_than('length(connectionString[*])', 0), + self.check_pattern('connectionString[0]', conn_str_pattern) + ]) + + self.cmd('iot hub connection-string show -n {0} -g {1} --all --eh'.format(LIVE_HUB, LIVE_RG), checks=[ + self.greater_than('length(connectionString[*])', 0), + self.check_pattern('connectionString[0]', conn_str_eventhub_pattern) + ]) + # With connection string # Error can't change key for a sas token with conn string self.cmd( "az iot hub generate-sas-token --login {} --kt secondary".format( - LIVE_HUB_CS + self.connection_string ), expect_failure=True, ) @@ -85,7 +139,7 @@ def test_hub(self): # With connection string self.cmd( 'iot hub query --query-command "{}" --login {}'.format( - "select * from devices", LIVE_HUB_CS + "select * from devices", self.connection_string ), checks=[self.check("length([*])", 0)], ) @@ -106,16 +160,20 @@ def test_hub(self): class TestIoTHubDevices(IoTLiveScenarioTest): def __init__(self, test_case): super(TestIoTHubDevices, self).__init__( - test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS + test_case, LIVE_HUB, LIVE_RG ) def test_hub_devices(self): device_count = 5 edge_device_count = 2 + edge_x509_device_count = 2 + total_edge_device_count = edge_x509_device_count + edge_device_count device_ids = self.generate_device_names(device_count) edge_device_ids = self.generate_device_names(edge_device_count, edge=True) - total_devices = device_count + edge_device_count + edge_x509_device_ids = self.generate_device_names(edge_x509_device_count, edge=True) + + total_devices = device_count + total_edge_device_count self.cmd( "iot hub device-identity create -d {} -n {} -g {}".format( @@ -162,25 +220,51 @@ def test_hub_devices(self): ) # All edge devices + child device - query_checks = [self.check("length([*])", edge_device_count + 1)] + query_checks = [self.check("length([*])", total_edge_device_count + 1)] for i in edge_device_ids: query_checks.append(self.exists("[?deviceId==`{}`]".format(i))) query_checks.append(self.exists("[?deviceId==`{}`]".format(device_ids[4]))) - # Not currently supported + # Edge x509_thumbprint self.cmd( - "iot hub device-identity create -d {} -n {} -g {} --auth-method x509_thumbprint --ee".format( - "willnotwork", LIVE_HUB, LIVE_RG + "iot hub device-identity create -d {} -n {} -g {} --auth-method x509_thumbprint --ptp {} --stp {} --ee".format( + edge_x509_device_ids[0], LIVE_HUB, LIVE_RG, PRIMARY_THUMBPRINT, SECONDARY_THUMBPRINT ), - expect_failure=True, + checks=[ + self.check("deviceId", edge_x509_device_ids[0]), + self.check("status", "enabled"), + self.check("statusReason", None), + self.check("capabilities.iotEdge", True), + self.check("connectionState", "Disconnected"), + self.check("authentication.symmetricKey.primaryKey", None), + self.check("authentication.symmetricKey.secondaryKey", None), + self.check( + "authentication.x509Thumbprint.primaryThumbprint", + PRIMARY_THUMBPRINT, + ), + self.check( + "authentication.x509Thumbprint.secondaryThumbprint", + SECONDARY_THUMBPRINT, + ), + ] ) - # Not currently supported + # Edge x509_ca self.cmd( "iot hub device-identity create -d {} -n {} -g {} --auth-method x509_ca --ee".format( - "willnotwork", LIVE_HUB, LIVE_RG + edge_x509_device_ids[1], LIVE_HUB, LIVE_RG ), - expect_failure=True, + checks=[ + self.check("deviceId", edge_x509_device_ids[1]), + self.check("status", "enabled"), + self.check("capabilities.iotEdge", True), + self.check("connectionState", "Disconnected"), + self.check("authentication.symmetricKey.primaryKey", None), + self.check("authentication.symmetricKey.secondaryKey", None), + self.check("authentication.x509Thumbprint.primaryThumbprint", None), + self.check("authentication.x509Thumbprint.secondaryThumbprint", None), + self.check("authentication.type", "certificateAuthority") + ] ) self.cmd( @@ -193,7 +277,7 @@ def test_hub_devices(self): # With connection string self.cmd( 'iot hub query -q "{}" --login {}'.format( - "select * from devices", LIVE_HUB_CS + "select * from devices", self.connection_string ), checks=query_checks, ) @@ -201,7 +285,7 @@ def test_hub_devices(self): # -1 for no return limit self.cmd( 'iot hub query -q "{}" --login {} --top -1'.format( - "select * from devices", LIVE_HUB_CS + "select * from devices", self.connection_string ), checks=query_checks, ) @@ -257,7 +341,7 @@ def test_hub_devices(self): self.cmd( '''iot hub device-identity create --device-id {} --login {} --auth-method x509_ca --status disabled --status-reason "{}"'''.format( - device_ids[2], LIVE_HUB_CS, status_reason + device_ids[2], self.connection_string, status_reason ), checks=[ self.check("deviceId", device_ids[2]), @@ -293,6 +377,7 @@ def test_hub_devices(self): self.exists("authentication.x509Thumbprint.primaryThumbprint"), self.check("authentication.x509Thumbprint.secondaryThumbprint", None), self.exists("deviceScope"), + self.exists("parentScopes"), self.check_pattern("deviceScope", child_device_scope_str_pattern), ], ) @@ -315,7 +400,7 @@ def test_hub_devices(self): # With connection string self.cmd( "iot hub device-identity show -d {} --login {}".format( - edge_device_ids[0], LIVE_HUB_CS + edge_device_ids[0], self.connection_string ), checks=[ self.check("deviceId", edge_device_ids[0]), @@ -346,13 +431,13 @@ def test_hub_devices(self): # List only edge devices self.cmd( "iot hub device-identity list -n {} -g {} --ee".format(LIVE_HUB, LIVE_RG), - checks=[self.check("length([*])", edge_device_count)], + checks=[self.check("length([*])", total_edge_device_count)], ) # With connection string self.cmd( - "iot hub device-identity list --ee --login {}".format(LIVE_HUB_CS), - checks=[self.check("length([*])", edge_device_count)], + "iot hub device-identity list --ee --login {}".format(self.connection_string), + checks=[self.check("length([*])", total_edge_device_count)], ) self.cmd( @@ -377,12 +462,59 @@ def test_hub_devices(self): ) self.cmd( - '''iot hub device-identity update -d {} -n {} -g {} --set authentication.symmetricKey.primaryKey="" - authentication.symmetricKey.secondaryKey=""'''.format( - edge_device_ids[1], LIVE_HUB, LIVE_RG + "iot hub device-identity update -d {} -n {} -g {} --ee {} --auth-method {}" + .format(device_ids[0], LIVE_HUB, LIVE_RG, False, 'x509_ca'), + checks=[ + self.check("deviceId", device_ids[0]), + self.check("status", "enabled"), + self.check("capabilities.iotEdge", False), + self.check("authentication.symmetricKey.primaryKey", None), + self.check("authentication.symmetricKey.secondaryKey", None), + self.check("authentication.x509Thumbprint.primaryThumbprint", None), + self.check("authentication.x509Thumbprint.secondaryThumbprint", None), + self.check("authentication.type", 'certificateAuthority') + ] + ) + + self.cmd( + "iot hub device-identity update -d {} -n {} -g {} --status-reason {}" + .format(device_ids[0], LIVE_HUB, LIVE_RG, 'TestStatusReason'), + checks=[ + self.check("deviceId", device_ids[0]), + self.check("statusReason", 'TestStatusReason'), + ] + ) + + self.cmd( + "iot hub device-identity update -d {} -n {} -g {} --ee {} --status {}" + " --status-reason {} --auth-method {} --ptp {} --stp {}" + .format(device_ids[0], LIVE_HUB, LIVE_RG, False, 'enabled', + 'StatusReasonUpdated', 'x509_thumbprint', PRIMARY_THUMBPRINT, SECONDARY_THUMBPRINT), + checks=[ + self.check("deviceId", device_ids[0]), + self.check("status", "enabled"), + self.check("capabilities.iotEdge", False), + self.check("statusReason", 'StatusReasonUpdated'), + self.check("authentication.x509Thumbprint.primaryThumbprint", PRIMARY_THUMBPRINT), + self.check("authentication.x509Thumbprint.secondaryThumbprint", SECONDARY_THUMBPRINT), + ] + ) + + self.cmd("iot hub device-identity update -d {} -n {} -g {} --auth-method {}" + .format(device_ids[0], LIVE_HUB, LIVE_RG, 'x509_thumbprint'), + expect_failure=True) + + self.cmd("iot hub device-identity update -d {} -n {} -g {} --auth-method {} --pk {}" + .format(device_ids[0], LIVE_HUB, LIVE_RG, 'shared_private_key', '123'), + expect_failure=True) + + self.cmd( + '''iot hub device-identity update -d {} -n {} -g {} --primary-key="" + --secondary-key=""'''.format( + device_ids[4], LIVE_HUB, LIVE_RG ), checks=[ - self.check("deviceId", edge_device_ids[1]), + self.check("deviceId", device_ids[4]), self.check("status", "enabled"), self.exists("authentication.symmetricKey.primaryKey"), self.exists("authentication.symmetricKey.secondaryKey"), @@ -393,16 +525,44 @@ def test_hub_devices(self): self.cmd( '''iot hub device-identity update -d {} --login {} --set authentication.symmetricKey.primaryKey="" authentication.symmetricKey.secondaryKey=""'''.format( - edge_device_ids[1], LIVE_HUB_CS + device_ids[4], self.connection_string ), checks=[ - self.check("deviceId", edge_device_ids[1]), + self.check("deviceId", device_ids[4]), self.check("status", "enabled"), self.exists("authentication.symmetricKey.primaryKey"), self.exists("authentication.symmetricKey.secondaryKey"), ], ) + # Test 'az iot hub device renew-key' + device = self.cmd( + '''iot hub device-identity renew-key -d {} -n {} -g {} --kt primary + '''.format( + edge_device_ids[1], LIVE_HUB, LIVE_RG + ), + checks=[ + self.check("deviceId", edge_device_ids[1]) + ] + ).get_output_in_json() + + # Test swap keys 'az iot hub device renew-key' + self.cmd( + '''iot hub device-identity renew-key -d {} -n {} -g {} --kt swap + '''.format( + edge_device_ids[1], LIVE_HUB, LIVE_RG + ), + checks=[ + self.check("authentication.symmetricKey.primaryKey", device['authentication']['symmetricKey']['secondaryKey']), + self.check("authentication.symmetricKey.secondaryKey", device['authentication']['symmetricKey']['primaryKey']) + ], + ) + + # Test 'az iot hub device renew-key' with non sas authentication + self.cmd("iot hub device-identity renew-key -d {} -n {} -g {} --kt secondary" + .format(device_ids[0], LIVE_HUB, LIVE_RG), + expect_failure=True) + sym_conn_str_pattern = r"^HostName={}\.azure-devices\.net;DeviceId={};SharedAccessKey=".format( LIVE_HUB, edge_device_ids[0] ) @@ -431,6 +591,27 @@ def test_hub_devices(self): checks=[self.check_pattern("connectionString", cer_conn_str_pattern)], ) + self.cmd( + "iot hub device-identity connection-string show -d {} -n {} -g {}".format( + edge_device_ids[0], LIVE_HUB, LIVE_RG + ), + checks=[self.check_pattern("connectionString", sym_conn_str_pattern)], + ) + + self.cmd( + "iot hub device-identity connection-string show -d {} -n {} -g {} --kt {}".format( + edge_device_ids[0], LIVE_HUB, LIVE_RG, "secondary" + ), + checks=[self.check_pattern("connectionString", sym_conn_str_pattern)], + ) + + self.cmd( + "iot hub device-identity connection-string show -d {} -n {} -g {}".format( + device_ids[2], LIVE_HUB, LIVE_RG + ), + checks=[self.check_pattern("connectionString", cer_conn_str_pattern)], + ) + self.cmd( "iot hub generate-sas-token -n {} -g {} -d {}".format( LIVE_HUB, LIVE_RG, edge_device_ids[0] @@ -463,21 +644,21 @@ def test_hub_devices(self): # With connection string self.cmd( "iot hub generate-sas-token -d {} --login {}".format( - edge_device_ids[0], LIVE_HUB_CS + edge_device_ids[0], self.connection_string ), checks=[self.exists("sas")], ) self.cmd( 'iot hub generate-sas-token -d {} --login {} --kt "secondary"'.format( - edge_device_ids[1], LIVE_HUB_CS + edge_device_ids[1], self.connection_string ), checks=[self.exists("sas")], ) self.cmd( 'iot hub generate-sas-token -d {} --login {} --pn "mypolicy"'.format( - edge_device_ids[1], LIVE_HUB_CS + edge_device_ids[1], self.connection_string ), expect_failure=True, ) @@ -486,12 +667,14 @@ def test_hub_devices(self): class TestIoTHubDeviceTwins(IoTLiveScenarioTest): def __init__(self, test_case): super(TestIoTHubDeviceTwins, self).__init__( - test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS + test_case, LIVE_HUB, LIVE_RG ) def test_hub_device_twins(self): self.kwargs["generic_dict"] = {"key": "value"} self.kwargs["bad_format"] = "{'key: 'value'}" + self.kwargs["patch_desired"] = {"patchScenario": {"desiredKey": "desiredValue"}} + self.kwargs["patch_tags"] = {"patchScenario": {"tagkey": "tagValue"}} device_count = 3 device_ids = self.generate_device_names(device_count) @@ -519,7 +702,7 @@ def test_hub_device_twins(self): # With connection string self.cmd( "iot hub device-twin show -d {} --login {}".format( - device_ids[0], LIVE_HUB_CS + device_ids[0], self.connection_string ), checks=[ self.check("deviceId", device_ids[0]), @@ -529,6 +712,58 @@ def test_hub_device_twins(self): ], ) + # Patch based twin update of desired props + self.cmd( + "iot hub device-twin update -d {} -n {} -g {} --desired {}".format( + device_ids[2], + LIVE_HUB, + LIVE_RG, + '"{patch_desired}"', + ), + checks=[ + self.check("deviceId", device_ids[2]), + self.check( + "properties.desired.patchScenario", + self.kwargs["patch_desired"]["patchScenario"], + ), + ], + ) + + # Patch based twin update of tags with connection string + self.cmd( + "iot hub device-twin update -d {} --login {} --tags {}".format( + device_ids[2], self.connection_string, '"{patch_tags}"' + ), + checks=[ + self.check("deviceId", device_ids[2]), + self.check( + "tags.patchScenario", self.kwargs["patch_tags"]["patchScenario"] + ), + ], + ) + + # Patch based twin update of desired + tags + self.cmd( + "iot hub device-twin update -d {} -n {} --desired {} --tags {}".format( + device_ids[2], + LIVE_HUB, + '"{patch_desired}"', + '"{patch_tags}"', + ), + checks=[ + self.check("deviceId", device_ids[2]), + self.check( + "properties.desired.patchScenario", + self.kwargs["patch_desired"]["patchScenario"], + ), + self.check( + "tags.patchScenario", + self.kwargs["patch_tags"]["patchScenario"] + ), + ], + ) + + # Deprecated generic update result = self.cmd( "iot hub device-twin update -d {} -n {} -g {} --set properties.desired.special={}".format( device_ids[0], LIVE_HUB, LIVE_RG, '"{generic_dict}"' @@ -549,7 +784,7 @@ def test_hub_device_twins(self): # With connection string result = self.cmd( "iot hub device-twin update -d {} --login {} --set properties.desired.special={}".format( - device_ids[0], LIVE_HUB_CS, '"{generic_dict}"' + device_ids[0], self.connection_string, '"{generic_dict}"' ) ).get_output_in_json() assert result["deviceId"] == device_ids[0] @@ -594,7 +829,7 @@ def test_hub_device_twins(self): # With connection string self.cmd( "iot hub device-twin replace -d {} --login {} -j '{}'".format( - device_ids[1], LIVE_HUB_CS, "{twin_payload}" + device_ids[1], self.connection_string, "{twin_payload}" ), checks=[ self.check("deviceId", device_ids[1]), @@ -607,7 +842,7 @@ def test_hub_device_twins(self): # Region specific test if self.region not in ["West US 2", "North Europe", "Southeast Asia"]: - warnings.warn("Skipping distributed-tracing tests. IoT Hub not in supported region!") + warnings.warn(UserWarning("Skipping distributed-tracing tests. IoT Hub not in supported region!")) else: self.cmd( "iot hub distributed-tracing show -d {} -n {} -g {}".format( @@ -630,7 +865,7 @@ def test_hub_device_twins(self): class TestIoTHubModules(IoTLiveScenarioTest): def __init__(self, test_case): super(TestIoTHubModules, self).__init__( - test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS + test_case, LIVE_HUB, LIVE_RG ) def test_hub_modules(self): @@ -673,7 +908,7 @@ def test_hub_modules(self): self.cmd( "iot hub module-identity create -d {} --login {} -m {}".format( - edge_device_ids[0], LIVE_HUB_CS, module_ids[0] + edge_device_ids[0], self.connection_string, module_ids[0] ), checks=[ self.check("deviceId", edge_device_ids[0]), @@ -702,15 +937,16 @@ def test_hub_modules(self): # sas token for module with connection string self.cmd( "iot hub generate-sas-token -d {} -m {} --login {}".format( - edge_device_ids[0], module_ids[1], LIVE_HUB_CS + edge_device_ids[0], module_ids[1], self.connection_string ), checks=[self.exists("sas")], ) # sas token for module with mixed case connection string + mixed_case_cstring = self.connection_string.replace("HostName", "hostname", 1) self.cmd( "iot hub generate-sas-token -d {} -m {} --login {}".format( - edge_device_ids[0], module_ids[1], LIVE_HUB_MIXED_CASE_CS + edge_device_ids[0], module_ids[1], mixed_case_cstring ), checks=[self.exists("sas")], ) @@ -722,7 +958,7 @@ def test_hub_modules(self): --auth-method x509_thumbprint --primary-thumbprint {} --secondary-thumbprint {}""".format( module_ids[0], device_ids[0], - LIVE_HUB_CS, + self.connection_string, PRIMARY_THUMBPRINT, SECONDARY_THUMBPRINT, ), @@ -762,7 +998,7 @@ def test_hub_modules(self): # With connection string self.cmd( """iot hub module-identity create --module-id {} --device-id {} --login {} --auth-method x509_ca""".format( - module_ids[0], edge_device_ids[1], LIVE_HUB_CS + module_ids[0], edge_device_ids[1], self.connection_string ), checks=[ self.check("deviceId", edge_device_ids[1]), @@ -804,7 +1040,7 @@ def test_hub_modules(self): self.cmd( '''iot hub module-identity update -d {} --login {} -m {} --set authentication.symmetricKey.primaryKey="" authentication.symmetricKey.secondaryKey=""'''.format( - edge_device_ids[0], LIVE_HUB_CS, module_ids[0] + edge_device_ids[0], self.connection_string, module_ids[0] ), checks=[ self.check("deviceId", edge_device_ids[0]), @@ -839,7 +1075,7 @@ def test_hub_modules(self): # With connection string self.cmd( "iot hub module-identity list -d {} --login {}".format( - edge_device_ids[0], LIVE_HUB_CS + edge_device_ids[0], self.connection_string ), checks=[ self.check("length([*])", 4), @@ -863,7 +1099,7 @@ def test_hub_modules(self): # With connection string self.cmd( "iot hub module-identity show -d {} --login {} -m {}".format( - edge_device_ids[0], LIVE_HUB_CS, module_ids[0] + edge_device_ids[0], self.connection_string, module_ids[0] ), checks=[ self.check("deviceId", edge_device_ids[0]), @@ -886,7 +1122,7 @@ def test_hub_modules(self): # With connection string self.cmd( "iot hub module-identity show-connection-string -d {} --login {} -m {}".format( - edge_device_ids[0], LIVE_HUB_CS, module_ids[0] + edge_device_ids[0], self.connection_string, module_ids[0] ), checks=[self.check_pattern("connectionString", mod_sym_conn_str_pattern)], ) @@ -898,12 +1134,34 @@ def test_hub_modules(self): checks=[self.check_pattern("connectionString", mod_sym_conn_str_pattern)], ) + self.cmd( + "iot hub module-identity connection-string show -d {} -n {} -g {} -m {}".format( + edge_device_ids[0], LIVE_HUB, LIVE_RG, module_ids[0] + ), + checks=[self.check_pattern("connectionString", mod_sym_conn_str_pattern)], + ) + + # With connection string + self.cmd( + "iot hub module-identity connection-string show -d {} --login {} -m {}".format( + edge_device_ids[0], self.connection_string, module_ids[0] + ), + checks=[self.check_pattern("connectionString", mod_sym_conn_str_pattern)], + ) + + self.cmd( + "iot hub module-identity connection-string show -d {} -n {} -g {} -m {} --kt {}".format( + edge_device_ids[0], LIVE_HUB, LIVE_RG, module_ids[0], "secondary" + ), + checks=[self.check_pattern("connectionString", mod_sym_conn_str_pattern)], + ) + for i in module_ids: if module_ids.index(i) == (module_count - 1): # With connection string self.cmd( "iot hub module-identity delete -d {} --login {} --module-id {}".format( - edge_device_ids[0], LIVE_HUB_CS, i + edge_device_ids[0], self.connection_string, i ), checks=self.is_empty(), ) @@ -919,12 +1177,14 @@ def test_hub_modules(self): class TestIoTHubModuleTwins(IoTLiveScenarioTest): def __init__(self, test_case): super(TestIoTHubModuleTwins, self).__init__( - test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS + test_case, LIVE_HUB, LIVE_RG ) def test_hub_module_twins(self): self.kwargs["generic_dict"] = {"key": "value"} self.kwargs["bad_format"] = "{'key: 'value'}" + self.kwargs["patch_desired"] = {"patchScenario": {"desiredKey": "desiredValue"}} + self.kwargs["patch_tags"] = {"patchScenario": {"tagkey": "tagValue"}} edge_device_count = 1 device_count = 1 @@ -987,7 +1247,7 @@ def test_hub_module_twins(self): # With connection string self.cmd( "iot hub module-twin show -d {} --login {} -m {}".format( - edge_device_ids[0], LIVE_HUB_CS, module_ids[0] + edge_device_ids[0], self.connection_string, module_ids[0] ), checks=[ self.check("deviceId", edge_device_ids[0]), @@ -997,6 +1257,63 @@ def test_hub_module_twins(self): ], ) + # Patch based twin update of desired props + self.cmd( + "iot hub module-twin update -d {} -n {} -g {} -m {} --desired {}".format( + edge_device_ids[0], + LIVE_HUB, + LIVE_RG, + module_ids[0], + '"{patch_desired}"', + ), + checks=[ + self.check("deviceId", edge_device_ids[0]), + self.check("moduleId", module_ids[0]), + self.check( + "properties.desired.patchScenario", + self.kwargs["patch_desired"]["patchScenario"], + ), + ], + ) + + # Patch based twin update of tags with connection string + self.cmd( + "iot hub module-twin update -d {} --login {} -m {} --tags {}".format( + edge_device_ids[0], self.connection_string, module_ids[0], '"{patch_tags}"' + ), + checks=[ + self.check("deviceId", edge_device_ids[0]), + self.check("moduleId", module_ids[0]), + self.check( + "tags.patchScenario", self.kwargs["patch_tags"]["patchScenario"] + ), + ], + ) + + # Patch based twin update of desired + tags + self.cmd( + "iot hub module-twin update -d {} -n {} -m {} --desired {} --tags {}".format( + device_ids[0], + LIVE_HUB, + module_ids[0], + '"{patch_desired}"', + '"{patch_tags}"', + ), + checks=[ + self.check("deviceId", device_ids[0]), + self.check("moduleId", module_ids[0]), + self.check( + "properties.desired.patchScenario", + self.kwargs["patch_desired"]["patchScenario"], + ), + self.check( + "tags.patchScenario", + self.kwargs["patch_tags"]["patchScenario"] + ), + ], + ) + + # Deprecated twin update style self.cmd( "iot hub module-twin update -d {} -n {} -g {} -m {} --set properties.desired.special={}".format( edge_device_ids[0], LIVE_HUB, LIVE_RG, module_ids[0], '"{generic_dict}"' @@ -1011,7 +1328,7 @@ def test_hub_module_twins(self): # With connection string self.cmd( "iot hub module-twin update -d {} --login {} -m {} --set properties.desired.special={}".format( - edge_device_ids[0], LIVE_HUB_CS, module_ids[0], '"{generic_dict}"' + edge_device_ids[0], self.connection_string, module_ids[0], '"{generic_dict}"' ), checks=[ self.check("deviceId", edge_device_ids[0]), @@ -1023,14 +1340,14 @@ def test_hub_module_twins(self): # Error case test type enforcer self.cmd( "iot hub module-twin update -d {} --login {} -m {} --set properties.desired={}".format( - edge_device_ids[0], LIVE_HUB_CS, module_ids[0], '"{bad_format}"' + edge_device_ids[0], self.connection_string, module_ids[0], '"{bad_format}"' ), expect_failure=True, ) self.cmd( "iot hub module-twin update -d {} --login {} -m {} --set tags={}".format( - edge_device_ids[0], LIVE_HUB_CS, module_ids[0], '"{bad_format}"' + edge_device_ids[0], self.connection_string, module_ids[0], '"{bad_format}"' ), expect_failure=True, ) @@ -1053,7 +1370,7 @@ def test_hub_module_twins(self): # With connection string self.cmd( "iot hub module-twin replace -d {} --login {} -m {} -j '{}'".format( - edge_device_ids[0], LIVE_HUB_CS, module_ids[0], content_path + edge_device_ids[0], self.connection_string, module_ids[0], content_path ), checks=[ self.check("deviceId", edge_device_ids[0]), @@ -1091,7 +1408,7 @@ def test_hub_module_twins(self): class TestIoTStorage(IoTLiveScenarioTest): def __init__(self, test_case): - super(TestIoTStorage, self).__init__(test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS) + super(TestIoTStorage, self).__init__(test_case, LIVE_HUB, LIVE_RG) @pytest.mark.skipif( not LIVE_STORAGE, reason="empty azext_iot_teststorageuri env var" @@ -1119,7 +1436,7 @@ def test_storage(self): # With connection string self.cmd( 'iot device upload-file -d {} --login {} --fp "{}" --ct {}'.format( - device_ids[0], LIVE_HUB_CS, content_path, "application/json" + device_ids[0], self.connection_string, content_path, "application/json" ), checks=self.is_empty(), ) @@ -1136,11 +1453,84 @@ def test_storage(self): ], ) + @pytest.mark.skipif( + not all([LIVE_STORAGE_ID, LIVE_STORAGE]), + reason="azext_iot_identity_teststorageid and azext_iot_teststorageuri env vars not set", + ) + def test_identity_storage(self): + identity_type_enable = "SystemAssigned" + identity_type_disable = "None" + storage_role = "Storage Blob Data Contributor" + + # check hub identity + identity_enabled = False + + hub_identity = self.cmd( + "iot hub show -n {}".format(LIVE_HUB) + ).get_output_in_json()["identity"] + + if hub_identity.get("type", None) != identity_type_enable: + # enable hub identity and get ID + hub_identity = self.cmd( + 'iot hub update -n {} --set identity.type="{}"'.format( + LIVE_HUB, identity_type_enable + ) + ).get_output_in_json()["identity"] + + identity_enabled = True + + hub_id = hub_identity.get("principalId", None) + assert hub_id + + # setup RBAC for storage account + storage_account_roles = self.cmd( + 'role assignment list --scope "{}" --role "{}" --query "[].principalId"'.format( + LIVE_STORAGE_ID, storage_role + ) + ).get_output_in_json() + + if hub_id not in storage_account_roles: + self.cmd( + 'role assignment create --assignee "{}" --role "{}" --scope "{}"'.format( + hub_id, storage_role, LIVE_STORAGE_ID + ) + ) + # give RBAC time to catch up + from time import sleep + sleep(30) + + # identity-based device-identity export + self.cmd( + 'iot hub device-identity export -n {} --bcu "{}" --auth-type {}'.format( + LIVE_HUB, LIVE_STORAGE, "identity" + ), + checks=[ + self.check("outputBlobContainerUri", LIVE_STORAGE), + self.check("failureReason", None), + self.check("type", "export"), + self.exists("jobId"), + ], + ) + + # if we enabled identity for this hub, undo identity and RBAC + if identity_enabled: + # delete role assignment first, disabling identity removes the assignee ID from AAD + self.cmd( + 'role assignment delete --assignee "{}" --role "{}" --scope "{}"'.format( + hub_id, storage_role, LIVE_STORAGE_ID + ) + ) + self.cmd( + "iot hub update -n {} --set 'identity.type=\"{}\"'".format( + LIVE_HUB, identity_type_disable + ) + ) + class TestIoTEdgeOffline(IoTLiveScenarioTest): def __init__(self, test_case): super(TestIoTEdgeOffline, self).__init__( - test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS + test_case, LIVE_HUB, LIVE_RG ) def test_edge_offline(self): @@ -1213,7 +1603,7 @@ def test_edge_offline(self): "iot hub device-identity set-parent -d {} --pd {} -n {} -g {}".format( edge_device_ids[0], edge_device_ids[1], LIVE_HUB, LIVE_RG ), - expect_failure=True, + checks=self.is_empty(), ) # add device as a child of non-edge device @@ -1264,7 +1654,7 @@ def test_edge_offline(self): "iot hub device-identity add-children -d {} --child-list {} -n {} -g {}".format( edge_device_ids[0], device_ids[0], LIVE_HUB, LIVE_RG ), - checks=self.is_empty(), + expect_failure=True, ) # add same device as a child of another edge device @@ -1291,7 +1681,6 @@ def test_edge_offline(self): expect_failure=False, ) - # TODO: Result should be JSON expected_output = "{}".format(device_ids[1]) assert output.get_output_in_json() == expected_output diff --git a/azext_iot/tests/test_iot_ext_unit.py b/azext_iot/tests/iothub/test_iot_ext_unit.py similarity index 74% rename from azext_iot/tests/test_iot_ext_unit.py rename to azext_iot/tests/iothub/test_iot_ext_unit.py index 2f7f0f51a..51fb2ada9 100644 --- a/azext_iot/tests/test_iot_ext_unit.py +++ b/azext_iot/tests/iothub/test_iot_ext_unit.py @@ -4,19 +4,18 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -''' +""" NOTICE: These tests are to be phased out and introduced in more modern form. Try not to add any new content, only fixes if necessary. Look at IoT Hub jobs or configuration tests for a better example. Also use responses fixtures like mocked_response for http request mocking. -''' +""" import pytest import json import os import responses import re -from uuid import uuid4 from azext_iot.operations import hub as subject from azext_iot.common.utility import ( validate_min_python_version, @@ -29,12 +28,11 @@ from azext_iot.constants import TRACING_PROPERTY, USER_AGENT, BASE_MQTT_API_VERSION from azext_iot.tests.generators import create_req_monitor_events, generate_generic_id from knack.util import CLIError -from .conftest import ( +from azext_iot.tests.conftest import ( fixture_cmd, build_mock_response, path_service_client, mock_target, - hub_entity, generate_cs, ) @@ -43,15 +41,14 @@ child_device_id = "child_device1" module_id = "mymod" config_id = "myconfig" +message_etag = "3k28zb44-0d00-4ddd-ade3-6110eb94c476" +c2d_purge_response = {"deviceId": device_id, "moduleId": None, "totalMessagesPurged": 3} generic_cs_template = "HostName={};SharedAccessKeyName={};SharedAccessKey={}" mock_target["cs"] = generate_cs() -# Patch Paths # -path_iot_hub_service_factory = "azext_iot.common._azure.iot_hub_service_factory" - # TODO generalize all fixtures across DPS/Hub unit tests @@ -98,6 +95,9 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): [ (generate_device_create_req()), (generate_device_create_req(ee=True)), + (generate_device_create_req(ee=True, auth="x509_ca")), + (generate_device_create_req(ee=True, auth="x509_thumbprint")), + (generate_device_create_req(ee=True, auth="x509_thumbprint", stp=None)), (generate_device_create_req(auth="x509_ca")), (generate_device_create_req(auth="x509_thumbprint")), (generate_device_create_req(auth="x509_thumbprint", stp=None)), @@ -195,12 +195,12 @@ def sc_invalid_args_device_create_setparent( self, mocker, fixture_ghcs, fixture_sas, request ): service_client = mocker.patch(path_service_client) - edge_kvp = {} + parent_kvp = {} if request.param[1] == 0: - edge_kvp.setdefault("capabilities", {"iotEdge": False}) + parent_kvp.setdefault("capabilities", {"iotEdge": False}) test_side_effect = [ build_mock_response( - mocker, request.param[0], generate_parent_device(**edge_kvp) + mocker, request.param[0], generate_parent_device(**parent_kvp) ) ] service_client.side_effect = test_side_effect @@ -226,19 +226,21 @@ def test_device_create_setparent_invalid_args( device_id, ) - @pytest.fixture(params=[(200, 0), (200, 1)]) + @pytest.fixture(params=[(200, 0), (200, 1), (200, 1)]) def sc_device_create_addchildren(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} + child_kvp = {} if request.param[1] == 1: - nonedge_kvp.setdefault("deviceScope", "abcd") + child_kvp.setdefault("parentScopes", ["abcd"]) + if request.param[1] == 1: + child_kvp.setdefault("capabilities", {"iotEdge": True}) test_side_effect = [ build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), build_mock_response(mocker, request.param[0], generate_parent_device()), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), build_mock_response(mocker, request.param[0], {}), ] @@ -270,21 +272,20 @@ def test_device_create_addchildren(self, sc_device_create_addchildren, req): assert "{}/devices/{}?".format(mock_target["entity"], child_device_id) in url assert args[0][0].method == "PUT" assert body["deviceId"] == child_device_id - assert body["deviceScope"] == generate_parent_device().get("deviceScope") + assert body["deviceScope"] == generate_parent_device().get( + "deviceScope" + ) or body["parentScopes"] == [generate_parent_device().get("deviceScope")] - @pytest.fixture(params=[(200, 0), (200, 1)]) + @pytest.fixture(params=[(200, 0)]) def sc_invalid_args_device_create_addchildren( self, mocker, fixture_ghcs, fixture_sas, request ): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} - if request.param[1] == 0: - nonedge_kvp.setdefault("capabilities", {"iotEdge": True}) - if request.param[1] == 1: - nonedge_kvp.setdefault("deviceScope", "abcd") + child_kvp = {} + child_kvp.setdefault("parentScopes", ["abcd"]) test_side_effect = [ build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ) ] service_client.side_effect = test_side_effect @@ -315,8 +316,10 @@ def test_device_create_addchildren_invalid_args( @pytest.mark.parametrize( "req, exp", [ - (generate_device_create_req(ee=True, auth="x509_thumbprint"), CLIError), - (generate_device_create_req(ee=True, auth="x509_ca"), CLIError), + ( + generate_device_create_req(ee=True, auth="x509_thumbprint", ptp=None), + ValueError, + ), (generate_device_create_req(auth="doesnotexist"), ValueError), ( generate_device_create_req(auth="x509_thumbprint", ptp=None, stp=""), @@ -355,13 +358,14 @@ def test_device_create_error(self, serviceclient_generic_error, req): def generate_device_show(**kvp): payload = { "authentication": { - "symmetricKey": {"primaryKey": "123", "secondaryKey": "321"}, + "symmetricKey": {"primaryKey": None, "secondaryKey": None}, + "x509Thumbprint": {"primaryThumbprint": None, "secondaryThumbprint": None}, "type": "sas", }, - "etag": "abcd", "capabilities": {"iotEdge": True}, "deviceId": device_id, "status": "disabled", + "statusReason": "unknown reason", } for k in kvp: if payload.get(k): @@ -369,6 +373,28 @@ def generate_device_show(**kvp): return payload +def device_update_con_arg( + edge_enabled=None, + status=None, + status_reason=None, + auth_method=None, + primary_thumbprint=None, + secondary_thumbprint=None, + primary_key=None, + secondary_key=None, +): + return { + "edge_enabled": edge_enabled, + "status": status, + "status_reason": status_reason, + "auth_method": auth_method, + "primary_thumbprint": primary_thumbprint, + "secondary_thumbprint": secondary_thumbprint, + "primary_key": primary_key, + "secondary_key": secondary_key, + } + + class TestDeviceUpdate: @pytest.fixture(params=[200]) def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): @@ -380,6 +406,8 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): @pytest.mark.parametrize( "req", [ + (generate_device_show(capabilities={"iotEdge": False})), + (generate_device_show(status="enabled")), ( generate_device_show( authentication={ @@ -399,12 +427,21 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): } ) ), - (generate_device_show(authentication={"type": "certificateAuthority"})), + ( + generate_device_show( + authentication={"type": "certificateAuthority"}, + etag=generate_generic_id(), + ) + ), ], ) def test_device_update(self, fixture_cmd, serviceclient, req): subject.iot_device_update( - fixture_cmd, req["deviceId"], hub_name=mock_target["entity"], parameters=req + fixture_cmd, + req["deviceId"], + hub_name=mock_target["entity"], + parameters=req, + etag=req.get("etag"), ) args = serviceclient.call_args assert ( @@ -425,7 +462,175 @@ def test_device_update(self, fixture_cmd, serviceclient, req): assert body["authentication"]["x509Thumbprint"]["secondaryThumbprint"] headers = args[0][0].headers - assert headers["If-Match"] == '"{}"'.format(req["etag"]) + target_etag = req.get("etag") + assert headers["If-Match"] == '"{}"'.format(target_etag if target_etag else "*") + + @pytest.mark.parametrize( + "req, arg", + [ + ( + generate_device_show(capabilities={"iotEdge": False}), + device_update_con_arg(edge_enabled=True), + ), + ( + generate_device_show(status="disabled"), + device_update_con_arg(status="enabled"), + ), + (generate_device_show(), device_update_con_arg(status_reason="test")), + ( + generate_device_show(), + device_update_con_arg( + auth_method="shared_private_key", + primary_key="primarykeyUpdated", + secondary_key="secondarykeyUpdated", + ), + ), + ( + generate_device_show( + authentication={ + "type": "selfSigned", + "symmetricKey": {"primaryKey": None, "secondaryKey": None}, + "x509Thumbprint": { + "primaryThumbprint": "123", + "secondaryThumbprint": "321", + }, + } + ), + device_update_con_arg( + auth_method="shared_private_key", + primary_key="primary_key", + secondary_key="secondary_key", + ), + ), + ( + generate_device_show( + authentication={ + "type": "certificateAuthority", + "symmetricKey": {"primaryKey": None, "secondaryKey": None}, + "x509Thumbprint": { + "primaryThumbprint": None, + "secondaryThumbprint": None, + }, + } + ), + device_update_con_arg( + auth_method="x509_thumbprint", + primary_thumbprint="primary_thumbprint", + secondary_thumbprint="secondary_thumbprint", + ), + ), + (generate_device_show(), device_update_con_arg(auth_method="x509_ca",)), + (generate_device_show(), device_update_con_arg(primary_key="secondary_key", secondary_key="primary_key")), + ( + generate_device_show( + authentication={ + "type": "selfSigned", + "symmetricKey": {"primaryKey": None, "secondaryKey": None}, + "x509Thumbprint": { + "primaryThumbprint": "123", + "secondaryThumbprint": "321", + }, + } + ), + device_update_con_arg(primary_thumbprint="321", secondary_thumbprint="123") + ) + ], + ) + def test_iot_device_custom(self, fixture_cmd, serviceclient, req, arg): + instance = subject.update_iot_device_custom( + req, + arg["edge_enabled"], + arg["status"], + arg["status_reason"], + arg["auth_method"], + arg["primary_thumbprint"], + arg["secondary_thumbprint"], + arg["primary_key"], + arg["secondary_key"], + ) + + if arg["edge_enabled"]: + assert instance["capabilities"]["iotEdge"] == arg["edge_enabled"] + if arg["status"]: + assert instance["status"] == arg["status"] + if arg["status_reason"]: + assert instance["statusReason"] == arg["status_reason"] + if arg["auth_method"]: + if arg["auth_method"] == "shared_private_key": + assert instance["authentication"]["type"] == "sas" + instance["authentication"]["symmetricKey"]["primaryKey"] == arg[ + "primary_key" + ] + instance["authentication"]["symmetricKey"]["secondaryKey"] == arg[ + "secondary_key" + ] + if arg["auth_method"] == "x509_thumbprint": + assert instance["authentication"]["type"] == "selfSigned" + if arg["primary_thumbprint"]: + instance["authentication"]["x509Thumbprint"][ + "primaryThumbprint" + ] = arg["primary_thumbprint"] + if arg["secondary_thumbprint"]: + instance["authentication"]["x509Thumbprint"][ + "secondaryThumbprint" + ] = arg["secondary_thumbprint"] + if arg["auth_method"] == "x509_ca": + assert instance["authentication"]["type"] == "certificateAuthority" + + @pytest.mark.parametrize( + "req, arg, exp", + [ + ( + generate_device_show(), + device_update_con_arg( + auth_method="shared_private_key", primary_key="primarykeyUpdated", + ), + CLIError, + ), + ( + generate_device_show(), + device_update_con_arg(auth_method="x509_thumbprint",), + CLIError, + ), + ( + generate_device_show(), + device_update_con_arg(auth_method="Unknown",), + ValueError, + ), + ( + generate_device_show(), + device_update_con_arg(primary_thumbprint="newThumbprint",), + ValueError, + ), + ( + generate_device_show( + authentication={ + "type": "selfSigned", + "symmetricKey": {"primaryKey": None, "secondaryKey": None}, + "x509Thumbprint": { + "primaryThumbprint": "123", + "secondaryThumbprint": "321", + }, + } + ), + device_update_con_arg(primary_key='updated_key'), + ValueError + ) + ], + ) + def test_iot_device_custom_invalid_args(self, serviceclient, req, arg, exp): + with pytest.raises(exp): + subject.update_iot_device_custom( + req, + arg["edge_enabled"], + arg["status"], + arg["status_reason"], + arg["auth_method"], + arg["primary_thumbprint"], + arg["secondary_thumbprint"], + arg["primary_key"], + arg["secondary_key"], + ) @pytest.mark.parametrize( "req, exp", @@ -443,7 +648,6 @@ def test_device_update(self, fixture_cmd, serviceclient, req): CLIError, ), (generate_device_show(authentication={"type": "doesnotexist"}), CLIError), - (generate_device_show(etag=None), CLIError), ], ) def test_device_update_invalid_args(self, serviceclient, req, exp): @@ -466,34 +670,105 @@ def test_device_update_error(self, serviceclient_generic_error, req): ) +class TestDeviceRegenerateKey: + @pytest.fixture(params=[200]) + def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): + service_client = mocker.patch(path_service_client) + kvp = {} + kvp.setdefault( + "authentication", + { + "symmetricKey": {"primaryKey": "123", "secondaryKey": "321"}, + "type": "sas", + }, + ) + test_side_effect = [ + build_mock_response(mocker, 200, generate_device_show(**kvp)), + build_mock_response(mocker, 200, {}), + ] + service_client.side_effect = test_side_effect + return service_client + + @pytest.mark.parametrize( + "req, etag", + [("primary", generate_generic_id()), ("secondary", None), ("swap", None)], + ) + def test_device_key_regenerate(self, fixture_cmd, serviceclient, req, etag): + subject.iot_device_key_regenerate( + fixture_cmd, mock_target["entity"], device_id, req, etag=etag + ) + args = serviceclient.call_args + assert ( + "{}/devices/{}?".format(mock_target["entity"], device_id) in args[0][0].url + ) + assert args[0][0].method == "PUT" + + body = json.loads(args[0][0].body) + if req == "primary": + assert body["authentication"]["symmetricKey"]["primaryKey"] != "123" + if req == "secondary": + assert body["authentication"]["symmetricKey"]["secondaryKey"] != "321" + if req == "swap": + assert body["authentication"]["symmetricKey"]["primaryKey"] == "321" + assert body["authentication"]["symmetricKey"]["secondaryKey"] == "123" + + headers = args[0][0].headers + assert headers["If-Match"] == '"{}"'.format(etag if etag else "*") + + @pytest.fixture(params=[200]) + def serviceclient_invalid_args(self, mocker, fixture_ghcs, fixture_sas, request): + service_client = mocker.patch(path_service_client) + kvp = {} + kvp.setdefault("authentication", {"type": "test"}) + test_side_effect = [ + build_mock_response(mocker, 200, generate_device_show(**kvp)) + ] + service_client.side_effect = test_side_effect + return service_client + + @pytest.mark.parametrize( + "req, exp", [("primary", CLIError), ("secondary", CLIError), ("swap", CLIError)] + ) + def test_device_key_regenerate_invalid_args( + self, fixture_cmd, serviceclient_invalid_args, req, exp + ): + with pytest.raises(exp): + subject.iot_device_key_regenerate( + fixture_cmd, mock_target["entity"], device_id, req + ) + + @pytest.mark.parametrize("req", ["primary", "secondary", "swap"]) + def test_device_key_regenerate_error(self, serviceclient_generic_error, req): + with pytest.raises(CLIError): + subject.iot_device_key_regenerate( + fixture_cmd, mock_target["entity"], device_id, req + ) + + class TestDeviceDelete: - @pytest.fixture(params=[(200, 204)]) + @pytest.fixture(params=[204]) def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - etag = str(uuid4()) - service_client.expected_etag = etag test_side_effect = [ - build_mock_response(mocker, request.param[0], {"etag": etag}), - build_mock_response(mocker, request.param[1]), + build_mock_response(mocker, request.param), ] service_client.side_effect = test_side_effect return service_client - def test_device_delete(self, serviceclient): - subject.iot_device_delete(fixture_cmd, device_id, mock_target["entity"]) + @pytest.mark.parametrize("target_etag", [generate_generic_id(), None]) + def test_device_delete(self, serviceclient, target_etag): + subject.iot_device_delete( + cmd=fixture_cmd, + device_id=device_id, + hub_name=mock_target["entity"], + etag=target_etag, + ) args = serviceclient.call_args url = args[0][0].url assert "{}/devices/{}?".format(mock_target["entity"], device_id) in url assert args[0][0].method == "DELETE" headers = args[0][0].headers - assert headers["If-Match"] == '"{}"'.format(serviceclient.expected_etag) - - @pytest.mark.parametrize("exception", [CLIError]) - def test_device_delete_invalid_args( - self, serviceclient_generic_invalid_or_missing_etag, exception - ): - with pytest.raises(exception): - subject.iot_device_delete(fixture_cmd, device_id, mock_target["entity"]) + assert headers["If-Match"] == '"{}"'.format(target_etag if target_etag else "*") def test_device_delete_error(self, serviceclient_generic_error): with pytest.raises(CLIError): @@ -502,23 +777,25 @@ def test_device_delete_error(self, serviceclient_generic_error): # Starting PoC for improving/simplyfing unit tests class TestDeviceShow: - def test_device_show(self, fixture_cmd2, mocked_response, fixture_ghcs): + def test_device_show(self, fixture_cmd, mocked_response, fixture_ghcs): device_id = generate_generic_id() mocked_response.add( method=responses.GET, - url=re.compile("https://{}/devices/{}".format(mock_target["entity"], device_id)), + url=re.compile( + "https://{}/devices/{}".format(mock_target["entity"], device_id) + ), body=json.dumps(generate_device_show(deviceId=device_id)), status=200, - content_type='application/json', - match_querystring=False + content_type="application/json", + match_querystring=False, ) - result = subject.iot_device_show(fixture_cmd2, device_id, mock_target["entity"]) + result = subject.iot_device_show(fixture_cmd, device_id, mock_target["entity"]) assert result["deviceId"] == device_id - def test_device_show_error(self, fixture_cmd2, service_client_generic_errors): + def test_device_show_error(self, fixture_cmd, service_client_generic_errors): with pytest.raises(CLIError): - subject.iot_device_show(fixture_cmd2, device_id, mock_target["entity"]) + subject.iot_device_show(fixture_cmd, device_id, mock_target["entity"]) class TestDeviceList: @@ -536,15 +813,15 @@ 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 yield mocked_response @pytest.mark.parametrize("top, edge", [(10, True), (1000, False)]) - def test_device_list(self, fixture_cmd2, service_client, top, edge): - result = subject.iot_device_list(fixture_cmd2, mock_target["entity"], top, edge) + def test_device_list(self, fixture_cmd, service_client, top, edge): + result = subject.iot_device_list(fixture_cmd, mock_target["entity"], top, edge) list_request = service_client.calls[0].request body = json.loads(list_request.body) @@ -563,14 +840,14 @@ def test_device_list(self, fixture_cmd2, service_client, top, edge): assert headers["x-ms-max-item-count"] == str(top) @pytest.mark.parametrize("top", [-2, 0]) - def test_device_list_invalid_args(self, fixture_cmd2, top): + def test_device_list_invalid_args(self, fixture_cmd, top): with pytest.raises(CLIError): - subject.iot_device_list(fixture_cmd2, mock_target["entity"], top) + subject.iot_device_list(fixture_cmd, mock_target["entity"], top) - def test_device_list_error(self, fixture_cmd2, service_client_generic_errors): + def test_device_list_error(self, fixture_cmd, service_client_generic_errors): service_client_generic_errors.assert_all_requests_are_fired = False with pytest.raises(CLIError): - subject.iot_device_list(fixture_cmd2, mock_target["entity"]) + subject.iot_device_list(fixture_cmd, mock_target["entity"]) def generate_module_create_req( @@ -675,7 +952,8 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): authentication={ "symmetricKey": {"primaryKey": "", "secondaryKey": ""}, "type": "sas", - } + }, + etag=generate_generic_id(), ) ), ( @@ -726,8 +1004,10 @@ def test_device_module_update(self, serviceclient, req): elif req["authentication"]["type"] == "selfSigned": assert body["authentication"]["x509Thumbprint"]["primaryThumbprint"] assert body["authentication"]["x509Thumbprint"]["secondaryThumbprint"] + + target_etag = req.get("etag") headers = args[0][0].headers - assert headers["If-Match"] == '"{}"'.format(req["etag"]) + assert headers["If-Match"] == '"{}"'.format(target_etag if target_etag else "*") @pytest.mark.parametrize( "req, exp", @@ -748,7 +1028,6 @@ def test_device_module_update(self, serviceclient, req): generate_device_module_show(authentication={"type": "doesnotexist"}), CLIError, ), - (generate_device_module_show(etag=None), CLIError), ], ) def test_device_module_update_invalid_args(self, serviceclient, req, exp): @@ -774,21 +1053,23 @@ def test_device_module_update_error(self, serviceclient_generic_error, req): class TestDeviceModuleDelete: - @pytest.fixture(params=[(200, 204)]) + @pytest.fixture(params=[204]) def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - etag = str(uuid4()) - service_client.expected_etag = etag test_side_effect = [ - build_mock_response(mocker, request.param[0], {"etag": etag}), - build_mock_response(mocker, request.param[1]), + build_mock_response(mocker, request.param), ] service_client.side_effect = test_side_effect return service_client - def test_device_module_delete(self, serviceclient): + @pytest.mark.parametrize("target_etag", [generate_generic_id(), None]) + def test_device_module_delete(self, serviceclient, target_etag): subject.iot_device_module_delete( - fixture_cmd, device_id, module_id=module_id, hub_name=mock_target["entity"] + fixture_cmd, + device_id, + module_id=module_id, + hub_name=mock_target["entity"], + etag=target_etag, ) args = serviceclient.call_args url = args[0][0].url @@ -797,19 +1078,7 @@ def test_device_module_delete(self, serviceclient): assert "devices/{}/modules/{}?".format(device_id, module_id) in url assert method == "DELETE" - assert headers["If-Match"] == '"{}"'.format(serviceclient.expected_etag) - - @pytest.mark.parametrize("exception", [CLIError]) - def test_device_module_invalid_args( - self, serviceclient_generic_invalid_or_missing_etag, exception - ): - with pytest.raises(exception): - subject.iot_device_module_delete( - fixture_cmd, - device_id, - module_id=module_id, - hub_name=mock_target["entity"], - ) + assert headers["If-Match"] == '"{}"'.format(target_etag if target_etag else "*") def test_device_module_delete_error(self, serviceclient_generic_error): with pytest.raises(CLIError): @@ -895,7 +1164,7 @@ def generate_device_twin_show(file_handle=False, **kvp): path = os.path.realpath("test_generic_twin.json") return path - payload = {"deviceId": device_id, "etag": "abcd"} + payload = {"deviceId": device_id} for k in kvp: payload[k] = kvp[k] return payload @@ -938,37 +1207,37 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): # Update does a GET/SHOW first @pytest.mark.parametrize( - "req", [(generate_device_twin_show(properties={"desired": {"key": "value"}}))] + "req", + [ + generate_device_twin_show( + properties={"desired": {"key": "value"}}, etag="abcd" + ), + generate_device_twin_show(properties={"desired": {"key": "value"}}), + ], ) def test_device_twin_update(self, serviceclient, req): subject.iot_device_twin_update( - fixture_cmd, req["deviceId"], hub_name=mock_target["entity"], parameters=req + fixture_cmd, + req["deviceId"], + hub_name=mock_target["entity"], + parameters=req, + etag=req.get("etag"), ) args = serviceclient.call_args body = json.loads(args[0][0].body) assert body == req assert "twins/{}".format(device_id) in args[0][0].url - @pytest.mark.parametrize( - "req, exp", [(generate_device_twin_show(etag=None), CLIError)] - ) - def test_device_twin_update_invalid_args(self, serviceclient, req, exp): - with pytest.raises(exp): - subject.iot_device_twin_update( - fixture_cmd, - req["deviceId"], - hub_name=mock_target["entity"], - parameters=req, - ) + target_etag = req.get("etag") + headers = args[0][0].headers + assert headers["If-Match"] == '"{}"'.format(target_etag if target_etag else "*") - @pytest.mark.parametrize( - "req", [(generate_device_twin_show()), (generate_device_twin_show(tags=""))] - ) + @pytest.mark.parametrize("req", [generate_device_twin_show()]) def test_device_twin_update_error(self, serviceclient_generic_error, req): with pytest.raises(CLIError): subject.iot_device_twin_update( fixture_cmd, - req["deviceId"], + device_id=req["deviceId"], hub_name=mock_target["entity"], parameters=req, ) @@ -985,23 +1254,32 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): # Replace does a GET/SHOW first @pytest.mark.parametrize( - "req, isfile", + "req, isfile, etag", [ - (generate_device_twin_show(moduleId=module_id), False), + ( + generate_device_twin_show(moduleId=module_id), + False, + generate_generic_id(), + ), ( generate_device_twin_show( moduleId=module_id, properties={"desired": {"key": "value"}} ), False, + None, ), - (generate_device_twin_show(file_handle=True), True), + (generate_device_twin_show(file_handle=True), True, None), ], ) - def test_device_twin_replace(self, serviceclient, req, isfile): + def test_device_twin_replace(self, serviceclient, req, isfile, etag): if not isfile: req = json.dumps(req) subject.iot_device_twin_replace( - fixture_cmd, device_id, hub_name=mock_target["entity"], target_json=req + cmd=fixture_cmd, + device_id=device_id, + hub_name=mock_target["entity"], + target_json=req, + etag=etag, ) args = serviceclient.call_args body = json.loads(args[0][0].body) @@ -1013,22 +1291,8 @@ def test_device_twin_replace(self, serviceclient, req, isfile): assert "{}/twins/{}?".format(mock_target["entity"], device_id) in args[0][0].url assert args[0][0].method == "PUT" - @pytest.mark.parametrize( - "req, exp", - [ - (generate_device_twin_show(etag=None), CLIError), - ({"invalid": "args"}, CLIError) - ], - ) - def test_device_twin_replace_invalid_args(self, serviceclient, req, exp): - with pytest.raises(exp): - serviceclient.return_value = build_mock_response(status_code=200, payload=req) - subject.iot_device_twin_replace( - fixture_cmd, - device_id, - hub_name=mock_target["entity"], - target_json=json.dumps(req), - ) + headers = args[0][0].headers + assert headers["If-Match"] == '"{}"'.format(etag if etag else "*") @pytest.mark.parametrize("req", [(generate_device_twin_show(moduleId=module_id))]) def test_device_twin_replace_error(self, serviceclient_generic_error, req): @@ -1063,7 +1327,7 @@ def test_device_module_twin_show_error(self, serviceclient_generic_error): with pytest.raises(CLIError): subject.iot_device_module_twin_show( fixture_cmd, - device_id, + device_id=device_id, hub_name=mock_target["entity"], module_id=module_id, ) @@ -1082,11 +1346,18 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): @pytest.mark.parametrize( "req", [ + ( + generate_device_twin_show( + moduleId=module_id, + properties={"desired": {"key": "value"}}, + etag=generate_generic_id(), + ) + ), ( generate_device_twin_show( moduleId=module_id, properties={"desired": {"key": "value"}} ) - ) + ), ], ) def test_device_module_twin_update(self, serviceclient, req): @@ -1096,6 +1367,7 @@ def test_device_module_twin_update(self, serviceclient, req): hub_name=mock_target["entity"], module_id=module_id, parameters=req, + etag=req.get("etag"), ) args = serviceclient.call_args body = json.loads(args[0][0].body) @@ -1104,36 +1376,16 @@ def test_device_module_twin_update(self, serviceclient, req): "twins/{}/modules/{}?".format(req["deviceId"], module_id) in args[0][0].url ) - @pytest.mark.parametrize( - "req, exp", - [ - ( - generate_device_twin_show( - moduleId=module_id, - properties={"desired": {"key": "value"}}, - etag=None, - ), - CLIError, - ), - (generate_device_twin_show(moduleId=module_id), CLIError), - ], - ) - def test_device_module_twin_update_invalid_args(self, serviceclient, req, exp): - with pytest.raises(exp): - subject.iot_device_module_twin_update( - fixture_cmd, - req["deviceId"], - hub_name=mock_target["entity"], - module_id=module_id, - parameters=req, - ) + target_etag = req.get("etag") + headers = args[0][0].headers + assert headers["If-Match"] == '"{}"'.format(target_etag if target_etag else "*") @pytest.mark.parametrize("req", [(generate_device_twin_show(moduleId=module_id))]) def test_device_module_twin_update_error(self, serviceclient_generic_error, req): with pytest.raises(CLIError): subject.iot_device_module_twin_update( fixture_cmd, - req["deviceId"], + device_id=req["deviceId"], hub_name=mock_target["entity"], module_id=module_id, parameters=req, @@ -1151,19 +1403,24 @@ def serviceclient(self, mocker, fixture_ghcs, fixture_sas, request): # Replace does a GET/SHOW first @pytest.mark.parametrize( - "req, isfile", + "req, isfile, etag", [ - (generate_device_twin_show(moduleId=module_id), False), + ( + generate_device_twin_show(moduleId=module_id), + False, + generate_generic_id(), + ), ( generate_device_twin_show( moduleId=module_id, properties={"desired": {"key": "value"}} ), False, + None, ), - (generate_device_twin_show(file_handle=True), True), + (generate_device_twin_show(file_handle=True), True, None), ], ) - def test_device_module_twin_replace(self, serviceclient, req, isfile): + def test_device_module_twin_replace(self, serviceclient, req, isfile, etag): if not isfile: req = json.dumps(req) subject.iot_device_module_twin_replace( @@ -1172,6 +1429,7 @@ def test_device_module_twin_replace(self, serviceclient, req, isfile): hub_name=mock_target["entity"], module_id=module_id, target_json=req, + etag=etag, ) args = serviceclient.call_args body = json.loads(args[0][0].body) @@ -1183,26 +1441,8 @@ def test_device_module_twin_replace(self, serviceclient, req, isfile): assert "twins/{}/modules/{}?".format(device_id, module_id) in args[0][0].url assert args[0][0].method == "PUT" - @pytest.mark.parametrize( - "req, exp", - [ - (generate_device_twin_show(moduleId=module_id, etag=None), CLIError), - ({"invalid": "payload"}, CLIError), - ], - ) - def test_device_module_twin_replace_invalid_args(self, mocker, serviceclient, req, exp): - with pytest.raises(exp): - serviceclient.return_value = build_mock_response( - mocker, 200, payload=req - ) - - subject.iot_device_module_twin_replace( - fixture_cmd, - device_id, - hub_name=mock_target["entity"], - module_id=module_id, - target_json=json.dumps(req), - ) + headers = args[0][0].headers + assert headers["If-Match"] == '"{}"'.format(etag if etag else "*") @pytest.mark.parametrize("req", [(generate_device_twin_show(moduleId=module_id))]) def test_device_module_twin_replace_error(self, serviceclient_generic_error, req): @@ -1256,9 +1496,7 @@ def test_query_basic(self, serviceclient, query, servresult, servtotal, top): continuation[-1] = None serviceclient.return_value = build_mock_response( - status_code=200, - payload=servresult, - headers_get_side_effect=continuation + status_code=200, payload=servresult, headers_get_side_effect=continuation ) result = subject.iot_query( @@ -1340,7 +1578,8 @@ def test_device_method(self, serviceclient, methodbody): if methodbody: assert body["payload"] == json.loads(payload) else: - assert body.get("payload") is None + # We must ensure null is passed for payload. + assert body["payload"] is None assert body["responseTimeoutInSeconds"] == timeout assert body["connectTimeoutInSeconds"] == timeout @@ -1420,7 +1659,8 @@ def test_device_module_method(self, serviceclient, methodbody): if methodbody: assert body["payload"] == json.loads(payload) else: - assert body.get("payload") is None + # We must ensure null is passed for payload. + assert body["payload"] is None assert body["responseTimeoutInSeconds"] == timeout assert body["connectTimeoutInSeconds"] == timeout @@ -1474,167 +1714,10 @@ def test_device_method_error(self, serviceclient_generic_error): ) -hub_suffix = "awesome-azure.net" - - -# TODO: Refactor to leverage fixture pattern and reduce redundent params -class TestGetIoTHubConnString: - @pytest.mark.parametrize( - "hubcount, targethub, policy_name, rg_name, include_events, login, failure_reason, mgmt_sdk_ver", - [ - (5, "hub1", "iothubowner", str(uuid4()), False, None, None, "0.4"), - (0, "hub1", "iothubowner", None, False, True, None, "0.4"), - (5, "hub1", "iothubowner", str(uuid4()), False, None, None, None), - (0, "hub1", "iothubowner", None, False, True, None, None), - (1, "hub0", "iothubowner", None, True, None, None, None), - (10, "hub3", "custompolicy", "myrg", False, None, None, None), - (1, "hub0", "custompolicy", None, False, None, None, None), - (3, "hub4", "iothubowner", None, False, None, "subscription", None), - (1, "hub1", "iothubowner", "myrg", False, None, "policy", None), - (1, "myhub", "iothubowner", "myrg", False, None, "resource", None), - ], - ) - def test_get_hub_conn_string( - self, - mocker, - hubcount, - targethub, - policy_name, - rg_name, - include_events, - login, - failure_reason, - mgmt_sdk_ver, - ): - from azext_iot.common._azure import get_iot_hub_connection_string - - def _build_hub(hub, name, rg): - hub.name = name - hub.properties.host_name = "{}.{}".format(name, hub_suffix) - - if mgmt_sdk_ver == "0.4": - hub.resourcegroup = rg - hub.additional_properties = None - else: - d = {} - d["resourcegroup"] = rg - hub.additional_properties.return_value = d - hub.additional_properties.get.return_value = rg - - return hub - - def _build_policy(policy, name): - policy.key_name = name - policy.primary_key = mock_target["primarykey"] - policy.secondary_key = mock_target["secondarykey"] - return policy - - def _build_event(hub): - hub.properties.event_hub_endpoints = {"events": mocker.Mock()} - hub.properties.event_hub_endpoints["events"].endpoint = "sb://" + str( - uuid4() - ) - hub.properties.event_hub_endpoints["events"].partition_count = "2" - hub.properties.event_hub_endpoints["events"].path = hub_entity - hub.properties.event_hub_endpoints["events"].partition_ids = ["0", "1"] - return hub - - cmd = mocker.Mock(name="cmd") - ihsf = mocker.patch(path_iot_hub_service_factory) - client = mocker.Mock(name="hubclient") - - if not rg_name: - hub_list = [] - for i in range(0, hubcount): - hub_list.append( - _build_hub(mocker.Mock(), "hub{}".format(i), str(uuid4())) - ) - client.list_by_subscription.return_value = hub_list - else: - client.get.return_value = _build_hub(mocker.Mock(), targethub, rg_name) - - if failure_reason == "resource": - client.get.side_effect = ValueError - elif failure_reason == "policy": - client.get_keys_for_key_name.side_effect = ValueError - else: - client.get_keys_for_key_name.return_value = _build_policy( - mocker.Mock(), policy_name - ) - - if include_events: - _build_event(hub_list[0]) - - client.config.subscription_id = mock_target["subscription"] - ihsf.return_value = client - - if not failure_reason: - if not login: - result = get_iot_hub_connection_string( - cmd, targethub, rg_name, policy_name, include_events=include_events - ) - - expecting_hub = "{}.{}".format(targethub, hub_suffix) - assert result["entity"] == expecting_hub - assert result["policy"] == policy_name - assert result["subscription"] == mock_target["subscription"] - assert result["cs"] == generic_cs_template.format( - expecting_hub, policy_name, mock_target["primarykey"] - ) - - if rg_name: - client.get.assert_called_with(rg_name, targethub) - assert result["resourcegroup"] == rg_name - else: - assert result["resourcegroup"] - - client.get_keys_for_key_name.assert_called_with( - mocker.ANY, targethub, policy_name - ) - - if include_events: - assert result["events"]["endpoint"] - assert result["events"]["partition_count"] - assert result["events"]["path"] - assert result["events"]["partition_ids"] - else: - hub = str(uuid4()) - policy = str(uuid4()) - key = str(uuid4()) - cs = generate_cs(hub, policy, key) - lower_cs = generate_cs(hub, policy, key, lower_case=True) - - result = get_iot_hub_connection_string( - cmd, targethub, rg_name, policy_name, login=cs - ) - result_lower = get_iot_hub_connection_string( - cmd, targethub, rg_name, policy_name, login=lower_cs - ) - - assert result["entity"] == hub - assert result["policy"] == policy - assert result["primarykey"] == key - assert not result.get("resourcegroup") - assert not result.get("subscription") - assert result["cs"] == generic_cs_template.format(hub, policy, key) - assert result_lower["entity"] == hub - assert result_lower["policy"] == policy - assert result_lower["primarykey"] == key - assert not result_lower.get("resourcegroup") - assert not result_lower.get("subscription") - assert ( - result_lower["cs"] - == generic_cs_template.format(hub, policy, key).lower() - ) - else: - with pytest.raises(CLIError): - get_iot_hub_connection_string(client, targethub, rg_name, policy_name) - - class TestCloudToDeviceMessaging: @pytest.fixture(params=["full", "min"]) def c2d_receive_scenario(self, fixture_ghcs, mocked_response, request): - from . generators import create_c2d_receive_response + from azext_iot.tests.generators import create_c2d_receive_response if request.param == "full": payload = create_c2d_receive_response() @@ -1643,15 +1726,117 @@ def c2d_receive_scenario(self, fixture_ghcs, mocked_response, request): mocked_response.add( method=responses.GET, - url=re.compile("https://{}/devices/(.+)/messages/deviceBound".format(mock_target["entity"])), + url=re.compile( + "https://{}/devices/{}/messages/deviceBound".format( + mock_target["entity"], device_id + ) + ), body=payload["body"], headers=payload["headers"], status=200, - match_querystring=False + match_querystring=False, ) yield (mocked_response, payload) + @pytest.fixture() + def c2d_receive_ack_scenario(self, fixture_ghcs, mocked_response): + from azext_iot.tests.generators import create_c2d_receive_response + payload = create_c2d_receive_response() + mocked_response.add( + method=responses.GET, + url=( + "https://{}/devices/{}/messages/deviceBound".format( + mock_target["entity"], device_id + ) + ), + body=payload["body"], + headers=payload["headers"], + status=200, + match_querystring=False, + ) + + eTag = payload["headers"]["etag"].strip('"') + # complete / reject + mocked_response.add( + method=responses.DELETE, + url="https://{}/devices/{}/messages/deviceBound/{}".format( + mock_target["entity"], device_id, eTag + ), + body="", + headers=payload["headers"], + status=204, + match_querystring=False, + ) + + # abandon + mocked_response.add( + method=responses.POST, + url="https://{}/devices/{}/messages/deviceBound/{}/abandon".format( + mock_target["entity"], device_id, eTag + ), + body="", + headers=payload["headers"], + status=204, + match_querystring=False, + ) + + yield (mocked_response, payload) + + @pytest.fixture() + def c2d_ack_complete_scenario(self, fixture_ghcs, mocked_response): + mocked_response.add( + method=responses.DELETE, + url="https://{}/devices/{}/messages/deviceBound/{}".format( + mock_target["entity"], device_id, message_etag + ), + body="", + status=204, + match_querystring=False, + ) + yield mocked_response + + @pytest.fixture() + def c2d_ack_reject_scenario(self, fixture_ghcs, mocked_response): + mocked_response.add( + method=responses.DELETE, + url="https://{}/devices/{}/messages/deviceBound/{}?reject=".format( + mock_target["entity"], device_id, message_etag + ), + body="", + status=204, + match_querystring=False, + ) + yield mocked_response + + @pytest.fixture() + def c2d_ack_abandon_scenario(self, fixture_ghcs, mocked_response): + mocked_response.add( + method=responses.POST, + url="https://{}/devices/{}/messages/deviceBound/{}/abandon".format( + mock_target["entity"], device_id, message_etag + ), + body="", + status=204, + match_querystring=False, + ) + yield mocked_response + + @pytest.fixture() + def c2d_purge_scenario(self, fixture_ghcs, mocked_response): + import json + + mocked_response.add( + method=responses.DELETE, + url="https://{}/devices/{}/commands".format( + mock_target["entity"], device_id + ), + body=json.dumps(c2d_purge_response), + content_type="application/json", + status=200, + ) + yield mocked_response + def test_c2d_receive(self, c2d_receive_scenario): service_client = c2d_receive_scenario[0] sample_c2d_receive = c2d_receive_scenario[1] @@ -1672,84 +1857,226 @@ def test_c2d_receive(self, c2d_receive_scenario): ) assert headers["IotHub-MessageLockTimeout"] == str(timeout) - assert result["properties"]["system"]["iothub-ack"] == sample_c2d_receive["headers"]["iothub-ack"] - assert result["properties"]["system"]["iothub-correlationid"] == sample_c2d_receive["headers"]["iothub-correlationid"] - assert result["properties"]["system"]["iothub-deliverycount"] == sample_c2d_receive["headers"]["iothub-deliverycount"] - assert result["properties"]["system"]["iothub-expiry"] == sample_c2d_receive["headers"]["iothub-expiry"] - assert result["properties"]["system"]["iothub-enqueuedtime"] == sample_c2d_receive["headers"]["iothub-enqueuedtime"] - assert result["properties"]["system"]["iothub-messageid"] == sample_c2d_receive["headers"]["iothub-messageid"] - assert result["properties"]["system"]["iothub-sequencenumber"] == sample_c2d_receive["headers"]["iothub-sequencenumber"] - assert result["properties"]["system"]["iothub-userid"] == sample_c2d_receive["headers"]["iothub-userid"] - assert result["properties"]["system"]["iothub-to"] == sample_c2d_receive["headers"]["iothub-to"] + assert ( + result["properties"]["system"]["iothub-ack"] + == sample_c2d_receive["headers"]["iothub-ack"] + ) + assert ( + result["properties"]["system"]["iothub-correlationid"] + == sample_c2d_receive["headers"]["iothub-correlationid"] + ) + assert ( + result["properties"]["system"]["iothub-deliverycount"] + == sample_c2d_receive["headers"]["iothub-deliverycount"] + ) + assert ( + result["properties"]["system"]["iothub-expiry"] + == sample_c2d_receive["headers"]["iothub-expiry"] + ) + assert ( + result["properties"]["system"]["iothub-enqueuedtime"] + == sample_c2d_receive["headers"]["iothub-enqueuedtime"] + ) + assert ( + result["properties"]["system"]["iothub-messageid"] + == sample_c2d_receive["headers"]["iothub-messageid"] + ) + assert ( + result["properties"]["system"]["iothub-sequencenumber"] + == sample_c2d_receive["headers"]["iothub-sequencenumber"] + ) + assert ( + result["properties"]["system"]["iothub-userid"] + == sample_c2d_receive["headers"]["iothub-userid"] + ) + assert ( + result["properties"]["system"]["iothub-to"] + == sample_c2d_receive["headers"]["iothub-to"] + ) assert result["etag"] == sample_c2d_receive["headers"]["etag"].strip('"') if sample_c2d_receive.get("body"): assert result["data"] == sample_c2d_receive["body"] - def test_c2d_complete(self, fixture_service_client_generic): - service_client = fixture_service_client_generic + def test_c2d_receive_ack(self, c2d_receive_ack_scenario): + service_client = c2d_receive_ack_scenario[0] + sample_c2d_receive = c2d_receive_ack_scenario[1] + + timeout = 120 + for ack in ["complete", "reject", "abandon"]: + result = subject.iot_c2d_message_receive( + fixture_cmd, + device_id, + mock_target["entity"], + timeout, + complete=(ack == "complete"), + reject=(ack == "reject"), + abandon=(ack == "abandon"), + ) + retrieve, action = service_client.calls[0], service_client.calls[1] + + # retrieve call + request = retrieve.request + url = request.url + headers = request.headers + assert ( + "{}/devices/{}/messages/deviceBound?".format( + mock_target["entity"], device_id + ) + in url + ) + assert headers["IotHub-MessageLockTimeout"] == str(timeout) + + assert ( + result["properties"]["system"]["iothub-ack"] + == sample_c2d_receive["headers"]["iothub-ack"] + ) + assert ( + result["properties"]["system"]["iothub-correlationid"] + == sample_c2d_receive["headers"]["iothub-correlationid"] + ) + assert ( + result["properties"]["system"]["iothub-deliverycount"] + == sample_c2d_receive["headers"]["iothub-deliverycount"] + ) + assert ( + result["properties"]["system"]["iothub-expiry"] + == sample_c2d_receive["headers"]["iothub-expiry"] + ) + assert ( + result["properties"]["system"]["iothub-enqueuedtime"] + == sample_c2d_receive["headers"]["iothub-enqueuedtime"] + ) + assert ( + result["properties"]["system"]["iothub-messageid"] + == sample_c2d_receive["headers"]["iothub-messageid"] + ) + assert ( + result["properties"]["system"]["iothub-sequencenumber"] + == sample_c2d_receive["headers"]["iothub-sequencenumber"] + ) + assert ( + result["properties"]["system"]["iothub-userid"] + == sample_c2d_receive["headers"]["iothub-userid"] + ) + assert ( + result["properties"]["system"]["iothub-to"] + == sample_c2d_receive["headers"]["iothub-to"] + ) + + assert result["etag"] == sample_c2d_receive["headers"]["etag"].strip('"') + + if sample_c2d_receive.get("body"): + assert result["data"] == sample_c2d_receive["body"] + + # ack call - complete / reject / abandon + request = action.request + url = request.url + headers = request.headers + method = request.method + + # all ack calls go to the same base URL + assert ( + "{}/devices/{}/messages/deviceBound/".format( + mock_target["entity"], device_id + ) + in url + ) + # check complete + if ack == "complete": + assert method == "DELETE" + # check reject + if ack == "reject": + assert method == "DELETE" + assert "reject=" in url + # check abandon + if ack == "abandon": + assert method == "POST" + assert "/abandon" in url + service_client.calls.reset() + + def test_c2d_receive_ack_errors(self): + with pytest.raises(CLIError): + subject.iot_c2d_message_receive( + fixture_cmd, + device_id, + hub_name=mock_target["entity"], + abandon=True, + complete=True, + ) + subject.iot_c2d_message_receive( + fixture_cmd, + device_id, + hub_name=mock_target["entity"], + abandon=False, + complete=True, + reject=True, + ) + subject.iot_c2d_message_receive( + fixture_cmd, + device_id, + hub_name=mock_target["entity"], + complete=True, + reject=True, + ) - etag = "3k28zb44-0d00-4ddd-ade3-6110eb94c476" - service_client.return_value.status_code = 204 + def test_c2d_complete(self, c2d_ack_complete_scenario): + service_client = c2d_ack_complete_scenario result = subject.iot_c2d_message_complete( - fixture_cmd, device_id, etag, hub_name=mock_target["entity"] + fixture_cmd, device_id, message_etag, hub_name=mock_target["entity"] ) - args = service_client.call_args - url = args[0][0].url - method = args[0][0].method + request = service_client.calls[0].request + url = request.url + method = request.method assert result is None assert method == "DELETE" assert ( "{}/devices/{}/messages/deviceBound/{}?".format( - mock_target["entity"], device_id, etag + mock_target["entity"], device_id, message_etag ) in url ) - def test_c2d_reject(self, fixture_service_client_generic): - service_client = fixture_service_client_generic + def test_c2d_reject(self, c2d_ack_reject_scenario): + service_client = c2d_ack_reject_scenario - etag = "3k28zb44-0d00-4ddd-ade3-6110eb94c476" - service_client.return_value.status_code = 204 result = subject.iot_c2d_message_reject( - fixture_cmd, device_id, etag, hub_name=mock_target["entity"] + fixture_cmd, device_id, message_etag, hub_name=mock_target["entity"] ) - args = service_client.call_args - url = args[0][0].url - method = args[0][0].method + request = service_client.calls[0].request + url = request.url + method = request.method assert result is None assert method == "DELETE" assert ( "{}/devices/{}/messages/deviceBound/{}?".format( - mock_target["entity"], device_id, etag + mock_target["entity"], device_id, message_etag ) in url ) assert "reject=" in url - def test_c2d_abandon(self, fixture_service_client_generic): - service_client = fixture_service_client_generic + def test_c2d_abandon(self, c2d_ack_abandon_scenario): + service_client = c2d_ack_abandon_scenario - etag = "3k28zb44-0d00-4ddd-ade3-6110eb94c476" - service_client.return_value.status_code = 204 result = subject.iot_c2d_message_abandon( - fixture_cmd, device_id, etag, hub_name=mock_target["entity"] + fixture_cmd, device_id, message_etag, hub_name=mock_target["entity"] ) - args = service_client.call_args - url = args[0][0].url - method = args[0][0].method + request = service_client.calls[0].request + url = request.url + method = request.method assert result is None assert method == "POST" assert ( "{}/devices/{}/messages/deviceBound/{}/abandon?".format( - mock_target["entity"], device_id, etag + mock_target["entity"], device_id, message_etag ) in url ) @@ -1769,6 +2096,22 @@ def test_c2d_receive_and_ack_errors(self, serviceclient_generic_error): fixture_cmd, device_id, hub_name=mock_target["entity"], etag="" ) + def test_c2d_message_purge(self, c2d_purge_scenario): + result = subject.iot_c2d_message_purge(fixture_cmd, device_id) + request = c2d_purge_scenario.calls[0].request + url = request.url + method = request.method + + assert method == "DELETE" + assert ( + "https://{}/devices/{}/commands".format(mock_target["entity"], device_id) + in url + ) + assert result + assert result.total_messages_purged == 3 + assert result.device_id == device_id + assert not result.module_id + class TestSasTokenAuth: def test_generate_sas_token(self): @@ -2020,7 +2363,6 @@ def test_monitor_events_entrypoint( "repair", ] - assert "pnp_context" not in monitor_events_args assert "interface" not in monitor_events_args for attribute in attribute_set: @@ -2046,6 +2388,7 @@ def generate_parent_device(**kvp): "deviceId": device_id, "status": "disabled", "deviceScope": "ms-azure-iot-edge://{}-1234".format(device_id), + "parentScopes": ["ms-azure-iot-edge://{}-5678".format(device_id)], } for k in kvp: if payload.get(k): @@ -2072,13 +2415,13 @@ class TestEdgeOffline: @pytest.fixture(params=[(200, 200)]) def sc_getparent(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} - nonedge_kvp.setdefault( - "deviceScope", generate_parent_device().get("deviceScope") + child_kvp = {} + child_kvp.setdefault( + "parentScopes", [generate_parent_device().get("deviceScope")] ) test_side_effect = [ build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), build_mock_response(mocker, request.param[0], generate_parent_device()), ] @@ -2098,14 +2441,14 @@ def test_device_get_parent(self, sc_getparent): @pytest.fixture(params=[(200, 0), (200, 1)]) def sc_invalid_args_getparent(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} + child_kvp = {} if request.param[1] == 0: - nonedge_kvp.setdefault("capabilities", {"iotEdge": True}) + child_kvp.setdefault("parentScopes", []) if request.param[1] == 1: - nonedge_kvp.setdefault("deviceId", "") + child_kvp.setdefault("deviceId", "") test_side_effect = [ build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ) ] service_client.side_effect = test_side_effect @@ -2124,13 +2467,13 @@ def test_device_getparent_error(self, serviceclient_generic_error): @pytest.fixture(params=[(200, 0), (200, 1)]) def sc_setparent(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} + child_kvp = {} if request.param[1] == 1: - nonedge_kvp.setdefault("deviceScope", "abcd") + child_kvp.setdefault("parentScopes", ["abcd"]) test_side_effect = [ build_mock_response(mocker, request.param[0], generate_parent_device()), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), build_mock_response(mocker, request.param[0], {}), ] @@ -2149,23 +2492,21 @@ def test_device_set_parent(self, sc_setparent): assert body["deviceId"] == child_device_id assert body["deviceScope"] == generate_parent_device().get("deviceScope") - @pytest.fixture(params=[(200, 0), (200, 1), (200, 2)]) + @pytest.fixture(params=[(200, 0), (200, 1)]) def sc_invalid_args_setparent(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - edge_kvp = {} - nonedge_kvp = {} + parent_kvp = {} + child_kvp = {} if request.param[1] == 0: - edge_kvp.setdefault("capabilities", {"iotEdge": False}) + parent_kvp.setdefault("capabilities", {"iotEdge": False}) if request.param[1] == 1: - nonedge_kvp.setdefault("capabilities", {"iotEdge": True}) - if request.param[1] == 2: - nonedge_kvp.setdefault("deviceScope", "abcd") + child_kvp.setdefault("parentScopes", ["abcd"]) test_side_effect = [ build_mock_response( - mocker, request.param[0], generate_parent_device(**edge_kvp) + mocker, request.param[0], generate_parent_device(**parent_kvp) ), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), ] service_client.side_effect = test_side_effect @@ -2198,12 +2539,12 @@ def test_device_setparent_error(self, sc_setparent_error): @pytest.fixture(params=[(200, 200)]) def sc_invalid_etag_setparent(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} - nonedge_kvp.setdefault("etag", None) + child_kvp = {} + child_kvp.setdefault("etag", None) test_side_effect = [ build_mock_response(mocker, request.param[0], generate_parent_device()), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), ] service_client.side_effect = test_side_effect @@ -2217,16 +2558,18 @@ def test_device_setparent_invalid_etag(self, sc_invalid_etag_setparent, exp): ) # add-children - @pytest.fixture(params=[(200, 0), (200, 1)]) + @pytest.fixture(params=[(200, 0), (200, 1), (200, 2)]) def sc_addchildren(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} + child_kvp = {} + if request.param[1] == 1: + child_kvp.setdefault("parentScopes", ["abcd"]) if request.param[1] == 1: - nonedge_kvp.setdefault("deviceScope", "abcd") + child_kvp.setdefault("capabilities", {"iotEdge": True}) test_side_effect = [ build_mock_response(mocker, request.param[0], generate_parent_device()), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), build_mock_response(mocker, request.param[0], {}), ] @@ -2243,25 +2586,25 @@ def test_device_children_add(self, sc_addchildren): assert "{}/devices/{}?".format(mock_target["entity"], child_device_id) in url assert args[0][0].method == "PUT" assert body["deviceId"] == child_device_id - assert body["deviceScope"] == generate_parent_device().get("deviceScope") + assert body["deviceScope"] == generate_parent_device().get( + "deviceScope" + ) or body["parentScopes"] == [generate_parent_device().get("deviceScope")] - @pytest.fixture(params=[(200, 0), (200, 1), (200, 2)]) + @pytest.fixture(params=[(200, 0), (200, 1)]) def sc_invalid_args_addchildren(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - edge_kvp = {} - nonedge_kvp = {} + parent_kvp = {} + child_kvp = {} if request.param[1] == 0: - edge_kvp.setdefault("capabilities", {"iotEdge": False}) + parent_kvp.setdefault("capabilities", {"iotEdge": False}) if request.param[1] == 1: - nonedge_kvp.setdefault("capabilities", {"iotEdge": True}) - if request.param[1] == 2: - nonedge_kvp.setdefault("deviceScope", "abcd") + child_kvp.setdefault("parentScopes", ["abcd"]) test_side_effect = [ build_mock_response( - mocker, request.param[0], generate_parent_device(**edge_kvp) + mocker, request.param[0], generate_parent_device(**parent_kvp) ), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), ] service_client.side_effect = test_side_effect @@ -2294,12 +2637,12 @@ def test_device_addchildren_error(self, sc_addchildren_error): @pytest.fixture(params=[(200, 200)]) def sc_invalid_etag_addchildren(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} - nonedge_kvp.setdefault("etag", None) + child_kvp = {} + child_kvp.setdefault("etag", None) test_side_effect = [ build_mock_response(mocker, request.param[0], generate_parent_device()), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), ] service_client.side_effect = test_side_effect @@ -2316,17 +2659,15 @@ def test_device_addchildren_invalid_etag(self, sc_invalid_etag_setparent, exp): @pytest.fixture def sc_listchildren(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} - nonedge_kvp.setdefault( - "deviceScope", generate_parent_device().get("deviceScope") + child_kvp = {} + child_kvp.setdefault( + "parentScopes", [generate_parent_device().get("deviceScope")] ) result = [] - result.append(generate_child_device(**nonedge_kvp)) + result.append(generate_child_device(**child_kvp)) test_side_effect = [ build_mock_response(mocker, 200, generate_parent_device()), - build_mock_response( - mocker, 200, result, {"x-ms-continuation": None} - ), + build_mock_response(mocker, 200, result, {"x-ms-continuation": None}), ] service_client.side_effect = test_side_effect return service_client @@ -2339,17 +2680,17 @@ def test_device_children_list(self, sc_listchildren): url = args[0][0].url assert "{}/devices/query?".format(mock_target["entity"]) in url assert args[0][0].method == "POST" - assert result == child_device_id + assert result == [child_device_id] @pytest.fixture(params=[(200, 0), (200, 1)]) def sc_invalid_args_listchildren(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - edge_kvp = {} + parent_kvp = {} if request.param[1] == 0: - edge_kvp.setdefault("capabilities", {"iotEdge": False}) + parent_kvp.setdefault("capabilities", {"iotEdge": False}) test_side_effect = [ build_mock_response( - mocker, request.param[0], generate_parent_device(**edge_kvp) + mocker, request.param[0], generate_parent_device(**parent_kvp) ), build_mock_response( mocker, request.param[0], [], {"x-ms-continuation": None} @@ -2358,13 +2699,6 @@ def sc_invalid_args_listchildren(self, mocker, fixture_ghcs, fixture_sas, reques service_client.side_effect = test_side_effect return service_client - @pytest.mark.parametrize("exp", [CLIError]) - def test_device_listchildren_invalid_args(self, sc_invalid_args_listchildren, exp): - with pytest.raises(exp): - subject.iot_device_children_list( - fixture_cmd, device_id, mock_target["entity"] - ) - def test_device_listchildren_error(self, serviceclient_generic_error): with pytest.raises(CLIError): subject.iot_device_children_list( @@ -2375,14 +2709,14 @@ def test_device_listchildren_error(self, serviceclient_generic_error): @pytest.fixture(params=[(200, 200)]) def sc_removechildrenlist(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} - nonedge_kvp.setdefault( - "deviceScope", generate_parent_device().get("deviceScope") + child_kvp = {} + child_kvp.setdefault( + "parentScopes", [generate_parent_device().get("deviceScope")] ) test_side_effect = [ build_mock_response(mocker, request.param[0], generate_parent_device()), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), build_mock_response(mocker, request.param[0], {}), ] @@ -2403,22 +2737,22 @@ def sc_invalid_args_removechildrenlist( self, mocker, fixture_ghcs, fixture_sas, request ): service_client = mocker.patch(path_service_client) - edge_kvp = {} - nonedge_kvp = {} + parent_kvp = {} + child_kvp = {} if request.param[1] == 0: - edge_kvp.setdefault("capabilities", {"iotEdge": False}) - if request.param[1] == 1: - nonedge_kvp.setdefault("capabilities", {"iotEdge": True}) + parent_kvp.setdefault("capabilities", {"iotEdge": False}) + if request.param[1] == 2: + child_kvp.setdefault("parentScopes", [""]) if request.param[1] == 2: - nonedge_kvp.setdefault("deviceScope", "") + child_kvp.setdefault("deviceScope", "") if request.param[1] == 3: - nonedge_kvp.setdefault("deviceScope", "other") + child_kvp.setdefault("deviceScope", "other") test_side_effect = [ build_mock_response( - mocker, request.param[0], generate_parent_device(**edge_kvp) + mocker, request.param[0], generate_parent_device(**parent_kvp) ), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), ] service_client.side_effect = test_side_effect @@ -2438,15 +2772,15 @@ def sc_invalid_etag_removechildrenlist( self, mocker, fixture_ghcs, fixture_sas, request ): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} - nonedge_kvp.setdefault( - "deviceScope", generate_parent_device().get("deviceScope") + child_kvp = {} + child_kvp.setdefault( + "parentScopes", [generate_parent_device().get("deviceScope")] ) - nonedge_kvp.setdefault("etag", None) + child_kvp.setdefault("etag", None) test_side_effect = [ build_mock_response(mocker, request.param[0], generate_parent_device()), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), build_mock_response(mocker, request.param[0], {}), ] @@ -2465,14 +2799,14 @@ def test_device_removechildrenlist_invalid_etag( @pytest.fixture(params=[(200, 400), (200, 401), (200, 500)]) def sc_removechildrenlist_error(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} - nonedge_kvp.setdefault( - "deviceScope", generate_parent_device().get("deviceScope") + child_kvp = {} + child_kvp.setdefault( + "parentScopes", [generate_parent_device().get("deviceScope")] ) test_side_effect = [ build_mock_response(mocker, request.param[0], generate_parent_device()), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), build_mock_response(mocker, request.param[1], {}), ] @@ -2488,19 +2822,19 @@ def test_device_removechildrenlist_error(self, sc_removechildrenlist_error): @pytest.fixture(params=[(200, 200)]) def sc_removechildrenall(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} - nonedge_kvp.setdefault( - "deviceScope", generate_parent_device().get("deviceScope") + child_kvp = {} + child_kvp.setdefault( + "parentScopes", [generate_parent_device().get("deviceScope")] ) result = [] - result.append(generate_child_device(**nonedge_kvp)) + result.append(generate_child_device(**child_kvp)) test_side_effect = [ build_mock_response(mocker, request.param[0], generate_parent_device()), build_mock_response( mocker, request.param[0], result, {"x-ms-continuation": None} ), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), build_mock_response(mocker, request.param[0], {}), ] @@ -2521,20 +2855,20 @@ def sc_invalid_args_removechildrenall( self, mocker, fixture_ghcs, fixture_sas, request ): service_client = mocker.patch(path_service_client) - edge_kvp = {} - nonedge_kvp = {} - nonedge_kvp.setdefault( - "deviceScope", generate_parent_device().get("deviceScope") + parent_kvp = {} + child_kvp = {} + child_kvp.setdefault( + "parentScopes", [generate_parent_device().get("deviceScope")] ) result = [] - result.append(generate_child_device(**nonedge_kvp)) + result.append(generate_child_device(**child_kvp)) if request.param[1] == 0: - edge_kvp.setdefault("capabilities", {"iotEdge": False}) + parent_kvp.setdefault("capabilities", {"iotEdge": False}) if request.param[1] == 1: result = [] test_side_effect = [ build_mock_response( - mocker, request.param[0], generate_parent_device(**edge_kvp) + mocker, request.param[0], generate_parent_device(**parent_kvp) ), build_mock_response( mocker, request.param[0], result, {"x-ms-continuation": None} @@ -2557,20 +2891,20 @@ def sc_invalid_etag_removechildrenall( self, mocker, fixture_ghcs, fixture_sas, request ): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} - nonedge_kvp.setdefault( - "deviceScope", generate_parent_device().get("deviceScope") + child_kvp = {} + child_kvp.setdefault( + "parentScopes", [generate_parent_device().get("deviceScope")] ) - nonedge_kvp.setdefault("etag", None) + child_kvp.setdefault("etag", None) result = [] - result.append(generate_child_device(**nonedge_kvp)) + result.append(generate_child_device(**child_kvp)) test_side_effect = [ build_mock_response(mocker, request.param[0], generate_parent_device()), build_mock_response( mocker, request.param[0], result, {"x-ms-continuation": None} ), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), build_mock_response(mocker, request.param[0], {}), ] @@ -2589,19 +2923,19 @@ def test_device_removechildrenall_invalid_etag( @pytest.fixture(params=[(200, 400), (200, 401), (200, 500)]) def sc_removechildrenall_error(self, mocker, fixture_ghcs, fixture_sas, request): service_client = mocker.patch(path_service_client) - nonedge_kvp = {} - nonedge_kvp.setdefault( - "deviceScope", generate_parent_device().get("deviceScope") + child_kvp = {} + child_kvp.setdefault( + "parentScopes", [generate_parent_device().get("deviceScope")] ) result = [] - result.append(generate_child_device(**nonedge_kvp)) + result.append(generate_child_device(**child_kvp)) test_side_effect = [ build_mock_response(mocker, request.param[0], generate_parent_device()), build_mock_response( mocker, request.param[0], result, {"x-ms-continuation": None} ), build_mock_response( - mocker, request.param[0], generate_child_device(**nonedge_kvp) + mocker, request.param[0], generate_child_device(**child_kvp) ), build_mock_response(mocker, request.param[1], {}), ] diff --git a/azext_iot/tests/test_iot_messaging_int.py b/azext_iot/tests/iothub/test_iot_messaging_int.py similarity index 83% rename from azext_iot/tests/test_iot_messaging_int.py rename to azext_iot/tests/iothub/test_iot_messaging_int.py index 99af5cffa..6b767631d 100644 --- a/azext_iot/tests/test_iot_messaging_int.py +++ b/azext_iot/tests/iothub/test_iot_messaging_int.py @@ -4,15 +4,13 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -import os import pytest import json from time import time from uuid import uuid4 -from . import IoTLiveScenarioTest, PREFIX_DEVICE - -# Temporary workaround. +from azext_iot.tests import IoTLiveScenarioTest, PREFIX_DEVICE +from azext_iot.tests.settings import DynamoSettings, ENV_SET_TEST_IOTHUB_BASIC from azext_iot.common.utility import ( validate_min_python_version, execute_onthread, @@ -20,25 +18,17 @@ validate_key_value_pairs ) -# Set these to the proper IoT Hub, IoT Hub Cstring and Resource Group for Live Integration Tests. -LIVE_HUB = os.environ.get("azext_iot_testhub") -LIVE_RG = os.environ.get("azext_iot_testrg") -LIVE_HUB_CS = os.environ.get("azext_iot_testhub_cs") +settings = DynamoSettings(ENV_SET_TEST_IOTHUB_BASIC) +LIVE_HUB = settings.env.azext_iot_testhub +LIVE_RG = settings.env.azext_iot_testrg LIVE_CONSUMER_GROUPS = ["test1", "test2", "test3"] -if not all([LIVE_HUB, LIVE_HUB_CS, LIVE_RG]): - raise ValueError( - "Set azext_iot_testhub, azext_iot_testhub_cs and azext_iot_testrg to run IoT Hub integration tests." - ) - - -# IoT Hub Messaging tests currently are run live due to non HTTP based interaction i.e. amqp, mqtt. class TestIoTHubMessaging(IoTLiveScenarioTest): def __init__(self, test_case): super(TestIoTHubMessaging, self).__init__( - test_case, LIVE_HUB, LIVE_RG, LIVE_HUB_CS + test_case, LIVE_HUB, LIVE_RG ) @pytest.mark.skipif( @@ -127,7 +117,7 @@ def test_uamqp_device_messaging(self): """iot device c2d-message send -d {} --login {} --data '{}' --cid {} --mid {} --ct {} --expiry {} --ce {} --ack positive --props {}""".format( device_ids[0], - LIVE_HUB_CS, + self.connection_string, "{c2d_json_send_data}", test_cid, test_mid, @@ -141,7 +131,7 @@ def test_uamqp_device_messaging(self): result = self.cmd( "iot device c2d-message receive -d {} --login {}".format( - device_ids[0], LIVE_HUB_CS + device_ids[0], self.connection_string ) ).get_output_in_json() @@ -166,7 +156,7 @@ def test_uamqp_device_messaging(self): self.cmd( "iot device c2d-message reject -d {} --etag {} --login {}".format( - device_ids[0], etag, LIVE_HUB_CS + device_ids[0], etag, self.connection_string ), checks=self.is_empty(), ) @@ -197,7 +187,7 @@ def test_uamqp_device_messaging(self): self.cmd( "iot device c2d-message send -d {} --ack {} --login {} --wait -y".format( - device_ids[0], "full", LIVE_HUB_CS + device_ids[0], "full", self.connection_string ) ) token.set() @@ -206,7 +196,7 @@ def test_uamqp_device_messaging(self): # Error - invalid wait when no ack requested self.cmd( "iot device c2d-message send -d {} --login {} --wait -y".format( - device_ids[0], LIVE_HUB_CS + device_ids[0], self.connection_string ), expect_failure=True, ) @@ -214,7 +204,7 @@ def test_uamqp_device_messaging(self): # Error - content-type is application/json but data is not. self.cmd( "iot device c2d-message send -d {} --login {} --ct application/json --data notjson".format( - device_ids[0], LIVE_HUB_CS + device_ids[0], self.connection_string ), expect_failure=True, ) @@ -222,7 +212,7 @@ def test_uamqp_device_messaging(self): # Error - expiry is in the past. self.cmd( "iot device c2d-message send -d {} --login {} --expiry {}".format( - device_ids[0], LIVE_HUB_CS, int(time() * 1000) + device_ids[0], self.connection_string, int(time() * 1000) ), expect_failure=True, ) @@ -248,7 +238,7 @@ def test_device_messaging(self): # With connection string self.cmd( "iot device c2d-message receive -d {} --login {}".format( - device_ids[0], LIVE_HUB_CS + device_ids[0], self.connection_string ), checks=self.is_empty(), ) @@ -264,7 +254,7 @@ def test_device_messaging(self): # With connection string self.cmd( "iot device c2d-message complete -d {} --login {} -e {}".format( - device_ids[0], LIVE_HUB_CS, etag + device_ids[0], self.connection_string, etag ), expect_failure=True, ) @@ -279,7 +269,7 @@ def test_device_messaging(self): # With connection string self.cmd( "iot device c2d-message reject -d {} --login {} -e {}".format( - device_ids[0], LIVE_HUB_CS, etag + device_ids[0], self.connection_string, etag ), expect_failure=True, ) @@ -294,7 +284,7 @@ def test_device_messaging(self): # With connection string self.cmd( "iot device c2d-message abandon -d {} --login {} --etag {}".format( - device_ids[0], LIVE_HUB_CS, etag + device_ids[0], self.connection_string, etag ), expect_failure=True, ) @@ -309,7 +299,7 @@ def test_device_messaging(self): # With connection string self.cmd( "iot device simulate -d {} --login {} --mc {} --mi {} --data '{}' --rs 'complete'".format( - device_ids[0], LIVE_HUB_CS, 2, 1, "IoT Ext Test" + device_ids[0], self.connection_string, 2, 1, "IoT Ext Test" ), checks=self.is_empty(), ) @@ -324,7 +314,7 @@ def test_device_messaging(self): # With connection string self.cmd( "iot device simulate -d {} --login {} --mc {} --mi {} --data '{}' --rs 'abandon' --protocol http".format( - device_ids[0], LIVE_HUB_CS, 2, 1, "IoT Ext Test" + device_ids[0], self.connection_string, 2, 1, "IoT Ext Test" ), checks=self.is_empty(), ) @@ -380,7 +370,7 @@ def test_device_messaging(self): # With connection string self.cmd( 'iot device send-d2c-message -d {} --login {} --props "MessageId=12345;CorrelationId=54321"'.format( - device_ids[0], LIVE_HUB_CS + device_ids[0], self.connection_string ), checks=self.is_empty(), ) @@ -410,7 +400,7 @@ def test_hub_monitor_events(self): # Test with invalid connection string self.cmd( - "iot hub monitor-events -t 1 -y --login {}".format(LIVE_HUB_CS + "zzz"), + "iot hub monitor-events -t 1 -y --login {}".format(self.connection_string + "zzz"), expect_failure=True, ) @@ -443,7 +433,7 @@ def test_hub_monitor_events(self): ) # Monitor events for all devices and include sys, anno, app self.command_execute_assert( - "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 10 -y -p sys anno app".format( + "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 5 -y -p sys anno app".format( LIVE_HUB, LIVE_RG, LIVE_CONSUMER_GROUPS[0], enqueued_time ), device_ids @@ -459,7 +449,7 @@ def test_hub_monitor_events(self): # Monitor events for a single device self.command_execute_assert( - "iot hub monitor-events -n {} -g {} -d {} --cg {} --et {} -t 10 -y -p all".format( + "iot hub monitor-events -n {} -g {} -d {} --cg {} --et {} -t 5 -y -p all".format( LIVE_HUB, LIVE_RG, device_ids[0], LIVE_CONSUMER_GROUPS[1], enqueued_time ), [ @@ -475,7 +465,7 @@ def test_hub_monitor_events(self): # Monitor events with device-id wildcards self.command_execute_assert( - "iot hub monitor-events -n {} -g {} -d {} --et {} -t 10 -y -p sys anno app".format( + "iot hub monitor-events -n {} -g {} -d {} --et {} -t 5 -y -p sys anno app".format( LIVE_HUB, LIVE_RG, PREFIX_DEVICE + "*", enqueued_time ), device_ids, @@ -491,7 +481,7 @@ def test_hub_monitor_events(self): ) self.command_execute_assert( - 'iot hub monitor-events -n {} -g {} --device-query "{}" --et {} -t 10 -y -p sys anno app'.format( + 'iot hub monitor-events -n {} -g {} --device-query "{}" --et {} -t 5 -y -p sys anno app'.format( LIVE_HUB, LIVE_RG, query_string, enqueued_time ), device_subset_include, @@ -501,7 +491,7 @@ def test_hub_monitor_events(self): device_subset_exclude = device_ids[device_count // 2 :] with pytest.raises(Exception): self.command_execute_assert( - 'iot hub monitor-events -n {} -g {} --device-query "{}" --et {} -t 10 -y -p sys anno app'.format( + 'iot hub monitor-events -n {} -g {} --device-query "{}" --et {} -t 5 -y -p sys anno app'.format( LIVE_HUB, LIVE_RG, query_string, enqueued_time ), device_subset_exclude, @@ -509,8 +499,8 @@ def test_hub_monitor_events(self): # Monitor events with --login parameter self.command_execute_assert( - "iot hub monitor-events -t 10 -y -p all --cg {} --et {} --login {}".format( - LIVE_CONSUMER_GROUPS[2], enqueued_time, LIVE_HUB_CS + "iot hub monitor-events -t 5 -y -p all --cg {} --et {} --login {}".format( + LIVE_CONSUMER_GROUPS[2], enqueued_time, self.connection_string ), device_ids, ) @@ -536,7 +526,7 @@ def test_hub_monitor_events(self): # Monitor messages for ugly JSON output self.command_execute_assert( - "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 10 -y".format( + "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 5 -y".format( LIVE_HUB, LIVE_RG, LIVE_CONSUMER_GROUPS[0], enqueued_time ), ["\\r\\n"], @@ -544,7 +534,7 @@ def test_hub_monitor_events(self): # Monitor messages and parse payload as JSON with the --ct parameter self.command_execute_assert( - "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 10 --ct application/json -y".format( + "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 5 --ct application/json -y".format( LIVE_HUB, LIVE_RG, LIVE_CONSUMER_GROUPS[1], enqueued_time ), ['"payload_data1": "payload_value1"'], @@ -569,7 +559,7 @@ def test_hub_monitor_events(self): # Monitor messages for pretty JSON output self.command_execute_assert( - "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 10 -y".format( + "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 5 -y".format( LIVE_HUB, LIVE_RG, LIVE_CONSUMER_GROUPS[0], enqueued_time ), ['"payload_data1": "payload_value1"'], @@ -577,7 +567,7 @@ def test_hub_monitor_events(self): # Monitor messages with yaml output self.command_execute_assert( - "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 10 -y -o yaml".format( + "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 5 -y -o yaml".format( LIVE_HUB, LIVE_RG, LIVE_CONSUMER_GROUPS[1], enqueued_time ), ["payload_data1: payload_value1"], @@ -602,7 +592,7 @@ def test_hub_monitor_events(self): # Monitor messages to ensure it returns improperly formatted JSON self.command_execute_assert( - "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 10 -y".format( + "iot hub monitor-events -n {} -g {} --cg {} --et {} -t 5 -y".format( LIVE_HUB, LIVE_RG, LIVE_CONSUMER_GROUPS[0], enqueued_time ), ['{\\r\\n\\"payload_data1\\"\\"payload_value1\\"\\r\\n}'], @@ -669,14 +659,14 @@ def test_hub_monitor_feedback(self): ack = "positive" self.cmd( "iot device c2d-message send -d {} --login {} --ack {} -y".format( - device_ids[0], LIVE_HUB_CS, ack + device_ids[0], self.connection_string, ack ), checks=self.is_empty(), ) result = self.cmd( "iot device c2d-message receive -d {} --login {}".format( - device_ids[0], LIVE_HUB_CS + device_ids[0], self.connection_string ) ).get_output_in_json() @@ -688,13 +678,13 @@ def test_hub_monitor_feedback(self): self.cmd( "iot device c2d-message complete -d {} --login {} -e {}".format( - device_ids[0], LIVE_HUB_CS, etag + device_ids[0], self.connection_string, etag ) ) self.command_execute_assert( "iot hub monitor-feedback --login {} -w {} -d {} -y".format( - LIVE_HUB_CS, msg_id, device_ids[0] + self.connection_string, msg_id, device_ids[0] ), ["description: Success"], ) @@ -705,34 +695,34 @@ def test_hub_monitor_feedback(self): # Create some noise self.cmd( "iot device c2d-message send -d {} --login {} --ack {} -y".format( - device_ids[0], LIVE_HUB_CS, ack + device_ids[0], self.connection_string, ack ), checks=self.is_empty(), ) result = self.cmd( "iot device c2d-message receive -d {} --login {}".format( - device_ids[0], LIVE_HUB_CS + device_ids[0], self.connection_string ) ).get_output_in_json() etag = result["etag"] self.cmd( "iot device c2d-message reject -d {} --login {} -e {}".format( - device_ids[0], LIVE_HUB_CS, etag + device_ids[0], self.connection_string, etag ) ) # Target message self.cmd( "iot device c2d-message send -d {} --login {} --ack {} -y".format( - device_ids[0], LIVE_HUB_CS, ack + device_ids[0], self.connection_string, ack ), checks=self.is_empty(), ) result = self.cmd( "iot device c2d-message receive -d {} --login {}".format( - device_ids[0], LIVE_HUB_CS + device_ids[0], self.connection_string ) ).get_output_in_json() @@ -744,11 +734,66 @@ def test_hub_monitor_feedback(self): self.cmd( "iot device c2d-message reject -d {} --login {} -e {}".format( - device_ids[0], LIVE_HUB_CS, etag + device_ids[0], self.connection_string, etag ) ) self.command_execute_assert( - "iot hub monitor-feedback --login {} -w {} -y".format(LIVE_HUB_CS, msg_id), + "iot hub monitor-feedback --login {} -w {} -y".format(self.connection_string, msg_id), ["description: Message rejected"], ) + + # purge messages + num_messages = 3 + for i in range(num_messages): + self.cmd( + "iot device c2d-message send -d {} --login {}".format( + device_ids[0], self.connection_string + ), + checks=self.is_empty(), + ) + purge_result = self.cmd( + "iot device c2d-message purge -d {} --login {}".format( + device_ids[0], self.connection_string + ) + ).get_output_in_json() + assert purge_result["deviceId"] == device_ids[0] + assert purge_result["totalMessagesPurged"] == num_messages + assert not purge_result["moduleId"] + + # Errors with multiple ack arguments + self.cmd( + "iot device c2d-message receive -d {} --login {} --complete --abandon".format( + device_ids[0], self.connection_string + ), + expect_failure=True, + ) + self.cmd( + "iot device c2d-message receive -d {} --login {} --reject --abandon".format( + device_ids[0], self.connection_string + ), + expect_failure=True, + ) + self.cmd( + "iot device c2d-message receive -d {} --login {} --reject --complete --abandon".format( + device_ids[0], self.connection_string + ), + expect_failure=True, + ) + + # Receive with auto-ack + for ack_test in ["complete", "abandon", "reject"]: + self.cmd( + "iot device c2d-message send -d {} --login {}".format( + device_ids[0], self.connection_string + ), + checks=self.is_empty(), + ) + result = self.cmd( + "iot device c2d-message receive -d {} --login {} --{}".format( + device_ids[0], self.connection_string, ack_test + ) + ).get_output_in_json() + assert result["ack"] == ack_test + assert json.dumps(result["data"]) + assert json.dumps(result["properties"]["system"]) diff --git a/azext_iot/tests/iothub/test_iothub_discovery_int.py b/azext_iot/tests/iothub/test_iothub_discovery_int.py new file mode 100644 index 000000000..1f83be955 --- /dev/null +++ b/azext_iot/tests/iothub/test_iothub_discovery_int.py @@ -0,0 +1,118 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.iothub.providers.discovery import ( + IotHubDiscovery, + PRIVILEDGED_ACCESS_RIGHTS_SET, +) +from azext_iot.tests import IoTLiveScenarioTest +from azext_iot.tests.settings import DynamoSettings, ENV_SET_TEST_IOTHUB_BASIC, Setting + +settings = DynamoSettings(ENV_SET_TEST_IOTHUB_BASIC) +LIVE_HUB = settings.env.azext_iot_testhub +LIVE_RG = settings.env.azext_iot_testrg + + +class TestIoTHubDiscovery(IoTLiveScenarioTest): + def __init__(self, test_case): + super(TestIoTHubDiscovery, self).__init__(test_case, LIVE_HUB, LIVE_RG) + self.cmd_shell = Setting() + setattr(self.cmd_shell, "cli_ctx", self.cli_ctx) + self.desired_policy_name = "iothubowner" + + def test_iothub_discovery(self): + discovery = IotHubDiscovery(self.cmd_shell) + + iothub = discovery.find_iothub(hub_name=LIVE_HUB) + assert iothub.name == LIVE_HUB + + auto_policy = discovery.find_policy(hub_name=LIVE_HUB, rg=LIVE_RG).as_dict() + rights_set = set(auto_policy["rights"].split(", ")) + assert rights_set == PRIVILEDGED_ACCESS_RIGHTS_SET + + # Assumption - Test Iothub includes the vanilla iothubowner policy + desired_policy = discovery.find_policy( + hub_name=LIVE_HUB, rg=LIVE_RG, policy_name=self.desired_policy_name + ).as_dict() + assert desired_policy["key_name"] == self.desired_policy_name + + policies = discovery.get_policies(hub_name=LIVE_HUB, rg=LIVE_RG) + assert len(policies) + + # Example for leveraging discovery to build cstring for every policy on target IotHub + cstrings = [discovery._build_target(iothub=iothub, policy=p)["cs"] for p in policies] + assert len(cstrings) + + sub_hubs = discovery.get_iothubs() + assert sub_hubs + + filtered_sub_hubs = [ + hub for hub in sub_hubs if hub.as_dict()["name"] == LIVE_HUB + ] + assert filtered_sub_hubs + + rg_hubs = discovery.get_iothubs(rg=LIVE_RG) + assert rg_hubs + + filtered_rg_hubs = [hub for hub in rg_hubs if hub.as_dict()["name"] == LIVE_HUB] + assert filtered_rg_hubs + + assert len(rg_hubs) <= len(sub_hubs) + + def test_iothub_targets(self): + discovery = IotHubDiscovery(self.cmd_shell) + + cs_target1 = discovery.get_target_by_cstring(self.connection_string) + assert_target(cs_target1, True) + + cs_target2 = discovery.get_target(hub_name=None, login=self.connection_string) + assert_target(cs_target2, True) + + auto_target = discovery.get_target(hub_name=LIVE_HUB) + assert_target(auto_target, rg=LIVE_RG) + + auto_target = discovery.get_target(hub_name=LIVE_HUB, resource_group_name=LIVE_RG) + assert_target(auto_target, rg=LIVE_RG) + + desired_target = discovery.get_target( + hub_name=LIVE_HUB, policy_name=self.desired_policy_name, include_events=True + ) + assert_target(desired_target, rg=LIVE_RG, include_events=True) + + sub_targets = discovery.get_targets() + [assert_target(tar) for tar in sub_targets] + + rg_targets = discovery.get_targets(resource_group_name=LIVE_RG, include_events=True) + [assert_target(tar, rg=LIVE_RG, include_events=True) for tar in rg_targets] + + assert len(rg_targets) <= len(sub_targets) + + +def assert_target(target: dict, by_cstring=False, include_events=False, **kwargs): + assert target["cs"] + assert target["policy"] + assert target["primarykey"] + assert target["entity"] + + if not by_cstring: + assert target["secondarykey"] + assert target["subscription"] and target["subscription"] != "unknown" + + if "rg" in kwargs: + assert target["resourcegroup"] == kwargs["rg"] + + assert target["location"] + assert target["sku_tier"] + assert target["secondarykey"] + + if include_events: + assert target["events"] + events = target["events"] + assert events["endpoint"] + assert events["partition_count"] + assert events["path"] + assert events["partition_ids"] + assert isinstance(events["partition_ids"], list) diff --git a/azext_iot/tests/iothub/test_iothub_discovery_unit.py b/azext_iot/tests/iothub/test_iothub_discovery_unit.py new file mode 100644 index 000000000..0466f546e --- /dev/null +++ b/azext_iot/tests/iothub/test_iothub_discovery_unit.py @@ -0,0 +1,58 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import pytest +from azext_iot.iothub.providers.discovery import IotHubDiscovery +from azext_iot.common._azure import parse_iot_hub_connection_string + + +@pytest.fixture +def get_mgmt_client(mocker, fixture_cmd): + patched_get_raw_token = mocker.patch( + "azure.cli.core._profile.Profile.get_raw_token" + ) + patched_get_raw_token.return_value = ( + mocker.MagicMock(name="creds"), + mocker.MagicMock(name="subscription"), + mocker.MagicMock(name="tenant"), + ) + patch = mocker.patch("azext_iot._factory.iot_hub_service_factory") + patch.return_value = None + + return patch + + +class TestIoTHubDiscovery: + def test_get_target_by_cstring(self, fixture_cmd, get_mgmt_client): + discovery = IotHubDiscovery(cmd=fixture_cmd) + + fake_login = ( + "HostName=CoolIoTHub.azure-devices.net;SharedAccessKeyName=iothubowner;" + "SharedAccessKey=AB+c/+5nm2XpDXcffhnGhnxz/TVF4m5ag7AuVIGwchj=" + ) + parsed_fake_login = parse_iot_hub_connection_string(fake_login) + + target = discovery.get_target( + hub_name=None, resource_group_name=None, login=fake_login + ) + + # Ensure no ARM calls are made + assert get_mgmt_client.call_count == 0 + + assert target["cs"] == fake_login + assert target["entity"] == parsed_fake_login["HostName"] + assert target["policy"] == parsed_fake_login["SharedAccessKeyName"] + assert target["primarykey"] == parsed_fake_login["SharedAccessKey"] + + target = discovery.get_target_by_cstring(fake_login) + + # Ensure no ARM calls are made + assert get_mgmt_client.call_count == 0 + + assert target["cs"] == fake_login + assert target["entity"] == parsed_fake_login["HostName"] + assert target["policy"] == parsed_fake_login["SharedAccessKeyName"] + assert target["primarykey"] == parsed_fake_login["SharedAccessKey"] diff --git a/azext_iot/tests/iothub/test_iothub_nested_edge_int.py b/azext_iot/tests/iothub/test_iothub_nested_edge_int.py new file mode 100644 index 000000000..dee8cb20e --- /dev/null +++ b/azext_iot/tests/iothub/test_iothub_nested_edge_int.py @@ -0,0 +1,243 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azext_iot.tests import IoTLiveScenarioTest +from ..settings import DynamoSettings, ENV_SET_TEST_IOTHUB_BASIC + +settings = DynamoSettings(ENV_SET_TEST_IOTHUB_BASIC) +LIVE_HUB = settings.env.azext_iot_testhub +LIVE_RG = settings.env.azext_iot_testrg + + +class TestIoTNestedEdge(IoTLiveScenarioTest): + def __init__(self, test_case): + super(TestIoTNestedEdge, self).__init__( + test_case, LIVE_HUB, LIVE_RG + ) + + def test_nested_edge(self): + device_count = 3 + edge_device_count = 2 + + device_ids = self.generate_device_names(device_count) + edge_device_ids = self.generate_device_names(edge_device_count, True) + + for edge_device in edge_device_ids: + self.cmd( + "iot hub device-identity create -d {} -n {} -g {} --ee".format( + edge_device, LIVE_HUB, LIVE_RG + ), + checks=[ + self.check("deviceId", edge_device), + self.check("status", "enabled"), + self.check("statusReason", None), + self.check("connectionState", "Disconnected"), + self.check("capabilities.iotEdge", True), + self.exists("authentication.symmetricKey.primaryKey"), + self.exists("authentication.symmetricKey.secondaryKey"), + self.exists("deviceScope"), + ], + ) + + for device in device_ids: + self.cmd( + "iot hub device-identity create -d {} -n {} -g {}".format( + device, LIVE_HUB, LIVE_RG + ), + checks=[ + self.check("deviceId", device), + self.check("status", "enabled"), + self.check("statusReason", None), + self.check("connectionState", "Disconnected"), + self.check("capabilities.iotEdge", False), + self.exists("authentication.symmetricKey.primaryKey"), + self.exists("authentication.symmetricKey.secondaryKey"), + self.check("deviceScope", None), + ], + ) + + # get parent of edge device + self.cmd( + "iot hub device-identity parent show -d {} -n {} -g {}".format( + edge_device_ids[0], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # get parent of device which doesn't have any parent set + self.cmd( + "iot hub device-identity parent show -d {} -n {} -g {}".format( + device_ids[0], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # setting non-edge device as a parent of non-edge device + self.cmd( + "iot hub device-identity parent set -d {} --pd {} -n {} -g {}".format( + device_ids[0], device_ids[1], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # setting edge device as a parent of edge device + self.cmd( + "iot hub device-identity parent set -d {} --pd {} -n {} -g {}".format( + edge_device_ids[0], edge_device_ids[1], LIVE_HUB, LIVE_RG + ), + checks=self.is_empty(), + ) + + # add device as a child of non-edge device + self.cmd( + "iot hub device-identity children add -d {} --child-list {} -n {} -g {}".format( + device_ids[0], device_ids[1], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # add device list as children of edge device + self.cmd( + "iot hub device-identity children add -d {} --child-list {} -n {} -g {}".format( + edge_device_ids[0], " ".join(device_ids), LIVE_HUB, LIVE_RG + ), + checks=self.is_empty(), + ) + + # setting edge device as a parent of non-edge device which already having different parent device + self.cmd( + "iot hub device-identity parent set -d {} --pd {} -n {} -g {}".format( + device_ids[2], edge_device_ids[1], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # setting edge device as a parent of non-edge device which already having different parent device by force + self.cmd( + "iot hub device-identity parent set -d {} --pd {} -n {} -g {} --force".format( + device_ids[2], edge_device_ids[1], LIVE_HUB, LIVE_RG + ), + checks=self.is_empty(), + ) + + # get parent of device + self.cmd( + "iot hub device-identity parent show -d {} -n {} -g {}".format( + device_ids[0], LIVE_HUB, LIVE_RG + ), + checks=[ + self.check("deviceId", edge_device_ids[0]), + self.exists("deviceScope"), + ], + ) + + # add same device as a child of same parent device + self.cmd( + "iot hub device-identity children add -d {} --child-list {} -n {} -g {}".format( + edge_device_ids[0], device_ids[0], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # add same device as a child of another edge device + self.cmd( + "iot hub device-identity children add -d {} --child-list {} -n {} -g {}".format( + edge_device_ids[1], device_ids[0], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # add same device as a child of another edge device by force + self.cmd( + "iot hub device-identity children add -d {} --child-list {} -n {} -g {} --force".format( + edge_device_ids[1], device_ids[0], LIVE_HUB, LIVE_RG + ), + checks=self.is_empty(), + ) + + # list child devices of edge device + output = self.cmd( + "iot hub device-identity children list -d {} -n {} -g {}".format( + edge_device_ids[0], LIVE_HUB, LIVE_RG + ), + expect_failure=False, + ) + + assert output.get_output_in_json() == [device_ids[1]] + + # removing all child devices of non-edge device + self.cmd( + "iot hub device-identity children remove -d {} -n {} -g {} --remove-all".format( + device_ids[0], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # remove all child devices from edge device + self.cmd( + "iot hub device-identity children remove -d {} -n {} -g {} --remove-all".format( + edge_device_ids[1], LIVE_HUB, LIVE_RG + ), + checks=self.is_empty(), + ) + + # removing all child devices of edge device which doesn't have any child devices + self.cmd( + "iot hub device-identity children remove -d {} -n {} -g {} --remove-all".format( + edge_device_ids[1], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # removing child devices of edge device neither passing child devices list nor remove-all parameter + self.cmd( + "iot hub device-identity children remove -d {} -n {} -g {}".format( + edge_device_ids[1], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # remove edge device from edge device + self.cmd( + "iot hub device-identity children remove -d {} --child-list {} -n {} -g {}".format( + edge_device_ids[1], edge_device_ids[0], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # remove device from edge device but device is a child of another edge device + self.cmd( + "iot hub device-identity children remove -d {} --child-list {} -n {} -g {}".format( + edge_device_ids[1], device_ids[1], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # remove device + self.cmd( + "iot hub device-identity children remove -d {} --child-list {} -n {} -g {}".format( + edge_device_ids[0], device_ids[1], LIVE_HUB, LIVE_RG + ), + checks=self.is_empty(), + ) + + # remove device which doesn't have any parent set + self.cmd( + "iot hub device-identity children remove -d {} --child-list {} -n {} -g {}".format( + edge_device_ids[0], device_ids[0], LIVE_HUB, LIVE_RG + ), + expect_failure=True, + ) + + # list child devices of edge device which doesn't have any children + output = self.cmd( + "iot hub device-identity children list -d {} -n {} -g {}".format( + edge_device_ids[1], LIVE_HUB, LIVE_RG + ), + expect_failure=False, + ) + + assert output.get_output_in_json() == [] diff --git a/azext_iot/tests/product/__init__.py b/azext_iot/tests/product/__init__.py new file mode 100644 index 000000000..757a425b4 --- /dev/null +++ b/azext_iot/tests/product/__init__.py @@ -0,0 +1,18 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azure.cli.testsdk import LiveScenarioTest +import pytest + + +@pytest.mark.skipif(True, reason="Skipping AICS tests due to environment inconsistencies") +class AICSLiveScenarioTest(LiveScenarioTest): + def __init__(self, test_scenario): + assert test_scenario + + super(AICSLiveScenarioTest, self).__init__(test_scenario) + AICSLiveScenarioTest.handle = self + self.kwargs.update({"BASE_URL": "https://test.certsvc.trafficmanager.net"}) diff --git a/azext_iot/tests/product/test_aics_e2e_int.py b/azext_iot/tests/product/test_aics_e2e_int.py new file mode 100644 index 000000000..a9fc51775 --- /dev/null +++ b/azext_iot/tests/product/test_aics_e2e_int.py @@ -0,0 +1,170 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +from time import sleep +from . import AICSLiveScenarioTest +from azext_iot.product.shared import ( + TaskType, + BadgeType, + DeviceTestTaskStatus, + AttestationType, +) + + +class TestProductDeviceTestTasks(AICSLiveScenarioTest): + def __init__(self, test_case): + super(TestProductDeviceTestTasks, self).__init__(test_case) + self.kwargs.update( + { + "generate_task": TaskType.GenerateTestCases.value, + "queue_task": TaskType.QueueTestRun.value, + } + ) + + def test_e2e(self): + + # Test requirement list + self.cmd("iot product requirement list --base-url {BASE_URL}") + + self.kwargs.update({"badge_type": BadgeType.IotDevice.value}) + requirements_output = self.cmd( + "iot product requirement list --bt {badge_type} --base-url {BASE_URL}" + ).get_output_in_json() + expected = [ + { + "badgeType": "IotDevice", + "provisioningRequirement": { + "provisioningTypes": ["SymmetricKey", "TPM", "X509"] + }, + } + ] + + assert requirements_output == expected + # Device test operations + test = self.cmd( + "iot product test create --at SymmetricKey --dt DevKit --base-url {BASE_URL}" + ).get_output_in_json() + assert test["deviceType"].lower() == "devkit" + assert test["provisioningConfiguration"]["type"].lower() == "symmetrickey" + assert test["provisioningConfiguration"]["symmetricKeyEnrollmentInformation"][ + "primaryKey" + ] + + self.kwargs.update({"device_test_id": test["id"]}) + + test = self.cmd( + "iot product test show -t {device_test_id} --base-url {BASE_URL}" + ).get_output_in_json() + assert test["id"] == self.kwargs["device_test_id"] + + updated = self.cmd( + "iot product test update -t {device_test_id} --at symmetricKey --base-url {BASE_URL}" + ).get_output_in_json() + assert updated["id"] == self.kwargs["device_test_id"] + assert ( + updated["provisioningConfiguration"]["type"] + == AttestationType.symmetricKey.value + ) + assert updated["provisioningConfiguration"]["symmetricKeyEnrollmentInformation"] + + # Generate test cases + generate_task = self.cmd( + "iot product test task create -t {device_test_id} --type {generate_task} --base-url {BASE_URL}" + ).get_output_in_json() + assert generate_task["status"] == DeviceTestTaskStatus.queued.value + + test_task = self.cmd( + "iot product test task show --running -t {device_test_id} --base-url {BASE_URL}" + ).get_output_in_json()[0] + + assert json.dumps(test_task) + assert test_task.get("status") == DeviceTestTaskStatus.queued.value + assert test_task.get("error") is None + assert test_task.get("type") == TaskType.GenerateTestCases.value + + # wait for generate task to complete + sleep(5) + + self.kwargs.update({"generate_task_id": test_task["id"]}) + test_task = self.cmd( + "iot product test task show -t {device_test_id} --task-id {generate_task_id} --base-url {BASE_URL}" + ).get_output_in_json() + assert test_task.get("error") is None + assert test_task.get("type") == TaskType.GenerateTestCases.value + + # Test case operations + case_list = self.cmd( + "iot product test case list -t {device_test_id} --base-url {BASE_URL}" + ).get_output_in_json() + assert json.dumps(case_list) + assert json.dumps(case_list["certificationBadgeTestCases"]) + + # TODO: Test case update + + # Queue a test run, await the run results + run = self.cmd( + "iot product test task create -t {device_test_id} --type {queue_task} --wait --base-url {BASE_URL}" + ).get_output_in_json() + # test run currently fails without simulator + assert run["status"] == DeviceTestTaskStatus.failed.value + assert json.dumps(run["certificationBadgeResults"]) + + self.kwargs.update({"run_id": run["id"]}) + # show run + run_get = self.cmd( + "iot product test run show -t {device_test_id} -r {run_id} --base-url {BASE_URL}" + ).get_output_in_json() + # show latest run + run_latest = self.cmd( + "iot product test run show -t {device_test_id} --base-url {BASE_URL}" + ).get_output_in_json() + assert run_get == run_latest + assert run_get["id"] == run_latest["id"] == self.kwargs["run_id"] + assert ( + run_get["status"] + == run_latest["status"] + == DeviceTestTaskStatus.failed.value + ) + + # Queue a test run without wait, get run_id + queue_task = self.cmd( + "iot product test task create -t {device_test_id} --type {queue_task} --base-url {BASE_URL}" + ).get_output_in_json() + assert queue_task["type"] == TaskType.QueueTestRun.value + assert queue_task["status"] == DeviceTestTaskStatus.queued.value + + self.kwargs.update({"queue_task_id": queue_task["id"]}) + + # allow test to start running + sleep(5) + + queue_task = self.cmd( + "iot product test task show -t {device_test_id} --task-id {queue_task_id} --base-url {BASE_URL}" + ).get_output_in_json() + assert queue_task["type"] == TaskType.QueueTestRun.value + assert queue_task["status"] == DeviceTestTaskStatus.running.value + + # Cancel running test task + self.cmd( + "iot product test task delete -t {device_test_id} --task-id {queue_task_id} --base-url {BASE_URL}" + ) + + # allow test to be cancelled + sleep(5) + + # get cancelled test task + show = self.cmd( + "iot product test task show -t {device_test_id} --task-id {queue_task_id} --base-url {BASE_URL}" + ).get_output_in_json() + + assert show["status"] == DeviceTestTaskStatus.cancelled.value + + # # Submit run + # self.cmd( + # "iot product test run submit -t {device_test_id} -r {run_id} --base-url {BASE_URL}", + # expect_failure=True, + # ) diff --git a/azext_iot/tests/product/test_command_requirement_list_int.py b/azext_iot/tests/product/test_command_requirement_list_int.py new file mode 100644 index 000000000..f78d76a6a --- /dev/null +++ b/azext_iot/tests/product/test_command_requirement_list_int.py @@ -0,0 +1,63 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from . import AICSLiveScenarioTest + + +class TestRequirementList(AICSLiveScenarioTest): + def test_list_default(self): + create_output = self.cmd( + "iot product requirement list --base-url {BASE_URL}" + ).get_output_in_json() + expected = [ + { + "badgeType": "IotDevice", + "provisioningRequirement": { + "provisioningTypes": ["SymmetricKey", "TPM", "X509"] + }, + } + ] + assert create_output == expected + + def test_list_device(self): + create_output = self.cmd( + "iot product requirement list --badge-type IotDevice --base-url {BASE_URL}" + ).get_output_in_json() + expected = [ + { + "badgeType": "IotDevice", + "provisioningRequirement": { + "provisioningTypes": ["SymmetricKey", "TPM", "X509"] + }, + } + ] + assert create_output == expected + + def test_list_edge(self): + create_output = self.cmd( + "iot product requirement list --badge-type IotEdgeCompatible --base-url {BASE_URL}" + ).get_output_in_json() + expected = [ + { + "badgeType": "IotEdgeCompatible", + "provisioningRequirement": {"provisioningTypes": ["ConnectionString"]}, + } + ] + assert create_output == expected + + def test_list_pnp(self): + create_output = self.cmd( + "iot product requirement list --badge-type Pnp --base-url {BASE_URL}" + ).get_output_in_json() + expected = [ + { + "badgeType": "Pnp", + "provisioningRequirement": { + "provisioningTypes": ["SymmetricKey", "TPM", "X509"] + }, + } + ] + assert create_output == expected diff --git a/azext_iot/tests/product/test_command_test_case_list_int.py b/azext_iot/tests/product/test_command_test_case_list_int.py new file mode 100644 index 000000000..bae26b222 --- /dev/null +++ b/azext_iot/tests/product/test_command_test_case_list_int.py @@ -0,0 +1,27 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from . import AICSLiveScenarioTest +from azext_iot.product.shared import BadgeType + + +class TestTestShowInt(AICSLiveScenarioTest): + def __init__(self, test_case): + self.test_id = "524ac74f-752b-4748-9667-45cd09e8a098" + super(TestTestShowInt, self).__init__(test_case) + + def test_case_list_test(self): + # call the GET /deviceTest/{test_id} + output = self.cmd( + "iot product test case list -t {} --base-url {}".format( + self.test_id, self.kwargs["BASE_URL"] + ) + ).get_output_in_json() + + assert ( + output["certificationBadgeTestCases"][0]["type"].lower() + == BadgeType.Pnp.value.lower() + ) diff --git a/azext_iot/tests/product/test_command_test_case_update_unit.py b/azext_iot/tests/product/test_command_test_case_update_unit.py new file mode 100644 index 000000000..e2573a344 --- /dev/null +++ b/azext_iot/tests/product/test_command_test_case_update_unit.py @@ -0,0 +1,53 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import unittest +import mock +from knack.util import CLIError +from azext_iot.product.test.command_test_cases import update + + +class TestTestCaseUpdate(unittest.TestCase): + def __init__(self, test_case): + self.test_id = "3beb0e67-33d0-4896-b69b-91c7b7ce8fab" + super(TestTestCaseUpdate, self).__init__(test_case) + + @mock.patch("os.path.exists") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.update_test_cases") + def test_update_with_missing_file(self, mock_api, mock_exists): + mock_exists.return_value = False + + with self.assertRaises(CLIError) as context: + update( + self, + test_id=self.test_id, + configuration_file="missingFile.json" + ) + + self.assertEqual( + "If attestation type is x509, certificate path is required", + str(context.exception), + ) + mock_api.assert_not_called() + + @mock.patch("os.path.exists") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.update_test_cases") + @mock.patch("azext_iot.product.test.command_test_cases.process_json_arg") + def test_update(self, mock_json_parser, mock_api, mock_exists): + mock_exists.return_value = True + mock_json_payload = {} + mock_json_parser.return_value = mock_json_payload + + update( + self, + test_id=self.test_id, + configuration_file="configurationFile.json" + ) + + mock_api.assert_called_with( + device_test_id=self.test_id, + certification_badge_test_cases=mock_json_payload + ) diff --git a/azext_iot/tests/product/test_command_test_create_int.py b/azext_iot/tests/product/test_command_test_create_int.py new file mode 100644 index 000000000..2991194ee --- /dev/null +++ b/azext_iot/tests/product/test_command_test_create_int.py @@ -0,0 +1,38 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from . import AICSLiveScenarioTest +from azext_iot.product.shared import AttestationType, BadgeType, DeviceType + + +class TestTestCreateInt(AICSLiveScenarioTest): + def __init__(self, test_case): + super(TestTestCreateInt, self).__init__(test_case) + + def test_create_symmetric_key(self): + device_type = DeviceType.DevKit.value + attestation_type = AttestationType.symmetricKey.value + badge_type = BadgeType.IotDevice.value + + # call the POST /deviceTest + output = self.cmd( + "iot product test create --dt {} --at {} --bt {} --base-url {}".format( + device_type, + attestation_type, + badge_type, + self.kwargs["BASE_URL"], + ) + ).get_output_in_json() + + assert output["deviceType"].lower() == device_type.lower() + assert ( + output["provisioningConfiguration"]["type"].lower() + == attestation_type.lower() + ) + # assert service created symmetric key info + assert output["provisioningConfiguration"]["symmetricKeyEnrollmentInformation"][ + "primaryKey" + ] diff --git a/azext_iot/tests/product/test_command_test_create_unit.py b/azext_iot/tests/product/test_command_test_create_unit.py new file mode 100644 index 000000000..b749b5218 --- /dev/null +++ b/azext_iot/tests/product/test_command_test_create_unit.py @@ -0,0 +1,302 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import unittest +import mock +from knack.util import CLIError +from azext_iot.product.test.command_tests import create, _process_models_directory as process_models +from azext_iot.product.shared import BadgeType, AttestationType, DeviceType, ValidationType + + +class TestTestCreateUnit(unittest.TestCase): + def __init__(self, test_case): + super(TestTestCreateUnit, self).__init__(test_case) + + def test_create_with_no_parameters_fails(self): + with self.assertRaises(CLIError): + create(self) + + def test_create_with_x509_and_no_certificate_fails(self): + with self.assertRaises(CLIError) as context: + create(self, attestation_type=AttestationType.x509.value) + + self.assertEqual( + "If attestation type is x509, certificate path is required", + str(context.exception), + ) + + def test_create_with_tpm_and_no_endorsement_key_fails(self): + with self.assertRaises(CLIError) as context: + create(self, attestation_type=AttestationType.tpm.value) + + self.assertEqual( + "If attestation type is TPM, endorsement key is required", + str(context.exception), + ) + + def test_edge_module_without_connection_string_fails(self): + with self.assertRaises(CLIError) as context: + create( + self, + attestation_type=AttestationType.connectionString.value, + badge_type=BadgeType.IotEdgeCompatible.value, + ) + + self.assertEqual( + "Connection string is required for Edge Compatible modules testing", + str(context.exception), + ) + + def test_connection_string_for_pnp_fails(self): + with self.assertRaises(CLIError) as context: + create( + self, + attestation_type=AttestationType.connectionString.value, + badge_type=BadgeType.Pnp.value, + models="./stuff", + ) + + self.assertEqual( + "Connection string is only available for Edge Compatible modules testing", + str(context.exception), + ) + + def test_connection_string_for_iot_device_fails(self): + with self.assertRaises(CLIError) as context: + create(self, attestation_type=AttestationType.connectionString.value) + + self.assertEqual( + "Connection string is only available for Edge Compatible modules testing", + str(context.exception), + ) + + def test_create_with_pnp_and_no_models_fails(self): + with self.assertRaises(CLIError) as context: + create(self, badge_type=BadgeType.Pnp.value) + + self.assertEqual( + "If badge type is Pnp, models is required", str(context.exception) + ) + + def test_create_with_missing_device_type_fails(self): + with self.assertRaises(CLIError) as context: + create( + self, + attestation_type=AttestationType.symmetricKey.value, + badge_type=BadgeType.Pnp.value, + models="models_folder", + ) + + self.assertEqual( + "If configuration file is not specified, attestation and device definition parameters must be specified", + str(context.exception), + ) + + def test_create_certification_with_missing_product_id_fails(self): + with self.assertRaises(CLIError) as context: + create( + self, + attestation_type=AttestationType.symmetricKey.value, + device_type=DeviceType.DevKit.value, + badge_type=BadgeType.Pnp.value, + models="models_folder", + validation_type=ValidationType.certification.value + ) + self.assertEqual( + "Product Id is required for validation type Certification", + str(context.exception), + ) + + @mock.patch("azext_iot.product.test.command_tests._process_models_directory") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.create_device_test") + def test_create_with_default_badge_type_doesnt_check_models( + self, mock_service, mock_process_models + ): + create( + self, + attestation_type=AttestationType.symmetricKey.value, + device_type=DeviceType.DevKit.value, + models="models_folder", + ) + + mock_process_models.assert_not_called() + mock_service.assert_called_with( + generate_provisioning_configuration=True, + body={ + "validationType": "Test", + "productId": None, + "deviceType": "DevKit", + "provisioningConfiguration": { + "type": "SymmetricKey", + "symmetricKeyEnrollmentInformation": {}, + }, + "certificationBadgeConfigurations": [{"type": "IotDevice"}], + }, + ) + + @mock.patch("azext_iot.product.test.command_tests._process_models_directory") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.create_device_test") + def test_create_with_pnp_badge_type_checks_models( + self, mock_service, mock_process_models + ): + mock_process_models.return_value = [ + '{"@id":"model1"}', + '{"@id":"model2"}', + '{"@id":"model3"}', + ] + create( + self, + attestation_type=AttestationType.symmetricKey.value, + device_type=DeviceType.DevKit.value, + models="models_folder", + badge_type=BadgeType.Pnp.value, + ) + + mock_process_models.assert_called_with("models_folder") + mock_service.assert_called_with( + generate_provisioning_configuration=True, + body={ + "validationType": "Test", + "productId": None, + "deviceType": "DevKit", + "provisioningConfiguration": { + "type": "SymmetricKey", + "symmetricKeyEnrollmentInformation": {}, + }, + "certificationBadgeConfigurations": [ + { + "type": "Pnp", + "digitalTwinModelDefinitions": [ + '{"@id":"model1"}', + '{"@id":"model2"}', + '{"@id":"model3"}', + ], + } + ], + }, + ) + + @mock.patch("azext_iot.product.test.command_tests._read_certificate_from_file") + @mock.patch("azext_iot.product.test.command_tests._process_models_directory") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.create_device_test") + def test_create_with_cert_auth_reads_cert_file( + self, mock_service, mock_process_models, mock_read_certificate + ): + mock_read_certificate.return_value = "MockBase64String" + mock_process_models.return_value = [ + '{"@id":"model1"}', + '{"@id":"model2"}', + '{"@id":"model3"}', + ] + create( + self, + attestation_type=AttestationType.x509.value, + device_type=DeviceType.DevKit.value, + models="models_folder", + badge_type=BadgeType.Pnp.value, + certificate_path="mycertificate.cer", + product_id="ABC123", + validation_type=ValidationType.certification.value + ) + + mock_read_certificate.assert_called_with("mycertificate.cer") + mock_process_models.assert_called_with("models_folder") + mock_service.assert_called_with( + generate_provisioning_configuration=True, + body={ + "validationType": "Certification", + "productId": "ABC123", + "deviceType": "DevKit", + "provisioningConfiguration": { + "type": "X509", + "x509EnrollmentInformation": { + "base64EncodedX509Certificate": "MockBase64String" + }, + }, + "certificationBadgeConfigurations": [ + { + "type": "Pnp", + "digitalTwinModelDefinitions": [ + '{"@id":"model1"}', + '{"@id":"model2"}', + '{"@id":"model3"}', + ], + } + ], + }, + ) + + @mock.patch("azext_iot.product.test.command_tests._read_certificate_from_file") + @mock.patch("azext_iot.product.test.command_tests._process_models_directory") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.create_device_test") + def test_create_with_tpm( + self, mock_service, mock_process_models, mock_read_certificate + ): + mock_process_models.return_value = [ + '{"@id":"model1"}', + '{"@id":"model2"}', + '{"@id":"model3"}', + ] + create( + self, + attestation_type=AttestationType.tpm.value, + endorsement_key="12345", + device_type=DeviceType.DevKit.value, + models="models_folder", + badge_type=BadgeType.Pnp.value, + certificate_path="mycertificate.cer", + ) + + mock_read_certificate.assert_not_called() + mock_process_models.assert_called_with("models_folder") + mock_service.assert_called_with( + generate_provisioning_configuration=True, + body={ + "validationType": "Test", + "productId": None, + "deviceType": "DevKit", + "provisioningConfiguration": { + "type": "TPM", + "tpmEnrollmentInformation": {"endorsementKey": "12345"}, + }, + "certificationBadgeConfigurations": [ + { + "type": "Pnp", + "digitalTwinModelDefinitions": [ + '{"@id":"model1"}', + '{"@id":"model2"}', + '{"@id":"model3"}', + ], + } + ], + }, + ) + + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.create_device_test") + @mock.patch("azext_iot.product.test.command_tests._create_from_file") + def test_create_with_configuration_file(self, mock_from_file, mock_sdk_create): + mock_file_data = {"mock": "data"} + mock_from_file.return_value = mock_file_data + create(self, configuration_file="somefile") + mock_from_file.assert_called_with("somefile") + mock_sdk_create.assert_called_with(generate_provisioning_configuration=True, body=mock_file_data) + + @mock.patch("os.scandir") + @mock.patch("os.path.isfile") + @mock.patch("azext_iot.common.utility.read_file_content") + def test_process_models_directory_as_file(self, mock_file_content, mock_is_file, mock_scan_tree): + mock_file_content.return_value = {"id": "my file"} + mock_is_file.return_value = True + + results = process_models("myPath.dtdl") + + self.assertEqual(len(results), 1) + mock_scan_tree.assert_not_called() + + results = process_models("myPath.json") + + self.assertEqual(len(results), 1) + mock_scan_tree.assert_not_called() diff --git a/azext_iot/tests/product/test_command_test_run_int.py b/azext_iot/tests/product/test_command_test_run_int.py new file mode 100644 index 000000000..61d812822 --- /dev/null +++ b/azext_iot/tests/product/test_command_test_run_int.py @@ -0,0 +1,65 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from . import AICSLiveScenarioTest +from azext_iot.product.shared import TaskType + + +class TestProductDeviceTestRuns(AICSLiveScenarioTest): + def __init__(self, _): + super(TestProductDeviceTestRuns, self).__init__(_) + self.kwargs.update( + { + "device_test_id": "524ac74f-752b-4748-9667-45cd09e8a098", + "generate_task": TaskType.GenerateTestCases.value, + "run_task": TaskType.QueueTestRun.value, + } + ) + + def setup(self): + # setup test runs + gen_task_id = self.cmd( + "iot product test task create -t {device_test_id} --type {generate_task} --wait --base-url {BASE_URL}" + ).get_output_in_json()["id"] + queue_task_id = self.cmd( + "iot product test task create -t {device_test_id} --type {run_task} --wait --base-url {BASE_URL}" + ).get_output_in_json()["id"] + self.kwargs.update( + {"generate_task_id": gen_task_id, "queue_task_id": queue_task_id} + ) + + def teardown(self): + self.cmd( + "iot product test task delete -t {device_test_id} --task-id {generate_task_id} --base-url {BASE_URL}" + ) + self.cmd( + "iot product test task delete -t {device_test_id} --task-id {queue_task_id} --base-url {BASE_URL}" + ) + + def test_product_device_test_run(self): + # get latest test run + latest = self.cmd( + "iot product test run show -t {device_test_id} --base-url {BASE_URL}" + ).get_output_in_json() + run_id = latest["id"] + self.kwargs.update({"test_run_id": run_id}) + specific = self.cmd( + "iot product test run show -t {device_test_id} -r {test_run_id} --base-url {BASE_URL}" + ).get_output_in_json() + + assert latest == specific + + # bad test/run id + self.cmd( + "iot product test run show -t bad_test_id -r bad_run_id --base-url {BASE_URL}", + expect_failure=True, + ) + + # submit (currently cannot submit failed test) + self.cmd( + "iot product test run submit -t {device_test_id} -r {test_run_id} --base-url {BASE_URL}", + expect_failure=True, + ) diff --git a/azext_iot/tests/product/test_command_test_run_unit.py b/azext_iot/tests/product/test_command_test_run_unit.py new file mode 100644 index 000000000..ceb80792a --- /dev/null +++ b/azext_iot/tests/product/test_command_test_run_unit.py @@ -0,0 +1,240 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import unittest +import pytest +import mock +import json +import responses +from datetime import datetime +from knack.util import CLIError +from azext_iot.product.test.command_test_runs import show, submit +from azext_iot.sdk.product.models import TestRun +from azext_iot.product.shared import BASE_URL + +mock_target = {} +mock_target["entity"] = BASE_URL +device_test_id = "12345" +device_test_run_id = "67890" + +api_string = "?api-version=2020-05-01-preview" + +patch_get_latest_test_run = "azext_iot.sdk.product.aicsapi.AICSAPI.get_latest_test_run" +patch_get_test_run = "azext_iot.sdk.product.aicsapi.AICSAPI.get_test_run" +patch_submit_test_run = "azext_iot.sdk.product.aicsapi.AICSAPI.submit_test_run" + +queued_task = TestRun( + id=device_test_run_id, + start_time=datetime.now(), + end_time=datetime.now(), + status="Queued", +) +started_task = TestRun( + id=device_test_run_id, + start_time=datetime.now(), + end_time=datetime.now(), + status="Started", +) +running_task = TestRun( + id=device_test_run_id, + start_time=datetime.now(), + end_time=datetime.now(), + status="Running", +) +completed_task = TestRun( + id=device_test_run_id, + start_time=datetime.now(), + end_time=datetime.now(), + status="Completed", +) + +queued_task_obj = { + "id": queued_task.id, + "start_time": "start_time", + "end_time": "end_time", + "status": "Queued", +} + +run_result_body = json.dumps(queued_task_obj) +finished_run_result_body = run_result_body.replace("Queued", "Completed") + + +class TestRunShow(unittest.TestCase): + @mock.patch(patch_get_latest_test_run) + @mock.patch(patch_get_test_run) + def test_run_show_latest(self, mock_get, mock_get_latest): + show(self, test_id=device_test_id) + + # no run_id, so should call get_latest + mock_get_latest.assert_called_with(device_test_id=device_test_id) + mock_get.assert_not_called() + + @mock.patch(patch_get_latest_test_run) + @mock.patch(patch_get_test_run) + def test_run_show(self, mock_get, mock_get_latest): + show(self, test_id=device_test_id, run_id=device_test_run_id) + + # one call to get + mock_get.assert_called_with( + device_test_id=device_test_id, test_run_id=device_test_run_id + ) + + # does not call get_latest + mock_get_latest.assert_not_called() + + @mock.patch(patch_get_latest_test_run, return_value=queued_task) + @mock.patch( + patch_get_test_run, + side_effect=iter([started_task, running_task, completed_task]), + ) + def test_run_show_latest_wait(self, mock_get, mock_get_latest): + result = show(self, test_id=device_test_id, wait=True, poll_interval=1) + assert mock_get_latest.call_count == 1 + + # three calls to 'get' until status is 'Completed', using run-id from get_latest call + mock_get.assert_called_with( + test_run_id=mock_get_latest.return_value.id, device_test_id=device_test_id + ) + assert mock_get.call_count == 3 + + # make sure we get the last result back + assert result.status == "Completed" + + @mock.patch(patch_get_test_run, return_value=None) + def test_sdk_run_show_error(self, fixture_cmd): + with self.assertRaises(CLIError) as context: + show(fixture_cmd, test_id=device_test_id, run_id=device_test_run_id) + + self.assertEqual( + "No test run found for test ID '{}' with run ID '{}'".format( + device_test_id, device_test_run_id + ), + str(context.exception), + ) + + +class TestRunSubmit(unittest.TestCase): + @mock.patch(patch_submit_test_run) + def test_run_submit(self, mock_submit): + submit(self, test_id=device_test_id, run_id=device_test_run_id) + mock_submit.assert_called_with( + device_test_id=device_test_id, test_run_id=device_test_run_id + ) + + +class TestRunSDK(object): + + # Gets + @pytest.fixture(params=[200]) + def service_client_get(self, mocked_response, fixture_mock_aics_token, request): + # get latest + mocked_response.add( + method=responses.GET, + url="{}/deviceTests/{}/testRuns/latest{}".format( + mock_target["entity"], device_test_id, api_string + ), + body=run_result_body, + headers={"x-ms-command-statuscode": str(request.param)}, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + # get specific (completed) + mocked_response.add( + method=responses.GET, + url="{}/deviceTests/{}/testRuns/{}{}".format( + mock_target["entity"], device_test_id, device_test_run_id, api_string + ), + body=finished_run_result_body, + headers={"x-ms-command-statuscode": str(request.param)}, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response + + # submit + @pytest.fixture(params=[204]) + def service_client_submit(self, mocked_response, fixture_mock_aics_token, request): + mocked_response.add( + method=responses.POST, + url="{}/deviceTests/{}/testRuns/{}/submit{}".format( + mock_target["entity"], device_test_id, device_test_run_id, api_string + ), + body="{}", + headers={"x-ms-command-statuscode": str(request.param)}, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response + + # get error (invalid test task or run id) + @pytest.fixture(params=[404]) + def service_client_error(self, mocked_response, fixture_mock_aics_token, request): + mocked_response.add( + method=responses.GET, + url="{}/deviceTests/{}/testRuns/{}{}".format( + mock_target["entity"], device_test_id, device_test_run_id, api_string + ), + body="", + headers={"x-ms-command-statuscode": str(request.param)}, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + + def test_sdk_run_show(self, fixture_cmd, service_client_get): + # get latest run + result = show(fixture_cmd, test_id=device_test_id) + req = service_client_get.calls[0].request + + assert "deviceTests/{}/testRuns/latest".format(device_test_id) in req.url + assert req.method == "GET" + assert result.id == device_test_run_id + + # specific run + result = show(fixture_cmd, test_id=device_test_id, run_id=device_test_run_id) + req = service_client_get.calls[1].request + + assert ( + "deviceTests/{}/testRuns/{}".format(device_test_id, device_test_run_id) + in req.url + ) + assert req.method == "GET" + assert result.id == device_test_run_id + + # get latest, with wait + def test_sdk_run_show_wait(self, fixture_cmd, service_client_get): + result = show(fixture_cmd, test_id=device_test_id, wait=True, poll_interval=1) + reqs = list(map(lambda call: call.request, service_client_get.calls)) + assert reqs[0].method == "GET" + url = reqs[0].url + assert "deviceTests/{}/testRuns/latest".format(device_test_id) in url + + assert reqs[1].method == "GET" + url = reqs[1].url + assert ( + "deviceTests/{}/testRuns/{}".format(device_test_id, device_test_run_id) + in url + ) + + assert result.id == device_test_run_id + assert result.status == "Completed" + + def test_sdk_task_submit(self, fixture_cmd, service_client_submit): + result = submit(fixture_cmd, test_id=device_test_id, run_id=device_test_run_id) + assert not result + + req = service_client_submit.calls[0].request + url = req.url + assert req.method == "POST" + assert ( + "deviceTests/{}/testRuns/{}/submit".format( + device_test_id, device_test_run_id + ) + in url + ) diff --git a/azext_iot/tests/product/test_command_test_search_unit.py b/azext_iot/tests/product/test_command_test_search_unit.py new file mode 100644 index 000000000..9eac7c744 --- /dev/null +++ b/azext_iot/tests/product/test_command_test_search_unit.py @@ -0,0 +1,51 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import unittest +import mock +from knack.util import CLIError +from azext_iot.product.test.command_tests import search +from azext_iot.sdk.product.models import DeviceTestSearchOptions + + +class SearchClass(unittest.TestCase): + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.search_device_test") + def test_search_not_called_when_no_criteria(self, mock_search): + with self.assertRaises(CLIError): + search(self) + + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.search_device_test") + def test_search_called_with_product_id(self, mock_search): + search(self, product_id="123") + mock_search.assert_called_with( + body=DeviceTestSearchOptions( + product_id="123", + dps_registration_id=None, + dps_x509_certificate_common_name=None, + ) + ) + + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.search_device_test") + def test_search_called_with_registration_id(self, mock_search): + search(self, registration_id="123") + mock_search.assert_called_with( + body=DeviceTestSearchOptions( + product_id=None, + dps_registration_id="123", + dps_x509_certificate_common_name=None, + ) + ) + + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.search_device_test") + def test_search_called_with_certificate_name(self, mock_search): + search(self, certificate_name="123") + mock_search.assert_called_with( + body=DeviceTestSearchOptions( + product_id=None, + dps_registration_id=None, + dps_x509_certificate_common_name="123", + ) + ) diff --git a/azext_iot/tests/product/test_command_test_show_int.py b/azext_iot/tests/product/test_command_test_show_int.py new file mode 100644 index 000000000..88d69ec04 --- /dev/null +++ b/azext_iot/tests/product/test_command_test_show_int.py @@ -0,0 +1,23 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from . import AICSLiveScenarioTest + + +class TestTestShowInt(AICSLiveScenarioTest): + def __init__(self, test_case): + self.test_id = "524ac74f-752b-4748-9667-45cd09e8a098" + super(TestTestShowInt, self).__init__(test_case) + + def test_show_test(self): + # call the GET /deviceTest/{test_id} + output = self.cmd( + "iot product test show -t {} --base-url {}".format( + self.test_id, self.kwargs["BASE_URL"] + ) + ).get_output_in_json() + + assert output["id"] == self.test_id diff --git a/azext_iot/tests/product/test_command_test_update_int.py b/azext_iot/tests/product/test_command_test_update_int.py new file mode 100644 index 000000000..c2ee221ef --- /dev/null +++ b/azext_iot/tests/product/test_command_test_update_int.py @@ -0,0 +1,29 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from . import AICSLiveScenarioTest +from azext_iot.product.shared import AttestationType + + +class TestTestUpdateInt(AICSLiveScenarioTest): + def __init__(self, test_case): + self.test_id = "524ac74f-752b-4748-9667-45cd09e8a098" + super(TestTestUpdateInt, self).__init__(test_case) + + def test_update_symmetric_key(self): + # call the GET /deviceTest/{test_id} + output = self.cmd( + "iot product test update -t {} --at symmetricKey --base-url {}".format( + self.test_id, self.kwargs["BASE_URL"] + ) + ).get_output_in_json() + + assert output["id"] == self.test_id + assert ( + output["provisioningConfiguration"]["type"] + == AttestationType.symmetricKey.value + ) + assert output["provisioningConfiguration"]["symmetricKeyEnrollmentInformation"] diff --git a/azext_iot/tests/product/test_command_test_update_unit.py b/azext_iot/tests/product/test_command_test_update_unit.py new file mode 100644 index 000000000..649163755 --- /dev/null +++ b/azext_iot/tests/product/test_command_test_update_unit.py @@ -0,0 +1,411 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import unittest +import mock +from knack.util import CLIError +from azext_iot.product.test.command_tests import update +from azext_iot.product.shared import BadgeType, AttestationType + + +class TestTestUpdateUnit(unittest.TestCase): + def __init__(self, test_case): + self.test_id = "3beb0e67-33d0-4896-b69b-91c7b7ce8fab" + super(TestTestUpdateUnit, self).__init__(test_case) + + def test_update_with_x509_and_no_certificate_fails(self): + with self.assertRaises(CLIError) as context: + update( + self, test_id=self.test_id, attestation_type=AttestationType.x509.value + ) + + self.assertEqual( + "If attestation type is x509, certificate path is required", + str(context.exception), + ) + + def test_update_with_tpm_and_no_endorsement_key_fails(self): + with self.assertRaises(CLIError) as context: + update( + self, test_id=self.test_id, attestation_type=AttestationType.tpm.value + ) + + self.assertEqual( + "If attestation type is tpm, endorsement key is required", + str(context.exception), + ) + + def test_update_with_pnp_and_no_models_fails(self): + with self.assertRaises(CLIError) as context: + update(self, test_id=self.test_id, badge_type=BadgeType.Pnp.value) + + self.assertEqual( + "If badge type is Pnp, models is required", str(context.exception) + ) + + def test_edge_module_without_connection_string_fails(self): + with self.assertRaises(CLIError) as context: + update( + self, + test_id=self.test_id, + attestation_type=AttestationType.connectionString.value, + badge_type=BadgeType.IotEdgeCompatible.value, + ) + + self.assertEqual( + "Connection string is required for Edge Compatible modules testing", + str(context.exception), + ) + + def test_connection_string_for_pnp_fails(self): + with self.assertRaises(CLIError) as context: + update( + self, + test_id=self.test_id, + attestation_type=AttestationType.connectionString.value, + badge_type=BadgeType.Pnp.value, + models="./stuff", + ) + + self.assertEqual( + "Connection string is only available for Edge Compatible modules testing", + str(context.exception), + ) + + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.update_device_test") + @mock.patch("azext_iot.product.test.command_tests._create_from_file") + def test_update_from_file(self, mock_from_file, mock_sdk_update): + mock_file_data = {"mock": "data"} + mock_from_file.return_value = mock_file_data + update(self, test_id=self.test_id, configuration_file="somefile") + mock_from_file.assert_called_with("somefile") + mock_sdk_update.assert_called_with( + device_test_id=self.test_id, + generate_provisioning_configuration=False, + body=mock_file_data, + raw=True, + ) + + @mock.patch("azext_iot.product.test.command_tests._read_certificate_from_file") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.update_device_test") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.get_device_test") + def test_update_symmetric_key_to_cert( + self, mock_from_get, mock_sdk_update, mock_read_certificate + ): + mock_read_certificate.return_value = "MockBase64String" + mock_test_data = { + "certificationBadgeConfigurations": [{"type": "IotDevice"}], + "deviceType": "DevKit", + "id": self.test_id, + "productId": "product_1234", + "provisioningConfiguration": { + "deviceConnectionString": None, + "deviceId": "device_1234", + "dpsRegistrationId": "DPS_1234", + "region": "region_1", + "symmetricKeyEnrollmentInformation": { + "primaryKey": "primary_key", + "registrationId": "registration_1234", + "scopeId": "scope_1", + "secondaryKey": "secondary_key", + }, + "tpmEnrollmentInformation": None, + "type": "SymmetricKey", + "x509EnrollmentInformation": None, + }, + "validationType": "Certification", + } + return_data = mock.Mock() + return_data.response.json.return_value = mock_test_data + + mock_from_get.return_value = return_data + update( + self, + test_id=self.test_id, + attestation_type=AttestationType.x509.value, + certificate_path="mycertificate.cer", + ) + mock_read_certificate.assert_called_with("mycertificate.cer") + mock_sdk_update.assert_called_with( + device_test_id=self.test_id, + generate_provisioning_configuration=True, + raw=True, + body={ + "certificationBadgeConfigurations": [{"type": "IotDevice"}], + "deviceType": "DevKit", + "id": self.test_id, + "productId": "product_1234", + "provisioningConfiguration": { + "dpsRegistrationId": "DPS_1234", + "type": AttestationType.x509.value, + "x509EnrollmentInformation": { + "base64EncodedX509Certificate": "MockBase64String" + }, + }, + "validationType": "Certification", + }, + ) + + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.update_device_test") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.get_device_test") + def test_update_symmetric_key_to_tpm(self, mock_from_get, mock_sdk_update): + mock_test_data = { + "certificationBadgeConfigurations": [{"type": "IotDevice"}], + "deviceType": "DevKit", + "id": self.test_id, + "productId": "product_1234", + "provisioningConfiguration": { + "deviceConnectionString": None, + "deviceId": "device_1234", + "dpsRegistrationId": "DPS_1234", + "region": "region_1", + "symmetricKeyEnrollmentInformation": { + "primaryKey": "primary_key", + "registrationId": "registration_1234", + "scopeId": "scope_1", + "secondaryKey": "secondary_key", + }, + "tpmEnrollmentInformation": None, + "type": "SymmetricKey", + "x509EnrollmentInformation": None, + }, + "validationType": "Certification", + } + return_data = mock.Mock() + return_data.response.json.return_value = mock_test_data + + mock_from_get.return_value = return_data + update( + self, + test_id=self.test_id, + attestation_type=AttestationType.tpm.value, + endorsement_key="endorsement_key", + ) + mock_sdk_update.assert_called_with( + device_test_id=self.test_id, + generate_provisioning_configuration=True, + raw=True, + body={ + "certificationBadgeConfigurations": [{"type": "IotDevice"}], + "deviceType": "DevKit", + "id": self.test_id, + "productId": "product_1234", + "provisioningConfiguration": { + "dpsRegistrationId": "DPS_1234", + "type": AttestationType.tpm.value, + "tpmEnrollmentInformation": {"endorsementKey": "endorsement_key"}, + }, + "validationType": "Certification", + }, + ) + + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.update_device_test") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.get_device_test") + def test_update_symmetric_key_to_symmetric_key( + self, mock_from_get, mock_sdk_update + ): + mock_test_data = { + "certificationBadgeConfigurations": [{"type": "IotDevice"}], + "deviceType": "DevKit", + "id": self.test_id, + "productId": "product_1234", + "provisioningConfiguration": { + "deviceConnectionString": None, + "deviceId": "device_1234", + "dpsRegistrationId": "DPS_1234", + "region": "region_1", + "symmetricKeyEnrollmentInformation": { + "primaryKey": "primary_key", + "registrationId": "registration_1234", + "scopeId": "scope_1", + "secondaryKey": "secondary_key", + }, + "tpmEnrollmentInformation": None, + "type": "SymmetricKey", + "x509EnrollmentInformation": None, + }, + "validationType": "Certification", + } + return_data = mock.Mock() + return_data.response.json.return_value = mock_test_data + + mock_from_get.return_value = return_data + update( + self, + test_id=self.test_id, + attestation_type=AttestationType.symmetricKey.value, + ) + mock_sdk_update.assert_called_with( + device_test_id=self.test_id, + generate_provisioning_configuration=True, + raw=True, + body={ + "certificationBadgeConfigurations": [{"type": "IotDevice"}], + "deviceType": "DevKit", + "id": self.test_id, + "productId": "product_1234", + "provisioningConfiguration": { + "dpsRegistrationId": "DPS_1234", + "type": AttestationType.symmetricKey.value, + "symmetricKeyEnrollmentInformation": {}, + }, + "validationType": "Certification", + }, + ) + + @mock.patch("azext_iot.product.test.command_tests._process_models_directory") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.update_device_test") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.get_device_test") + def test_update_iotdevice_to_pnp( + self, mock_from_get, mock_sdk_update, mock_process_models + ): + mock_process_models.return_value = [ + '{"@id":"model1"}', + '{"@id":"model2"}', + '{"@id":"model3"}', + ] + mock_test_data = { + "certificationBadgeConfigurations": [{"type": "IotDevice"}], + "deviceType": "DevKit", + "id": self.test_id, + "productId": "product_1234", + "provisioningConfiguration": { + "deviceConnectionString": None, + "deviceId": "device_1234", + "dpsRegistrationId": "DPS_1234", + "region": "region_1", + "symmetricKeyEnrollmentInformation": { + "primaryKey": "primary_key", + "registrationId": "registration_1234", + "scopeId": "scope_1", + "secondaryKey": "secondary_key", + }, + "tpmEnrollmentInformation": None, + "type": "SymmetricKey", + "x509EnrollmentInformation": None, + }, + "validationType": "Certification", + } + return_data = mock.Mock() + return_data.response.json.return_value = mock_test_data + + mock_from_get.return_value = return_data + update( + self, + test_id=self.test_id, + badge_type=BadgeType.Pnp.value, + models="model_folder", + ) + mock_process_models.assert_called_with("model_folder") + mock_sdk_update.assert_called_with( + device_test_id=self.test_id, + generate_provisioning_configuration=False, + raw=True, + body={ + "certificationBadgeConfigurations": [ + { + "type": BadgeType.Pnp.value, + "digitalTwinModelDefinitions": [ + '{"@id":"model1"}', + '{"@id":"model2"}', + '{"@id":"model3"}', + ], + } + ], + "deviceType": "DevKit", + "id": self.test_id, + "productId": "product_1234", + "provisioningConfiguration": { + "deviceConnectionString": None, + "deviceId": "device_1234", + "dpsRegistrationId": "DPS_1234", + "region": "region_1", + "symmetricKeyEnrollmentInformation": { + "primaryKey": "primary_key", + "registrationId": "registration_1234", + "scopeId": "scope_1", + "secondaryKey": "secondary_key", + }, + "tpmEnrollmentInformation": None, + "type": "SymmetricKey", + "x509EnrollmentInformation": None, + }, + "validationType": "Certification", + }, + ) + + @mock.patch("azext_iot.product.test.command_tests._process_models_directory") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.update_device_test") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.get_device_test") + def test_update_pnp_to_iotdevice( + self, mock_from_get, mock_sdk_update, mock_process_models + ): + mock_test_data = { + "certificationBadgeConfigurations": [ + { + "type": BadgeType.Pnp.value, + "digitalTwinModelDefinitions": [ + '{"@id":"model1"}', + '{"@id":"model2"}', + '{"@id":"model3"}', + ], + } + ], + "deviceType": "DevKit", + "id": self.test_id, + "productId": "product_1234", + "provisioningConfiguration": { + "deviceConnectionString": None, + "deviceId": "device_1234", + "dpsRegistrationId": "DPS_1234", + "region": "region_1", + "symmetricKeyEnrollmentInformation": { + "primaryKey": "primary_key", + "registrationId": "registration_1234", + "scopeId": "scope_1", + "secondaryKey": "secondary_key", + }, + "tpmEnrollmentInformation": None, + "type": "SymmetricKey", + "x509EnrollmentInformation": None, + }, + "validationType": "Certification", + } + return_data = mock.Mock() + return_data.response.json.return_value = mock_test_data + + mock_from_get.return_value = return_data + update(self, test_id=self.test_id, badge_type=BadgeType.IotDevice.value) + mock_process_models.assert_not_called() + mock_sdk_update.assert_called_with( + device_test_id=self.test_id, + generate_provisioning_configuration=False, + raw=True, + body={ + "certificationBadgeConfigurations": [ + {"type": BadgeType.IotDevice.value, } + ], + "deviceType": "DevKit", + "id": self.test_id, + "productId": "product_1234", + "provisioningConfiguration": { + "deviceConnectionString": None, + "deviceId": "device_1234", + "dpsRegistrationId": "DPS_1234", + "region": "region_1", + "symmetricKeyEnrollmentInformation": { + "primaryKey": "primary_key", + "registrationId": "registration_1234", + "scopeId": "scope_1", + "secondaryKey": "secondary_key", + }, + "tpmEnrollmentInformation": None, + "type": "SymmetricKey", + "x509EnrollmentInformation": None, + }, + "validationType": "Certification", + }, + ) diff --git a/azext_iot/tests/product/test_device_test_tasks_int.py b/azext_iot/tests/product/test_device_test_tasks_int.py new file mode 100644 index 000000000..8ca08d2b1 --- /dev/null +++ b/azext_iot/tests/product/test_device_test_tasks_int.py @@ -0,0 +1,82 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +from time import sleep +from . import AICSLiveScenarioTest +from azext_iot.product.shared import TaskType, DeviceTestTaskStatus + + +class TestProductDeviceTestTasks(AICSLiveScenarioTest): + def __init__(self, _): + super(TestProductDeviceTestTasks, self).__init__(_) + self.kwargs.update( + { + "device_test_id": "524ac74f-752b-4748-9667-45cd09e8a098", + "generate_task": TaskType.GenerateTestCases.value, + "queue_task": TaskType.QueueTestRun.value, + } + ) + + def setup(self): + return True + + def teardown(self): + return True + + def test_product_device_test_tasks(self): + + # create task for GenerateTestCases + created = self.cmd( + "iot product test task create -t {device_test_id} --type {generate_task} --wait --base-url {BASE_URL}" + ).get_output_in_json() + assert created["deviceTestId"] == self.kwargs["device_test_id"] + assert json.dumps(created) + + test_task_id = created["id"] + self.kwargs.update({"device_test_task_id": test_task_id}) + + # show task + show = self.cmd( + "iot product test task show -t {device_test_id} --task-id {device_test_task_id} --base-url {BASE_URL}" + ).get_output_in_json() + assert json.dumps(show) + assert show["deviceTestId"] == self.kwargs["device_test_id"] + assert show["id"] == self.kwargs["device_test_task_id"] + + # Queue a test run without wait, get run_id + queue_task = self.cmd( + "iot product test task create -t {device_test_id} --type {queue_task} --base-url {BASE_URL}" + ).get_output_in_json() + assert queue_task["type"] == TaskType.QueueTestRun.value + assert queue_task["status"] == DeviceTestTaskStatus.queued.value + + self.kwargs.update({"queue_task_id": queue_task["id"]}) + + # allow test to start running + sleep(5) + + queue_task = self.cmd( + "iot product test task show -t {device_test_id} --task-id {queue_task_id} --base-url {BASE_URL}" + ).get_output_in_json() + assert queue_task["type"] == TaskType.QueueTestRun.value + assert queue_task["status"] != DeviceTestTaskStatus.queued.value + + if queue_task["status"] == DeviceTestTaskStatus.running: + # Cancel running test task + self.cmd( + "iot product test task delete -t {device_test_id} --task-id {queue_task_id} --base-url {BASE_URL}" + ) + + # allow test to be cancelled + sleep(5) + + # get cancelled test task + show = self.cmd( + "iot product test task show -t {device_test_id} --task-id {queue_task_id} --base-url {BASE_URL}" + ).get_output_in_json() + + assert show["status"] == DeviceTestTaskStatus.cancelled.value diff --git a/azext_iot/tests/product/test_device_test_tasks_unit.py b/azext_iot/tests/product/test_device_test_tasks_unit.py new file mode 100644 index 000000000..dae5e3197 --- /dev/null +++ b/azext_iot/tests/product/test_device_test_tasks_unit.py @@ -0,0 +1,345 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import unittest +import pytest +import mock +import json +import responses +from knack.util import CLIError +from azext_iot.sdk.product.models import DeviceTestTask, TestRun +from azext_iot.product.test.command_test_tasks import create, delete, show +from azext_iot.product.shared import TaskType +from azext_iot.product.shared import BASE_URL + +mock_target = {} +mock_target["entity"] = BASE_URL +device_test_id = "12345" +device_test_task_id = "54321" +device_test_run_id = "67890" +task_result = { + "id": device_test_task_id, + "status": "Queued", + "type": "QueueTestRun", + "deviceTestId": device_test_id, + "resultLink": "{}/testRuns/{}".format(device_test_id, device_test_run_id), +} + +run_result = { + "id": device_test_run_id, + "start_time": "start_time", + "end_time": "end_time", + "status": "Completed", + "certificationBadgeResults": [], +} + +queued_task = DeviceTestTask( + id=device_test_task_id, device_test_id=device_test_id, status="Queued" +) +started_task = DeviceTestTask( + id=device_test_task_id, device_test_id=device_test_id, status="Started" +) +running_task = DeviceTestTask( + id=device_test_task_id, device_test_id=device_test_id, status="Running" +) +completed_task = DeviceTestTask( + id=device_test_task_id, device_test_id=device_test_id, status="Completed", +) + +task_result_body = json.dumps(task_result) +finished_task_result_body = task_result_body.replace("Queued", "Completed") +api_string = "?api-version=2020-05-01-preview" + + +class TestTaskCreate(unittest.TestCase): + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.create_device_test_task") + def test_task_create(self, mock_create): + create(self, test_id=device_test_id) + mock_create.assert_called_with( + device_test_id=device_test_id, task_type=TaskType.QueueTestRun.value + ) + + @mock.patch( + "azext_iot.sdk.product.aicsapi.AICSAPI.create_device_test_task", + return_value=queued_task, + ) + @mock.patch( + "azext_iot.sdk.product.aicsapi.AICSAPI.get_device_test_task", + side_effect=iter([started_task, running_task, completed_task]), + ) + @mock.patch("time.sleep") + def test_task_create_wait(self, mock_sleep, mock_get, mock_create): + result = create(self, test_id=device_test_id, wait=True, poll_interval=1) + + # one call to create + mock_create.assert_called_with( + device_test_id=device_test_id, task_type=TaskType.QueueTestRun.value + ) + assert mock_create.call_count == 1 + + # three calls to 'get' until status is 'Completed' + mock_get.assert_called_with( + task_id=device_test_task_id, device_test_id=device_test_id + ) + assert mock_get.call_count == 3 + + # make sure we get the last result back + assert result.status == "Completed" + + @mock.patch( + "azext_iot.sdk.product.aicsapi.AICSAPI.create_device_test_task", + return_value=queued_task, + ) + @mock.patch( + "azext_iot.sdk.product.aicsapi.AICSAPI.get_device_test_task", + side_effect=iter([started_task, running_task, completed_task]), + ) + def test_task_create_no_wait(self, mock_get, mock_create): + result = create(self, test_id=device_test_id, wait=False, poll_interval=1) + + # one call to create + mock_create.assert_called_with( + device_test_id=device_test_id, task_type=TaskType.QueueTestRun.value + ) + assert mock_create.call_count == 1 + + # no calls to 'get' since wait==Falce + mock_get.assert_not_called() + + # initial create response returned + assert result == queued_task + + @mock.patch( + "azext_iot.sdk.product.aicsapi.AICSAPI.create_device_test_task", + return_value={"error": "task currently running"}, + ) + def test_task_create_failure(self, mock_create): + with self.assertRaises(CLIError) as context: + create(self, test_id=device_test_id, wait=False) + self.assertTrue({"error": "task currently running"}, context) + + @mock.patch( + "azext_iot.sdk.product.aicsapi.AICSAPI.create_device_test_task", + return_value=None, + ) + def test_task_create_empty_response(self, mock_create): + with self.assertRaises(CLIError) as context: + create(self, test_id=device_test_id, wait=False) + self.assertTrue( + "Failed to create device test task - please ensure a device test exists with Id {}".format( + device_test_id + ), + context.exception, + ) + + +class TestTaskShow(unittest.TestCase): + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.get_device_test_task") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.get_running_device_test_tasks") + def test_task_show_task(self, mock_get_running, mock_get_task): + show(self, test_id=device_test_id, task_id="456") + mock_get_task.assert_called_with(task_id="456", device_test_id=device_test_id) + self.assertEqual(mock_get_task.call_count, 1) + self.assertEqual(mock_get_running.call_count, 0) + + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.get_device_test_task") + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.get_running_device_test_tasks") + def test_task_show_running(self, mock_get_running, mock_get_task): + show(self, test_id=device_test_id, running=True) + mock_get_running.assert_called_with(device_test_id=device_test_id) + self.assertEqual(mock_get_running.call_count, 1) + self.assertEqual(mock_get_task.call_count, 0) + + def test_task_show_incorrect_params(self): + with self.assertRaises(CLIError) as context: + show(self, test_id=device_test_id) + self.assertTrue( + "Please provide a task-id for individual task details, or use the --running argument to list all running tasks", + context.exception, + ) + + +class TestTaskDelete(unittest.TestCase): + @mock.patch("azext_iot.sdk.product.aicsapi.AICSAPI.cancel_device_test_task") + def test_task_delete(self, mock_delete): + delete(self, test_id=device_test_id, task_id="234") + assert mock_delete.call_count == 1 + mock_delete.assert_called_with(task_id="234", device_test_id=device_test_id) + + +class TestTasksSDK(object): + + # create call + @pytest.fixture(params=[202]) + def service_client_create(self, mocked_response, fixture_mock_aics_token, request): + + # create test task + mocked_response.add( + method=responses.POST, + url="{}/deviceTests/{}/tasks{}".format( + mock_target["entity"], device_test_id, api_string + ), + body=task_result_body, + headers={"x-ms-command-statuscode": str(request.param)}, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response + + # create task, get task, get run (for --wait) + @pytest.fixture(params=[200]) + def service_client_create_wait( + self, service_client_create, mocked_response, fixture_mock_aics_token, request + ): + mocked_response.add( + method=responses.GET, + url="{}/deviceTests/{}/tasks/{}{}".format( + mock_target["entity"], device_test_id, device_test_task_id, api_string + ), + body=finished_task_result_body, + headers={"x-ms-command-statuscode": str(request.param)}, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + # get completed queued test run + mocked_response.add( + method=responses.GET, + url="{}/deviceTests/{}/testRuns/{}{}".format( + mock_target["entity"], device_test_id, device_test_run_id, api_string + ), + body=json.dumps(run_result), + headers={"x-ms-command-statuscode": str(200)}, + status=200, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response + + # delete task + @pytest.fixture(params=[202]) + def service_client_delete(self, mocked_response, fixture_mock_aics_token, request): + mocked_response.add( + method=responses.DELETE, + url="{}/deviceTests/{}/tasks/{}{}".format( + mock_target["entity"], device_test_id, device_test_task_id, api_string + ), + body="{}", + headers={"x-ms-command-statuscode": str(request.param)}, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + + yield mocked_response + + # get single task + @pytest.fixture(params=[200]) + def service_client_get(self, mocked_response, fixture_mock_aics_token, request): + + mocked_response.add( + method=responses.GET, + url="{}/deviceTests/{}/tasks/{}{}".format( + mock_target["entity"], device_test_id, device_test_task_id, api_string + ), + body=task_result_body, + headers={"x-ms-command-statuscode": str(request.param)}, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response + + # get running tasks + @pytest.fixture(params=[200]) + def service_client_get_running( + self, mocked_response, fixture_mock_aics_token, request + ): + mocked_response.add( + method=responses.GET, + url="{}/deviceTests/{}/tasks/running{}".format( + mock_target["entity"], device_test_id, api_string + ), + body="[{}]".format(task_result_body), + headers={"x-ms-command-statuscode": str(request.param)}, + status=request.param, + content_type="application/json", + match_querystring=False, + ) + yield mocked_response + + def test_sdk_task_create(self, fixture_cmd, service_client_create): + result = create(fixture_cmd, test_id=device_test_id) + req = service_client_create.calls[0].request + + assert req.method == "POST" + assert json.loads(req.body)["taskType"] == "QueueTestRun" + assert result.id == device_test_task_id + assert result.device_test_id == device_test_id + + def test_sdk_task_create_wait(self, fixture_cmd, service_client_create_wait): + result = create(fixture_cmd, test_id=device_test_id, wait=True, poll_interval=1) + reqs = list(map(lambda call: call.request, service_client_create_wait.calls)) + + # Call 0 - create test task + assert reqs[0].method == "POST" + assert json.loads(reqs[0].body)["taskType"] == "QueueTestRun" + url = reqs[0].url + assert "deviceTests/{}/tasks".format(device_test_id) in url + + # Call 1 - get task status + assert reqs[1].method == "GET" + url = reqs[1].url + assert ( + "deviceTests/{}/tasks/{}".format(device_test_id, device_test_task_id) in url + ) + + # Call 2 - get run results + assert reqs[2].method == "GET" + url = reqs[2].url + assert ( + "deviceTests/{}/testRuns/{}".format(device_test_id, device_test_run_id) + in url + ) + + # awaiting a queued test run should yield a test run object + assert isinstance(result, TestRun) + assert result.id == device_test_run_id + assert result.status == "Completed" + + def test_sdk_task_delete(self, fixture_cmd, service_client_delete): + result = delete( + fixture_cmd, test_id=device_test_id, task_id=device_test_task_id + ) + assert not result + + req = service_client_delete.calls[0].request + url = req.url + assert req.method == "DELETE" + assert ( + "deviceTests/{}/tasks/{}".format(device_test_id, device_test_task_id) in url + ) + + def test_sdk_task_show_task(self, fixture_cmd, service_client_get): + result = show(fixture_cmd, test_id=device_test_id, task_id=device_test_task_id) + req = service_client_get.calls[0].request + url = req.url + assert ( + "deviceTests/{}/tasks/{}".format(device_test_id, device_test_task_id) in url + ) + assert req.method == "GET" + assert result.id == device_test_task_id + assert result.device_test_id == device_test_id + + def test_sdk_task_show_running(self, fixture_cmd, service_client_get_running): + result = show(fixture_cmd, test_id=device_test_id, running=True) + req = service_client_get_running.calls[0].request + url = req.url + assert "deviceTests/{}/tasks/running".format(device_test_id) in url + assert req.method == "GET" + assert result[0].id == device_test_task_id + assert result[0].device_test_id == device_test_id diff --git a/azext_iot/tests/settings.py b/azext_iot/tests/settings.py index 3b03560c1..6c865c170 100644 --- a/azext_iot/tests/settings.py +++ b/azext_iot/tests/settings.py @@ -7,7 +7,10 @@ from os import environ -ENV_SET_TEST_IOTHUB_BASIC = ["azext_iot_testhub", "azext_iot_testrg", "azext_iot_testhub_cs"] +ENV_SET_TEST_IOTHUB_BASIC = [ + "azext_iot_testhub", + "azext_iot_testrg", +] class Setting(object): @@ -17,7 +20,10 @@ class Setting(object): # Example of a dynamic class # TODO: Evaluate moving this to the extension prime time class DynamoSettings(object): - def __init__(self, req_env_set, opt_env_set=None): + def __init__(self, req_env_set: list = None, opt_env_set: list = None): + if not req_env_set: + req_env_set = [] + if not isinstance(req_env_set, list): raise TypeError("req_env_set must be a list") @@ -29,10 +35,12 @@ def __init__(self, req_env_set, opt_env_set=None): raise TypeError("opt_env_set must be a list") self._build_config(opt_env_set, optional=True) - def _build_config(self, env_set, optional=False): + def _build_config(self, env_set: list, optional: bool = False): for key in env_set: value = environ.get(key) if not value: if not optional: - raise RuntimeError("'{}' environment variable required.".format(key)) + raise RuntimeError( + "{} environment variables required.".format(",".join(env_set)) + ) setattr(self.env, key, value) diff --git a/azext_iot/tests/test_constants.py b/azext_iot/tests/test_constants.py new file mode 100644 index 000000000..d39a00250 --- /dev/null +++ b/azext_iot/tests/test_constants.py @@ -0,0 +1,17 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +class FileNames: + central_device_template_file = "central/json/device_template.json" + central_deeply_nested_device_template_file = ( + "central/json/deeply_nested_template.json" + ) + central_device_file = "central/json/device.json" + central_device_twin_file = "central/json/device_twin.json" + central_property_validation_template_file = ( + "central/json/property_validation_template.json" + ) diff --git a/azext_iot/tests/test_device_digitaltwin_interfaces.json b/azext_iot/tests/test_device_digitaltwin_interfaces.json deleted file mode 100644 index 22cf08ae5..000000000 --- a/azext_iot/tests/test_device_digitaltwin_interfaces.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "interfaces": { - "environmentalSensor": { - "name": "environmentalSensor", - "properties": { - "brightness": { - "desired": { - "value": 123 - }, - "reported": { - "desiredState": { - "code": 200, - "description": "Brightness updated", - "version": 4 - }, - "value": 123 - } - }, - "name": { - "desired": { - "value": "test" - }, - "reported": { - "desiredState": { - "code": 200, - "description": "Property Updated Successfully", - "version": 4 - }, - "value": "test" - } - }, - "state": { - "reported": { - "value": true - } - } - } - }, - "urn_azureiot_ModelDiscovery_DigitalTwin": { - "name": "urn_azureiot_ModelDiscovery_DigitalTwin", - "properties": { - "modelInformation": { - "reported": { - "value": { - "interfaces": { - "environmentalSensor": "urn:contoso:com:EnvironmentalSensor:1", - "urn_azureiot_ModelDiscovery_DigitalTwin": "urn:azureiot:ModelDiscovery:DigitalTwin:1", - "urn_azureiot_ModelDiscovery_ModelInformation": "urn:azureiot:ModelDiscovery:ModelInformation:1" - }, - "modelId": "urn:azureiot:testdevicecapabilitymodel:1" - } - } - } - } - } - }, - "version": 1 - } \ No newline at end of file diff --git a/azext_iot/tests/test_device_digitaltwin_invoke_command.json b/azext_iot/tests/test_device_digitaltwin_invoke_command.json deleted file mode 100644 index fe34ec6b9..000000000 --- a/azext_iot/tests/test_device_digitaltwin_invoke_command.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "blinkRequest": { - "interval": 1 - } -} \ No newline at end of file diff --git a/azext_iot/tests/test_device_digitaltwin_property_update.json b/azext_iot/tests/test_device_digitaltwin_property_update.json deleted file mode 100644 index 09bcbd6b4..000000000 --- a/azext_iot/tests/test_device_digitaltwin_property_update.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "environmentalSensor": { - "properties": { - "name": { - "desired": { - "value": "test-update" - } - } - } - } -} \ No newline at end of file diff --git a/azext_iot/tests/test_iot_central_int.py b/azext_iot/tests/test_iot_central_int.py deleted file mode 100644 index 531af6d9b..000000000 --- a/azext_iot/tests/test_iot_central_int.py +++ /dev/null @@ -1,51 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import os -from azure.cli.testsdk import LiveScenarioTest - -APP_ID = os.environ.get("azext_iot_central_app_id") -DEVICE_ID = os.environ.get("azext_iot_central_device_id") - -if not all([APP_ID, DEVICE_ID]): - raise ValueError('Set azext_iot_central_app_id ' - 'and azext_iot_central_device_id to run integration tests. ') - - -class TestIotCentral(LiveScenarioTest): - def __init__(self, test_method): - super(TestIotCentral, self).__init__('test_central_device_show') - - def test_central_device_show(self): - # Verify incorrect app-id throws error - self.cmd('az iotcentral device-twin show --app-id incorrect-app --device-id "{}"'. - format(DEVICE_ID), expect_failure=True) - self.cmd('az iot central device-twin show --app-id incorrect-app --device-id "{}"'. - format(DEVICE_ID), expect_failure=True) - # Verify incorrect device-id throws error - self.cmd('az iotcentral device-twin show --app-id "{}" --device-id incorrect-device'. - format(APP_ID), expect_failure=True) - self.cmd('az iot central device-twin show --app-id "{}" --device-id incorrect-device'. - format(APP_ID), expect_failure=True) - # Verify that no errors are thrown when device shown - # We cannot verify that the result is correct, as the Azure CLI for IoT Central does not support adding devices - self.cmd('az iotcentral device-twin show --app-id "{}" --device-id "{}"'. - format(APP_ID, DEVICE_ID), expect_failure=False) - self.cmd('az iot central device-twin show --app-id "{}" --device-id "{}"'. - format(APP_ID, DEVICE_ID), expect_failure=False) - - def test_central_monitor_events(self): - # Test with invalid app-id - self.cmd('iotcentral app monitor-events --app-id {}'. - format(APP_ID + "zzz"), expect_failure=True) - self.cmd('iot central app monitor-events --app-id {}'. - format(APP_ID + "zzz"), expect_failure=True) - # Ensure no failure - # We cannot verify that the result is correct, as the Azure CLI for IoT Central does not support adding devices - self.cmd('iotcentral app monitor-events --app-id {}'. - format(APP_ID), expect_failure=False) - self.cmd('iot central app monitor-events --app-id {}'. - format(APP_ID), expect_failure=False) diff --git a/azext_iot/tests/test_iot_central_unit.py b/azext_iot/tests/test_iot_central_unit.py deleted file mode 100644 index 3f09852cf..000000000 --- a/azext_iot/tests/test_iot_central_unit.py +++ /dev/null @@ -1,150 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -from azext_iot.operations import central as subject -from azext_iot.common.shared import SdkType -from azure.cli.core.mock import DummyCli -import mock -import pytest -from knack.util import CLIError -from azext_iot.common.utility import validate_min_python_version - -device_id = 'mydevice' -app_id = 'myapp' -device_twin_result = '{device twin result}' -resource = 'shared_resource' - - -@pytest.fixture() -def fixture_iot_token(mocker): - sas = mocker.patch('azext_iot.operations.central.get_iot_hub_token_from_central_app_id') - sas.return_value = 'SharedAccessSignature sr={}&sig=signature&se=expiry&skn=service'.format(resource) - return sas - - -@pytest.fixture() -def fixture_cmd(mocker): - # Placeholder for later use - cmd = mock.MagicMock() - cmd.cli_ctx = DummyCli() - return cmd - - -@pytest.fixture() -def fixture_bind_sdk(mocker): - class mock_service_sdk: - def get_twin(self, device_id): - return device_twin_result - - mock = mocker.patch('azext_iot.operations.central._bind_sdk') - mock.return_value = (mock_service_sdk(), None) - return mock - - -@pytest.fixture() -def fixture_requests_post(mocker): - class MockJsonObject: - def get(self, _value): - return '' - - def value(self): - return 'fixture_requests_post value' - - class ReturnObject: - def json(self): - return MockJsonObject() - - mock = mocker.patch('requests.post') - mock.return_value = ReturnObject() - - -@pytest.fixture() -def fixture_azure_profile(mocker): - mock = mocker.patch('azure.cli.core._profile.Profile.__init__') - mock.return_value = None - - mock_method = mocker.patch('azure.cli.core._profile.Profile.get_raw_token') - - class MockTokenWithGet: - def get(self, _value, _default): - return 'value' - - mock_method.return_value = [['raw token 0 - A', 'raw token 0 -b', MockTokenWithGet()], 'raw token 1', 'raw token 2'] - - -@pytest.fixture() -def fixture_get_aad_token(mocker): - mock = mocker.patch('azext_iot.common._azure._get_aad_token') - mock.return_value = {'accessToken': "token"} - - -@pytest.fixture() -def fixture_get_iot_central_tokens(mocker): - mock = mocker.patch('azext_iot.common._azure.get_iot_central_tokens') - - mock.return_value = { - 'eventhubSasToken': { - 'hostname': 'part1/part2/part3', - 'entityPath': 'entityPath', - 'sasToken': 'sasToken' - }, - 'expiry': '0000', - 'iothubTenantSasToken': { - 'sasToken': 'iothubTenantSasToken' - } - } - - -class TestCentralHelpers(): - def test_get_iot_central_tokens(self, fixture_requests_post, fixture_get_aad_token): - from azext_iot.common._azure import get_iot_central_tokens - - # Test to ensure get_iot_central_tokens calls requests.post and tokens are returned - assert get_iot_central_tokens({}, 'app_id', 'api-uri').value() == 'fixture_requests_post value' - - def test_get_aad_token(self, fixture_azure_profile): - from azext_iot.common._azure import _get_aad_token - - class Cmd: - cli_ctx = 'test' - - # Test to ensure _get_aad_token is called and returns the right values based on profile.get_raw_tokens - assert _get_aad_token(Cmd(), 'resource') == { - 'accessToken': 'raw token 0 -b', - 'expiresOn': 'value', - 'subscription': 'raw token 1', - 'tenant': 'raw token 2', - 'tokenType': 'raw token 0 - A' - } - - def test_get_iot_hub_token_from_central_app_id(self, fixture_get_iot_central_tokens): - from azext_iot.common._azure import get_iot_hub_token_from_central_app_id - - # Test to ensure get_iot_hub_token_from_central_app_id returns iothubTenantSasToken - assert get_iot_hub_token_from_central_app_id({}, 'app_id', 'api-uri') == 'iothubTenantSasToken' - - -class TestDeviceTwinShow(): - def test_device_twin_show_calls_get_twin(self, fixture_iot_token, fixture_bind_sdk, fixture_cmd): - result = subject.iot_central_device_show(fixture_cmd, device_id, app_id, 'api-uri') - - # Ensure get_twin is called and result is returned - assert result is device_twin_result - - # Ensure _bind_sdk is called with correct parameters - assert fixture_bind_sdk.called is True - args = fixture_bind_sdk.call_args - assert args[0] == ({'entity': resource}, SdkType.service_sdk) - - -@pytest.mark.skipif(not validate_min_python_version(3, 5, exit_on_fail=False), reason="minimum python version not satisfied") -class TestMonitorEvents(): - @pytest.mark.parametrize("timeout, exception", [ - (-1, CLIError), - ]) - def test_monitor_events_invalid_args(self, timeout, exception, fixture_cmd): - with pytest.raises(exception): - subject.iot_central_monitor_events(fixture_cmd, app_id, timeout=timeout) diff --git a/azext_iot/tests/test_iot_digitaltwin_unit.py b/azext_iot/tests/test_iot_digitaltwin_unit.py deleted file mode 100644 index ae1320ba5..000000000 --- a/azext_iot/tests/test_iot_digitaltwin_unit.py +++ /dev/null @@ -1,611 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import pytest -import json -import os - -from azext_iot.operations import digitaltwin as subject -from azext_iot.operations.digitaltwin import INTERFACE_KEY_NAME -from azext_iot.constants import PNP_ENDPOINT -from azext_iot.tests.conftest import build_mock_response -from azext_iot.tests.generators import create_req_monitor_events -from knack.util import CLIError -from azext_iot.common.utility import read_file_content - -_device_digitaltwin_invoke_command_payload = ( - "test_device_digitaltwin_invoke_command.json" -) -_device_digitaltwin_payload_file = "test_device_digitaltwin_interfaces.json" -_device_digitaltwin_property_update_payload_file = ( - "test_device_digitaltwin_property_update.json" -) -_pnp_show_interface_file = "test_pnp_interface_show.json" -_pnp_list_interface_file = "test_pnp_interface_list.json" -path_iot_hub_monitor_events_entrypoint = ( - "azext_iot.operations.digitaltwin._iot_hub_monitor_events" -) -device_id = "mydevice" -hub_entity = "myhub.azure-devices.net" - -# Patch Paths # -path_mqtt_client = "azext_iot.operations._mqtt.mqtt.Client" -path_service_client = "msrest.service_client.ServiceClient.send" -path_ghcs = "azext_iot.operations.digitaltwin.get_iot_hub_connection_string" -path_pnpcs = "azext_iot.operations.pnp.get_iot_pnp_connection_string" -path_sas = "azext_iot._factory.SasTokenAuthentication" - -mock_target = {} -mock_target["entity"] = hub_entity -mock_target["primarykey"] = "rJx/6rJ6rmG4ak890+eW5MYGH+A0uzRvjGNjg3Ve8sfo=" -mock_target["secondarykey"] = "aCd/6rJ6rmG4ak890+eW5MYGH+A0uzRvjGNjg3Ve8sfo=" -mock_target["policy"] = "iothubowner" -mock_target["subscription"] = "5952cff8-bcd1-4235-9554-af2c0348bf23" -mock_target["location"] = "westus2" -mock_target["sku_tier"] = "Standard" -generic_cs_template = "HostName={};SharedAccessKeyName={};SharedAccessKey={}" - -mock_pnptarget = {} -mock_pnptarget["entity"] = PNP_ENDPOINT -mock_pnptarget["primarykey"] = "rJx/6rJ6rmG4ak890+eW5MYGH+A0uzRvjGNjg3Ve8sfo=" -mock_pnptarget["repository_id"] = "1b10ab39a62946ffada85db3b785b3dd" -mock_pnptarget["policy"] = "43325732479e453093a3d1ae5b95c62e" -generic_pnpcs_template = ( - "HostName={};RepositoryId={};SharedAccessKeyName={};SharedAccessKey={}" -) - - -def generate_pnpcs( - pnp=PNP_ENDPOINT, - repository=mock_pnptarget["repository_id"], - policy=mock_target["policy"], - key=mock_target["primarykey"], -): - return generic_pnpcs_template.format(pnp, repository, policy, key) - - -def generate_cs( - hub=hub_entity, policy=mock_target["policy"], key=mock_target["primarykey"] -): - return generic_cs_template.format(hub, policy, key) - - -mock_pnptarget["cs"] = generate_pnpcs() -mock_target["cs"] = generate_cs() - - -def change_dir(): - from inspect import getsourcefile - - os.chdir(os.path.dirname(os.path.abspath(getsourcefile(lambda: 0)))) - - -def generate_device_interfaces_payload(): - change_dir() - return json.loads(read_file_content(_device_digitaltwin_payload_file)) - - -def generate_pnp_interface_show_payload(): - change_dir() - return json.loads(read_file_content(_pnp_show_interface_file)) - - -def generate_pnp_interface_list_payload(): - change_dir() - return json.loads(read_file_content(_pnp_list_interface_file)) - - -def generate_device_digitaltwin_property_update_payload(content_from_file=False): - change_dir() - if content_from_file: - return (None, _device_digitaltwin_property_update_payload_file) - - return ( - str(read_file_content(_device_digitaltwin_property_update_payload_file)), - _device_digitaltwin_property_update_payload_file, - ) - - -def generate_device_digitaltwin_invoke_command_payload(content_from_file=False): - change_dir() - if content_from_file: - return (None, _device_digitaltwin_invoke_command_payload) - - return ( - str(read_file_content(_device_digitaltwin_invoke_command_payload)), - _device_digitaltwin_invoke_command_payload, - ) - - -@pytest.fixture() -def fixture_ghcs(mocker): - ghcs = mocker.patch(path_ghcs) - ghcs.return_value = mock_target - return ghcs - - -@pytest.fixture() -def fixture_pnpcs(mocker): - pnpcs = mocker.patch(path_pnpcs) - pnpcs.return_value = mock_pnptarget - return pnpcs - - -@pytest.fixture() -def fixture_monitor_events_entrypoint(mocker): - return mocker.patch(path_iot_hub_monitor_events_entrypoint) - - -class TestDTInterfaceList(object): - @pytest.fixture(params=[200]) - def serviceclient(self, mocker, request): - service_client = mocker.patch(path_service_client) - output = generate_device_interfaces_payload() - service_client.return_value = build_mock_response(mocker, request.param, output) - return service_client - - def test_iot_digitaltwin_interface_list(self, fixture_cmd, serviceclient): - result = subject.iot_digitaltwin_interface_list( - fixture_cmd, device_id=device_id, login=mock_target["cs"] - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - - assert method == "GET" - assert ( - "{}/digitalTwins/{}/interfaces/{}?".format( - mock_target["entity"], device_id, INTERFACE_KEY_NAME - ) - in url - ) - assert json.dumps(result) - assert len(result["interfaces"]) == 3 - - @pytest.mark.parametrize("exp", [(CLIError)]) - def test_iot_digitaltwin_interface_list_error( - self, fixture_cmd, serviceclient_generic_error, exp - ): - with pytest.raises(exp): - subject.iot_digitaltwin_interface_list( - fixture_cmd, device_id=device_id, login=mock_target["cs"] - ) - - -class TestDTCommandList(object): - @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - output = generate_device_interfaces_payload() - payload_list = generate_pnp_interface_list_payload() - payload_show = generate_pnp_interface_show_payload() - test_side_effect = [ - build_mock_response(mocker, request.param, payload=output), - build_mock_response(mocker, request.param, payload=payload_list), - build_mock_response(mocker, request.param, payload=payload_show), - ] - service_client.side_effect = test_side_effect - return service_client - - def test_iot_digitaltwin_command_list(self, fixture_cmd, serviceclient): - result = subject.iot_digitaltwin_command_list( - fixture_cmd, - device_id=device_id, - source_model="public", - interface="environmentalSensor", - login=mock_target["cs"], - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - - assert method == "GET" - assert "/models/" in url - assert json.dumps(result) - assert len(result["interfaces"]) == 1 - assert result["interfaces"][0]["name"] == "environmentalSensor" - assert len(result["interfaces"][0]["commands"]) == 3 - - @pytest.mark.parametrize("exp", [(CLIError)]) - def test_iot_digitaltwin_command_list_error( - self, fixture_cmd, serviceclient_generic_error, exp - ): - with pytest.raises(exp): - subject.iot_digitaltwin_command_list( - fixture_cmd, - device_id=device_id, - source_model="public", - login=mock_target["cs"], - ) - - @pytest.mark.parametrize("interface, exp", [("inter1", CLIError)]) - def test_iot_digitaltwin_command_list_args_error( - self, fixture_cmd, serviceclient, interface, exp - ): - with pytest.raises(exp): - subject.iot_digitaltwin_command_list( - fixture_cmd, - device_id=device_id, - interface=interface, - login=mock_target["cs"], - source_model="public", - ) - - -class TestDTPropertiesList(object): - @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - output = generate_device_interfaces_payload() - payload_list = generate_pnp_interface_list_payload() - payload_show = generate_pnp_interface_show_payload() - test_side_effect = [ - build_mock_response(mocker, request.param, payload=output), - build_mock_response(mocker, request.param, payload=payload_list), - build_mock_response(mocker, request.param, payload=payload_show), - ] - service_client.side_effect = test_side_effect - return service_client - - @pytest.mark.parametrize("target_interface", [("environmentalSensor")]) - def test_iot_digitaltwin_properties_list( - self, fixture_cmd, serviceclient, target_interface - ): - result = subject.iot_digitaltwin_properties_list( - fixture_cmd, - device_id=device_id, - source_model="public", - interface=target_interface, - login=mock_target["cs"], - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - - assert method == "GET" - assert "/models/" in url - assert json.dumps(result) - if target_interface: - assert len(result["interfaces"]) == 1 - assert result["interfaces"][0]["name"] == "environmentalSensor" - assert len(result["interfaces"][0]["properties"]) == 3 - - @pytest.mark.parametrize("exp", [(CLIError)]) - def test_iot_digitaltwin_properties_list_error( - self, fixture_cmd, serviceclient_generic_error, exp - ): - with pytest.raises(exp): - subject.iot_digitaltwin_properties_list( - fixture_cmd, - device_id=device_id, - source_model="public", - login=mock_target["cs"], - ) - - @pytest.mark.parametrize("interface, exp", [("inter1", CLIError)]) - def test_iot_digitaltwin_properties_list_args_error( - self, fixture_cmd, serviceclient, interface, exp - ): - with pytest.raises(exp): - subject.iot_digitaltwin_properties_list( - fixture_cmd, - device_id=device_id, - interface=interface, - login=mock_target["cs"], - source_model="public", - ) - - -class TestDTPropertyUpdate(object): - @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {}) - return service_client - - @pytest.mark.parametrize( - "payload_scenario", - [ - (generate_device_digitaltwin_property_update_payload()), - ( - generate_device_digitaltwin_property_update_payload( - content_from_file=True - ) - ), - ], - ) - def test_iot_digitaltwin_property_update( - self, fixture_cmd, serviceclient, payload_scenario - ): - - payload = None - - # If file path provided - if not payload_scenario[0]: - payload = payload_scenario[1] - else: - payload = str( - read_file_content(_device_digitaltwin_property_update_payload_file) - ) - - subject.iot_digitaltwin_property_update( - fixture_cmd, - device_id=device_id, - interface_payload=payload, - login=mock_target["cs"], - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - - assert method == "PATCH" - assert ( - "{}/digitalTwins/{}/interfaces?".format(mock_target["entity"], device_id) - in url - ) - - @pytest.mark.parametrize( - "payload_scenario", [(generate_device_digitaltwin_property_update_payload())] - ) - def test_iot_digitaltwin_property_update_error( - self, fixture_cmd, serviceclient_generic_error, payload_scenario - ): - with pytest.raises(CLIError): - subject.iot_digitaltwin_property_update( - fixture_cmd, - device_id=device_id, - interface_payload=payload_scenario[0], - login=mock_target["cs"], - ) - - -class TestDTInvokeCommand(object): - @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - output = generate_device_interfaces_payload() - payload_list = generate_pnp_interface_list_payload() - payload_show = generate_pnp_interface_show_payload() - test_side_effect = [ - build_mock_response(mocker, request.param, payload=output), - build_mock_response(mocker, request.param, payload=payload_list), - build_mock_response(mocker, request.param, payload=payload_show), - build_mock_response(mocker, request.param, {}), - ] - service_client.side_effect = test_side_effect - return service_client - - @pytest.mark.parametrize( - "command_payload", - [ - (generate_device_digitaltwin_invoke_command_payload()), - ( - generate_device_digitaltwin_invoke_command_payload( - content_from_file=True - ) - ), - ], - ) - def test_iot_digitaltwin_invoke_command( - self, fixture_cmd, serviceclient, command_payload - ): - - payload = None - interface = "environmentalSensor" - command = "blink" - - # If file path provided - if not command_payload[0]: - payload = command_payload[1] - else: - payload = str(read_file_content(_device_digitaltwin_invoke_command_payload)) - - subject.iot_digitaltwin_invoke_command( - fixture_cmd, - device_id=device_id, - interface=interface, - command_name=command, - command_payload=payload, - login=mock_target["cs"], - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - - assert method == "POST" - assert ( - "{}/digitalTwins/{}/interfaces/{}/commands/{}?".format( - mock_target["entity"], device_id, interface, command - ) - in url - ) - - @pytest.fixture(params=[(200, 400), (200, 401), (200, 500)]) - def serviceclient_error(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - output = generate_device_interfaces_payload() - test_side_effect = [ - build_mock_response(mocker, request.param[0], payload=output), - build_mock_response(mocker, request.param[1], {}), - ] - service_client.side_effect = test_side_effect - return service_client - - @pytest.mark.parametrize( - "command_payload, interface, command", - [ - ( - generate_device_digitaltwin_invoke_command_payload(), - "environmentalSensor", - "blink", - ), - ( - generate_device_digitaltwin_invoke_command_payload( - content_from_file=True - ), - "environmentalSensor", - "blink", - ), - (generate_device_digitaltwin_invoke_command_payload(), "test", "blink"), - ( - generate_device_digitaltwin_invoke_command_payload(), - "environmentalSensor", - "test", - ), - ], - ) - def test_iot_digitaltwin_property_update_error( - self, fixture_cmd, serviceclient_error, command_payload, interface, command - ): - payload = None - if not command_payload[0]: - payload = command_payload[1] - else: - payload = str(read_file_content(_device_digitaltwin_invoke_command_payload)) - with pytest.raises(CLIError): - subject.iot_digitaltwin_invoke_command( - fixture_cmd, - device_id=device_id, - interface=interface, - command_name=command, - command_payload=payload, - login=mock_target["cs"], - ) - - -class TestDTMonitorEvents(object): - @pytest.mark.parametrize( - "req", - [ - ( - create_req_monitor_events( - device_id=device_id, - interface_name="environmentalSensor", - yes=True, - properties="all", - consumer_group="group1", - login=mock_target["cs"], - ) - ), - ( - create_req_monitor_events( - device_id=device_id, - interface_name="environmentalSensor", - yes=False, - properties="sys anno", - login=mock_target["cs"], - ) - ), - ( - create_req_monitor_events( - device_id=device_id, - interface_name="environmentalSensor", - enqueued_time="5432154321", - yes=True, - properties="sys", - login=mock_target["cs"], - ) - ), - ( - create_req_monitor_events( - device_id=device_id, - enqueued_time="321321321", - content_type="application/json", - timeout=100, - yes=True, - properties="all", - login=mock_target["cs"], - ) - ), - ( - create_req_monitor_events( - device_id=device_id, - timeout=100, - yes=True, - properties="all", - hub_name="myhub", - resource_group_name="myrg", - ) - ), - ( - create_req_monitor_events( - device_id=device_id, - yes=True, - properties="all", - login=mock_target["cs"], - ) - ), - ( - create_req_monitor_events( - device_id=device_id, repair=True, login=mock_target["cs"] - ) - ), - (create_req_monitor_events(login=mock_target["cs"])), - (create_req_monitor_events(hub_name="myhub")), - ( - create_req_monitor_events( - device_query="select * from devices", login=mock_target["cs"] - ) - ), - ( - create_req_monitor_events( - interface_name="environmentalSensor", hub_name="myiothub" - ) - ), - ( - create_req_monitor_events( - device_id="auxFridgeUnit-*", - device_query="select * from devices", - interface_name="environmentalSensor", - login=mock_target["cs"], - ) - ), - ], - ) - def test_iot_digitaltwin_monitor_events_entrypoint( - self, fixture_cmd, fixture_monitor_events_entrypoint, req - ): - subject.iot_digitaltwin_monitor_events( - fixture_cmd, - device_id=req["device_id"], - device_query=req["device_query"], - interface=req["interface_name"], - consumer_group=req["consumer_group"], - content_type=req["content_type"], - enqueued_time=req["enqueued_time"], - timeout=req["timeout"], - hub_name=req["hub_name"], - resource_group_name=req["resource_group_name"], - yes=req["yes"], - properties=req["properties"], - repair=req["repair"], - login=req["login"], - ) - - monitor_events_args = fixture_monitor_events_entrypoint.call_args[1] - - assert monitor_events_args["pnp_context"] - - dt_attribute_set = [ - "device_id", - "device_query", - "interface_name", - "consumer_group", - "enqueued_time", - "content_type", - "timeout", - "login", - "hub_name", - "resource_group_name", - "yes", - "properties", - "repair", - ] - for attribute in dt_attribute_set: - if req[attribute]: - assert monitor_events_args[attribute] == req[attribute] - else: - assert not monitor_events_args[attribute] diff --git a/azext_iot/tests/test_iot_dps_int.py b/azext_iot/tests/test_iot_dps_int.py deleted file mode 100644 index 34994b19f..000000000 --- a/azext_iot/tests/test_iot_dps_int.py +++ /dev/null @@ -1,305 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import os -from azure.cli.testsdk import LiveScenarioTest -from azext_iot.common.shared import EntityStatusType, AttestationType, AllocationType -from azext_iot.common.certops import create_self_signed_certificate - -# Set these to the proper IoT Hub DPS, IoT Hub and Resource Group for Integration Tests. -dps = os.environ.get('azext_iot_testdps') -rg = os.environ.get('azext_iot_testrg') -hub = os.environ.get('azext_iot_testhub') - -if not all([dps, rg, hub]): - raise ValueError('Set azext_iot_testhub, azext_iot_testdps ' - 'and azext_iot_testrg to run integration tests.') - -cert_name = 'test' -cert_path = cert_name + '-cert.pem' - - -class IoTDpsTest(LiveScenarioTest): - - provisioning_status = EntityStatusType.enabled.value - provisioning_status_new = EntityStatusType.disabled.value - - def __init__(self, test_method): - super(IoTDpsTest, self).__init__('test_dps_enrollment_tpm_lifecycle') - output_dir = os.getcwd() - create_self_signed_certificate(cert_name, 200, output_dir, True) - self.kwargs['generic_dict'] = {'count': None, 'key': 'value', 'metadata': None, 'version': None} - - def __del__(self): - if os.path.exists(cert_path): - os.remove(cert_path) - - def test_dps_enrollment_tpm_lifecycle(self): - enrollment_id = self.create_random_name('enrollment-for-test', length=48) - endorsement_key = ('AToAAQALAAMAsgAgg3GXZ0SEs/gakMyNRqXXJP1S124GUgtk8qHaGzMUaaoABgCAAEMAEAgAAAAAAAEAibym9HQP9vxCGF5dVc1Q' - 'QsAGe021aUGJzNol1/gycBx3jFsTpwmWbISRwnFvflWd0w2Mc44FAAZNaJOAAxwZvG8GvyLlHh6fGKdh+mSBL4iLH2bZ4Ry22cB3' - 'CJVjXmdGoz9Y/j3/NwLndBxQC+baNvzvyVQZ4/A2YL7vzIIj2ik4y+ve9ir7U0GbNdnxskqK1KFIITVVtkTIYyyFTIR0BySjPrRI' - 'Dj7r7Mh5uF9HBppGKQCBoVSVV8dI91lNazmSdpGWyqCkO7iM4VvUMv2HT/ym53aYlUrau+Qq87Tu+uQipWYgRdF11KDfcpMHqqzB' - 'QQ1NpOJVhrsTrhyJzO7KNw==') - device_id = self.create_random_name('device-id-for-test', length=48) - attestation_type = AttestationType.tpm.value - hub_host_name = '{}.azure-devices.net'.format(hub) - - etag = self.cmd('iot dps enrollment create --enrollment-id {} --attestation-type {}' - ' -g {} --dps-name {} --endorsement-key {}' - ' --provisioning-status {} --device-id {} --initial-twin-tags {}' - ' --initial-twin-properties {} --allocation-policy {} --iot-hubs {}' - .format(enrollment_id, attestation_type, rg, dps, endorsement_key, - self.provisioning_status, device_id, - '"{generic_dict}"', '"{generic_dict}"', AllocationType.static.value, hub_host_name), - checks=[ - self.check('attestation.type', attestation_type), - self.check('registrationId', enrollment_id), - self.check('provisioningStatus', - self.provisioning_status), - self.check('deviceId', device_id), - self.check('allocationPolicy', AllocationType.static.value), - self.check('iotHubs', hub_host_name.split()), - self.check('initialTwin.tags', - self.kwargs['generic_dict']), - self.check('initialTwin.properties.desired', - self.kwargs['generic_dict']), - self.exists('reprovisionPolicy'), - self.check('reprovisionPolicy.migrateDeviceData', True), - self.check('reprovisionPolicy.updateHubAssignment', True) - ]).get_output_in_json()['etag'] - - self.cmd('iot dps enrollment list -g {} --dps-name {}'.format(rg, dps), checks=[ - self.check('length(@)', 1), - self.check('[0].registrationId', enrollment_id) - ]) - - self.cmd('iot dps enrollment show -g {} --dps-name {} --enrollment-id {}' - .format(rg, dps, enrollment_id), - checks=[self.check('registrationId', enrollment_id)]) - - self.cmd('iot dps enrollment update -g {} --dps-name {} --enrollment-id {}' - ' --provisioning-status {} --etag {}' - .format(rg, dps, enrollment_id, self.provisioning_status_new, etag), - checks=[ - self.check('attestation.type', attestation_type), - self.check('registrationId', enrollment_id), - self.check('provisioningStatus', - self.provisioning_status_new), - self.check('deviceId', device_id), - self.check('allocationPolicy', AllocationType.static.value), - self.check('iotHubs', hub_host_name.split()), - self.exists('initialTwin.tags'), - self.exists('initialTwin.properties.desired') - ]) - - self.cmd('iot dps enrollment delete -g {} --dps-name {} --enrollment-id {}' - .format(rg, dps, enrollment_id)) - - def test_dps_enrollment_x509_lifecycle(self): - enrollment_id = self.create_random_name('enrollment-for-test', length=48) - attestation_type = AttestationType.x509.value - device_id = self.create_random_name('device-id-for-test', length=48) - hub_host_name = '{}.azure-devices.net'.format(hub) - - etag = self.cmd('iot dps enrollment create --enrollment-id {} --attestation-type {}' - ' -g {} --dps-name {} --cp {} --scp {}' - ' --provisioning-status {} --device-id {}' - ' --initial-twin-tags {} --initial-twin-properties {}' - ' --allocation-policy {} --iot-hubs {}' - .format(enrollment_id, attestation_type, rg, dps, cert_path, - cert_path, self.provisioning_status, device_id, - '"{generic_dict}"', '"{generic_dict}"', - AllocationType.hashed.value, - hub_host_name), - checks=[ - self.check('attestation.type', attestation_type), - self.check('registrationId', enrollment_id), - self.check('provisioningStatus', - self.provisioning_status), - self.check('deviceId', device_id), - self.check('allocationPolicy', AllocationType.hashed.value), - self.check('iotHubs', hub_host_name.split()), - self.check('initialTwin.tags', - self.kwargs['generic_dict']), - self.check('initialTwin.properties.desired', - self.kwargs['generic_dict']), - self.exists('reprovisionPolicy'), - self.check('reprovisionPolicy.migrateDeviceData', True), - self.check('reprovisionPolicy.updateHubAssignment', True) - ]).get_output_in_json()['etag'] - - self.cmd('iot dps enrollment list -g {} --dps-name {}'.format(rg, dps), - checks=[ - self.check('length(@)', 1), - self.check('[0].registrationId', enrollment_id)]) - - self.cmd('iot dps enrollment show -g {} --dps-name {} --enrollment-id {}' - .format(rg, dps, enrollment_id), - checks=[self.check('registrationId', enrollment_id)]) - - self.cmd('iot dps enrollment update -g {} --dps-name {} --enrollment-id {}' - ' --provisioning-status {} --etag {} --rc' - .format(rg, dps, enrollment_id, self.provisioning_status_new, etag), - checks=[ - self.check('attestation.type', attestation_type), - self.check('registrationId', enrollment_id), - self.check('provisioningStatus', - self.provisioning_status_new), - self.check('deviceId', device_id), - self.check('allocationPolicy', AllocationType.hashed.value), - self.check('iotHubs', hub_host_name.split()), - self.exists('initialTwin.tags'), - self.exists('initialTwin.properties.desired'), - self.check( - 'attestation.type.x509.clientCertificates.primary', None) - ]) - - self.cmd('iot dps enrollment delete -g {} --dps-name {} --enrollment-id {}' - .format(rg, dps, enrollment_id)) - - def test_dps_enrollment_symmetrickey_lifecycle(self): - enrollment_id = self.create_random_name('enrollment-for-test', length=48) - attestation_type = AttestationType.symmetricKey.value - primary_key = 'x3XNu1HeSw93rmtDXduRUZjhqdGbcqR/zloWYiyPUzw=' - secondary_key = 'PahMnOSBblv9CRn5B765iK35jTvnjDUjYP9hKBZa4Ug=' - device_id = self.create_random_name('device-id-for-test', length=48) - reprovisionPolicy_reprovisionandresetdata = 'reprovisionandresetdata' - hub_host_name = '{}.azure-devices.net'.format(hub) - - etag = self.cmd('iot dps enrollment create --enrollment-id {} --attestation-type {}' - ' -g {} --dps-name {} --pk {} --sk {}' - ' --provisioning-status {} --device-id {}' - ' --initial-twin-tags {} --initial-twin-properties {}' - ' --allocation-policy {} --rp {} --edge-enabled' - .format(enrollment_id, attestation_type, rg, dps, primary_key, - secondary_key, self.provisioning_status, device_id, - '"{generic_dict}"', '"{generic_dict}"', - AllocationType.geolatency.value, - reprovisionPolicy_reprovisionandresetdata,), - checks=[ - self.check('attestation.type', attestation_type), - self.check('registrationId', enrollment_id), - self.check('provisioningStatus', - self.provisioning_status), - self.check('deviceId', device_id), - self.check('allocationPolicy', AllocationType.geolatency.value), - self.check('iotHubHostName', hub_host_name), - self.check('initialTwin.tags', - self.kwargs['generic_dict']), - self.check('initialTwin.properties.desired', - self.kwargs['generic_dict']), - self.exists('reprovisionPolicy'), - self.check('reprovisionPolicy.migrateDeviceData', False), - self.check('reprovisionPolicy.updateHubAssignment', True), - self.check('capabilities.iotEdge', True) - ]).get_output_in_json()['etag'] - - self.cmd('iot dps enrollment list -g {} --dps-name {}'.format(rg, dps), - checks=[ - self.check('length(@)', 1), - self.check('[0].registrationId', enrollment_id)]) - - self.cmd('iot dps enrollment show -g {} --dps-name {} --enrollment-id {}' - .format(rg, dps, enrollment_id), - checks=[self.check('registrationId', enrollment_id)]) - - self.cmd('iot dps enrollment update -g {} --dps-name {} --enrollment-id {}' - ' --provisioning-status {} --etag {} --rc --edge-enabled False' - .format(rg, dps, enrollment_id, self.provisioning_status_new, etag), - checks=[ - self.check('attestation.type', attestation_type), - self.check('registrationId', enrollment_id), - self.check('provisioningStatus', - self.provisioning_status_new), - self.check('deviceId', device_id), - self.check('allocationPolicy', AllocationType.geolatency.value), - self.check('iotHubHostName', hub_host_name), - self.exists('initialTwin.tags'), - self.exists('initialTwin.properties.desired'), - self.check('attestation.symmetric_key.primary_key', primary_key), - self.check('capabilities.iotEdge', False) - ]) - - self.cmd('iot dps enrollment delete -g {} --dps-name {} --enrollment-id {}' - .format(rg, dps, enrollment_id)) - - def test_dps_enrollment_group_lifecycle(self): - enrollment_id = self.create_random_name('enrollment-for-test', length=48) - reprovisionPolicy_never = 'never' - hub_host_name = '{}.azure-devices.net'.format(hub) - etag = self.cmd('iot dps enrollment-group create --enrollment-id {} -g {} --dps-name {}' - ' --cp {} --scp {} --provisioning-status {} --allocation-policy {}' - ' --iot-hubs {} --edge-enabled' - .format(enrollment_id, rg, dps, cert_path, cert_path, - self.provisioning_status, AllocationType.geolatency.value, - hub_host_name), - checks=[ - self.check('enrollmentGroupId', enrollment_id), - self.check('provisioningStatus', - self.provisioning_status), - self.exists('reprovisionPolicy'), - self.check('allocationPolicy', AllocationType.geolatency.value), - self.check('iotHubs', hub_host_name.split()), - self.check('reprovisionPolicy.migrateDeviceData', True), - self.check('reprovisionPolicy.updateHubAssignment', True), - self.check('capabilities.iotEdge', True) - ]).get_output_in_json()['etag'] - - self.cmd('iot dps enrollment-group list -g {} --dps-name {}'.format(rg, dps), checks=[ - self.check('length(@)', 1), - self.check('[0].enrollmentGroupId', enrollment_id) - ]) - - self.cmd('iot dps enrollment-group show -g {} --dps-name {} --enrollment-id {}' - .format(rg, dps, enrollment_id), - checks=[self.check('enrollmentGroupId', enrollment_id)]) - - self.cmd('iot dps enrollment-group update -g {} --dps-name {} --enrollment-id {}' - ' --provisioning-status {} --rsc --etag {} --rp {} --allocation-policy {}' - '--edge-enabled False' - .format(rg, dps, enrollment_id, self.provisioning_status_new, etag, - reprovisionPolicy_never, AllocationType.hashed.value), - checks=[ - self.check('attestation.type', AttestationType.x509.value), - self.check('enrollmentGroupId', enrollment_id), - self.check('provisioningStatus', self.provisioning_status_new), - self.check('attestation.type.x509.clientCertificates.secondary', None), - self.exists('reprovisionPolicy'), - self.check('allocationPolicy', AllocationType.hashed.value), - self.check('iotHubs', hub_host_name.split()), - self.check('reprovisionPolicy.migrateDeviceData', False), - self.check('reprovisionPolicy.updateHubAssignment', False), - self.check('capabilities.iotEdge', False) - ]) - - self.cmd('iot dps registration list -g {} --dps-name {} --enrollment-id {}' - .format(rg, dps, enrollment_id), - checks=[self.check('length(@)', 0)]) - - cert_name = self.create_random_name('certificate-for-test', length=48) - cert_etag = self.cmd('iot dps certificate create -g {} --dps-name {} --name {} --p {}' - .format(rg, dps, cert_name, cert_path), - checks=[self.check('name', cert_name)]).get_output_in_json()['etag'] - - self.cmd('iot dps enrollment-group update -g {} --dps-name {} --enrollment-id {}' - ' --cn {} --etag {}' - .format(rg, dps, enrollment_id, cert_name, cert_etag), - checks=[ - self.check('attestation.type', - AttestationType.x509.value), - self.check('enrollmentGroupId', enrollment_id), - self.check( - 'attestation.x509.caReferences.primary', cert_name), - self.check( - 'attestation.x509.caReferences.secondary', None) - ]) - - self.cmd('iot dps enrollment-group delete -g {} --dps-name {} --enrollment-id {}' - .format(rg, dps, enrollment_id)) - - self.cmd('iot dps certificate delete -g {} --dps-name {} --name {} --etag {}' - .format(rg, dps, cert_name, cert_etag)) diff --git a/azext_iot/tests/test_iot_pnp_int.py b/azext_iot/tests/test_iot_pnp_int.py deleted file mode 100644 index 2fd27488b..000000000 --- a/azext_iot/tests/test_iot_pnp_int.py +++ /dev/null @@ -1,237 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import random -import json -import os - -from io import open -from os.path import exists -from azure.cli.testsdk import LiveScenarioTest -from azext_iot.common.utility import read_file_content - - -# Set these to the proper PnP Endpoint, PnP Cstring and PnP Repository for Live Integration Tests. -_endpoint = os.environ.get("azext_pnp_endpoint") -_repo_id = os.environ.get("azext_pnp_repository") -_repo_cs = os.environ.get("azext_pnp_cs") - -_interface_payload = "test_pnp_create_payload_interface.json" -_capability_model_payload = "test_pnp_create_payload_model.json" - -if not all([_endpoint, _repo_id, _repo_cs]): - raise ValueError( - "Set azext_pnp_endpoint, azext_pnp_repository and azext_pnp_cs to run PnP model integration tests." - ) - - -def change_dir(): - from inspect import getsourcefile - - os.chdir(os.path.dirname(os.path.abspath(getsourcefile(lambda: 0)))) - - -class TestPnPModel(LiveScenarioTest): - - rand_val = random.randint(1, 10001) - - def __init__(self, _): - super(TestPnPModel, self).__init__(_) - self.kwargs.update( - { - "endpoint": _endpoint, - "repo": _repo_id, - "repo_cs": _repo_cs, - "interface": "test_interface_definition.json", - "interface_updated": "test_interface_updated_definition.json", - "model": "test_model_definition.json", - "model_updated": "test_model_updated_definition.json", - } - ) - - def setUp(self): - change_dir() - if self._testMethodName == "test_interface_life_cycle": - interface = str(read_file_content(_interface_payload)) - _interface_id = "{}{}".format( - json.loads(interface)["@id"], TestPnPModel.rand_val - ) - self.kwargs.update({"interface_id": _interface_id}) - interface_newContent = interface.replace( - json.loads(interface)["@id"], self.kwargs["interface_id"] - ) - interface_newContent = interface_newContent.replace("\n", "") - - fo = open(self.kwargs["interface"], "w+", encoding="utf-8") - fo.write(interface_newContent) - fo.close() - - if self._testMethodName == "test_model_life_cycle": - model = str(read_file_content(_capability_model_payload)) - _model_id = "{}{}".format(json.loads(model)["@id"], TestPnPModel.rand_val) - self.kwargs.update({"model_id": _model_id}) - model_newContent = model.replace( - json.loads(model)["@id"], self.kwargs["model_id"] - ) - model_newContent = model_newContent.replace("\n", "") - - fo = open(self.kwargs["model"], "w+", encoding="utf-8") - fo.write(model_newContent) - fo.close() - - def tearDown(self): - change_dir() - if exists(self.kwargs["interface_updated"]): - os.remove(self.kwargs["interface_updated"]) - if exists(self.kwargs["model_updated"]): - os.remove(self.kwargs["model_updated"]) - if exists(self.kwargs["interface"]): - os.remove(self.kwargs["interface"]) - if exists(self.kwargs["model"]): - os.remove(self.kwargs["model"]) - - def test_interface_life_cycle(self): - - # Error: missing repo-id or login - self.cmd( - "iot pnp interface create -e {endpoint} --def {interface}", - expect_failure=True, - ) - - # Error: Invalid Interface definition file - self.cmd( - "iot pnp interface create -e {endpoint} -r {repo} --def interface", - expect_failure=True, - ) - - # Error: wrong path of Interface definition - self.cmd( - "iot pnp interface create -e {endpoint} -r {repo} --def interface.json", - expect_failure=True, - ) - - # Success: Create new Interface - self.cmd( - "iot pnp interface create -e {endpoint} -r {repo} --def {interface}", - checks=self.is_empty(), - ) - - # Checking the Interface list - self.cmd( - "iot pnp interface list -e {endpoint} -r {repo}", - checks=[ - self.greater_than("length([*])", 0), - self.exists("[?urnId==`{}`]".format(self.kwargs["interface_id"])), - ], - ) - - # Get Interface - interface = self.cmd( - "iot pnp interface show -e {endpoint} -r {repo} -i {interface_id}" - ).get_output_in_json() - assert json.dumps(interface) - assert interface["@id"] == self.kwargs["interface_id"] - assert interface["displayName"] == "MXChip1" - assert len(interface["contents"]) > 0 - - # Success: Update Interface - interface = str(read_file_content(self.kwargs["interface"])) - display_name = json.loads(interface)["displayName"] - interface_newContent = interface.replace( - display_name, "{}-Updated".format(display_name) - ) - interface_newContent = interface_newContent.replace("\n", "") - fo = open(self.kwargs["interface_updated"], "w+", encoding="utf-8") - fo.write(interface_newContent) - fo.close() - self.cmd( - "iot pnp interface update -e {endpoint} -r {repo} --def {interface_updated}", - checks=self.is_empty(), - ) - - # Publish Interface - # Error must be published from partner tenant - self.cmd( - "iot pnp interface publish -e {endpoint} -r {repo} -i {interface_id}", - expect_failure=True, - ) - - # Success: Delete Interface - self.cmd( - "iot pnp interface delete -e {endpoint} -r {repo} -i {interface_id}", - checks=self.is_empty(), - ) - - def test_model_life_cycle(self): - - # Error: missing repo-id or login - self.cmd( - "iot pnp capability-model create -e {endpoint} --def {model}", - expect_failure=True, - ) - - # Error: Invalid Capability-Model definition file - self.cmd( - "iot pnp capability-model create -e {endpoint} -r {repo} --def model", - expect_failure=True, - ) - - # Error: wrong path of Capability-Model definition - self.cmd( - "iot pnp capability-model create -e {endpoint} -r {repo} --def model.json", - expect_failure=True, - ) - - # Success: Create new Capability-Model - self.cmd( - "iot pnp capability-model create -e {endpoint} -r {repo} --def {model}", - checks=self.is_empty(), - ) - - # Checking the Capability-Model list - self.cmd( - "iot pnp capability-model list -e {endpoint} -r {repo}", - checks=[ - self.greater_than("length([*])", 0), - self.exists("[?urnId==`{}`]".format(self.kwargs["model_id"])), - ], - ) - - # Get Capability-Model - model = self.cmd( - "iot pnp capability-model show -e {endpoint} -r {repo} -m {model_id}" - ).get_output_in_json() - assert json.dumps(model) - assert model["@id"] == self.kwargs["model_id"] - assert len(model["implements"]) > 0 - - # Success: Update Capability-Model - model = str(read_file_content(self.kwargs["model"])) - display_name = json.loads(model)["displayName"] - model_newContent = model.replace( - display_name, "{}-Updated".format(display_name) - ) - model_newContent = model_newContent.replace("\n", "") - fo = open(self.kwargs["model_updated"], "w+", encoding="utf-8") - fo.write(model_newContent) - fo.close() - self.cmd( - "iot pnp capability-model update -e {endpoint} -r {repo} --def {model_updated}", - checks=self.is_empty(), - ) - - # Publish Capability-Model - # Error must be published from partner tenant - self.cmd( - "iot pnp capability-model publish -e {endpoint} -r {repo} -m {model_id}", - expect_failure=True, - ) - - # Success: Delete Capability-Model - self.cmd( - "iot pnp capability-model delete -e {endpoint} -r {repo} -m {model_id}", - checks=self.is_empty(), - ) diff --git a/azext_iot/tests/test_iot_pnp_unit.py b/azext_iot/tests/test_iot_pnp_unit.py deleted file mode 100644 index ee69fba0b..000000000 --- a/azext_iot/tests/test_iot_pnp_unit.py +++ /dev/null @@ -1,950 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -import pytest -import json -import os - -from uuid import uuid4 -from azext_iot.operations import pnp as subject -from azext_iot.common.utility import url_encode_str, read_file_content -from knack.util import CLIError -from .conftest import fixture_cmd, path_service_client, build_mock_response - -_repo_endpoint = "https://{}.{}".format(str(uuid4()), "com") -_repo_id = str(uuid4()).replace("-", "") -_repo_keyname = str(uuid4()).replace("-", "") -_repo_secret = "lMT+wSy8TIzDASRMlhxwpYxG3mWba45YqCFUo6Qngju5uZS9V4tM2yh5pn3zdB0FC3yRx91UnSWjdr/jLutPbg==" -generic_cs_template = ( - "HostName={};RepositoryId={};SharedAccessKeyName={};SharedAccessKey={}" -) -path_ghcs = "azext_iot.operations.pnp.get_iot_pnp_connection_string" -_pnp_create_interface_payload_file = "test_pnp_create_payload_interface.json" -_pnp_create_model_payload_file = "test_pnp_create_payload_model.json" -_pnp_show_interface_file = "test_pnp_interface_show.json" -_pnp_generic_interface_id = "urn:example:interfaces:MXChip:1" -_pnp_generic_model_id = "urn:example:capabilityModels:Mxchip:1" - - -@pytest.fixture() -def fixture_ghcs(mocker): - ghcs = mocker.patch(path_ghcs) - ghcs.return_value = mock_target - return ghcs - - -def generate_cs( - endpoint=_repo_endpoint, repository=_repo_id, policy=_repo_keyname, key=_repo_secret -): - return generic_cs_template.format(endpoint, repository, policy, key) - - -def change_dir(): - from inspect import getsourcefile - - os.chdir(os.path.dirname(os.path.abspath(getsourcefile(lambda: 0)))) - - -mock_target = {} -mock_target["cs"] = generate_cs() -mock_target["policy"] = _repo_keyname -mock_target["primarykey"] = _repo_secret -mock_target["repository_id"] = _repo_id -mock_target["entity"] = _repo_endpoint -mock_target["entity"] = mock_target["entity"].replace("https://", "") -mock_target["entity"] = mock_target["entity"].replace("http://", "") - - -def generate_pnp_interface_create_payload(content_from_file=False): - change_dir() - if content_from_file: - return (None, _pnp_create_interface_payload_file) - - return ( - str(read_file_content(_pnp_create_interface_payload_file)), - _pnp_create_interface_payload_file, - ) - - -class TestModelRepoInterfaceCreate(object): - @pytest.fixture(params=[201, 204, 412]) - def serviceclient(self, mocker, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {}) - return service_client - - @pytest.mark.parametrize( - "payload_scenario", - [ - (generate_pnp_interface_create_payload()), - (generate_pnp_interface_create_payload(content_from_file=True)), - ], - ) - def test_interface_create(self, fixture_cmd, serviceclient, payload_scenario): - payload = None - - # If file path provided - if not payload_scenario[0]: - payload = payload_scenario[1] - else: - payload = str(read_file_content(_pnp_create_interface_payload_file)) - - subject.iot_pnp_interface_create( - fixture_cmd, interface_definition=payload, login=mock_target["cs"] - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - data = args[0][0].data - headers = args[0][0].headers - - assert method == "PUT" - assert ( - "{}/models/{}?".format( - _repo_endpoint, url_encode_str(_pnp_generic_interface_id, plus=True) - ) - in url - ) - assert "repositoryId={}".format(_repo_id) in url - assert json.dumps(data) - assert headers.get("Authorization") - - @pytest.mark.parametrize( - "payload_scenario", [(generate_pnp_interface_create_payload())] - ) - def test_interface_create_error( - self, fixture_cmd, serviceclient_generic_error, payload_scenario - ): - with pytest.raises(CLIError): - subject.iot_pnp_interface_create( - fixture_cmd, - login=mock_target["cs"], - interface_definition=payload_scenario[0], - ) - - @pytest.mark.parametrize( - "payload_scenario, exp", [(generate_pnp_interface_create_payload(), CLIError)] - ) - def test_interface_create_invalid_args(self, serviceclient, payload_scenario, exp): - with pytest.raises(exp): - subject.iot_pnp_interface_create( - fixture_cmd, interface_definition=payload_scenario - ) - - def test_interface_create_invalid_payload(self, serviceclient): - - payload = str(read_file_content(_pnp_create_interface_payload_file)) - payload = json.loads(payload) - del payload["@id"] - payload = json.dumps(payload) - with pytest.raises(CLIError): - subject.iot_pnp_interface_create( - fixture_cmd, login=mock_target["cs"], interface_definition=payload - ) - - -class TestModelRepoInterfaceUpdate(object): - @pytest.fixture(params=[(200, 201), (200, 204), (200, 412)]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - payload = { - "continuationToken": "null", - "results": [ - { - "comment": "", - "createdOn": "2019-07-09T07:46:06.044161+00:00", - "description": "", - "displayName": "MXChip 1", - "etag": '"41006e67-0000-0800-0000-5d2501b80000"', - "modelName": "example:interfaces:MXChip", - "publisherId": "aabbaabb-aabb-aabb-aabb-aabbaabbaabb", - "publisherName": "microsoft.com", - "type": "Interface", - "updatedOn": "2019-07-09T21:06:00.072063+00:00", - "urnId": "urn:example:interfaces:MXChip:1", - "version": 1, - } - ], - } - test_side_effect = [ - build_mock_response(mocker, request.param[0], payload=payload), - build_mock_response(mocker, request.param[1], {}), - ] - service_client.side_effect = test_side_effect - return service_client - - @pytest.mark.parametrize( - "payload_scenario", - [ - generate_pnp_interface_create_payload(), - generate_pnp_interface_create_payload(content_from_file=True), - ], - ) - def test_interface_update(self, fixture_cmd, serviceclient, payload_scenario): - payload = None - - # If file path provided - if not payload_scenario[0]: - payload = payload_scenario[1] - else: - payload = str(read_file_content(_pnp_create_interface_payload_file)) - - subject.iot_pnp_interface_update( - fixture_cmd, - interface_definition=payload, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - data = args[0][0].data - headers = args[0][0].headers - - assert method == "PUT" - assert ( - "{}/models/{}?".format( - _repo_endpoint, url_encode_str(_pnp_generic_interface_id, plus=True) - ) - in url - ) - assert "repositoryId={}".format(_repo_id) in url - assert json.dumps(data) - assert headers.get("Authorization") - - @pytest.mark.parametrize( - "payload_scenario", - [ - (generate_pnp_interface_create_payload()), - (generate_pnp_interface_create_payload(content_from_file=True)), - ], - ) - def test_model_update_error( - self, fixture_cmd, serviceclient_generic_error, payload_scenario - ): - if not payload_scenario[0]: - payload = payload_scenario[1] - else: - payload = str(read_file_content(_pnp_create_interface_payload_file)) - with pytest.raises(CLIError): - subject.iot_pnp_interface_update( - fixture_cmd, - interface_definition=payload, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - - @pytest.mark.parametrize( - "payload_scenario, exp", [(generate_pnp_interface_create_payload(), CLIError)] - ) - def test_interface_update_invalid_args(self, serviceclient, payload_scenario, exp): - with pytest.raises(exp): - subject.iot_pnp_interface_update( - fixture_cmd, interface_definition=payload_scenario - ) - - def test_interface_update_invalid_payload(self, serviceclient): - - payload = str(read_file_content(_pnp_create_interface_payload_file)) - payload = json.loads(payload) - payload["@id"] = "fake_invalid_id" - payload = json.dumps(payload) - with pytest.raises(CLIError): - subject.iot_pnp_interface_update( - fixture_cmd, - interface_definition=payload, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - - -class TestModelRepoInterfacePublish(object): - @pytest.fixture(params=[(200, 200, 201), (200, 200, 204), (200, 200, 412)]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - payload_list = { - "continuationToken": "null", - "results": [ - { - "comment": "", - "createdOn": "2019-07-09T07:46:06.044161+00:00", - "description": "", - "displayName": "MXChip 1", - "etag": '"41006e67-0000-0800-0000-5d2501b80000"', - "modelName": "example:interfaces:MXChip", - "publisherId": "aabbaabb-aabb-aabb-aabb-aabbaabbaabb", - "publisherName": "microsoft.com", - "type": "Interface", - "updatedOn": "2019-07-09T21:06:00.072063+00:00", - "urnId": "urn:example:interfaces:MXChip:1", - "version": 1, - } - ], - } - payload_show = { - "@id": "urn:example:interfaces:MXChip:1", - "@type": "Interface", - "displayName": "MXChip 1", - "contents": [ - { - "@type": "Property", - "displayName": "Die Number", - "name": "dieNumber", - "schema": "double", - } - ], - "@context": "http://azureiot.com/v1/contexts/Interface.json", - } - test_side_effect = [ - build_mock_response(mocker, request.param[0], payload=payload_list), - build_mock_response(mocker, request.param[1], payload=payload_show), - build_mock_response(mocker, request.param[2], {}), - ] - service_client.side_effect = test_side_effect - return service_client - - @pytest.mark.parametrize("target_interface", [(_pnp_generic_interface_id)]) - def test_interface_publish(self, fixture_cmd, serviceclient, target_interface): - subject.iot_pnp_interface_publish( - fixture_cmd, interface=target_interface, login=mock_target["cs"] - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - data = args[0][0].data - headers = args[0][0].headers - - assert method == "PUT" - assert ( - "{}/models/{}?".format( - _repo_endpoint, url_encode_str(_pnp_generic_interface_id, plus=True) - ) - in url - ) - assert json.loads(data)["@id"] == _pnp_generic_interface_id - assert headers.get("Authorization") - - @pytest.mark.parametrize("target_interface", [("acv.17")]) - def test_interface_publish_error( - self, fixture_cmd, serviceclient_generic_error, target_interface - ): - with pytest.raises(CLIError): - subject.iot_pnp_interface_publish( - fixture_cmd, interface=target_interface, login=mock_target["cs"] - ) - - -class TestModelRepoInterfaceShow(object): - @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - payload = { - "@id": "urn:example:interfaces:MXChip:1", - "@type": "Interface", - "displayName": "MXChip 1", - "contents": [ - { - "@type": "Property", - "displayName": "Die Number", - "name": "dieNumber", - "schema": "double", - } - ], - "@context": "http://azureiot.com/v1/contexts/Interface.json", - } - service_client.return_value = build_mock_response( - mocker, request.param, payload - ) - return service_client - - @pytest.mark.parametrize("target_interface", [(_pnp_generic_interface_id)]) - def test_interface_show(self, fixture_cmd, serviceclient, target_interface): - result = subject.iot_pnp_interface_show( - fixture_cmd, - interface=target_interface, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - headers = args[0][0].headers - - assert method == "GET" - assert ( - "{}/models/{}?".format( - _repo_endpoint, url_encode_str(_pnp_generic_interface_id, plus=True) - ) - in url - ) - assert "repositoryId={}".format(_repo_id) in url - assert json.dumps(result) - assert headers.get("Authorization") - - @pytest.fixture(params=[200]) - def serviceclientemptyresult(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {}) - return service_client - - @pytest.mark.parametrize("target_interface", [(_pnp_generic_interface_id)]) - def test_interface_show_error( - self, fixture_cmd, serviceclientemptyresult, target_interface - ): - with pytest.raises(CLIError): - subject.iot_pnp_interface_show( - fixture_cmd, - interface=target_interface, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - - -class TestModelRepoInterfaceList(object): - @pytest.fixture(params=[200]) - def service_client(self, mocker, fixture_ghcs, request): - serviceclient = mocker.patch(path_service_client) - payload = { - "continuationToken": "null", - "results": [ - { - "comment": "", - "createdOn": "2019-07-09T07:46:06.044161+00:00", - "description": "", - "displayName": "MXChip 1", - "etag": '"41006e67-0000-0800-0000-5d2501b80000"', - "modelName": "example:interfaces:MXChip", - "publisherId": "aabbaabb-aabb-aabb-aabb-aabbaabbaabb", - "publisherName": "microsoft.com", - "type": "Interface", - "updatedOn": "2019-07-09T21:06:00.072063+00:00", - "urnId": "urn:example:interfaces:MXChip:1", - "version": 1, - } - ], - } - serviceclient.return_value = build_mock_response(mocker, request.param, payload) - return serviceclient - - def test_interface_list(self, fixture_cmd, service_client): - result = subject.iot_pnp_interface_list( - fixture_cmd, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - args = service_client.call_args - url = args[0][0].url - method = args[0][0].method - headers = args[0][0].headers - - assert method == "POST" - assert "{}/models/search?".format(_repo_endpoint) in url - assert "repositoryId={}".format(_repo_id) in url - assert len(result) == 1 - assert headers.get("Authorization") - - def test_interface_list_error(self, fixture_cmd, serviceclient_generic_error): - with pytest.raises(CLIError): - subject.iot_pnp_interface_list( - fixture_cmd, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - - -class TestModelRepoInterfaceDelete(object): - @pytest.fixture(params=[204]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {}) - return service_client - - @pytest.mark.parametrize("target_interface", [(_pnp_generic_interface_id)]) - def test_interface_delete(self, fixture_cmd, serviceclient, target_interface): - subject.iot_pnp_interface_delete( - fixture_cmd, - interface=target_interface, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - headers = args[0][0].headers - - assert method == "DELETE" - assert ( - "{}/models/{}?".format( - _repo_endpoint, url_encode_str(_pnp_generic_interface_id, plus=True) - ) - in url - ) - assert "repositoryId={}".format(_repo_id) in url - assert headers.get("Authorization") - - @pytest.mark.parametrize("target_interface", [("acv.17")]) - def test_model_delete_error( - self, fixture_cmd, serviceclient_generic_error, target_interface - ): - with pytest.raises(CLIError): - subject.iot_pnp_interface_delete( - fixture_cmd, - interface=target_interface, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - - -def generate_pnp_model_create_payload(content_from_file=False): - change_dir() - if content_from_file: - return (None, _pnp_create_model_payload_file) - - return ( - str(read_file_content(_pnp_create_model_payload_file)), - _pnp_create_model_payload_file, - ) - - -class TestModelRepoModelCreate(object): - @pytest.fixture(params=[201, 204, 412]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {}) - return service_client - - @pytest.mark.parametrize( - "payload_scenario", - [ - (generate_pnp_model_create_payload()), - (generate_pnp_model_create_payload(content_from_file=True)), - ], - ) - def test_model_create(self, fixture_cmd, serviceclient, payload_scenario): - - payload = None - - # If file path provided - if not payload_scenario[0]: - payload = payload_scenario[1] - else: - payload = str(read_file_content(_pnp_create_model_payload_file)) - - subject.iot_pnp_model_create( - fixture_cmd, - model_definition=payload, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - data = args[0][0].data - headers = args[0][0].headers - - assert method == "PUT" - assert ( - "{}/models/{}?".format( - _repo_endpoint, url_encode_str(_pnp_generic_model_id, plus=True) - ) - in url - ) - assert "repositoryId={}".format(_repo_id) in url - assert json.dumps(data) - assert headers.get("Authorization") - - @pytest.mark.parametrize( - "payload_scenario", - [ - (generate_pnp_model_create_payload()), - (generate_pnp_model_create_payload(content_from_file=True)), - ], - ) - def test_model_create_error( - self, fixture_cmd, serviceclient_generic_error, payload_scenario - ): - if not payload_scenario[0]: - payload = payload_scenario[1] - else: - payload = str(read_file_content(_pnp_create_model_payload_file)) - with pytest.raises(CLIError): - subject.iot_pnp_model_create( - fixture_cmd, - model_definition=payload, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - - @pytest.mark.parametrize( - "payload_scenario, exp", [(generate_pnp_model_create_payload(), CLIError)] - ) - def test_model_create_invalid_args(self, serviceclient, payload_scenario, exp): - with pytest.raises(exp): - subject.iot_pnp_model_create(fixture_cmd, model_definition=payload_scenario) - - def test_model_create_invalid_payload(self, serviceclient): - - payload = str(read_file_content(_pnp_create_model_payload_file)) - payload = json.loads(payload) - del payload["@id"] - payload = json.dumps(payload) - with pytest.raises(CLIError): - subject.iot_pnp_model_create( - fixture_cmd, login=mock_target["cs"], model_definition=payload - ) - - -class TestModelRepoModelUpdate(object): - @pytest.fixture(params=[(200, 201), (200, 204), (200, 412)]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - payload = { - "continuationToken": "null", - "results": [ - { - "comment": "", - "createdOn": "2019-07-09T07:46:06.044161+00:00", - "description": "", - "displayName": "Mxchip 1", - "etag": '"41006e67-0000-0800-0000-5d2501b80000"', - "modelName": "example:capabilityModels:Mxchip", - "publisherId": "aabbaabb-aabb-aabb-aabb-aabbaabbaabb", - "publisherName": "microsoft.com", - "type": "CapabilityModel", - "updatedOn": "2019-07-09T21:06:00.072063+00:00", - "urnId": "urn:example:capabilityModels:Mxchip:1", - "version": 1, - } - ], - } - test_side_effect = [ - build_mock_response(mocker, request.param[0], payload=payload), - build_mock_response(mocker, request.param[1], {}), - ] - service_client.side_effect = test_side_effect - return service_client - - @pytest.mark.parametrize( - "payload_scenario", - [ - generate_pnp_model_create_payload(), - generate_pnp_model_create_payload(content_from_file=True), - ], - ) - def test_model_update(self, fixture_cmd, serviceclient, payload_scenario): - - payload = None - - # If file path provided - if not payload_scenario[0]: - payload = payload_scenario[1] - else: - payload = str(read_file_content(_pnp_create_model_payload_file)) - - subject.iot_pnp_model_update( - fixture_cmd, - model_definition=payload, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - data = args[0][0].data - headers = args[0][0].headers - - assert method == "PUT" - assert ( - "{}/models/{}?".format( - _repo_endpoint, url_encode_str(_pnp_generic_model_id, plus=True) - ) - in url - ) - assert "repositoryId={}".format(_repo_id) in url - assert json.dumps(data) - assert headers.get("Authorization") - - @pytest.mark.parametrize( - "payload_scenario", - [ - (generate_pnp_model_create_payload()), - (generate_pnp_model_create_payload(content_from_file=True)), - ], - ) - def test_model_update_error( - self, fixture_cmd, serviceclient_generic_error, payload_scenario - ): - if not payload_scenario[0]: - payload = payload_scenario[1] - else: - payload = str(read_file_content(_pnp_create_model_payload_file)) - with pytest.raises(CLIError): - subject.iot_pnp_model_update( - fixture_cmd, - model_definition=payload, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - - @pytest.mark.parametrize( - "payload_scenario, exp", [(generate_pnp_model_create_payload(), CLIError)] - ) - def test_model_update_invalid_args(self, serviceclient, payload_scenario, exp): - with pytest.raises(exp): - subject.iot_pnp_model_update(fixture_cmd, model_definition=payload_scenario) - - def test_model_update_invalid_payload(self, serviceclient): - - payload = str(read_file_content(_pnp_create_model_payload_file)) - payload = json.loads(payload) - payload["@id"] = "fake_invalid_id" - payload = json.dumps(payload) - with pytest.raises(CLIError): - subject.iot_pnp_model_update( - fixture_cmd, - model_definition=payload, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - - -class TestModelRepoModelPublish(object): - @pytest.fixture(params=[(200, 200, 201), (200, 200, 204), (200, 200, 412)]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - payload_list = { - "continuationToken": "null", - "results": [ - { - "comment": "", - "createdOn": "2019-07-09T07:46:06.044161+00:00", - "description": "", - "displayName": "Mxchip 1", - "etag": '"41006e67-0000-0800-0000-5d2501b80000"', - "modelName": "example:capabilityModels:Mxchip", - "publisherId": "aabbaabb-aabb-aabb-aabb-aabbaabbaabb", - "publisherName": "microsoft.com", - "type": "CapabilityModel", - "updatedOn": "2019-07-09T21:06:00.072063+00:00", - "urnId": "urn:example:capabilityModels:Mxchip:1", - "version": 1, - } - ], - } - payload_show = { - "@id": "urn:example:capabilityModels:Mxchip:1", - "@type": "CapabilityModel", - "displayName": "Mxchip 1", - "implements": [ - {"schema": "urn:example:interfaces:MXChip:1", "name": "MXChip1"} - ], - "@context": "http://azureiot.com/v1/contexts/CapabilityModel.json", - } - test_side_effect = [ - build_mock_response(mocker, request.param[0], payload=payload_list), - build_mock_response(mocker, request.param[1], payload=payload_show), - build_mock_response(mocker, request.param[2], {}), - ] - service_client.side_effect = test_side_effect - return service_client - - @pytest.mark.parametrize("target_model", [(_pnp_generic_model_id)]) - def test_model_publish(self, fixture_cmd, serviceclient, target_model): - subject.iot_pnp_model_publish( - fixture_cmd, model=target_model, login=mock_target["cs"] - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - data = args[0][0].data - headers = args[0][0].headers - - assert method == "PUT" - assert ( - "{}/models/{}?".format( - _repo_endpoint, url_encode_str(_pnp_generic_model_id, plus=True) - ) - in url - ) - assert json.loads(data)["@id"] == _pnp_generic_model_id - assert headers.get("Authorization") - - @pytest.mark.parametrize("target_model", [("acv.17")]) - def test_model_publish_error( - self, fixture_cmd, serviceclient_generic_error, target_model - ): - with pytest.raises(CLIError): - subject.iot_pnp_model_publish( - fixture_cmd, model=target_model, login=mock_target["cs"] - ) - - -class TestModelRepoModelShow(object): - @pytest.fixture(params=[200]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - payload = { - "@id": "urn:example:capabilityModels:Mxchip:1", - "@type": "CapabilityModel", - "displayName": "Mxchip 1", - "implements": [ - {"schema": "urn:example:interfaces:MXChip:1", "name": "MXChip1"} - ], - "@context": "http://azureiot.com/v1/contexts/CapabilityModel.json", - } - service_client.return_value = build_mock_response( - mocker, request.param, payload=payload - ) - return service_client - - @pytest.mark.parametrize("target_model", [(_pnp_generic_model_id)]) - def test_model_show(self, fixture_cmd, serviceclient, target_model): - result = subject.iot_pnp_model_show( - fixture_cmd, - model=target_model, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - headers = args[0][0].headers - - assert method == "GET" - assert ( - "{}/models/{}?".format( - _repo_endpoint, url_encode_str(_pnp_generic_model_id, plus=True) - ) - in url - ) - assert "repositoryId={}".format(_repo_id) in url - assert json.dumps(result) - assert headers.get("Authorization") - - @pytest.mark.parametrize("target_model", [("acv:17")]) - def test_model_show_error( - self, fixture_cmd, serviceclient_generic_error, target_model - ): - with pytest.raises(CLIError): - subject.iot_pnp_model_show( - fixture_cmd, - model=target_model, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - - @pytest.fixture(params=[200]) - def serviceclientemptyresult(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {}) - return service_client - - @pytest.mark.parametrize("target_model", [(_pnp_generic_model_id)]) - def test_model_show_no_result( - self, fixture_cmd, serviceclientemptyresult, target_model - ): - with pytest.raises(CLIError): - subject.iot_pnp_model_show( - fixture_cmd, - model=target_model, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - - -class TestModelRepoModelList(object): - @pytest.fixture(params=[200]) - def service_client(self, mocker, fixture_ghcs, request): - serviceclient = mocker.patch(path_service_client) - payload = { - "continuationToken": "null", - "results": [ - { - "comment": "", - "createdOn": "2019-07-09T07:46:06.044161+00:00", - "description": "", - "displayName": "Mxchip 1", - "etag": '"41006e67-0000-0800-0000-5d2501b80000"', - "modelName": "example:capabilityModels:Mxchip", - "publisherId": "aabbaabb-aabb-aabb-aabb-aabbaabbaabb", - "publisherName": "microsoft.com", - "type": "CapabilityModel", - "updatedOn": "2019-07-09T21:06:00.072063+00:00", - "urnId": "urn:example:capabilityModels:Mxchip:1", - "version": 1, - } - ], - } - serviceclient.return_value = build_mock_response(mocker, request.param, payload) - return serviceclient - - def test_model_list(self, fixture_cmd, service_client): - result = subject.iot_pnp_model_list( - fixture_cmd, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - args = service_client.call_args - url = args[0][0].url - method = args[0][0].method - headers = args[0][0].headers - - assert method == "POST" - assert "{}/models/search?".format(_repo_endpoint) in url - assert "repositoryId={}".format(_repo_id) in url - assert len(result) == 1 - assert headers.get("Authorization") - - def test_model_list_error(self, fixture_cmd, serviceclient_generic_error): - with pytest.raises(CLIError): - subject.iot_pnp_model_list( - fixture_cmd, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - - -class TestModelRepoModelDelete(object): - @pytest.fixture(params=[204]) - def serviceclient(self, mocker, fixture_ghcs, request): - service_client = mocker.patch(path_service_client) - service_client.return_value = build_mock_response(mocker, request.param, {}) - return service_client - - @pytest.mark.parametrize("target_model", [(_pnp_generic_model_id)]) - def test_model_delete(self, fixture_cmd, serviceclient, target_model): - subject.iot_pnp_model_delete( - fixture_cmd, - model=target_model, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) - args = serviceclient.call_args - url = args[0][0].url - method = args[0][0].method - headers = args[0][0].headers - - assert method == "DELETE" - assert ( - "{}/models/{}?".format( - _repo_endpoint, url_encode_str(_pnp_generic_model_id, plus=True) - ) - in url - ) - assert "repositoryId={}".format(_repo_id) in url - assert headers.get("Authorization") - - @pytest.mark.parametrize("target_model", [("acv.17")]) - def test_model_delete_error( - self, fixture_cmd, serviceclient_generic_error, target_model - ): - with pytest.raises(CLIError): - subject.iot_pnp_model_delete( - fixture_cmd, - model=target_model, - repo_endpoint=mock_target["entity"], - repo_id=mock_target["repository_id"], - ) diff --git a/azext_iot/tests/test_pnp_create_payload_interface.json b/azext_iot/tests/test_pnp_create_payload_interface.json deleted file mode 100644 index feb634998..000000000 --- a/azext_iot/tests/test_pnp_create_payload_interface.json +++ /dev/null @@ -1,201 +0,0 @@ -{ - "@id": "urn:example:interfaces:MXChip:1", - "@type": "Interface", - "displayName": "MXChip1", - "contents": [{ - "@type": "Property", - "name": "dieNumber", - "displayName": "Die Number", - "schema": "double" - }, - { - "@type": "Property", - "name": "setCurrent", - "displayName": "Current", - "writable": true, - "schema": "double", - "displayUnit": "amps" - }, - { - "@type": "Property", - "name": "setVoltage", - "displayName": "Voltage", - "writable": true, - "schema": "double", - "displayUnit": "volts" - }, - { - "@type": "Property", - "name": "fanSpeed", - "displayName": "Fan Speed", - "writable": true, - "schema": "double", - "displayUnit": "rpm" - }, - { - "@type": "Property", - "name": "activateIR", - "displayName": "IR", - "writable": true, - "schema": "boolean" - }, - { - "@type": "Telemetry", - "name": "humidity", - "displayName": "Humidity", - "schema": "double", - "displayUnit": "%" - }, - { - "@type": "Telemetry", - "name": "pressure", - "displayName": "Pressure", - "schema": "double", - "displayUnit": "hPa" - }, - { - "@type": "Telemetry", - "name": "magnetometer", - "displayName": "Magnetometer", - "schema": { - "@type": "Object", - "fields": [{ - "name": "x", - "schema": "double", - "displayUnit": "mgauss" - }, - { - "name": "y", - "schema": "double", - "displayUnit": "mgauss" - }, - { - "name": "z", - "schema": "double", - "displayUnit": "mgauss" - } - ] - } - }, - { - "@type": "Telemetry", - "name": "accelerometer", - "displayName": "Accelerometer", - "schema": { - "@type": "Object", - "fields": [{ - "name": "x", - "schema": "double", - "displayUnit": "mg" - }, - { - "name": "y", - "schema": "double", - "displayUnit": "mg" - }, - { - "name": "z", - "schema": "double", - "displayUnit": "mg" - } - ] - } - }, - { - "@type": "Telemetry", - "name": "gyroscope", - "displayName": "Gyroscope", - "schema": { - "@type": "Object", - "fields": [{ - "name": "x", - "schema": "double", - "displayUnit": "mdps" - }, - { - "name": "y", - "schema": "double", - "displayUnit": "mdps" - }, - { - "name": "z", - "schema": "double", - "displayUnit": "mdps" - } - ] - } - }, - { - "@type": "Telemetry", - "name": "buttonBPressed", - "displayName": "Button B Pressed", - "schema": "string" - }, - { - "@type": "Telemetry", - "name": "deviceState", - "displayName": "Device State", - "schema": { - "@type": "Enum", - "valueSchema": "string", - "enumValues": [{ - "name": "normal", - "displayName": "Normal", - "enumValue": "NORMAL" - }, - { - "name": "danger", - "displayName": "Danger", - "enumValue": "DANGER" - }, - { - "name": "caution", - "displayName": "Caution", - "enumValue": "CAUTION" - } - ] - } - }, - { - "@type": "Command", - "name": "echo", - "displayName": "Echo", - "request": { - "name": "name1", - "schema": { - "@type": "Object", - "fields": [{ - "name": "displayedValue", - "displayName": "Value to display", - "schema": "string" - }] - } - }, - "response": { - "name": "name2", - "schema": "string" - } - }, - { - "@type": "Command", - "name": "countdown", - "displayName": "Countdown", - "request": { - "name": "name1", - "schema": { - "@type": "Object", - "fields": [{ - "name": "countFrom", - "displayName": "Count from", - "schema": "double" - }] - } - }, - "response": { - "name": "name3", - "schema": "double" - } - } - ], - "@context": "http://azureiot.com/v1/contexts/IoTModel.json" -} \ No newline at end of file diff --git a/azext_iot/tests/test_pnp_create_payload_model.json b/azext_iot/tests/test_pnp_create_payload_model.json deleted file mode 100644 index 1ba329d4d..000000000 --- a/azext_iot/tests/test_pnp_create_payload_model.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@id": "urn:example:capabilityModels:Mxchip:1", - "@type": "CapabilityModel", - "displayName": "Mxchip 1", - "implements": [{ - "schema": "urn:example:interfaces:MXChip:1", - "name": "MXChip1" - }], - "@context": "http://azureiot.com/v1/contexts/IoTModel.json" -} \ No newline at end of file diff --git a/azext_iot/tests/test_pnp_interface_list.json b/azext_iot/tests/test_pnp_interface_list.json deleted file mode 100644 index a0da071cd..000000000 --- a/azext_iot/tests/test_pnp_interface_list.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "continuationToken": null, - "results": [{ - "comment": "Requires temperature and humidity sensors.", - "createdOn": "2019-07-03T22:42:21.929126+00:00", - "description": "Provides functionality to report temperature, humidity. Provides telemetry, commands and read-write properties", - "displayName": "Environmental Sensor", - "etag": "\"110043fc-0000-0800-0000-5d27a3960000\"", - "modelName": "contoso:com:EnvironmentalSensor", - "publisherId": "72f988bf-86f1-41af-91ab-2d7cd011db47", - "publisherName": "microsoft.com", - "type": "Interface", - "updatedOn": "2019-07-03T22:42:21.929126+00:00", - "urnId": "urn:contoso:com:EnvironmentalSensor:1", - "version": 1 - }] -} \ No newline at end of file diff --git a/azext_iot/tests/test_pnp_interface_show.json b/azext_iot/tests/test_pnp_interface_show.json deleted file mode 100644 index 1e93f496d..000000000 --- a/azext_iot/tests/test_pnp_interface_show.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "@context": "http://azureiot.com/v1/contexts/IoTModel.json", - "@id": "urn:contoso:com:EnvironmentalSensor:1", - "@type": "Interface", - "comment": "Requires temperature and humidity sensors.", - "contents": [{ - "@type": "Property", - "description": "The state of the device. Two states online/offline are available.", - "displayName": "Device State", - "name": "state", - "schema": "boolean" - }, - { - "@type": "Property", - "description": "The name of the customer currently operating the device.", - "displayName": "Customer Name", - "name": "name", - "schema": "string", - "writable": true - }, - { - "@type": "Property", - "description": "The brightness level for the light on the device. Can be specified as 1 (high), 2 (medium), 3 (low)", - "displayName": "Brightness Level", - "name": "brightness", - "schema": "long", - "writable": true - }, - { - "@type": [ - "Telemetry", - "SemanticType/Temperature" - ], - "description": "Current temperature on the device", - "displayName": "Temperature", - "name": "temp", - "schema": "double", - "unit": "Units/Temperature/fahrenheit" - }, - { - "@type": [ - "Telemetry", - "SemanticType/Humidity" - ], - "description": "Current humidity on the device", - "displayName": "Humidity", - "name": "humid", - "schema": "double", - "unit": "Units/Humidity/percent" - }, - { - "@type": "Command", - "commandType": "synchronous", - "description": "This command will begin blinking the LED for given time interval.", - "name": "blink", - "request": { - "name": "blinkRequest", - "schema": "long" - }, - "response": { - "name": "blinkResponse", - "schema": "string" - } - }, - { - "@type": "Command", - "commandType": "synchronous", - "comment": "This Commands will turn-on the LED light on the device.", - "name": "turnon", - "response": { - "name": "turnonResponse", - "schema": "string" - } - }, - { - "@type": "Command", - "commandType": "synchronous", - "comment": "This Commands will turn-off the LED light on the device.", - "name": "turnoff", - "response": { - "name": "turnoffResponse", - "schema": "string" - } - } - ], - "description": "Provides functionality to report temperature, humidity. Provides telemetry, commands and read-write properties", - "displayName": "Environmental Sensor" -} \ No newline at end of file diff --git a/azext_iot/tests/test_iot_utility_unit.py b/azext_iot/tests/utility/test_iot_utility_unit.py similarity index 64% rename from azext_iot/tests/test_iot_utility_unit.py rename to azext_iot/tests/utility/test_iot_utility_unit.py index a2ed83f86..df943f6d8 100644 --- a/azext_iot/tests/test_iot_utility_unit.py +++ b/azext_iot/tests/utility/test_iot_utility_unit.py @@ -1,34 +1,49 @@ -import pytest +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + import json +import mock +import pytest +import os + from knack.util import CLIError from azure.cli.core.extension import get_extension_path -from azext_iot.common.utility import validate_min_python_version +from azext_iot.common.utility import ( + validate_min_python_version, + process_json_arg, + read_file_content, + logger, + ensure_iothub_sdk_min_version, +) from azext_iot.common.deps import ensure_uamqp -from azext_iot._validators import mode2_iot_login_handler from azext_iot.constants import EVENT_LIB, EXTENSION_NAME -from azext_iot.common.utility import process_json_arg, read_file_content, logger +from azext_iot._validators import mode2_iot_login_handler +from azext_iot.common.embedded_cli import EmbeddedCLI class TestMinPython(object): @pytest.mark.parametrize("pymajor, pyminor", [(3, 6), (3, 4), (2, 7)]) - def test_min_python(self, mocker, pymajor, pyminor): - version_mock = mocker.patch("azext_iot.common.utility.sys.version_info") - version_mock.major = pymajor - version_mock.minor = pyminor + def test_min_python(self, pymajor, pyminor): + with mock.patch("azext_iot.common.utility.sys.version_info") as version_mock: + version_mock.major = pymajor + version_mock.minor = pyminor - assert validate_min_python_version(2, 7) + assert validate_min_python_version(2, 7) @pytest.mark.parametrize( "pymajor, pyminor, exception", [(3, 6, SystemExit), (3, 4, SystemExit), (2, 7, SystemExit)], ) - def test_min_python_error(self, mocker, pymajor, pyminor, exception): - version_mock = mocker.patch("azext_iot.common.utility.sys.version_info") - version_mock.major = 2 - version_mock.minor = 6 + def test_min_python_error(self, pymajor, pyminor, exception): + with mock.patch("azext_iot.common.utility.sys.version_info") as version_mock: + version_mock.major = 2 + version_mock.minor = 6 - with pytest.raises(exception): - validate_min_python_version(pymajor, pyminor) + with pytest.raises(exception): + validate_min_python_version(pymajor, pyminor) class TestMode2Handler(object): @@ -42,7 +57,7 @@ class TestMode2Handler(object): {"dps_name": None, "login": "connection_string"}, {"dps_name": "mydps", "login": "connection_string"}, {"dps_name": None, "login": None}, - {"cmd.name": "webapp", "login": None} + {"cmd.name": "webapp", "login": None}, ] ) def mode2_scenario(self, mocker, request): @@ -226,7 +241,8 @@ def test_inline_json_fail(self, content, argname): ) @pytest.mark.parametrize( - "content, argname", [("iothub/configurations/test_adm_device_content.json", "myarg0")] + "content, argname", + [("../iothub/configurations/test_adm_device_content.json", "myarg0")], ) def test_file_json(self, content, argname, set_cwd): result = process_json_arg(content, argument_name=argname) @@ -251,7 +267,7 @@ def test_file_json_fail_invalidpath(self, content, argname, set_cwd, mocker): ) assert mocked_util_logger.call_args[0][1] == argname - @pytest.mark.parametrize("content, argname", [("generators.py", "myarg0")]) + @pytest.mark.parametrize("content, argname", [("../generators.py", "myarg0")]) def test_file_json_fail_invalidcontent(self, content, argname, set_cwd, mocker): mocked_util_logger = mocker.patch.object(logger, "warning", autospec=True) @@ -264,3 +280,102 @@ def test_file_json_fail_invalidcontent(self, content, argname, set_cwd, mocker): ) ) assert mocked_util_logger.call_count == 0 + + +class TestVersionComparison(object): + @pytest.mark.parametrize( + "current, minimum, expected", + [ + ("1.0", "2.0", False), + ("1.8.7", "1.8.6.4", True), + ("1.0+a", "1.0", True), + ("1.0+a", "1.0+b", False), + ("1.0", "1.0", True), + ("2.0.1.9", "2.0.6", False), + ], + ) + def test_ensure_iothub_sdk_min_version(self, mocker, current, minimum, expected): + try: + mocker.patch("azure.mgmt.iothub.__version__", current) + except: + mocker.patch("azure.mgmt.iothub._configuration.VERSION", current) + + assert ensure_iothub_sdk_min_version(minimum) == expected + + +class TestEmbeddedCli(object): + @pytest.fixture(params=[0, 1]) + def mocked_azclient(self, mocker, request): + def mock_invoke(args, out_file): + out_file.write(json.dumps({"generickey": "genericvalue"})) + return request.param + + azclient = mocker.patch("azext_iot.common.embedded_cli.get_default_cli") + azclient.return_value.invoke.side_effect = mock_invoke + azclient.test_meta.error_code = request.param + return azclient + + @pytest.mark.parametrize( + "command, subscription", + [ + ("iot hub device-identity create -n abcd -d dcba", None), + ( + "iot hub device-twin show -n 'abcd' -d 'dcba'", + "20a300e5-a444-4130-bb5a-1abd08ad930a", + ), + ], + ) + def test_embedded_cli(self, mocked_azclient, command, subscription): + import shlex + + cli = EmbeddedCLI() + cli.invoke(command=command, subscription=subscription) + + # Due to forced json output + command += " -o json" + + if subscription: + command += " --subscription '{}'".format(subscription) + + expected_args = shlex.split(command) + call = mocked_azclient().invoke.call_args_list[0] + actual_args, _ = call + assert expected_args == actual_args[0] + success = cli.success() + + if mocked_azclient.test_meta.error_code == 1: + assert not success + elif mocked_azclient.test_meta.error_code == 0: + assert success + + assert cli.output + assert cli.as_json() + + +class TestCliInit(object): + def test_package_init(self): + from azext_iot.constants import EXTENSION_ROOT + + tests_root = "tests" + directory_structure = {} + + def _validate_directory(path): + for entry in os.scandir(path): + if entry.is_dir(follow_symlinks=False) and all( + [not entry.name.startswith("__"), tests_root not in entry.path] + ): + directory_structure[entry.path] = None + _validate_directory(entry.path) + else: + if entry.path.endswith("__init__.py"): + directory_structure[os.path.dirname(entry.path)] = entry.path + + _validate_directory(EXTENSION_ROOT) + + invalid_directories = [] + for directory in directory_structure: + if directory_structure[directory] is None: + invalid_directories.append("Directory: '{}' missing __init__.py".format(directory)) + + if invalid_directories: + pytest.fail(", ".join(invalid_directories)) diff --git a/azext_iot/tests/utility/test_monitor_parsers_unit.py b/azext_iot/tests/utility/test_monitor_parsers_unit.py new file mode 100644 index 000000000..3d1622181 --- /dev/null +++ b/azext_iot/tests/utility/test_monitor_parsers_unit.py @@ -0,0 +1,624 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import json +import mock +import pytest + +from uamqp.message import Message, MessageProperties +from azext_iot.central.providers import ( + CentralDeviceProvider, + CentralDeviceTemplateProvider, +) +from azext_iot.central.models.template import Template +from azext_iot.central.models.device import Device +from azext_iot.monitor.parsers import common_parser, central_parser +from azext_iot.monitor.parsers import strings +from azext_iot.monitor.models.arguments import CommonParserArguments +from azext_iot.monitor.models.enum import Severity +from azext_iot.tests.helpers import load_json +from azext_iot.tests.test_constants import FileNames + + +def _encode_app_props(app_props: dict): + return {key.encode(): value.encode() for key, value in app_props.items()} + + +def _validate_issues( + parser: common_parser.CommonParser, + severity: Severity, + expected_total_issues: int, + expected_specified_issues: int, + expected_detailss: list, +): + issues = parser.issues_handler.get_all_issues() + specified_issues = parser.issues_handler.get_issues_with_severity(severity) + assert len(issues) == expected_total_issues + assert len(specified_issues) == expected_specified_issues + + actual_messages = [issue.details for issue in specified_issues] + for expected_details in expected_detailss: + assert expected_details in actual_messages + + +@pytest.fixture( + params=[ + common_parser.INTERFACE_NAME_IDENTIFIER_V1, + common_parser.INTERFACE_NAME_IDENTIFIER_V2, + ] +) +def interface_identifier_bytes(request): + return request.param + + +class TestCommonParser: + device_id = "some-device-id" + payload = {"String": "someValue"} + encoding = "UTF-8" + content_type = "application/json" + + bad_encoding = "ascii" + bad_payload = "{bad-payload" + bad_content_type = "bad-content-type" + + @pytest.mark.parametrize( + "device_id, encoding, content_type, interface_name, component_name, " + "module_id, payload, properties, app_properties", + [ + ( + "device-id", + "utf-8", + "application/json", + "interface_name", + "component_name", + "module-id", + {"payloadKey": "payloadValue"}, + {"propertiesKey": "propertiesValue"}, + {"appPropsKey": "appPropsValue"}, + ), + ( + "device-id", + "utf-8", + "application/json", + "interface_name", + "component_name", + "", + {"payloadKey": "payloadValue"}, + {"propertiesKey": "propertiesValue"}, + {"appPropsKey": "appPropsValue"}, + ), + ( + "device-id", + "utf-8", + "application/json", + "interface_name", + "", + "", + {"payloadKey": "payloadValue"}, + {"propertiesKey": "propertiesValue"}, + {"appPropsKey": "appPropsValue"}, + ), + ( + "device-id", + "utf-8", + "application/json", + "", + "", + "", + {"payloadKey": "payloadValue"}, + {"propertiesKey": "propertiesValue"}, + {"appPropsKey": "appPropsValue"}, + ), + ( + "device-id", + "utf-8", + "application/json", + "", + "", + "", + {}, + {"propertiesKey": "propertiesValue"}, + {"appPropsKey": "appPropsValue"}, + ), + ( + "device-id", + "utf-8", + "application/json", + "", + "", + "", + {}, + {}, + {"appPropsKey": "appPropsValue"}, + ), + ("device-id", "utf-8", "application/json", "", "", "", {}, {}, {}), + ], + ) + def test_parse_message_should_succeed( + self, + device_id, + encoding, + content_type, + interface_name, + component_name, + payload, + properties, + app_properties, + module_id, + interface_identifier_bytes, + ): + # setup + properties = MessageProperties( + content_encoding=encoding, content_type=content_type + ) + message = Message( + body=json.dumps(payload).encode(), + properties=properties, + annotations={ + common_parser.DEVICE_ID_IDENTIFIER: device_id.encode(), + interface_identifier_bytes: interface_name.encode(), + common_parser.MODULE_ID_IDENTIFIER: module_id.encode(), + common_parser.COMPONENT_NAME_IDENTIFIER: component_name.encode(), + }, + application_properties=_encode_app_props(app_properties), + ) + args = CommonParserArguments(properties=["all"], content_type=content_type) + parser = common_parser.CommonParser(message=message, common_parser_args=args) + + # act + parsed_msg = parser.parse_message() + + # verify + assert parsed_msg["event"]["payload"] == payload + assert parsed_msg["event"]["origin"] == device_id + device_identifier = str(common_parser.DEVICE_ID_IDENTIFIER, "utf8") + assert parsed_msg["event"]["annotations"][device_identifier] == device_id + module_identifier = str(common_parser.MODULE_ID_IDENTIFIER, "utf8") + if module_id: + assert parsed_msg["event"]["annotations"][module_identifier] == module_id + else: + assert not parsed_msg["event"]["annotations"].get(module_identifier) + properties = parsed_msg["event"]["properties"] + assert properties["system"]["content_encoding"] == encoding + assert properties["system"]["content_type"] == content_type + assert properties["application"] == app_properties + + assert parsed_msg["event"]["interface"] == interface_name + assert parsed_msg["event"]["component"] == component_name + + if interface_name: + interface_identifier = str(interface_identifier_bytes, "utf8") + assert ( + parsed_msg["event"]["annotations"][interface_identifier] + == interface_name + ) + + if component_name: + component_identifier = str(common_parser.COMPONENT_NAME_IDENTIFIER, "utf8") + assert ( + parsed_msg["event"]["annotations"][component_identifier] + == component_name + ) + + assert len(parser.issues_handler.get_all_issues()) == 0 + + def test_parse_message_bad_content_type_should_warn(self): + # setup + encoded_payload = json.dumps(self.payload).encode() + properties = MessageProperties(content_type=self.bad_content_type) + message = Message( + body=encoded_payload, + properties=properties, + annotations={common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode()}, + ) + args = CommonParserArguments(content_type="application/json") + parser = common_parser.CommonParser(message=message, common_parser_args=args) + + # act + parsed_msg = parser.parse_message() + + # verify + assert parsed_msg["event"]["payload"] == self.payload + + expected_details_1 = strings.invalid_encoding_none_found() + expected_details_2 = strings.content_type_mismatch( + self.bad_content_type, "application/json" + ) + _validate_issues( + parser, Severity.warning, 2, 2, [expected_details_1, expected_details_2], + ) + + def test_parse_bad_type_and_bad_payload_should_error(self): + # setup + encoded_payload = self.bad_payload.encode() + properties = MessageProperties( + content_type=self.bad_content_type, content_encoding=self.encoding + ) + message = Message( + body=encoded_payload, + properties=properties, + annotations={common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode()}, + ) + args = CommonParserArguments(content_type="application/json") + parser = common_parser.CommonParser(message=message, common_parser_args=args) + + # act + parsed_msg = parser.parse_message() + + # verify + # since the content_encoding header is not present, just dump the raw payload + payload = str(encoded_payload, "utf8") + assert parsed_msg["event"]["payload"] == payload + + expected_details_1 = strings.content_type_mismatch( + self.bad_content_type, "application/json" + ) + _validate_issues(parser, Severity.warning, 2, 1, [expected_details_1]) + + expected_details_2 = strings.invalid_json() + _validate_issues(parser, Severity.error, 2, 1, [expected_details_2]) + + def test_parse_message_bad_encoding_should_warn(self): + # setup + properties = MessageProperties( + content_encoding=self.bad_encoding, content_type=self.content_type + ) + message = Message( + body=json.dumps(self.payload).encode(self.bad_encoding), + properties=properties, + annotations={common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode()}, + ) + args = CommonParserArguments() + parser = common_parser.CommonParser(message=message, common_parser_args=args) + + # act + parser.parse_message() + + expected_details = strings.invalid_encoding(self.bad_encoding) + _validate_issues(parser, Severity.warning, 1, 1, [expected_details]) + + def test_parse_message_bad_json_should_fail(self): + # setup + properties = MessageProperties( + content_encoding=self.encoding, content_type=self.content_type + ) + message = Message( + body=self.bad_payload.encode(), + properties=properties, + annotations={common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode()}, + ) + args = CommonParserArguments() + parser = common_parser.CommonParser(message=message, common_parser_args=args) + + # act + parsed_msg = parser.parse_message() + + # verify + # parsing should attempt to place raw payload into result even if parsing fails + assert parsed_msg["event"]["payload"] == self.bad_payload + + expected_details = strings.invalid_json() + _validate_issues(parser, Severity.error, 1, 1, [expected_details]) + + +class TestCentralParser: + device_id = "some-device-id" + payload = {"String": "someValue"} + encoding = "UTF-8" + content_type = "application/json" + app_properties = {"appPropsKey": "appPropsValue"} + component_name = "some-component-name" + + bad_encoding = "ascii" + bad_payload = "bad-payload" + bad_field_name = {"violates-regex": "someValue"} + bad_content_type = "bad-content-type" + + bad_dcm_payload = {"temperature": "someValue"} + type_mismatch_payload = {"Bool": "someValue"} + + def test_parse_message_bad_field_name_should_fail(self): + # setup + device_template = self._get_template() + + properties = MessageProperties( + content_encoding=self.encoding, content_type=self.content_type + ) + message = Message( + body=json.dumps(self.bad_field_name).encode(), + properties=properties, + annotations={common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode()}, + ) + args = CommonParserArguments() + parser = self._create_parser( + device_template=device_template, message=message, args=args + ) + + # act + parsed_msg = parser.parse_message() + + # verify + # parsing should attempt to place raw payload into result even if parsing fails + assert parsed_msg["event"]["payload"] == self.bad_field_name + + # field name contains '-' character error + expected_details_1 = strings.invalid_field_name( + list(self.bad_field_name.keys()) + ) + _validate_issues(parser, Severity.error, 2, 1, [expected_details_1]) + + # field name not present in template warning + expected_details_2 = strings.invalid_field_name_mismatch_template( + list(self.bad_field_name.keys()), device_template.schema_names + ) + + _validate_issues(parser, Severity.warning, 2, 1, [expected_details_2]) + + def test_validate_against_template_should_fail(self): + # setup + device_template = self._get_template() + + properties = MessageProperties( + content_encoding=self.encoding, content_type=self.content_type + ) + message = Message( + body=json.dumps(self.bad_dcm_payload).encode(), + properties=properties, + annotations={common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode()}, + application_properties=_encode_app_props(self.app_properties), + ) + args = CommonParserArguments(properties=["all"]) + parser = self._create_parser( + device_template=device_template, message=message, args=args + ) + + # act + parsed_msg = parser.parse_message() + + # verify + assert parsed_msg["event"]["payload"] == self.bad_dcm_payload + assert parsed_msg["event"]["origin"] == self.device_id + device_identifier = str(common_parser.DEVICE_ID_IDENTIFIER, "utf8") + assert parsed_msg["event"]["annotations"][device_identifier] == self.device_id + + properties = parsed_msg["event"]["properties"] + assert properties["system"]["content_encoding"] == self.encoding + assert properties["system"]["content_type"] == self.content_type + assert properties["application"] == self.app_properties + + expected_details = strings.invalid_field_name_mismatch_template( + list(self.bad_dcm_payload.keys()), device_template.schema_names + ) + + _validate_issues(parser, Severity.warning, 1, 1, [expected_details]) + + def test_validate_against_no_component_template_should_fail(self): + # setup + device_template = self._get_template() + + properties = MessageProperties( + content_encoding=self.encoding, content_type=self.content_type + ) + message = Message( + body=json.dumps(self.bad_dcm_payload).encode(), + properties=properties, + annotations={ + common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode(), + common_parser.COMPONENT_NAME_IDENTIFIER: self.component_name.encode(), + }, + application_properties=_encode_app_props(self.app_properties), + ) + args = CommonParserArguments(properties=["all"]) + parser = self._create_parser( + device_template=device_template, message=message, args=args + ) + + # act + parsed_msg = parser.parse_message() + + # verify + assert parsed_msg["event"]["payload"] == self.bad_dcm_payload + assert parsed_msg["event"]["origin"] == self.device_id + device_identifier = str(common_parser.DEVICE_ID_IDENTIFIER, "utf8") + assert parsed_msg["event"]["annotations"][device_identifier] == self.device_id + component_identifier = str(common_parser.COMPONENT_NAME_IDENTIFIER, "utf8") + assert ( + parsed_msg["event"]["annotations"][component_identifier] + == self.component_name + ) + properties = parsed_msg["event"]["properties"] + assert properties["system"]["content_encoding"] == self.encoding + assert properties["system"]["content_type"] == self.content_type + assert properties["application"] == self.app_properties + + expected_details = strings.invalid_component_name(self.component_name, list()) + + _validate_issues(parser, Severity.warning, 1, 1, [expected_details]) + + def test_validate_against_invalid_component_template_should_fail(self): + # setup + device_template = Template( + load_json(FileNames.central_property_validation_template_file) + ) + + properties = MessageProperties( + content_encoding=self.encoding, content_type=self.content_type + ) + message = Message( + body=json.dumps(self.bad_dcm_payload).encode(), + properties=properties, + annotations={ + common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode(), + common_parser.COMPONENT_NAME_IDENTIFIER: self.component_name.encode(), + }, + application_properties=_encode_app_props(self.app_properties), + ) + args = CommonParserArguments(properties=["all"]) + parser = self._create_parser( + device_template=device_template, message=message, args=args + ) + + # act + parsed_msg = parser.parse_message() + + # verify + assert parsed_msg["event"]["payload"] == self.bad_dcm_payload + assert parsed_msg["event"]["origin"] == self.device_id + device_identifier = str(common_parser.DEVICE_ID_IDENTIFIER, "utf8") + assert parsed_msg["event"]["annotations"][device_identifier] == self.device_id + component_identifier = str(common_parser.COMPONENT_NAME_IDENTIFIER, "utf8") + assert ( + parsed_msg["event"]["annotations"][component_identifier] + == self.component_name + ) + properties = parsed_msg["event"]["properties"] + assert properties["system"]["content_encoding"] == self.encoding + assert properties["system"]["content_type"] == self.content_type + assert properties["application"] == self.app_properties + + expected_details = strings.invalid_component_name( + self.component_name, list(device_template.components.keys()) + ) + + _validate_issues(parser, Severity.warning, 1, 1, [expected_details]) + + def test_validate_invalid_telmetry_component_template_should_fail(self): + # setup + device_template = Template( + load_json(FileNames.central_property_validation_template_file) + ) + + properties = MessageProperties( + content_encoding=self.encoding, content_type=self.content_type + ) + message = Message( + body=json.dumps(self.bad_dcm_payload).encode(), + properties=properties, + annotations={ + common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode(), + common_parser.COMPONENT_NAME_IDENTIFIER: list( + device_template.components.keys() + )[1].encode(), + }, + application_properties=_encode_app_props(self.app_properties), + ) + args = CommonParserArguments(properties=["all"]) + parser = self._create_parser( + device_template=device_template, message=message, args=args + ) + + # act + parsed_msg = parser.parse_message() + + # verify + assert parsed_msg["event"]["payload"] == self.bad_dcm_payload + assert parsed_msg["event"]["origin"] == self.device_id + device_identifier = str(common_parser.DEVICE_ID_IDENTIFIER, "utf8") + assert parsed_msg["event"]["annotations"][device_identifier] == self.device_id + component_identifier = str(common_parser.COMPONENT_NAME_IDENTIFIER, "utf8") + assert ( + parsed_msg["event"]["annotations"][component_identifier] + == list(device_template.components.keys())[1] + ) + properties = parsed_msg["event"]["properties"] + assert properties["system"]["content_encoding"] == self.encoding + assert properties["system"]["content_type"] == self.content_type + assert properties["application"] == self.app_properties + + expected_details = strings.invalid_field_name_component_mismatch_template( + list(self.bad_dcm_payload.keys()), device_template.component_schema_names, + ) + + _validate_issues(parser, Severity.warning, 1, 1, [expected_details]) + + def test_validate_against_bad_template_should_not_throw(self): + # setup + device_template = "an_unparseable_template" + + properties = MessageProperties( + content_encoding=self.encoding, content_type=self.content_type + ) + message = Message( + body=json.dumps(self.bad_dcm_payload).encode(), + properties=properties, + annotations={common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode()}, + application_properties=_encode_app_props(self.app_properties), + ) + args = CommonParserArguments(properties=["all"]) + parser = self._create_parser( + device_template=device_template, message=message, args=args + ) + + # haven't found a better way to force the error to occur within parser + parser._central_template_provider.get_device_template = lambda x: Template( + device_template + ) + + # act + parsed_msg = parser.parse_message() + + # verify + assert parsed_msg["event"]["payload"] == self.bad_dcm_payload + assert parsed_msg["event"]["origin"] == self.device_id + + expected_details = strings.device_template_not_found( + "Could not parse iot central device template." + ) + + _validate_issues(parser, Severity.error, 1, 1, [expected_details]) + + def test_type_mismatch_should_error(self): + # setup + device_template = self._get_template() + + properties = MessageProperties( + content_encoding=self.encoding, content_type=self.content_type + ) + message = Message( + body=json.dumps(self.type_mismatch_payload).encode(), + properties=properties, + annotations={common_parser.DEVICE_ID_IDENTIFIER: self.device_id.encode()}, + application_properties=_encode_app_props(self.app_properties), + ) + args = CommonParserArguments(properties=["all"]) + parser = self._create_parser( + device_template=device_template, message=message, args=args + ) + + # act + parsed_msg = parser.parse_message() + + # verify + assert parsed_msg["event"]["payload"] == self.type_mismatch_payload + assert parsed_msg["event"]["origin"] == self.device_id + assert parsed_msg["event"]["properties"]["application"] == self.app_properties + + field_name = list(self.type_mismatch_payload.keys())[0] + data = list(self.type_mismatch_payload.values())[0] + data_type = "boolean" + expected_details = strings.invalid_primitive_schema_mismatch_template( + field_name, data_type, data + ) + _validate_issues(parser, Severity.error, 1, 1, [expected_details]) + + def _get_template(self): + return Template(load_json(FileNames.central_device_template_file)) + + def _create_parser( + self, device_template: Template, message: Message, args: CommonParserArguments + ): + device_provider = CentralDeviceProvider(cmd=None, app_id=None) + template_provider = CentralDeviceTemplateProvider(cmd=None, app_id=None) + device_provider.get_device = mock.MagicMock(return_value=Device({})) + template_provider.get_device_template = mock.MagicMock( + return_value=device_template + ) + return central_parser.CentralParser( + message=message, + central_device_provider=device_provider, + central_template_provider=template_provider, + common_parser_args=args, + ) diff --git a/azext_iot/tests/utility/test_uamqp_import.py b/azext_iot/tests/utility/test_uamqp_import.py new file mode 100644 index 000000000..e66b24ccd --- /dev/null +++ b/azext_iot/tests/utility/test_uamqp_import.py @@ -0,0 +1,29 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import mock + + +class TestUamqpImport(object): + def test_import_error(self): + """ + This test should throw if any module is importing uamqp + Add any new top level modules here to ensure they aren't importing uamqp + """ + with mock.patch.dict("sys.modules", {"uamqp": None}): + import azext_iot.assets + import azext_iot.central + import azext_iot.common + import azext_iot.dps + import azext_iot.iothub + import azext_iot.models + + assert azext_iot.assets + assert azext_iot.central + assert azext_iot.common + assert azext_iot.dps + assert azext_iot.iothub + assert azext_iot.models diff --git a/dev_requirements b/dev_requirements index 4d4c3c42f..25ac53c05 100644 --- a/dev_requirements +++ b/dev_requirements @@ -2,11 +2,14 @@ pytest pytest-mock pytest-cov pytest-env -pylint +uamqp~=1.2 mock;python_version<'3.3' responses -flake8 +urllib3[secure]>=1.21.1,<=1.25 black;python_version>='3.6' wheel==0.30.0 pre-commit -six==1.12 +six>=1.12 +pylint +flake8 +azure-iot-device diff --git a/docs/install-help.md b/docs/install-help.md index 4cbf9a1be..5813f4a73 100644 --- a/docs/install-help.md +++ b/docs/install-help.md @@ -23,7 +23,7 @@ After installing Azure CLI in my supported Linux environment, I try to install t Make sure you install the right distribution of Azure CLI that is compatible with your platform. -For example using the recommended installation path of [Linux via apt](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-apt?view=azure-cli-latest), validate that your `/etc/apt/sources.list.d/azure-cli.list` file has the proper distribution identifier. +For example using the recommended installation path of [Linux via apt](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-apt), validate that your `/etc/apt/sources.list.d/azure-cli.list` file has the proper distribution identifier. On an Ubuntu 16.04 environment provided with the [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10) the sources list file should have an entry tagged with 'xenial': diff --git a/linter_exclusions.yml b/linter_exclusions.yml new file mode 100644 index 000000000..83b77abf8 --- /dev/null +++ b/linter_exclusions.yml @@ -0,0 +1,15 @@ +dt endpoint create servicebus: + parameters: + servicebus_resource_group: + rule_exclusions: + - parameter_should_not_end_in_resource_group +dt endpoint create eventgrid: + parameters: + eventgrid_resource_group: + rule_exclusions: + - parameter_should_not_end_in_resource_group +dt endpoint create eventhub: + parameters: + eventhub_resource_group: + rule_exclusions: + - parameter_should_not_end_in_resource_group diff --git a/pytest.ini.example b/pytest.ini.example index 8708ac4aa..ede7b5ff2 100644 --- a/pytest.ini.example +++ b/pytest.ini.example @@ -15,11 +15,16 @@ env = AZURE_TEST_RUN_LIVE=True azext_iot_testrg= azext_iot_testhub= - azext_iot_testhub_cs= azext_iot_testdps= azext_iot_teststorageuri= - azext_pnp_endpoint= - azext_pnp_repository= - azext_pnp_cs= + azext_iot_teststorageid= azext_iot_central_app_id= - azext_iot_central_device_id= + azext_dt_region= + azext_dt_ep_eventgrid_topic= + azext_dt_ep_servicebus_namespace= + azext_dt_ep_servicebus_policy= + azext_dt_ep_servicebus_topic= + azext_dt_ep_eventhub_namespace= + azext_dt_ep_eventhub_policy= + azext_dt_ep_eventhub_topic= + azext_dt_ep_rg= diff --git a/setup.cfg b/setup.cfg index d80e75ff3..7784f4010 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[bdist_wheel] -universal=1 - [flake8] max-complexity = 10 max-line-length = 130 diff --git a/setup.py b/setup.py index f563ba4b1..23833446c 100644 --- a/setup.py +++ b/setup.py @@ -41,14 +41,14 @@ # 'jmespath==0.9.3', # 'pyyaml==3.13' # 'knack>=0.3.1' -# 'jsonschema==3.0.2' +# 'jsonschema==3.2.0' # 'enum34' (when python_version < 3.4) # There is also a dependency for uamqp for amqp based commands # though that is installed out of band (managed by the extension) # for compatibility reasons. -DEPENDENCIES = ["paho-mqtt==1.5.0", "jsonschema==3.0.2", "setuptools"] +DEPENDENCIES = ["paho-mqtt==1.5.0", "jsonschema==3.2.0", "packaging"] CLASSIFIERS = [ @@ -56,29 +56,21 @@ "Intended Audience :: Developers", "Intended Audience :: System Administrators", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "License :: OSI Approved :: MIT License", ] -with open("HISTORY.rst", "r", encoding="utf-8") as f: - HISTORY = f.read() - short_description = "The Azure IoT extension for Azure CLI." setup( name=PACKAGE_NAME, version=VERSION, + python_requires=">=3.6,<4", description=short_description, - long_description="{} Intended for power users and/or automation of IoT solutions at scale.".format(short_description) - + "\n\n" - + HISTORY, + long_description="{} Intended for power users and/or automation of IoT solutions at scale.".format(short_description), license="MIT", author="Microsoft", author_email="iotupx@microsoft.com", # +@digimaun