├── .DS_Store ├── .github ├── FUNDING.yml └── workflows │ ├── publish-gh-pages.yml │ └── publish-release.yml ├── .gitignore ├── .htmlhintrc ├── .npmignore ├── CHANGELOG.md ├── README.md ├── ROADMAP.md ├── examples ├── .DS_Store ├── DockerWindowsSettings.png ├── action-inspect-flows.json ├── bitcoin.png ├── flow.json ├── flow.png ├── flows-all.json ├── flows.json ├── list-flows.json └── support_banner.png ├── gulpfile.js ├── package-lock.json ├── package.json ├── src ├── docker-config-actions.html ├── docker-config-actions.ts ├── docker-configuration.html ├── docker-configuration.ts ├── docker-container-actions.html ├── docker-container-actions.ts ├── docker-engine-actions.html ├── docker-engine-actions.ts ├── docker-events.html ├── docker-events.ts ├── docker-image-actions.html ├── docker-image-actions.ts ├── docker-network-actions.html ├── docker-network-actions.ts ├── docker-node-actions.html ├── docker-node-actions.ts ├── docker-plugin-actions.html ├── docker-plugin-actions.ts ├── docker-secret-actions.html ├── docker-secret-actions.ts ├── docker-service-actions.html ├── docker-service-actions.ts ├── docker-swarm-actions.html ├── docker-swarm-actions.ts ├── docker-task-actions.html ├── docker-task-actions.ts ├── docker-volume-actions.html ├── docker-volume-actions.ts └── icons │ └── docker.png ├── test ├── docker-container-actions_spec.js ├── docker-containers_spec.js ├── docker-events_spec.js └── docker-images_spec.js └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naimo84/node-red-contrib-dockerode/d04142772dfd7e101a86e9dbd02710812169e840/.DS_Store -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: naimo84 2 | ko_fi: naimo84 3 | custom: ['https://paypal.me/NeumannBenjamin'] 4 | -------------------------------------------------------------------------------- /.github/workflows/publish-gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: publish-gh-pages 2 | on: 3 | push: 4 | branches: [ docs ] 5 | jobs: 6 | build-and-deploy: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2.3.1 11 | - name: Install and Build 12 | run: | 13 | npm install 14 | npm run build 15 | - name: Deploy 16 | uses: JamesIves/github-pages-deploy-action@4.1.4 17 | with: 18 | branch: gh-pages 19 | folder: build -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: publish-release 2 | on: 3 | release: 4 | types: [ released ] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | - name: Install Node.js 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: 16 15 | registry-url: 'https://registry.npmjs.org' 16 | - run: npm install 17 | - run: npm run build 18 | - run: npm publish --access public 19 | env: 20 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 21 | - name: Pack tarball 22 | id: pack_tarball 23 | run: | 24 | PACK_NAME=$(npm pack | tail -n 1) 25 | echo "::set-output name=tar_filename::$PACK_NAME" 26 | - name: Upload 27 | uses: actions/upload-artifact@v2 28 | with: 29 | name: package 30 | path: "*.tgz" 31 | - name: Get release 32 | id: get_release 33 | uses: bruceadams/get-release@v1.2.3 34 | env: 35 | GITHUB_TOKEN: ${{ github.token }} 36 | - name: Upload asset to github release page 37 | id: upload-release-asset 38 | uses: actions/upload-release-asset@v1 39 | env: 40 | GITHUB_TOKEN: ${{ github.token }} 41 | with: 42 | upload_url: ${{ steps.get_release.outputs.upload_url }} 43 | asset_path: ./${{ steps.pack_tarball.outputs.tar_filename }} 44 | asset_name: ${{ steps.pack_tarball.outputs.tar_filename }} 45 | asset_content_type: application/gzip -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | dist 4 | build -------------------------------------------------------------------------------- /.htmlhintrc: -------------------------------------------------------------------------------- 1 | { 2 | "doctype-first": false 3 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .vscode 3 | test/* 4 | *.js.map 5 | src/* 6 | gulpfile.js 7 | examples/*.png 8 | *.tgz -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-red-contrib-dockerode 2 | 3 | This Node RED module connects Docker with Node-RED. 4 | 5 | > Node-RED is a tool for wiring together hardware devices, APIs and online services in new and interesting ways. 6 | 7 |

8 | 9 | 10 | 11 |

12 | 13 | 14 | ## :sparkling_heart: Support my projects 15 | 16 | I open-source almost everything I can, and I try to reply to everyone needing help using these projects. Obviously, 17 | this takes time. You can integrate and use these projects in your applications _for free_! You can even change the source code and redistribute (even resell it). 18 | 19 | Thank you to all my backers! 20 | ### People 21 | 22 | - [fflorent](https://github.com/fflorent) 23 | - [Speeedy0815](https://github.com/Speeedy0815) 24 | - Ralf S. 25 | - Enno L. 26 | - Jürgen G. 27 | - Mark MC G. 28 | - Kay-Uwe M. 29 | - Craig O. 30 | - Manuel G. 31 | 32 | ### Become a backer 33 | 34 | 35 | However, if you get some profit from this or just want to encourage me to continue creating stuff, there are few ways you can do it: 36 | 37 | - Starring and sharing the projects you like :rocket: 38 | - **Crypto.com**  —  Use my referral link https://crypto.com/app/f2smbah8fm to sign up for Crypto.com and we both get $25 USD :) 39 | 40 | - [![PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg?style=for-the-badge)][paypal-donations]   —   You can make one-time donations via PayPal. I'll probably buy a ~~coffee~~ tea. :tea: 41 | - [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/T6T412CXA)  —  I'll buy a ~~tea~~ coffee. :coffee: :wink: 42 | 43 | Thanks! :heart: 44 | 45 | ## :cloud: Installation 46 | 47 | First of all install [Node-RED](http://nodered.org/docs/getting-started/installation) 48 | 49 | ## :yum: How to contribute 50 | 51 | * git clone https://github.com/naimo84/node-red-contrib-dockerode.git 52 | * cd node-red-contrib-dockerode 53 | * npm install 54 | * gulp 55 | * cd ~/.node-red 56 | * npm install /path/to/node-red-contrib-dockerode 57 | 58 | 59 | 60 | ## Usage 61 | 62 | ### Configuration: 63 | 64 | #### docker.sock 65 | 66 | - ***Using Node-RED in a Docker-Container*** 67 | 68 | The Node-RED container must have access to the docker.sock, so you have to add the docker-group ID to the container with
docker run ... --group-add 250
the ID 250 may be different on your system. 69 | 70 | #### Exposing TCP-Daemon port 71 | 72 | - ***hostname*** hostname of docker (e.g. "localhost") 73 | - ***port*** port of docker (e.g. "2375") 74 | 75 | In order to expose the docker-engine TCP daemon, you have to do the following: 76 | 77 | - ***Docker for Windows / Docker Desktop:*** 78 |
Under Settings / General check "Expose daemon on tcp://localhost:2375 without TLS" 79 | 80 | ![DockerWindowsSettings.png](https://github.com/naimo84/node-red-contrib-dockerode/raw/master/examples/DockerWindowsSettings.png) 81 | 82 | - ***Docker-CE*** 83 | 84 | See https://success.docker.com/article/how-do-i-enable-the-remote-api-for-dockerd 85 | 86 | or: 87 | 88 | ``` 89 | # File: /etc/default/docker 90 | # Use DOCKER_OPTS to modify the daemon startup options. 91 | #DOCKER_OPTS="" 92 | DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock" 93 | ``` 94 | 95 | or: 96 | 97 | ``` 98 | # File: /lib/systemd/system/docker.service 99 | ExecStart=/usr/bin/docker daemon -H fd:// -H tcp://0.0.0.0:2375 100 | ``` 101 | 102 | [badge_brave]: ./examples/support_banner.png 103 | [badge_paypal]: https://img.shields.io/badge/Donate-PayPal-blue.svg 104 | [paypal-donations]: https://paypal.me/NeumannBenjamin 105 | 106 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | ### System 2 | * POST /auth [API v1.40 - SystemAuth](https://docs.docker.com/engine/api/v1.40/#operation/SystemAuth) 3 | * POST /info [API v1.40 - SystemInfo](https://docs.docker.com/engine/api/v1.40/#operation/SystemInfo) 4 | * POST /version [API v1.40 - SystemVersion](https://docs.docker.com/engine/api/v1.40/#operation/SystemVersion) 5 | * POST /_ping [API v1.40 - SystemPing](https://docs.docker.com/engine/api/v1.40/#operation/SystemPing) 6 | * HEAD /_ping [API v1.40 - SystemPingHead](https://docs.docker.com/engine/api/v1.40/#operation/SystemPingHead) 7 | * GET /events [API v1.40 - SystemEvents](https://docs.docker.com/engine/api/v1.40/#operation/SystemEvents) 8 | * GET /system/df [API v1.40 - SystemDataUsage](https://docs.docker.com/engine/api/v1.40/#operation/SystemDataUsage) 9 | * ## TODO: 10 | * * Test 11 | 12 | ### Distribution 13 | GET /distribution/{name}/json [API v1.40 - DistributionInspect](https://docs.docker.com/engine/api/v1.40/#operation/DistributionInspect) 14 | * ## TODO: 15 | * * Test 16 | 17 | ### Session 18 | * POST /session [API v1.40 - Session](https://docs.docker.com/engine/api/v1.40/#operation/Session) 19 | * ## TODO: 20 | * * Test 21 | 22 | ### Containers 23 | * GET /containers/json [API v1.40 - ContainerList](https://docs.docker.com/engine/api/v1.40/#operation/ContainerList) 24 | 25 | * POST /containers/create [API v1.40 - ContainerCreate](https://docs.docker.com/engine/api/v1.40/#operation/ContainerCreate) 26 | * * Locate or implement 27 | * GET /containers/(id or name)/json [API v1.40 - ContainerInspect](https://docs.docker.com/engine/api/v1.40/#operation/ContainerInspect) 28 | * * ~~Errors~~ 29 | * * ~~Tested~~ 30 | * GET /containers/(id or name)/top [API v1.40 - ContainerTop](https://docs.docker.com/engine/api/v1.40/#operation/ContainerTop) 31 | * * ~~Errors~~ 32 | * * ~~Tested~~ 33 | * GET /containers/(id or name)/logs [API v1.40 - ContainerLogs](https://docs.docker.com/engine/api/v1.40/#operation/ContainerLogs) 34 | * * ~~Errors~~ 35 | * * Failed 36 | * GET /containers/(id or name)/changes [API v1.40 - ContainerChanges](https://docs.docker.com/engine/api/v1.40/#operation/ContainerChanges) 37 | * * ~~Errors~~ 38 | * * ~~Tested~~ 39 | * GET /containers/(id or name)/export [API v1.40 - ContainerExport](https://docs.docker.com/engine/api/v1.40/#operation/ContainerExport) 40 | * * ~~Errors~~ 41 | * * Failed 42 | * GET /containers/(id or name)/stats [API v1.40 - ContainerStats](https://docs.docker.com/engine/api/v1.40/#operation/ContainerStats) 43 | * * ~~Errors~~ 44 | * * Failed 45 | * POST /containers/{id}/resize [API v1.40 - ](https://docs.docker.com/engine/api/v1.40/#operation/ContainerResize) 46 | * * ~~Errors~~ 47 | * * Failed 48 | * POST /containers/(id or name)/start [API v1.40 - ContainerResize](https://docs.docker.com/engine/api/v1.40/#operation/ContainerStart) 49 | * * ~~Errors~~ 50 | * * Failed Odd messge 51 | * POST /containers/(id or name)/stop [API v1.40 - ContainerStop](https://docs.docker.com/engine/api/v1.40/#operation/ContainerStop) 52 | * * ~~Errors~~ 53 | * * Failed Odd messge 54 | * POST /containers/(id or name)/restart [API v1.40 - ](https://docs.docker.com/engine/api/v1.40/#operation/ContainerRestart) 55 | * * ~~Errors~~ 56 | * * Failed Odd messge 57 | * POST /containers/(id or name)/kill [API v1.40 - ](https://docs.docker.com/engine/api/v1.40/#operation/ContainerKill) 58 | * * ~~Errors~~ 59 | * * Failed Odd messge 60 | * POST /containers/(id or name)/update [API v1.40 - ContainerKill](https://docs.docker.com/engine/api/v1.40/#operation/ContainerUpdate) 61 | * * ~~Errors~~ 62 | * * Failed Odd messge 63 | * POST /containers/(id or name)/rename [API v1.40 - ContainerRename](https://docs.docker.com/engine/api/v1.40/#operation/ContainerRename) 64 | * * ~~Errors~~ 65 | * * Failed Odd messge 66 | * POST /containers/(id or name)/pause [API v1.40 - ContainerPause](https://docs.docker.com/engine/api/v1.40/#operation/ContainerPause) 67 | * * ~~Errors~~ 68 | * * Failed Odd messge 69 | * POST /containers/(id or name)/unpause [API v1.40 - ContainerUnpause](https://docs.docker.com/engine/api/v1.40/#operation/ContainerUnpause) 70 | * * ~~Errors~~ 71 | * * Failed Odd messge 72 | * POST /containers/(id or name)/attach [API v1.40 - ](https://docs.docker.com/engine/api/v1.40/#operation/ContainerAttach) 73 | * * ~~Errors~~ 74 | * * Failed Odd messge 75 | * GET /containers/{id}/attach/ws [API v1.40 - ContainerAttach](https://docs.docker.com/engine/api/v1.40/#operation/ContainerAttachWebsocket) 76 | * * ~~Errors~~ 77 | * * Failed Odd messge 78 | * * Not found in dockerode 79 | * POST /containers/(id or name)/wait [API v1.40 - ContainerWait](https://docs.docker.com/engine/api/v1.40/#operation/ContainerWait) 80 | * * ~~Errors~~ 81 | * * Failed Odd messge 82 | * DELETE /containers/(id or name) [API v1.40 - ContainerDelete](https://docs.docker.com/engine/api/v1.40/#operation/ContainerDelete) 83 | * * ~~Errors~~ 84 | * * Failed Odd messge 85 | * HEAD /containers/{id}/archive [API v1.40 - ContainerArchiveInfo](https://docs.docker.com/engine/api/v1.40/#operation/ContainerArchiveInfo) 86 | * * ~~Errors~~ 87 | * * Failed Odd messge 88 | * PUT /containers/(id or name)/archive [API v1.40 - ContainerArchive](https://docs.docker.com/engine/api/v1.40/#operation/ContainerArchive) 89 | * * ~~Errors~~ 90 | * * Fix and test 91 | * POST /containers/prune [API v1.40 - ContainerPrune](https://docs.docker.com/engine/api/v1.40/#operation/ContainerPrune) 92 | * * Not found in dockerode 93 | * ## TODO: 94 | * * Test 95 | 96 | ### Exec 97 | * POST /containers/{id}/exec [API v1.40 - ContainerExec](https://docs.docker.com/engine/api/v1.40/#operation/ContainerExec) 98 | * POST /exec/{id}/start [API v1.40 - ExecStart](https://docs.docker.com/engine/api/v1.40/#operation/ExecStart) 99 | * POST /exec/{id}/resize [API v1.40 - ExecResize](https://docs.docker.com/engine/api/v1.40/#operation/ExecResize) 100 | * GET /exec/{id}/json [API v1.40 - ExecInspect](https://docs.docker.com/engine/api/v1.40/#operation/ExecInspect) 101 | * ## TODO: 102 | * * Test 103 | 104 | ### Volumes 105 | * GET /volumes [API v1.40 - VolumeList](https://docs.docker.com/engine/api/v1.40/#operation/VolumeList) 106 | * POST /volumes/create [API v1.40 - VolumeCreate](https://docs.docker.com/engine/api/v1.40/#operation/VolumeCreate) 107 | * GET /volumes/(name) [API v1.40 - VolumeInspect](https://docs.docker.com/engine/api/v1.40/#operation/VolumeInspect) 108 | * DELETE /volumes/(name) [API v1.40 - VolumeDelete](https://docs.docker.com/engine/api/v1.40/#operation/VolumeDelete) 109 | * POST /volumes/prune [API v1.40 - VolumePrune](https://docs.docker.com/engine/api/v1.40/#operation/VolumePrune) 110 | * ## TODO: 111 | * * Test 112 | 113 | ### Images 114 | * GET /images/json [API v1.40 - ImageList](https://docs.docker.com/engine/api/v1.40/#operation/ImageList) 115 | * POST /build [API v1.40 - ImageBuild](https://docs.docker.com/engine/api/v1.40/#operation/ImageBuild) 116 | * POST /build/prune [API v1.40 - BuildPrune](https://docs.docker.com/engine/api/v1.40/#operation/BuildPrune) 117 | * POST /images/create [API v1.40 - ImageCreate](https://docs.docker.com/engine/api/v1.40/#operation/ImageCreate) 118 | * GET /images/(name)/json [API v1.40 - ImageInspect](https://docs.docker.com/engine/api/v1.40/#operation/ImageInspect) 119 | * GET /images/(name)/history [API v1.40 - ImageHistory](https://docs.docker.com/engine/api/v1.40/#operation/ImageHistory) 120 | * POST /images/(name)/push [API v1.40 - ImagePush](https://docs.docker.com/engine/api/v1.40/#operation/ImagePush) 121 | * POST /images/(name)/tag [API v1.40 - ImageTag](https://docs.docker.com/engine/api/v1.40/#operation/ImageTag) 122 | * DELETE /images/(name) [API v1.40 - ImageDelete](https://docs.docker.com/engine/api/v1.40/#operation/ImageDelete) 123 | * GET /images/search [API v1.40 - ImageSearch](https://docs.docker.com/engine/api/v1.40/#operation/ImageSearch) 124 | * POST /images/prune [API v1.40 - ImagePrune](https://docs.docker.com/engine/api/v1.40/#operation/ImagePrune) 125 | * POST /commit [API v1.40 - ImageCommit](https://docs.docker.com/engine/api/v1.40/#operation/ImageCommit) 126 | * GET /images/(name)/get [API v1.40 - ](https://docs.docker.com/engine/api/v1.40/#operation/ImageGet) 127 | * GET /images/get [API v1.40 - ImageGet](https://docs.docker.com/engine/api/v1.40/#operation/ImageGetAll) 128 | * POST /images/load [API v1.40 - ImageLoad](https://docs.docker.com/engine/api/v1.40/#operation/ImageLoad) 129 | * ## TODO: 130 | * * Test 131 | 132 | ### Networks 133 | * GET /networks [API v1.40 - NetworkList](https://docs.docker.com/engine/api/v1.40/#operation/NetworkList) 134 | * GET /networks/(id or name) [API v1.40 - NetworkInspect](https://docs.docker.com/engine/api/v1.40/#operation/NetworkInspect) 135 | * DELETE /networks/(id or name) [API v1.40 - NetworkDelete](https://docs.docker.com/engine/api/v1.40/#operation/NetworkDelete) 136 | * POST /networks/create [API v1.40 - NetworkCreate](https://docs.docker.com/engine/api/v1.40/#operation/NetworkCreate) 137 | * POST /networks/(id or name)/connect [API v1.40 - NetworkConnect](https://docs.docker.com/engine/api/v1.40/#operation/NetworkConnect) 138 | * POST /networks/(id or name)/disconnect [API v1.40 - NetworkDisconnect](https://docs.docker.com/engine/api/v1.40/#operation/NetworkDisconnect) 139 | * POST /networks/prune [API v1.40 - NetworkPrune](https://docs.docker.com/engine/api/v1.40/#operation/NetworkPrune) 140 | * ## TODO: 141 | * * Test 142 | 143 | ### Plugins 144 | * GET /plugins [API v1.40 - PluginList](https://docs.docker.com/engine/api/v1.40/#operation/PluginList) 145 | * GET /plugins/privileges [API v1.40 - GetPluginPrivileges](https://docs.docker.com/engine/api/v1.40/#operation/GetPluginPrivileges) 146 | * POST /plugins/pull [API v1.40 - PluginPull](https://docs.docker.com/engine/api/v1.40/#operation/PluginPull) 147 | * GET /plugins/{name}/json [API v1.40 - PluginInspect](https://docs.docker.com/engine/api/v1.40/#operation/PluginInspect) 148 | * DELETE /plugins/{name} [API v1.40 - PluginDelete](https://docs.docker.com/engine/api/v1.40/#operation/PluginDelete) 149 | * POST /plugins/{name}/enable [API v1.40 - PluginEnable](https://docs.docker.com/engine/api/v1.40/#operation/PluginEnable) 150 | * POST /plugins/{name}/disable [API v1.40 - PluginDisable](https://docs.docker.com/engine/api/v1.40/#operation/PluginDisable) 151 | * POST /plugins/{name}/upgrade [API v1.40 - PluginUpgrade](https://docs.docker.com/engine/api/v1.40/#operation/PluginUpgrade) 152 | * POST /plugins/create [API v1.40 - PluginCreate](https://docs.docker.com/engine/api/v1.40/#operation/PluginCreate) 153 | * POST /plugins/{name}/push [API v1.40 - PluginPush](https://docs.docker.com/engine/api/v1.40/#operation/PluginPush) 154 | * POST /plugins/{name}/set [API v1.40 - PluginSet](https://docs.docker.com/engine/api/v1.40/#operation/PluginSet) 155 | * ## TODO: 156 | * * Test 157 | 158 | ### Configs 159 | * GET /configs [API v1.40 - ConfigList](https://docs.docker.com/engine/api/v1.40/#operation/ConfigList) 160 | * POST /configs/create [API v1.40 - ConfigCreate](https://docs.docker.com/engine/api/v1.40/#operation/ConfigCreate) 161 | * * ~~Errors~~ 162 | * * ~~Tested~~ 163 | * GET /configs/{id} [API v1.40 - ConfigInspect](https://docs.docker.com/engine/api/v1.40/#operation/ConfigInspect) 164 | * * ~~Errors~~ 165 | * DELETE /configs/{id} [API v1.40 - ConfigDelete](https://docs.docker.com/engine/api/v1.40/#operation/ConfigDelete) 166 | * * ~~Errors~~ 167 | * POST /configs/{id}/update [API v1.40 - ConfigUpdate](https://docs.docker.com/engine/api/v1.40/#operation/ConfigUpdate) 168 | * * ~~Errors~~ 169 | * ## TODO: 170 | * * Test 171 | 172 | ### Secrets 173 | * GET /secrets [API v1.40 - SecretList](https://docs.docker.com/engine/api/v1.40/#operation/SecretList) 174 | * POST /secrets/create [API v1.40 - SecretCreate](https://docs.docker.com/engine/api/v1.40/#operation/SecretCreate) 175 | * GET /secrets/{id} [API v1.40 - SecretInspect](https://docs.docker.com/engine/api/v1.40/#operation/SecretInspect) 176 | * DELETE /secrets/{id} [API v1.40 - SecretDelete](https://docs.docker.com/engine/api/v1.40/#operation/SecretDelete) 177 | * POST /secrets/{id}/update [API v1.40 - SecretUpdate](https://docs.docker.com/engine/api/v1.40/#operation/SecretUpdate) 178 | * ## TODO: 179 | * * Test 180 | 181 | ### Nodes 182 | * GET /nodes [API v1.40 - NodeList](https://docs.docker.com/engine/api/v1.40/#operation/NodeList) 183 | * GET /nodes/{id} [API v1.40 - NodeInspect](https://docs.docker.com/engine/api/v1.40/#operation/NodeInspect) 184 | * DELETE /nodes/{id} [API v1.40 - NodeDelete](https://docs.docker.com/engine/api/v1.40/#operation/NodeDelete) 185 | * POST /nodes/{id}/update [API v1.40 - NodeUpdate](https://docs.docker.com/engine/api/v1.40/#operation/NodeUpdate) 186 | * ## TODO: 187 | * * Test 188 | 189 | ### Services 190 | * GET /services [API v1.40 - ServiceCreate](https://docs.docker.com/engine/api/v1.40/#operation/c) 191 | * POST /services/create [API v1.40 - ServiceCreate](https://docs.docker.com/engine/api/v1.40/#operation/ServiceCreate) 192 | * GET /services/{id} [API v1.40 - ServiceInspect](https://docs.docker.com/engine/api/v1.40/#operation/ServiceInspect) 193 | * DELETE /services/{id} [API v1.40 - ServiceDelete](https://docs.docker.com/engine/api/v1.40/#operation/ServiceDelete) 194 | * POST /services/{id}/update [API v1.40 - ServiceUpdate](https://docs.docker.com/engine/api/v1.40/#operation/ServiceUpdate) 195 | * ## TODO: 196 | * * Test 197 | 198 | ### Swarm 199 | * GET /swarm [API v1.40 - Swarm](https://docs.docker.com/engine/api/v1.40/#tag/Swarm) 200 | * POST /swarm/init [API v1.40 - SwarmInit](https://docs.docker.com/engine/api/v1.40/#operation/SwarmInit) 201 | * POST /swarm/join [API v1.40 - SwarmJoin](https://docs.docker.com/engine/api/v1.40/#operation/SwarmJoin) 202 | * POST /swarm/leave [API v1.40 - SwarmLeave](https://docs.docker.com/engine/api/v1.40/#operation/SwarmLeave) 203 | * POST /swarm/update [API v1.40 - SwarmUpdate](https://docs.docker.com/engine/api/v1.40/#operation/SwarmUpdate) 204 | * ## TODO: 205 | * * Test 206 | 207 | ### Tasks 208 | * GET /tasks [API v1.40 - TaskList](https://docs.docker.com/engine/api/v1.40/#operation/TaskList) 209 | * GET /tasks/(id) [API v1.40 - TaskInspect](https://docs.docker.com/engine/api/v1.40/#operation/TaskInspect) 210 | * GET /tasks/{id}/logs [API v1.40 - TaskLogs](https://docs.docker.com/engine/api/v1.40/#operation/TaskLogs) 211 | * ## TODO: 212 | * * Test 213 | -------------------------------------------------------------------------------- /examples/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naimo84/node-red-contrib-dockerode/d04142772dfd7e101a86e9dbd02710812169e840/examples/.DS_Store -------------------------------------------------------------------------------- /examples/DockerWindowsSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naimo84/node-red-contrib-dockerode/d04142772dfd7e101a86e9dbd02710812169e840/examples/DockerWindowsSettings.png -------------------------------------------------------------------------------- /examples/action-inspect-flows.json: -------------------------------------------------------------------------------- 1 | [{"id":"493fedf0.d14764","type":"docker-plugin-actions","z":"2c4e61f8.7fa14e","name":"","config":"789ca14c.874fe","plugin":"docker.io/vieux/sshfs:latest","action":"","x":530,"y":600,"wires":[["3d0404c.ff9f3fc"]]},{"id":"789ca14c.874fe","type":"docker-configuration","z":"","host":"/var/run/docker.sock","port":""}] -------------------------------------------------------------------------------- /examples/bitcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naimo84/node-red-contrib-dockerode/d04142772dfd7e101a86e9dbd02710812169e840/examples/bitcoin.png -------------------------------------------------------------------------------- /examples/flow.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "4c3b05bf.206fbc", 4 | "type": "tab", 5 | "label": "Docker", 6 | "disabled": false, 7 | "info": "" 8 | }, 9 | { 10 | "id": "cdee0698.8e7b98", 11 | "type": "docker-events", 12 | "z": "4c3b05bf.206fbc", 13 | "name": "", 14 | "config": "579edabe.09fe34", 15 | "x": 370, 16 | "y": 120, 17 | "wires": [ 18 | [ 19 | "dcf691b9.d092f" 20 | ] 21 | ] 22 | }, 23 | { 24 | "id": "dcf691b9.d092f", 25 | "type": "debug", 26 | "z": "4c3b05bf.206fbc", 27 | "name": "", 28 | "active": true, 29 | "tosidebar": true, 30 | "console": false, 31 | "tostatus": false, 32 | "complete": "false", 33 | "x": 630, 34 | "y": 180, 35 | "wires": [] 36 | }, 37 | { 38 | "id": "2749d98c.8b80b6", 39 | "type": "inject", 40 | "z": "4c3b05bf.206fbc", 41 | "name": "Trigger", 42 | "topic": "", 43 | "payload": "", 44 | "payloadType": "str", 45 | "repeat": "", 46 | "crontab": "", 47 | "once": false, 48 | "onceDelay": 0.1, 49 | "x": 190, 50 | "y": 180, 51 | "wires": [ 52 | [ 53 | "fc156449.abcde8" 54 | ] 55 | ] 56 | }, 57 | { 58 | "id": "2b4da824.b1d358", 59 | "type": "docker-images", 60 | "z": "4c3b05bf.206fbc", 61 | "name": "", 62 | "config": "579edabe.09fe34", 63 | "x": 380, 64 | "y": 240, 65 | "wires": [ 66 | [ 67 | "dcf691b9.d092f" 68 | ] 69 | ] 70 | }, 71 | { 72 | "id": "fc156449.abcde8", 73 | "type": "docker-containers", 74 | "z": "4c3b05bf.206fbc", 75 | "name": "", 76 | "config": "579edabe.09fe34", 77 | "x": 390, 78 | "y": 180, 79 | "wires": [ 80 | [ 81 | "dcf691b9.d092f" 82 | ] 83 | ] 84 | }, 85 | { 86 | "id": "45796a6b.e92334", 87 | "type": "inject", 88 | "z": "4c3b05bf.206fbc", 89 | "name": "Trigger", 90 | "topic": "", 91 | "payload": "", 92 | "payloadType": "str", 93 | "repeat": "", 94 | "crontab": "", 95 | "once": false, 96 | "onceDelay": 0.1, 97 | "x": 190, 98 | "y": 240, 99 | "wires": [ 100 | [ 101 | "2b4da824.b1d358" 102 | ] 103 | ] 104 | }, 105 | { 106 | "id": "84dfb014.8caf", 107 | "type": "docker-container-actions", 108 | "z": "4c3b05bf.206fbc", 109 | "name": "", 110 | "config": "579edabe.09fe34", 111 | "container": "test", 112 | "action": "", 113 | "x": 400, 114 | "y": 300, 115 | "wires": [ 116 | [ 117 | "dcf691b9.d092f" 118 | ] 119 | ] 120 | }, 121 | { 122 | "id": "bdc65a41.31f6a8", 123 | "type": "inject", 124 | "z": "4c3b05bf.206fbc", 125 | "name": "start", 126 | "topic": "", 127 | "payload": "start", 128 | "payloadType": "str", 129 | "repeat": "", 130 | "crontab": "", 131 | "once": false, 132 | "onceDelay": 0.1, 133 | "x": 190, 134 | "y": 300, 135 | "wires": [ 136 | [ 137 | "84dfb014.8caf" 138 | ] 139 | ] 140 | }, 141 | { 142 | "id": "f2535691.050318", 143 | "type": "inject", 144 | "z": "4c3b05bf.206fbc", 145 | "name": "stop", 146 | "topic": "", 147 | "payload": "stop", 148 | "payloadType": "str", 149 | "repeat": "", 150 | "crontab": "", 151 | "once": false, 152 | "onceDelay": 0.1, 153 | "x": 190, 154 | "y": 340, 155 | "wires": [ 156 | [ 157 | "84dfb014.8caf" 158 | ] 159 | ] 160 | }, 161 | { 162 | "id": "579edabe.09fe34", 163 | "type": "docker-configuration", 164 | "z": "", 165 | "host": "localhost", 166 | "port": "2375" 167 | } 168 | ] -------------------------------------------------------------------------------- /examples/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naimo84/node-red-contrib-dockerode/d04142772dfd7e101a86e9dbd02710812169e840/examples/flow.png -------------------------------------------------------------------------------- /examples/flows.json: -------------------------------------------------------------------------------- 1 | [{"id":"30e2a557.11c8fa","type":"tab","label":"List","disabled":false,"info":""},{"id":"2c4e61f8.7fa14e","type":"tab","label":"Action Inspect","disabled":false,"info":""},{"id":"bc635cab.6a09d","type":"tab","label":"Events","disabled":false,"info":""},{"id":"6f250d7d.f92b84","type":"tab","label":"Container Actions","disabled":false,"info":""},{"id":"49886661.38cda8","type":"tab","label":"Image Actions","disabled":false,"info":""},{"id":"636535a8.0b3b9c","type":"tab","label":"Service Actions","disabled":false,"info":""},{"id":"ba1fa15.d0ffa6","type":"tab","label":"Task Actions","disabled":false,"info":""},{"id":"873fccf9.b9f54","type":"tab","label":"Node Actions","disabled":false,"info":""},{"id":"970da91d.a3a7a8","type":"tab","label":"Secret Actions","disabled":false,"info":""},{"id":"f47b225e.85ceb","type":"tab","label":"Network Actions","disabled":false,"info":""},{"id":"b94f799a.9fef68","type":"tab","label":"Volume Actions","disabled":false,"info":""},{"id":"913a0952.76d548","type":"tab","label":"Plugin Action","disabled":false,"info":""},{"id":"789ca14c.874fe","type":"docker-configuration","z":"","host":"/var/run/docker.sock","port":""},{"id":"30d46297.30cefe","type":"debug","z":"30e2a557.11c8fa","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":750,"y":400,"wires":[]},{"id":"84b666c4.8a2fd8","type":"docker-secrets","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":520,"wires":[["30d46297.30cefe"]]},{"id":"f0b68932.58f9e8","type":"docker-volumes","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":580,"wires":[["30d46297.30cefe"]]},{"id":"d703e731.486808","type":"docker-plugins","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":640,"wires":[["30d46297.30cefe"]]},{"id":"2309b83e.56d988","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":640,"wires":[["d703e731.486808"]]},{"id":"163ca0ac.4f1cbf","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":520,"wires":[["84b666c4.8a2fd8"]]},{"id":"a195e180.a8cfa","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":460,"wires":[["7aa92544.b7c2fc"]]},{"id":"e8d6d08d.ab969","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":580,"wires":[["f0b68932.58f9e8"]]},{"id":"529d35eb.7bf70c","type":"docker-nodes","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":470,"y":400,"wires":[["30d46297.30cefe"]]},{"id":"d5d5e1c8.69158","type":"docker-tasks","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":470,"y":340,"wires":[["30d46297.30cefe"]]},{"id":"5fd6bae4.05f754","type":"docker-services","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":280,"wires":[["30d46297.30cefe"]]},{"id":"ad8a94b.2fb9268","type":"docker-containers","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":490,"y":220,"wires":[["30d46297.30cefe"]]},{"id":"66341f67.50435","type":"docker-images","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":160,"wires":[["30d46297.30cefe"]]},{"id":"b9c3164b.b25508","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":400,"wires":[["529d35eb.7bf70c"]]},{"id":"5830e11c.dff72","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":280,"wires":[["5fd6bae4.05f754"]]},{"id":"345fedac.247432","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":220,"wires":[["ad8a94b.2fb9268"]]},{"id":"1a4945cc.f551fa","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":340,"wires":[["d5d5e1c8.69158"]]},{"id":"d41c87be.bfcee8","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":160,"wires":[["66341f67.50435"]]},{"id":"7aa92544.b7c2fc","type":"docker-networks","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":460,"wires":[["30d46297.30cefe"]]},{"id":"f5f23376.19d42","type":"docker-network-actions","z":"2c4e61f8.7fa14e","name":"","config":"789ca14c.874fe","network":"docker-Traefik_private","action":"","x":600,"y":480,"wires":[["fe027d79.b1ae5"]]},{"id":"ee4949ad.63e9c8","type":"docker-volume-actions","z":"2c4e61f8.7fa14e","name":"","config":"789ca14c.874fe","volume":"896bbbffe6e824882e95270f8caad2aba25a67df76ccec42c837355985edfdff","action":"","x":600,"y":540,"wires":[["fe027d79.b1ae5"]]},{"id":"1e0cf6f2.869b79","type":"docker-secret-actions","z":"2c4e61f8.7fa14e","name":"","config":"789ca14c.874fe","secret":"basic-auth-user","action":"inspect","x":600,"y":420,"wires":[["fe027d79.b1ae5"]]},{"id":"27ebd8db.654b68","type":"docker-image-actions","z":"2c4e61f8.7fa14e","name":"","config":"789ca14c.874fe","image":"sha256:ed82e641b0d524c305ddc77a8d458c1f1e9197b895555dc89941ae807f7eed4c","action":"","x":590,"y":120,"wires":[["fe027d79.b1ae5"]]},{"id":"fe027d79.b1ae5","type":"debug","z":"2c4e61f8.7fa14e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":830,"y":120,"wires":[]},{"id":"6c92b5dd.d259ac","type":"change","z":"2c4e61f8.7fa14e","name":"","rules":[{"t":"move","p":"payload.action","pt":"msg","to":"action","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":350,"y":120,"wires":[["27ebd8db.654b68","6bb67a98.2df094","41c0f68.eead708","ea911dad.637cb","c5926c78.5c357","1e0cf6f2.869b79","f5f23376.19d42","ee4949ad.63e9c8","493fedf0.d14764"]]},{"id":"2cfdabf6.6645f4","type":"inject","z":"2c4e61f8.7fa14e","name":"","topic":"","payload":"{\"action\":\"inspect\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":120,"wires":[["6c92b5dd.d259ac"]]},{"id":"723208fd.3f2a28","type":"docker-events","z":"bc635cab.6a09d","name":"","config":"789ca14c.874fe","x":220,"y":140,"wires":[["10034a13.fde4b6"]]},{"id":"10034a13.fde4b6","type":"debug","z":"bc635cab.6a09d","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":380,"y":140,"wires":[]},{"id":"493fedf0.d14764","type":"docker-plugin-actions","z":"2c4e61f8.7fa14e","name":"","config":"789ca14c.874fe","plugin":"docker.io/vieux/sshfs:latest","action":"","x":590,"y":600,"wires":[["fe027d79.b1ae5"]]},{"id":"c5926c78.5c357","type":"docker-node-actions","z":"2c4e61f8.7fa14e","name":"","config":"789ca14c.874fe","node":"docker-desktop","action":"","x":590,"y":360,"wires":[["fe027d79.b1ae5"]]},{"id":"ea911dad.637cb","type":"docker-task-actions","z":"2c4e61f8.7fa14e","name":"","config":"789ca14c.874fe","task":"r1qhhym0s5gzqf5uwtijmhf73","action":"","x":590,"y":300,"wires":[["fe027d79.b1ae5"]]},{"id":"41c0f68.eead708","type":"docker-service-actions","z":"2c4e61f8.7fa14e","name":"","config":"789ca14c.874fe","service":"docker-Traefik_whoami","action":"","x":600,"y":240,"wires":[["fe027d79.b1ae5"]]},{"id":"f4c18bb.918cc78","type":"docker-image-actions","z":"49886661.38cda8","name":"","config":"789ca14c.874fe","image":"sha256:ed82e641b0d524c305ddc77a8d458c1f1e9197b895555dc89941ae807f7eed4c","action":"","x":610,"y":40,"wires":[["ba46b4d.2c9f048"]]},{"id":"ba46b4d.2c9f048","type":"debug","z":"49886661.38cda8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":810,"y":40,"wires":[]},{"id":"8c3fd148.02a79","type":"change","z":"49886661.38cda8","name":"","rules":[{"t":"move","p":"payload.action","pt":"msg","to":"action","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":40,"wires":[["f4c18bb.918cc78"]]},{"id":"bffdd600.eef378","type":"inject","z":"49886661.38cda8","name":"","topic":"","payload":"{\"action\":\"history\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":40,"wires":[["8c3fd148.02a79"]]},{"id":"c64020dc.bf9ab","type":"inject","z":"49886661.38cda8","name":"","topic":"","payload":"{\"action\":\"tag\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":100,"wires":[["8c3fd148.02a79"]]},{"id":"da289ce.7769e6","type":"docker-container-actions","z":"6f250d7d.f92b84","name":"","config":"789ca14c.874fe","container":"docker-Traefik_traefik.1.ifkm9pi1004lwrojz075i1ygu","action":"","cmd":"","x":630,"y":80,"wires":[["7f849cff.3d1294"]]},{"id":"538a7ca.0d25a84","type":"inject","z":"6f250d7d.f92b84","name":"","topic":"","payload":"{\"action\":\"inspect\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":80,"wires":[["794ea30a.f7dc5c"]]},{"id":"7f849cff.3d1294","type":"debug","z":"6f250d7d.f92b84","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":850,"y":80,"wires":[]},{"id":"794ea30a.f7dc5c","type":"change","z":"6f250d7d.f92b84","name":"","rules":[{"t":"move","p":"payload.action","pt":"msg","to":"action","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":80,"wires":[["da289ce.7769e6"]]},{"id":"16e75790.fbe098","type":"inject","z":"6f250d7d.f92b84","name":"","topic":"","payload":"{\"action\":\"stop\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":200,"wires":[["794ea30a.f7dc5c"]]},{"id":"50fccb82.4eb7e4","type":"docker-service-actions","z":"636535a8.0b3b9c","name":"","config":"789ca14c.874fe","service":"docker-Traefik_whoami","action":"","x":580,"y":60,"wires":[["d783ba3b.488fe8"]]},{"id":"d78f9d0b.94362","type":"inject","z":"636535a8.0b3b9c","name":"","topic":"","payload":"{\"action\":\"inspect\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":60,"wires":[["b55c823f.737d1"]]},{"id":"b55c823f.737d1","type":"change","z":"636535a8.0b3b9c","name":"","rules":[{"t":"move","p":"payload.action","pt":"msg","to":"action","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":370,"y":60,"wires":[["50fccb82.4eb7e4"]]},{"id":"7d53ec3f.8430a4","type":"inject","z":"636535a8.0b3b9c","name":"","topic":"","payload":"{\"action\":\"logs\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":120,"wires":[["b55c823f.737d1"]]},{"id":"56234ae4.4730e4","type":"inject","z":"636535a8.0b3b9c","name":"","topic":"","payload":"{\"action\":\"remove\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":180,"wires":[["b55c823f.737d1"]]},{"id":"b6bab857.4596a8","type":"inject","z":"6f250d7d.f92b84","name":"","topic":"","payload":"{\"action\":\"start\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":140,"wires":[["794ea30a.f7dc5c"]]},{"id":"6bb67a98.2df094","type":"docker-container-actions","z":"2c4e61f8.7fa14e","name":"","config":"789ca14c.874fe","container":"nodered_nodered_1","action":"","cmd":"","x":610,"y":180,"wires":[["fe027d79.b1ae5"]]},{"id":"d783ba3b.488fe8","type":"debug","z":"636535a8.0b3b9c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":900,"y":60,"wires":[]},{"id":"2d204398.19272c","type":"docker-task-actions","z":"ba1fa15.d0ffa6","name":"","config":"789ca14c.874fe","task":"ayczjadojmq9ddnwihkapbyu4","action":"","x":600,"y":80,"wires":[["41973181.46586"]]},{"id":"14a642de.dcb2ad","type":"inject","z":"ba1fa15.d0ffa6","name":"","topic":"","payload":"{\"action\":\"inspect\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":80,"wires":[["b7f3d9ed.313e88"]]},{"id":"b7f3d9ed.313e88","type":"change","z":"ba1fa15.d0ffa6","name":"","rules":[{"t":"move","p":"payload.action","pt":"msg","to":"action","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":80,"wires":[["2d204398.19272c"]]},{"id":"41973181.46586","type":"debug","z":"ba1fa15.d0ffa6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":780,"y":80,"wires":[]},{"id":"d326f1b7.f7c55","type":"inject","z":"873fccf9.b9f54","name":"","topic":"","payload":"{\"action\":\"inspect\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":40,"wires":[["c7409940.432e18"]]},{"id":"c7409940.432e18","type":"change","z":"873fccf9.b9f54","name":"","rules":[{"t":"move","p":"payload.action","pt":"msg","to":"action","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":40,"wires":[["a886ea87.f59a18"]]},{"id":"e1970dec.01472","type":"debug","z":"873fccf9.b9f54","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":40,"wires":[]},{"id":"a886ea87.f59a18","type":"docker-node-actions","z":"873fccf9.b9f54","name":"","config":"789ca14c.874fe","node":"docker-desktop","action":"","x":610,"y":40,"wires":[["e1970dec.01472"]]},{"id":"497a0f95.d9d6e","type":"inject","z":"970da91d.a3a7a8","name":"","topic":"","payload":"{\"action\":\"inspect\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":40,"wires":[["6f67a463.46900c"]]},{"id":"6f67a463.46900c","type":"change","z":"970da91d.a3a7a8","name":"","rules":[{"t":"move","p":"payload.action","pt":"msg","to":"action","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":40,"wires":[["4eaf3a7b.b530a4"]]},{"id":"90d7276e.0467e8","type":"debug","z":"970da91d.a3a7a8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":40,"wires":[]},{"id":"4eaf3a7b.b530a4","type":"docker-secret-actions","z":"970da91d.a3a7a8","name":"","config":"789ca14c.874fe","secret":"basic-auth-user","action":"","x":600,"y":40,"wires":[["90d7276e.0467e8"]]},{"id":"8623cd71.8cd59","type":"inject","z":"f47b225e.85ceb","name":"","topic":"","payload":"{\"action\":\"inspect\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":40,"wires":[["b4bf5d0b.5ce3e"]]},{"id":"b4bf5d0b.5ce3e","type":"change","z":"f47b225e.85ceb","name":"","rules":[{"t":"move","p":"payload.action","pt":"msg","to":"action","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":40,"wires":[["f8e8bf6a.d58c2"]]},{"id":"cac309f0.f43ec8","type":"debug","z":"f47b225e.85ceb","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":40,"wires":[]},{"id":"f8e8bf6a.d58c2","type":"docker-network-actions","z":"f47b225e.85ceb","name":"","config":"789ca14c.874fe","network":"docker-Traefik_private","action":"","x":600,"y":40,"wires":[["cac309f0.f43ec8"]]},{"id":"6537538f.3adb8c","type":"inject","z":"b94f799a.9fef68","name":"","topic":"","payload":"{\"action\":\"inspect\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":40,"wires":[["a2ad97e4.c280d8"]]},{"id":"a2ad97e4.c280d8","type":"change","z":"b94f799a.9fef68","name":"","rules":[{"t":"move","p":"payload.action","pt":"msg","to":"action","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":40,"wires":[["294232b5.bb9e0e"]]},{"id":"be34e8f3.9e6a08","type":"debug","z":"b94f799a.9fef68","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":40,"wires":[]},{"id":"294232b5.bb9e0e","type":"docker-volume-actions","z":"b94f799a.9fef68","name":"","config":"789ca14c.874fe","volume":"docker_mailconfig","action":"","x":600,"y":40,"wires":[["be34e8f3.9e6a08"]]},{"id":"116d78d0.7651b7","type":"docker-plugin-actions","z":"913a0952.76d548","name":"","config":"789ca14c.874fe","plugin":"docker.io/vieux/sshfs:latest","action":"","x":610,"y":40,"wires":[["9ae2cbd1.faded8"]]},{"id":"cbd4ec33.3a2b5","type":"inject","z":"913a0952.76d548","name":"","topic":"","payload":"{\"action\":\"inspect\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":40,"wires":[["b9fcfe0b.69024"]]},{"id":"b9fcfe0b.69024","type":"change","z":"913a0952.76d548","name":"","rules":[{"t":"move","p":"payload.action","pt":"msg","to":"action","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":40,"wires":[["116d78d0.7651b7"]]},{"id":"9ae2cbd1.faded8","type":"debug","z":"913a0952.76d548","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":790,"y":40,"wires":[]}] -------------------------------------------------------------------------------- /examples/list-flows.json: -------------------------------------------------------------------------------- 1 | [{"id":"30e2a557.11c8fa","type":"tab","label":"List","disabled":false,"info":""},{"id":"30d46297.30cefe","type":"debug","z":"30e2a557.11c8fa","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":750,"y":400,"wires":[]},{"id":"84b666c4.8a2fd8","type":"docker-secrets","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":520,"wires":[["30d46297.30cefe"]]},{"id":"f0b68932.58f9e8","type":"docker-volumes","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":580,"wires":[["30d46297.30cefe"]]},{"id":"d703e731.486808","type":"docker-plugins","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":640,"wires":[["30d46297.30cefe"]]},{"id":"2309b83e.56d988","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":640,"wires":[["d703e731.486808"]]},{"id":"163ca0ac.4f1cbf","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":520,"wires":[["84b666c4.8a2fd8"]]},{"id":"a195e180.a8cfa","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":460,"wires":[["7aa92544.b7c2fc"]]},{"id":"e8d6d08d.ab969","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":580,"wires":[["f0b68932.58f9e8"]]},{"id":"529d35eb.7bf70c","type":"docker-nodes","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":470,"y":400,"wires":[["30d46297.30cefe"]]},{"id":"d5d5e1c8.69158","type":"docker-tasks","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":470,"y":340,"wires":[["30d46297.30cefe"]]},{"id":"5fd6bae4.05f754","type":"docker-services","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":280,"wires":[["30d46297.30cefe"]]},{"id":"ad8a94b.2fb9268","type":"docker-containers","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":490,"y":220,"wires":[["30d46297.30cefe"]]},{"id":"66341f67.50435","type":"docker-images","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":160,"wires":[["30d46297.30cefe"]]},{"id":"b9c3164b.b25508","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":400,"wires":[["529d35eb.7bf70c"]]},{"id":"5830e11c.dff72","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":280,"wires":[["5fd6bae4.05f754"]]},{"id":"345fedac.247432","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":220,"wires":[["ad8a94b.2fb9268"]]},{"id":"1a4945cc.f551fa","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":340,"wires":[["d5d5e1c8.69158"]]},{"id":"d41c87be.bfcee8","type":"inject","z":"30e2a557.11c8fa","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":160,"wires":[["66341f67.50435"]]},{"id":"7aa92544.b7c2fc","type":"docker-networks","z":"30e2a557.11c8fa","name":"","config":"789ca14c.874fe","x":480,"y":460,"wires":[["30d46297.30cefe"]]},{"id":"789ca14c.874fe","type":"docker-configuration","z":"","host":"/var/run/docker.sock","port":""}] -------------------------------------------------------------------------------- /examples/support_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naimo84/node-red-contrib-dockerode/d04142772dfd7e101a86e9dbd02710812169e840/examples/support_banner.png -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var ts = require("gulp-typescript"); 3 | var tsProject = ts.createProject("tsconfig.json"); 4 | var sourcemaps = require('gulp-sourcemaps'); 5 | var nodemon = require('gulp-nodemon'); 6 | var watch = require('gulp-watch'); 7 | 8 | var paths = { 9 | pages: ['src/*.html'], 10 | src: 'src', 11 | dist: 'dist' 12 | }; 13 | 14 | function copyHtml() { 15 | gulp.src('src/icons/*.png', { base: paths.src }) 16 | .pipe(gulp.dest(paths.dist)) 17 | return gulp.src(paths.pages, { base: paths.src }) 18 | .pipe(gulp.dest(paths.dist)); 19 | } 20 | 21 | gulp.task("copy-html", copyHtml); 22 | 23 | gulp.task('develop', function (done) { 24 | var stream = nodemon({ 25 | legacyWatch: true, 26 | exec: 'node --inspect=9229 --preserve-symlinks --experimental-modules --trace-warnings /usr/lib/node_modules/node-red/red.js', 27 | ext: '*.js', 28 | watch: [paths.dist], 29 | ignore: ["*.map"], 30 | done: done, 31 | verbose: true, 32 | delay: 10000, 33 | env: { "NO_UPDATE_NOTIFIER": "1" } 34 | }); 35 | 36 | copyHtml(); 37 | tsProject.src() 38 | .pipe(sourcemaps.init()) 39 | .pipe(tsProject()) 40 | .js 41 | .pipe(sourcemaps.write('.')) 42 | .pipe(gulp.dest(paths.dist)); 43 | 44 | watch(paths.pages).on('change', () => { 45 | copyHtml(); 46 | stream.emit('restart', 10000) 47 | }); 48 | 49 | watch('src/*.ts').on('change', () => { 50 | tsProject.src() 51 | .pipe(sourcemaps.init()) 52 | .pipe(tsProject()) 53 | .js 54 | .pipe(sourcemaps.write('.')) 55 | .pipe(gulp.dest(paths.dist)); 56 | 57 | stream.emit('restart', 10000) 58 | }); 59 | 60 | stream 61 | .on('restart', function () { 62 | console.log('restarted!') 63 | }) 64 | .on('crash', function () { 65 | console.error('Application has crashed!\n') 66 | stream.emit('restart', 10000) // restart the server in 10 seconds 67 | }) 68 | }) 69 | 70 | gulp.task("default", gulp.series( 71 | gulp.parallel('copy-html'), 72 | () => { 73 | return tsProject.src() 74 | .pipe(sourcemaps.init()) 75 | .pipe(tsProject()) 76 | .js 77 | .pipe(sourcemaps.write('.')) 78 | .pipe(gulp.dest(paths.dist)); 79 | }) 80 | ); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-dockerode", 3 | "version": "0.15.0", 4 | "description": "node-red nodes to communicate with docker", 5 | "author": { 6 | "name": "naimo84", 7 | "email": "git@neumann-benjamin.de" 8 | }, 9 | "contributors": [ 10 | { 11 | "name": "naimo84", 12 | "email": "git@neumann-benjamin.de" 13 | }, 14 | { 15 | "name": "ethanbrooks", 16 | "email": "ethan.brooks@gmail.com" 17 | } 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/naimo84/node-red-contrib-dockerode" 22 | }, 23 | "keywords": [ 24 | "node-red", 25 | "docker" 26 | ], 27 | "license": "MIT", 28 | "licenses": [ 29 | { 30 | "type": "MIT", 31 | "url": "https://github.com/naimo84/node-red-contrib-dockerode/blob/master/LICENSE" 32 | } 33 | ], 34 | "scripts": { 35 | "build": "gulp", 36 | "release": "release-it", 37 | "dev": "gulp develop", 38 | "nodered": "node-red" 39 | }, 40 | "node-red": { 41 | "nodes": { 42 | "docker-configuration": "dist/docker-configuration.js", 43 | "docker-events": "dist/docker-events.js", 44 | "docker-swarm-actions": "dist/docker-swarm-actions.js", 45 | "docker-engine-actions": "dist/docker-engine-actions.js", 46 | "docker-config-actions": "dist/docker-config-actions.js", 47 | "docker-image-actions": "dist/docker-image-actions.js", 48 | "docker-container-actions": "dist/docker-container-actions.js", 49 | "docker-service-actions": "dist/docker-service-actions.js", 50 | "docker-task-actions": "dist/docker-task-actions.js", 51 | "docker-node-actions": "dist/docker-node-actions.js", 52 | "docker-secret-actions": "dist/docker-secret-actions.js", 53 | "docker-network-actions": "dist/docker-network-actions.js", 54 | "docker-volume-actions": "dist/docker-volume-actions.js", 55 | "docker-plugin-actions": "dist/docker-plugin-actions.js" 56 | } 57 | }, 58 | "dependencies": { 59 | "dockerode": "^3.3.1", 60 | "stream": "0.0.2", 61 | "debug": "^4.3.2" 62 | }, 63 | "devDependencies": { 64 | "@types/dockerode": "^2.5.20", 65 | "@types/node": "^12.7.2", 66 | "@types/node-red": "^0.20.0", 67 | "commitlint": "^8.1.0", 68 | "dockerode-mock": "^0.3.2", 69 | "gulp": "^4.0.2", 70 | "gulp-nodemon": "^2.4.2", 71 | "gulp-sourcemaps": "^2.6.5", 72 | "gulp-typescript": "^6.0.0-alpha.1", 73 | "gulp-watch": "^5.0.1", 74 | "husky": "^3.0.5", 75 | "mocha": "6.2.0", 76 | "node-red": "3.1.9", 77 | "node-red-contrib-mock-node": "^0.4.0", 78 | "node-red-node-test-helper": "^0.2.3", 79 | "should": "13.2.3", 80 | "release-it": "^14.11.8", 81 | "typescript": "^3.5.3" 82 | }, 83 | "release-it": { 84 | "git": { 85 | "commitMessage": "chore: release v${version}", 86 | "changelog": "npx auto-changelog --stdout --commit-limit false --unreleased --template https://raw.githubusercontent.com/release-it/release-it/master/templates/changelog-compact.hbs" 87 | }, 88 | "github": { 89 | "release": true 90 | }, 91 | "npm": { 92 | "publish": false 93 | }, 94 | "hooks": { 95 | "after:bump": "npx auto-changelog -p" 96 | } 97 | }, 98 | "commitlint": { 99 | "format": { 100 | "helpUrl": "test" 101 | }, 102 | "rules": { 103 | "body-leading-blank": [ 104 | 1, 105 | "always" 106 | ], 107 | "footer-leading-blank": [ 108 | 1, 109 | "always" 110 | ], 111 | "header-max-length": [ 112 | 2, 113 | "always", 114 | 72 115 | ], 116 | "scope-case": [ 117 | 2, 118 | "always", 119 | "lower-case" 120 | ], 121 | "subject-case": [ 122 | 2, 123 | "never", 124 | [ 125 | "sentence-case", 126 | "start-case", 127 | "pascal-case", 128 | "upper-case" 129 | ] 130 | ], 131 | "subject-empty": [ 132 | 2, 133 | "never" 134 | ], 135 | "subject-full-stop": [ 136 | 2, 137 | "never", 138 | "." 139 | ], 140 | "type-case": [ 141 | 2, 142 | "always", 143 | "lower-case" 144 | ], 145 | "type-empty": [ 146 | 2, 147 | "never" 148 | ], 149 | "type-enum": [ 150 | 2, 151 | "always", 152 | [ 153 | "build", 154 | "chore", 155 | "ci", 156 | "docs", 157 | "feat", 158 | "fix", 159 | "perf", 160 | "refactor", 161 | "revert", 162 | "style", 163 | "test" 164 | ] 165 | ] 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/docker-config-actions.html: -------------------------------------------------------------------------------- 1 | 2 | 62 | 63 | 90 | 91 | -------------------------------------------------------------------------------- /src/docker-config-actions.ts: -------------------------------------------------------------------------------- 1 | import { Red, Node } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | module.exports = function (RED: Red) { 6 | 7 | function DockerConfigAction(n: any) { 8 | RED.nodes.createNode(this, n); 9 | let config = RED.nodes.getNode(n.config) as unknown as DockerConfiguration; 10 | let client = config.getClient(); 11 | this.on('input', (msg) => { 12 | RED.log.debug(msg); 13 | let configId: string = n.config || msg.payload.configId || msg.configId || undefined; 14 | let action = n.action || msg.action || msg.payload.action || undefined; 15 | let options = n.options || msg.options || msg.payload.options || undefined; 16 | if (configId === undefined && !['list', 'prune', 'create'].includes(action)) { 17 | this.error("Config id/name must be provided via configuration or via `msg.config`"); 18 | return; 19 | } 20 | this.status({}); 21 | executeAction(configId, options, client, action, this,msg); 22 | }); 23 | 24 | function executeAction(configId: string, options: any, client: Dockerode, action: string, node: Node,msg) { 25 | 26 | let config = client.getConfig(configId); 27 | 28 | switch (action) { 29 | 30 | case 'list': 31 | // https://docs.docker.com/engine/api/v1.40/#operation/ConfigList 32 | client.listConfigs({ all: true }) 33 | .then(res => { 34 | node.status({ fill: 'green', shape: 'dot', text: configId + ' started' }); 35 | node.send(Object.assign(msg,{ payload: res })); 36 | }).catch(err => { 37 | if (err.statusCode === 400) { 38 | node.error(`Bad parameter: ${err.reason}`, msg); 39 | node.send({ payload: err }); 40 | } else if (err.statusCode === 500) { 41 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 42 | node.send({ payload: err }); 43 | } else { 44 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 45 | return; 46 | } 47 | }); 48 | break; 49 | 50 | case 'create': 51 | // https://docs.docker.com/engine/api/v1.40/#operation/ConfigCreate 52 | client.createConfig(options) 53 | .then(res => { 54 | node.status({ fill: 'green', shape: 'dot', text: configId + ' remove' }); 55 | node.send(Object.assign(msg,{ payload: res })); 56 | }).catch(err => { 57 | if (err.statusCode === 500) { 58 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 59 | node.send({ payload: err }); 60 | } else if (err.statusCode === 409) { 61 | node.error(`Name conflicts with an existing objectd: [${configId}]`, msg); 62 | node.send({ payload: err }); 63 | } else { 64 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 65 | return; 66 | } 67 | }); 68 | break; 69 | 70 | case 'inspect': 71 | // https://docs.docker.com/engine/api/v1.40/#operation/ConfigInspect 72 | config.inspect() 73 | .then(res => { 74 | node.status({ fill: 'green', shape: 'dot', text: configId + ' started' }); 75 | node.send(Object.assign(msg,{ payload: res })); 76 | }).catch(err => { 77 | if (err.statusCode === 503) { 78 | node.error(`Node is not part of a swarm: [${configId}]`, msg); 79 | node.send({ payload: err }); 80 | } else if (err.statusCode === 404) { 81 | node.error(`Config not found: [${configId}]`, msg); 82 | node.send({ payload: err }); 83 | } else if (err.statusCode === 500) { 84 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 85 | node.send({ payload: err }); 86 | } else { 87 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 88 | return; 89 | } 90 | }); 91 | break; 92 | 93 | case 'remove': 94 | // https://docs.docker.com/engine/api/v1.40/#operation/ConfigDelete 95 | config.remove() 96 | .then(res => { 97 | node.status({ fill: 'green', shape: 'dot', text: configId + ' remove' }); 98 | node.send(Object.assign(msg,{ payload: res })); 99 | }).catch(err => { 100 | if (err.statusCode === 503) { 101 | node.error(`Node is not part of a swarm: [${configId}]`, msg); 102 | node.send({ payload: err }); 103 | } else if (err.statusCode === 404) { 104 | node.error(`Config not found: [${configId}]`, msg); 105 | node.send({ payload: err }); 106 | } else if(err.statusCode === 500) { 107 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 108 | node.send({ payload: err }); 109 | } else { 110 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 111 | return; 112 | } 113 | }); 114 | break; 115 | 116 | case 'update': 117 | // https://docs.docker.com/engine/api/v1.40/#operation/ConfigUpdate 118 | config.update() 119 | .then(res => { 120 | node.status({ fill: 'green', shape: 'dot', text: configId + ' remove' }); 121 | node.send(Object.assign(msg,{ payload: res })); 122 | }).catch(err => { 123 | if (err.statusCode === 503) { 124 | node.error(`Node is not part of a swarm: [${configId}]`, msg); 125 | node.send({ payload: err }); 126 | } else if (err.statusCode === 400) { 127 | node.error(`Bad parameter: [${configId}]`, msg); 128 | node.send({ payload: err }); 129 | } else if (err.statusCode === 404) { 130 | node.error(`Config not found: [${configId}]`, msg); 131 | node.send({ payload: err }); 132 | } else if(err.statusCode === 500) { 133 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 134 | node.send({ payload: err }); 135 | } else { 136 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 137 | return; 138 | } 139 | }); 140 | break; 141 | 142 | default: 143 | node.error(`Called with an unknown action: ${action}`, msg); 144 | return; 145 | } 146 | } 147 | } 148 | 149 | 150 | RED.httpAdmin.post("/configSearch", RED.auth.needsPermission('flows.write'), function (req, res) { 151 | RED.log.debug("POST /configSearch"); 152 | 153 | const nodeId = req.body.id; 154 | let config = RED.nodes.getNode(nodeId); 155 | 156 | discoverSonos(config, (configs) => { 157 | RED.log.debug("GET /configSearch: " + configs.length + " found"); 158 | res.json(configs); 159 | }); 160 | }); 161 | 162 | function discoverSonos(config, discoveryCallback) { 163 | let client = config.getClient(); 164 | client.listConfigs({ all: true }) 165 | .then(configs => discoveryCallback(configs)) 166 | .catch(err => this.error(err)); 167 | } 168 | 169 | RED.nodes.registerType('docker-config-actions', DockerConfigAction); 170 | } 171 | 172 | -------------------------------------------------------------------------------- /src/docker-configuration.html: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | -------------------------------------------------------------------------------- /src/docker-configuration.ts: -------------------------------------------------------------------------------- 1 | import { Red } from 'node-red'; 2 | import { readFileSync } from 'fs'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | 6 | export interface DockerConfiguration { 7 | host: string 8 | ca: string 9 | cert: string 10 | key: string 11 | port: number 12 | action: string 13 | container: string 14 | options: any 15 | getClient(): Dockerode 16 | } 17 | 18 | module.exports = function (RED: Red) { 19 | 20 | 21 | function DockerConfiguration(n) { 22 | RED.nodes.createNode(this, n); 23 | 24 | let node: DockerConfiguration = this; 25 | node.host = n.host; 26 | node.port = n.port || "2375"; 27 | node.options = n.options; 28 | node.ca = n.ca; 29 | node.key = n.key; 30 | node.cert = n.cert; 31 | 32 | node.getClient = (): Dockerode => { 33 | let dockeropt = {}; 34 | 35 | if (node.host.includes("docker.sock")) { 36 | dockeropt = { 37 | socketPath: node.host 38 | } 39 | } else { 40 | dockeropt = { 41 | host: node.host, 42 | port: node.port 43 | } 44 | } 45 | 46 | if (node.ca) { 47 | dockeropt = Object.assign(dockeropt, { 48 | ca: readFileSync(node.ca) 49 | }) 50 | } 51 | if (node.key) { 52 | dockeropt = Object.assign(dockeropt, { 53 | key: readFileSync(node.key) 54 | }) 55 | } 56 | if (node.cert) { 57 | dockeropt = Object.assign(dockeropt, { 58 | cert: readFileSync(node.cert) 59 | }) 60 | } 61 | return new Dockerode(dockeropt); 62 | }; 63 | } 64 | RED.nodes.registerType("docker-configuration", DockerConfiguration); 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/docker-engine-actions.html: -------------------------------------------------------------------------------- 1 | 2 | 68 | 69 | 96 | 97 | -------------------------------------------------------------------------------- /src/docker-engine-actions.ts: -------------------------------------------------------------------------------- 1 | import { Red, Node } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | module.exports = function (RED: Red) { 6 | 7 | function DockerEngineAction(n: any) { 8 | RED.nodes.createNode(this, n); 9 | let config = RED.nodes.getNode(n.config) as unknown as DockerConfiguration; 10 | let client = config.getClient(); 11 | this.on('input', (msg) => { 12 | 13 | let action = n.action || msg.action || msg.payload.action || undefined; 14 | let options = n.options || msg.options|| msg.options || msg.payload.options || undefined; 15 | let file = n.options || msg.options|| msg.options || undefined; 16 | let containerId: string = n.containerId || msg.payload.containerId || msg.containerId || n.containerName || msg.payload.containerName || msg.containerName || undefined; 17 | if (containerId === undefined && !['list', 'prune', 'create'].includes(action)) { 18 | this.error("Container id/name must be provided via configuration or via `msg.containerId`"); 19 | return; 20 | } 21 | this.status({}); 22 | executeAction(containerId, file, client, action, options, this,msg); 23 | }); 24 | 25 | function executeAction(containerId: string, file: File, client: Dockerode, action: string, options: any, node: Node,msg) { 26 | 27 | let engine = client; 28 | 29 | switch (action) { 30 | case 'auth': 31 | engine.checkAuth(options) 32 | .then(res => { 33 | node.status({ fill: 'green', shape: 'dot', text: ' remove' }); 34 | node.send(Object.assign(msg,{ payload: res })); 35 | }).catch(err => { 36 | if (err.statusCode === 500) { 37 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 38 | node.send({ payload: err }); 39 | } else { 40 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 41 | return; 42 | } 43 | }); 44 | break; 45 | case 'info': 46 | engine.info() 47 | .then(res => { 48 | node.status({ fill: 'green', shape: 'dot', text: ' remove' }); 49 | node.send(Object.assign(msg,{ payload: res })); 50 | }).catch(err => { 51 | if (err.statusCode === 500) { 52 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 53 | node.send({ payload: err }); 54 | } else { 55 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 56 | return; 57 | } 58 | }); 59 | break; 60 | 61 | case 'version': 62 | engine.version() 63 | .then(res => { 64 | node.status({ fill: 'green', shape: 'dot', text: 'Engine started' }); 65 | node.send(Object.assign(msg,{ payload: res })); 66 | }).catch(err => { 67 | if (err.statusCode === 500) { 68 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 69 | node.send({ payload: err }); 70 | } else { 71 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 72 | return; 73 | } 74 | }); 75 | break; 76 | case 'ping': 77 | engine.ping() 78 | .then(res => { 79 | node.status({ fill: 'green', shape: 'dot', text: ' remove' }); 80 | node.send(Object.assign(msg,{ payload: res })); 81 | }).catch(err => { 82 | if (err.statusCode === 500) { 83 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 84 | node.send({ payload: err }); 85 | } else { 86 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 87 | return; 88 | } 89 | }); 90 | break; 91 | case 'df': 92 | engine.df() 93 | .then(res => { 94 | node.status({ fill: 'green', shape: 'dot', text: ' remove' }); 95 | node.send(Object.assign(msg,{ payload: res })); 96 | }).catch(err => { 97 | if (err.statusCode === 500) { 98 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 99 | node.send({ payload: err }); 100 | } else { 101 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 102 | return; 103 | } 104 | }); 105 | break; 106 | case 'import-image': 107 | engine.importImage(options, file) 108 | .then(res => { 109 | node.status({ fill: 'green', shape: 'dot', text: ' remove' }); 110 | node.send(Object.assign(msg,{ payload: res })); 111 | }).catch(err => { 112 | if (err.statusCode === 500) { 113 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 114 | node.send({ payload: err }); 115 | } else { 116 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 117 | return; 118 | } 119 | }); 120 | break; 121 | /* case 'run': 122 | engine.run() 123 | .then(res => { 124 | node.status({ fill: 'green', shape: 'dot', text: ' remove' }); 125 | node.send(Object.assign(msg,{ payload: res })); 126 | }).catch(err => { 127 | if (err.statusCode === 500) { 128 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 129 | node.send({ payload: err }); 130 | } else { 131 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 132 | return; 133 | } 134 | }); 135 | break; 136 | */ 137 | case 'build': 138 | engine.buildImage(options) 139 | .then(res => { 140 | node.status({ fill: 'green', shape: 'dot', text: ' stopped' }); 141 | node.send(Object.assign(msg,{ payload: res })); 142 | }).catch(err => { 143 | if (err.statusCode === 500) { 144 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 145 | node.send({ payload: err }); 146 | } else { 147 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 148 | return; 149 | } 150 | }); 151 | break; 152 | 153 | 154 | case 'exec-start': 155 | engine.getExec(containerId).start(options) 156 | .then(res => { 157 | node.status({ fill: 'green', shape: 'dot', text: ' remove' }); 158 | node.send(Object.assign(msg,{ payload: res })); 159 | }).catch(err => { 160 | if (err.statusCode === 500) { 161 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 162 | node.send({ payload: err }); 163 | } else { 164 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 165 | return; 166 | } 167 | }); 168 | break; 169 | case 'exec-resize': 170 | engine.getExec(containerId).start(options) 171 | .then(res => { 172 | node.status({ fill: 'green', shape: 'dot', text: containerId + ' remove' }); 173 | node.send(Object.assign(msg,{ payload: res })); 174 | }).catch(err => { 175 | if (err.statusCode === 500) { 176 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 177 | node.send({ payload: err }); 178 | } else { 179 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 180 | return; 181 | } 182 | }); 183 | break; 184 | case 'exec-json': 185 | engine.getExec(containerId).inspect() 186 | .then(res => { 187 | node.status({ fill: 'green', shape: 'dot', text: containerId + ' remove' }); 188 | node.send(Object.assign(msg,{ payload: res })); 189 | }).catch(err => { 190 | if (err.statusCode === 500) { 191 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 192 | node.send({ payload: err }); 193 | } else { 194 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 195 | return; 196 | } 197 | }); 198 | break; 199 | default: 200 | node.error(`Called with an unknown action: ${action}`, msg); 201 | return; 202 | } 203 | } 204 | } 205 | 206 | RED.nodes.registerType('docker-engine-actions', DockerEngineAction); 207 | } 208 | 209 | -------------------------------------------------------------------------------- /src/docker-events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /src/docker-events.ts: -------------------------------------------------------------------------------- 1 | import { Red } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | 4 | export interface DockerEvent { 5 | Type?: string, 6 | Action?: string, 7 | time?: string, 8 | timeNano?: string 9 | } 10 | 11 | module.exports = function (RED: Red) { 12 | 13 | function DockerEvents(n: any) { 14 | let node: any = this as any; 15 | RED.nodes.createNode(node, n); 16 | 17 | let config = (RED.nodes.getNode(n.config) as unknown as DockerConfiguration); 18 | let client = config.getClient(); 19 | let globalEvents = null; 20 | 21 | node.on('input', async (msg, send, done) => { 22 | try { 23 | if(globalEvents !== null){ 24 | globalEvents.removeAllListeners(); 25 | globalEvents.destroy(); 26 | } 27 | 28 | const events: NodeJS.ReadableStream = await client.getEvents() 29 | globalEvents = events; 30 | node.status({ fill: 'green', shape: 'dot', text: 'node-red:common.status.connected' }); 31 | 32 | events.on('data', (data) => { 33 | let event: DockerEvent = {}; 34 | try { 35 | event = JSON.parse(data.toString()); 36 | } catch (e) { 37 | node.error('Error parsing JSON', msg); 38 | return 39 | } 40 | 41 | send({ 42 | _msgid: RED.util.generateId(), 43 | type: event.Type, 44 | action: event.Action, 45 | time: event.time, 46 | timeNano: event.timeNano, 47 | payload: event 48 | }); 49 | done(); 50 | }); 51 | 52 | events.on('close', () => { 53 | node.status({ fill: 'red', shape: 'ring', text: 'node-red:common.status.disconnected' }); 54 | node.warn('Docker event stream closed.'); 55 | send([null, { 56 | _msgid: RED.util.generateId(), 57 | type: 'dockerode', 58 | payload: { 59 | connected: false 60 | } 61 | }]) 62 | done(); 63 | }); 64 | events.on('error', (err) => { 65 | node.status({ fill: 'red', shape: 'ring', text: 'node-red:common.status.disconnected' }); 66 | send([null, { 67 | _msgid: RED.util.generateId(), 68 | type: 'dockerode', 69 | payload: { 70 | connected: false 71 | } 72 | }]) 73 | done('Error:', err); 74 | }); 75 | events.on('end', () => { 76 | node.status({ fill: 'yellow', shape: 'ring', text: 'stream ended' }); 77 | node.warn('Docker event stream ended.'); 78 | done(); 79 | }); 80 | } 81 | catch { 82 | console.error('Failed to attach docker event listener.'); 83 | send([null, { 84 | _msgid: RED.util.generateId(), 85 | type: 'dockerode', 86 | payload: { 87 | connected: false 88 | } 89 | }]) 90 | done(); 91 | } 92 | }) 93 | node.emit("input", {}); 94 | } 95 | 96 | RED.nodes.registerType('docker-events', DockerEvents); 97 | } 98 | -------------------------------------------------------------------------------- /src/docker-image-actions.html: -------------------------------------------------------------------------------- 1 | 2 | 71 | 72 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/docker-image-actions.ts: -------------------------------------------------------------------------------- 1 | import { Red, Node } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | module.exports = function (RED: Red) { 6 | 7 | 8 | function DockerImageAction(n: any) { 9 | RED.nodes.createNode(this, n); 10 | let config = RED.nodes.getNode(n.config) as unknown as DockerConfiguration; 11 | let client = config.getClient(); 12 | this.on('input', (msg) => { 13 | 14 | let imageId: string = n.image || msg.payload.imageId || msg.imageId || undefined; 15 | let action = n.action || msg.action || msg.payload.action || undefined; 16 | let options = {}; 17 | 18 | 19 | if (imageId === undefined && !['list', 'prune', 'create'].includes(action)) { 20 | this.error("Image id/name must be provided via configuration or via `msg.image`"); 21 | return; 22 | } 23 | this.status({}); 24 | executeAction(imageId, options, client, action, this,msg); 25 | }); 26 | 27 | function executeAction(imageId: string, options: any, client: Dockerode, action: string, node: Node,msg) { 28 | 29 | let image = client.getImage(imageId); 30 | 31 | switch (action) { 32 | 33 | case 'list': 34 | // https://docs.docker.com/engine/api/v1.40/#operation/ImagesList 35 | client.listImages({ all: true }) 36 | .then(res => { 37 | node.status({ fill: 'green', shape: 'dot', text: imageId + ' started' }); 38 | node.send(Object.assign(msg,{ payload: res })); 39 | }).catch(err => { 40 | if (err.statusCode === 400) { 41 | node.error(`Bad parameter: ${err.reason}`, msg); 42 | node.send({ payload: err }); 43 | } else if (err.statusCode === 500) { 44 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 45 | node.send({ payload: err }); 46 | } else { 47 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 48 | return; 49 | } 50 | }); 51 | break; 52 | 53 | case 'inspect': 54 | // https://docs.docker.com/engine/api/v1.40/#operation/ImageInspect 55 | image.inspect() 56 | .then(res => { 57 | node.status({ fill: 'green', shape: 'dot', text: imageId + ' started' }); 58 | node.send(Object.assign(msg,{ payload: res })); 59 | }).catch(err => { 60 | if (err.statusCode === 500) { 61 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 62 | node.send({ payload: err }); 63 | } else { 64 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 65 | return; 66 | } 67 | }); 68 | break; 69 | 70 | case 'create': 71 | // https://docs.docker.com/engine/api/v1.40/#operation/ImageCreate 72 | client.createImage(options) 73 | .then(res => { 74 | node.status({ fill: 'green', shape: 'dot', text: imageId + ' remove' }); 75 | node.send(Object.assign(msg,{ payload: res })); 76 | }).catch(err => { 77 | if (err.statusCode === 500) { 78 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 79 | node.send({ payload: err }); 80 | } else { 81 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 82 | return; 83 | } 84 | }); 85 | break; 86 | 87 | case 'remove': 88 | // https://docs.docker.com/engine/api/v1.40/#operation/ImageRemove 89 | image.remove() 90 | .then(res => { 91 | node.status({ fill: 'green', shape: 'dot', text: imageId + ' remove' }); 92 | node.send(Object.assign(msg,{ payload: res })); 93 | }).catch(err => { 94 | if (err.statusCode === 500) { 95 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 96 | node.send({ payload: err }); 97 | } else { 98 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 99 | return; 100 | } 101 | }); 102 | break; 103 | case 'history': 104 | // https://docs.docker.com/engine/api/v1.40/#operation/ImageHistory 105 | image.history() 106 | .then(res => { 107 | node.status({ fill: 'green', shape: 'dot', text: imageId + ' remove' }); 108 | node.send(Object.assign(msg,{ payload: res })); 109 | }).catch(err => { 110 | if (err.statusCode === 500) { 111 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 112 | node.send({ payload: err }); 113 | } else { 114 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 115 | return; 116 | } 117 | }); 118 | break; 119 | case 'tag': 120 | // https://docs.docker.com/engine/api/v1.40/#operation/ImageTag 121 | let repo = 'bla/bla'; 122 | let tag = 'Hello'; 123 | image.tag({"repo": repo, "tag": tag}) 124 | .then(res => { 125 | node.status({ fill: 'green', shape: 'dot', text: imageId + ' remove' }); 126 | node.send(Object.assign(msg,{ payload: res })); 127 | }).catch(err => { 128 | if (err.statusCode === 500) { 129 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 130 | node.send({ payload: err }); 131 | } else { 132 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 133 | return; 134 | } 135 | }); 136 | break; 137 | case 'push': 138 | // https://docs.docker.com/engine/api/v1.40/#operation/ImagePush 139 | image.push() 140 | .then(res => { 141 | node.status({ fill: 'green', shape: 'dot', text: imageId + ' remove' }); 142 | node.send(Object.assign(msg,{ payload: res })); 143 | }).catch(err => { 144 | if (err.statusCode === 500) { 145 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 146 | node.send({ payload: err }); 147 | } else { 148 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 149 | return; 150 | } 151 | }); 152 | break; 153 | default: 154 | node.error(`Called with an unknown action: ${action}`, msg); 155 | return; 156 | } 157 | } 158 | } 159 | 160 | RED.httpAdmin.post("/imageSearch", RED.auth.needsPermission('flows.write'), function (req, res) { 161 | RED.log.debug("POST /imageSearch"); 162 | 163 | const nodeId = req.body.id; 164 | let config = RED.nodes.getNode(nodeId); 165 | 166 | discoverSonos(config, (images) => { 167 | RED.log.debug("GET /imageSearch: " + images.length + " found"); 168 | res.json(images); 169 | }); 170 | }); 171 | 172 | function discoverSonos(config, discoveryCallback) { 173 | let client = config.getClient(); 174 | client.listImages({ all: true }) 175 | .then(images => discoveryCallback(images)) 176 | .catch(err => this.error(err)); 177 | } 178 | 179 | RED.nodes.registerType('docker-image-actions', DockerImageAction); 180 | } 181 | 182 | -------------------------------------------------------------------------------- /src/docker-network-actions.html: -------------------------------------------------------------------------------- 1 | 2 | 74 | 75 | 116 | 117 | -------------------------------------------------------------------------------- /src/docker-network-actions.ts: -------------------------------------------------------------------------------- 1 | import { Red, Node } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | module.exports = function (RED: Red) { 6 | 7 | function DockerNetworkAction(n: any) { 8 | RED.nodes.createNode(this, n); 9 | let config = RED.nodes.getNode(n.config) as unknown as DockerConfiguration; 10 | let client = config.getClient(); 11 | this.on('input', (msg) => { 12 | 13 | let networkId: string = RED.util.evaluateNodeProperty(n.network !== '' ? n.network : '{}', n.networktype, n, msg) || msg.payload.networkId || msg.networkId || undefined; 14 | let action = n.action || msg.action || msg.payload.action || undefined; 15 | let options = RED.util.evaluateNodeProperty(n.options !== '' ? n.options : '{}', n.optionstype, n, msg) || msg.options || msg.payload.options || undefined; 16 | 17 | if (networkId === undefined && !['list', 'prune', 'create'].includes(action)) { 18 | this.error("Network id/name must be provided via configuration or via `msg.network`"); 19 | return; 20 | } 21 | this.status({}); 22 | executeAction(networkId, options, client, action, this, msg); 23 | }); 24 | 25 | async function executeAction(networkId: string, options: any, client: Dockerode, action: string, node: Node, msg) { 26 | 27 | let network = client.getNetwork(networkId); 28 | 29 | switch (action) { 30 | 31 | case 'list': 32 | // https://docs.docker.com/engine/api/v1.40/#operation/NetworkList 33 | client.listNetworks({ all: true }) 34 | .then(res => { 35 | node.status({ fill: 'green', shape: 'dot', text: networkId + ' started' }); 36 | node.send(Object.assign(msg, { payload: res })); 37 | }).catch(err => { 38 | if (err.statusCode === 400) { 39 | node.error(`Bad parameter: ${err.reason}`, msg); 40 | node.send({ payload: err }); 41 | } else if (err.statusCode === 500) { 42 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 43 | node.send({ payload: err }); 44 | } else { 45 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 46 | return; 47 | } 48 | }); 49 | break; 50 | 51 | case 'inspect': 52 | // https://docs.docker.com/engine/api/v1.40/#operation/NetworkInspect 53 | network.inspect() 54 | .then(res => { 55 | node.status({ fill: 'green', shape: 'dot', text: networkId + ' started' }); 56 | node.send(Object.assign(msg, { payload: res })); 57 | }).catch(err => { 58 | if (err.statusCode === 500) { 59 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 60 | node.send({ payload: err }); 61 | } else { 62 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 63 | return; 64 | } 65 | }); 66 | break; 67 | 68 | case 'remove': 69 | // https://docs.docker.com/engine/api/v1.40/#operation/NetworkRemove 70 | network.remove() 71 | .then(res => { 72 | node.status({ fill: 'green', shape: 'dot', text: networkId + ' remove' }); 73 | node.send(Object.assign(msg, { payload: res })); 74 | }).catch(err => { 75 | if (err.statusCode === 500) { 76 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 77 | node.send({ payload: err }); 78 | } else { 79 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 80 | return; 81 | } 82 | }); 83 | break; 84 | 85 | case 'connect': 86 | // https://docs.docker.com/engine/api/v1.40/#operation/NetworkConnect 87 | network.connect() 88 | .then(res => { 89 | node.status({ fill: 'green', shape: 'dot', text: networkId + ' remove' }); 90 | node.send(Object.assign(msg, { payload: res })); 91 | }).catch(err => { 92 | if (err.statusCode === 500) { 93 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 94 | node.send({ payload: err }); 95 | } else { 96 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 97 | return; 98 | } 99 | }); 100 | break; 101 | 102 | case 'disconnect': 103 | // https://docs.docker.com/engine/api/v1.40/#operation/NetworkDisconnect 104 | network.disconnect() 105 | .then(res => { 106 | node.status({ fill: 'green', shape: 'dot', text: networkId + ' remove' }); 107 | node.send(Object.assign(msg, { payload: res })); 108 | }).catch(err => { 109 | if (err.statusCode === 500) { 110 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 111 | node.send({ payload: err }); 112 | } else { 113 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 114 | return; 115 | } 116 | }); 117 | break; 118 | 119 | case 'prune': 120 | // https://docs.docker.com/engine/api/v1.40/#operation/NetworkPrune 121 | client.pruneNetworks() 122 | .then(res => { 123 | node.status({ fill: 'green', shape: 'dot', text: networkId + ' remove' }); 124 | node.send(Object.assign(msg, { payload: res })); 125 | }).catch(err => { 126 | if (err.statusCode === 500) { 127 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 128 | node.send({ payload: err }); 129 | } else { 130 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 131 | return; 132 | } 133 | }); 134 | break; 135 | case 'create': 136 | let create = false; 137 | try { 138 | await network.inspect(); 139 | } catch (err) { 140 | create = true 141 | } 142 | if (create) { 143 | // https://docs.docker.com/engine/api/v1.40/#operation/NetworkCreate 144 | client.createNetwork(Object.assign(options, { 'name': networkId })) 145 | .then(res => { 146 | node.status({ fill: 'green', shape: 'dot', text: networkId + ' created' }); 147 | node.send(Object.assign(msg, { payload: res })); 148 | }).catch(err => { 149 | if (err.statusCode === 500) { 150 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 151 | node.send({ payload: err }); 152 | } else { 153 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 154 | return; 155 | } 156 | }); 157 | } 158 | else { 159 | node.status({ fill: 'green', shape: 'dot', text: networkId + ' already exists' }); 160 | node.send(Object.assign(msg, { payload: {} })); 161 | } 162 | break; 163 | 164 | default: 165 | node.error(`Called with an unknown action: ${action}`, msg); 166 | return; 167 | } 168 | } 169 | } 170 | 171 | RED.httpAdmin.post("/networkSearch", RED.auth.needsPermission('flows.write'), function (req, res) { 172 | RED.log.debug("POST /networkSearch"); 173 | 174 | const nodeId = req.body.id; 175 | let config = RED.nodes.getNode(nodeId); 176 | 177 | discoverSonos(config, (networks) => { 178 | RED.log.debug("GET /networkSearch: " + networks.length + " found"); 179 | res.json(networks); 180 | }); 181 | }); 182 | 183 | function discoverSonos(config, discoveryCallback) { 184 | let client = config.getClient(); 185 | client.listNetworks({ all: true }) 186 | .then(networks => discoveryCallback(networks)) 187 | .catch(err => this.error(err)); 188 | } 189 | 190 | RED.nodes.registerType('docker-network-actions', DockerNetworkAction); 191 | } 192 | 193 | -------------------------------------------------------------------------------- /src/docker-node-actions.html: -------------------------------------------------------------------------------- 1 | 2 | 73 | 74 | 111 | 112 | -------------------------------------------------------------------------------- /src/docker-node-actions.ts: -------------------------------------------------------------------------------- 1 | import { Red, Node } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | module.exports = function (RED: Red) { 6 | 7 | function DockerNodeAction(n: any) { 8 | RED.nodes.createNode(this, n); 9 | let config = RED.nodes.getNode(n.config) as unknown as DockerConfiguration; 10 | let client = config.getClient(); 11 | this.on('input', (msg) => { 12 | 13 | let nodeId: string = n.node || msg.payload.nodeId || msg.nodeId || undefined; 14 | let action = n.action || msg.action || msg.payload.action || undefined; 15 | 16 | if (nodeId === undefined && !['list', 'prune', 'create'].includes(action)) { 17 | this.error("Node id/name must be provided via configuration or via `msg.node`"); 18 | return; 19 | } 20 | this.status({}); 21 | executeAction(nodeId, client, action, this, msg, { 22 | options: RED.util.evaluateNodeProperty(n.options !== '' ? n.options : '{}', n.optionstype, n, msg) || {}, 23 | }); 24 | }); 25 | 26 | function executeAction(nodeId: string, client: Dockerode, action: string, node: Node, msg, config: { options: {} }) { 27 | 28 | let nodeClient = client.getNode(nodeId); 29 | 30 | switch (action) { 31 | 32 | case 'list': 33 | // https://docs.docker.com/engine/api/v1.40/#operation/NodeList 34 | client.listNodes({ all: true }) 35 | .then(res => { 36 | node.status({ fill: 'green', shape: 'dot', text: nodeId + ' started' }); 37 | node.send(Object.assign(msg, { payload: res })); 38 | }).catch(err => { 39 | if (err.statusCode === 400) { 40 | node.error(`Bad parameter: ${err.reason}`, msg); 41 | node.send({ payload: err }); 42 | } else if (err.statusCode === 500) { 43 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 44 | node.send({ payload: err }); 45 | } else { 46 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 47 | return; 48 | } 49 | }); 50 | break; 51 | case 'inspect': 52 | // https://docs.docker.com/engine/api/v1.40/#operation/NodeInspect 53 | nodeClient.inspect() 54 | .then(res => { 55 | node.status({ fill: 'green', shape: 'dot', text: nodeId + ' started' }); 56 | node.send(Object.assign(msg, { payload: res })); 57 | }).catch(err => { 58 | if (err.statusCode === 500) { 59 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 60 | node.send({ payload: err }); 61 | } else { 62 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 63 | return; 64 | } 65 | }); 66 | break; 67 | case 'remove': 68 | // https://docs.docker.com/engine/api/v1.40/#operation/NodeDelete 69 | nodeClient.remove() 70 | .then(res => { 71 | node.status({ fill: 'green', shape: 'dot', text: nodeId + ' stopped' }); 72 | node.send(Object.assign(msg, { payload: res })); 73 | }).catch(err => { 74 | if (err.statusCode === 500) { 75 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 76 | node.send({ payload: err }); 77 | } else { 78 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 79 | return; 80 | } 81 | }); 82 | break; 83 | case 'update': 84 | // https://docs.docker.com/engine/api/v1.40/#operation/NodeUpdate 85 | nodeClient.update(config.options) 86 | .then(res => { 87 | node.status({ fill: 'green', shape: 'dot', text: nodeId + ' restarted' }); 88 | node.send(Object.assign(msg, { payload: res })); 89 | }).catch(err => { 90 | if (err.statusCode === 500) { 91 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 92 | node.send({ payload: err }); 93 | } else { 94 | node.error(`System Error: [${err.statusCode}] ${err}`, msg); 95 | return; 96 | } 97 | }); 98 | break; 99 | 100 | 101 | default: 102 | node.error(`Called with an unknown action: ${action}`, msg); 103 | return; 104 | } 105 | } 106 | } 107 | 108 | 109 | RED.httpAdmin.post("/nodeSearch", RED.auth.needsPermission('flows.write'), function (req, res) { 110 | RED.log.debug("POST /nodeSearch"); 111 | 112 | const nodeId = req.body.id; 113 | let config = RED.nodes.getNode(nodeId); 114 | 115 | discoverSonos(config, (nodes) => { 116 | RED.log.debug("GET /nodeSearch: " + nodes.length + " found"); 117 | res.json(nodes); 118 | }); 119 | }); 120 | 121 | function discoverSonos(config, discoveryCallback) { 122 | let client = config.getClient(); 123 | client.listNodes({ all: true }) 124 | .then(nodes => discoveryCallback(nodes)) 125 | .catch(err => this.error(err)); 126 | } 127 | 128 | 129 | RED.nodes.registerType('docker-node-actions', DockerNodeAction); 130 | } 131 | 132 | -------------------------------------------------------------------------------- /src/docker-plugin-actions.html: -------------------------------------------------------------------------------- 1 | 2 | 71 | 72 | 105 | 106 | -------------------------------------------------------------------------------- /src/docker-plugin-actions.ts: -------------------------------------------------------------------------------- 1 | import { Red, Node } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | module.exports = function (RED: Red) { 6 | 7 | function DockerPluginAction(n: any) { 8 | RED.nodes.createNode(this, n); 9 | let config = RED.nodes.getNode(n.config) as unknown as DockerConfiguration; 10 | let client = config.getClient(); 11 | this.on('input', (msg) => { 12 | 13 | let pluginId: string = n.plugin || msg.payload.pluginId || msg.pluginId || undefined; 14 | let action = n.action || msg.action || msg.payload.action || undefined; 15 | let options = n.options || msg.options || msg.payload.options || undefined; 16 | 17 | if (pluginId === undefined && !['list', 'prune', 'create'].includes(action)) { 18 | this.error("Plugin id/name must be provided via configuration or via `msg.plugin`"); 19 | return; 20 | } 21 | this.status({}); 22 | executeAction(pluginId, options, client, action, this,msg); 23 | }); 24 | 25 | function executeAction(pluginId: string, options: any, client: Dockerode, action: string, node: Node ,msg) { 26 | 27 | let remote ={}; 28 | let plugin = client.getPlugin(pluginId, remote); 29 | 30 | switch (action) { 31 | 32 | case 'list': 33 | // https://docs.docker.com/engine/api/v1.40/#operation/PluginList 34 | client.listPlugins({ all: true }) 35 | .then(res => { 36 | node.status({ fill: 'green', shape: 'dot', text: pluginId + ' started' }); 37 | node.send(Object.assign(msg,{ payload: res })); 38 | }).catch(err => { 39 | if (err.statusCode === 400) { 40 | node.error(`Bad parameter: ${err.reason}`, msg); 41 | node.send({ payload: err }); 42 | } else if (err.statusCode === 500) { 43 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 44 | node.send({ payload: err }); 45 | } else { 46 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 47 | return; 48 | } 49 | }); 50 | break; 51 | 52 | 53 | case 'inspect': 54 | // https://docs.docker.com/engine/api/v1.40/#operation/PluginInspect 55 | plugin.inspect() 56 | .then(res => { 57 | node.status({ fill: 'green', shape: 'dot', text: pluginId + ' started' }); 58 | node.send(Object.assign(msg,{ payload: res })); 59 | }).catch(err => { 60 | if (err.statusCode === 500) { 61 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 62 | node.send({ payload: err }); 63 | } else { 64 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 65 | return; 66 | } 67 | }); 68 | break; 69 | case 'remove': 70 | // https://docs.docker.com/engine/api/v1.40/#operation/PluginDelete 71 | plugin.remove() 72 | .then(res => { 73 | node.status({ fill: 'green', shape: 'dot', text: pluginId + ' remove' }); 74 | node.send(Object.assign(msg,{ payload: res })); 75 | }).catch(err => { 76 | if (err.statusCode === 500) { 77 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 78 | node.send({ payload: err }); 79 | } else { 80 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 81 | return; 82 | } 83 | }); 84 | break; 85 | 86 | case 'enable': 87 | // https://docs.docker.com/engine/api/v1.40/#operation/PluginEndable 88 | plugin.enable() 89 | .then(res => { 90 | node.status({ fill: 'green', shape: 'dot', text: pluginId + ' remove' }); 91 | node.send(Object.assign(msg,{ payload: res })); 92 | }).catch(err => { 93 | if (err.statusCode === 500) { 94 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 95 | node.send({ payload: err }); 96 | } else { 97 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 98 | return; 99 | } 100 | }); 101 | break; 102 | 103 | case 'disable': 104 | // https://docs.docker.com/engine/api/v1.40/#operation/PluginDisable 105 | plugin.disable() 106 | .then(res => { 107 | node.status({ fill: 'green', shape: 'dot', text: pluginId + ' remove' }); 108 | node.send(Object.assign(msg,{ payload: res })); 109 | }).catch(err => { 110 | if (err.statusCode === 500) { 111 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 112 | node.send({ payload: err }); 113 | } else { 114 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 115 | return; 116 | } 117 | }); 118 | break; 119 | 120 | case 'configure': 121 | // https://docs.docker.com/engine/api/v1.40/#operation/PluginConfigue 122 | plugin.configure() 123 | .then(res => { 124 | node.status({ fill: 'green', shape: 'dot', text: pluginId + ' remove' }); 125 | node.send(Object.assign(msg,{ payload: res })); 126 | }).catch(err => { 127 | if (err.statusCode === 500) { 128 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 129 | node.send({ payload: err }); 130 | } else { 131 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 132 | return; 133 | } 134 | }); 135 | break; 136 | 137 | 138 | case 'privileges': 139 | // https://docs.docker.com/engine/api/v1.40/#operation/PluginPrivledges 140 | plugin.privileges() 141 | .then(res => { 142 | node.status({ fill: 'green', shape: 'dot', text: pluginId + ' remove' }); 143 | node.send(Object.assign(msg,{ payload: res })); 144 | }).catch(err => { 145 | if (err.statusCode === 500) { 146 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 147 | node.send({ payload: err }); 148 | } else { 149 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 150 | return; 151 | } 152 | }); 153 | break; 154 | case 'push': 155 | // https://docs.docker.com/engine/api/v1.40/#operation/PluginPush 156 | plugin.push() 157 | .then(res => { 158 | node.status({ fill: 'green', shape: 'dot', text: pluginId + ' remove' }); 159 | node.send(Object.assign(msg,{ payload: res })); 160 | }).catch(err => { 161 | if (err.statusCode === 500) { 162 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 163 | node.send({ payload: err }); 164 | } else { 165 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 166 | return; 167 | } 168 | }); 169 | break; 170 | 171 | case 'pull': 172 | // https://docs.docker.com/engine/api/v1.40/#operation/PluginPull 173 | plugin.pull(options) 174 | .then(res => { 175 | node.status({ fill: 'green', shape: 'dot', text: pluginId + ' remove' }); 176 | node.send(Object.assign(msg,{ payload: res })); 177 | }).catch(err => { 178 | if (err.statusCode === 500) { 179 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 180 | node.send({ payload: err }); 181 | } else { 182 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 183 | return; 184 | } 185 | }); 186 | break; 187 | 188 | case 'upgrade': 189 | // https://docs.docker.com/engine/api/v1.40/#operation/PluginUpgrade 190 | plugin.upgrade(options) 191 | .then(res => { 192 | node.status({ fill: 'green', shape: 'dot', text: pluginId + ' remove' }); 193 | node.send(Object.assign(msg,{ payload: res })); 194 | }).catch(err => { 195 | if (err.statusCode === 500) { 196 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 197 | node.send({ payload: err }); 198 | } else { 199 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 200 | return; 201 | } 202 | }); 203 | break; 204 | 205 | case 'create': 206 | // https://docs.docker.com/engine/api/v1.40/#operation/PluginCreate 207 | client.createPlugin(options) 208 | .then(res => { 209 | node.status({ fill: 'green', shape: 'dot', text: pluginId + ' remove' }); 210 | node.send(Object.assign(msg,{ payload: res })); 211 | }).catch(err => { 212 | if (err.statusCode === 500) { 213 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 214 | node.send({ payload: err }); 215 | } else { 216 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 217 | return; 218 | } 219 | }); 220 | break; 221 | 222 | default: 223 | node.error(`Called with an unknown action: ${action}`, msg); 224 | return; 225 | } 226 | } 227 | } 228 | 229 | RED.httpAdmin.post("/pluginSearch", RED.auth.needsPermission('flows.write'), function (req, res) { 230 | RED.log.debug("POST /pluginSearch"); 231 | 232 | const nodeId = req.body.id; 233 | let config = RED.nodes.getNode(nodeId); 234 | 235 | discoverSonos(config, (plugins) => { 236 | RED.log.debug("GET /pluginSearch: " + plugins.length + " found"); 237 | res.json(plugins); 238 | }); 239 | }); 240 | 241 | function discoverSonos(config, discoveryCallback) { 242 | let client = config.getClient(); 243 | client.listPlugins({ all: true }) 244 | .then(plugins => discoveryCallback(plugins)) 245 | .catch(err => this.error(err)); 246 | } 247 | 248 | RED.nodes.registerType('docker-plugin-actions', DockerPluginAction); 249 | } 250 | 251 | -------------------------------------------------------------------------------- /src/docker-secret-actions.html: -------------------------------------------------------------------------------- 1 | 2 | 58 | 59 | 86 | 87 | -------------------------------------------------------------------------------- /src/docker-secret-actions.ts: -------------------------------------------------------------------------------- 1 | import { Red, Node } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | module.exports = function (RED: Red) { 6 | 7 | function DockerSecretAction(n: any) { 8 | RED.nodes.createNode(this, n); 9 | let config = RED.nodes.getNode(n.config) as unknown as DockerConfiguration; 10 | let client = config.getClient(); 11 | this.on('input', (msg) => { 12 | 13 | let secretId: string = n.secret || msg.payload.secretId || msg.secretId || undefined; 14 | //TODO: make this disabled by default 15 | let action = n.action || msg.action || msg.payload.action || undefined; 16 | let options = n.options || msg.options || msg.payload.options || undefined; 17 | if (secretId === undefined && !['list', 'prune', 'create'].includes(action)) { 18 | this.error("Secret id/name must be provided via configuration or via `msg.secret`"); 19 | return; 20 | } 21 | this.status({}); 22 | executeAction(secretId, options, client, action, this,msg); 23 | }); 24 | 25 | function executeAction(secretId: string, options: any, client: Dockerode, action: string, node: Node,msg) { 26 | 27 | let secret = client.getSecret(secretId); 28 | 29 | switch (action) { 30 | 31 | case 'list': 32 | // https://docs.docker.com/engine/api/v1.40/#operation/SecretList 33 | client.listSecrets({ all: true }) 34 | .then(res => { 35 | node.status({ fill: 'green', shape: 'dot', text: secretId + ' started' }); 36 | node.send(Object.assign(msg,{ payload: res })); 37 | }).catch(err => { 38 | if (err.statusCode === 400) { 39 | node.error(`Bad parameter: ${err.reason}`, msg); 40 | node.send({ payload: err }); 41 | } else if (err.statusCode === 500) { 42 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 43 | node.send({ payload: err }); 44 | } else { 45 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 46 | return; 47 | } 48 | }); 49 | break; 50 | 51 | case 'inspect': 52 | // https://docs.docker.com/engine/api/v1.40/#operation/SecretInspect 53 | secret.inspect() 54 | .then(res => { 55 | node.status({ fill: 'green', shape: 'dot', text: secretId + ' started' }); 56 | node.send(Object.assign(msg,{ payload: res })); 57 | }).catch(err => { 58 | if (err.statusCode === 500) { 59 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 60 | node.send({ payload: err }); 61 | } else { 62 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 63 | return; 64 | } 65 | }); 66 | break; 67 | 68 | case 'remove': 69 | // https://docs.docker.com/engine/api/v1.40/#operation/SecretDelete 70 | secret.remove() 71 | .then(res => { 72 | node.status({ fill: 'green', shape: 'dot', text: secretId + ' stopped' }); 73 | node.send(Object.assign(msg,{ payload: res })); 74 | }).catch(err => { 75 | if (err.statusCode === 500) { 76 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 77 | node.send({ payload: err }); 78 | } else { 79 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 80 | return; 81 | } 82 | }); 83 | break; 84 | 85 | case 'update': 86 | // https://docs.docker.com/engine/api/v1.40/#operation/SecretUpdate 87 | secret.update() 88 | .then(res => { 89 | node.status({ fill: 'green', shape: 'dot', text: secretId + ' restarted' }); 90 | node.send(Object.assign(msg,{ payload: res })); 91 | }).catch(err => { 92 | if (err.statusCode === 500) { 93 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 94 | node.send({ payload: err }); 95 | } else { 96 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 97 | return; 98 | } 99 | }); 100 | break; 101 | 102 | case 'create': 103 | // https://docs.docker.com/engine/api/v1.40/#operation/SecretCreate 104 | client.createSecret(options) 105 | .then(res => { 106 | node.status({ fill: 'green', shape: 'dot', text: secretId + ' restarted' }); 107 | node.send(Object.assign(msg,{ payload: res })); 108 | }).catch(err => { 109 | if (err.statusCode === 500) { 110 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 111 | node.send({ payload: err }); 112 | } else { 113 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 114 | return; 115 | } 116 | }); 117 | break; 118 | default: 119 | node.error(`Called with an unknown action: ${action}`, msg); 120 | return; 121 | } 122 | } 123 | } 124 | 125 | RED.httpAdmin.post("/secretSearch", RED.auth.needsPermission('flows.write'), function (req, res) { 126 | RED.log.debug("POST /secretSearch"); 127 | 128 | const nodeId = req.body.id; 129 | let config = RED.nodes.getNode(nodeId); 130 | 131 | discoverSonos(config, (secrets) => { 132 | RED.log.debug("GET /secretSearch: " + secrets.length + " found"); 133 | res.json(secrets); 134 | }); 135 | }); 136 | 137 | function discoverSonos(config, discoveryCallback) { 138 | let client = config.getClient(); 139 | client.listSecrets({ all: true }) 140 | .then(secrets => discoveryCallback(secrets)) 141 | .catch(err => this.error(err)); 142 | } 143 | 144 | RED.nodes.registerType('docker-secret-actions', DockerSecretAction); 145 | } 146 | 147 | -------------------------------------------------------------------------------- /src/docker-service-actions.html: -------------------------------------------------------------------------------- 1 | 2 | 68 | 69 | 102 | 103 | -------------------------------------------------------------------------------- /src/docker-service-actions.ts: -------------------------------------------------------------------------------- 1 | import { Red, Node } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | module.exports = function (RED: Red) { 6 | 7 | function DockerServiceAction(n: any) { 8 | RED.nodes.createNode(this, n); 9 | let config = RED.nodes.getNode(n.config) as unknown as DockerConfiguration; 10 | let client = config.getClient(); 11 | this.on('input', (msg) => { 12 | 13 | let serviceId: string = n.service || msg.payload?.serviceId || msg.serviceId || undefined; 14 | if(serviceId === undefined){ 15 | serviceId = n.serviceName || msg.payload?.serviceName || msg.serviceName || undefined; 16 | } 17 | let action = n.action || msg.action || msg.payload?.action || undefined; 18 | if (serviceId === undefined && !['list', 'prune', 'create'].includes(action)) { 19 | this.error("Service id/name must be provided via configuration or via `msg.service`"); 20 | return; 21 | } 22 | let options = RED.util.evaluateNodeProperty(n.options, n.optionstype, n, msg) || msg.options || msg.options || msg.payload?.options || undefined; 23 | 24 | this.status({}); 25 | executeAction(serviceId, options, client, action, this,msg); 26 | }); 27 | 28 | function executeAction(serviceId: string, options: any ,client: Dockerode, action: string, node: Node,msg) { 29 | 30 | let service = client.getService(serviceId); 31 | 32 | switch (action) { 33 | 34 | case 'list': 35 | // https://docs.docker.com/engine/api/v1.40/#operation/ServiceList 36 | client.listServices({ all: true }) 37 | .then(res => { 38 | node.status({ fill: 'green', shape: 'dot', text: serviceId + ' started' }); 39 | node.send(Object.assign(msg,{ payload: res })); 40 | }).catch(err => { 41 | if (err.statusCode === 400) { 42 | node.error(`Bad parameter: ${err.reason}`, msg); 43 | node.send({ payload: err }); 44 | } else if (err.statusCode === 500) { 45 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 46 | node.send({ payload: err }); 47 | } else { 48 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 49 | return; 50 | } 51 | }); 52 | break; 53 | 54 | case 'create': 55 | // https://docs.docker.com/engine/api/v1.40/#operation/ServiceCreate 56 | client.createService(options) 57 | .then(res => { 58 | node.status({ fill: 'green', shape: 'dot', text: serviceId + ' started' }); 59 | node.send(Object.assign(msg,{ payload: res })); 60 | }).catch(err => { 61 | if (err.statusCode === 400) { 62 | node.error(`Bad parameter: ${err.reason}`, msg); 63 | node.send({ payload: err }); 64 | } else if (err.statusCode === 500) { 65 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 66 | node.send({ payload: err }); 67 | } else { 68 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 69 | return; 70 | } 71 | }); 72 | break; 73 | 74 | case 'inspect': 75 | // https://docs.docker.com/engine/api/v1.40/#operation/ServiceInspect 76 | service.inspect() 77 | .then(res => { 78 | node.status({ fill: 'green', shape: 'dot', text: 'Inspected: ' + serviceId }); 79 | node.send(Object.assign(msg,{ payload: res })); 80 | }).catch(err => { 81 | if (err.statusCode === 500) { 82 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 83 | node.send({ payload: err }); 84 | } else { 85 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 86 | return; 87 | } 88 | }); 89 | break; 90 | 91 | case 'update': 92 | // https://docs.docker.com/engine/api/v1.40/#operation/ServiceUpdate 93 | service.update(options) 94 | .then(res => { 95 | node.status({ fill: 'green', shape: 'dot', text: 'Updated: ' + serviceId }); 96 | node.send(Object.assign(msg,{ payload: res })); 97 | }).catch(err => { 98 | if (err.statusCode === 500) { 99 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 100 | node.send({ payload: err }); 101 | } else { 102 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 103 | return; 104 | } 105 | }); 106 | break; 107 | 108 | case 'remove': 109 | // https://docs.docker.com/engine/api/v1.40/#operation/ServiceDelete 110 | service.remove() 111 | .then(res => { 112 | node.status({ fill: 'green', shape: 'dot', text: 'Update: ' + serviceId }); 113 | node.send(Object.assign(msg,{ payload: res })); 114 | }).catch(err => { 115 | if (err.statusCode === 500) { 116 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 117 | node.send({ payload: err }); 118 | } else { 119 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 120 | return; 121 | } 122 | }); 123 | break; 124 | //Todo: tail 125 | case 'logs': 126 | // https://docs.docker.com/engine/api/v1.40/#operation/ServiceLogs 127 | service.logs() 128 | .then(res => { 129 | node.status({ fill: 'green', shape: 'dot', text: 'Logging: ' + serviceId }); 130 | node.send(Object.assign(msg,{ payload: res })); 131 | }).catch(err => { 132 | if (err.statusCode === 500) { 133 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 134 | node.send({ payload: err }); 135 | } else { 136 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 137 | return; 138 | } 139 | }); 140 | break; 141 | 142 | default: 143 | node.error(`Called with an unknown action: ${action}`, msg); 144 | return; 145 | } 146 | } 147 | } 148 | 149 | RED.httpAdmin.post("/serviceSearch", RED.auth.needsPermission('flows.write'), function (req, res) { 150 | RED.log.debug("POST /serviceSearch"); 151 | 152 | const nodeId = req.body.id; 153 | let config = RED.nodes.getNode(nodeId); 154 | 155 | discoverSonos(config, (services) => { 156 | RED.log.debug("GET /serviceSearch: " + services.length + " found"); 157 | res.json(services); 158 | }); 159 | }); 160 | 161 | function discoverSonos(config, discoveryCallback) { 162 | let client = config.getClient(); 163 | client.listServices({ all: true }) 164 | .then(services => discoveryCallback(services)) 165 | .catch(err => this.error(err)); 166 | } 167 | 168 | RED.nodes.registerType('docker-service-actions', DockerServiceAction); 169 | } 170 | 171 | -------------------------------------------------------------------------------- /src/docker-swarm-actions.html: -------------------------------------------------------------------------------- 1 | 2 | 42 | 43 | 63 | 64 | -------------------------------------------------------------------------------- /src/docker-swarm-actions.ts: -------------------------------------------------------------------------------- 1 | import { Red, Node } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | module.exports = function (RED: Red) { 6 | 7 | function DockerSwarmAction(n: any) { 8 | RED.nodes.createNode(this, n); 9 | let config = RED.nodes.getNode(n.config) as unknown as DockerConfiguration; 10 | let client = config.getClient(); 11 | this.on('input', (msg) => { 12 | 13 | 14 | let action = n.action || msg.action || msg.payload.action || undefined; 15 | let options = n.options || msg.options|| msg.options || msg.payload.options || undefined; 16 | 17 | this.status({}); 18 | executeAction( options, client, action, this,msg); 19 | }); 20 | 21 | function executeAction(options: any, client: Dockerode, action: string, node: Node,msg) { 22 | 23 | let swarm = client; 24 | 25 | switch (action) { 26 | case 'inspect': 27 | // https://docs.docker.com/engine/api/v1.40/#operation/SwarmInspect 28 | swarm.swarmInspect() 29 | .then(res => { 30 | node.status({ fill: 'green', shape: 'dot', text: ' started' }); 31 | node.send(Object.assign(msg,{ payload: res })); 32 | }).catch(err => { 33 | if (err.statusCode === 500) { 34 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 35 | node.send({ payload: err }); 36 | } else { 37 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 38 | return; 39 | } 40 | }); 41 | break; 42 | 43 | case 'update': 44 | // https://docs.docker.com/engine/api/v1.40/#operation/SwarmUpdate 45 | swarm.swarmUpdate(options) 46 | .then(res => { 47 | node.status({ fill: 'green', shape: 'dot', text: ' stopped' }); 48 | node.send(Object.assign(msg,{ payload: res })); 49 | }).catch(err => { 50 | if (err.statusCode === 500) { 51 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 52 | node.send({ payload: err }); 53 | } else { 54 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 55 | return; 56 | } 57 | }); 58 | break; 59 | 60 | case 'join': 61 | // https://docs.docker.com/engine/api/v1.40/#operation/SwarmJoin 62 | swarm.swarmJoin(options) 63 | .then(res => { 64 | node.status({ fill: 'green', shape: 'dot', text: ' remove' }); 65 | node.send(Object.assign(msg,{ payload: res })); 66 | }).catch(err => { 67 | if (err.statusCode === 500) { 68 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 69 | node.send({ payload: err }); 70 | } else { 71 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 72 | return; 73 | } 74 | }); 75 | break; 76 | case 'leave': 77 | // https://docs.docker.com/engine/api/v1.40/#operation/SwarmLeave 78 | swarm.swarmLeave(options) 79 | .then(res => { 80 | node.status({ fill: 'green', shape: 'dot', text: ' remove' }); 81 | node.send(Object.assign(msg,{ payload: res })); 82 | }).catch(err => { 83 | if (err.statusCode === 500) { 84 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 85 | node.send({ payload: err }); 86 | } else { 87 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 88 | return; 89 | } 90 | }); 91 | break; 92 | 93 | case 'init': 94 | // https://docs.docker.com/engine/api/v1.40/#operation/SwarmInit 95 | swarm.swarmInit(options) 96 | .then(res => { 97 | node.status({ fill: 'green', shape: 'dot', text: ' remove' }); 98 | node.send(Object.assign(msg,{ payload: res })); 99 | }).catch(err => { 100 | if (err.statusCode === 500) { 101 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 102 | node.send({ payload: err }); 103 | }else if (err.statusCode === 400) { 104 | node.warn(`Unable to init swarm. Bad payload.`); 105 | node.send({ payload: err }); 106 | } else { 107 | node.error(`Error init swarm: [${err.statusCode}] ${err.reason}`, msg); 108 | return; 109 | } 110 | }); 111 | break; 112 | default: 113 | node.error(`Called with an unknown action: ${action}`, msg); 114 | return; 115 | } 116 | } 117 | } 118 | 119 | RED.nodes.registerType('docker-swarm-actions', DockerSwarmAction); 120 | } 121 | 122 | -------------------------------------------------------------------------------- /src/docker-task-actions.html: -------------------------------------------------------------------------------- 1 | 2 | 56 | 57 | 82 | 83 | -------------------------------------------------------------------------------- /src/docker-task-actions.ts: -------------------------------------------------------------------------------- 1 | import { Red, Node } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | module.exports = function (RED: Red) { 6 | 7 | function DockerTaskAction(n: any) { 8 | RED.nodes.createNode(this, n); 9 | let config = RED.nodes.getNode(n.config) as unknown as DockerConfiguration; 10 | let client = config.getClient(); 11 | this.on('input', (msg) => { 12 | 13 | let taskId: string = n.task || msg.payload.taskId || msg.taskId || undefined; 14 | let action = n.action || msg.action || msg.payload.action || undefined; 15 | 16 | 17 | if (taskId === undefined && !['list', 'prune', 'create'].includes(action)) { 18 | this.error("Task id/name must be provided via configuration or via `msg.task`"); 19 | return; 20 | } 21 | this.status({}); 22 | executeAction(taskId, client, action, this,msg); 23 | }); 24 | 25 | function executeAction(taskId: string, client: Dockerode, action: string, node: Node,msg) { 26 | 27 | let task = client.getTask(taskId); 28 | 29 | switch (action) { 30 | 31 | 32 | 33 | case 'list': 34 | // https://docs.docker.com/engine/api/v1.40/#operation/TaskList 35 | client.listTasks({ all: true }) 36 | .then(res => { 37 | node.status({ fill: 'green', shape: 'dot', text: taskId + ' started' }); 38 | node.send(Object.assign(msg,{ payload: res })); 39 | }).catch(err => { 40 | if (err.statusCode === 400) { 41 | node.error(`Bad parameter: ${err.reason}`, msg); 42 | node.send({ payload: err }); 43 | } else if (err.statusCode === 500) { 44 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 45 | node.send({ payload: err }); 46 | } else { 47 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 48 | return; 49 | } 50 | }); 51 | break; 52 | 53 | case 'inspect': 54 | // https://docs.docker.com/engine/api/v1.40/#operation/TaskInspect 55 | task.inspect() 56 | .then(res => { 57 | node.status({ fill: 'green', shape: 'dot', text: taskId + ' started' }); 58 | node.send(Object.assign(msg,{ payload: res })); 59 | }).catch(err => { 60 | if (err.statusCode === 500) { 61 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 62 | node.send({ payload: err }); 63 | } else { 64 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 65 | return; 66 | } 67 | }); 68 | break; 69 | /* 70 | case 'log': 71 | // https://docs.docker.com/engine/api/v1.40/#operation/Session 72 | task.logs(options) 73 | .then(res => { 74 | node.status({ fill: 'green', shape: 'dot', text: taskId + ' started' }); 75 | node.send(Object.assign(msg,{ payload: res })); 76 | }).catch(err => { 77 | if (err.statusCode === 500) { 78 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 79 | node.send({ payload: err }); 80 | } else { 81 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 82 | return; 83 | } 84 | }); 85 | break; 86 | */ 87 | default: 88 | node.error(`Called with an unknown action: ${action}`, msg); 89 | return; 90 | } 91 | } 92 | } 93 | 94 | RED.httpAdmin.post("/taskSearch", RED.auth.needsPermission('flows.write'), function (req, res) { 95 | RED.log.debug("POST /taskSearch"); 96 | 97 | const nodeId = req.body.id; 98 | let config = RED.nodes.getNode(nodeId); 99 | 100 | discoverSonos(config, (tasks) => { 101 | RED.log.debug("GET /taskSearch: " + tasks.length + " found"); 102 | res.json(tasks); 103 | }); 104 | }); 105 | 106 | function discoverSonos(config, discoveryCallback) { 107 | let client = config.getClient(); 108 | client.listTasks({ all: true }) 109 | // .then(tasks => console.log(tasks)) 110 | .then(tasks => discoveryCallback(tasks)) 111 | .catch(err => this.error(err)); 112 | } 113 | 114 | RED.nodes.registerType('docker-task-actions', DockerTaskAction); 115 | } 116 | 117 | -------------------------------------------------------------------------------- /src/docker-volume-actions.html: -------------------------------------------------------------------------------- 1 | 2 | 72 | 73 | 113 | 114 | -------------------------------------------------------------------------------- /src/docker-volume-actions.ts: -------------------------------------------------------------------------------- 1 | import { Red, Node } from 'node-red'; 2 | import { DockerConfiguration } from './docker-configuration'; 3 | import * as Dockerode from 'dockerode'; 4 | 5 | 6 | module.exports = function (RED: Red) { 7 | 8 | function DockerVolumeAction(n: any) { 9 | RED.nodes.createNode(this, n); 10 | let config = RED.nodes.getNode(n.config) as unknown as DockerConfiguration; 11 | let client = config.getClient(); 12 | this.on('input', (msg, send, done) => { 13 | 14 | let volumeId: string = RED.util.evaluateNodeProperty(n.volume, n.volumetype, n, msg) || msg.payload.volumeId || msg.volumeId || undefined; 15 | let action = n.action || msg.action || msg.payload.action || undefined; 16 | let options = RED.util.evaluateNodeProperty(n.options, n.optionstype, n, msg) || msg.options || msg.payload.options || undefined; 17 | if (volumeId === undefined && !['list', 'prune', 'create'].includes(action)) { 18 | this.error("Volume id/name must be provided via configuration or via `msg.volume`"); 19 | return; 20 | } 21 | this.status({}); 22 | executeAction(volumeId, options, client, action, this, msg, send, done); 23 | }); 24 | 25 | function executeAction(volumeId: string, options: any, client: Dockerode, action: string, node: Node, msg, send, done) { 26 | 27 | let volume = client.getVolume(volumeId); 28 | 29 | switch (action) { 30 | 31 | case 'list': 32 | // https://docs.docker.com/engine/api/v1.40/#operation/VolumeList 33 | client.listVolumes({ all: true }) 34 | .then(res => { 35 | node.status({ fill: 'green', shape: 'dot', text: volumeId + ' started' }); 36 | node.send(Object.assign(msg, { payload: res })); 37 | }).catch(err => { 38 | if (err.statusCode === 400) { 39 | node.error(`Bad parameter: ${err.reason}`, msg); 40 | node.send({ payload: err }); 41 | } else if (err.statusCode === 500) { 42 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 43 | node.send({ payload: err }); 44 | } else { 45 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 46 | return; 47 | } 48 | }); 49 | break; 50 | 51 | case 'inspect': 52 | // https://docs.docker.com/engine/api/v1.40/#operation/VolumeInspect 53 | volume.inspect() 54 | .then(res => { 55 | node.status({ fill: 'green', shape: 'dot', text: volumeId + ' started' }); 56 | node.send(Object.assign(msg, { payload: res })); 57 | }).catch(err => { 58 | // 404 No such volume 59 | if (err.statusCode === 500) { 60 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 61 | node.send({ payload: err }); 62 | } else { 63 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 64 | return; 65 | } 66 | }); 67 | break; 68 | case 'remove': 69 | // https://docs.docker.com/engine/api/v1.40/#operation/VolumeDelete 70 | volume.remove() 71 | .then(res => { 72 | node.status({ fill: 'green', shape: 'dot', text: volumeId + ' stopped' }); 73 | node.send(Object.assign(msg, { payload: res })); 74 | }).catch(err => { 75 | if (err.statusCode === 404) { 76 | node.error(`No such volume or volume driver`, msg); 77 | node.send({ payload: err }); 78 | } else if (err.statusCode === 409) { 79 | node.error(`Volume is in use and cannot be removed`, msg); 80 | node.send({ payload: err }); 81 | } else if (err.statusCode === 500) { 82 | node.error(`Server error: [${err.statusCode}] ${err.reason}`, msg); 83 | node.send({ payload: err }); 84 | } else { 85 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 86 | return; 87 | } 88 | }); 89 | break; 90 | case 'create': 91 | // https://docs.docker.com/engine/api/v1.40/#operation/VolumeCreate 92 | options.name = volumeId; 93 | client.createVolume(options).then(res => { 94 | node.status({ fill: 'green', shape: 'dot', text: volumeId + ' created' }); 95 | send(Object.assign(msg, { payload: res })); 96 | done(); 97 | }).catch(err => { 98 | if (err.statusCode === 500) { 99 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 100 | node.send({ payload: err }); 101 | } else { 102 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 103 | return; 104 | } 105 | }); 106 | break; 107 | 108 | case 'prune': 109 | // https://docs.docker.com/engine/api/v1.40/#operation/VolumePrune 110 | client.pruneVolumes() 111 | .then(res => { 112 | node.status({ fill: 'green', shape: 'dot', text: volumeId + ' stopped' }); 113 | node.send(Object.assign(msg, { payload: res })); 114 | }).catch(err => { 115 | if (err.statusCode === 500) { 116 | node.error(`Server Error: [${err.statusCode}] ${err.reason}`, msg); 117 | node.send({ payload: err }); 118 | } else { 119 | node.error(`System Error: [${err.statusCode}] ${err.reason}`, msg); 120 | return; 121 | } 122 | }); 123 | break; 124 | 125 | default: 126 | node.error(`Called with an unknown action: ${action}`, msg); 127 | return; 128 | } 129 | } 130 | } 131 | 132 | RED.httpAdmin.post("/volumeSearch", RED.auth.needsPermission('flows.write'), function (req, res) { 133 | RED.log.debug("POST /volumeSearch"); 134 | 135 | const nodeId = req.body.id; 136 | let config = RED.nodes.getNode(nodeId); 137 | 138 | discoverSonos(config, (volumes) => { 139 | RED.log.debug("GET /volumeSearch: " + volumes.length + " found"); 140 | res.json(volumes); 141 | }); 142 | }); 143 | 144 | function discoverSonos(config, discoveryCallback) { 145 | let client = config.getClient(); 146 | client.listVolumes({ all: true }) 147 | // .then(volumes => console.log(volumes)) 148 | .then(volumes => discoveryCallback(volumes)) 149 | .catch(err => this.error(err)); 150 | } 151 | 152 | RED.nodes.registerType('docker-volume-actions', DockerVolumeAction); 153 | } 154 | 155 | -------------------------------------------------------------------------------- /src/icons/docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naimo84/node-red-contrib-dockerode/d04142772dfd7e101a86e9dbd02710812169e840/src/icons/docker.png -------------------------------------------------------------------------------- /test/docker-container-actions_spec.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var helper = require("node-red-node-test-helper"); 3 | 4 | helper.init(require.resolve('node-red')); 5 | 6 | describe('Container Actions Node', function () { 7 | 8 | beforeEach(function (done) { 9 | helper.startServer(done); 10 | }); 11 | 12 | afterEach(function (done) { 13 | helper.unload().then(function () { 14 | helper.stopServer(done); 15 | }); 16 | }); 17 | 18 | it('should be loaded', function (done) { 19 | var flow = [ 20 | { id: "c1", type: "docker-configuration" }, 21 | { id: "n1", type: "docker-container-actions", config: "c1" } 22 | ]; 23 | var dockerContainersNode = require("../dist/docker-container-actions.js"); 24 | var dockerConfigNode = require("../dist/docker-configuration.js"); 25 | 26 | 27 | 28 | helper.load([dockerConfigNode, dockerContainersNode], flow, function () { 29 | var n1 = helper.getNode("n1"); 30 | n1.should.have.property('type', 'docker-container-actions'); 31 | done(); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/docker-containers_spec.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var helper = require("node-red-node-test-helper"); 3 | var docker = require('dockerode-mock') 4 | 5 | 6 | helper.init(require.resolve('node-red')); 7 | 8 | 9 | 10 | describe('Containers Node', function () { 11 | 12 | beforeEach(function (done) { 13 | helper.startServer(done); 14 | }); 15 | 16 | afterEach(function (done) { 17 | helper.unload().then(function () { 18 | helper.stopServer(done); 19 | }); 20 | }); 21 | 22 | it('should be loaded', function (done) { 23 | var flow = [ 24 | { id: "c1", type: "docker-configuration" }, 25 | { id: "n1", type: "docker-containers", config: "c1" } 26 | ]; 27 | var dockerContainersNode = require("../dist/docker-containers.js"); 28 | var dockerConfigNode = require("../dist/docker-configuration.js"); 29 | 30 | 31 | 32 | helper.load([dockerConfigNode, dockerContainersNode], flow, function () { 33 | var n1 = helper.getNode("n1"); 34 | n1.should.have.property('type', 'docker-containers'); 35 | done(); 36 | }); 37 | }); 38 | 39 | // it('should return docker container', function (done) { 40 | // docker.overrides = { 41 | // 'GET /_ping': { error: 'connection error' }, 42 | // 'GET /containers/json?': {}, 43 | // } 44 | 45 | // var flow = [ 46 | // { 47 | // "id": "n2", 48 | // "type": "helper" 49 | // }, 50 | // { 51 | // "id": "dc1", 52 | // "type": "docker-containers", 53 | // "config": "c1", 54 | // "wires": [ 55 | // [ 56 | // "n2" 57 | // ] 58 | // ] 59 | // }, 60 | // { 61 | // "id": "c1", 62 | // "type": "docker-configuration", 63 | // "host": "localhost", 64 | // "port": "2375" 65 | // } 66 | // ]; 67 | 68 | // var dockerContainersNode = require("../dist/docker-containers.js"); 69 | // var dockerConfigNode = require("../dist/configuration.js"); 70 | 71 | // helper.load([dockerConfigNode, dockerContainersNode], flow, function () { 72 | // try { 73 | // var n2 = helper.getNode("n2"); 74 | // var dc1 = helper.getNode("dc1"); 75 | // dc1.receive({ payload: "" }); 76 | // n2.on("input", function (msg) { 77 | // msg.should.not.be.empty(); 78 | // done(); 79 | // }); 80 | 81 | // } catch (err) { 82 | // console.log(err); 83 | // } 84 | // }); 85 | // }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/docker-events_spec.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var helper = require("node-red-node-test-helper"); 3 | var docker = require('dockerode-mock') 4 | 5 | 6 | helper.init(require.resolve('node-red')); 7 | 8 | 9 | 10 | describe('Events Node', function () { 11 | 12 | beforeEach(function (done) { 13 | helper.startServer(done); 14 | }); 15 | 16 | afterEach(function (done) { 17 | helper.unload().then(function () { 18 | helper.stopServer(done); 19 | }); 20 | }); 21 | 22 | it('should be loaded', function (done) { 23 | var flow = [ 24 | { id: "c1", type: "docker-configuration" }, 25 | { id: "n1", type: "docker-events", config: "c1" } 26 | ]; 27 | var dockerContainersNode = require("../dist/docker-events.js"); 28 | var dockerConfigNode = require("../dist/docker-configuration.js"); 29 | 30 | 31 | 32 | helper.load([dockerConfigNode, dockerContainersNode], flow, function () { 33 | var n1 = helper.getNode("n1"); 34 | n1.should.have.property('type', 'docker-events'); 35 | done(); 36 | }); 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /test/docker-images_spec.js: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var helper = require("node-red-node-test-helper"); 3 | var docker = require('dockerode-mock') 4 | 5 | 6 | helper.init(require.resolve('node-red')); 7 | 8 | 9 | 10 | describe('Iamges Node', function () { 11 | 12 | beforeEach(function (done) { 13 | helper.startServer(done); 14 | }); 15 | 16 | afterEach(function (done) { 17 | helper.unload().then(function () { 18 | helper.stopServer(done); 19 | }); 20 | }); 21 | 22 | it('should be loaded', function (done) { 23 | var flow = [ 24 | { id: "c1", type: "docker-configuration" }, 25 | { id: "n1", type: "docker-images", config: "c1" } 26 | ]; 27 | var dockerContainersNode = require("../dist/docker-images.js"); 28 | var dockerConfigNode = require("../dist/docker-configuration.js"); 29 | 30 | 31 | 32 | helper.load([dockerConfigNode, dockerContainersNode], flow, function () { 33 | var n1 = helper.getNode("n1"); 34 | n1.should.have.property('type', 'docker-images'); 35 | done(); 36 | }); 37 | }); 38 | 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "*": [ 6 | "types/*", 7 | "src/*" 8 | ] 9 | }, 10 | "outDir": "./dist", 11 | "target": "es5", 12 | "moduleResolution": "node", 13 | "strict": false, 14 | "sourceMap": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | }, 18 | "include": [ 19 | "./src/**/*" 20 | ], 21 | "exclude": [ 22 | "node_modules" 23 | ] 24 | } --------------------------------------------------------------------------------