├── .github └── workflows │ └── flowzone.yml ├── .gitignore ├── .versionbot └── CHANGELOG.yml ├── CHANGELOG.md ├── Dockerfile.aarch64 ├── Dockerfile.template ├── README.md ├── VERSION ├── autowire.py ├── balena.yml ├── docker-compose.yml ├── download.sh ├── entry.sh ├── helper_methods.py ├── logo.png ├── plugins ├── __pycache__ │ ├── applicationInsightsOutput.cpython-36.pyc │ ├── applicationInsightsOutputPlugin.cpython-36.pyc │ ├── balena_influx.cpython-36.pyc │ ├── balena_input.cpython-36.pyc │ ├── deviceMetricsInput.cpython-36.pyc │ ├── externalHttpListener.cpython-36.pyc │ ├── externalHttpPullInput.cpython-36.pyc │ ├── httpOutput.cpython-36.pyc │ ├── httpPullInputPlugin.cpython-36.pyc │ ├── influxOutput.cpython-36.pyc │ ├── influxOutputPlugin.cpython-36.pyc │ ├── internalHttpPullInput.cpython-36.pyc │ ├── mqttInput.cpython-36.pyc │ ├── mqttInputPlugin.cpython-36.pyc │ └── ttnInputPlugin.cpython-36.pyc ├── applicationInsightsOutput.py ├── baseConfig.py ├── deviceMetricsInput.py ├── externalHttpListener.py ├── externalHttpPullInput.py ├── httpOutput.py ├── influx2Output.py ├── influxOutput.py ├── internalHttpPullInput.py ├── mqttInput.py └── mqttOutput.py ├── repo.yml └── requirements.txt /.github/workflows/flowzone.yml: -------------------------------------------------------------------------------- 1 | name: Flowzone 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, closed] 6 | branches: 7 | - "main" 8 | - "master" 9 | 10 | jobs: 11 | flowzone: 12 | name: Flowzone 13 | uses: product-os/flowzone/.github/workflows/flowzone.yml@master 14 | secrets: inherit 15 | with: 16 | balena_slugs: 'balenalabs/connector,balenalabs/connector-aarch64,balenalabs/connector-armv7hf,balenalabs/connector-amd64' 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/settings.json 2 | .vscode/launch.json 3 | autowire/plugins/__pycache__/balena_influx.cpython-36.pyc 4 | 5 | telegraf.conf 6 | plugins/__pycache__/applicationInsightsOutput.cpython-36.pyc 7 | plugins/__pycache__/baseConfig.cpython-36.pyc 8 | plugins/__pycache__/deviceMetricsInput.cpython-36.pyc 9 | plugins/__pycache__/externalHttpListener.cpython-36.pyc 10 | plugins/__pycache__/externalHttpPullInput.cpython-36.pyc 11 | plugins/__pycache__/httpOutput.cpython-36.pyc 12 | plugins/__pycache__/influxOutput.cpython-36.pyc 13 | plugins/__pycache__/internalHttpPullInput.cpython-36.pyc 14 | plugins/__pycache__/mqttInput.cpython-36.pyc 15 | Dockerfile.raspberrypi4-64 16 | plugins/__pycache__/applicationInsightsOutput.cpython-36.pyc 17 | plugins/externalHttpListener.py 18 | plugins/__pycache__/applicationInsightsOutput.cpython-36.pyc 19 | plugins/__pycache__/deviceMetricsInput.cpython-36.pyc 20 | plugins/__pycache__/externalHttpListener.cpython-36.pyc 21 | plugins/__pycache__/externalHttpPullInput.cpython-36.pyc 22 | plugins/__pycache__/httpOutput.cpython-36.pyc 23 | plugins/__pycache__/influxOutput.cpython-36.pyc 24 | plugins/__pycache__/internalHttpPullInput.cpython-36.pyc 25 | plugins/__pycache__/mqttInput.cpython-36.pyc 26 | Dockerfile.raspberrypi4-64 27 | -------------------------------------------------------------------------------- /.versionbot/CHANGELOG.yml: -------------------------------------------------------------------------------- 1 | - commits: 2 | - subject: Pin aarch64 base images to match exactly 3 | hash: 84beddcdf938a6aafd059ed2e00c396fc9b7566c 4 | body: > 5 | Match both build stages to same Python version to avoid 6 | `ModuleNotFoundError: No module named 'pluginbase'` errors. Use base 7 | image version that exists for build and run. 8 | footer: 9 | Change-type: patch 10 | change-type: patch 11 | Signed-off-by: Alan Boris 12 | signed-off-by: Alan Boris 13 | author: Alan Boris 14 | nested: [] 15 | - subject: Pin aarch64 base images more specifically 16 | hash: 9bdc1a879d4a4dbbb658ab936ba338cc6cd0f4b8 17 | body: > 18 | Match both build stages to same Python version to avoid 19 | `ModuleNotFoundError: No module named 'pluginbase'` errors. 20 | footer: 21 | Change-type: patch 22 | change-type: patch 23 | Signed-off-by: Alan Boris 24 | signed-off-by: Alan Boris 25 | author: Alan Boris 26 | nested: [] 27 | version: 1.1.8 28 | title: "" 29 | date: 2023-03-27T03:22:07.460Z 30 | - commits: 31 | - subject: Bump Python SDK version 32 | hash: 1eb0570dbaf10d1cb8889cf1f96759cd00865e80 33 | body: | 34 | Fixes issue with device.get_with_service_details() error. 35 | footer: 36 | Change-type: patch 37 | change-type: patch 38 | Signed-off-by: Alan Boris 39 | signed-off-by: Alan Boris 40 | author: Alan Boris 41 | nested: [] 42 | version: 1.1.7 43 | title: "" 44 | date: 2023-03-25T04:03:51.314Z 45 | - commits: 46 | - subject: moving from balenablocks to balena-labs-projects 47 | hash: efc2e0fdd27834d090fde5f9acf30386aa38b8a3 48 | body: "" 49 | footer: 50 | Signed-off-by: Flynn Joffray 51 | signed-off-by: Flynn Joffray 52 | Change-type: patch 53 | change-type: patch 54 | author: Flynn Joffray 55 | nested: [] 56 | version: 1.1.6 57 | title: "" 58 | date: 2022-11-16T01:20:41.075Z 59 | - version: 1.1.5 60 | date: 2022-04-07T10:09:17Z 61 | commits: 62 | - hash: 3283d8d52623339722cc867455880721d7042cf1 63 | author: Phil Wilson 64 | footers: 65 | change-type: patch 66 | signed-off-by: Phil Wilson 67 | subject: fix readme 68 | body: null 69 | - hash: ce70acdc69f8516ecf16983326199b255a7f2cca 70 | author: Phil Wilson 71 | footers: 72 | change-type: patch 73 | signed-off-by: Phil Wilson 74 | subject: build not pull 75 | body: null 76 | - hash: be705e7c821907cdbc3e68dba0718560a420b632 77 | author: Phil Wilson 78 | footers: 79 | change-type: patch 80 | signed-off-by: Phil Wilson 81 | subject: Update balenaSDK and dependencies 82 | body: null 83 | - hash: 3fae907f7000e7153ca0c6e3bfbc61d6e656cfa0 84 | author: Phil Wilson 85 | subject: Merge branch 'master' into fixbuild 86 | body: null 87 | - hash: 5e94fb05d3b4df00c8156684245cb5a6cb168418 88 | author: Phil Wilson 89 | footers: 90 | change-type: patch 91 | signed-off-by: Phil Wilson 92 | subject: Fix build for all arches 93 | body: null 94 | - hash: eef5b600696bdcf95c87e6a171f8dd68440eec60 95 | author: Phil Wilson 96 | subject: Merge branch 'fixbuild' of https://github.com/balenablocks/connector 97 | into fixbuild 98 | body: null 99 | - hash: e1ffee0ec3643cdeec6617ca998ed9251c111659 100 | author: Phil Wilson 101 | footers: 102 | change-type: patch 103 | signed-off-by: Phil Wilson 104 | subject: fix aarch64 rust issue 105 | body: null 106 | - version: 1.1.4 107 | - date: 2022-04-05T10:15:05Z 108 | - commits: 109 | - hash: 933a11715681a0b1bb0ccba3a3db2ea85159e20b 110 | - commits: 111 | - author: Phil Wilson 112 | - commits: 113 | - footers: 114 | change-type: patch 115 | - commits: 116 | - footers: 117 | signed-off-by: Phil Wilson 118 | - commits: 119 | - subject: Move to GHAction 120 | - commits: 121 | - body: null 122 | - commits: 123 | - hash: b499d2f859c0bfa5a61fdc2178ac0f8d10a2da8d 124 | - commits: 125 | - author: Phil Wilson 126 | - commits: 127 | - footers: 128 | change-type: patch 129 | - commits: 130 | - footers: 131 | signed-off-by: Phil Wilson 132 | - commits: 133 | - subject: fix action 134 | - commits: 135 | - body: null 136 | - commits: 137 | - hash: a96e3fb79e5c6da15800a4e0cdd6adc1d534927d 138 | - commits: 139 | - author: Phil Wilson 140 | - commits: 141 | - subject: Create balena.yml 142 | - commits: 143 | - body: null 144 | - version: 1.1.3 145 | - date: 2022-03-25T14:09:47Z 146 | - commits: 147 | - hash: 134d9c81ff52e52e87b01a71b7056cd9908906c7 148 | - commits: 149 | - author: Bill Love 150 | - commits: 151 | - footers: 152 | change-type: patch 153 | - commits: 154 | - subject: Allow x86_64 version 155 | - commits: 156 | - body: Bumped version of telegraf to 1.22.0 and added x86_64 option. 157 | - commits: 158 | - hash: 48c18d9e12ed28c9e335afd232b260479c308d4e 159 | - commits: 160 | - author: Bill Love 161 | - commits: 162 | - subject: Update download.sh 163 | - commits: 164 | - body: null 165 | - version: 1.1.2 166 | - date: 2021-11-11T18:27:52Z 167 | - commits: 168 | - hash: 3b6be106432c642787b3ddc299842cfa788b3533 169 | - commits: 170 | - author: Phil Wilson 171 | - commits: 172 | - footers: 173 | change-type: patch 174 | - commits: 175 | - footers: 176 | signed-off-by: Phil Wilson 177 | - commits: 178 | - subject: fix python image version 179 | - commits: 180 | - body: null 181 | - version: 1.1.1 182 | - date: 2021-11-08T18:27:40Z 183 | - commits: 184 | - hash: 92b97ed47711a98f4a182d7a2a7a9d7f518ae7e1 185 | - commits: 186 | - author: Phil Wilson 187 | - commits: 188 | - footers: 189 | change-type: patch 190 | - commits: 191 | - footers: 192 | signed-off-by: Phil Wilson 193 | - commits: 194 | - subject: fix pi4 image 195 | - commits: 196 | - body: null 197 | - version: 1.1.0 198 | - date: 2021-11-02T09:29:01Z 199 | - commits: 200 | - hash: 4af37361f33652f9fa5037afede2a97a4540d068 201 | - commits: 202 | - author: Nutchanon Ninyawee 203 | - commits: 204 | - footers: 205 | change-type: minor 206 | - commits: 207 | - footers: 208 | signed-off-by: Nutchanon Ninyawee 246 | - commits: 247 | - subject: Fix bugs and image build failure 248 | - commits: 249 | - body: null 250 | - version: 1.0.0 251 | - date: 2021-10-21T14:00:05Z 252 | - commits: 253 | - hash: 78c121d7ff994cac2330d44713fb1557a6632a79 254 | - commits: 255 | - author: Phil Wilson 256 | - commits: 257 | - subject: code cleanup 258 | - commits: 259 | - body: null 260 | - commits: 261 | - hash: 3bf12efef14cde3561340188e3290565e6ef701d 262 | - commits: 263 | - author: Phil Wilson 264 | - commits: 265 | - subject: code cleanup 266 | - commits: 267 | - body: null 268 | - commits: 269 | - hash: 443af46b009595c9b71b2be402c7da0a35d63973 270 | - commits: 271 | - author: Phil Wilson 272 | - commits: 273 | - subject: Merge branch 'code-cleanup' of 274 | https://github.com/balenablocks/connector into code-cleanup 275 | - commits: 276 | - body: null 277 | - commits: 278 | - hash: 5c60014f5ce7b90c560a847fe08723cb6c021e94 279 | - commits: 280 | - author: Phil Wilson 281 | - commits: 282 | - footers: 283 | change-type: major 284 | - commits: 285 | - footers: 286 | signed-off-by: Phil Wilson 287 | - commits: 288 | - subject: code cleanup 289 | - commits: 290 | - body: null 291 | - version: 0.1.0 292 | - date: 2021-10-20T08:28:20Z 293 | - commits: 294 | - hash: 0a2810ad65401a93b33fbd7d692f41b38f308f9b 295 | - commits: 296 | - author: Tomás Migone 297 | - commits: 298 | - footers: 299 | connects-to: "#5" 300 | - commits: 301 | - footers: 302 | closes: "#22, #37" 303 | - commits: 304 | - footers: 305 | change-type: minor 306 | - commits: 307 | - footers: 308 | signed-off-by: Tomás Migone 309 | - commits: 310 | - subject: Add support for local mode 311 | - commits: 312 | - body: null 313 | - commits: 314 | - hash: 2e5bef8e10ceb3eda207586d67b7e5c1af993ad6 315 | - commits: 316 | - author: Tomás Migone 317 | - commits: 318 | - footers: 319 | connects-to: "#5" 320 | - commits: 321 | - footers: 322 | closes: "#22, #37" 323 | - commits: 324 | - footers: 325 | change-type: minor 326 | - commits: 327 | - footers: 328 | signed-off-by: Tomás Migone 329 | - commits: 330 | - subject: Add support for local mode 331 | - commits: 332 | - body: null 333 | - commits: 334 | - hash: fe404d98cbfaf021f12fa1a160b0ebe151d8e6b5 335 | - commits: 336 | - author: Tomás Migone 337 | - commits: 338 | - footers: 339 | change-type: patch 340 | - commits: 341 | - footers: 342 | signed-off-by: Tomás Migone 343 | - commits: 344 | - subject: Update README.md 345 | - commits: 346 | - body: null 347 | - commits: 348 | - hash: ce2408be0ebf1be3f74ee0d0e50ca8177e81c831 349 | - commits: 350 | - author: Tomás Migone 351 | - commits: 352 | - footers: 353 | change-type: patch 354 | - commits: 355 | - footers: 356 | signed-off-by: Tomás Migone 357 | - commits: 358 | - subject: Update README.md 359 | - commits: 360 | - body: null 361 | - commits: 362 | - hash: 6b795c046d156221635f9992dbc6be6b410c9cef 363 | - commits: 364 | - author: Phil Wilson 365 | - commits: 366 | - subject: Merge branch 'support-local-mode' of 367 | https://github.com/balenablocks/connector into support-local-mode 368 | - commits: 369 | - body: null 370 | - commits: 371 | - hash: cf8d969c48b064a2f8b054292f6577d8146cafe0 372 | - commits: 373 | - author: Nutchanon Ninyawee 374 | - commits: 375 | - subject: "fix telegraf: not found" 376 | - commits: 377 | - body: "/app/entry.sh: line 27: exec: telegraf: not found" 378 | - version: 0.0.5 379 | - date: 2021-06-15T13:15:00Z 380 | - commits: 381 | - hash: b4e3b6b5e01a1dce3616e3b740e1849851b4f673 382 | - commits: 383 | - author: Phil Wilson 384 | - commits: 385 | - footers: 386 | change-type: patch 387 | - commits: 388 | - footers: 389 | signed-off-by: Phil Wilson 390 | - commits: 391 | - subject: Add MQTT strings setting 392 | - commits: 393 | - body: null 394 | - version: 0.0.4 395 | - date: 2021-05-11T09:39:23Z 396 | - commits: 397 | - hash: 8dcb9a2a90b39823776d5cf9360c0dd82583e8d0 398 | - commits: 399 | - author: Phil Wilson 400 | - commits: 401 | - footers: 402 | change-type: patch 403 | - commits: 404 | - footers: 405 | signed-off-by: Phil Wilson 406 | - commits: 407 | - subject: Add manifest, bump telegraf version 408 | - commits: 409 | - body: null 410 | - commits: 411 | - hash: b3a61717fc9979a5e28795c2bbb775fca481eff2 412 | - commits: 413 | - author: Phil Wilson 414 | - commits: 415 | - footers: 416 | change-type: patch 417 | - commits: 418 | - footers: 419 | signed-off-by: Phil Wilson 420 | - commits: 421 | - subject: readme 422 | - commits: 423 | - body: null 424 | - version: 0.0.3 425 | - date: 2021-03-01T14:01:11Z 426 | - commits: 427 | - hash: 1d43be34da61dd8c2278f60b5df6d0afc43f1fc5 428 | - commits: 429 | - author: Tomás Migone 430 | - commits: 431 | - footers: 432 | change-type: patch 433 | - commits: 434 | - footers: 435 | signed-off-by: Tomás Migone 436 | - commits: 437 | - subject: "meta: run versionist to update balena.yml version" 438 | - commits: 439 | - body: null 440 | - version: 0.0.2 441 | - date: 2021-02-25T12:43:58Z 442 | - commits: 443 | - hash: f20f5fa4ac2b7f429a63bfdd11e95c3685b6624c 444 | - commits: 445 | - author: Tomás Migone 446 | - commits: 447 | - footers: 448 | change-type: patch 449 | - commits: 450 | - footers: 451 | signed-off-by: Tomás Migone 452 | - commits: 453 | - subject: "meta: add versionist" 454 | - commits: 455 | - body: null 456 | - commits: 457 | - hash: 3ab00a8f6ba9c3724f294abc970dd24c98cdba4f 458 | - commits: 459 | - author: Phil Wilson 460 | - commits: 461 | - footers: 462 | change-type: patch 463 | - commits: 464 | - footers: 465 | signed-off-by: Phil Wilson 466 | - commits: 467 | - subject: hub meta 468 | - commits: 469 | - body: null 470 | - commits: 471 | - hash: e10e419b0423dface57637251dc563792129fe70 472 | - commits: 473 | - author: Phil Wilson 474 | - commits: 475 | - footers: 476 | change-type: patch 477 | - commits: 478 | - footers: 479 | signed-off-by: Phil Wilson 480 | - commits: 481 | - subject: MQTT output plugin 482 | - commits: 483 | - body: null 484 | - commits: 485 | - hash: cd83c50f6ce5d5b329ba8b45b20ea42faa754e0f 486 | - commits: 487 | - author: Phil Wilson 488 | - commits: 489 | - footers: 490 | change-type: patch 491 | - commits: 492 | - footers: 493 | signed-off-by: Phil Wilson 494 | - commits: 495 | - subject: fix 496 | - commits: 497 | - body: null 498 | - commits: 499 | - hash: 61fd47bf856d592eb3f04534de9646d6ad720673 500 | - commits: 501 | - author: Phil Wilson 502 | - commits: 503 | - footers: 504 | change-type: patch 505 | - commits: 506 | - footers: 507 | signed-off-by: Phil Wilson 508 | - commits: 509 | - subject: Added topic prefix 510 | - commits: 511 | - body: null 512 | - commits: 513 | - hash: fa801194ed75e25efaeb564f323a384a4e33ca98 514 | - commits: 515 | - author: Chris Crocker-White 516 | - commits: 517 | - footers: 518 | change-type: patch 519 | - commits: 520 | - footers: 521 | signed-off-by: Chris Crocker-White 522 | - commits: 523 | - subject: Handle empty env var, ordering, multi URLs 524 | - commits: 525 | - body: "Fixes #10" 526 | - commits: 527 | - hash: e11d274062b160b744382ceec7e3be2759ac6ae7 528 | - commits: 529 | - author: Phil 530 | - commits: 531 | - subject: Add json_string_fields functionality 532 | - commits: 533 | - body: null 534 | - commits: 535 | - hash: 17c7b7d4f99dd0d1b4e69e3365f5d3bb4429ad95 536 | - commits: 537 | - author: Chris Crocker-White 538 | - commits: 539 | - footers: 540 | change-type: patch 541 | - commits: 542 | - footers: 543 | signed-off-by: Chris Crocker-White 544 | - commits: 545 | - subject: "Add passthrough for ext. HTTP headers fixes #14" 546 | - commits: 547 | - body: null 548 | - commits: 549 | - hash: b281add1554c30ccde699bdbd7adf4ce0afdd6fe 550 | - commits: 551 | - author: Phil 552 | - commits: 553 | - subject: Add HTTP timeout config 554 | - commits: 555 | - body: null 556 | - commits: 557 | - hash: c5d2de434e5425ad586031911b56564ec5a0c815 558 | - commits: 559 | - author: Phil 560 | - commits: 561 | - subject: Fix MQTT data sources 562 | - commits: 563 | - body: null 564 | - commits: 565 | - hash: 2d4c77277a77505038bd375a1f4dd8e14fbb217f 566 | - commits: 567 | - author: Phil 568 | - commits: 569 | - subject: Debug function 570 | - commits: 571 | - body: null 572 | - commits: 573 | - hash: 2b9361ddf313128caa6a0bd86cb177b897fcd2f5 574 | - commits: 575 | - author: Phil 576 | - commits: 577 | - subject: Fixes for bugs raised during initial testing 578 | - commits: 579 | - body: null 580 | - commits: 581 | - hash: a8fb6f75947a4b624121b1e95c9c6f7c15dd7e3f 582 | - commits: 583 | - author: Phil 584 | - commits: 585 | - subject: Pull data source configuration 586 | - commits: 587 | - body: null 588 | - commits: 589 | - hash: 03eba82f66828183b4ee64e2f66ca30ce1c03392 590 | - commits: 591 | - author: Phil Wilson 592 | - commits: 593 | - subject: Merge branch 'master' into PoC 594 | - commits: 595 | - body: null 596 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file 4 | automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY! 5 | This project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | # v1.1.8 8 | ## (2023-03-27) 9 | 10 | * Pin aarch64 base images to match exactly [Alan Boris] 11 | * Pin aarch64 base images more specifically [Alan Boris] 12 | 13 | # v1.1.7 14 | ## (2023-03-25) 15 | 16 | * Bump Python SDK version [Alan Boris] 17 | 18 | # v1.1.6 19 | ## (2022-11-16) 20 | 21 | * moving from balenablocks to balena-labs-projects [Flynn Joffray] 22 | 23 | # v1.1.5 24 | ## (2022-04-07) 25 | 26 | * fix aarch64 rust issue [Phil Wilson] 27 | * Fix build for all arches [Phil Wilson] 28 | * Update balenaSDK and dependencies [Phil Wilson] 29 | * build not pull [Phil Wilson] 30 | * fix readme [Phil Wilson] 31 | 32 | # v1.1.4 33 | ## (2022-04-05) 34 | 35 | * fix action [Phil Wilson] 36 | * Move to GHAction [Phil Wilson] 37 | 38 | # v1.1.3 39 | ## (2022-03-25) 40 | 41 | * Allow x86_64 version [Bill Love] 42 | 43 | # v1.1.2 44 | ## (2021-11-11) 45 | 46 | * fix python image version [Phil Wilson] 47 | 48 | # v1.1.1 49 | ## (2021-11-08) 50 | 51 | * fix pi4 image [Phil Wilson] 52 | 53 | # v1.1.0 54 | ## (2021-10-30) 55 | 56 | * Add influx2 autowiring script [Nutchanon Ninyawee] 57 | 58 | # v1.0.2 59 | ## (2021-10-29) 60 | 61 | * added Content-Type header to http json output [mcraa] 62 | 63 | # v1.0.1 64 | ## (2021-10-21) 65 | 66 | * Fix bugs and image build failure [Phil Wilson] 67 | 68 | # v1.0.0 69 | ## (2021-10-21) 70 | 71 | * code cleanup [Phil Wilson] 72 | 73 | # v0.1.0 74 | ## (2021-10-20) 75 | 76 | * Update README.md [Tomás Migone] 77 | * Update README.md [Tomás Migone] 78 | * Add support for local mode [Tomás Migone] 79 | * Add support for local mode [Tomás Migone] 80 | 81 | # v0.0.5 82 | ## (2021-06-15) 83 | 84 | * Add MQTT strings setting [Phil Wilson] 85 | 86 | # v0.0.4 87 | ## (2021-05-11) 88 | 89 | * readme [Phil Wilson] 90 | * Add manifest, bump telegraf version [Phil Wilson] 91 | 92 | # v0.0.3 93 | ## (2021-03-01) 94 | 95 | * meta: run versionist to update balena.yml version [Tomás Migone] 96 | 97 | # v0.0.2 98 | ## (2021-02-24) 99 | 100 | * meta: add versionist [Tomás Migone] 101 | * logo [Phil Wilson] 102 | * hub meta [Phil Wilson] 103 | * Added topic prefix [Phil Wilson] 104 | * fix [Phil Wilson] 105 | * MQTT output plugin [Phil Wilson] 106 | * Handle empty env var, ordering, multi URLs [Chris Crocker-White] 107 | * Add passthrough for ext. HTTP headers fixes #14 [Chris Crocker-White] 108 | -------------------------------------------------------------------------------- /Dockerfile.aarch64: -------------------------------------------------------------------------------- 1 | FROM balenalib/%%BALENA_MACHINE_NAME%%-alpine-python:3.8.6-build as build 2 | 3 | RUN mkdir /install 4 | WORKDIR /install 5 | 6 | ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 7 | RUN install_packages build-base python3-dev py3-setuptools libffi-dev openssl-dev 8 | 9 | COPY requirements.txt /requirements.txt 10 | ENV PATH=/root/.local/bin:$PATH 11 | RUN pip3 install --upgrade pip 12 | RUN pip3 install --user wheel 13 | RUN pip3 install --user -r /requirements.txt 14 | 15 | FROM balenalib/%%BALENA_MACHINE_NAME%%-alpine-python:3.8.6-run 16 | COPY --from=build /root/.local /root/.local 17 | ENV PATH=/root/.local/bin:$PATH 18 | 19 | WORKDIR /app 20 | COPY ./*.sh ./ 21 | RUN chmod +x *.sh 22 | 23 | RUN install_packages wget tar 24 | 25 | # download and install telegraf for the ARCH 26 | RUN /app/download.sh "%%BALENA_ARCH%%" 27 | 28 | COPY VERSION . 29 | COPY ./plugins ./plugins/ 30 | COPY *.py ./ 31 | 32 | ENTRYPOINT ["bash", "/app/entry.sh"] 33 | -------------------------------------------------------------------------------- /Dockerfile.template: -------------------------------------------------------------------------------- 1 | FROM balenalib/%%BALENA_MACHINE_NAME%%-alpine-python:3.9-build as build 2 | 3 | RUN mkdir /install 4 | WORKDIR /install 5 | 6 | # ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 7 | RUN install_packages build-base python3-dev py3-setuptools libffi-dev openssl-dev rust cargo 8 | 9 | COPY requirements.txt /requirements.txt 10 | ENV PATH=/root/.local/bin:$PATH 11 | RUN pip3 install --upgrade pip 12 | RUN pip3 install --user wheel 13 | RUN pip3 install --user -r /requirements.txt 14 | 15 | FROM balenalib/%%BALENA_MACHINE_NAME%%-alpine-python:3.9-run 16 | COPY --from=build /root/.local /root/.local 17 | ENV PATH=/root/.local/bin:$PATH 18 | 19 | WORKDIR /app 20 | COPY ./*.sh ./ 21 | RUN chmod +x *.sh 22 | 23 | RUN install_packages wget tar 24 | 25 | # download and install telegraf for the ARCH 26 | RUN /app/download.sh "%%BALENA_ARCH%%" 27 | 28 | COPY VERSION . 29 | COPY ./plugins ./plugins/ 30 | COPY *.py ./ 31 | 32 | ENTRYPOINT ["bash", "/app/entry.sh"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # balena-blocks/connector 2 | 3 | [![balena](https://github.com/balena-labs-projects/connector/actions/workflows/balena.yml/badge.svg)](https://github.com/balena-labs-projects/connector/actions/workflows/balena.yml) 4 | 5 | Intelligently connect data sources with data sinks in block-based balena applications. 6 | The `connector` block is a docker image that runs [telegraf](https://www.influxdata.com/time-series-platform/telegraf/) and code to find other services running on the device, and intelligently connect them. 7 | 8 | ## Features 9 | 10 | - Automatically finds HTTP data sources on the device 11 | - Automatically finds supported data storage services on the device 12 | - Configurable HTTP listener 13 | - Configurable external HTTP data source(s) 14 | - Configurable HTTP output 15 | - Device metric support (CPU and Memory usage) 16 | 17 | ## Usage 18 | 19 | #### docker-compose file 20 | To use this image, create a container in your `docker-compose.yml` file as shown below: 21 | 22 | ```yaml 23 | version: '2.1' 24 | 25 | services: 26 | connector: 27 | image: bh.cr/balenalabs/connector- 28 | restart: always 29 | labels: 30 | io.balena.features.balena-api: '1' # necessary to discover services 31 | io.balena.features.supervisor-api: 1 # necessary to discover services in local mode 32 | privileged: true # necessary to change container hostname 33 | ports: 34 | - "8080" # only necessary if using ExternalHttpListener (see below) 35 | ``` 36 | 37 | You can also set your `docker-compose.yml` to build a `dockerfile.template` file, and use the build variable `%%BALENA_ARCH%%` so that the correct image is automatically built for your device type (see [supported devices](#Supported-devices)): 38 | 39 | *docker-compose.yml:* 40 | ```yaml 41 | version: '2.1' 42 | 43 | services: 44 | connector: 45 | build: ./ 46 | restart: always 47 | labels: 48 | io.balena.features.balena-api: '1' # necessary to discover services 49 | io.balena.features.supervisor-api: 1 # necessary to discover services in local mode 50 | privileged: true # necessary to change container hostname 51 | ports: 52 | - "8080" # only necessary if using ExternalHttpListener (see below) 53 | ``` 54 | *dockerfile.template* 55 | 56 | ```dockerfile 57 | FROM bh.cr/balenalabs/connector-%%BALENA_ARCH%% 58 | ``` 59 | 60 | ## Supported devices 61 | The `connector` block has been tested to work on the following devices: 62 | 63 | | Device Type | Status | 64 | | ------------- | ------------- | 65 | | Raspberry Pi 3b+ | ✔ | 66 | | Raspberry Pi 4 | ✔ | 67 | | Intel NUC | ✔ | 68 | | Generic AMD64 | ✔ | 69 |
70 | 71 | ## Data Sources 72 | ### Internal HTTP 73 | This type of data source runs it's own HTTP server and provides data readings as `json` strings. The service must expose port `7575` like this: 74 | 75 | ```yaml 76 | sensor: 77 | build: ./sensor 78 | expose: 79 | - '7575' 80 | ``` 81 | The `connector` block will find this service and configure telegraf to periodically pull from it via HTTP. 82 | 83 | The default timeout for retrieving data is 2 seconds. You can change this by setting `INTERNAL_HTTP_TIMEOUT` to the number of seconds (e.g. `4`). 84 | 85 | ### MQTT 86 | By adding an MQTT broker to an application, you can push data into the `connector` block. Add your broker such as: 87 | 88 | ```yaml 89 | mqtt: 90 | image: arm32v6/eclipse-mosquitto 91 | ports: 92 | - "1883:1883" 93 | restart: always 94 | ``` 95 | As long as you call the service `mqtt` the `connector` block will automatically find it and configure telegraf to pull data from the broker. Ensure the data is formatted as `json` strings. Telegraf will be configured to only pull from the `sensors` topic, so any other data you may wish to put onto the MQTT broker will not be stored (e.g. control or signalling messages). 96 | 97 | *Example code:* 98 | ```python 99 | client = mqtt.Client("1") 100 | client.connect("localhost") 101 | 102 | while(True): 103 | value = GetReading() # code omitted for brevity 104 | client.publish("sensors",json.dumps(value)) 105 | time.sleep(5) 106 | ``` 107 | 108 | #### String fields 109 | By default any string fields recieved from MQTT are ignored. For any fields you want to be brought in you will need to specify them in a variable called `MQTT_INPUT_STRINGS_FIELDS` as a comma-separated list. See the section HTTP section below for a worked example. 110 | 111 | ### External HTTP Pull 112 | This type of source is pulled from a provide via the internet. It is enabled by adding an environment variable to the `connector` service called `EXTERNAL_HTTP_PULL_URL` and setting it to the URL of the source: 113 | 114 | ![alt text](https://i.ibb.co/z4MVcxw/External-HTTPConfig.jpg "balenaCloud device service variable") 115 | 116 | Setting the vaiable `EXTERNAL_HTTP_PULL_NAME` (as above) allows you to rename the resulting data source, otherwise it will appear in your data sinks (see below) as `inputs.http`. 117 | 118 | #### Headers 119 | Some HTTP APIs that you might like to use with `EXTERNAL_HTTP_PULL` will require authorization. For that reason you can pass additional parameters using the format `EXTERNAL_HTTP_PULL_HEADER_`. For example: `EXTERNAL_HTTP_PULL_HEADER_Authorization` could be set to `Basic: YWxhZGRpbjpvcGVuc2VzYW1l`. 120 | 121 | #### String fields 122 | By default any string fields recieved from a HTTP API are ignored. For any fields you want to be brought in you will need to specify them in a variable called `EXTERNAL_HTTP_PULL_STRINGS_FIELDS` as a comma-separated list. Here's a worked example: 123 | 124 | Say my weather HTTP API brings in the following JSON: 125 | 126 | ```json 127 | { 128 | "timezone":3600, 129 | "id":2643743, 130 | "name":"London", 131 | "cod":200 132 | "sys":{ 133 | "type":1, 134 | "id":1414, 135 | "country":"GB", 136 | "sunrise":1597726289, 137 | "sunset":1597778246 138 | }, 139 | "weather":[ 140 | { 141 | "id":802, 142 | "main":"Clouds", 143 | "description":"scattered clouds", 144 | "icon":"03d" 145 | } 146 | ] 147 | } 148 | ``` 149 | In that example, to bring in the "name" field so that "London" appears in my data, I need to add `name` to my `EXTERNAL_HTTP_PULL_STRINGS_FIELDS` variable. 150 |
However, because the "country" element is nested within the "sys" element, I need to using some notation to specify that JSON path, like this `sys_country`. 151 |
Notice also that the "weather" element has an array of nested elements, including a description of the weather. To get that description I'll need to specify the path (like above), but I also need to specify the index of the array element, in this case "0". So I'll add `weather_0_description` to my environment variable. All together that will look like this: 152 | 153 | ![alt text](https://i.ibb.co/q5spmsw/external-Http-String-Fields.jpg "json string fields") 154 | 155 | ### External HTTP PUSH 156 | This type of data source pushes to your device. It is configured by enabling a built-in HTTP listener with the environment variable `ENABLE_EXTERNAL_HTTP_LISTENER` set to `1`: 157 | 158 | ![alt text](https://i.ibb.co/rwYbXWj/External-HTTPListener-Config.jpg "balenaCloud device service variable") 159 | 160 | Again, the resulting data source can be given a custom name (as above) by setting the `EXTERNAL_HTTP_LISTENER_NAME` variable. 161 | 162 | Additionally, you sometimes need to specify a `json_query` path - which effectively limits the portion of the JSON document being parsed. This path can be specified with the `EXTERNAL_HTTP_LISTENER_JSON_QUERY` variable. 163 | 164 | ### Device Metrics 165 | This data source provides the in-built telegraf metrics for the CPU and Memory usage of the device. It is enabled by setting the environment variable `ENABLE_DEVICE_METRICS` to `1`. 166 | 167 | This data source is useful for testing `connector` or simply to allow device resource monitoring as part of your application. 168 | 169 | ## Data Sinks 170 | ### InfluxDB 171 | Adding an influx timeseries database to your application will cause the `connector` block to configure telegraf to push data into it. You must name the service `influxdb` for it to be automatically discovered, such as: 172 | 173 | ```yaml 174 | influxdb: 175 | image: influxdb@sha256:73f876e0c3bd02900f829d4884f53fdfffd7098dd572406ba549eed955bf821f 176 | container_name: influxdb 177 | restart: always 178 | ``` 179 | 180 | By default the database used will be called `balena`, however you can set a custom database name by setting the `INFLUXDB_DB` environment variable. 181 | 182 | ### HTTP Data Sink 183 | This data sink will send the data to a URL specified with the environment variable `HTTP_PUSH_URL`. 184 | 185 | ### Azure Monitor 186 | This data sink pushes data into the Azure Monitor service, using an [Azure Application Insights](https://docs.microsoft.com/en-us/azure/azure-monitor/app/cloudservices) account. In order to use this sink, login (or create) to your Microsoft Azure account, create an Application Insights resource and copy the instrumentation key. Guide here: 187 | https://docs.microsoft.com/en-us/azure/azure-monitor/app/create-new-resource 188 | 189 | Place this key into an environment variable on your balena device called `APPLICATION_INSIGHTS_KEY`. 190 | 191 | You can now view the data by pointing Azure Monitor to your Application Insights account and charting the correct metrics: 192 | 193 | ![alt text](https://i.ibb.co/6r61Ykg/azure.jpg "Azure AppInsights") 194 | 195 | ## Customisation 196 | 197 | ### Configuring the pull interval 198 | 199 | You can change the pull interval for internal and external HTTP endpoints by setting the `PULL_INTERVAL` env variable. 200 | The default value is `10s`. 201 | For a list of valid inputs, refer to the [telegraf documentation](https://github.com/influxdata/telegraf/blob/master/docs/CONFIGURATION.md#intervals). 202 | 203 | ### Extend image configuration 204 | 205 | By default the `connector` block creates a telegraf configuration file from the combination of discovered services and device environment variables. However for custom configurations you can overload the `CMD` directive, as such: 206 | 207 | *dockerfile.template* 208 | ```Dockerfile 209 | FROM bh.cr/balenalabs/connector-%%BALENA_ARCH%% 210 | 211 | COPY customTelegraf.conf . 212 | 213 | CMD ["--config customTelegraf.conf"] 214 | ``` 215 | 216 | This will stop the auto-wiring code from running and cause telegraf to be run purely from the supplied configuration file. 217 | 218 | ## Troubleshooting 219 | 220 | You can turn on telegraf debugging by setting the environment variable `DEBUG` to `1`. This turns on debug logging to the console. 221 | 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.1.8 -------------------------------------------------------------------------------- /autowire.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append(".") 4 | sys.path.append("./plugins") 5 | from functools import partial 6 | from pluginbase import PluginBase 7 | from balena import Balena 8 | import toml 9 | import requests 10 | 11 | class AutoWire(): 12 | balena = "" 13 | services = None 14 | 15 | def __init__(self, balena): 16 | self.balena = balena 17 | 18 | ### Gets the services running on the device from the release definition ### 19 | def GetServices(self): 20 | if self.services is None: 21 | # Use the device UUID to get the device model and check device mode 22 | device_id = os.environ.get('BALENA_DEVICE_UUID') 23 | local_mode = balena.models.device.is_in_local_mode(device_id) 24 | 25 | if local_mode: 26 | supervisor_address = os.environ.get('BALENA_SUPERVISOR_ADDRESS') 27 | r = requests.get(supervisor_address + '/v2/local/target-state') 28 | services = r.json()["state"]["local"]["apps"]["1"]["services"] 29 | self.services = {} 30 | for service in services: 31 | self.services[service.get("serviceName")] = service.get("config") 32 | else: 33 | device = self.balena.models.device.get_with_service_details(device_id, True) 34 | # get the commit the device is on 35 | serviceName = os.getenv("BALENA_SERVICE_NAME") or "connector" 36 | commit = device["current_services"].get(serviceName)[0]["commit"] 37 | # use the commit to get the release the device is on 38 | release = self.balena.models.release.get(commit) 39 | # use the release to find the services configured 40 | self.services = release["composition"]["services"] 41 | 42 | return self.services 43 | 44 | ### Returns a default input section (device temp) if 45 | ### no input sections have been created by plugins. 46 | ### (i.e. no input sources configured or discovered) 47 | def GenerateDefaultInputConfig(self, config): 48 | section = "[[inputs.temp]]" 49 | 50 | if('inputs' not in toml.loads(config).keys()): 51 | return section 52 | 53 | if(len(toml.loads(config)['inputs']) == 0): 54 | return section 55 | 56 | return None 57 | 58 | ### Loads the plugins and passes the service list to each one ### 59 | ### A plugin outputs it's config only if there is an entry in ### 60 | ### the list for the backend service it configures ### 61 | def GetConfig(self): 62 | services = self.GetServices() 63 | config = "" 64 | # Use PluginBase to find the plugins 65 | here = os.path.abspath(os.path.dirname(__file__)) 66 | get_path = partial(os.path.join, here) 67 | plugin_base = PluginBase(package='plugins') 68 | plugin_source = plugin_base.make_plugin_source(searchpath=[get_path('plugins')]) 69 | 70 | # Call each plugin and pass in the list of services 71 | for plugin_name in plugin_source.list_plugins(): 72 | plugin = plugin_source.load_plugin(plugin_name) 73 | # Add each plugin output to the config string we're building 74 | section = plugin.invoke(services) 75 | if(section is not None): 76 | config = (config + str(section)) 77 | 78 | defaultSection = self.GenerateDefaultInputConfig(config) 79 | if(defaultSection is not None): 80 | print("No data sources configured or detected. Enabling default: device temperature.") 81 | config = (config + str(defaultSection)) 82 | 83 | return config 84 | 85 | # Authenticate with balenaCloud 86 | balena = Balena() 87 | auth_token = os.environ.get('BALENA_API_KEY') or sys.exit("No AUTH_TOKEN device variable set. Cannot authenticate with balenaCloud") 88 | balena.auth.login_with_token(auth_token) 89 | 90 | # Create the autowire class 91 | autowire = AutoWire(balena) 92 | print("balenalabs/connector") 93 | print("----------------------") 94 | print('Intelligently connecting data sources with data sinks') 95 | # Get the config and write to to the file. 96 | config = autowire.GetConfig() 97 | doc = toml.loads(config) 98 | f = open('telegraf.conf', 'w') 99 | f.write(toml.dumps(doc)) 100 | f.close() 101 | -------------------------------------------------------------------------------- /balena.yml: -------------------------------------------------------------------------------- 1 | name: connector 2 | description: Intelligently connect data sources with data sinks. 3 | 4 | post-provisioning: >- 5 | [![balena](https://github.com/balena-labs-projects/connector/actions/workflows/balena.yml/badge.svg)](https://github.com/balena-labs-projects/connector/actions/workflows/balena.yml) 6 | 7 | ## Usage instructions 8 | 9 | Include this snippet in your docker-compose.yml file under 'services': 10 | 11 | ``` 12 | browser: 13 | image: bh.cr/balenalabs/connector- # where is one of aarch64, armv7hf or amd64 14 | restart: always 15 | labels: 16 | io.balena.features.balena-api: '1' # necessary to discover services 17 | io.balena.features.supervisor-api: 1 # necessary to discover services in local mode 18 | privileged: true # necessary to change container hostname 19 | ports: 20 | - "8080" # only necessary if using ExternalHttpListener 21 | ``` 22 | 23 | To pin to a specific version of this block use: 24 | 25 | ``` 26 | browser: 27 | image: bh.cr/balenalabs/connector-/ 28 | ... 29 | ``` 30 | version: 1.1.8 31 | type: sw.application 32 | assets: 33 | repository: 34 | type: blob.asset 35 | data: 36 | url: 'https://github.com/balena-labs-projects/connector' 37 | logo: 38 | type: blob.asset 39 | data: 40 | url: 'https://raw.githubusercontent.com/balena-labs-projects/connector/master/logo.png' 41 | data: 42 | defaultDeviceType: raspberrypi3 43 | supportedDeviceTypes: 44 | - raspberrypi4-64 45 | - fincm3 46 | - raspberrypi3 47 | - raspberrypi3-64 48 | - raspberrypi400-64 49 | - intel-nuc 50 | - genericx86-64-ext 51 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | connector: 5 | build: . 6 | restart: always 7 | labels: 8 | io.balena.features.balena-api: '1' # necessary to discover services 9 | io.balena.features.supervisor-api: 1 # necessary to discover services in local mode 10 | privileged: true # necessary to change container hostname 11 | ports: 12 | - "8080" # only necessary if using ExternalHttpListener (see below) -------------------------------------------------------------------------------- /download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | version="1.22.0" 4 | outfile="/tmp/telegraf.tar.gz" 5 | download_base="https://dl.influxdata.com/telegraf/releases/" 6 | case $1 in 7 | "aarch64") package_file="telegraf-${version}_linux_arm64.tar.gz" 8 | ;; 9 | "armv7hf") package_file="telegraf-${version}_linux_armhf.tar.gz" 10 | ;; 11 | "amd64") package_file="telegraf-${version}_static_linux_amd64.tar.gz" 12 | ;; 13 | *) echo >&2 "error: unsupported architecture ($1)"; exit 1 ;; 14 | esac 15 | wget -O "${outfile}" "${download_base}${package_file}" 16 | 17 | tar -xvf /tmp/telegraf.tar.gz 18 | cp ./telegraf-*/usr/bin/telegraf ./ 19 | rm -rf ./telegraf-* -------------------------------------------------------------------------------- /entry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Run balena base image entrypoint script 5 | /usr/bin/entry.sh echo "" 6 | 7 | echo "balenaLabs connector version: $( 0: 26 | timeout = os.environ.get('INTERNAL_HTTP_TIMEOUT') or '2' 27 | for service, port in pullDataSources.items(): 28 | print("Adding {service} internal HTTP source".format(service=service)) 29 | sourceConf = """[[inputs.http]] 30 | urls = [ 31 | "http://{service}:{port}" 32 | ] 33 | 34 | timeout = "{timeout}s" 35 | data_format = "json" 36 | name_override = "{service}" 37 | """.format(service=service, port=port[0].split('/')[0], timeout=timeout) 38 | output = (output + sourceConf) 39 | 40 | stringFields = os.environ.get('INTERNAL_HTTP_PULL_STRINGS_FIELDS') 41 | if(stringFields is not None): 42 | stringFieldsSection = helpers.formatStringField(stringFields) 43 | output = output + stringFieldsSection 44 | 45 | return output -------------------------------------------------------------------------------- /plugins/mqttInput.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append(".") 3 | 4 | import os 5 | from helper_methods import helpers 6 | 7 | SERVICE_NAME = "mqtt" 8 | 9 | def invoke(services): 10 | if(SERVICE_NAME in services.keys()): 11 | print("Loading {name} plugin".format(name=SERVICE_NAME)) 12 | return getConfigSection() 13 | 14 | def getConfigSection(): 15 | output = """ 16 | [[inputs.mqtt_consumer]] 17 | servers = ["mqtt://mqtt:1883"] 18 | topics = [ 19 | "sensors/#", 20 | "balena/#" 21 | ] 22 | 23 | data_format = "json" 24 | """ 25 | 26 | stringFields = os.environ.get('MQTT_INPUT_STRINGS_FIELDS') 27 | if(stringFields is not None): 28 | stringFieldsSection = helpers.formatStringField(stringFields) 29 | output = output + stringFieldsSection 30 | 31 | return output -------------------------------------------------------------------------------- /plugins/mqttOutput.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | SERVICE_NAME = "MQTT Push" 4 | 5 | def invoke(services): 6 | server = os.environ.get('MQTT_OUTPUT_SERVER') or None 7 | port = os.environ.get('MQTT_OUTPUT_PORT') or '1883' 8 | if(server is None): 9 | return None 10 | print("Loading {name} plugin".format(name=SERVICE_NAME)) 11 | return getConfigSection(server, port) 12 | 13 | def getConfigSection(server, port): 14 | output = """ 15 | [[outputs.mqtt]] 16 | servers = ["{url}:{port}"] 17 | topic_prefix = "balena" 18 | data_format = "json" 19 | qos = 2 20 | """.format(url=server, port=port) 21 | 22 | username = os.environ.get('MQTT_OUTPUT_USERNAME') 23 | if(username is not None): 24 | usernameSection = """ username = '{value}'\n""".format(value=username) 25 | output = output + usernameSection 26 | 27 | password = os.environ.get('MQTT_OUTPUT_PASSWORD') 28 | if(password is not None): 29 | passwordSection = """ password = '{value}'\n""".format(value=password) 30 | output = output + passwordSection 31 | 32 | return output 33 | 34 | 35 | -------------------------------------------------------------------------------- /repo.yml: -------------------------------------------------------------------------------- 1 | type: generic -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyJWT==2.0.0 2 | balena-sdk==12.3.1 3 | PluginBase 4 | toml 5 | requests 6 | --------------------------------------------------------------------------------