├── .devcontainer ├── Dockerfile ├── devcontainer.json └── library-scripts │ └── docker-in-docker-debian.sh ├── .env.tmp ├── .github ├── ISSUE_TEMPLATE │ ├── 01_bug_report.yml │ └── 02_feature_request.yml ├── pull_request_template.md └── workflows │ └── wiki.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── SECURITY.md ├── assets ├── armdeployment.png ├── armoutputs.png ├── deploy │ ├── ARMDeployment.sln │ └── ARMDeployment │ │ ├── ARMDeployment.deployproj │ │ ├── Deploy-AzureResourceGroup.ps1 │ │ ├── azuredeploy.json │ │ └── azuredeploy.parameters.json ├── edgedevtool2mins.png ├── edgedevtoolintro.png ├── edgedevtoolquickstartlarge.png ├── edgedevtoolquickstartsmall.png └── edgedevtoolwsl.png ├── azure-pipelines.yml ├── build.sh ├── cleanup.sh ├── docker └── tool │ ├── build-docker.sh │ ├── deps.txt │ ├── linux │ ├── Dockerfile │ ├── Dockerfile.base │ ├── install-dev.sh │ ├── run.ps1 │ └── run.sh │ ├── push-docker.sh │ └── windows │ ├── Dockerfile │ ├── Dockerfile.base │ ├── install-dev.bat │ └── run.ps1 ├── docs ├── _Sidebar.md ├── azure-setup.md ├── command-list.md ├── command-tips.md ├── edge-device-setup.md ├── environment-setup │ ├── install-docker.md │ ├── manual-dev-machine-setup.md │ ├── python-virtual-environment-setup.md │ └── run-devcontainer-docker.md ├── home.md ├── migration-guides.md ├── overview.md ├── quickstart.md ├── run-modules-on-simulator.md ├── run-modules-on-vm.md ├── test-coverage.md ├── troubleshooting.md └── usage.md ├── iotedgedev ├── __init__.py ├── args.py ├── azurecli.py ├── buildoptionsparser.py ├── buildprofile.py ├── cli.py ├── connectionstring.py ├── constants.py ├── containerregistry.py ├── decorators.py ├── deploymentmanifest.py ├── dockercls.py ├── dotnet.py ├── edge.py ├── envvars.py ├── iothub.py ├── module.py ├── modules.py ├── organizedgroup.py ├── output.py ├── simulator.py ├── solution.py ├── telemetry.py ├── telemetryconfig.py ├── telemetryuploader.py ├── template │ ├── .env.tmp │ ├── .gitignore │ ├── deployment.template.json │ ├── launch_c.json │ ├── launch_csharp.json │ ├── launch_java.json │ ├── launch_node.json │ └── launch_python.json ├── utility.py └── version.py ├── pytest.ini ├── requirements.txt ├── requirements_dev.txt ├── scripts ├── gen-help-markdown.bat ├── install-pip.sh └── setup-wsl.sh ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── assets │ ├── deployment.manifest_invalid.json │ ├── deployment.manifest_invalid_createoptions.json │ ├── deployment.manifest_invalid_schema.json │ ├── deployment.template.non_str_placeholder.json │ ├── deployment.template_1.json │ ├── deployment.template_2.json │ ├── deployment.template_3.json │ ├── deployment.template_4.json │ ├── deployment.template_invalidresult.json │ ├── deployment.template_without_schema_template.json │ ├── launch.json │ ├── launch_without_nodejs.json │ └── test_solution_shared_lib │ │ ├── .gitignore │ │ ├── .vscode │ │ └── launch.json │ │ ├── deployment.debug.template.json │ │ ├── deployment.escapedpath.template.json │ │ ├── deployment.template.json │ │ ├── layered_deployment.flattened_props.template.json │ │ ├── libs │ │ └── sharedlib │ │ │ ├── Class1.cs │ │ │ └── sharedlib.csproj │ │ ├── modules │ │ ├── non_module_project │ │ │ └── placeholder.txt │ │ └── sample_module │ │ │ ├── .gitignore │ │ │ ├── Dockerfile.amd64 │ │ │ ├── Dockerfile.amd64.debug │ │ │ ├── Dockerfile.windows-amd64 │ │ │ ├── Program.cs │ │ │ ├── module.json │ │ │ └── sample_module.csproj │ │ └── sample_module_2 │ │ ├── .gitignore │ │ ├── Dockerfile.amd64 │ │ ├── Dockerfile.amd64.debug │ │ ├── Dockerfile.windows-amd64 │ │ ├── Program.cs │ │ ├── module.json │ │ └── sample_module_2.csproj ├── test_azurecli.py ├── test_buildoptionsparser.py ├── test_cli.py ├── test_config.py ├── test_connectionstring.py ├── test_decorators.py ├── test_deploymentmanifest.py ├── test_envvars.py ├── test_iotedgedev_iothub_deploy.py ├── test_iotedgedev_simulator.py ├── test_iotedgedev_solution.py ├── test_iotedgedev_solution_build.py ├── test_iotedgedev_solution_deploy.py ├── test_iotedgedev_solution_init.py ├── test_iotedgedev_solution_tag.py ├── test_utility.py └── utility.py ├── tox.ini └── vsts_ci ├── .vsts-ci.yml ├── darwin └── continuous-build-darwin.yml ├── linux └── continuous-build-linux.yml └── win32 └── continuous-build-win32.yml /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # Dependencies versions 2 | ARG PYTHON_VERSION="3.9" 3 | 4 | FROM mcr.microsoft.com/vscode/devcontainers/python:0-${PYTHON_VERSION} 5 | 6 | # Dependencies versions 7 | ARG AZURE_CLI_VERSION="2.34.1-1~bullseye" 8 | ARG NODE_VERSION="17.x" 9 | ARG DOTNET_VERSION="5.0" 10 | ARG JDK_VERSION="2:1.11-72" 11 | ARG MAVEN_VERSION="3.6.3-5" 12 | 13 | # Bring in utility scripts 14 | COPY .devcontainer/library-scripts/*.sh /tmp/library-scripts/ 15 | 16 | RUN \ 17 | # Install some basics 18 | apt-get update && \ 19 | apt-get upgrade -y && \ 20 | apt-get install -y --no-install-recommends build-essential apt-utils && \ 21 | apt-get install -y libffi-dev libssl-dev vim sudo && \ 22 | apt-get upgrade -y 23 | 24 | RUN \ 25 | # Upgrade to latest pip 26 | python -m pip install --upgrade pip && \ 27 | # Install node, npm 28 | curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION} | bash - && \ 29 | apt-get install -y nodejs && \ 30 | npm install npm@latest -g && \ 31 | # Install azure-cli 32 | apt-get install -y ca-certificates curl apt-transport-https lsb-release gnupg && \ 33 | curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null && \ 34 | echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/azure-cli.list && \ 35 | apt-get update && apt-get install -y azure-cli=${AZURE_CLI_VERSION} && \ 36 | az extension add --name azure-iot --system && \ 37 | az extension update --name azure-iot && \ 38 | # Use Docker script from script library to set things up - enable non-root docker, user vscode, using moby 39 | /bin/bash /tmp/library-scripts/docker-in-docker-debian.sh "true" "vscode" "true" \ 40 | # Install Yoeman, node.js modules 41 | npm install -g yo && \ 42 | npm i -g yo generator-azure-iot-edge-module && \ 43 | # Install dotnet SDK 44 | cd ~ && \ 45 | wget https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \ 46 | dpkg -i packages-microsoft-prod.deb && \ 47 | rm packages-microsoft-prod.deb && \ 48 | apt-get update && apt-get install -y apt-transport-https && \ 49 | apt-get update && apt-get install -y dotnet-sdk-${DOTNET_VERSION} && \ 50 | # Install Java JDK, Maven 51 | apt-get install -y default-jdk=${JDK_VERSION} && \ 52 | apt-get install -y maven=${MAVEN_VERSION} && \ 53 | # Install bash completion 54 | apt-get update && apt-get -y install bash-completion && \ 55 | # Clean up 56 | apt-get autoremove -y && \ 57 | apt-get clean -y && \ 58 | rm -rf /tmp/* && \ 59 | rm -rf /var/lib/apt/lists/* 60 | 61 | # customize vscode environmnet 62 | USER vscode 63 | RUN \ 64 | git clone https://github.com/magicmonty/bash-git-prompt.git $HOME/.bash-git-prompt --depth=1 && \ 65 | echo "\n# setup GIT prompt and completion\nif [ -f \"$HOME/.bash-git-prompt/gitprompt.sh\" ]; then\n GIT_PROMPT_ONLY_IN_REPO=1 && source $HOME/.bash-git-prompt/gitprompt.sh;\nfi" >> $HOME/.bashrc && \ 66 | echo "source /usr/share/bash-completion/bash_completion" >> $HOME/.bashrc && \ 67 | echo "\n# add local python programs to PATH\nexport PATH=$PATH:$HOME/.local/bin\n" >> $HOME/.bashrc && \ 68 | # enable some useful aliases 69 | sed -i -e "s/#alias/alias/" $HOME/.bashrc && \ 70 | # add the cookiecutter 71 | pip install cookiecutter 72 | 73 | # launch docker-ce 74 | ENTRYPOINT [ "/usr/local/share/docker-init.sh" ] 75 | CMD [ "sleep", "infinity" ] 76 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.140.1/containers/dotnetcore 3 | { 4 | "name": "iotedgedev", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | "context": ".." 8 | }, 9 | 10 | "runArgs": ["--init", "--privileged"], 11 | "mounts": [ 12 | // Keep command history 13 | "source=ostf-bashhistory,target=/commandhistory,type=volume", 14 | // Use docker-in-docker socket 15 | "source=dind-var-lib-docker,target=/var/lib/docker,type=volume" 16 | ], 17 | 18 | "overrideCommand": false, 19 | "postCreateCommand": "docker image prune -a -f && sudo chown vscode:users -R /home/vscode/Dev && pip install -r requirements_dev.txt && pip install -e .", 20 | 21 | // Set *default* container specific settings.json values on container create. 22 | "settings": { 23 | "#terminal.integrated.defaultProfile.linux#": "/bin/bash" 24 | }, 25 | 26 | // Add the IDs of extensions you want installed when the container is created. 27 | "extensions": [ 28 | "ms-python.python", 29 | "ms-azuretools.vscode-docker", 30 | "redhat.vscode-yaml", 31 | "mikestead.dotenv", 32 | "streetsidesoftware.code-spell-checker", 33 | "yzhang.markdown-all-in-one", 34 | "davidanson.vscode-markdownlint" 35 | ], 36 | 37 | "workspaceMount": "source=${localWorkspaceFolder},target=/home/vscode/Dev,type=bind,consistency=cached", 38 | "workspaceFolder": "/home/vscode/Dev", 39 | "remoteUser": "vscode" 40 | } 41 | -------------------------------------------------------------------------------- /.env.tmp: -------------------------------------------------------------------------------- 1 | # 2 | # CONNECTION STRINGS 3 | # 4 | 5 | IOTHUB_CONNECTION_STRING="" 6 | 7 | DEVICE_CONNECTION_STRING="" 8 | 9 | # 10 | # CONTAINER REGISTRY 11 | # 12 | # Settings for your default container registry. 13 | # - Local Registry: Set CONTAINER_REGISTRY_SERVER to "localhost:5000" - USERNAME/PASSWORD are not required. 14 | # - Azure Container Registry: Set CONTAINER_REGISTRY_SERVER to "myregistry.azurecr.io". USERNAME/PASSWORD are required. 15 | # - Docker Hub: Set CONTAINER_REGISTRY_SERVER and CONTAINER_REGISTRY_USERNAME to your Docker Hub username. Set CONTAINER_REGISTRY_PASSWORD to your Docker Hub password. 16 | 17 | CONTAINER_REGISTRY_SERVER="localhost:5000" 18 | CONTAINER_REGISTRY_USERNAME="" 19 | CONTAINER_REGISTRY_PASSWORD="" 20 | 21 | # To specify additional container registries ensure the prefix is CONTAINER_REGISTRY_SERVER_, CONTAINER_REGISTRY_USERNAME_, CONTAINER_REGISTRY_PASSWORD_ 22 | # And the token following the prefix uniquely associates the SERVER/USERNAME/PASSWORD 23 | # Token can be any string of alphanumeric characters 24 | 25 | # CONTAINER_REGISTRY_SERVER_2="" 26 | # CONTAINER_REGISTRY_USERNAME_2="" 27 | # CONTAINER_REGISTRY_PASSWORD_2="" 28 | 29 | # 30 | # HOST 31 | # 32 | 33 | EDGE_RUNTIME_VERSION="1.2" 34 | EDGEAGENT_SCHEMA_VERSION="1.1" 35 | EDGEHUB_SCHEMA_VERSION="1.2" 36 | 37 | # 38 | # MODULES 39 | # 40 | 41 | BYPASS_MODULES="" 42 | # "" - to build all modules 43 | # "*" - to bypass all modules 44 | # "filtermodule, module1" - Comma delimited list of modules to bypass when building 45 | 46 | ACTIVE_DOCKER_PLATFORMS="" 47 | # "" - to only build platforms specified in DEPLOYMENT_CONFIG_TEMPLATE_FILE 48 | # "*" - to build all platforms 49 | # "amd64,amd64.debug" - Comma delimited list of platforms to build 50 | 51 | CONTAINER_TAG="" 52 | 53 | # 54 | # IOTHUB DEPLOYMENT 55 | # 56 | 57 | IOTHUB_DEPLOYMENT_TARGET_CONDITION="" 58 | # To specifiy the target condition of a IoT Hub deployment 59 | # Required when creating a deployment on IoT Hub 60 | # Examples: 61 | # IOTHUB_DEPLOYMENT_TARGET_CONDITION="tags.environment='dev'" 62 | # IOTHUB_DEPLOYMENT_TARGET_CONDITION="tags.building=9 and tags.environment='test'" 63 | 64 | # 65 | # SOLUTION SETTINGS 66 | # 67 | 68 | CONFIG_OUTPUT_DIR="config" 69 | DEPLOYMENT_CONFIG_TEMPLATE_FILE="deployment.template.json" 70 | DEPLOYMENT_CONFIG_DEBUG_TEMPLATE_FILE="deployment.debug.template.json" 71 | DEFAULT_PLATFORM="amd64" 72 | MODULES_PATH="modules" 73 | LOGS_PATH="logs" 74 | DEVICE_TAGS="" 75 | # To specifiy the tags for an edge device 76 | # Examples: 77 | # DEVICE_TAGS="{"environment":"dev"}" 78 | # DEVICE_TAGS="{"environment":"dev", "building":"9"}" 79 | 80 | 81 | # 82 | # DOCKER LOGS COMMAND 83 | # 84 | # Command used when calling iotedgedev docker --logs or --show-logs 85 | 86 | LOGS_CMD="start /B start cmd.exe @cmd /k docker logs {0} -f" 87 | # "start /B start cmd.exe @cmd /k docker logs {0} -f" - for CMD 88 | # "docker logs {0} -f -new_console:sV" - for ConEmu 89 | 90 | # 91 | # AZURE SETTINGS 92 | # 93 | # These settings will override parameters to the `iotedgedev azure --setup` command. 94 | # CREDENTIALS="username password" 95 | # SERVICE_PRINCIPAL="username password tenant" 96 | # RESOURCE_GROUP_LOCATION="australiaeast|australiasoutheast|brazilsouth|canadacentral|canadaeast|centralindia|centralus|eastasia|eastus|eastus2|japanwest|japaneast|northeurope|northcentralus|southindia|uksouth|ukwest|westus|westeurope|southcentralus|westcentralus|westus2" 97 | # IOTHUB_SKU="F1|S1|S2|S3" 98 | # UPDATE_DOTENV="True|False" 99 | 100 | SUBSCRIPTION_ID="" 101 | RESOURCE_GROUP_NAME="" 102 | RESOURCE_GROUP_LOCATION="" 103 | IOTHUB_NAME="" 104 | IOTHUB_SKU="" 105 | EDGE_DEVICE_ID="" 106 | CREDENTIALS="" 107 | SERVICE_PRINCIPAL="" 108 | UPDATE_DOTENV="" 109 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01_bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: Create a report to help us improve 3 | title: "[BUG] " 4 | labels: ["bug", "triage"] 5 | 6 | body: 7 | - type: textarea 8 | id: background 9 | attributes: 10 | label: Description 11 | description: Please provide the description of issue you're seeing. 12 | placeholder: Description 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: expected-behavior 17 | attributes: 18 | label: Expected behavior 19 | description: | 20 | Provide a description of the expected behavior. 21 | placeholder: Expected behavior 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: actual-behavior 26 | attributes: 27 | label: Actual behavior 28 | description: | 29 | Provide a description of the actual behavior observed. If applicable please include any error messages, exception stacktraces or memory dumps. 30 | placeholder: Actual behavior 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: repro-steps 35 | attributes: 36 | label: Steps to Reproduce 37 | description: | 38 | Please include minimal steps to reproduce the problem if possible. E.g.: the smallest possible code snippet; or a small project, with steps to run it. If possible include text as text rather than screenshots (so it shows up in searches). 39 | placeholder: Minimal Reproduction 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: environment 44 | attributes: 45 | label: Environment 46 | description: | 47 | Please provide more information on your environment: 48 | - `iotedgedev` Version: 49 | - Python Version: 50 | - Pip Version: 51 | - Development machine OS Version: 52 | - IoT Edge device OS Version: 53 | placeholder: Environment 54 | validations: 55 | required: true 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02_feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project. 3 | title: "[FEATURE REQ] <title>" 4 | labels: ["feature", "triage"] 5 | 6 | body: 7 | - type: textarea 8 | id: background 9 | attributes: 10 | label: Description. 11 | description: What feature would you like to get added? What problem is it solving? 12 | placeholder: Feature description 13 | validations: 14 | required: true 15 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | <!-- DO NOT DELETE THIS TEMPLATE --> 2 | 3 | ## Checklist 4 | 5 | This checklist is used to make sure that common guidelines for a pull request are followed. 6 | 7 | - [ ] Versions update reflected in all places (both __init__.py files, CHANGELOG, setup.py, setup.cfg) 8 | - [ ] Unit tests pass locally 9 | - [ ] Quickstart steps locally validated 10 | - [ ] Passes unit tests in CICD pipeline (green on Github pipeline) 11 | - [ ] Pypi RC version passes Edge CICD pipeline validation for cross-tool validation 12 | - [ ] Pull request includes test coverage for the included changes 13 | - [ ] Documentation is updated for the included changes 14 | 15 | ### General guidelines 16 | 17 | - [ ] Title of the pull request is clear and informative. 18 | - [ ] There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. 19 | 20 | ## Description 21 | 22 | <!-- 23 | Please add a number of a relevant issue below 24 | --> 25 | Fixes # 26 | 27 | <!-- 28 | Please add an informative description that covers that changes made by the pull request. 29 | --> 30 | 31 | ### Additional information 32 | 33 | <!-- 34 | Please add any additional information you have about the PR, such as relevant links. 35 | --> -------------------------------------------------------------------------------- /.github/workflows/wiki.yml: -------------------------------------------------------------------------------- 1 | name: Update wiki 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | # Allows you to run this workflow manually from the Actions tab 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Push documentation changes to wiki 17 | env: 18 | EMAIL: actions@github.com 19 | USER_NAME: Github Actions 20 | REPO_DEST: iotedgedev.wiki 21 | REPO_DEST_URL: github.com/Azure/iotedgedev.wiki.git 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | run: | 24 | set -e 25 | 26 | # Save iotedgedev repo folder path 27 | REPO_SOURCE=$(pwd) 28 | 29 | # Exit iotedgedev repo folder 30 | cd .. 31 | 32 | # Clone repositories 33 | git clone https://${REPO_DEST_URL} 34 | 35 | # Update wiki repository with documentation folder contents 36 | cd ${REPO_DEST} 37 | git rm -rf . 38 | git clean -fxd 39 | yes 2>/dev/null | cp -rf ${REPO_SOURCE}/docs/* . 40 | git reset 41 | 42 | # If changes found, commit and push them 43 | if git diff-index HEAD && [ ! -n "$(git status -s)" ]; then 44 | git status 45 | echo "No changes found in /docs. Exiting..." 46 | else 47 | git status 48 | echo "Changes found in /docs. Committing and pushing..." 49 | git config user.email ${EMAIL} 50 | git config user.name ${USER_NAME} 51 | git add . 52 | git commit -m "Update documentation | From github actions number: ${GITHUB_RUN_ID}" 53 | git push "https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@${REPO_DEST_URL}" 54 | fi 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | *.whl 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | # pyenv python configuration file 63 | .python-version 64 | 65 | **/.vs 66 | **/bin 67 | **/obj 68 | **/out 69 | .env 70 | .backup 71 | venv 72 | /logs 73 | /config 74 | .config 75 | config 76 | /build 77 | 78 | 79 | py36 80 | .pypirc 81 | tests/test_solution 82 | tests/test_solution_shared_lib 83 | empty_dir/ 84 | README 85 | 86 | node_modules 87 | 88 | /docker/linux/Dockerfile.expanded 89 | 90 | .pytest_cache 91 | 92 | # VSCode 93 | .vscode/* 94 | !.vscode/settings.json 95 | !.vscode/tasks.json 96 | !.vscode/launch.json 97 | !.vscode/extensions.json 98 | 99 | # rope 100 | .vscode/.ropeproject/ 101 | 102 | # ctags 103 | .vscode/tags 104 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: debug test file", 6 | "type": "python", 7 | "request": "launch", 8 | "module": "pytest", 9 | "args": [ 10 | "-v", 11 | "${relativeFile}" 12 | ], 13 | "cwd": "${workspaceFolder}" 14 | }, 15 | { 16 | "name": "Python Module", 17 | "type": "python", 18 | "request": "launch", 19 | "module": "iotedgedev.cli", 20 | "args": [ 21 | "init" 22 | ], 23 | "cwd": "${workspaceFolder}/tests/test_iotedgedev_solution" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "${workspaceFolder}\\venv\\Scripts\\python.exe", 3 | "cSpell.words": [ 4 | "devcontainer", 5 | "dotenv", 6 | "edgesolution", 7 | "envvar", 8 | "gitignore", 9 | "iotedge", 10 | "iotedgedev", 11 | "iothub", 12 | "mkdir", 13 | "posix", 14 | "quickstart", 15 | "venv", 16 | "virtualenv" 17 | ], 18 | "python.linting.enabled": true, 19 | "python.linting.pylintEnabled": false, 20 | "python.linting.flake8Enabled": true, 21 | "python.linting.flake8Args": [ 22 | "--max-line-length=200" 23 | ], 24 | "python.formatting.provider": "autopep8", 25 | "python.formatting.autopep8Args": [ 26 | "--max-line-length", 27 | "200" 28 | ], 29 | "python.testing.unittestEnabled": true, 30 | "python.testing.pytestEnabled": true 31 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 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. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This section describes how to get your developer workspace running for the first time so that you're ready to start making contributions. 4 | 5 | Please fork, branch and pull-request any changes you'd like to make. For more information on how to create a fork, see: [Fork a repo - GitHub Docs](https://docs.github.com/en/get-started/quickstart/fork-a-repo) 6 | 7 | ## Workspace setup 8 | 9 | 1. Clone your fork of the repository 10 | 11 | `git clone https://github.com/<your_id>/iotedgedev.git` 12 | 13 | 2. Install **[Docker](https://docs.docker.com/engine/installation/)** 14 | - Windows 15 | - Be sure to check whether you are running in Linux container mode or Windows container mode. 16 | - Linux 17 | - We've seen some issues with docker.io. If IoT Edge doesn't run for you, then try installing Docker CE directly instead of via docker.io. Use the [CE install steps](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-docker-ce), or use the [convenience script](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-using-the-convenience-script). 18 | - By default, you need `sudo` to run `docker` commands. If you want to avoid this, please follow the [post-installation steps for Linux](https://docs.docker.com/install/linux/linux-postinstall/#manage-docker-as-a-non-root-user). 19 | 20 | 3. Setup development environment 21 | 22 | There are three options to setup your development environment: 23 | 24 | - **Devcontainers**: Start the devcontainer from VS Code. See [Developing inside a Container](https://code.visualstudio.com/docs/remote/containers) for steps on how to do so. 25 | - **Local**: Setup the development environment manually. Please follow the [Manual Development Machine Setup Wiki](docs/environment-setup/manual-dev-machine-setup.md). 26 | - Run IoT Edge Dev Tool in editable mode: 27 | Run ›`pip install -e .` from the root of the repo to see changes to iotedgedev commands as you change code. 28 | 29 | - **Codespaces**: Create a [GitHub Codespaces](https://github.com/features/codespaces) directly from your fork via the GitHub UI. 30 | 31 | 4. Rename `.env.tmp` in the root of the repo to `.env` and set the `IOTHUB_CONNECTION_STRING` and `DEVICE_CONNECTION_STRING` values to settings from your existing IoT Hub and Edge Device. If you don't have these, or want to create new ones, you could run `iotedgedev iothub setup` in the root of the repo to setup your resources and fill out the values automatically. 32 | 33 | ### Known Issues 34 | 35 | 1. "iotedgedev command not found": Sometimes the `postCreateCommand` from the [container](.devcontainer/devcontainer.json) does not get executed and the environment is not correctly initialized. Run `pip install -e .` in the root of the repo to fix this. 36 | 37 | ## Run and debug tests 38 | 39 | To **run** and **debug** the tests **individually** you can use the VSCode Test Runner UI provided by the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) (installed by default in the devcontainer). 40 | 41 | An alternative way to **debug** is to select `Python: debug test file` in the `Run and debug` tab of vscode, open the test file you'd like to debug and hit `F5`. 42 | 43 | You can choose one of these following commands to easily **run all** tests: 44 | 45 | ```sh 46 | # Run all tests with all python interpreters (fails for the ones not installed) 47 | # This is the command that runs in the pipeline for all python versions 48 | make test-all 49 | # Run tests with tox in python version 3.9 (the python version installed in the devcontainer) 50 | tox -e py39 51 | # Run all tests with pytest for python version 3.9 (nicest output, fastest) 52 | make test 53 | ``` 54 | 55 | > It is recommended to run all tests with `tox -e py39` or `make test-all` at least once before making the PR. The pytest test runner environment is slightly different from tox, so some tests may pass with that and not in tox, resulting in failures in the pipeline. 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ------------------------------------------- START OF LICENSE ----------------------------------------- 2 | Azure IoT Edge Dev Tool 3 | Copyright (c) Microsoft Corporation 4 | All rights reserved. 5 | 6 | MIT License 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | 14 | ----------------------------------------------- END OF LICENSE ------------------------------------------ -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.md 2 | include LICENSE 3 | include README.md 4 | include iotedgedev/template/*.* 5 | recursive-include tests * 6 | recursive-exclude * __pycache__ 7 | recursive-exclude * *.py[co] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | define BROWSER_PYSCRIPT 4 | import os, webbrowser, sys 5 | try: 6 | from urllib import pathname2url 7 | except: 8 | from urllib.request import pathname2url 9 | 10 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 11 | endef 12 | export BROWSER_PYSCRIPT 13 | 14 | define PRINT_HELP_PYSCRIPT 15 | import re, sys 16 | 17 | for line in sys.stdin: 18 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 19 | if match: 20 | target, help = match.groups() 21 | print("%-20s %s" % (target, help)) 22 | endef 23 | export PRINT_HELP_PYSCRIPT 24 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 25 | 26 | help: 27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 28 | 29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 30 | 31 | 32 | clean-build: ## remove build artifacts 33 | rm -fr build/ 34 | rm -fr dist/ 35 | rm -fr .eggs/ 36 | find . -name '*.egg-info' -exec rm -fr {} + 37 | find . -name '*.egg' -exec rm -f {} + 38 | 39 | clean-pyc: ## remove Python file artifacts 40 | find . -name '*.pyc' -exec rm -f {} + 41 | find . -name '*.pyo' -exec rm -f {} + 42 | find . -name '*~' -exec rm -f {} + 43 | find . -name '__pycache__' -exec rm -fr {} + 44 | 45 | clean-test: ## remove test and coverage artifacts 46 | rm -fr .tox/ 47 | rm -f .coverage 48 | rm -fr htmlcov/ 49 | 50 | lint: ## check style with flake8 51 | flake8 iotedgedev tests 52 | 53 | test: ## run tests quickly with the default Python 54 | pytest ./tests 55 | 56 | test-all: ## run tests on every Python version with tox 57 | tox 58 | 59 | coverage: ## check code coverage quickly with the default Python 60 | coverage run --source iotedgedev setup.py test 61 | coverage report -m 62 | coverage html 63 | $(BROWSER) htmlcov/index.html 64 | 65 | docs: ## generate Sphinx HTML documentation, including API docs 66 | rm -f docs/iotedgedev.rst 67 | rm -f docs/modules.rst 68 | sphinx-apidoc -o docs/ iotedgedev 69 | $(MAKE) -C docs clean 70 | $(MAKE) -C docs html 71 | $(BROWSER) docs/_build/html/index.html 72 | 73 | servedocs: docs ## compile the docs watching for changes 74 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 75 | 76 | release: clean ## package and upload a release 77 | python setup.py sdist upload 78 | python setup.py bdist_wheel upload 79 | 80 | dist: clean ## builds source and wheel package 81 | python setup.py sdist 82 | python setup.py bdist_wheel 83 | ls -l dist 84 | 85 | install: clean ## install the package to the active Python's site-packages 86 | python setup.py install 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure IoT Edge Dev Tool 2 | 3 | [![PyPI version](https://badge.fury.io/py/iotedgedev.svg)](https://badge.fury.io/py/iotedgedev) 4 | [![Build Status](https://dev.azure.com/Azure-IoT-DDE-EdgeExperience/IoTEdgeDev/_apis/build/status/Azure.iotedgedev?branchName=main)](https://dev.azure.com/Azure-IoT-DDE-EdgeExperience/IoTEdgeDev/_build/latest?definitionId=35&branchName=main) 5 | 6 | The **IoT Edge Dev Tool** greatly simplifies [Azure IoT Edge](https://azure.microsoft.com/en-us/services/iot-edge/) development down to simple commands driven by environment variables. 7 | 8 | - It gets you started with IoT Edge development with the [IoT Edge Dev Container](https://hub.docker.com/r/microsoft/iotedgedev/) and IoT Edge solution scaffolding that contains a default module and all the required configuration files. 9 | - It speeds up your inner-loop dev (dev, debug, test) by reducing multi-step build & deploy processes into one-line CLI commands as well as drives your outer-loop CI/CD pipeline. _You can use all the same commands in both stages of your development life-cycle._ 10 | 11 | ## Overview 12 | 13 | For the absolute fastest way to get started with IoT Edge Dev, please see the [quickstart](docs/quickstart.md) section. 14 | 15 | For a more detailed overview of IoT Edge Dev Tool including setup, commands and troubleshooting, please see the [Wiki](https://github.com/Azure/iotedgedev/wiki). 16 | 17 | ## Data/Telemetry 18 | 19 | This project collects usage data and sends it to Microsoft to help improve our products and services. Read our [privacy statement](http://go.microsoft.com/fwlink/?LinkId=521839) to learn more. 20 | If you don’t wish to send usage data to Microsoft, you can change your telemetry settings by updating `collect_telemetry` to `no` in `~/.iotedgedev/settings.ini`. 21 | 22 | ## Contributing 23 | 24 | This project welcomes contributions and suggestions. Please refer to the [Contributing file](CONTRIBUTING.md) for details on contributing changes. 25 | 26 | Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, 27 | and actually do, grant us the rights to use your contribution. For details, visit 28 | <https://cla.microsoft.com>. 29 | 30 | When you submit a pull request, a CLA-bot will automatically determine whether you need 31 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 32 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 33 | 34 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 35 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 36 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 37 | 38 | ## Support 39 | 40 | The team monitors the issue section on regular basis and will try to assist with troubleshooting or questions related IoT Edge tools on a best effort basis. 41 | 42 | A few tips before opening an issue. Try to generalize the problem as much as possible. Examples include: 43 | 44 | - Removing 3rd party components 45 | - Reproduce the issue with provided deployment manifest used 46 | - Specify whether issue is reproducible on physical device or simulated device or both 47 | Also, Consider consulting on the [docker docs channel](https://github.com/docker/docker.github.io) for general docker questions. 48 | 49 | ## Additional Resources 50 | 51 | Please refer to the [Wiki](https://github.com/Azure/iotedgedev/wiki) for details on setup, usage, and troubleshooting. 52 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | <!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK --> 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | <!-- END MICROSOFT SECURITY.MD BLOCK --> 42 | -------------------------------------------------------------------------------- /assets/armdeployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/iotedgedev/8765d719aa18c97ce3ebd02fdc51236210b3daf3/assets/armdeployment.png -------------------------------------------------------------------------------- /assets/armoutputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/iotedgedev/8765d719aa18c97ce3ebd02fdc51236210b3daf3/assets/armoutputs.png -------------------------------------------------------------------------------- /assets/deploy/ARMDeployment.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{151D2E53-A2C4-4D7D-83FE-D05416EBD58E}") = "ARMDeployment", "ARMDeployment\ARMDeployment.deployproj", "{80BD4064-A00F-4AFD-BED3-F9AC609663D1}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {80BD4064-A00F-4AFD-BED3-F9AC609663D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {80BD4064-A00F-4AFD-BED3-F9AC609663D1}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {80BD4064-A00F-4AFD-BED3-F9AC609663D1}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {80BD4064-A00F-4AFD-BED3-F9AC609663D1}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {93B65869-967C-4B8C-A7C8-374A641540E5} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /assets/deploy/ARMDeployment/ARMDeployment.deployproj: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 3 | <ItemGroup Label="ProjectConfigurations"> 4 | <ProjectConfiguration Include="Debug|AnyCPU"> 5 | <Configuration>Debug</Configuration> 6 | <Platform>AnyCPU</Platform> 7 | </ProjectConfiguration> 8 | <ProjectConfiguration Include="Release|AnyCPU"> 9 | <Configuration>Release</Configuration> 10 | <Platform>AnyCPU</Platform> 11 | </ProjectConfiguration> 12 | </ItemGroup> 13 | <PropertyGroup Label="Globals"> 14 | <ProjectGuid>80bd4064-a00f-4afd-bed3-f9ac609663d1</ProjectGuid> 15 | </PropertyGroup> 16 | <PropertyGroup> 17 | <TargetFrameworkIdentifier>Deployment</TargetFrameworkIdentifier> 18 | <TargetFrameworkVersion>1.0</TargetFrameworkVersion> 19 | <PrepareForBuildDependsOn> 20 | </PrepareForBuildDependsOn> 21 | </PropertyGroup> 22 | <Import Condition=" Exists('Deployment.targets') " Project="Deployment.targets" /> 23 | <Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" /> 24 | <!-- vertag<:>start tokens<:>maj.min --> 25 | <Import Condition=" Exists('$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Deployment\1.1\DeploymentProject.targets') " Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Deployment\1.1\DeploymentProject.targets" /> 26 | <!-- vertag<:>end --> 27 | <ItemGroup> 28 | <Content Include="azuredeploy.json" /> 29 | <Content Include="azuredeploy.parameters.json" /> 30 | <None Include="Deployment.targets"> 31 | <Visible>False</Visible> 32 | </None> 33 | <Content Include="Deploy-AzureResourceGroup.ps1" /> 34 | </ItemGroup> 35 | <Target Name="GetReferenceAssemblyPaths" /> 36 | </Project> -------------------------------------------------------------------------------- /assets/deploy/ARMDeployment/azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | } 6 | } -------------------------------------------------------------------------------- /assets/edgedevtool2mins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/iotedgedev/8765d719aa18c97ce3ebd02fdc51236210b3daf3/assets/edgedevtool2mins.png -------------------------------------------------------------------------------- /assets/edgedevtoolintro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/iotedgedev/8765d719aa18c97ce3ebd02fdc51236210b3daf3/assets/edgedevtoolintro.png -------------------------------------------------------------------------------- /assets/edgedevtoolquickstartlarge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/iotedgedev/8765d719aa18c97ce3ebd02fdc51236210b3daf3/assets/edgedevtoolquickstartlarge.png -------------------------------------------------------------------------------- /assets/edgedevtoolquickstartsmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/iotedgedev/8765d719aa18c97ce3ebd02fdc51236210b3daf3/assets/edgedevtoolquickstartsmall.png -------------------------------------------------------------------------------- /assets/edgedevtoolwsl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/iotedgedev/8765d719aa18c97ce3ebd02fdc51236210b3daf3/assets/edgedevtoolwsl.png -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # stop on error 4 | set -e 5 | 6 | function show_help 7 | { 8 | echo "Usage:" 9 | echo "build.sh local|test|prod none|minor|major imagename [windows|linux]" 10 | echo "local: don't upload to pypi, test: uses pypitest, prod: uses pypi" 11 | echo "none: don't bumpversion, minor: bumpversion minor --no-commit --no-tag, major: bumpversion major" 12 | echo "imagename: localhost:5000/iotedgedev, jongacr.azurecr.io/iotedgedev, microsoft/iotedgedev" 13 | echo "windows: builds only windows container, linux: builds only linux container. omit to build both." 14 | echo "NOTES: 1. You must have .pypirc in repo root with pypi and pypitest sections. 2. You must have .env file in root with connection strings set." 15 | 16 | exit 1 17 | } 18 | 19 | MODE="$1" 20 | VERSION_BUMP="$2" 21 | IMAGE_NAME="$3" 22 | PLATFORM="$4" 23 | 24 | if [ -z "$MODE" ] || [ -z "$VERSION_BUMP" ] || [ -z "$IMAGE_NAME" ]; then 25 | show_help 26 | fi 27 | 28 | echo -e "\n===== Setting up build environment" 29 | if [ "$MODE" = "local" ]; then 30 | echo "Environment: $MODE" 31 | elif [ "$MODE" = "test" ]; then 32 | echo "Environment: $MODE" 33 | elif [ "$MODE" = "prod" ]; then 34 | echo "Environment: $MODE" 35 | else 36 | echo "ERROR> Build mode parameter not known. must be 'local', 'prod' or 'test'" 37 | exit 1 38 | fi 39 | 40 | if [ ! -z $PLATFORM ]; then 41 | echo "Platform: $PLATFORM" 42 | fi 43 | 44 | if [ "$OSTYPE" = "msys" ]; then 45 | echo -e "\n===== Checking pre-requisistes" 46 | IS_ADMIN=$(powershell '([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")') 47 | if [ "$IS_ADMIN" = "False" ]; then 48 | echo "ERROR> Build script must be run as administrator" 49 | exit 1 50 | fi 51 | fi 52 | 53 | #TODO 54 | # check if running in administrator mode 55 | # make sure docker is in linux mode 56 | # make sure docker supports manifest option 57 | # stop and restart docker to make sure to avoid networking problem? 58 | # check that dockerhub exists and is accessible 59 | # check that pipy repo exists and is accessible 60 | # make sure there are no pending changes in GIT otherwise bumpversion will complain 61 | 62 | function run_tox { 63 | echo -e "\n===== Preventive cleanup" 64 | rm __pycache__ -rf 65 | rm .pytest_cache -rf 66 | rm .tox -rf 67 | rm .pytest_cache -rf 68 | rm tests/__pycache__ -rf 69 | 70 | echo -e "\n===== Running Tox" 71 | tox 72 | } 73 | 74 | function get_version 75 | { 76 | VERSION=$(cat ./iotedgedev/__init__.py | grep '__version__' | grep -oP "'\K[^']+") 77 | echo ${VERSION} 78 | } 79 | 80 | function run_bumpversion { 81 | 82 | if [ "$VERSION_BUMP" = "none" ]; then 83 | return 84 | fi 85 | 86 | echo -e "\n===== Bumping version" 87 | 88 | if [ "$MODE" = "prod" ]; then 89 | bumpversion $VERSION_BUMP 90 | else 91 | bumpversion $VERSION_BUMP --no-commit --no-tag --allow-dirty 92 | fi 93 | } 94 | 95 | function run_build_wheel 96 | { 97 | echo -e "\n===== Building Python Wheel" 98 | python setup.py bdist_wheel 99 | } 100 | 101 | function run_upload_pypi 102 | { 103 | if [ "$MODE" != "local" ]; then 104 | echo -e "\n===== Uploading to PyPi" 105 | PYPI=$([ "$MODE" = "prod" ] && echo "pypi" || echo "pypitest") 106 | twine upload -r ${PYPI} --config-file .pypirc dist/iotedgedev-$(get_version)-py3-none-any.whl 107 | fi 108 | } 109 | 110 | function run_build_docker 111 | { 112 | echo -e "\n===== Building Docker Containers" 113 | ./docker/tool/build-docker.sh $IMAGE_NAME $PLATFORM 114 | } 115 | 116 | function run_push_docker 117 | { 118 | echo -e "\n===== Pushing Docker Containers" 119 | ./docker/tool/push-docker.sh $IMAGE_NAME $PLATFORM 120 | } 121 | 122 | function run_push_git 123 | { 124 | if [ "$MODE" = "prod" ]; then 125 | echo 'Resetting __init__ file' 126 | git checkout ./iotedgedev/__init__.py 127 | 128 | echo -e "\n===== Pushing Tags to Git" 129 | git push --tags && git push 130 | fi 131 | } 132 | 133 | function set_analytics_key 134 | { 135 | if [[ ${AIKEY//-/} =~ ^[[:xdigit:]]{32}$ ]]; then 136 | echo 'Found AIKEY environment variable. Replacing __AIkey__' 137 | sed -i "/__AIkey__/c __AIkey__ = '${AIKEY}'" ./iotedgedev/__init__.py 138 | fi 139 | } 140 | 141 | set_analytics_key 142 | run_bumpversion 143 | run_tox 144 | run_build_wheel 145 | run_build_docker 146 | run_upload_pypi 147 | run_push_docker 148 | run_push_git 149 | 150 | echo -e "\n===== All done" 151 | -------------------------------------------------------------------------------- /cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm __pycache__ -rf 4 | rm .pytest_cache -rf 5 | rm .tox -rf 6 | rm .pytest_cache -rf 7 | rm tests/__pycache__ -rf 8 | -------------------------------------------------------------------------------- /docker/tool/build-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # stop on error 4 | set -e 5 | 6 | # make sure we're in docker folder 7 | original_folder=$PWD 8 | 9 | if [ ! -z "$(echo $PWD | grep /docker/tool$)" ]; then 10 | in_docker_folder=1 11 | else 12 | in_docker_folder=0 13 | cd docker/tool 14 | fi 15 | 16 | # read IoTEdgeDev version from python __init__ file 17 | 18 | export VERSION=$(cat ../../iotedgedev/__init__.py | grep '__version__' | awk '{print $3}' | sed "s;';;g") 19 | 20 | IMAGE_NAME="$1" 21 | PLATFORM="$2" 22 | 23 | if [ "$IMAGE_NAME" = "--help" ]; then 24 | echo "Usage:" 25 | echo "build-docker.sh imagename [linux|windows]" 26 | exit 1 27 | fi 28 | 29 | PYTHON2="2.7.14" #TODO READ FROM deps.txt 30 | PYTHON3="3.9.12" 31 | 32 | build_linux=1 33 | build_windows=1 34 | 35 | function switch_docker 36 | { 37 | echo "===== Switching Docker engine" 38 | echo "===== From: " $(docker version --format '{{.Server.Os}}') 39 | /c/Program\ Files/Docker/Docker/DockerCli.exe -SwitchDaemon 40 | echo "===== To: " $(docker version --format '{{.Server.Os}}') 41 | } 42 | 43 | function get_docker_mode 44 | { 45 | echo $(docker version --format '{{.Server.Os}}') 46 | } 47 | 48 | function check_docker_expected_mode 49 | { 50 | local mode=$(get_docker_mode) 51 | 52 | if [ $mode != $PLATFORM ]; then 53 | echo "===== ERROR: docker is not in expected mode: '$PLATFORM'" 54 | exit 1 55 | fi 56 | } 57 | 58 | function build_linux 59 | { 60 | echo "===== Building Linux Based images" 61 | 62 | check_docker_expected_mode "linux" 63 | 64 | cd linux 65 | 66 | rm iotedgedev-$VERSION-py3-none-any.whl --force 67 | 68 | cp ../../../dist/iotedgedev-$VERSION-py3-none-any.whl iotedgedev-$VERSION-py3-none-any.whl 69 | 70 | docker build \ 71 | -f Dockerfile.base \ 72 | -t iotedgedev-linux-base \ 73 | . 74 | 75 | docker build \ 76 | -f Dockerfile \ 77 | --build-arg IOTEDGEDEV_VERSION=$VERSION \ 78 | -t $IMAGE_NAME:$VERSION-amd64 \ 79 | -t $IMAGE_NAME:latest-amd64 \ 80 | -t $IMAGE_NAME:$VERSION \ 81 | -t $IMAGE_NAME:latest \ 82 | . 83 | 84 | rm iotedgedev-$VERSION-py3-none-any.whl --force 85 | 86 | cd .. 87 | } 88 | 89 | function build_windows 90 | { 91 | echo "===== Building Windows Based images" 92 | 93 | check_docker_expected_mode "windows" 94 | 95 | cd windows 96 | 97 | rm iotedgedev-$VERSION-py3-none-any.whl --force 98 | 99 | cp ../../../dist/iotedgedev-$VERSION-py3-none-any.whl iotedgedev-$VERSION-py3-none-any.whl 100 | 101 | docker build \ 102 | -f Dockerfile.base \ 103 | -t iotedgedev-windows-base \ 104 | --build-arg PYTHON2_VERSION=$PYTHON2 \ 105 | --build-arg PYTHON3_VERSION=$PYTHON3 \ 106 | . 107 | 108 | docker build \ 109 | -f Dockerfile \ 110 | --build-arg IOTEDGEDEV_VERSION=$VERSION \ 111 | -t microsoft/iotedgedev:$VERSION-windows-amd64 \ 112 | -t microsoft/iotedgedev:latest-windows-amd64 \ 113 | -t mcr.microsoft.com/public/iotedge/iotedgedev:$VERSION-windows-amd64 \ 114 | -t mcr.microsoft.com/public/iotedge/iotedgedev:latest-windows-amd64 \ 115 | -t $IMAGE_NAME:$VERSION-windows-amd64 \ 116 | -t $IMAGE_NAME:latest-windows-amd64 \ 117 | . 118 | 119 | rm iotedgedev-$VERSION-py3-none-any.whl --force 120 | 121 | cd .. 122 | } 123 | 124 | 125 | 126 | if [ ! -z "$PLATFORM" ]; then 127 | if [ "$PLATFORM" = "linux" ]; then 128 | build_windows=0 129 | echo "===== Building Linux image only" 130 | elif [ "$PLATFORM" = "windows" ]; then 131 | build_linux=0 132 | echo "===== Building Windows image only" 133 | else 134 | echo "Unknown option: $PLATFORM" 135 | echo "Use --help for help" 136 | exit 1 137 | fi 138 | else 139 | echo "===== Building Windows and Linux images" 140 | fi 141 | 142 | mode=$(get_docker_mode) 143 | echo "===== Docker is in '$mode' container mode" 144 | if [ $mode = "windows" ]; then 145 | # Docker is in Windows Container mode 146 | if [ $build_windows = "1" ]; then 147 | build_windows 148 | fi 149 | if [ $build_linux = "1" ]; then 150 | switch_docker 151 | build_linux 152 | switch_docker 153 | fi 154 | else 155 | # Docker is in Linux Container mode 156 | if [ $build_linux -eq "1" ]; then 157 | build_linux 158 | fi 159 | if [ $build_windows -eq "1" ]; then 160 | switch_docker 161 | build_windows 162 | switch_docker 163 | fi 164 | fi 165 | 166 | if [ in_docker_folder = 0 ]; then 167 | cd original_folder 168 | fi 169 | -------------------------------------------------------------------------------- /docker/tool/deps.txt: -------------------------------------------------------------------------------- 1 | PYTHON2_VERSION=2.7.14 2 | PYTHON3_VERSION=3.9.12 3 | NODEJS_VERSION=8.11.1 4 | DOTNETCORESDK_VERSION=2.1.4 5 | DOCKER_VERSION=17.09.0 6 | DOCKER_COMPOSE_VERSION 1.22.0 7 | 8 | -------------------------------------------------------------------------------- /docker/tool/linux/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM iotedgedev-linux-base 2 | ARG IOTEDGEDEV_VERSION 3 | COPY iotedgedev-$IOTEDGEDEV_VERSION-py3-none-any.whl dist/iotedgedev-$IOTEDGEDEV_VERSION-py3-none-any.whl 4 | RUN sudo -H pip3 install dist/iotedgedev-$IOTEDGEDEV_VERSION-py3-none-any.whl && \ 5 | sudo rm -rf dist/ 6 | ENV LC_ALL C.UTF-8 7 | ENV LANG C.UTF-8 8 | -------------------------------------------------------------------------------- /docker/tool/linux/Dockerfile.base: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/mirror/docker/library/ubuntu:20.04 2 | ENV DEBIAN_FRONTEND noninteractive 3 | ENV DOTNETCORESDK_VERSION 2.1 4 | ENV DOCKER_COMPOSE_VERSION 1.22.0 5 | ENV MAVEN_VERSION=3.5.4 6 | RUN apt-get update && apt-get install -y apt-transport-https ca-certificates curl software-properties-common && \ 7 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && \ 8 | add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \ 9 | apt-get update && \ 10 | apt-get install -y docker-ce 11 | RUN apt-get install git gnupg gnupg2 gnupg1 -y && \ 12 | apt-get install -y --no-install-recommends dialog apt-utils curl apt-transport-https python3-pip libltdl-dev && \ 13 | curl -sL https://deb.nodesource.com/setup_14.x | bash - && \ 14 | apt-get install -y nodejs 15 | RUN apt-get install -y wget && \ 16 | wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg && \ 17 | mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ && \ 18 | wget -q https://packages.microsoft.com/config/ubuntu/18.04/prod.list && \ 19 | mv prod.list /etc/apt/sources.list.d/microsoft-prod.list && \ 20 | apt-get install -y apt-transport-https && \ 21 | apt-get update && \ 22 | apt-get install -y dotnet-sdk-$DOTNETCORESDK_VERSION 23 | RUN curl -L https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose && \ 24 | chmod +x /usr/local/bin/docker-compose 25 | RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash 26 | RUN apt-get update && \ 27 | npm i npm@latest -g && \ 28 | npm i -g azure-iothub yo generator-azure-iot-edge-module && \ 29 | apt-get install -y --no-install-recommends python-dev build-essential libssl-dev libffi-dev libxml2-dev libxslt1-dev zlib1g-dev sudo 30 | RUN apt install python3.9 -y && \ 31 | rm /usr/bin/python3 && \ 32 | ln -s /usr/bin/python3.9 /usr/bin/python3 && \ 33 | python3 -m pip install --upgrade pip && \ 34 | pip3 install setuptools && \ 35 | pip3 install cookiecutter 36 | RUN apt-get -y install openjdk-8-jdk 37 | RUN apt-get update && \ 38 | apt-get install -y ca-certificates-java && \ 39 | update-ca-certificates -f && \ 40 | rm -rf /var/cache/oracle-jdk8-installer 41 | ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64 42 | ENV PATH $JAVA_HOME/bin:$PATH 43 | RUN mkdir -p /usr/share/maven /usr/share/maven/ref && \ 44 | curl -fsSL -o /tmp/apache-maven.tar.gz https://www.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz && \ 45 | tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 && \ 46 | rm -f /tmp/apache-maven.tar.gz && \ 47 | ln -s /usr/share/maven/bin/mvn /usr/bin/mvn 48 | RUN apt-get clean && \ 49 | rm -rf /var/lib/apt/lists/* 50 | WORKDIR /home/iotedge 51 | COPY install-dev.sh /scripts/install-dev.sh 52 | RUN sed -i 's/\r//' /scripts/install-dev.sh 53 | 54 | # Switch to a non-root user because Yeoman does not work with root users 55 | # https://github.com/Azure/iotedgedev/issues/312 56 | RUN useradd -m iotedgedev && \ 57 | echo 'iotedgedev ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \ 58 | chown -R iotedgedev:iotedgedev /home/iotedge 59 | USER iotedgedev 60 | # Azure CLI extensions are installed per user 61 | RUN az extension add --name azure-iot && \ 62 | echo 'alias python=python3' >> ~/.bashrc && \ 63 | echo 'alias pip=pip3' >> ~/.bashrc 64 | ENV DEBIAN_FRONTEND teletype 65 | -------------------------------------------------------------------------------- /docker/tool/linux/install-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script will be executed INSIDE the container 4 | 5 | cd /home/iotedge/tool 6 | pip install -r requirements_dev.txt 7 | pip install -e . 8 | cd /home/iotedge -------------------------------------------------------------------------------- /docker/tool/linux/run.ps1: -------------------------------------------------------------------------------- 1 | # Get IoTEdgeDev source folder 2 | $source_folder = Get-Location | Split-Path | Split-Path | Split-Path 3 | 4 | # Run Docker Container 5 | docker run -it -v //var//run//docker.sock://var//run//docker.sock -v ${source_folder}:/home/iotedge/tool microsoft/iotedgedev:latest-linux -------------------------------------------------------------------------------- /docker/tool/linux/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | folder=$(echo $PWD | cut -d/ -f-6 | sed 's,/,//,g') 4 | 5 | winpty docker run -it -v //var//run//docker.sock://var//run//docker.sock -v $folder://home//iotedge//tool microsoft/iotedgedev:latest-linux 6 | -------------------------------------------------------------------------------- /docker/tool/push-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # stop on error 4 | set -e 5 | 6 | function show_help 7 | { 8 | echo "Usage:" 9 | echo "push-docker.sh [<version>]" 10 | echo "" 11 | echo "version: version to push. Automatically detected if not specified" 12 | exit 1 13 | } 14 | 15 | # iotedgedevtoolscontainerregistry.azure.io is the ACR that has a webhook to publish to MCR 16 | # only this ACR should be used 17 | ACR_LOGIN_SERVER="iotedgedevtoolscontainerregistry.azurecr.io" 18 | IMAGE_NAME="iotedgedev" 19 | VERSION="$1" 20 | 21 | if [ "$VERSION" = "--help" ]; then 22 | show_help 23 | fi 24 | 25 | if [ -z "$VERSION" ]; then 26 | echo -e "\n===== Detecting version" 27 | VERSION=$(grep '__version__' ../../iotedgedev/__init__.py | grep -oP "'\K[^']+") 28 | echo "Detected version $VERSION" 29 | fi 30 | 31 | echo -e "\n===== Pushing Docker images" 32 | docker push $ACR_LOGIN_SERVER/public/iotedge/$IMAGE_NAME:$VERSION-amd64 33 | docker push $ACR_LOGIN_SERVER/public/iotedge/$IMAGE_NAME:$VERSION 34 | docker push $ACR_LOGIN_SERVER/public/iotedge/$IMAGE_NAME:latest-amd64 35 | docker push $ACR_LOGIN_SERVER/public/iotedge/$IMAGE_NAME:latest -------------------------------------------------------------------------------- /docker/tool/windows/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM iotedgedev-windows-base 2 | ARG IOTEDGEDEV_VERSION 3 | COPY iotedgedev-$IOTEDGEDEV_VERSION-py3-none-any.whl dist/iotedgedev-latest-py3-none-any.whl 4 | RUN pip install dist/iotedgedev-latest-py3-none-any.whl -------------------------------------------------------------------------------- /docker/tool/windows/Dockerfile.base: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/windows/servercore:1709 AS base 2 | SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] 3 | ARG PYTHON2_VERSION 4 | ARG PYTHON3_VERSION 5 | ENV NODEJS_VERSION 8.11.1 6 | ENV DOTNETCORESDK_VERSION 2.1.402 7 | ENV DOCKER_VERSION 17.09.0 8 | ENV GIT_VERSION 2.18.0 9 | ENV DESTINATION_FOLDER C:\\tools 10 | WORKDIR /tmp 11 | RUN $python_url = ('https://www.python.org/ftp/python/{0}/python-{0}.amd64.msi' -f $env:PYTHON2_VERSION); \ 12 | Write-Host ('Downloading {0}...' -f $python_url); \ 13 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \ 14 | (New-Object System.Net.WebClient).DownloadFile($python_url, '/tmp/python2-installer.msi'); \ 15 | $install_folder = Join-Path -Path $env:DESTINATION_FOLDER -ChildPath 'python2'; \ 16 | Write-Host ('Installing into {0}...' -f $install_folder); \ 17 | Start-Process python2-installer.msi -Wait -ArgumentList @('/quiet', 'InstallAllUsers=1', 'TargetDir={0}' -f $install_folder, 'PrependPath=1', 'Shortcuts=0', 'Include_doc=0','Include_pip=1', 'Include_test=0'); 18 | RUN $python_url = ('https://www.python.org/ftp/python/{0}/python-{0}-amd64.exe' -f $env:PYTHON3_VERSION); \ 19 | Write-Host ('Downloading {0}...' -f $python_url); \ 20 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \ 21 | (New-Object System.Net.WebClient).DownloadFile($python_url, '/tmp/python3-installer.exe'); \ 22 | $install_folder = Join-Path -Path $env:DESTINATION_FOLDER -ChildPath 'python3'; \ 23 | Write-Host ('Installing into {0}...' -f $install_folder); \ 24 | Start-Process python3-installer.exe -Wait -ArgumentList @('/quiet', 'InstallAllUsers=1', 'TargetDir={0}' -f $install_folder, 'PrependPath=1', 'Shortcuts=0', 'Include_doc=0','Include_pip=1', 'Include_test=0'); 25 | RUN $node_url = ('https://nodejs.org/dist/v{0}/node-v{0}-x64.msi' -f $env:NODEJS_VERSION); \ 26 | Write-Host ('Downloading {0}...' -f $node_url); \ 27 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \ 28 | (New-Object System.Net.WebClient).DownloadFile($node_url, '/tmp/nodejs-installer.msi'); \ 29 | $install_folder = Join-Path -Path $env:DESTINATION_FOLDER -ChildPath 'node'; \ 30 | Write-Host ('Installing into {0}...' -f $install_folder); \ 31 | Start-Process nodejs-installer.msi -Wait -ArgumentList @('/quiet', '/q', 'InstallDir={0}' -f $install_folder); 32 | RUN $dotnetcoresdk_url = ('https://dotnetcli.blob.core.windows.net/dotnet/Sdk/{0}/dotnet-sdk-{0}-win-x64.zip' -f $env:DOTNETCORESDK_VERSION); \ 33 | Write-Host ('Downloading {0}...' -f $dotnetcoresdk_url); \ 34 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \ 35 | (New-Object System.Net.WebClient).DownloadFile($dotnetcoresdk_url, '/tmp/dotnetcoresdk.zip'); \ 36 | $unpack_folder = Join-Path -Path $env:DESTINATION_FOLDER -ChildPath 'dotnetcoresdk'; \ 37 | Write-Host ('Unpacking into {0}...' -f $unpack_folder); \ 38 | Expand-Archive dotnetcoresdk.zip -DestinationPath $unpack_folder; 39 | RUN $docker_url = ('https://download.docker.com/win/static/stable/x86_64/docker-{0}-ce.zip' -f $env:DOCKER_VERSION);\ 40 | (New-Object System.Net.WebClient).DownloadFile($docker_url, '/tmp/docker.zip'); \ 41 | $install_folder = Join-Path -Path $env:DESTINATION_FOLDER -ChildPath ''; \ 42 | Expand-Archive -Path .\docker.zip -DestinationPath $install_folder; \ 43 | Remove-Item ('{0}\docker\dockerd.exe' -f $env:DESTINATION_FOLDER) 44 | RUN $git_url = ('https://github.com/git-for-windows/git/releases/download/v{0}.windows.1/MinGit-{0}-64-bit.zip' -f $env:GIT_VERSION); \ 45 | Write-Host ('Downloading {0}...' -f $git_url); \ 46 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \ 47 | (New-Object System.Net.WebClient).DownloadFile($git_url, '/tmp/git.zip'); \ 48 | $unpack_folder = Join-Path -Path $env:DESTINATION_FOLDER -ChildPath 'git'; \ 49 | Write-Host ('Unpacking into {0}...' -f $unpack_folder); \ 50 | Expand-Archive .\git.zip -DestinationPath $unpack_folder; 51 | 52 | FROM microsoft/nanoserver:1709 53 | RUN mkdir c:\\tools 54 | COPY --from=base ["tools/", "/tools"] 55 | USER ContainerAdministrator 56 | RUN setx /M PATH "%PATH%;C:\tools\dotnetcoresdk;C:\tools\node\;C:\tools\python3;C:\tools\python3\Scripts;c:\tools\docker\;" 57 | USER ContainerUser 58 | RUN python -m pip install --upgrade pip 59 | RUN pip install azure-cli cookiecutter docker-compose 60 | RUN npm i -g iothub-explorer yo generator-azure-iot-edge-module 61 | RUN az extension add --name azure-iot 62 | WORKDIR /home/iotedge 63 | COPY install-dev.bat /scripts/install-dev.bat -------------------------------------------------------------------------------- /docker/tool/windows/install-dev.bat: -------------------------------------------------------------------------------- 1 | cd /home/iotedge/tool 2 | pip install -r requirements_dev.txt 3 | pip install -e . 4 | cd /home/iotedge -------------------------------------------------------------------------------- /docker/tool/windows/run.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$interface = 'vEthernet (nat)' 3 | ) 4 | 5 | $ErrorActionPreference = "Stop" 6 | 7 | # 8 | # NOTE 9 | # Make sure option "Expose daemon on tcp://localhost:2375 without TLS" is checked in Docker 10 | # and also make sure that 11 | # "hosts": [ "tcp://0.0.0.0:2375" ] 12 | # is added to your 13 | # C:\ProgramData\Docker\config\daemon.json 14 | # file 15 | 16 | # Make sure firewall allows comminication via TCP 2375 17 | if ((Get-NetFirewallRule -DisplayName "Docker Proxy" -ErrorAction Ignore) -eq $null) 18 | { 19 | New-NetFirewallRule -DisplayName "Docker Proxy" -LocalPort 2375 -Action Allow -Protocol TCP 20 | } 21 | 22 | # Find IP Address used by Docker NAT 23 | $docker_gateway = (Get-NetIPAddress -InterfaceAlias $interface -AddressFamily IPv4 | Select-Object IPAddress).IPAddress 24 | $docker_host ="tcp://{0}:2375" -f $docker_gateway 25 | 26 | # Get IoTEdgeDev source folder 27 | $source_folder = Get-Location | Split-Path | Split-Path 28 | 29 | # Run Docker Container 30 | docker run -it -e DOCKER_HOST=${docker_host} -v ${source_folder}:c:/home/iotedge/tool microsoft/iotedgedev:latest-windows -------------------------------------------------------------------------------- /docs/_Sidebar.md: -------------------------------------------------------------------------------- 1 | * [Home](home) 2 | * Dev Machine Setup 3 | * [Quickstart](quickstart) 4 | * [Manual Setup](environment-setup/manual-dev-machine-setup) 5 | * [Azure Setup](azure-setup) 6 | * [Edge Device Setup](edge-device-setup) 7 | * [Command List](command-list) 8 | * [Command Tips](command-tips) 9 | * [Python Virtual Environment Setup](environment-setup/python-virtual-environment-setup) 10 | * [Test Coverage](test-coverage) 11 | * [Troubleshooting](troubleshooting) 12 | * [Contributing](https://github.com/Azure/iotedgedev/blob/main/CONTRIBUTING.md) 13 | * Misc 14 | * [Migration Guides for Public Preview Users](migration-guides) 15 | -------------------------------------------------------------------------------- /docs/azure-setup.md: -------------------------------------------------------------------------------- 1 | ## Automated Setup 2 | 3 | The following will show you how to setup your Azure Resources via the CLI instead of using the Portal. 4 | 5 | First, create a solution with the following command: 6 | 7 | `iotedgedev new edgesolution1` 8 | 9 | Then, `cd` into that solution: 10 | 11 | `cd edgesolution` 12 | 13 | Then, run the `iotedgedev iothub setup` command to setup your Azure Resources. This command will bring you through a series of prompts to create Azure Resources and retrieve your IoT Hub and Edge Device connection strings and save them to the `.env` file in the root of the project. All subsequent commands will use those environment variables. 14 | 15 | Here are all the `iotedgedev iothub setup` command options: 16 | 17 | > You can override all of these parameters with environment variables. Please see the .env file in your solution for details. 18 | 19 | ```sh 20 | iotedgedev iothub setup 21 | --credentials USERNAME PASSWORD 22 | --service-principal USERNAME PASSWORD TENANT 23 | --subscription THE_SUBSCRIPTION_ID 24 | --resource-group-location THE_RG_LOCATION 25 | --resource-group-name THE_RG_NAME 26 | --iothub-sku THE_IOT_SKU 27 | --iothub-name THE_IOT_NAME 28 | --edge-device-id THE_EDGE_DEVICE_NAME 29 | --update-dotenv 30 | ``` 31 | 32 | You can use the following `az cli` command to create a service principal: 33 | 34 | ```sh 35 | az ad sp create-for-rbac -n "iotedgedev01" 36 | ``` 37 | 38 | > Note: Running `iotedgedev iothub setup` without any other parameters will save you time from looking up the required parameter values. The command will help you choose the parameters in an interactive way. 39 | 40 | Alternatively, you can deploy the IoT Hub **and** Container Registry with this **Deploy to Azure** template: 41 | 42 | [![Deploy to Azure Button](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fazure%2Fiotedgedev%2Fmain%2Fassets%2Fdeploy%2FARMDeployment%2Fazuredeploy.json) 43 | 44 | > Note: If you do not need a Container Registry, or are planning to use a local registry, then you should run the **iotedgedev iothub setup** command instead of running this **Deploy to Azure** template, because the template includes a Container Registry. 45 | 46 | ## Manual Setup 47 | 48 | 1. [**Create Azure IoT Hub**](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-csharp-csharp-getstarted#create-an-iot-hub) 49 | 2. **Create Edge Device** using the Azure Portal 50 | - In your IoT Hub, click "IoT Edge", then click "Add IoT Edge Device" 51 | 3. **Container Registry** 52 | When you develop for IoT Edge, you need to host your images in a container registry, which the IoT Edge runtime will fetch the modules images from when it starts. 53 | 54 | > By default, the IoT Edge Dev Tool will use the Local Registry. 55 | 56 | We have tested the following options, but you can host your images on any Docker compatible registry host. 57 | 58 | 1. Local Registry 59 | 60 | Set `CONTAINER_REGISTRY_SERVER` to `localhost:5000` and leave `CONTAINER_REGISTRY_USERNAME` and `CONTAINER_REGISTRY_PASSWORD` blank. 61 | 62 | ```sh 63 | CONTAINER_REGISTRY_SERVER="localhost:5000" 64 | ``` 65 | 66 | 2. Azure Container Registry 67 | 68 | You can create an [**Azure Container Registry**](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal) and host your images there. 69 | - Make sure you enable Admin Access when you create the Azure Container Registry 70 | 71 | After created, open .env and set the following: 72 | 73 | ```sh 74 | CONTAINER_REGISTRY_SERVER="ACR URI" 75 | CONTAINER_REGISTRY_USERNAME="ACR USERNAME" 76 | CONTAINER_REGISTRY_PASSWORD="ACR PASSWORD" 77 | ``` 78 | 79 | Example: 80 | 81 | ```sh 82 | CONTAINER_REGISTRY_SERVER="jong.azurecr.io" 83 | CONTAINER_REGISTRY_USERNAME="jong" 84 | CONTAINER_REGISTRY_PASSWORD="p@$$w0rd" 85 | ``` 86 | 87 | 3. Docker Hub 88 | 89 | You can also host your images on Docker Hub. Create a Docker Hub account and then open .env and enter the following: 90 | 91 | ```sh 92 | CONTAINER_REGISTRY_SERVER="DOCKER HUB USERNAME" 93 | CONTAINER_REGISTRY_USERNAME="DOCKER HUB USERNAME" 94 | CONTAINER_REGISTRY_PASSWORD="DOCKER HUB PASSWORD" 95 | ``` 96 | 97 | Example: 98 | 99 | ```sh 100 | CONTAINER_REGISTRY_SERVER="jongallant" 101 | CONTAINER_REGISTRY_USERNAME="jongallant" 102 | CONTAINER_REGISTRY_PASSWORD="p@$$w0rd" 103 | ``` 104 | -------------------------------------------------------------------------------- /docs/command-tips.md: -------------------------------------------------------------------------------- 1 | ### Setup Container Registry 2 | 3 | You can also use IoT Edge Dev Tool to host the IoT Edge runtime from your own Azure Container Registry or a local container registry. Set the `.env` values for your container registry and run the following command. It will pull all the IoT Edge containers from Microsoft Container Registry, tag them and push them to the container registry you have specified in `.env`. 4 | 5 | > Use 'sudo' for Linux/Raspberry Pi 6 | 7 | ``` 8 | iotedgedev docker setup-registry 9 | ``` 10 | 11 | 12 | ### View Docker Logs 13 | 14 | #### Show Logs 15 | IoT Edge Dev Tool also includes a "Show Logs" command that will open a new command prompt for each module it finds in your IoT Edge config. Just run the following command: 16 | 17 | > Note: I haven't figured out how to launch new SSH windows in a reliable way. It's in the backlog. For now, you must be on the desktop of the machine to run this command. 18 | 19 | ``` 20 | iotedgedev docker log --show 21 | ``` 22 | 23 | You can configure the logs command in the `.env` file with the `LOGS_CMD` setting. The `.env` file provides two options, one for [ConEmu](https://conemu.github.io/) and one for Cmd.exe. 24 | 25 | #### Save Logs 26 | 27 | You can also output the logs to the LOGS_PATH directory. The following command will output all the logs and add them to an `edge-logs.zip` file that you can send to the Azure IoT support team if they request it. 28 | 29 | ``` 30 | iotedgedev docker log --save 31 | ``` 32 | 33 | #### Both Show and Save Logs 34 | 35 | Run the following to show and save logs with a single command 36 | 37 | ``` 38 | iotedgedev docker log --show --save 39 | ``` 40 | 41 | 42 | ### Local Docker Registry Setup 43 | 44 | Instead of using a cloud based container registry, you can use a local Docker registry. Here's how to get it setup. 45 | 46 | 1. Set `CONTAINER_REGISTRY_SERVER` in `.env` file to `localhost:5000`. You can enter a different port if you'd like to. 47 | 1. Add `localhost:5000` and `127.0.0.1:5000` to Docker -> Settings -> Daemon -> Insecure Registries 48 | 49 | > In the latest IoT Edge Dev Tool, Step 2 above hasn't been required. But, if you run into issues, you may want to try adding those Insecure Registries. 50 | 51 | IoT Edge Dev Tool will look for `localhost` in your setting and take care of the rest for you. -------------------------------------------------------------------------------- /docs/edge-device-setup.md: -------------------------------------------------------------------------------- 1 | IoT Edge Dev Tool is intended to help with IoT Edge development and doesn't necessarily need to be taken on as a dependency in production or integration environments, where you'll likely want to use the IoT Edge runtime directly. Please check below documents for the instructions on setting up IoT Edge runtime: 2 | - [Linux X64](https://docs.microsoft.com/en-us/azure/iot-edge/how-to-install-iot-edge-linux) 3 | - [Linux ARM32](https://docs.microsoft.com/en-us/azure/iot-edge/how-to-install-iot-edge-linux-arm) 4 | - [Windows with Windows container](https://docs.microsoft.com/en-us/azure/iot-edge/how-to-install-iot-edge-windows-with-windows) 5 | - [Windows with Linux container](https://docs.microsoft.com/en-us/azure/iot-edge/how-to-install-iot-edge-windows-with-linux) 6 | - [IoT Core](https://docs.microsoft.com/en-us/azure/iot-edge/how-to-install-iot-core) 7 | 8 | Having said that, there's nothing stopping you from deploying IoT Edge Dev Tool to your IoT Edge device. It may be helpful if you want to run the `iotedgedev docker clean` command to clean up Docker containers and images. Or if you want to run `iotedgedev docker log --show` to see all the log files on the device or `iotedgedev docker log --save` to output to the LOGS_PATH directory. 9 | 10 | > Please note that you will need .NET Core SDK 2.1 to add C# modules and C# Function modules on a Raspberry Pi. Check [Manual Dev Machine Setup](environment-setup/manual-dev-machine-setup) to learn how to install. 11 | 12 | ### Raspberry Pi 13 | 14 | #### Config Changes 15 | 16 | If you are running on Raspberry Pi you need to use the arm32v7 Dockerfile. Open `deployment.template.json`, find the JSON dictionary for `filtermodule`, and replace `"image": "${MODULES.filtermodule.amd64}",` with `"image": "${MODULES.filtermodule.arm32v7}",` 17 | 18 | #### Environment Variable Change 19 | 20 | Open your `.env` file and add `arm32v7` to your `ACTIVE_DOCKER_PLATFORMS` setting. This will tell the IoT Edge Dev Tool to also build the arm32v7 images. 21 | 22 | ```sh 23 | ACTIVE_DOCKER_PLATFORMS="amd64,arm32v7" 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/environment-setup/install-docker.md: -------------------------------------------------------------------------------- 1 | # Install [Docker CE](https://docs.docker.com/install/) 2 | 3 | - Windows 4 | - Be sure to check whether you are running in Linux container mode or Windows container mode. 5 | - Linux 6 | - We've seen some issues with docker.io. If Edge doesn't run for you, then try installing Docker CE directly instead of via docker.io. Use the [CE install steps](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-docker-ce), or use the [convenience script](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-using-the-convenience-script). 7 | - By default, you need `sudo` to run `docker` commands. If you want to avoid this, please follow the [post-installation steps for Linux](https://docs.docker.com/install/linux/linux-postinstall/#manage-docker-as-a-non-root-user). 8 | 9 | Note: If the device is behind the proxy server, you can set the [proxy manually](https://docs.docker.com/network/proxy/#configure-the-docker-client). 10 | -------------------------------------------------------------------------------- /docs/environment-setup/manual-dev-machine-setup.md: -------------------------------------------------------------------------------- 1 | Here is what you need to do to get Azure IoT Edge Dev Tool (aka `iotedgedev`) running on your dev machine manually instead of using the IoT Edge Dev Container. 2 | 3 | If you are using a separate Edge device, like a Raspberry Pi, you do not need to run all of these steps on your IoT Edge device, you can install and setup Edge runtime directly on the device. See the [Edge Device Setup](edge-device-setup) wiki page for more information on setting up your Edge device. 4 | 5 | > Note: See the ["Test Coverage"](test-coverage) wiki page to see what the IoT Edge Dev Tool has been tested with. 6 | 7 | 1. Install **Python 2.7+ or Python 3.6+** and **pip** (Python 3.6 is recommended) 8 | - Windows: [Install from Python's website](https://www.python.org/downloads/) 9 | - Linux: `sudo apt install python-pip` or `sudo apt install python3-pip` 10 | - macOS: The OpenSSL used by the system built-in Python is old and vulnerable. Please use Python installed with [Homebrew](https://docs.brew.sh/Homebrew-and-Python) 11 | 12 | 2. Install **[Azure CLI 2.0](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest)** 13 | 14 | 3. Install **[Azure CLI IoT extension](https://github.com/Azure/azure-iot-cli-extension/)** 15 | 16 | - New Install: `az extension add --name azure-iot` 17 | - Update Install: `az extension update --name azure-iot` 18 | 19 | 4. (Python < 3.5 only) Install **[Node.js](https://nodejs.org/en/download/)** and the **`iothub-explorer`** package 20 | 21 | - uamqp, which is needed by Azure CLI IoT extension for monitoring messages, is not supported in Python < 3.5. For Python < 3.5 users, please install [Node.js](https://nodejs.org/en/download/) and the `iothub-explorer` Node.js package: `npm i -g iothub-explorer` 22 | 23 | 5. (Raspberry Pi only) Install extra system dependencies 24 | 25 | ```sh 26 | sudo apt-get install python2.7-dev libffi-dev libssl-dev -y 27 | ``` 28 | 29 | 6. (Linux only) Install [Docker Compose](https://docs.docker.com/compose/) 30 | 31 | ```sh 32 | pip install -U docker-compose 33 | ``` 34 | 35 | 7. Install **`iotedgedev`** 36 | 37 | > You do not need to do this if you're going to be developing on iotedgedev 38 | 39 | > You do not need to run this on the Edge device. See the [Edge Device Setup](edge-device-setup) page for more information on setting up your Edge device. 40 | 41 | > You can also run under a Python Virtual Environment. See the [Python Virtual Environment Setup](python-virtual-environment-setup) instructions page for details on how to set that up. 42 | 43 | > There is a known dependency conflict between `iotedgedev` and now-deprecated `azure-iot-edge-runtime-ctl`. Please make sure you have uninstalled `azure-iot-edge-runtime-ctl` before installing `iotedgedev`: `pip uninstall azure-iot-edge-runtime-ctl`, or use a clean virtual environment. 44 | 45 | ```sh 46 | pip install -U iotedgedev 47 | ``` 48 | 49 | 8. Install module dependencies 50 | 51 | - **C# module and C# Azure Functions module** 52 | 53 | Install **[.NET Core SDK 2.1 or later](https://www.microsoft.com/net/download)** 54 | > You need .NET Core SDK 2.1 or later to run it on ARM. There is no official document on installing .NET Core SDK on ARM yet, but you can follow the [ARM32v7 Dockerfile](https://github.com/dotnet/dotnet-docker/blob/master/src/sdk/3.1/buster/arm32v7/Dockerfile). 55 | 56 | - **Python module** 57 | 58 | 1. Install **[Git](https://git-scm.com/)** 59 | 2. Install **[Cookiecutter](https://github.com/audreyr/cookiecutter)** 60 | 61 | ```sh 62 | pip install -U cookiecutter 63 | ``` 64 | 65 | - **Node.js module** 66 | 67 | 1. Install **[Node.js](https://nodejs.org/en/download/)** 68 | 2. Install **[Yeoman](http://yeoman.io/)** and **[Azure IoT Edge Node.js module generator](https://github.com/Azure/generator-azure-iot-edge-module)** packages 69 | 70 | ```sh 71 | npm i -g yo generator-azure-iot-edge-module 72 | ``` 73 | 74 | - **Java module** 75 | 76 | 1. Install **[JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html)** 77 | 2. Install **[Maven](https://maven.apache.org/)** 78 | 79 | 9. Follow the [Usage Wiki](../usage.md) to learn the usage of IoT Edge Dev Tool 80 | -------------------------------------------------------------------------------- /docs/environment-setup/python-virtual-environment-setup.md: -------------------------------------------------------------------------------- 1 | You can run IoT Edge Dev Tool inside a [Python Virtual Environment](https://docs.python.org/3/tutorial/venv.html). A Python virtual environment is a self-contained directory tree that contains a Python installation for a particular version of Python, plus a number of additional packages. 2 | 3 | 1. Install `virtualenv` 4 | 5 | `pip install virtualenv` 6 | 7 | 2. Create a virtual environment 8 | 9 | `virtualenv venv` 10 | 11 | > `venv` is just a env name that can be anything you want, but we recommend sticking with `venv` if you want to contribute to IoT Edge Dev Tool because the `.gitignore` file excludes it. 12 | 13 | > To create a virtual environment with a Python version different with your system default, just use the `--python/-p` option to specify the Python executable path, *e.g.*: 14 | > 15 | > `virtualenv --python /usr/bin/python2.7 py27` 16 | 17 | 3. Activate the virtual environment 18 | 19 | - Windows 20 | - cmd.exe: `venv\Scripts\activate.bat` 21 | - PowerShell: `venv\Scripts\activate.ps1` (You may need to run `Set-ExecutionPolicy RemoteSigned` in an Administrator Powershell first to allow scripts to run) 22 | 23 | - Posix: `source venv/bin/activate` 24 | > It will be active until you deactivate it or close the terminal instance. 25 | 26 | 4. Install dependencies 27 | 28 | Continue with the instructions above starting with the [Manual Dev Machine Setup](Environment-Setup/manual-dev-machine-setup) -> Install Dependencies. 29 | 30 | 5. Deactivate the virtual environment 31 | 32 | When you are done with your virtualenv, you can deactivate it with the follow command: 33 | 34 | `deactivate` 35 | -------------------------------------------------------------------------------- /docs/environment-setup/run-devcontainer-docker.md: -------------------------------------------------------------------------------- 1 | # Run the IoT Edge Dev Container with Docker 2 | 3 | Before you run the container, you will need to create a local folder to store your IoT Edge solution files. 4 | 5 | ## Windows 6 | 7 | ```cmd 8 | mkdir c:\temp\iotedge 9 | docker run -ti -v /var/run/docker.sock:/var/run/docker.sock -v c:/temp/iotedge:/home/iotedge mcr.microsoft.com/iotedge/iotedgedev 10 | ``` 11 | 12 | ## Linux 13 | 14 | ```bash 15 | sudo mkdir /home/iotedge 16 | sudo docker run -ti -v /var/run/docker.sock:/var/run/docker.sock -v ~/iotedge:/home/iotedge mcr.microsoft.com/iotedge/iotedgedev 17 | ``` 18 | 19 | ## macOS 20 | 21 | ```bash 22 | mkdir ~/iotedge 23 | docker run -ti -v /var/run/docker.sock:/var/run/docker.sock -v ~/iotedge:/home/iotedge mcr.microsoft.com/iotedge/iotedgedev 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/home.md: -------------------------------------------------------------------------------- 1 | Welcome to the IoT Edge Dev Tool wiki! 2 | 3 | The **IoT Edge Dev Tool** greatly simplifies [Azure IoT Edge](https:/azure.microsoft.com/en-us/services/iot-edge/) development down to simple commands driven by environment variables. 4 | 5 | - It gets you started with IoT Edge development with the [IoT Edge Dev Container](quickstart) and IoT Edge solution scaffolding that contains a default module and all the required configuration files. 6 | - It speeds up your inner-loop dev (dev, debug, test) by reducing multi-step build & deploy processes into one-line CLI commands as well as drives your outer-loop CI/CD pipeline. _You can use all the same commands in both stages of your development life-cycle._ 7 | 8 | ## Issues 9 | 10 | Please use the [GitHub issues page](https://github.com/azure/iotedgedev/issues) to report any issues. 11 | -------------------------------------------------------------------------------- /docs/overview.md: -------------------------------------------------------------------------------- 1 | The **Azure IoT Edge Dev Tool** enables you to do all of the following with simple one-line CLI commands. 2 | 3 | 1. **Start Container**: 4 | 5 | `docker run -it -v /var/run/docker.sock:/var/run/docker.sock -v c:/temp/iotedge:/home/iotedge microsoft/iotedgedev` 6 | 7 | This container includes all of the dependencies you need for IoT Edge development, including: 8 | 9 | - Docker 10 | - Python 11 | - Pip 12 | - Azure CLI 13 | - Git 14 | - .NET Core SDK 15 | - OpenJDK 16 | - Node.js 17 | - [C# module template](https://www.nuget.org/packages/Microsoft.Azure.IoT.Edge.Module/) 18 | - Maven (for scaffolding [Java modules](https://github.com/Microsoft/azure-maven-archetypes/tree/master/azure-iot-edge-archetype)) 19 | - [Yeoman](http://yeoman.io/) and [Node.js module template](https://www.npmjs.com/package/generator-azure-iot-edge-module) 20 | - [Cookiecutter](https://cookiecutter.readthedocs.io/en/latest/) (for scaffolding [Python modules](https://github.com/Azure/cookiecutter-azure-iot-edge-module)) 21 | - [C# Functions module template](https://www.nuget.org/packages/Microsoft.Azure.IoT.Edge.Function/) 22 | 23 | You can also directly install with: `pip install iotedgedev` 24 | 25 | 1. **Create Solution**: Create a new IoT Edge Solution that includes a sample module and all the the required configuration files. 26 | 27 | ``` 28 | iotedgedev new edgesolution` 29 | cd edgesolution 30 | ``` 31 | 32 | 1. **Setup Azure**: Creates or selects your Azure IoT Hub and Edge Device and updates your Environment Variables. 33 | 34 | `iotedgedev iothub setup` 35 | 36 | > This must be run from the root of your solution, so make sure you cd into the `edgesolution1` folder before you run this command. 37 | 38 | 1. **Build, Push & Deploy**: Build, Push and Deploy modules: 39 | 40 | `iotedgedev push --deploy` 41 | 42 | > This will run `iotedgedev build`, `iotedgedev push`, and `iotedgedev deploy` on deployment.template.json targeting amd64 platform. You can use the `--file` and `--platform` parameters to change this behavior. 43 | 44 | If your module is included in the `BYPASS_MODULES` environment variable, or not included in the deployment manifest template, then the `iotedgedev build` and `iotedgedev push` steps will be skipped. 45 | 46 | 1. **Setup**: Setup the IoT Edge Simulator (provided by the [iotedgehubdev](https://pypi.org/project/iotedgehubdev/) tool): 47 | 48 | `iotedgedev setup` 49 | 50 | 1. **Start**: Start IoT Edge Simulator: 51 | 52 | `iotedgedev start -f config/deployment.amd64.json` 53 | 54 | 1. **View Messages**: View Messages Sent from IoT Edge to IoT Hub: 55 | 56 | `iotedgedev monitor` 57 | 58 | 1. **View Logs**: View and Save Docker log files: 59 | 60 | `iotedgedev docker log` 61 | 62 | 1. **Setup Custom Registry**: Use a Custom Container Registry: 63 | 64 | `iotedgedev docker setup-registry` 65 | 66 | Please see [Azure IoT Edge Dev Resources](https://github.com/Azure/iotedgedev) for links to official docs and other IoT Edge dev information. -------------------------------------------------------------------------------- /docs/run-modules-on-simulator.md: -------------------------------------------------------------------------------- 1 | 2 | # Set up and start the IoT Edge Simulator 3 | 4 | In order to run the modules defined in a deployment configuration (such as `config/deployment.amd64.json`) on your development machine using the `iotedgedev` simulator run the following command: 5 | 6 | ```sh 7 | iotedgedev start --setup --file config/deployment.amd64.json 8 | ``` 9 | 10 | > The [IoT Edge Hub Simulator](https://github.com/Azure/iotedgehubdev) starts the containers defined in the IoT edge device manifest in your local machine. 11 | 12 | <details> 13 | <summary>More information</summary> 14 | 15 | 1. You will see an "IoT Edge Simulator has been started in solution mode." message at the end of the terminal output 16 | 2. Run ` docker ps`, you will see your modules running as well as an edgeHubDev container 17 | 18 | </details> 19 | -------------------------------------------------------------------------------- /docs/run-modules-on-vm.md: -------------------------------------------------------------------------------- 1 | # Set up and start modules on a virtual machine 2 | 3 | 1. [**Set up virtual machine to run Azure IoT Edge**](https://docs.microsoft.com/en-us/azure/iot-edge/how-to-install-iot-edge-ubuntuvm) 4 | 2. **Create and configure an ACR (Azure Container Registry)** 5 | 1. [Create an Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal) 6 | 2. Configure `.env` that was generated in the `iotedgedev init` step with the ACR by modifying these lines with appropriate values: 7 | 8 | ```sh 9 | CONTAINER_REGISTRY_SERVER="localhost:5000" 10 | CONTAINER_REGISTRY_USERNAME="" 11 | CONTAINER_REGISTRY_PASSWORD="" 12 | ``` 13 | 14 | 3. **Push modules to ACR with** 15 | 16 | `iotedgedev push` 17 | 18 | 4. **Add registry credentials to deployment manifest** 19 | 20 | 1. Replace generated `deployment.template.json` field `registryCredentials` with 21 | 22 | ```json 23 | "registryCredentials": { 24 | "privateAcr": { 25 | "username": "$CONTAINER_REGISTRY_USERNAME", 26 | "password": "$CONTAINER_REGISTRY_PASSWORD", 27 | "address": "$CONTAINER_REGISTRY_SERVER" 28 | } 29 | } 30 | ``` 31 | 32 | > Note: `privateAcr` can be renamed to anything else 33 | 2. Regenerate the `config/deployment.amd64.json` 34 | 35 | `iotedgedev genconfig` 36 | 37 | 5. **Deploy modules to device** 38 | 39 | `iotedgedev solution deploy` 40 | 41 | 6. **Troubleshooting** 42 | 43 | - Verify modules deployment status via executing: 44 | 45 | ```bash 46 | # Populate variables from .env 47 | edge_device_id=$(dotenv get DEVICE_CONNECTION_STRING | grep -oP '(?<=DeviceId=).*(?=;Shared)') 48 | iot_hub_connection_string=$(dotenv get IOTHUB_CONNECTION_STRING) 49 | # Execute query 50 | az iot hub module-identity list --device-id $edge_device_id --login $iot_hub_connection_string 51 | ``` 52 | 53 | - [Troubleshoot IoT Edge devices from the Azure portal](https://docs.microsoft.com/en-us/azure/iot-edge/troubleshoot-in-portal) 54 | - SSH into the virtual machine and follow [troubleshoot your IoT Edge device](https://docs.microsoft.com/en-us/azure/iot-edge/troubleshoot) 55 | -------------------------------------------------------------------------------- /docs/test-coverage.md: -------------------------------------------------------------------------------- 1 | This module has been tested with the following: 2 | - Windows 10 April 2018 Update 3 | - Ubuntu 16.04 4 | - macOS High Sierra 10.13.6 5 | - Raspberry Pi with Raspbian Stretch 6 | - Python 2.7.13, Python 3.6.5 and Python 3.7.0 7 | - Docker Version 18.06.1-ce-win73 (19507), Channel: stable, b0f14f1 -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | 1. `Invalid Reference Format` 2 | 3 | ```sh 4 | 500 Server Error: Internal Server Error for url: http+docker://localunixsocket/v1.30/images 5 | 500 Server Error: Internal Server Error ("invalid reference format") 6 | ``` 7 | 8 | Solution: You likely installed Docker via `sudo apt install docker.io`. Use the proper steps for [CE here](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-docker-ce), or use the [convenience script](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-using-the-convenience-script). 9 | 10 | 2. `('Connection aborted.', error(2, 'No such file or directory'))` 11 | 12 | This means that you have likely do not have `DOCKER_HOST` Environment Variable set. 13 | 14 | ```sh 15 | ERROR: Could not login to Container Registry. Please verify your credentials in CONTAINER_REGISTRY_ environment variables. 16 | ``` 17 | 18 | ```sh 19 | ('Connection aborted.', error(2, 'No such file or directory')) 20 | ERROR: Could not connect to docker daemon. 21 | ERROR: Docker is unavailable 22 | CRITICAL: IoT Edge dependency not available: docker -------------------------------------------------------------------------------- /iotedgedev/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for Azure IoT Edge Dev Tool.""" 2 | 3 | __author__ = 'Microsoft Corporation' 4 | __email__ = 'opencode@microsoft.com' 5 | __version__ = '3.3.8' 6 | __AIkey__ = '95b20d64-f54f-4de3-8ad5-165a75a6c6fe' 7 | -------------------------------------------------------------------------------- /iotedgedev/args.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # We had to implement this custom arg parsing class because Click doesn't have a handler to detect command before any arguments are parsed, 4 | # which we need for the dotenv load command. We don't want to load dotenv for some commands and we terse output for other commands. 5 | 6 | 7 | class Args(): 8 | def get_current_command(self): 9 | if sys.argv and len(sys.argv) > 1 and not self.is_info_command(): 10 | return ' '.join(sys.argv[1:]).strip() 11 | else: 12 | return '' 13 | 14 | def is_info_command(self): 15 | for arg in sys.argv: 16 | if arg.startswith('--version') or arg.startswith('-h') or arg.startswith('--help'): 17 | return True 18 | return False 19 | -------------------------------------------------------------------------------- /iotedgedev/buildprofile.py: -------------------------------------------------------------------------------- 1 | class BuildProfile: 2 | def __init__(self, dockerfile, context_path, extra_options): 3 | self.dockerfile = dockerfile 4 | self.context_path = context_path 5 | self.extra_options = extra_options 6 | -------------------------------------------------------------------------------- /iotedgedev/connectionstring.py: -------------------------------------------------------------------------------- 1 | from .utility import Utility 2 | 3 | 4 | class ConnectionString: 5 | def __init__(self, connection_string: str): 6 | self.connection_string = connection_string 7 | self.data = dict() 8 | 9 | if self.connection_string: 10 | parts = self.connection_string.split(';') 11 | if len(parts) > 0: 12 | for part in parts: 13 | subpart = part.split('=', 1) 14 | if len(subpart) == 2: 15 | self.data[subpart[0].lower()] = subpart[1].strip() 16 | 17 | if self.data: 18 | self.iothub_host = IoTHubHost(self["hostname"]) 19 | self.shared_access_key = None 20 | if("sharedaccesskey" in self.data): 21 | self.shared_access_key = self["sharedaccesskey"] 22 | else: 23 | # unexpected input of connection. Set to None and throw error 24 | self.connection_string = None 25 | 26 | def __getitem__(self, key): 27 | return self.data[key] 28 | 29 | 30 | class IoTHubConnectionString(ConnectionString): 31 | def __init__(self, connection_string: str): 32 | super().__init__(connection_string) 33 | 34 | if self.connection_string: 35 | self.shared_access_key_name = self["sharedaccesskeyname"] 36 | 37 | 38 | class DeviceConnectionString(ConnectionString): 39 | def __init__(self, connection_string: str): 40 | super().__init__(connection_string) 41 | 42 | if("deviceid" in self.data): 43 | self.device_id = self["deviceid"] 44 | else: 45 | self.device_id = None 46 | 47 | 48 | class IoTHubHost: 49 | def __init__(self, hostname): 50 | self.name = hostname 51 | if self.name and "." in self.name: 52 | self.hub_name = self.name.split('.')[0] 53 | # get connection string hostname hash to count distint IoT Hub number 54 | self.name_hash = Utility.get_sha256_hash(self.name) 55 | # get hostname suffix (e.g., azure-devices.net) to distinguish national clouds 56 | self.name_suffix = self.name[self.name.index(".")+1:] 57 | else: 58 | self.hub_name = "" 59 | self.name_hash = "" 60 | self.name_suffix = "" 61 | -------------------------------------------------------------------------------- /iotedgedev/constants.py: -------------------------------------------------------------------------------- 1 | class Constants: 2 | default_config_folder = "config" 3 | default_modules_folder = "modules" 4 | default_deployment_template_file = "deployment.template.json" 5 | default_deployment_debug_template_file = "deployment.debug.template.json" 6 | default_platform = "amd64" 7 | deployment_template_suffix = ".template.json" 8 | deployment_template_schema_version = "4.0.0" 9 | moduledir_placeholder_pattern = r'\${MODULEDIR<(.+)>(\..+)?}' 10 | deployment_template_schema_url = "http://json.schemastore.org/azure-iot-edge-deployment-template-4.0" 11 | deployment_manifest_schema_url = "http://json.schemastore.org/azure-iot-edge-deployment-2.0" 12 | azure_cli_iot_ext_source_url = "https://github.com/Azure/azure-iot-cli-extension/releases/download/v0.10.11/azure_iot-0.10.11-py3-none-any.whl" 13 | -------------------------------------------------------------------------------- /iotedgedev/containerregistry.py: -------------------------------------------------------------------------------- 1 | class ContainerRegistry: 2 | def __init__(self, server, username, password): 3 | self.server = server 4 | self.username = username 5 | self.password = password 6 | -------------------------------------------------------------------------------- /iotedgedev/decorators.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from functools import wraps 3 | 4 | import click 5 | 6 | PARAMS_WITH_VALUES = {'edge_runtime_version'} 7 | 8 | def with_telemetry(func): 9 | @wraps(func) 10 | def _wrapped_func(*args, **kwargs): 11 | from . import telemetry 12 | from .telemetryconfig import TelemetryConfig 13 | 14 | config = TelemetryConfig() 15 | config.check_firsttime() 16 | params = parse_params(*args, **kwargs) 17 | telemetry.start(func.__name__, params) 18 | try: 19 | value = func(*args, **kwargs) 20 | telemetry.success() 21 | telemetry.flush() 22 | return value 23 | except Exception as e: 24 | from .output import Output 25 | Output().error(str(e)) 26 | telemetry.fail(str(e), 'Command failed') 27 | telemetry.flush() 28 | sys.exit(1) 29 | 30 | return _wrapped_func 31 | 32 | 33 | def suppress_all_exceptions(fallback_return=None): 34 | """We need to suppress exceptions for some internal functions such as those related to telemetry. 35 | They should not be visible to users. 36 | """ 37 | def _decorator(func): 38 | @wraps(func) 39 | def _wrapped_func(*args, **kwargs): 40 | try: 41 | return func(*args, **kwargs) 42 | except Exception: 43 | if fallback_return: 44 | return fallback_return 45 | else: 46 | pass 47 | 48 | return _wrapped_func 49 | 50 | return _decorator 51 | 52 | 53 | @suppress_all_exceptions() 54 | def parse_params(*args, **kwargs): 55 | """Record the parameter keys and whether the values are None""" 56 | params = [] 57 | for key, value in kwargs.items(): 58 | if (value is None) or (key in PARAMS_WITH_VALUES): 59 | params.append('{0}={1}'.format(key, value)) 60 | else: 61 | params.append('{0}!=None'.format(key)) 62 | return params 63 | 64 | 65 | def hash256_result(func): 66 | """Secure the return string of the annotated function with SHA256 algorithm. If the annotated 67 | function doesn't return string or return None, raise ValueError.""" 68 | @wraps(func) 69 | def _wrapped_func(*args, **kwargs): 70 | val = func(*args, **kwargs) 71 | if not val: 72 | raise ValueError('Return value is None') 73 | elif not isinstance(val, str): 74 | raise ValueError('Return value is not string') 75 | 76 | from .utility import Utility 77 | return Utility.get_sha256_hash(val) 78 | 79 | return _wrapped_func 80 | 81 | 82 | def module_template_options(func): 83 | """Merge the module template option decorators into a single one.""" 84 | template_dec = click.option("--template", 85 | "-t", 86 | default="csharp", 87 | show_default=True, 88 | required=False, 89 | type=click.Choice(["c", "csharp", "java", "nodejs", "python", "csharpfunction"]), 90 | help="Specify the template used to create the default module") 91 | group_id_dec = click.option("--group-id", 92 | "-g", 93 | default="com.edgemodule", 94 | show_default=True, 95 | help="(Java modules only) Specify the groupId") 96 | return template_dec(group_id_dec(func)) 97 | 98 | 99 | def add_module_options(envvars, init=False): 100 | """Decorate commands which involve adding modules to the solution.""" 101 | """`init` specifies whether the command initializes a new solution.""" 102 | if init: 103 | module_name_dec = click.option("--module", 104 | "-m", 105 | required=False, 106 | default=envvars.get_envvar("DEFAULT_MODULE_NAME", default="filtermodule"), 107 | show_default=True, 108 | help="Specify the name of the default module") 109 | else: 110 | module_name_dec = click.argument("name", 111 | required=True) 112 | 113 | def _decorator(func): 114 | return module_name_dec(module_template_options(func)) 115 | 116 | return _decorator 117 | -------------------------------------------------------------------------------- /iotedgedev/dotnet.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides interfaces to interact with dotnet CLI 3 | """ 4 | 5 | 6 | class DotNet: 7 | def __init__(self, output, utility): 8 | self.output = output 9 | self.utility = utility 10 | # Fail fast if dotnet is not on path 11 | self.utility.check_dependency(["dotnet", "--version"], "To add new C# modules and C# Functions modules, the .NET Core SDK") 12 | 13 | def install_module_template(self): 14 | # Use C# module template version 3.2.0; this is the last version before the upgrdae to .NET 7 which no longer uses dockerfiles 15 | cmd = "dotnet new -i Microsoft.Azure.IoT.Edge.Module::3.2.0 --force" 16 | self.output.header(cmd) 17 | self.utility.exe_proc(cmd.split()) 18 | 19 | def install_function_template(self): 20 | cmd = "dotnet new -i Microsoft.Azure.IoT.Edge.Function" 21 | self.output.header(cmd) 22 | self.utility.exe_proc(cmd.split()) 23 | 24 | def create_custom_module(self, name, repo, cwd): 25 | cmd = "dotnet new aziotedgemodule -n {0} -r {1}".format(name, repo) 26 | self.output.header(cmd) 27 | self.utility.exe_proc(cmd.split(), cwd=cwd) 28 | 29 | def create_function_module(self, name, repo, cwd): 30 | cmd = "dotnet new aziotedgefunction -n {0} -r {1}".format(name, repo) 31 | self.output.header(cmd) 32 | self.utility.exe_proc(cmd.split(), cwd=cwd) 33 | -------------------------------------------------------------------------------- /iotedgedev/edge.py: -------------------------------------------------------------------------------- 1 | from iotedgedev.output import Output 2 | from iotedgedev.envvars import EnvVars 3 | from iotedgedev.azurecli import AzureCli 4 | 5 | 6 | class Edge: 7 | def __init__(self, envvars: EnvVars, output: Output, azure_cli: AzureCli): 8 | self.envvars = envvars 9 | self.output = output 10 | self.azure_cli = azure_cli 11 | 12 | def deploy(self, manifest_file: str): 13 | 14 | self.output.header("DEPLOYING CONFIGURATION") 15 | 16 | self.envvars.verify_envvar_has_val("IOTHUB_CONNECTION_STRING", self.envvars.IOTHUB_CONNECTION_INFO) 17 | self.envvars.verify_envvar_has_val("EDGE_DEVICE_ID", self.envvars.DEVICE_CONNECTION_INFO.device_id) 18 | self.envvars.verify_envvar_has_val("DEPLOYMENT_CONFIG_FILE", self.envvars.DEPLOYMENT_CONFIG_FILE) 19 | 20 | if self.azure_cli.set_modules(config=manifest_file, 21 | connection_string=self.envvars.IOTHUB_CONNECTION_INFO, 22 | device_id=self.envvars.DEVICE_CONNECTION_INFO.device_id): 23 | self.output.footer("DEPLOYMENT COMPLETE") 24 | 25 | def tag(self, tags): 26 | self.output.header("UPDATE DEVICE TAG") 27 | if not tags: 28 | tags = self.envvars.get_envvar("DEVICE_TAGS", True) 29 | self.envvars.verify_envvar_has_val("IOTHUB_CONNECTION_STRING", self.envvars.IOTHUB_CONNECTION_INFO) 30 | self.envvars.verify_envvar_has_val("DEVICE_CONNECTION_STRING", self.envvars.DEVICE_CONNECTION_INFO) 31 | 32 | if self.azure_cli.set_device_tag(connection_string=self.envvars.IOTHUB_CONNECTION_INFO, device_id=self.envvars.DEVICE_CONNECTION_INFO.device_id, tags=tags): 33 | self.output.footer("TAG UPDATE COMPLETE") 34 | -------------------------------------------------------------------------------- /iotedgedev/iothub.py: -------------------------------------------------------------------------------- 1 | from iotedgedev.envvars import EnvVars 2 | from iotedgedev.output import Output 3 | from iotedgedev.azurecli import AzureCli 4 | from .utility import Utility 5 | from .version import PY35 6 | 7 | 8 | class IoTHub: 9 | def __init__(self, envvars: EnvVars, output: Output, utility: Utility, azure_cli: AzureCli): 10 | self.envvars = envvars 11 | self.output = output 12 | self.utility = utility 13 | self.azure_cli = azure_cli 14 | 15 | def deploy(self, manifest_file: str, layered_deployment_name: str, priority: str, target_condition: str): 16 | 17 | self.output.header("DEPLOYING CONFIGURATION") 18 | 19 | self.envvars.verify_envvar_has_val("IOTHUB_CONNECTION_STRING", self.envvars.IOTHUB_CONNECTION_INFO) 20 | if not target_condition: 21 | target_condition = self.envvars.get_envvar("IOTHUB_DEPLOYMENT_TARGET_CONDITION", True) 22 | self.envvars.verify_envvar_has_val("DEPLOYMENT_CONFIG_FILE", self.envvars.DEPLOYMENT_CONFIG_FILE) 23 | 24 | if self.azure_cli.create_deployment(config=manifest_file, 25 | connection_string=self.envvars.IOTHUB_CONNECTION_INFO, 26 | deployment_name=layered_deployment_name, 27 | target_condition=target_condition, 28 | priority=priority): 29 | self.output.footer("DEPLOYMENT COMPLETE") 30 | 31 | def monitor_events(self, timeout=0): 32 | 33 | self.envvars.verify_envvar_has_val("IOTHUB_CONNECTION_STRING", self.envvars.IOTHUB_CONNECTION_STRING) 34 | self.envvars.verify_envvar_has_val("DEVICE_CONNECTION_STRING", self.envvars.DEVICE_CONNECTION_STRING) 35 | 36 | if timeout is None: 37 | timeout = 0 38 | 39 | self.output.header("MONITOR EVENTS") 40 | self.output.status("It may take 1-2 minutes before you start to see messages below.") 41 | 42 | if PY35: 43 | self.monitor_events_cli(timeout) 44 | else: 45 | self.monitor_events_node(timeout) 46 | 47 | def monitor_events_node(self, timeout=0): 48 | try: 49 | 50 | cmd = ['iothub-explorer', '--login', self.envvars.IOTHUB_CONNECTION_STRING, 'monitor-events', self.envvars.DEVICE_CONNECTION_INFO.device_id] 51 | 52 | if timeout != 0: 53 | cmd.extend(['--duration', timeout]) 54 | 55 | self.utility.call_proc(cmd, shell=not self.envvars.is_posix()) 56 | 57 | except Exception as ex: 58 | self.output.error("Problem while trying to call iothub-explorer. Please ensure that you have installed the iothub-explorer npm package with: npm i -g iothub-explorer.") 59 | self.output.error(str(ex)) 60 | 61 | def monitor_events_cli(self, timeout=0): 62 | self.azure_cli.monitor_events(self.envvars.DEVICE_CONNECTION_INFO.device_id, 63 | self.envvars.IOTHUB_CONNECTION_INFO.connection_string, 64 | self.envvars.IOTHUB_CONNECTION_INFO.iothub_host.hub_name, 65 | timeout) 66 | -------------------------------------------------------------------------------- /iotedgedev/module.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from .utility import Utility 5 | 6 | 7 | class Module(object): 8 | def __init__(self, envvars, utility, module_dir): 9 | self.utility = utility 10 | self.module_dir = module_dir 11 | self.module_json_file = os.path.join(self.module_dir, "module.json") 12 | self.file_json_content = None 13 | self.load_module_json() 14 | 15 | def load_module_json(self): 16 | if os.path.exists(self.module_json_file): 17 | try: 18 | self.file_json_content = json.loads(Utility.get_file_contents(self.module_json_file, expandvars=True)) 19 | except KeyError as e: 20 | raise KeyError("Error parsing {0} from module.json file : {1}".format(str(e), self.module_json_file)) 21 | except IOError: 22 | raise IOError("Error loading module.json file : {0}".format(self.module_json_file)) 23 | else: 24 | raise FileNotFoundError("No module.json file found. module.json file is required in the root of your module folder") 25 | 26 | @property 27 | def platforms(self): 28 | return self.file_json_content.get("image", {}).get("tag", {}).get("platforms", "") 29 | 30 | @property 31 | def tag_version(self): 32 | tag = self.file_json_content.get("image", {}).get("tag", {}).get("version", "0.0.0") 33 | 34 | return tag 35 | 36 | @property 37 | def repository(self): 38 | return self.file_json_content.get("image", {}).get("repository", "") 39 | 40 | @repository.setter 41 | def repository(self, repo): 42 | self.utility.nested_set(self.file_json_content, ["image", "repository"], repo) 43 | 44 | @property 45 | def build_options(self): 46 | return self.file_json_content.get("image", {}).get("buildOptions", []) 47 | 48 | @property 49 | def context_path(self): 50 | context_path = self.file_json_content.get("image", {}).get("contextPath", ".") 51 | return os.path.abspath(os.path.join(self.module_dir, context_path)) 52 | 53 | def get_dockerfile_by_platform(self, platform): 54 | platforms = self.file_json_content.get("image", {}).get("tag", {}).get("platforms", {}) 55 | if platform not in platforms: 56 | raise KeyError("Dockerfile for {0} is not defined in {1}", platform, self.module_json_file) 57 | 58 | return os.path.abspath(os.path.join(self.module_dir, platforms.get(platform))) 59 | 60 | def dump(self): 61 | with open(self.module_json_file, "w") as f: 62 | json.dump(self.file_json_content, f, indent=2) 63 | -------------------------------------------------------------------------------- /iotedgedev/organizedgroup.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | 4 | class OrganizedGroup(click.Group): 5 | """A subclass of click.Group which allows specifying an `order` parameter (0 by default) to sort the commands and groups""" 6 | 7 | def __init__(self, *args, **kwargs): 8 | self.orders = {} 9 | super(OrganizedGroup, self).__init__(*args, **kwargs) 10 | 11 | def get_help(self, ctx): 12 | self.list_commands = self.list_commands_for_help 13 | return super(OrganizedGroup, self).get_help(ctx) 14 | 15 | def list_commands_for_help(self, ctx): 16 | """reorder the list of commands when listing the help""" 17 | commands = super(OrganizedGroup, self).list_commands(ctx) 18 | return (c[1] for c in sorted( 19 | (self.orders.get(command, 0), command) 20 | for command in commands)) 21 | 22 | def command(self, *args, **kwargs): 23 | """Behaves the same as `click.Group.command()` except capture 24 | a priority for listing command names in help. 25 | """ 26 | order = kwargs.pop('order', 0) 27 | orders = self.orders 28 | 29 | def decorator(f): 30 | cmd = super(OrganizedGroup, self).command(*args, **kwargs)(f) 31 | orders[cmd.name] = order 32 | return cmd 33 | 34 | return decorator 35 | 36 | def group(self, *args, **kwargs): 37 | """Behaves the same as `click.Group.group()` except capture 38 | a priority for listing command names in help. 39 | """ 40 | order = kwargs.pop('order', 0) 41 | orders = self.orders 42 | 43 | def decorator(f): 44 | cmd = super(OrganizedGroup, self).group(*args, **kwargs)(f) 45 | orders[cmd.name] = order 46 | return cmd 47 | 48 | return decorator 49 | -------------------------------------------------------------------------------- /iotedgedev/output.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | 4 | class Output: 5 | 6 | def info(self, text, suppress=False): 7 | if not suppress: 8 | self.echo(text, color='yellow') 9 | 10 | def warning(self, text): 11 | self.echo("Warning: %s" % text, color='yellow') 12 | 13 | def status(self, text): 14 | self.info(text) 15 | self.line() 16 | 17 | def prompt(self, text): 18 | self.echo(text, color='white') 19 | 20 | def error(self, text): 21 | self.echo("ERROR: " + text, color='red', err=True) 22 | 23 | def header(self, text, suppress=False): 24 | 25 | if not suppress: 26 | self.line() 27 | s = "======== {0} ========".format(text).upper() 28 | m = "="*len(s) 29 | self.echo(m, color='white') 30 | self.echo(s, color='white') 31 | self.echo(m, color='white') 32 | self.line() 33 | 34 | def param(self, text, value, status, suppress): 35 | if value and not suppress: 36 | self.header("SETTING " + text) 37 | self.status(status) 38 | 39 | def footer(self, text, suppress=False): 40 | if not suppress: 41 | self.info(text.upper()) 42 | self.line() 43 | 44 | def procout(self, text, nl=True): 45 | self.echo(text, dim=True, nl=nl) 46 | 47 | def line(self): 48 | self.echo(text="") 49 | 50 | def echo(self, text, color="", dim=False, nl=True, err=False): 51 | try: 52 | click.secho(text, fg=color, dim=dim, nl=nl, err=err) 53 | except Exception: 54 | print(text) 55 | 56 | def confirm(self, text, default=False, abort=True): 57 | return click.confirm(text, default=default, abort=abort) 58 | 59 | def prompt_question(self, text, default=""): 60 | self.line() 61 | return click.prompt(text, default=default) 62 | -------------------------------------------------------------------------------- /iotedgedev/simulator.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .modules import Modules 4 | from .utility import Utility 5 | 6 | class Simulator: 7 | def __init__(self, envvars, output): 8 | self.envvars = envvars 9 | self.output = output 10 | self.utility = Utility(self.envvars, self.output) 11 | 12 | def setup(self, gateway_host, iothub_connection_string=""): 13 | self.output.header("Setting Up IoT Edge Simulator") 14 | self.envvars.verify_envvar_has_val("DEVICE_CONNECTION_STRING", self.envvars.DEVICE_CONNECTION_STRING) 15 | 16 | cmd = ["iotedgehubdev", "setup", "-c", self.envvars.DEVICE_CONNECTION_STRING] 17 | if gateway_host: 18 | cmd.extend(["-g", gateway_host]) 19 | if iothub_connection_string: 20 | cmd.extend(["-i", iothub_connection_string]) 21 | self.utility.exe_proc(cmd) 22 | 23 | def start_single(self, inputs, port): 24 | self.output.header("Starting IoT Edge Simulator in Single Mode") 25 | 26 | cmd = ["iotedgehubdev", "start", "-i", inputs] 27 | if port: 28 | cmd.extend(["-p", str(port)]) 29 | self.utility.exe_proc(cmd) 30 | 31 | def start_solution(self, manifest_file, default_platform, verbose=True, build=False): 32 | if build: 33 | mod = Modules(self.envvars, self.output) 34 | manifest_file = mod.build(manifest_file, default_platform) 35 | 36 | if not os.path.exists(manifest_file): 37 | raise FileNotFoundError("Deployment manifest {0} not found. Please build the solution before starting IoT Edge simulator.".format(self.envvars.DEPLOYMENT_CONFIG_FILE_PATH)) 38 | 39 | self.output.header("Starting IoT Edge Simulator in Solution Mode") 40 | 41 | cmd = ["iotedgehubdev", "start", "-d", manifest_file] 42 | if verbose: 43 | cmd.append("-v") 44 | self.utility.call_proc(cmd) 45 | 46 | def stop(self): 47 | self.output.header("Stopping IoT Edge Simulator") 48 | self.utility.call_proc(["iotedgehubdev", "stop"]) 49 | 50 | def modulecred(self, local, output_file): 51 | self.output.header("Getting Target Module Credentials") 52 | 53 | cmd = ["iotedgehubdev", "modulecred"] 54 | if local: 55 | cmd.append("-l") 56 | if output_file: 57 | cmd.extend(["-o", output_file]) 58 | self.utility.exe_proc(cmd) 59 | -------------------------------------------------------------------------------- /iotedgedev/solution.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .constants import Constants 4 | 5 | 6 | class Solution: 7 | def __init__(self, output, utility): 8 | self.output = output 9 | self.utility = utility 10 | 11 | def create(self, name, module, template, runtime_tag, group_id): 12 | if name == ".": 13 | dir_path = os.getcwd() 14 | else: 15 | dir_path = os.path.join(os.getcwd(), name) 16 | 17 | if not self.utility.is_dir_empty(dir_path): 18 | raise ValueError("Directory is not empty. Run `iotedgedev iothub setup` to retrieve or create required Azure resources or clean the directory.") 19 | 20 | self.output.header("CREATING AZURE IOT EDGE SOLUTION: {0}".format(name)) 21 | 22 | self.utility.ensure_dir(dir_path) 23 | 24 | self.utility.copy_from_template_dir(Constants.default_deployment_template_file, dir_path, replacements={"%MODULE%": module}) 25 | self.utility.copy_from_template_dir(Constants.default_deployment_template_file, dir_path, 26 | dest_file=Constants.default_deployment_debug_template_file, replacements={"%MODULE%": module}) 27 | self.utility.copy_from_template_dir(".gitignore", dir_path) 28 | edgeagent_schema_version = runtime_tag 29 | edgehub_schema_version = runtime_tag 30 | # exception for runtime version 1.2 31 | if(runtime_tag == "1.2"): 32 | edgeagent_schema_version = "1.1" 33 | edgehub_schema_version = "1.2" 34 | 35 | self.utility.copy_from_template_dir(".env.tmp", dir_path, dest_file=".env", replacements={ 36 | "%EDGE_RUNTIME_VERSION%": runtime_tag, "%EDGEAGENT_SCHEMA_VERSION%": edgeagent_schema_version, "%EDGEHUB_SCHEMA_VERSION%": edgehub_schema_version}) 37 | 38 | if template == "java": 39 | mod_cmd = "iotedgedev solution add {0} --template {1} --group-id {2}".format(module, template, group_id) 40 | else: 41 | mod_cmd = "iotedgedev solution add {0} --template {1}".format(module, template) 42 | 43 | self.output.header(mod_cmd) 44 | self.utility.call_proc(mod_cmd.split(), cwd=name) 45 | 46 | self.output.footer("Azure IoT Edge Solution Created") 47 | if name != ".": 48 | self.output.info("Execute 'cd {0}' to navigate to your new solution.".format(name)) 49 | -------------------------------------------------------------------------------- /iotedgedev/telemetry.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import os 4 | import platform 5 | import subprocess 6 | import sys 7 | import uuid 8 | from collections import defaultdict 9 | from functools import wraps 10 | 11 | from . import telemetryuploader 12 | from .telemetryconfig import TelemetryConfig 13 | from .decorators import hash256_result, suppress_all_exceptions 14 | 15 | PRODUCT_NAME = 'iotedgedev' 16 | 17 | 18 | class TelemetrySession(object): 19 | def __init__(self, correlation_id=None): 20 | self.start_time = None 21 | self.end_time = None 22 | self.correlation_id = correlation_id or str(uuid.uuid4()) 23 | self.command = 'command_name' 24 | self.parameters = [] 25 | self.result = 'None' 26 | self.result_summary = None 27 | self.exception = None 28 | self.extra_props = {} 29 | self.machineId = self._get_hash_mac_address() 30 | self.events = defaultdict(list) 31 | 32 | def generate_payload(self): 33 | props = { 34 | 'EventId': str(uuid.uuid4()), 35 | 'CorrelationId': self.correlation_id, 36 | 'MachineId': self.machineId, 37 | 'ProductName': PRODUCT_NAME, 38 | 'ProductVersion': _get_core_version(), 39 | 'CommandName': self.command, 40 | 'OS.Type': platform.system().lower(), 41 | 'OS.Version': platform.version().lower(), 42 | 'Result': self.result, 43 | 'StartTime': str(self.start_time), 44 | 'EndTime': str(self.end_time), 45 | 'Parameters': ','.join(self.parameters) 46 | } 47 | 48 | if self.result_summary: 49 | props['ResultSummary'] = self.result_summary 50 | 51 | if self.exception: 52 | props['Exception'] = self.exception 53 | 54 | props.update(self.extra_props) 55 | 56 | self.events[_get_AI_key()].append({ 57 | 'name': '{}/command'.format(PRODUCT_NAME), 58 | 'properties': props 59 | }) 60 | 61 | payload = json.dumps(self.events) 62 | return _remove_symbols(payload) 63 | 64 | @suppress_all_exceptions() 65 | @hash256_result 66 | def _get_hash_mac_address(self): 67 | s = '' 68 | for index, c in enumerate(hex(uuid.getnode())[2:].upper()): 69 | s += c 70 | if index % 2: 71 | s += '-' 72 | 73 | s = s.strip('-') 74 | return s 75 | 76 | 77 | _session = TelemetrySession() 78 | 79 | 80 | def _user_agrees_to_telemetry(func): 81 | @wraps(func) 82 | def _wrapper(*args, **kwargs): 83 | config = TelemetryConfig() 84 | if not config.get_boolean(config.DEFAULT_DIRECT, config.TELEMETRY_SECTION): 85 | return None 86 | return func(*args, **kwargs) 87 | 88 | return _wrapper 89 | 90 | 91 | @suppress_all_exceptions() 92 | def start(cmdname, params=[]): 93 | _session.command = cmdname 94 | _session.start_time = datetime.datetime.utcnow() 95 | if params is not None: 96 | _session.parameters.extend(params) 97 | 98 | 99 | @suppress_all_exceptions() 100 | def success(): 101 | _session.result = 'Success' 102 | 103 | 104 | @suppress_all_exceptions() 105 | def fail(exception, summary): 106 | _session.exception = exception 107 | _session.result = 'Fail' 108 | _session.result_summary = summary 109 | 110 | 111 | @suppress_all_exceptions() 112 | def add_extra_props(props): 113 | if props is not None: 114 | _session.extra_props.update(props) 115 | 116 | 117 | @_user_agrees_to_telemetry 118 | @suppress_all_exceptions() 119 | def flush(): 120 | # flush out current information 121 | _session.end_time = datetime.datetime.utcnow() 122 | 123 | payload = _session.generate_payload() 124 | if payload: 125 | _upload_telemetry_with_user_agreement(payload) 126 | 127 | # reset session fields, retaining correlation id and application 128 | _session.__init__(correlation_id=_session.correlation_id) 129 | 130 | 131 | @suppress_all_exceptions(fallback_return=None) 132 | def _get_core_version(): 133 | from iotedgedev import __version__ as core_version 134 | return core_version 135 | 136 | 137 | @suppress_all_exceptions() 138 | def _get_AI_key(): 139 | from iotedgedev import __AIkey__ as key 140 | return key 141 | 142 | 143 | # This includes a final user-agreement-check; ALL methods sending telemetry MUST call this. 144 | @_user_agrees_to_telemetry 145 | @suppress_all_exceptions() 146 | def _upload_telemetry_with_user_agreement(payload, **kwargs): 147 | # Call telemetry uploader as a subprocess to prevent blocking iotedgedev process 148 | subprocess.Popen([sys.executable, os.path.realpath(telemetryuploader.__file__), payload], **kwargs) 149 | 150 | 151 | def _remove_symbols(s): 152 | if isinstance(s, str): 153 | for c in '$%^&|': 154 | s = s.replace(c, '_') 155 | return s 156 | -------------------------------------------------------------------------------- /iotedgedev/telemetryconfig.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import configparser 4 | 5 | from .decorators import suppress_all_exceptions 6 | 7 | PRIVACY_STATEMENT = """ 8 | Welcome to iotedgedev! 9 | ------------------------- 10 | Telemetry 11 | --------- 12 | The iotedgedev collects usage data in order to improve your experience. 13 | The data is anonymous and does not include commandline argument values. 14 | The data is collected by Microsoft. 15 | 16 | You can change your telemetry settings by updating '{0}' to 'no' in {1} 17 | """ 18 | 19 | 20 | class TelemetryConfig(object): 21 | DEFAULT_DIRECT = 'DEFAULT' 22 | FIRSTTIME_SECTION = 'firsttime' 23 | TELEMETRY_SECTION = 'collect_telemetry' 24 | 25 | def __init__(self): 26 | self.config_parser = configparser.ConfigParser({ 27 | self.FIRSTTIME_SECTION: 'yes' 28 | }) 29 | self.setup() 30 | 31 | @suppress_all_exceptions() 32 | def setup(self): 33 | config_path = self.get_config_path() 34 | config_folder = os.path.dirname(config_path) 35 | if not os.path.exists(config_folder): 36 | os.makedirs(config_folder) 37 | if not os.path.exists(config_path): 38 | self.dump() 39 | else: 40 | self.load() 41 | self.dump() 42 | 43 | @suppress_all_exceptions() 44 | def load(self): 45 | with open(self.get_config_path(), 'r') as f: 46 | if hasattr(self.config_parser, 'read_file'): 47 | self.config_parser.read_file(f) 48 | 49 | @suppress_all_exceptions() 50 | def dump(self): 51 | with open(self.get_config_path(), 'w') as f: 52 | self.config_parser.write(f) 53 | 54 | @suppress_all_exceptions() 55 | def get(self, direct, section): 56 | return self.config_parser.get(direct, section) 57 | 58 | @suppress_all_exceptions() 59 | def get_boolean(self, direct, section): 60 | return self.config_parser.getboolean(direct, section) 61 | 62 | @suppress_all_exceptions() 63 | def set(self, direct, section, val): 64 | if val is not None: 65 | self.config_parser.set(direct, section, val) 66 | self.dump() 67 | 68 | @suppress_all_exceptions() 69 | def check_firsttime(self): 70 | if self.get(self.DEFAULT_DIRECT, self.FIRSTTIME_SECTION) != 'no': 71 | self.set(self.DEFAULT_DIRECT, self.FIRSTTIME_SECTION, 'no') 72 | print(PRIVACY_STATEMENT.format(self.TELEMETRY_SECTION, self.get_config_path())) 73 | self.set(self.DEFAULT_DIRECT, self.TELEMETRY_SECTION, 'yes') 74 | self.dump() 75 | 76 | @suppress_all_exceptions() 77 | def get_config_path(self): 78 | config_folder = self.get_config_folder() 79 | if config_folder: 80 | return os.path.join(config_folder, 'setting.ini') 81 | return None 82 | 83 | @suppress_all_exceptions() 84 | def get_config_folder(self): 85 | return os.path.join(os.path.expanduser("~"), '.iotedgedev') 86 | -------------------------------------------------------------------------------- /iotedgedev/telemetryuploader.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | 4 | from applicationinsights import TelemetryClient 5 | from applicationinsights.channel import (SynchronousQueue, SynchronousSender, 6 | TelemetryChannel) 7 | from applicationinsights.exceptions import enable 8 | from urllib.request import Request, urlopen 9 | 10 | from iotedgedev.decorators import suppress_all_exceptions 11 | 12 | 13 | class LimitedRetrySender(SynchronousSender): 14 | def __init__(self): 15 | super(LimitedRetrySender, self).__init__() 16 | 17 | def send(self, data_to_send): 18 | """ Override the default resend mechanism in SenderBase. Stop resend when it fails.""" 19 | request_payload = json.dumps([a.write() for a in data_to_send]) 20 | 21 | req = Request(self._service_endpoint_uri, bytearray(request_payload, 'utf-8'), 22 | {'Accept': 'application/json', 'Content-Type': 'application/json; charset=utf-8'}) 23 | try: 24 | urlopen(req, timeout=10) 25 | except Exception: 26 | pass 27 | 28 | 29 | @suppress_all_exceptions() 30 | def upload(data_to_save): 31 | data_to_save = json.loads(data_to_save) 32 | 33 | for instrumentation_key in data_to_save: 34 | client = TelemetryClient(instrumentation_key=instrumentation_key, 35 | telemetry_channel=TelemetryChannel(queue=SynchronousQueue(LimitedRetrySender()))) 36 | enable(instrumentation_key) 37 | for record in data_to_save[instrumentation_key]: 38 | name = record['name'] 39 | raw_properties = record['properties'] 40 | properties = {} 41 | measurements = {} 42 | for k, v in raw_properties.items(): 43 | if isinstance(v, str): 44 | properties[k] = v 45 | else: 46 | measurements[k] = v 47 | client.track_event(name, properties, measurements) 48 | client.flush() 49 | 50 | 51 | if __name__ == '__main__': 52 | # If user doesn't agree to upload telemetry, this scripts won't be executed. The caller should control. 53 | upload(sys.argv[1]) 54 | -------------------------------------------------------------------------------- /iotedgedev/template/.env.tmp: -------------------------------------------------------------------------------- 1 | # 2 | # CONNECTION STRINGS 3 | # 4 | 5 | IOTHUB_CONNECTION_STRING="" 6 | 7 | DEVICE_CONNECTION_STRING="" 8 | 9 | # 10 | # CONTAINER REGISTRY 11 | # 12 | # Settings for your default container registry. 13 | # - Local Registry: Set CONTAINER_REGISTRY_SERVER to "localhost:5000" - USERNAME/PASSWORD are not required. 14 | # - Azure Container Registry: Set CONTAINER_REGISTRY_SERVER to "myregistry.azurecr.io". USERNAME/PASSWORD are required. 15 | # - Docker Hub: Set CONTAINER_REGISTRY_SERVER and CONTAINER_REGISTRY_USERNAME to your Docker Hub username. Set CONTAINER_REGISTRY_PASSWORD to your Docker Hub password. 16 | 17 | CONTAINER_REGISTRY_SERVER="localhost:5000" 18 | CONTAINER_REGISTRY_USERNAME="" 19 | CONTAINER_REGISTRY_PASSWORD="" 20 | 21 | # To specify additional container registries ensure the prefix is CONTAINER_REGISTRY_SERVER_, CONTAINER_REGISTRY_USERNAME_, CONTAINER_REGISTRY_PASSWORD_ 22 | # And the token following the prefix uniquely associates the SERVER/USERNAME/PASSWORD 23 | # Token can be any string of alphanumeric characters 24 | 25 | # CONTAINER_REGISTRY_SERVER_2="" 26 | # CONTAINER_REGISTRY_USERNAME_2="" 27 | # CONTAINER_REGISTRY_PASSWORD_2="" 28 | 29 | # 30 | # HOST 31 | # 32 | 33 | EDGE_RUNTIME_VERSION="%EDGE_RUNTIME_VERSION%" 34 | EDGEAGENT_SCHEMA_VERSION="%EDGEAGENT_SCHEMA_VERSION%" 35 | EDGEHUB_SCHEMA_VERSION="%EDGEHUB_SCHEMA_VERSION%" 36 | 37 | # 38 | # MODULES 39 | # 40 | 41 | BYPASS_MODULES="" 42 | # "" - to build all modules 43 | # "*" - to bypass all modules 44 | # "filtermodule, module1" - Comma delimited list of modules to bypass when building 45 | 46 | CONTAINER_TAG="" 47 | 48 | # 49 | # SOLUTION SETTINGS 50 | # 51 | 52 | CONFIG_OUTPUT_DIR="config" 53 | DEPLOYMENT_CONFIG_TEMPLATE_FILE="deployment.template.json" 54 | DEPLOYMENT_CONFIG_DEBUG_TEMPLATE_FILE="deployment.debug.template.json" 55 | DEFAULT_PLATFORM="amd64" 56 | MODULES_PATH="modules" 57 | 58 | LOGS_PATH="logs" 59 | 60 | # 61 | # DOCKER LOGS COMMAND 62 | # 63 | # Command used when calling iotedgedev docker --logs or --show-logs 64 | 65 | LOGS_CMD="start /B start cmd.exe @cmd /k docker logs {0} -f" 66 | # "start /B start cmd.exe @cmd /k docker logs {0} -f" - for CMD 67 | # "docker logs {0} -f -new_console:sV" - for ConEmu 68 | 69 | # 70 | # AZURE SETTINGS 71 | # 72 | # These settings will override parameters to the `iotedgedev azure --setup` command. 73 | # CREDENTIALS="username password" 74 | # SERVICE_PRINCIPAL="username password tenant" 75 | # RESOURCE_GROUP_LOCATION="australiaeast|australiasoutheast|brazilsouth|canadacentral|canadaeast|centralindia|centralus|eastasia|eastus|eastus2|japanwest|japaneast|northeurope|northcentralus|southindia|uksouth|ukwest|westus|westeurope|southcentralus|westcentralus|westus2" 76 | # IOTHUB_SKU="F1|S1|S2|S3" 77 | # UPDATE_DOTENV="True|False" 78 | 79 | SUBSCRIPTION_ID="" 80 | RESOURCE_GROUP_NAME="" 81 | RESOURCE_GROUP_LOCATION="" 82 | IOTHUB_NAME="" 83 | IOTHUB_SKU="" 84 | EDGE_DEVICE_ID="" 85 | CREDENTIALS="" 86 | SERVICE_PRINCIPAL="" 87 | UPDATE_DOTENV="" 88 | -------------------------------------------------------------------------------- /iotedgedev/template/.gitignore: -------------------------------------------------------------------------------- 1 | **/.vs 2 | **/bin 3 | **/obj 4 | **/out 5 | build 6 | .env 7 | venv 8 | logs 9 | .config 10 | config -------------------------------------------------------------------------------- /iotedgedev/template/deployment.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema-template": "4.0.0", 3 | "modulesContent": { 4 | "$edgeAgent": { 5 | "properties.desired": { 6 | "schemaVersion": "${EDGEAGENT_SCHEMA_VERSION}", 7 | "runtime": { 8 | "type": "docker", 9 | "settings": { 10 | "minDockerVersion": "v1.25", 11 | "loggingOptions": "", 12 | "registryCredentials": {} 13 | } 14 | }, 15 | "systemModules": { 16 | "edgeAgent": { 17 | "type": "docker", 18 | "settings": { 19 | "image": "mcr.microsoft.com/azureiotedge-agent:${EDGE_RUNTIME_VERSION}", 20 | "createOptions": {} 21 | } 22 | }, 23 | "edgeHub": { 24 | "type": "docker", 25 | "status": "running", 26 | "restartPolicy": "always", 27 | "settings": { 28 | "image": "mcr.microsoft.com/azureiotedge-hub:${EDGE_RUNTIME_VERSION}", 29 | "createOptions": { 30 | "HostConfig": { 31 | "PortBindings": { 32 | "5671/tcp": [ 33 | { 34 | "HostPort": "5671" 35 | } 36 | ], 37 | "8883/tcp": [ 38 | { 39 | "HostPort": "8883" 40 | } 41 | ], 42 | "443/tcp": [ 43 | { 44 | "HostPort": "443" 45 | } 46 | ] 47 | } 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | "modules": { 54 | "tempSensor": { 55 | "version": "1.0", 56 | "type": "docker", 57 | "status": "running", 58 | "restartPolicy": "always", 59 | "settings": { 60 | "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", 61 | "createOptions": {} 62 | } 63 | } 64 | } 65 | } 66 | }, 67 | "$edgeHub": { 68 | "properties.desired": { 69 | "schemaVersion": "${EDGEHUB_SCHEMA_VERSION}", 70 | "routes": { 71 | "sensorTo%MODULE%": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/%MODULE%/inputs/input1\")" 72 | }, 73 | "storeAndForwardConfiguration": { 74 | "timeToLiveSecs": 7200 75 | } 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /iotedgedev/template/launch_c.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "%MODULE% Remote Debug (C)", 6 | "type": "cppdbg", 7 | "request": "attach", 8 | "program": "./main", 9 | "processId": "${command:pickRemoteProcess}", 10 | "pipeTransport": { 11 | "pipeCwd": "${workspaceFolder}", 12 | "pipeProgram": "docker", 13 | "pipeArgs": [ 14 | "exec", 15 | "-i", 16 | "%MODULE%", 17 | "sh", 18 | "-c" 19 | ], 20 | "debuggerPath": "/usr/bin/gdb" 21 | }, 22 | "sourceFileMap": { 23 | "%APP_FOLDER%": "${workspaceFolder}/modules/%MODULE_FOLDER%" 24 | }, 25 | "linux": { 26 | "MIMode": "gdb", 27 | "setupCommands": [ 28 | { 29 | "description": "Enable pretty-printing for gdb", 30 | "text": "-enable-pretty-printing", 31 | "ignoreFailures": true 32 | } 33 | ] 34 | }, 35 | "osx": { 36 | "MIMode": "lldb" 37 | }, 38 | "windows": { 39 | "MIMode": "gdb", 40 | "setupCommands": [ 41 | { 42 | "description": "Enable pretty-printing for gdb", 43 | "text": "-enable-pretty-printing", 44 | "ignoreFailures": true 45 | } 46 | ] 47 | } 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /iotedgedev/template/launch_csharp.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "%MODULE% Remote Debug (.NET Core)", 6 | "type": "coreclr", 7 | "request": "attach", 8 | "processId": "${command:pickRemoteProcess}", 9 | "pipeTransport": { 10 | "pipeProgram": "docker", 11 | "pipeArgs": [ 12 | "exec", 13 | "-i", 14 | "%MODULE%", 15 | "sh", 16 | "-c" 17 | ], 18 | "debuggerPath": "~/vsdbg/vsdbg", 19 | "pipeCwd": "${workspaceFolder}", 20 | "quoteArgs": true 21 | }, 22 | "sourceFileMap": { 23 | "%APP_FOLDER%": "${workspaceFolder}/modules/%MODULE_FOLDER%" 24 | }, 25 | "justMyCode": true 26 | }, 27 | { 28 | "name": "%MODULE% Local Debug (.NET Core)", 29 | "type": "coreclr", 30 | "request": "launch", 31 | "program": "${workspaceRoot}/modules/%MODULE_FOLDER%/bin/Debug/netcoreapp2.1/%MODULE%.dll", 32 | "args": [], 33 | "cwd": "${workspaceRoot}/modules/%MODULE_FOLDER%", 34 | "internalConsoleOptions": "openOnSessionStart", 35 | "stopAtEntry": false, 36 | "console": "internalConsole", 37 | "env": { 38 | "EdgeHubConnectionString": "${config:azure-iot-edge.EdgeHubConnectionString}", 39 | "EdgeModuleCACertificateFile": "${config:azure-iot-edge.EdgeModuleCACertificateFile}" 40 | } 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /iotedgedev/template/launch_java.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "java", 6 | "name": "%MODULE% Local Debug (java)", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}/modules/%MODULE_FOLDER%", 9 | "console": "internalConsole", 10 | "stopOnEntry": false, 11 | "mainClass": "%GROUP_ID%.App", 12 | "args": "", 13 | "projectName": "%MODULE%", 14 | "env": { 15 | "EdgeHubConnectionString": "${config:azure-iot-edge.EdgeHubConnectionString}", 16 | "EdgeModuleCACertificateFile": "${config:azure-iot-edge.EdgeModuleCACertificateFile}" 17 | } 18 | }, 19 | { 20 | "type": "java", 21 | "name": "%MODULE% Remote Debug (java)", 22 | "request": "attach", 23 | "hostName": "localhost", 24 | "port": 5005 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /iotedgedev/template/launch_node.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "%MODULE% Remote Debug (Node.js)", 6 | "type": "node", 7 | "request": "attach", 8 | "port": 9229, 9 | "address": "localhost", 10 | "localRoot": "${workspaceRoot}/modules/%MODULE_FOLDER%", 11 | "remoteRoot": "/app", 12 | "protocol": "inspector" 13 | }, 14 | { 15 | "name": "%MODULE% Remote Debug (Node.js in Windows Container)", 16 | "type": "node", 17 | "request": "attach", 18 | "port": 9229, 19 | "address": "localhost", 20 | "localRoot": "${workspaceRoot}/modules/%MODULE_FOLDER%", 21 | "remoteRoot": "C:\\app", 22 | "protocol": "inspector" 23 | }, 24 | { 25 | "name": "%MODULE% Local Debug (Node.js)", 26 | "type": "node", 27 | "request": "launch", 28 | "program": "${workspaceRoot}/modules/%MODULE_FOLDER%/app.js", 29 | "console": "integratedTerminal", 30 | "env": { 31 | "EdgeHubConnectionString": "${config:azure-iot-edge.EdgeHubConnectionString}", 32 | "EdgeModuleCACertificateFile": "${config:azure-iot-edge.EdgeModuleCACertificateFile}" 33 | } 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /iotedgedev/template/launch_python.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [{ 4 | "name": "%MODULE% Remote Debug (Python)", 5 | "type": "python", 6 | "request": "attach", 7 | "port": 5678, 8 | "host": "localhost", 9 | "logToFile": true, 10 | "redirectOutput": true, 11 | "pathMappings": [{ 12 | "localRoot": "${workspaceFolder}/modules/%MODULE_FOLDER%", 13 | "remoteRoot": "%APP_FOLDER%" 14 | }], 15 | "windows": { 16 | "pathMappings": [{ 17 | "localRoot": "${workspaceFolder}\\modules\\%MODULE_FOLDER%", 18 | "remoteRoot": "%APP_FOLDER%" 19 | }] 20 | } 21 | }] 22 | } 23 | -------------------------------------------------------------------------------- /iotedgedev/version.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | PY35 = sys.version_info >= (3, 5) 4 | PY3 = sys.version_info >= (3, 0) 5 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | e2e 4 | unit -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==8.0.1 2 | docker==5.0.3 3 | python-dotenv==0.19.0 4 | requests==2.25.1 5 | fstrings==0.1.0 6 | msrestazure==0.6.4 7 | iotedgehubdev==0.14.18 8 | applicationinsights==0.11.9 9 | commentjson==0.9.0 10 | azure-cli-core==2.34.1 11 | pypiwin32==219; sys_platform == 'win32' and python_version < '3.6' 12 | pypiwin32==223; sys_platform == 'win32' and python_version >= '3.6' 13 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | bumpversion==0.5.3 3 | wheel==0.30.0 4 | watchdog==0.8.3 5 | docker-compose==1.29.1 6 | flake8 7 | pycodestyle==2.7.0 8 | autopep8==1.5.7 9 | tox==3.24.1 10 | coverage==4.1 11 | Sphinx==4.4.0 12 | cookiecutter==1.7.3 13 | PyYAML>=5.4 14 | pylint==2.3.0 15 | pytest==6.2.4 16 | setuptools==57.0.0 17 | twine==3.4.2 18 | -------------------------------------------------------------------------------- /scripts/gen-help-markdown.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | ECHO ## Commands 3 | 4 | ECHO **iotedgedev** 5 | ECHO ``` 6 | iotedgedev -h 7 | ECHO ``` 8 | 9 | ECHO **iotedgedev add** 10 | ECHO ``` 11 | iotedgedev add -h 12 | ECHO ``` 13 | 14 | ECHO **iotedgedev build** 15 | ECHO ``` 16 | iotedgedev build -h 17 | ECHO ``` 18 | 19 | ECHO **iotedgedev deploy** 20 | ECHO ``` 21 | iotedgedev deploy -h 22 | ECHO ``` 23 | 24 | ECHO **iotedgedev genconfig** 25 | ECHO ``` 26 | iotedgedev genconfig -h 27 | ECHO ``` 28 | 29 | ECHO **iotedgedev init** 30 | ECHO ``` 31 | iotedgedev init -h 32 | ECHO ``` 33 | 34 | ECHO **iotedgedev log** 35 | ECHO ``` 36 | iotedgedev log -h 37 | ECHO ``` 38 | 39 | ECHO **iotedgedev monitor** 40 | ECHO ``` 41 | iotedgedev monitor -h 42 | ECHO ``` 43 | 44 | ECHO **iotedgedev new** 45 | ECHO ``` 46 | iotedgedev new -h 47 | ECHO ``` 48 | 49 | ECHO **iotedgedev push** 50 | ECHO ``` 51 | iotedgedev push -h 52 | ECHO ``` 53 | 54 | ECHO **iotedgedev setup** 55 | ECHO ``` 56 | iotedgedev setup -h 57 | ECHO ``` 58 | 59 | ECHO **iotedgedev start** 60 | ECHO ``` 61 | iotedgedev start -h 62 | ECHO ``` 63 | 64 | ECHO **iotedgedev stop** 65 | ECHO ``` 66 | iotedgedev stop -h 67 | ECHO ``` 68 | 69 | ECHO **iotedgedev docker** 70 | ECHO ``` 71 | iotedgedev docker -h 72 | ECHO ``` 73 | 74 | ECHO **iotedgedev docker clean** 75 | ECHO ``` 76 | iotedgedev docker clean -h 77 | ECHO ``` 78 | 79 | ECHO **iotedgedev docker log** 80 | ECHO ``` 81 | iotedgedev docker log -h 82 | ECHO ``` 83 | 84 | ECHO **iotedgedev docker setup** 85 | ECHO ``` 86 | iotedgedev docker setup -h 87 | ECHO ``` 88 | 89 | ECHO **iotedgedev iothub** 90 | ECHO ``` 91 | iotedgedev iothub -h 92 | ECHO ``` 93 | 94 | ECHO **iotedgedev iothub monitor** 95 | ECHO ``` 96 | iotedgedev iothub monitor -h 97 | ECHO ``` 98 | 99 | ECHO **iotedgedev iothub setup** 100 | ECHO ``` 101 | iotedgedev iothub setup -h 102 | ECHO ``` 103 | 104 | ECHO **iotedgedev simulator** 105 | ECHO ``` 106 | iotedgedev simulator -h 107 | ECHO ``` 108 | 109 | ECHO **iotedgedev simulator modulecred** 110 | ECHO ``` 111 | iotedgedev simulator modulecred -h 112 | ECHO ``` 113 | 114 | ECHO **iotedgedev simulator setup** 115 | ECHO ``` 116 | iotedgedev simulator setup -h 117 | ECHO ``` 118 | 119 | ECHO **iotedgedev simulator start** 120 | ECHO ``` 121 | iotedgedev simulator start -h 122 | ECHO ``` 123 | 124 | ECHO **iotedgedev simulator stop** 125 | ECHO ``` 126 | iotedgedev simulator stop -h 127 | ECHO ``` 128 | 129 | ECHO **iotedgedev solution** 130 | ECHO ``` 131 | iotedgedev solution -h 132 | ECHO ``` 133 | 134 | ECHO **iotedgedev solution add** 135 | ECHO ``` 136 | iotedgedev solution add -h 137 | ECHO ``` 138 | 139 | ECHO **iotedgedev solution build** 140 | ECHO ``` 141 | iotedgedev solution build -h 142 | ECHO ``` 143 | 144 | ECHO **iotedgedev solution build** 145 | ECHO ``` 146 | iotedgedev solution build -h 147 | ECHO ``` 148 | 149 | ECHO **iotedgedev solution deploy** 150 | ECHO ``` 151 | iotedgedev solution deploy -h 152 | ECHO ``` 153 | 154 | ECHO **iotedgedev solution e2e** 155 | ECHO ``` 156 | iotedgedev solution e2e -h 157 | ECHO ``` 158 | 159 | ECHO **iotedgedev solution genconfig** 160 | ECHO ``` 161 | iotedgedev solution genconfig -h 162 | ECHO ``` 163 | 164 | ECHO **iotedgedev solution init** 165 | ECHO ``` 166 | iotedgedev solution init -h 167 | ECHO ``` 168 | 169 | ECHO **iotedgedev solution new** 170 | ECHO ``` 171 | iotedgedev solution new -h 172 | ECHO ``` 173 | 174 | ECHO **iotedgedev solution push** 175 | ECHO ``` 176 | iotedgedev solution push -h 177 | ECHO ``` 178 | -------------------------------------------------------------------------------- /scripts/install-pip.sh: -------------------------------------------------------------------------------- 1 | curl -O https://bootstrap.pypa.io/get-pip.py && python get-pip.py 2 | -------------------------------------------------------------------------------- /scripts/setup-wsl.sh: -------------------------------------------------------------------------------- 1 | echo "PATH=\"$PATH:$HOME/bin:$HOME/.local/bin:/mnt/c/Program\ Files/Docker/Docker/resources/bin\"" >> ~/.bashrc 2 | echo "alias docker=docker.exe" >> ~/.bashrc 3 | echo "alias docker-machine=docker-machine.exe" >> ~/.bashrc 4 | echo "alias docker-compose=docker-compose.exe" >> ~/.bashrc 5 | echo "export DOCKER_HOST='${DOCKER_HOST}'" >> ~/.bashrc 6 | source ~/.bashrc 7 | sudo sh -c "echo Defaults env_keep += \"DOCKER_HOST\" >> /etc/sudoers.d/docker" -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 3.3.8 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:iotedgedev/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [flake8] 15 | exclude = docs 16 | 17 | [aliases] 18 | 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import find_packages, setup 3 | 4 | 5 | with open('CHANGELOG.md') as history_file: 6 | history = history_file.read() 7 | 8 | requirements = [ 9 | 'click>=6.0', 10 | 'bcrypt<=3.1.7', 11 | 'docker >= 3.7.0', 12 | 'python-dotenv', 13 | 'requests >= 2.20.0, <= 2.25.1', 14 | 'fstrings', 15 | # Note >=2.35.0 cannot be used as is not compatible with the docker dependency; 16 | # docker requires websocket-client==0.56.0 and azure-cli-core>=2.35.0 requires websocket-client==1.31.1. 17 | 'azure-cli-core >= 2.34.1, < 2.35.0', 18 | 'iotedgehubdev == 0.14.18', 19 | 'applicationinsights == 0.11.9', 20 | 'commentjson == 0.9.0', 21 | 'pyyaml>=5.4', 22 | 'pypiwin32==219; sys_platform == "win32" and python_version < "3.6"', 23 | 'pypiwin32==223; sys_platform == "win32" and python_version >= "3.6"', 24 | 'more-itertools < 8.1.0' 25 | ] 26 | 27 | setup_requirements = [ 28 | ] 29 | 30 | test_requirements = [ 31 | ] 32 | 33 | 34 | setup( 35 | name='iotedgedev', 36 | version='3.3.8', 37 | description='The Azure IoT Edge Dev Tool greatly simplifies the IoT Edge development process by automating many routine manual tasks, such as building, deploying, pushing modules and configuring the IoT Edge Runtime.', 38 | long_description='See https://github.com/azure/iotedgedev for usage instructions.', 39 | author='Microsoft Corporation', 40 | author_email='vsciet@microsoft.com', 41 | url='https://github.com/azure/iotedgedev', 42 | packages=find_packages(include=['iotedgedev']), 43 | entry_points={ 44 | 'console_scripts': [ 45 | 'iotedgedev=iotedgedev.cli:main' 46 | ] 47 | }, 48 | include_package_data=True, 49 | install_requires=requirements, 50 | license='MIT license', 51 | zip_safe=False, 52 | keywords='azure iot edge dev tool', 53 | python_requires='>=3.6, <3.10', 54 | classifiers=[ 55 | 'Development Status :: 5 - Production/Stable', 56 | 'Intended Audience :: Developers', 57 | 'License :: OSI Approved :: MIT License', 58 | 'Natural Language :: English', 59 | 'Programming Language :: Python :: 3', 60 | 'Programming Language :: Python :: 3.6', 61 | 'Programming Language :: Python :: 3.7', 62 | 'Programming Language :: Python :: 3.8', 63 | 'Programming Language :: Python :: 3.9' 64 | ], 65 | test_suite='tests', 66 | tests_require=test_requirements, 67 | setup_requires=setup_requirements 68 | ) 69 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Test package for Azure IoT Edge Dev Tool .""" 2 | 3 | __author__ = 'Microsoft Corporation' 4 | __email__ = 'opencode@microsoft.com' 5 | __version__ = '3.3.8' 6 | -------------------------------------------------------------------------------- /tests/assets/deployment.manifest_invalid_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "modulesContent": { 3 | "$edgeAgent": { 4 | "properties.desired": { 5 | "schemaVersion": "${EDGEAGENT_SCHEMA_VERSION}", 6 | "runtime": { 7 | "type": "docker", 8 | "settings": { 9 | "minDockerVersion": "v1.25", 10 | "loggingOptions": "", 11 | "registryCredentials": { 12 | "test": { 13 | "username": 1, 14 | "password": "pwd" 15 | }, 16 | "test2": { 17 | "username": "$USERNAME", 18 | "password": "$PASSWORD", 19 | "address": "" 20 | } 21 | } 22 | } 23 | }, 24 | "systemModules": { 25 | "edgeAgent": { 26 | "type": "docker", 27 | "settings": { 28 | "image": "mcr.microsoft.com/azureiotedge-agent:${EDGE_RUNTIME_VERSION}" 29 | } 30 | }, 31 | "edgeHub": { 32 | "type": "docker", 33 | "status": "running", 34 | "restartPolicy": "always", 35 | "settings": { 36 | "image": "mcr.microsoft.com/azureiotedge-hub:${EDGE_RUNTIME_VERSION}", 37 | "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}" 38 | } 39 | } 40 | }, 41 | "modules": { 42 | "tempSensor": { 43 | "version": "1.0", 44 | "type": "docker", 45 | "status": "running", 46 | "restartPolicy": "always", 47 | "settings": { 48 | "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", 49 | "createOptions": "{}" 50 | } 51 | } 52 | } 53 | } 54 | }, 55 | "$edgeHub": { 56 | "properties.desired": { 57 | "schemaVersion": "${EDGEHUB_SCHEMA_VERSION}", 58 | "routes": { 59 | "sensorTocsharpmodule": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/csharpmodule/inputs/input1\")", 60 | "csharpmoduleToIoTHub": "FROM /messages/modules/csharpmodule/outputs/* INTO $upstream", 61 | "csharpfunctionToIoTHub": "FROM /messages/modules/csharpfunction/outputs/* INTO $upstream" 62 | }, 63 | "storeAndForwardConfiguration": { 64 | "timeToLiveSecs": 7200 65 | } 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /tests/assets/deployment.template.non_str_placeholder.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema-template": "4.0.0", 3 | "modulesContent": { 4 | "$edgeAgent": { 5 | "properties.desired": { 6 | "schemaVersion": "${EDGEAGENT_SCHEMA_VERSION}", 7 | "runtime": { 8 | "type": "docker", 9 | "settings": { 10 | "minDockerVersion": "v1.25", 11 | "loggingOptions": "", 12 | "registryCredentials": {} 13 | } 14 | }, 15 | "systemModules": { 16 | "edgeAgent": { 17 | "type": "docker", 18 | "settings": { 19 | "image": "mcr.microsoft.com/azureiotedge-agent:${EDGE_RUNTIME_VERSION}", 20 | "createOptions": {} 21 | } 22 | }, 23 | "edgeHub": { 24 | "type": "docker", 25 | "status": "running", 26 | "restartPolicy": "always", 27 | "settings": { 28 | "image": "mcr.microsoft.com/azureiotedge-hub:${EDGE_RUNTIME_VERSION}", 29 | "createOptions": { 30 | "HostConfig": { 31 | "PortBindings": { 32 | "5671/tcp": [ 33 | { 34 | "HostPort": "5671" 35 | } 36 | ], 37 | "8883/tcp": [ 38 | { 39 | "HostPort": "8883" 40 | } 41 | ], 42 | "443/tcp": [ 43 | { 44 | "HostPort": "443" 45 | } 46 | ] 47 | } 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | "modules": { 54 | "tempSensor": { 55 | "version": "1.0", 56 | "type": "docker", 57 | "status": "running", 58 | "restartPolicy": "always", 59 | "settings": { 60 | "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", 61 | "createOptions": {} 62 | } 63 | } 64 | } 65 | } 66 | }, 67 | "$edgeHub": { 68 | "properties.desired": { 69 | "schemaVersion": "${EDGEHUB_SCHEMA_VERSION}", 70 | "routes": { 71 | "sensorTofiltermodule": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/filtermodule/inputs/input1\")" 72 | }, 73 | "storeAndForwardConfiguration": { 74 | "timeToLiveSecs": ${TTL} 75 | } 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /tests/assets/deployment.template_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema-template": "4.0.0", 3 | "modulesContent": { 4 | "$edgeAgent": { 5 | "properties.desired": { 6 | "schemaVersion": "${EDGEAGENT_SCHEMA_VERSION}", 7 | "runtime": { 8 | "type": "docker", 9 | "settings": { 10 | "minDockerVersion": "v1.25", 11 | "loggingOptions": "", 12 | "registryCredentials": {} 13 | } 14 | }, 15 | "systemModules": { 16 | "edgeAgent": { 17 | "type": "docker", 18 | "settings": { 19 | "image": "mcr.microsoft.com/azureiotedge-agent:${EDGE_RUNTIME_VERSION}", 20 | "createOptions": {} 21 | } 22 | }, 23 | "edgeHub": { 24 | "type": "docker", 25 | "status": "running", 26 | "restartPolicy": "always", 27 | "settings": { 28 | "image": "mcr.microsoft.com/azureiotedge-hub:${EDGE_RUNTIME_VERSION}", 29 | "createOptions": { 30 | "HostConfig": { 31 | "PortBindings": { 32 | "5671/tcp": [ 33 | { 34 | "HostPort": "5671" 35 | } 36 | ], 37 | "8883/tcp": [ 38 | { 39 | "HostPort": "8883" 40 | } 41 | ], 42 | "443/tcp": [ 43 | { 44 | "HostPort": "443" 45 | } 46 | ] 47 | } 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | "modules": { 54 | "tempSensor": { 55 | "version": "1.0", 56 | "type": "docker", 57 | "status": "running", 58 | "restartPolicy": "always", 59 | "settings": { 60 | "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", 61 | "createOptions": {} 62 | } 63 | } 64 | } 65 | } 66 | }, 67 | "$edgeHub": { 68 | "properties.desired": { 69 | "schemaVersion": "${EDGEHUB_SCHEMA_VERSION}", 70 | "routes": { 71 | "sensorTofiltermodule": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/filtermodule/inputs/input1\")" 72 | }, 73 | "storeAndForwardConfiguration": { 74 | "timeToLiveSecs": 7200 75 | } 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /tests/assets/deployment.template_without_schema_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "modulesContent": { 3 | "$edgeAgent": { 4 | "properties.desired": { 5 | "schemaVersion": "${EDGEAGENT_SCHEMA_VERSION}", 6 | "runtime": { 7 | "type": "docker", 8 | "settings": { 9 | "minDockerVersion": "v1.25", 10 | "loggingOptions": "", 11 | "registryCredentials": {} 12 | } 13 | }, 14 | "systemModules": { 15 | "edgeAgent": { 16 | "type": "docker", 17 | "settings": { 18 | "image": "mcr.microsoft.com/azureiotedge-agent:${EDGE_RUNTIME_VERSION}", 19 | "createOptions": {} 20 | } 21 | }, 22 | "edgeHub": { 23 | "type": "docker", 24 | "status": "running", 25 | "restartPolicy": "always", 26 | "settings": { 27 | "image": "mcr.microsoft.com/azureiotedge-hub:${EDGE_RUNTIME_VERSION}", 28 | "createOptions": { 29 | "HostConfig": { 30 | "PortBindings": { 31 | "5671/tcp": [ 32 | { 33 | "HostPort": "5671" 34 | } 35 | ], 36 | "8883/tcp": [ 37 | { 38 | "HostPort": "8883" 39 | } 40 | ], 41 | "443/tcp": [ 42 | { 43 | "HostPort": "443" 44 | } 45 | ] 46 | } 47 | } 48 | } 49 | } 50 | } 51 | }, 52 | "modules": { 53 | "tempSensor": { 54 | "version": "1.0", 55 | "type": "docker", 56 | "status": "running", 57 | "restartPolicy": "always", 58 | "settings": { 59 | "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", 60 | "createOptions": {} 61 | } 62 | }, 63 | "sample_module": { 64 | "version": "1.0", 65 | "type": "docker", 66 | "status": "running", 67 | "restartPolicy": "always", 68 | "settings": { 69 | "image": "${MODULES.sample_module}", 70 | "createOptions": {} 71 | } 72 | }, 73 | "sample_module_2": { 74 | "version": "1.0", 75 | "type": "docker", 76 | "status": "running", 77 | "restartPolicy": "always", 78 | "settings": { 79 | "image": "${MODULEDIR<./sample_module_2>}", 80 | "createOptions": {} 81 | } 82 | } 83 | } 84 | } 85 | }, 86 | "$edgeHub": { 87 | "properties.desired": { 88 | "schemaVersion": "${EDGEHUB_SCHEMA_VERSION}", 89 | "routes": { 90 | "sample_moduleToIoTHub": "FROM /messages/modules/sample_module/outputs/* INTO $upstream", 91 | "sensorTosample_module": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/sample_module/inputs/input1\")" 92 | }, 93 | "storeAndForwardConfiguration": { 94 | "timeToLiveSecs": 7200 95 | } 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/.gitignore: -------------------------------------------------------------------------------- 1 | config/ 2 | .env -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "sample_module Remote Debug (.NET Core)", 6 | "type": "coreclr", 7 | "request": "attach", 8 | "processId": "${command:pickRemoteProcess}", 9 | "pipeTransport": { 10 | "pipeProgram": "docker", 11 | "pipeArgs": [ 12 | "exec", 13 | "-i", 14 | "sample_module", 15 | "sh", 16 | "-c" 17 | ], 18 | "debuggerPath": "~/vsdbg/vsdbg", 19 | "pipeCwd": "${workspaceFolder}", 20 | "quoteArgs": true 21 | }, 22 | "sourceFileMap": { 23 | "/app": "${workspaceFolder}/modules/sample_module" 24 | }, 25 | "justMyCode": true 26 | }, 27 | { 28 | "name": "sample_module Local Debug (.NET Core)", 29 | "type": "coreclr", 30 | "request": "launch", 31 | "program": "${workspaceRoot}/modules/sample_module/bin/Debug/netcoreapp2.1/sample_module.dll", 32 | "args": [], 33 | "cwd": "${workspaceRoot}/modules/sample_module", 34 | "internalConsoleOptions": "openOnSessionStart", 35 | "stopAtEntry": false, 36 | "console": "internalConsole", 37 | "env": { 38 | "EdgeHubConnectionString": "${config:azure-iot-edge.EdgeHubConnectionString}", 39 | "EdgeModuleCACertificateFile": "${config:azure-iot-edge.EdgeModuleCACertificateFile}" 40 | } 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/deployment.debug.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema-template": "4.0.0", 3 | "modulesContent": { 4 | "$edgeAgent": { 5 | "properties.desired": { 6 | "schemaVersion": "${EDGEAGENT_SCHEMA_VERSION}", 7 | "runtime": { 8 | "type": "docker", 9 | "settings": { 10 | "minDockerVersion": "v1.25", 11 | "loggingOptions": "", 12 | "registryCredentials": {} 13 | } 14 | }, 15 | "systemModules": { 16 | "edgeAgent": { 17 | "type": "docker", 18 | "settings": { 19 | "image": "mcr.microsoft.com/azureiotedge-agent:${EDGE_RUNTIME_VERSION}", 20 | "createOptions": {} 21 | } 22 | }, 23 | "edgeHub": { 24 | "type": "docker", 25 | "status": "running", 26 | "restartPolicy": "always", 27 | "settings": { 28 | "image": "mcr.microsoft.com/azureiotedge-hub:${EDGE_RUNTIME_VERSION}", 29 | "createOptions": { 30 | "HostConfig": { 31 | "PortBindings": { 32 | "5671/tcp": [ 33 | { 34 | "HostPort": "5671" 35 | } 36 | ], 37 | "8883/tcp": [ 38 | { 39 | "HostPort": "8883" 40 | } 41 | ], 42 | "443/tcp": [ 43 | { 44 | "HostPort": "443" 45 | } 46 | ] 47 | } 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | "modules": { 54 | "tempSensor": { 55 | "version": "1.0", 56 | "type": "docker", 57 | "status": "running", 58 | "restartPolicy": "always", 59 | "settings": { 60 | "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", 61 | "createOptions": {} 62 | } 63 | }, 64 | "sample_module": { 65 | "version": "1.0", 66 | "type": "docker", 67 | "status": "running", 68 | "restartPolicy": "always", 69 | "settings": { 70 | "image": "${MODULES.sample_module.debug}", 71 | "createOptions": {} 72 | } 73 | }, 74 | "sample_module_2": { 75 | "version": "1.0", 76 | "type": "docker", 77 | "status": "running", 78 | "restartPolicy": "always", 79 | "settings": { 80 | "image": "${MODULEDIR<./sample_module_2>.debug}", 81 | "createOptions": {} 82 | } 83 | } 84 | } 85 | } 86 | }, 87 | "$edgeHub": { 88 | "properties.desired": { 89 | "schemaVersion": "${EDGEHUB_SCHEMA_VERSION}", 90 | "routes": { 91 | "sample_moduleToIoTHub": "FROM /messages/modules/sample_module/outputs/* INTO $upstream", 92 | "sensorTosample_module": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/sample_module/inputs/input1\")" 93 | }, 94 | "storeAndForwardConfiguration": { 95 | "timeToLiveSecs": 7200 96 | } 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/deployment.escapedpath.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema-template": "4.0.0", 3 | "modulesContent": { 4 | "$edgeAgent": { 5 | "properties.desired": { 6 | "schemaVersion": "${EDGEAGENT_SCHEMA_VERSION}", 7 | "runtime": { 8 | "type": "docker", 9 | "settings": { 10 | "minDockerVersion": "v1.25", 11 | "loggingOptions": "", 12 | "registryCredentials": {} 13 | } 14 | }, 15 | "systemModules": { 16 | "edgeAgent": { 17 | "type": "docker", 18 | "settings": { 19 | "image": "mcr.microsoft.com/azureiotedge-agent:${EDGE_RUNTIME_VERSION}", 20 | "createOptions": {} 21 | } 22 | }, 23 | "edgeHub": { 24 | "type": "docker", 25 | "status": "running", 26 | "restartPolicy": "always", 27 | "settings": { 28 | "image": "mcr.microsoft.com/azureiotedge-hub:${EDGE_RUNTIME_VERSION}", 29 | "createOptions": { 30 | "HostConfig": { 31 | "PortBindings": { 32 | "5671/tcp": [ 33 | { 34 | "HostPort": "5671" 35 | } 36 | ], 37 | "8883/tcp": [ 38 | { 39 | "HostPort": "8883" 40 | } 41 | ], 42 | "443/tcp": [ 43 | { 44 | "HostPort": "443" 45 | } 46 | ] 47 | } 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | "modules": { 54 | "tempSensor": { 55 | "version": "1.0", 56 | "type": "docker", 57 | "status": "running", 58 | "restartPolicy": "always", 59 | "settings": { 60 | "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", 61 | "createOptions": {} 62 | } 63 | }, 64 | "sample_module_2": { 65 | "version": "1.0", 66 | "type": "docker", 67 | "status": "running", 68 | "restartPolicy": "always", 69 | "settings": { 70 | "image": "${MODULEDIR<.\\sample_module_2>}", 71 | "createOptions": {} 72 | } 73 | } 74 | } 75 | } 76 | }, 77 | "$edgeHub": { 78 | "properties.desired": { 79 | "schemaVersion": "${EDGEHUB_SCHEMA_VERSION}", 80 | "routes": { 81 | "sample_moduleToIoTHub": "FROM /messages/modules/sample_module/outputs/* INTO $upstream", 82 | "sensorTosample_module": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/sample_module/inputs/input1\")" 83 | }, 84 | "storeAndForwardConfiguration": { 85 | "timeToLiveSecs": 7200 86 | } 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/deployment.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema-template": "4.0.0", 3 | "modulesContent": { 4 | "$edgeAgent": { 5 | "properties.desired": { 6 | "schemaVersion": "${EDGEAGENT_SCHEMA_VERSION}", 7 | "runtime": { 8 | "type": "docker", 9 | "settings": { 10 | "minDockerVersion": "v1.25", 11 | "loggingOptions": "", 12 | "registryCredentials": { 13 | "testacr": { 14 | "username": "$USERNAME", 15 | "password": "$PASSWORD", 16 | "address": "$CONTAINER_REGISTRY_SERVER" 17 | } 18 | } 19 | } 20 | }, 21 | "systemModules": { 22 | "edgeAgent": { 23 | "type": "docker", 24 | "settings": { 25 | "image": "mcr.microsoft.com/azureiotedge-agent:${EDGE_RUNTIME_VERSION}", 26 | "createOptions": {} 27 | } 28 | }, 29 | "edgeHub": { 30 | "type": "docker", 31 | "status": "running", 32 | "restartPolicy": "always", 33 | "settings": { 34 | "image": "mcr.microsoft.com/azureiotedge-hub:${EDGE_RUNTIME_VERSION}", 35 | "createOptions": { 36 | "HostConfig": { 37 | "PortBindings": { 38 | "5671/tcp": [ 39 | { 40 | "HostPort": "5671" 41 | } 42 | ], 43 | "8883/tcp": [ 44 | { 45 | "HostPort": "8883" 46 | } 47 | ], 48 | "443/tcp": [ 49 | { 50 | "HostPort": "443" 51 | } 52 | ] 53 | } 54 | } 55 | } 56 | } 57 | } 58 | }, 59 | "modules": { 60 | "tempSensor": { 61 | "version": "1.0", 62 | "type": "docker", 63 | "status": "running", 64 | "restartPolicy": "always", 65 | "settings": { 66 | "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0", 67 | "createOptions": {} 68 | } 69 | }, 70 | "sample_module": { 71 | "version": "1.0", 72 | "type": "docker", 73 | "status": "running", 74 | "restartPolicy": "always", 75 | "settings": { 76 | "image": "${MODULES.sample_module}", 77 | "createOptions": {} 78 | } 79 | }, 80 | "sample_module_2": { 81 | "version": "1.0", 82 | "type": "docker", 83 | "status": "running", 84 | "restartPolicy": "always", 85 | "settings": { 86 | "image": "${MODULEDIR<./sample_module_2>}", 87 | "createOptions": {} 88 | } 89 | } 90 | } 91 | } 92 | }, 93 | "$edgeHub": { 94 | "properties.desired": { 95 | "schemaVersion": "${EDGEHUB_SCHEMA_VERSION}", 96 | "routes": { 97 | "sample_moduleToIoTHub": "FROM /messages/modules/sample_module/outputs/* INTO $upstream", 98 | "sensorTosample_module": "FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/sample_module/inputs/input1\")" 99 | }, 100 | "storeAndForwardConfiguration": { 101 | "timeToLiveSecs": 7200 102 | } 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/layered_deployment.flattened_props.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "content": { 3 | "modulesContent": { 4 | "$edgeAgent": { 5 | "properties.desired": { 6 | "schemaVersion": "${EDGEAGENT_SCHEMA_VERSION}", 7 | "runtime": { 8 | "type": "docker", 9 | "settings": { 10 | "minDockerVersion": "v1.25", 11 | "loggingOptions": "", 12 | "registryCredentials": {} 13 | } 14 | }, 15 | "modules": { 16 | "sample_module": { 17 | "version": "1.0", 18 | "type": "docker", 19 | "status": "running", 20 | "restartPolicy": "always", 21 | "settings": { 22 | "image": "${MODULES.sample_module}", 23 | "createOptions": {} 24 | } 25 | } 26 | }, 27 | "systemModules": { 28 | "edgeAgent": { 29 | "type": "docker", 30 | "settings": { 31 | "image": "mcr.microsoft.com/azureiotedge-agent:${EDGE_RUNTIME_VERSION}" 32 | } 33 | }, 34 | "edgeHub": { 35 | "type": "docker", 36 | "status": "running", 37 | "settings": { 38 | "image": "mcr.microsoft.com/azureiotedge-hub:${EDGE_RUNTIME_VERSION}" 39 | }, 40 | "restartPolicy": "always" 41 | } 42 | } 43 | } 44 | }, 45 | "$edgeHub": { 46 | "properties.desired": { 47 | "schemaVersion": "${EDGEHUB_SCHEMA_VERSION}", 48 | "routes": { 49 | "route": "FROM /messages/* INTO $upstream" 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/libs/sharedlib/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace sharedlib 4 | { 5 | public static class Class1 6 | { 7 | public static string Foo() 8 | { 9 | return "foo"; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/libs/sharedlib/sharedlib.csproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Microsoft.NET.Sdk"> 2 | 3 | <PropertyGroup> 4 | <TargetFramework>netstandard2.0</TargetFramework> 5 | </PropertyGroup> 6 | 7 | </Project> 8 | -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/modules/non_module_project/placeholder.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/iotedgedev/8765d719aa18c97ce3ebd02fdc51236210b3daf3/tests/assets/test_solution_shared_lib/modules/non_module_project/placeholder.txt -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/modules/sample_module/.gitignore: -------------------------------------------------------------------------------- 1 | # .NET Core 2 | project.lock.json 3 | project.fragment.lock.json 4 | artifacts/ 5 | **/Properties/launchSettings.json 6 | 7 | *_i.c 8 | *_p.c 9 | *_i.h 10 | *.ilk 11 | *.meta 12 | *.obj 13 | *.pch 14 | *.pdb 15 | *.pgc 16 | *.pgd 17 | *.rsp 18 | *.sbr 19 | *.tlb 20 | *.tli 21 | *.tlh 22 | *.tmp 23 | *.tmp_proj 24 | *.log 25 | *.vspscc 26 | *.vssscc 27 | .builds 28 | *.pidb 29 | *.svclog 30 | *.scc 31 | .vs 32 | 33 | [Bb]in/ 34 | [Oo]bj/ -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/modules/sample_module/Dockerfile.amd64: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:2.1 AS build-env 2 | 3 | COPY ./libs /app/libs 4 | COPY ./modules/sample_module/*.csproj /app/modules/sample_module/ 5 | COPY ./modules/sample_module /app/modules/sample_module 6 | 7 | WORKDIR /app/modules/sample_module 8 | RUN dotnet restore 9 | RUN dotnet publish -c Release -o /app/out 10 | 11 | FROM mcr.microsoft.com/dotnet/runtime:2.1-stretch-slim 12 | WORKDIR /app 13 | COPY --from=build-env /app/out ./ 14 | 15 | RUN useradd -ms /bin/bash moduleuser 16 | USER moduleuser 17 | 18 | ENTRYPOINT ["dotnet", "sample_module.dll"] -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/modules/sample_module/Dockerfile.amd64.debug: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/runtime:2.1-stretch-slim AS base 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y --no-install-recommends unzip procps && \ 5 | rm -rf /var/lib/apt/lists/* 6 | 7 | RUN useradd -ms /bin/bash moduleuser 8 | USER moduleuser 9 | RUN curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/vsdbg 10 | 11 | FROM mcr.microsoft.com/dotnet/sdk:2.1 AS build-env 12 | 13 | COPY ./libs /app/libs 14 | COPY ./modules/sample_module/*.csproj /app/modules/sample_module/ 15 | COPY ./modules/sample_module /app/modules/sample_module 16 | 17 | WORKDIR /app/modules/sample_module 18 | RUN dotnet restore 19 | RUN dotnet publish -c Debug -o /app/out 20 | 21 | FROM base 22 | WORKDIR /app 23 | COPY --from=build-env /app/out ./ 24 | 25 | ENTRYPOINT ["dotnet", "sample_module.dll"] -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/modules/sample_module/Dockerfile.windows-amd64: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:2.1 AS build-env 2 | 3 | COPY ./libs /app/libs 4 | COPY ./modules/sample_module/*.csproj /app/modules/sample_module/ 5 | COPY ./modules/sample_module /app/modules/sample_module 6 | 7 | WORKDIR /app/modules/sample_module 8 | RUN dotnet restore 9 | RUN dotnet publish -c Release -o /app/out 10 | 11 | FROM mcr.microsoft.com/dotnet/runtime:2.1-nanoserver-1809 12 | WORKDIR /app 13 | COPY --from=build-env /app/out ./ 14 | 15 | ENTRYPOINT ["dotnet", "sample_module.dll"] -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/modules/sample_module/Program.cs: -------------------------------------------------------------------------------- 1 | namespace sample_module 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Runtime.InteropServices; 6 | using System.Runtime.Loader; 7 | using System.Security.Cryptography.X509Certificates; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Microsoft.Azure.Devices.Client; 12 | using sharedlib; 13 | 14 | class Program 15 | { 16 | static int counter; 17 | 18 | static void Main(string[] args) 19 | { 20 | Class1.Foo(); 21 | 22 | Init().Wait(); 23 | 24 | // Wait until the app unloads or is cancelled 25 | var cts = new CancellationTokenSource(); 26 | AssemblyLoadContext.Default.Unloading += (ctx) => cts.Cancel(); 27 | Console.CancelKeyPress += (sender, cpe) => cts.Cancel(); 28 | WhenCancelled(cts.Token).Wait(); 29 | } 30 | 31 | /// <summary> 32 | /// Handles cleanup operations when app is cancelled or unloads 33 | /// </summary> 34 | public static Task WhenCancelled(CancellationToken cancellationToken) 35 | { 36 | var tcs = new TaskCompletionSource<bool>(); 37 | cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs); 38 | return tcs.Task; 39 | } 40 | 41 | /// <summary> 42 | /// Initializes the ModuleClient and sets up the callback to receive 43 | /// messages containing temperature information 44 | /// </summary> 45 | static async Task Init() 46 | { 47 | AmqpTransportSettings amqpSetting = new AmqpTransportSettings(TransportType.Amqp_Tcp_Only); 48 | ITransportSettings[] settings = { amqpSetting }; 49 | 50 | // Open a connection to the Edge runtime 51 | ModuleClient ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(settings); 52 | await ioTHubModuleClient.OpenAsync(); 53 | Console.WriteLine("IoT Hub module client initialized."); 54 | 55 | // Register callback to be called when a message is received by the module 56 | await ioTHubModuleClient.SetInputMessageHandlerAsync("input1", PipeMessage, ioTHubModuleClient); 57 | } 58 | 59 | /// <summary> 60 | /// This method is called whenever the module is sent a message from the EdgeHub. 61 | /// It just pipe the messages without any change. 62 | /// It prints all the incoming messages. 63 | /// </summary> 64 | static async Task<MessageResponse> PipeMessage(Message message, object userContext) 65 | { 66 | int counterValue = Interlocked.Increment(ref counter); 67 | 68 | var moduleClient = userContext as ModuleClient; 69 | if (moduleClient == null) 70 | { 71 | throw new InvalidOperationException("UserContext doesn't contain " + "expected values"); 72 | } 73 | 74 | byte[] messageBytes = message.GetBytes(); 75 | string messageString = Encoding.UTF8.GetString(messageBytes); 76 | Console.WriteLine($"Received message: {counterValue}, Body: [{messageString}]"); 77 | 78 | if (!string.IsNullOrEmpty(messageString)) 79 | { 80 | var pipeMessage = new Message(messageBytes); 81 | foreach (var prop in message.Properties) 82 | { 83 | pipeMessage.Properties.Add(prop.Key, prop.Value); 84 | } 85 | await moduleClient.SendEventAsync("output1", pipeMessage); 86 | Console.WriteLine("Received message sent"); 87 | } 88 | return MessageResponse.Completed; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/modules/sample_module/module.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema-version": "0.0.1", 3 | "description": "", 4 | "image": { 5 | "repository": "${CONTAINER_REGISTRY_SERVER}/sample_module", 6 | "tag": { 7 | "version": "0.0.1-RC", 8 | "platforms": { 9 | "amd64": "./Dockerfile.amd64", 10 | "amd64.debug": "./Dockerfile.amd64.debug", 11 | "windows-amd64": "./Dockerfile.windows-amd64" 12 | } 13 | }, 14 | "buildOptions": [], 15 | "contextPath": "../../" 16 | }, 17 | "language": "csharp" 18 | } -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/modules/sample_module/sample_module.csproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Microsoft.NET.Sdk"> 2 | <PropertyGroup> 3 | <OutputType>Exe</OutputType> 4 | <TargetFramework>netcoreapp2.1</TargetFramework> 5 | </PropertyGroup> 6 | 7 | <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netcoreapp2.1|AnyCPU'"> 8 | <TreatWarningsAsErrors>True</TreatWarningsAsErrors> 9 | <TreatSpecificWarningsAsErrors /> 10 | </PropertyGroup> 11 | 12 | <ItemGroup> 13 | <PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.40.0" /> 14 | <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" /> 15 | <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.0" /> 16 | <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.0.0" /> 17 | <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" /> 18 | <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" /> 19 | <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" /> 20 | <PackageReference Include="System.Runtime.Loader" Version="4.3.0" /> 21 | </ItemGroup> 22 | 23 | <ItemGroup> 24 | <ProjectReference Include="..\..\libs\sharedlib\sharedlib.csproj" /> 25 | </ItemGroup> 26 | </Project> 27 | -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/sample_module_2/.gitignore: -------------------------------------------------------------------------------- 1 | # .NET Core 2 | project.lock.json 3 | project.fragment.lock.json 4 | artifacts/ 5 | **/Properties/launchSettings.json 6 | 7 | *_i.c 8 | *_p.c 9 | *_i.h 10 | *.ilk 11 | *.meta 12 | *.obj 13 | *.pch 14 | *.pdb 15 | *.pgc 16 | *.pgd 17 | *.rsp 18 | *.sbr 19 | *.tlb 20 | *.tli 21 | *.tlh 22 | *.tmp 23 | *.tmp_proj 24 | *.log 25 | *.vspscc 26 | *.vssscc 27 | .builds 28 | *.pidb 29 | *.svclog 30 | *.scc 31 | .vs 32 | 33 | [Bb]in/ 34 | [Oo]bj/ -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/sample_module_2/Dockerfile.amd64: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:2.1 AS build-env 2 | 3 | COPY ./libs /app/libs 4 | COPY ./sample_module_2/*.csproj /app/sample_module_2/ 5 | COPY ./sample_module_2 /app/sample_module_2 6 | 7 | WORKDIR /app/sample_module_2 8 | RUN dotnet restore 9 | RUN dotnet publish -c Release -o /app/out 10 | 11 | FROM mcr.microsoft.com/dotnet/runtime:2.1-stretch-slim 12 | WORKDIR /app 13 | COPY --from=build-env /app/out ./ 14 | 15 | RUN useradd -ms /bin/bash moduleuser 16 | USER moduleuser 17 | 18 | ENTRYPOINT ["dotnet", "sample_module_2.dll"] -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/sample_module_2/Dockerfile.amd64.debug: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/runtime:2.1-stretch-slim AS base 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y --no-install-recommends unzip procps && \ 5 | rm -rf /var/lib/apt/lists/* 6 | 7 | RUN useradd -ms /bin/bash moduleuser 8 | USER moduleuser 9 | RUN curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/vsdbg 10 | 11 | FROM mcr.microsoft.com/dotnet/sdk:2.1 AS build-env 12 | 13 | COPY ./libs /app/libs 14 | COPY ./sample_module_2/*.csproj /app/sample_module_2/ 15 | COPY ./sample_module_2 /app/sample_module_2 16 | 17 | WORKDIR /app/sample_module_2 18 | RUN dotnet restore 19 | RUN dotnet publish -c Debug -o /app/out 20 | 21 | FROM base 22 | WORKDIR /app 23 | COPY --from=build-env /app/out ./ 24 | 25 | ENTRYPOINT ["dotnet", "sample_module_2.dll"] -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/sample_module_2/Dockerfile.windows-amd64: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:2.1 AS build-env 2 | 3 | COPY ./libs /app/libs 4 | COPY ./sample_module_2/*.csproj /app/sample_module_2/ 5 | COPY ./sample_module_2 /app/sample_module_2 6 | 7 | WORKDIR /app/sample_module_2 8 | RUN dotnet restore 9 | RUN dotnet publish -c Release -o /app/out 10 | 11 | FROM mcr.microsoft.com/dotnet/runtime:2.1-nanoserver-1809 12 | WORKDIR /app 13 | COPY --from=build-env /app/out ./ 14 | 15 | ENTRYPOINT ["dotnet", "sample_module_2.dll"] -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/sample_module_2/Program.cs: -------------------------------------------------------------------------------- 1 | namespace sample_module 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Runtime.InteropServices; 6 | using System.Runtime.Loader; 7 | using System.Security.Cryptography.X509Certificates; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Microsoft.Azure.Devices.Client; 12 | using sharedlib; 13 | 14 | class Program 15 | { 16 | static int counter; 17 | 18 | static void Main(string[] args) 19 | { 20 | Class1.Foo(); 21 | 22 | Init().Wait(); 23 | 24 | // Wait until the app unloads or is cancelled 25 | var cts = new CancellationTokenSource(); 26 | AssemblyLoadContext.Default.Unloading += (ctx) => cts.Cancel(); 27 | Console.CancelKeyPress += (sender, cpe) => cts.Cancel(); 28 | WhenCancelled(cts.Token).Wait(); 29 | } 30 | 31 | /// <summary> 32 | /// Handles cleanup operations when app is cancelled or unloads 33 | /// </summary> 34 | public static Task WhenCancelled(CancellationToken cancellationToken) 35 | { 36 | var tcs = new TaskCompletionSource<bool>(); 37 | cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs); 38 | return tcs.Task; 39 | } 40 | 41 | /// <summary> 42 | /// Initializes the ModuleClient and sets up the callback to receive 43 | /// messages containing temperature information 44 | /// </summary> 45 | static async Task Init() 46 | { 47 | AmqpTransportSettings amqpSetting = new AmqpTransportSettings(TransportType.Amqp_Tcp_Only); 48 | ITransportSettings[] settings = { amqpSetting }; 49 | 50 | // Open a connection to the Edge runtime 51 | ModuleClient ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(settings); 52 | await ioTHubModuleClient.OpenAsync(); 53 | Console.WriteLine("IoT Hub module client initialized."); 54 | 55 | // Register callback to be called when a message is received by the module 56 | await ioTHubModuleClient.SetInputMessageHandlerAsync("input1", PipeMessage, ioTHubModuleClient); 57 | } 58 | 59 | /// <summary> 60 | /// This method is called whenever the module is sent a message from the EdgeHub. 61 | /// It just pipe the messages without any change. 62 | /// It prints all the incoming messages. 63 | /// </summary> 64 | static async Task<MessageResponse> PipeMessage(Message message, object userContext) 65 | { 66 | int counterValue = Interlocked.Increment(ref counter); 67 | 68 | var moduleClient = userContext as ModuleClient; 69 | if (moduleClient == null) 70 | { 71 | throw new InvalidOperationException("UserContext doesn't contain " + "expected values"); 72 | } 73 | 74 | byte[] messageBytes = message.GetBytes(); 75 | string messageString = Encoding.UTF8.GetString(messageBytes); 76 | Console.WriteLine($"Received message: {counterValue}, Body: [{messageString}]"); 77 | 78 | if (!string.IsNullOrEmpty(messageString)) 79 | { 80 | var pipeMessage = new Message(messageBytes); 81 | foreach (var prop in message.Properties) 82 | { 83 | pipeMessage.Properties.Add(prop.Key, prop.Value); 84 | } 85 | await moduleClient.SendEventAsync("output1", pipeMessage); 86 | Console.WriteLine("Received message sent"); 87 | } 88 | return MessageResponse.Completed; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/sample_module_2/module.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema-version": "0.0.1", 3 | "description": "", 4 | "image": { 5 | "repository": "${CONTAINER_REGISTRY_SERVER}/sample_module_2", 6 | "tag": { 7 | "version": "0.0.1-RC", 8 | "platforms": { 9 | "amd64": "./Dockerfile.amd64", 10 | "amd64.debug": "./Dockerfile.amd64.debug", 11 | "windows-amd64": "./Dockerfile.windows-amd64" 12 | } 13 | }, 14 | "buildOptions": [], 15 | "contextPath": "../" 16 | }, 17 | "language": "csharp" 18 | } -------------------------------------------------------------------------------- /tests/assets/test_solution_shared_lib/sample_module_2/sample_module_2.csproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Microsoft.NET.Sdk"> 2 | <PropertyGroup> 3 | <OutputType>Exe</OutputType> 4 | <TargetFramework>netcoreapp2.1</TargetFramework> 5 | </PropertyGroup> 6 | 7 | <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netcoreapp2.1|AnyCPU'"> 8 | <TreatWarningsAsErrors>True</TreatWarningsAsErrors> 9 | <TreatSpecificWarningsAsErrors /> 10 | </PropertyGroup> 11 | 12 | <ItemGroup> 13 | <PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.40.0" /> 14 | <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" /> 15 | <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.0" /> 16 | <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.0.0" /> 17 | <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" /> 18 | <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" /> 19 | <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" /> 20 | <PackageReference Include="System.Runtime.Loader" Version="4.3.0" /> 21 | </ItemGroup> 22 | 23 | <ItemGroup> 24 | <ProjectReference Include="..\libs\sharedlib\sharedlib.csproj" /> 25 | </ItemGroup> 26 | </Project> 27 | -------------------------------------------------------------------------------- /tests/test_azurecli.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from iotedgedev.azurecli import get_query_argument_for_id_and_name 4 | 5 | pytestmark = pytest.mark.unit 6 | 7 | 8 | def get_terms(query): 9 | # These tests are all asserting that the query contains two terms enclosed in 10 | # [?], separated by || 11 | # They don't care about the order. Tests will fail if the square brackets and || 12 | # contract is violated, but we'd want them to fail in that case. 13 | return query[2:len(query)-1].split(" || ") 14 | 15 | 16 | def test_lowercase_token_should_be_lowercase_for_name_and_id(): 17 | token = "abc123" 18 | query = get_query_argument_for_id_and_name(token) 19 | terms = get_terms(query) 20 | 21 | assert len(terms) == 2 22 | assert "starts_with(@.id,'abc123')" in terms 23 | assert "contains(@.name,'abc123')" in terms 24 | 25 | 26 | def test_mixedcase_token_should_be_lowercase_for_id_but_unmodified_for_name(): 27 | token = "AbC123" 28 | query = get_query_argument_for_id_and_name(token) 29 | terms = get_terms(query) 30 | 31 | assert len(terms) == 2 32 | assert "starts_with(@.id,'abc123')" in terms 33 | assert "contains(@.name,'AbC123')" in terms 34 | -------------------------------------------------------------------------------- /tests/test_buildoptionsparser.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from iotedgedev.buildoptionsparser import BuildOptionsParser 3 | 4 | pytestmark = pytest.mark.unit 5 | 6 | 7 | def test_filter_build_options(): 8 | build_options = [ 9 | "--rm", 10 | "-f test", 11 | "--file test", 12 | "-t image", 13 | "--tag image" 14 | ] 15 | build_options_parser = BuildOptionsParser(build_options) 16 | assert not build_options_parser.parse_build_options() 17 | 18 | 19 | def test_parse_to_dict(): 20 | build_options = [ 21 | "--add-host=github.com:192.30.255.112", 22 | "--add-host=ports.ubuntu.com:91.189.88.150", 23 | "--build-arg a=b", 24 | "--build-arg c=d", 25 | "--label e=f", 26 | "--label g" 27 | ] 28 | sdk_options = { 29 | 'extra_hosts': { 30 | 'github.com': '192.30.255.112', 31 | 'ports.ubuntu.com': '91.189.88.150' 32 | }, 33 | 'buildargs': { 34 | 'a': 'b', 35 | 'c': 'd' 36 | }, 37 | 'labels': { 38 | 'e': 'f', 39 | 'g': '' 40 | } 41 | } 42 | build_options_parser = BuildOptionsParser(build_options) 43 | assert sdk_options == build_options_parser.parse_build_options() 44 | 45 | 46 | def test_parse_to_list(): 47 | build_options = [ 48 | "--cache-from a", 49 | "--cache-from b" 50 | ] 51 | sdk_options = { 52 | 'cache_from': ['a', 'b'] 53 | } 54 | build_options_parser = BuildOptionsParser(build_options) 55 | assert sdk_options == build_options_parser.parse_build_options() 56 | 57 | 58 | def test_parse_val(): 59 | build_options = [ 60 | "--network bridge", 61 | "--platform Linux", 62 | "--shm-size 1000000", 63 | "--target target" 64 | ] 65 | sdk_options = { 66 | 'network_mode': 'bridge', 67 | 'platform': 'Linux', 68 | 'shmsize': '1000000', 69 | 'target': 'target' 70 | } 71 | build_options_parser = BuildOptionsParser(build_options) 72 | assert sdk_options == build_options_parser.parse_build_options() 73 | 74 | 75 | def test_parse_container_limits(): 76 | build_options = [ 77 | "--cpu-shares 50", 78 | "--cpuset-cpus 0-1", 79 | "--memory 10000000", 80 | "--memory-swap 2000000" 81 | ] 82 | sdk_options = { 83 | 'container_limits': { 84 | 'cpushares': '50', 85 | 'cpusetcpus': '0-1', 86 | 'memory': '10000000', 87 | 'memswap': '2000000' 88 | } 89 | } 90 | build_options_parser = BuildOptionsParser(build_options) 91 | assert sdk_options == build_options_parser.parse_build_options() 92 | 93 | 94 | def test_parse_flag(): 95 | build_options = [ 96 | "--pull=true", 97 | "-q=false", 98 | "--no-cache" 99 | ] 100 | sdk_options = { 101 | 'pull': True, 102 | 'quiet': False, 103 | 'nocache': True 104 | } 105 | build_options_parser = BuildOptionsParser(build_options) 106 | assert sdk_options == build_options_parser.parse_build_options() 107 | 108 | 109 | def test_invalid_build_options(): 110 | with pytest.raises(KeyError): 111 | build_options = [ 112 | "--cgroup-parent", 113 | "--compress", 114 | "--cpu-period", 115 | "--cpuset-mems 10", 116 | ] 117 | build_options_parser = BuildOptionsParser(build_options) 118 | build_options_parser.parse_build_options() 119 | 120 | 121 | def test_filtered_valid_build_options(): 122 | build_options = [ 123 | "--rm", 124 | "--file test", 125 | "--tag image", 126 | "--add-host=github.com:192.30.255.112", 127 | "--add-host=ports.ubuntu.com:91.189.88.150", 128 | "--cache-from a", 129 | "--cache-from b", 130 | "--network bridge", 131 | "--platform Linux", 132 | "--cpu-shares 50", 133 | "--memory 10000000", 134 | "--pull=true", 135 | "-q=false", 136 | "--no-cache" 137 | ] 138 | sdk_options = { 139 | 'extra_hosts': { 140 | 'github.com': '192.30.255.112', 141 | 'ports.ubuntu.com': '91.189.88.150' 142 | }, 143 | 'cache_from': ['a', 'b'], 144 | 'network_mode': 'bridge', 145 | 'platform': 'Linux', 146 | 'container_limits': { 147 | 'cpushares': '50', 148 | 'memory': '10000000', 149 | }, 150 | 'pull': True, 151 | 'quiet': False, 152 | 'nocache': True 153 | } 154 | build_options_parser = BuildOptionsParser(build_options) 155 | assert sdk_options == build_options_parser.parse_build_options() 156 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | from iotedgedev.cli import main 2 | from .utility import get_cli_command_structure 3 | import pytest 4 | 5 | pytestmark = pytest.mark.unit 6 | 7 | 8 | def test_cli_structure(): 9 | # Arrange 10 | expected_structure = { 11 | "solution": { 12 | "new": None, 13 | "init": None, 14 | "e2e": None, 15 | "add": None, 16 | "build": None, 17 | "push": None, 18 | "deploy": None, 19 | "tag": None, 20 | "genconfig": None 21 | }, 22 | "simulator": { 23 | "setup": None, 24 | "start": None, 25 | "stop": None, 26 | "modulecred": None 27 | }, 28 | "iothub": { 29 | "deploy": None, 30 | "monitor": None, 31 | "setup": None 32 | }, 33 | "docker": { 34 | "setup": None, 35 | "clean": None, 36 | "log": None 37 | }, 38 | "new": None, 39 | "init": None, 40 | "add": None, 41 | "build": None, 42 | "push": None, 43 | "deploy": None, 44 | "genconfig": None, 45 | "setup": None, 46 | "start": None, 47 | "stop": None, 48 | "monitor": None, 49 | "log": None 50 | } 51 | # Act 52 | structure = get_cli_command_structure(main) 53 | 54 | # Assert 55 | assert structure == expected_structure 56 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from iotedgedev.telemetryconfig import TelemetryConfig 6 | 7 | pytestmark = pytest.mark.unit 8 | 9 | 10 | def test_firsttime(request): 11 | config = TelemetryConfig() 12 | 13 | def clean(): 14 | config_path = config.get_config_path() 15 | if os.path.exists(config_path): 16 | os.remove(config_path) 17 | request.addfinalizer(clean) 18 | 19 | clean() 20 | config = TelemetryConfig() 21 | 22 | assert config.get(config.DEFAULT_DIRECT, config.FIRSTTIME_SECTION) == 'yes' 23 | assert config.get(config.DEFAULT_DIRECT, config.TELEMETRY_SECTION) is None 24 | 25 | config.check_firsttime() 26 | 27 | assert config.get(config.DEFAULT_DIRECT, config.FIRSTTIME_SECTION) == 'no' 28 | assert config.get(config.DEFAULT_DIRECT, config.TELEMETRY_SECTION) == 'yes' 29 | -------------------------------------------------------------------------------- /tests/test_connectionstring.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from iotedgedev.connectionstring import (ConnectionString, 4 | DeviceConnectionString, 5 | IoTHubConnectionString) 6 | 7 | pytestmark = pytest.mark.unit 8 | 9 | emptystring = "" 10 | valid_connectionstring = "HostName=testhub.azure-devices.net;SharedAccessKey=gibberish" 11 | valid_iothub_connectionstring = "HostName=ChaoyiTestIoT.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=moregibberish" 12 | valid_device_connectionstring = "HostName=testhub.azure-devices.net;DeviceId=testdevice;SharedAccessKey=othergibberish" 13 | invalid_connectionstring = "HostName=azure-devices.net;SharedAccessKey=gibberish" 14 | invalid_iothub_connectionstring = "HostName=testhub.azure-devices.net;SharedAccessKey=moregibberish" 15 | invalid_device_connectionstring = "HostName=testhub.azure-devices.net;DeviceId=;SharedAccessKey=othergibberish" 16 | empty_hostname_iothub_connectionstring = "HostName=;SharedAccessKeyName=iothubowner;SharedAccessKey=moregibberish" 17 | non_sas_connectionstring = "HostName=testhub.azure-devices.net;DeviceId=testdevice;x509=true" 18 | 19 | 20 | def test_non_sas_connectionstring(): 21 | connStr = ConnectionString(non_sas_connectionstring) 22 | assert not connStr.connection_string 23 | 24 | 25 | def test_empty_connectionstring(): 26 | connectionstring = ConnectionString(emptystring) 27 | assert not connectionstring.data 28 | 29 | 30 | def test_empty_hostname_iothub_connectionstring(): 31 | connectionstring = ConnectionString(empty_hostname_iothub_connectionstring) 32 | assert connectionstring.iothub_host.name == "" 33 | assert connectionstring.iothub_host.hub_name == "" 34 | assert connectionstring.shared_access_key == "moregibberish" 35 | assert connectionstring.iothub_host.name_hash == "" 36 | 37 | 38 | def test_empty_iothub_connectionstring(): 39 | connectionstring = IoTHubConnectionString(emptystring) 40 | assert not connectionstring.data 41 | 42 | 43 | def test_empty_device_connectionstring(): 44 | connectionstring = DeviceConnectionString(emptystring) 45 | assert not connectionstring.data 46 | 47 | 48 | def test_valid_connectionstring(): 49 | connectionstring = ConnectionString(valid_connectionstring) 50 | assert connectionstring.iothub_host.name == "testhub.azure-devices.net" 51 | assert connectionstring.iothub_host.hub_name == "testhub" 52 | assert connectionstring.shared_access_key == "gibberish" 53 | 54 | 55 | def test_valid_iothub_connectionstring(): 56 | connectionstring = IoTHubConnectionString(valid_iothub_connectionstring) 57 | assert connectionstring.iothub_host.name == "ChaoyiTestIoT.azure-devices.net" 58 | assert connectionstring.iothub_host.hub_name == "ChaoyiTestIoT" 59 | assert connectionstring.shared_access_key_name == "iothubowner" 60 | assert connectionstring.shared_access_key == "moregibberish" 61 | assert connectionstring.iothub_host.name_hash == "6b8fcfea09003d5f104771e83bd9ff54c592ec2277ec1815df91dd64d1633778" 62 | 63 | 64 | def test_valid_devicehub_connectionstring(): 65 | connectionstring = DeviceConnectionString(valid_device_connectionstring) 66 | assert connectionstring.iothub_host.name == "testhub.azure-devices.net" 67 | assert connectionstring.iothub_host.hub_name == "testhub" 68 | assert connectionstring.device_id == "testdevice" 69 | assert connectionstring.shared_access_key == "othergibberish" 70 | 71 | 72 | def test_invalid_connectionstring(): 73 | connectionstring = ConnectionString(invalid_connectionstring) 74 | assert connectionstring.iothub_host.hub_name != "testhub" 75 | 76 | 77 | def test_invalid_iothub_connectionstring(): 78 | with pytest.raises(KeyError): 79 | IoTHubConnectionString(invalid_iothub_connectionstring) 80 | 81 | 82 | def test_invalid_devicehub_connectionstring(): 83 | connectionstring = DeviceConnectionString(invalid_device_connectionstring) 84 | assert connectionstring.iothub_host.name == "testhub.azure-devices.net" 85 | assert connectionstring.iothub_host.hub_name == "testhub" 86 | assert not connectionstring.device_id 87 | assert connectionstring.shared_access_key == "othergibberish" 88 | -------------------------------------------------------------------------------- /tests/test_decorators.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | import pytest 4 | 5 | from iotedgedev.decorators import hash256_result, suppress_all_exceptions 6 | 7 | pytestmark = pytest.mark.unit 8 | 9 | 10 | def test_suppress_all_exceptions(): 11 | @suppress_all_exceptions() 12 | def test_valid(): 13 | return 'Everything is OK' 14 | assert test_valid() == 'Everything is OK' 15 | 16 | @suppress_all_exceptions('fallback') 17 | def test_exception_fallback(): 18 | raise Exception 19 | assert test_exception_fallback() == 'fallback' 20 | 21 | @suppress_all_exceptions() 22 | def test_exception_nofallback(): 23 | raise Exception 24 | assert not test_exception_nofallback() 25 | 26 | 27 | def test_hash256_result(): 28 | @hash256_result 29 | def test_valid(): 30 | return "test" 31 | expect_hash = hashlib.sha256("test".encode('utf-8')) 32 | assert str(expect_hash.hexdigest()) == test_valid() 33 | 34 | @hash256_result 35 | def test_none(): 36 | return None 37 | with pytest.raises(ValueError): 38 | test_none() 39 | 40 | @hash256_result 41 | def test_nostring(): 42 | return 0 43 | with pytest.raises(ValueError): 44 | test_nostring() 45 | -------------------------------------------------------------------------------- /tests/test_iotedgedev_solution_init.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import uuid 4 | from unittest import mock 5 | from .utility import (runner_invoke) 6 | 7 | 8 | def test_solution_init_without_name(): 9 | result = runner_invoke(['solution', 'init'], True) 10 | 11 | assert "Directory is not empty" in result.output 12 | 13 | 14 | def test_solution_init_with_invalid_name_non_empty_dir(): 15 | dirname = f'test-{uuid.uuid4()}' 16 | os.makedirs(f'{dirname}/empty_dir') 17 | 18 | result = runner_invoke(['solution', 'init', dirname], True) 19 | 20 | assert "Directory is not empty" in result.output 21 | shutil.rmtree(dirname, ignore_errors=True) 22 | 23 | 24 | def test_solution_init_with_valid_name(): 25 | dirname = f'test-{uuid.uuid4()}' 26 | 27 | # Mock calls to additional commands, to avoid triggering user prompts 28 | with mock.patch('iotedgedev.utility.Utility.call_proc') as mock_call_proc: 29 | result = runner_invoke(['solution', 'init', dirname], True) 30 | 31 | assert 'AZURE IOT EDGE SOLUTION CREATED' in result.output 32 | mock_call_proc.assert_called_with(["iotedgedev", "iothub", "setup", "--update-dotenv"]) 33 | assert mock_call_proc.call_count == 2 34 | shutil.rmtree(dirname, ignore_errors=True) 35 | -------------------------------------------------------------------------------- /tests/test_iotedgedev_solution_tag.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from .utility import ( 4 | runner_invoke, 5 | ) 6 | from iotedgedev.azurecli import AzureCli 7 | from iotedgedev.envvars import EnvVars 8 | from iotedgedev.output import Output 9 | from iotedgedev.connectionstring import DeviceConnectionString 10 | from unittest import mock 11 | 12 | pytestmark = pytest.mark.e2e 13 | 14 | output = Output() 15 | envvars = EnvVars(output) 16 | test_solution_shared_lib_dir = os.path.join(os.getcwd(), "tests", "assets", "test_solution_shared_lib") 17 | 18 | 19 | # Test that cmd line tags (--tags) overrides DEVICE_TAGS from .env 20 | @ mock.patch.dict(os.environ, {"DEVICE_TAGS": "invalid_target"}) 21 | def test_add_tags(): 22 | # Arrange 23 | os.chdir(test_solution_shared_lib_dir) 24 | 25 | # Act 26 | result = runner_invoke(['solution', 'tag', '--tags', '{"environment":"dev","building":"9"}']) 27 | 28 | # Assert 29 | assert 'TAG UPDATE COMPLETE' in result.output 30 | assert '{"environment":"dev","building":"9"}' in result.output 31 | assert 'ERROR' not in result.output 32 | 33 | # Cleanup 34 | azure_cli = AzureCli(output, envvars) 35 | 36 | assert azure_cli.invoke_az_cli_outproc(["iot", "hub", "device-twin", "replace", "-d", DeviceConnectionString(envvars.get_envvar("DEVICE_CONNECTION_STRING")).device_id, 37 | "-l", envvars.get_envvar("IOTHUB_CONNECTION_STRING"), "--json", "{}"]) 38 | 39 | 40 | @pytest.mark.parametrize( 41 | "tags", 42 | [ 43 | "tags.environment='dev'", 44 | "dev" 45 | ] 46 | ) 47 | def test_add_invalid_tag(tags): 48 | # Arrange 49 | os.chdir(test_solution_shared_lib_dir) 50 | 51 | # Act 52 | result = runner_invoke(['solution', 'tag', '--tags', tags]) 53 | 54 | # Assert 55 | assert f"ERROR: Failed to add tag: '{tags}' to device" in result.output 56 | 57 | 58 | def test_error_missing_tag(): 59 | # Arrange 60 | os.chdir(test_solution_shared_lib_dir) 61 | 62 | # Act 63 | with pytest.raises(Exception) as context: 64 | runner_invoke(['solution', 'tag', '--tags']) 65 | 66 | # Assert 67 | assert "Error: Option '--tags' requires an argument." in str(context) 68 | 69 | 70 | @ mock.patch.dict(os.environ, {"DEVICE_TAGS": '{"environment":"dev","building":"9"}'}) 71 | def test_default_tag_from_env(): 72 | # Arrange 73 | os.chdir(test_solution_shared_lib_dir) 74 | 75 | # Act 76 | result = runner_invoke(['solution', 'tag']) 77 | 78 | # Assert 79 | assert 'TAG UPDATE COMPLETE' in result.output 80 | assert '{"environment":"dev","building":"9"}' in result.output 81 | assert 'ERROR' not in result.output 82 | 83 | # Cleanup 84 | azure_cli = AzureCli(output, envvars) 85 | 86 | assert azure_cli.invoke_az_cli_outproc(["iot", "hub", "device-twin", "replace", "-d", DeviceConnectionString(envvars.get_envvar("DEVICE_CONNECTION_STRING")).device_id, 87 | "-l", envvars.get_envvar("IOTHUB_CONNECTION_STRING"), "--json", "{}"]) 88 | 89 | 90 | def test_missing_default_tag_from_env(): 91 | # Arrange 92 | os.chdir(test_solution_shared_lib_dir) 93 | 94 | # Act 95 | with pytest.raises(Exception) as context: 96 | runner_invoke(['solution', 'tag']) 97 | 98 | # Assert 99 | assert "ERROR: Environment Variable DEVICE_TAGS not set. Either add to .env file or to your system's Environment Variables" in str(context) 100 | -------------------------------------------------------------------------------- /tests/utility.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | import subprocess 4 | import sys 5 | 6 | import click 7 | from click.testing import CliRunner 8 | from iotedgedev.dockercls import Docker 9 | from iotedgedev.envvars import EnvVars 10 | from iotedgedev.output import Output 11 | from iotedgedev.utility import Utility 12 | 13 | output = Output() 14 | envvars = EnvVars(output) 15 | 16 | 17 | def assert_list_equal(list1, list2): 18 | assert len(list1) == len(list2) and sorted(list1) == sorted(list2) 19 | 20 | 21 | def assert_file_equal(file1, file2): 22 | with open(file1, "r") as f1: 23 | with open(file2, "r") as f2: 24 | assert f1.read() == f2.read() 25 | 26 | 27 | def assert_json_file_equal(file1, file2): 28 | with open(file1, "r") as f1: 29 | with open(file2, "r") as f2: 30 | assert json.load(f1) == json.load(f2) 31 | 32 | 33 | def get_docker_client(): 34 | envvars.load(force=True) 35 | utility = Utility(envvars, output) 36 | docker_client = Docker(envvars, utility, output) 37 | docker_client.init_registry() 38 | return docker_client 39 | 40 | 41 | def get_docker_os_type(): 42 | os_type = get_docker_client().get_os_type().lower() 43 | return os_type 44 | 45 | 46 | def get_platform_type(): 47 | if get_docker_os_type() == 'windows': 48 | platform_type = 'windows-amd64' 49 | else: 50 | platform_type = 'amd64' 51 | return platform_type 52 | 53 | 54 | def get_all_docker_containers(): 55 | output = start_process(['docker', 'ps', '-a'], False) 56 | return output 57 | 58 | 59 | def get_all_docker_images(): 60 | output = start_process(['docker', 'image', 'ls'], False) 61 | return output 62 | 63 | 64 | def prune_docker_images(): 65 | output = start_process(['docker', 'image', 'prune', '-f'], False) 66 | return output 67 | 68 | 69 | def prune_docker_containers(): 70 | output = start_process(['docker', 'container', 'prune', '-f'], False) 71 | return output 72 | 73 | 74 | def prune_docker_build_cache(): 75 | output = start_process(['docker', 'builder', 'prune', '-f'], False) 76 | return output 77 | 78 | 79 | def remove_docker_container(container_name): 80 | output = start_process(['docker', 'rm', '-f', container_name], False) 81 | return output 82 | 83 | 84 | def remove_docker_image(image_name): 85 | output = start_process(['docker', 'rmi', '-f', image_name], False) 86 | return output 87 | 88 | 89 | def runner_invoke(args, expect_failure=False): 90 | runner = CliRunner() 91 | with runner.isolation(env={"DEFAULT_PLATFORM": get_platform_type()}): 92 | iotedgedev_import = "iotedgedev.cli" 93 | cli = __import__(iotedgedev_import, fromlist=['main']) 94 | # Remove "iotedgedev.cli" import from cache, to prevent variables being saved across tests 95 | del sys.modules[iotedgedev_import] 96 | result = runner.invoke(cli.main, args) 97 | if (result.exit_code == 0) or (expect_failure is True): 98 | return result 99 | else: 100 | raise Exception(result.stdout) 101 | 102 | 103 | def start_process(command, is_shell): 104 | process = subprocess.Popen(command, shell=is_shell, 105 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 106 | output, error = process.communicate() 107 | if process.returncode == 0: 108 | return str(output) 109 | else: 110 | raise Exception(error) 111 | 112 | 113 | def update_file_content(file_path, actual_value, expected_value): 114 | with open(file_path, "r+") as f: 115 | stream_data = f.read() 116 | ret = re.sub(actual_value, expected_value, stream_data) 117 | f.seek(0) 118 | f.truncate() 119 | f.write(ret) 120 | 121 | 122 | def get_file_content(file_path): 123 | with open(file_path, "r+") as f: 124 | content = f.read() 125 | 126 | return content 127 | 128 | 129 | def get_cli_command_structure(obj): 130 | if isinstance(obj, click.Group): 131 | return {name: get_cli_command_structure(value) 132 | for name, value in obj.commands.items()} 133 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, py37, py38, py39 3 | 4 | #[travis] 5 | #python = 6 | # 3.6: py36 7 | 8 | #[testenv:flake8] 9 | #basepython=python 10 | #deps=flake8 11 | #commands=flake8 iotedgedev 12 | 13 | [testenv] 14 | deps = -rrequirements_dev.txt 15 | #setenv = PIP_EXTRA_INDEX_URL=https://test.pypi.org/simple/ 16 | commands = pytest -s -v {posargs} 17 | #setenv = 18 | # PYTHONPATH = {toxinidir} 19 | 20 | #commands = 21 | # python setup.py test 22 | 23 | passenv = * 24 | 25 | ; If you want to make tox run the tests with the same versions, create a 26 | ; requirements.txt with the pinned versions and uncomment the following lines: 27 | ; deps = 28 | ; -r{toxinidir}/requirements.txt 29 | 30 | -------------------------------------------------------------------------------- /vsts_ci/.vsts-ci.yml: -------------------------------------------------------------------------------- 1 | # Python package 2 | # Create and test a Python package on multiple Python versions. 3 | # Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more: 4 | # https://docs.microsoft.com/vsts/pipelines/languages/python 5 | pr: 6 | - main 7 | 8 | trigger: none 9 | 10 | jobs: 11 | - job: Windows 12 | pool: 13 | vmImage: windows-2019 14 | strategy: 15 | matrix: 16 | Python36: 17 | python.version: "3.6" 18 | TOXENV: "py36" 19 | Python37: 20 | python.version: "3.7" 21 | TOXENV: "py37" 22 | Python38: 23 | python.version: "3.8" 24 | TOXENV: "py38" 25 | Python39: 26 | python.version: "3.9" 27 | TOXENV: "py39" 28 | maxParallel: 1 29 | steps: 30 | - template: win32/continuous-build-win32.yml 31 | 32 | # - job: MacOS 33 | # pool: 34 | # vmImage: macOS-10.13 35 | # strategy: 36 | # matrix: 37 | # Python36: 38 | # python.version: "3.6" 39 | # TOXENV: "py36" 40 | # Python37: 41 | # python.version: "3.7" 42 | # TOXENV: "py37" 43 | # Python38: 44 | # python.version: "3.8" 45 | # TOXENV: "py38" 46 | # Python39: 47 | # python.version: "3.9" 48 | # TOXENV: "py39" 49 | # maxParallel: 1 50 | # steps: 51 | # - template: darwin/continuous-build-darwin.yml 52 | 53 | - job: Ubuntu20 54 | pool: 55 | vmImage: ubuntu-20.04 56 | strategy: 57 | matrix: 58 | Python36: 59 | python.version: "3.6" 60 | TOXENV: "py36" 61 | Python37: 62 | python.version: "3.7" 63 | TOXENV: "py37" 64 | Python38: 65 | python.version: "3.8" 66 | TOXENV: "py38" 67 | Python39: 68 | python.version: "3.9" 69 | TOXENV: "py39" 70 | maxParallel: 1 71 | steps: 72 | - template: linux/continuous-build-linux.yml 73 | -------------------------------------------------------------------------------- /vsts_ci/darwin/continuous-build-darwin.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - powershell: | 3 | ((Get-Content -path "$(BUILD.REPOSITORY.LOCALPATH)\.env.tmp" -Raw) -replace 'IOTHUB_CONNECTION_STRING=".*"','IOTHUB_CONNECTION_STRING="$(IOTHUB_CONNECTION_STRING)"' -replace 'DEVICE_CONNECTION_STRING=".*"','DEVICE_CONNECTION_STRING="$(DEVICE_CONNECTION_STRING)"' -replace 'CONTAINER_REGISTRY_SERVER=".*"','CONTAINER_REGISTRY_SERVER="$(CONTAINER_REGISTRY_SERVER)"' -replace 'CONTAINER_REGISTRY_USERNAME=".*"','CONTAINER_REGISTRY_USERNAME="$(CONTAINER_REGISTRY_USERNAME)"' -replace 'CONTAINER_REGISTRY_PASSWORD=".*"','CONTAINER_REGISTRY_PASSWORD="$(CONTAINER_REGISTRY_PASSWORD)"') | Set-Content -Path "$(BUILD.REPOSITORY.LOCALPATH)\.env.tmp" 4 | displayName: "Update .env.tmp file" 5 | 6 | - task: UsePythonVersion@0 7 | inputs: 8 | versionSpec: "$(python.version)" 9 | addToPath: true 10 | architecture: "x64" 11 | 12 | - task: NodeTool@0 13 | displayName: "Use Node 8.x" 14 | inputs: 15 | versionSpec: 8.x 16 | checkLatest: true 17 | 18 | - script: | 19 | node --version 20 | npm --version 21 | npm i -g iothub-explorer 22 | iothub-explorer --version 23 | displayName: "Install IoT Hub Explorer" 24 | 25 | - powershell: | 26 | npm install -g yo 27 | npm i -g yo generator-azure-iot-edge-module 28 | yo --version 29 | displayName: "Install Yeoman and Azure IoT Edge Node.js module generator packages" 30 | 31 | - script: | 32 | pip install --upgrade pip 33 | pip install --upgrade tox 34 | displayName: "Update and install required tools" 35 | 36 | - script: | 37 | brew install docker 38 | brew install docker-machine 39 | brew link --overwrite docker-machine 40 | brew unlink docker-machine-driver-xhyve 41 | brew install docker-machine-driver-xhyve 42 | sudo chown root:wheel $(brew --prefix)/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve 43 | sudo chmod u+s $(brew --prefix)/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve 44 | mkdir -p /Users/vsts/.docker/machine/cache 45 | curl -Lo /Users/vsts/.docker/machine/cache/boot2docker.iso https://github.com/boot2docker/boot2docker/releases/download/v18.06.1-ce/boot2docker.iso 46 | docker-machine create default --driver xhyve --xhyve-boot2docker-url /Users/vsts/.docker/machine/cache/boot2docker.iso 47 | docker-machine env default 48 | eval $(docker-machine env default) 49 | brew services start docker-machine 50 | brew install docker-compose 51 | sleep 10 52 | docker version 53 | sudo -E tox -e "$(TOXENV)" 54 | displayName: "Install docker and run tests against iotedgedev source code with tox" 55 | -------------------------------------------------------------------------------- /vsts_ci/linux/continuous-build-linux.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - powershell: | 3 | ((Get-Content -path "$(BUILD.REPOSITORY.LOCALPATH)\.env.tmp" -Raw) -replace 'IOTHUB_CONNECTION_STRING=".*"','IOTHUB_CONNECTION_STRING="$(IOTHUB_CONNECTION_STRING)"' -replace 'DEVICE_CONNECTION_STRING=".*"','DEVICE_CONNECTION_STRING="$(DEVICE_CONNECTION_STRING)"' -replace 'CONTAINER_REGISTRY_SERVER=".*"','CONTAINER_REGISTRY_SERVER="$(CONTAINER_REGISTRY_SERVER)"' -replace 'CONTAINER_REGISTRY_USERNAME=".*"','CONTAINER_REGISTRY_USERNAME="$(CONTAINER_REGISTRY_USERNAME)"' -replace 'CONTAINER_REGISTRY_PASSWORD=".*"','CONTAINER_REGISTRY_PASSWORD="$(CONTAINER_REGISTRY_PASSWORD)"') | Set-Content -Path "$(BUILD.REPOSITORY.LOCALPATH)\.env.tmp" 4 | displayName: "Update .env.tmp file" 5 | 6 | - task: UsePythonVersion@0 7 | inputs: 8 | versionSpec: "$(python.version)" 9 | addToPath: true 10 | architecture: "x64" 11 | 12 | - powershell: | 13 | sudo npm install -g yo 14 | sudo npm i -g yo generator-azure-iot-edge-module 15 | displayName: "Install Yeoman and Azure IoT Edge Node.js module generator packages" 16 | 17 | - powershell: | 18 | pip install --upgrade pip 19 | pip install --upgrade tox 20 | sudo npm i -g iothub-explorer 21 | az --version 22 | az extension add --name azure-iot 23 | displayName: "Update and install required tools" 24 | 25 | - script: | 26 | mvn -v 27 | sudo -E `which tox` -e "$(TOXENV)" 28 | displayName: "Run test" 29 | -------------------------------------------------------------------------------- /vsts_ci/win32/continuous-build-win32.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - pwsh: | 3 | ((Get-Content -path "$(BUILD.REPOSITORY.LOCALPATH)\.env.tmp" -Raw) -replace 'IOTHUB_CONNECTION_STRING=".*"','IOTHUB_CONNECTION_STRING="$(IOTHUB_CONNECTION_STRING)"' -replace 'DEVICE_CONNECTION_STRING=".*"','DEVICE_CONNECTION_STRING="$(DEVICE_CONNECTION_STRING)"' -replace 'CONTAINER_REGISTRY_SERVER=".*"','CONTAINER_REGISTRY_SERVER="$(CONTAINER_REGISTRY_SERVER)"' -replace 'CONTAINER_REGISTRY_USERNAME=".*"','CONTAINER_REGISTRY_USERNAME="$(CONTAINER_REGISTRY_USERNAME)"' -replace 'CONTAINER_REGISTRY_PASSWORD=".*"','CONTAINER_REGISTRY_PASSWORD="$(CONTAINER_REGISTRY_PASSWORD)"') | Set-Content -Path "$(BUILD.REPOSITORY.LOCALPATH)\.env.tmp" 4 | displayName: "Update .env.tmp file" 5 | 6 | - task: UsePythonVersion@0 7 | inputs: 8 | versionSpec: "$(python.version)" 9 | addToPath: true 10 | architecture: "x64" 11 | 12 | - pwsh: | 13 | npm i -g iothub-explorer yo generator-azure-iot-edge-module 14 | az --version 15 | az extension add --name azure-iot 16 | displayName: "Install IoT Hub explorer, Yeoman and Azure IoT Edge Node.js module generator packages" 17 | 18 | - pwsh: | 19 | mkdir C:\registry 20 | docker run -d -p 5000:5000 --restart=always --name registry -v C:\registry:C:\registry stefanscherer/registry-windows:2.6.2 21 | displayName: "Pull and run local registry containers" 22 | 23 | - pwsh: | 24 | pip install tox 25 | tox -e "$(TOXENV)" 26 | displayName: "Run tests against iotedgedev source code" 27 | --------------------------------------------------------------------------------