├── .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] "
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 |
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 |
25 | Fixes #
26 |
27 |
30 |
31 | ### Additional information
32 |
33 |
--------------------------------------------------------------------------------
/.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//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 | [](https://badge.fury.io/py/iotedgedev)
4 | [](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 | .
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 |
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 |
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 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 |
8 |
9 | Release
10 | AnyCPU
11 |
12 |
13 |
14 | 80bd4064-a00f-4afd-bed3-f9ac609663d1
15 |
16 |
17 | Deployment
18 | 1.0
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | False
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/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 []"
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 | [](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 |
13 | More information
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 |
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 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
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 | ///
32 | /// Handles cleanup operations when app is cancelled or unloads
33 | ///
34 | public static Task WhenCancelled(CancellationToken cancellationToken)
35 | {
36 | var tcs = new TaskCompletionSource();
37 | cancellationToken.Register(s => ((TaskCompletionSource)s).SetResult(true), tcs);
38 | return tcs.Task;
39 | }
40 |
41 | ///
42 | /// Initializes the ModuleClient and sets up the callback to receive
43 | /// messages containing temperature information
44 | ///
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 | ///
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 | ///
64 | static async Task 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 |
2 |
3 | Exe
4 | netcoreapp2.1
5 |
6 |
7 |
8 | True
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
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 | ///
32 | /// Handles cleanup operations when app is cancelled or unloads
33 | ///
34 | public static Task WhenCancelled(CancellationToken cancellationToken)
35 | {
36 | var tcs = new TaskCompletionSource();
37 | cancellationToken.Register(s => ((TaskCompletionSource)s).SetResult(true), tcs);
38 | return tcs.Task;
39 | }
40 |
41 | ///
42 | /// Initializes the ModuleClient and sets up the callback to receive
43 | /// messages containing temperature information
44 | ///
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 | ///
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 | ///
64 | static async Task 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 |
2 |
3 | Exe
4 | netcoreapp2.1
5 |
6 |
7 |
8 | True
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
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 |
--------------------------------------------------------------------------------