├── LICENSE ├── README.md ├── cmd └── containerd-shim-wasm-v1 │ └── main.go ├── go.mod ├── go.sum ├── service.go └── wasmtime ├── container.go ├── platform.go └── util.go /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## containerd wasm shim 2 | 3 | **PROOF OF CONCEPT WARNING** 4 | 5 | A wasm implementation of a containerd runtime using the containerd shim interface 6 | 7 | Uses wasmtime to execute wasm/wasi binaries 8 | -------------------------------------------------------------------------------- /cmd/containerd-shim-wasm-v1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | Copyright The containerd Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/containerd/containerd/v2/pkg/shim" 23 | wasm "github.com/dmcgowan/containerd-wasm" 24 | ) 25 | 26 | func main() { 27 | shim.Run(context.Background(), wasm.NewManager("io.containerd.wasm.v1")) 28 | } 29 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dmcgowan/containerd-wasm 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/containerd/cgroups/v3 v3.0.3 7 | github.com/containerd/console v1.0.4 8 | github.com/containerd/containerd/api v1.8.0 9 | github.com/containerd/containerd/v2 v2.0.0 10 | github.com/containerd/errdefs v1.0.0 11 | github.com/containerd/errdefs/pkg v0.3.0 12 | github.com/containerd/fifo v1.1.0 13 | github.com/containerd/log v0.1.0 14 | github.com/containerd/plugin v1.0.0 15 | github.com/containerd/ttrpc v1.2.6 16 | github.com/containerd/typeurl/v2 v2.2.2 17 | github.com/opencontainers/runtime-spec v1.2.0 18 | github.com/pkg/errors v0.9.1 19 | github.com/sirupsen/logrus v1.9.3 20 | golang.org/x/sys v0.26.0 21 | ) 22 | 23 | require ( 24 | github.com/Microsoft/go-winio v0.6.2 // indirect 25 | github.com/Microsoft/hcsshim v0.12.9 // indirect 26 | github.com/cilium/ebpf v0.11.0 // indirect 27 | github.com/containerd/continuity v0.4.5 // indirect 28 | github.com/containerd/go-runc v1.1.0 // indirect 29 | github.com/containerd/platforms v1.0.0-rc.0 // indirect 30 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 31 | github.com/docker/go-units v0.5.0 // indirect 32 | github.com/fsnotify/fsnotify v1.7.0 // indirect 33 | github.com/godbus/dbus/v5 v5.1.0 // indirect 34 | github.com/gogo/protobuf v1.3.2 // indirect 35 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 36 | github.com/google/go-cmp v0.6.0 // indirect 37 | github.com/klauspost/compress v1.17.11 // indirect 38 | github.com/mdlayher/socket v0.4.1 // indirect 39 | github.com/mdlayher/vsock v1.2.1 // indirect 40 | github.com/moby/sys/mountinfo v0.7.2 // indirect 41 | github.com/moby/sys/user v0.3.0 // indirect 42 | github.com/moby/sys/userns v0.1.0 // indirect 43 | github.com/opencontainers/go-digest v1.0.0 // indirect 44 | github.com/opencontainers/image-spec v1.1.0 // indirect 45 | github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 // indirect 46 | github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect 47 | go.opencensus.io v0.24.0 // indirect 48 | golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 // indirect 49 | golang.org/x/mod v0.21.0 // indirect 50 | golang.org/x/net v0.30.0 // indirect 51 | golang.org/x/sync v0.9.0 // indirect 52 | golang.org/x/text v0.19.0 // indirect 53 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect 54 | google.golang.org/grpc v1.67.1 // indirect 55 | google.golang.org/protobuf v1.35.1 // indirect 56 | sigs.k8s.io/yaml v1.4.0 // indirect 57 | tags.cncf.io/container-device-interface v0.8.0 // indirect 58 | tags.cncf.io/container-device-interface/specs-go v0.8.0 // indirect 59 | ) 60 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 4 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 5 | github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= 6 | github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= 7 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 8 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 9 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 10 | github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= 11 | github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= 12 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 13 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 14 | github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= 15 | github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= 16 | github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= 17 | github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 18 | github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0= 19 | github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= 20 | github.com/containerd/containerd/v2 v2.0.0 h1:qLDdFaAykQrIyLiqwQrNLLz95wiC36bAZVwioUwqShM= 21 | github.com/containerd/containerd/v2 v2.0.0/go.mod h1:j25kDy9P48/ngb1sxWIFfK6GsnqOHoSqo1EpAod20VQ= 22 | github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= 23 | github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= 24 | github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= 25 | github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= 26 | github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= 27 | github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= 28 | github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= 29 | github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= 30 | github.com/containerd/go-runc v1.1.0 h1:OX4f+/i2y5sUT7LhmcJH7GYrjjhHa1QI4e8yO0gGleA= 31 | github.com/containerd/go-runc v1.1.0/go.mod h1:xJv2hFF7GvHtTJd9JqTS2UVxMkULUYw4JN5XAUZqH5U= 32 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 33 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 34 | github.com/containerd/platforms v1.0.0-rc.0 h1:GuHWSKgVVO3POn6nRBB4sH63uPOLa87yuuhsGLWaXAA= 35 | github.com/containerd/platforms v1.0.0-rc.0/go.mod h1:T1XAzzOdYs3it7l073MNXyxRwQofJfqwi/8cRjufIk4= 36 | github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= 37 | github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= 38 | github.com/containerd/ttrpc v1.2.6 h1:zG+Kn5EZ6MUYCS1t2Hmt2J4tMVaLSFEJVOraDQwNPC4= 39 | github.com/containerd/ttrpc v1.2.6/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= 40 | github.com/containerd/typeurl/v2 v2.2.2 h1:3jN/k2ysKuPCsln5Qv8bzR9cxal8XjkxPogJfSNO31k= 41 | github.com/containerd/typeurl/v2 v2.2.2/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= 42 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 43 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 44 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 45 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 46 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 47 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 48 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 49 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 50 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 51 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 52 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 53 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 54 | github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= 55 | github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 56 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 57 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 58 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 59 | github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= 60 | github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 61 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 62 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 63 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 64 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 65 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 66 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 67 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 68 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 69 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 70 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 71 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 72 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 73 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 74 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 75 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 76 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 77 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 78 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 79 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 80 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 81 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 82 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 83 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 84 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 85 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 86 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 87 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 88 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 89 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 90 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 91 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 92 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 93 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 94 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 95 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 96 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 97 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 98 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 99 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 100 | github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= 101 | github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= 102 | github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= 103 | github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= 104 | github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b/go.mod h1:pzzDgJWZ34fGzaAZGFW22KVZDfyrYW+QABMrWnJBnSs= 105 | github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= 106 | github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= 107 | github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= 108 | github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= 109 | github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= 110 | github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= 111 | github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= 112 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 113 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 114 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 115 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 116 | github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 117 | github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= 118 | github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 119 | github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 h1:DmNGcqH3WDbV5k8OJ+esPWbqUOX5rMLR2PMvziDMJi0= 120 | github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626/go.mod h1:BRHJJd0E+cx42OybVYSgUvZmU0B8P9gZuRXlZUP7TKI= 121 | github.com/opencontainers/selinux v1.9.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= 122 | github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8= 123 | github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= 124 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 125 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 126 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 127 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 128 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 129 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 130 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 131 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 132 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 133 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 134 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 135 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 136 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 137 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 138 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 139 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 140 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 141 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 142 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 143 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 144 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 145 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 146 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 147 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 148 | github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= 149 | github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= 150 | github.com/urfave/cli v1.19.1/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 151 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 152 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= 153 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 154 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 155 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 156 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 157 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 158 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 159 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 160 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 161 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 162 | go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= 163 | go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 164 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 165 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 166 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 167 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 168 | golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4= 169 | golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 170 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 171 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 172 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 173 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 174 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 175 | golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= 176 | golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 177 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 178 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 179 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 180 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 181 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 182 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 183 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 184 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 185 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 186 | golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= 187 | golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 188 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 189 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 190 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 191 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 192 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 193 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 194 | golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= 195 | golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 196 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 197 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 198 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 199 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 200 | golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 201 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 202 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 203 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 204 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 205 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 206 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 207 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 208 | golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= 209 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 210 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 211 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 212 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 213 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 214 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 215 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 216 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 217 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 218 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 219 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 220 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 221 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 222 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 223 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 224 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 225 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 226 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 227 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= 228 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 229 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 230 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 231 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 232 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 233 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 234 | google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= 235 | google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= 236 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 237 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 238 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 239 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 240 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 241 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 242 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 243 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 244 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 245 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 246 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 247 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 248 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 249 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 250 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 251 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 252 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 253 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 254 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 255 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 256 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 257 | tags.cncf.io/container-device-interface v0.8.0 h1:8bCFo/g9WODjWx3m6EYl3GfUG31eKJbaggyBDxEldRc= 258 | tags.cncf.io/container-device-interface v0.8.0/go.mod h1:Apb7N4VdILW0EVdEMRYXIDVRZfNJZ+kmEUss2kRRQ6Y= 259 | tags.cncf.io/container-device-interface/specs-go v0.8.0 h1:QYGFzGxvYK/ZLMrjhvY0RjpUavIn4KcmRmVP/JjdBTA= 260 | tags.cncf.io/container-device-interface/specs-go v0.8.0/go.mod h1:BhJIkjjPh4qpys+qm4DAYtUyryaTDg9zris+AczXyws= 261 | -------------------------------------------------------------------------------- /service.go: -------------------------------------------------------------------------------- 1 | package wasm 2 | 3 | /* 4 | Copyright The containerd Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "io" 24 | "net" 25 | "os" 26 | "os/exec" 27 | "path/filepath" 28 | "sync" 29 | "syscall" 30 | "time" 31 | 32 | "github.com/containerd/cgroups/v3" 33 | "github.com/containerd/cgroups/v3/cgroup1" 34 | eventstypes "github.com/containerd/containerd/api/events" 35 | taskAPI "github.com/containerd/containerd/api/runtime/task/v3" 36 | "github.com/containerd/containerd/api/types" 37 | apitypes "github.com/containerd/containerd/api/types" 38 | "github.com/containerd/containerd/api/types/runc/options" 39 | "github.com/containerd/containerd/api/types/task" 40 | "github.com/containerd/containerd/v2/core/mount" 41 | "github.com/containerd/containerd/v2/pkg/namespaces" 42 | "github.com/containerd/containerd/v2/pkg/oci" 43 | "github.com/containerd/containerd/v2/pkg/oom" 44 | oomv1 "github.com/containerd/containerd/v2/pkg/oom/v1" 45 | oomv2 "github.com/containerd/containerd/v2/pkg/oom/v2" 46 | "github.com/containerd/containerd/v2/pkg/protobuf" 47 | ptypes "github.com/containerd/containerd/v2/pkg/protobuf/types" 48 | "github.com/containerd/containerd/v2/pkg/schedcore" 49 | "github.com/containerd/containerd/v2/pkg/shim" 50 | "github.com/containerd/containerd/v2/pkg/shutdown" 51 | "github.com/containerd/containerd/v2/pkg/stdio" 52 | "github.com/containerd/containerd/v2/plugins" 53 | "github.com/containerd/errdefs" 54 | "github.com/containerd/errdefs/pkg/errgrpc" 55 | "github.com/containerd/log" 56 | "github.com/containerd/plugin" 57 | "github.com/containerd/plugin/registry" 58 | "github.com/containerd/ttrpc" 59 | "github.com/containerd/typeurl/v2" 60 | "github.com/dmcgowan/containerd-wasm/wasmtime" 61 | "github.com/pkg/errors" 62 | "github.com/sirupsen/logrus" 63 | "golang.org/x/sys/unix" 64 | goruntime "runtime" 65 | ) 66 | 67 | var ( 68 | _ = shim.TTRPCService(&service{}) 69 | empty = &ptypes.Empty{} 70 | ) 71 | 72 | // group labels specifies how the shim groups services. 73 | // currently supports a runc.v2 specific .group label and the 74 | // standard k8s pod label. Order matters in this list 75 | var groupLabels = []string{ 76 | "io.containerd.runc.v2.group", 77 | "io.kubernetes.cri.sandbox-id", 78 | } 79 | 80 | type spec struct { 81 | Annotations map[string]string `json:"annotations,omitempty"` 82 | } 83 | 84 | func init() { 85 | registry.Register(&plugin.Registration{ 86 | Type: plugins.TTRPCPlugin, 87 | ID: "task", 88 | Requires: []plugin.Type{ 89 | plugins.EventPlugin, 90 | plugins.InternalPlugin, 91 | }, 92 | InitFn: func(ic *plugin.InitContext) (interface{}, error) { 93 | pp, err := ic.GetByID(plugins.EventPlugin, "publisher") 94 | if err != nil { 95 | return nil, err 96 | } 97 | ss, err := ic.GetByID(plugins.InternalPlugin, "shutdown") 98 | if err != nil { 99 | return nil, err 100 | } 101 | return newTaskService(ic.Context, pp.(shim.Publisher), ss.(shutdown.Service)) 102 | }, 103 | }) 104 | } 105 | 106 | func newTaskService(ctx context.Context, publisher shim.Publisher, sd shutdown.Service) (taskAPI.TTRPCTaskService, error) { 107 | var ( 108 | ep oom.Watcher 109 | err error 110 | ) 111 | if cgroups.Mode() == cgroups.Unified { 112 | ep, err = oomv2.New(publisher) 113 | } else { 114 | ep, err = oomv1.New(publisher) 115 | } 116 | if err != nil { 117 | return nil, err 118 | } 119 | go ep.Run(ctx) 120 | s := &service{ 121 | context: ctx, 122 | events: make(chan interface{}, 128), 123 | ec: make(chan wasmtime.Exit), 124 | ep: ep, 125 | shutdown: sd, 126 | containers: make(map[string]*wasmtime.Container), 127 | } 128 | go s.processExits() 129 | if err := s.initPlatform(); err != nil { 130 | return nil, errors.Wrap(err, "failed to initialized platform behavior") 131 | } 132 | go s.forward(ctx, publisher) 133 | return s, nil 134 | } 135 | 136 | // service is the shim implementation of a remote shim over GRPC 137 | type service struct { 138 | mu sync.Mutex 139 | eventSendMu sync.Mutex 140 | 141 | context context.Context 142 | events chan interface{} 143 | platform stdio.Platform 144 | ec chan wasmtime.Exit 145 | ep oom.Watcher 146 | 147 | // id only used in cleanup case 148 | id string 149 | 150 | containers map[string]*wasmtime.Container 151 | 152 | shutdown shutdown.Service 153 | } 154 | 155 | func newCommand(ctx context.Context, id, containerdAddress, containerdTTRPCAddress string, debug bool) (*exec.Cmd, error) { 156 | ns, err := namespaces.NamespaceRequired(ctx) 157 | if err != nil { 158 | return nil, err 159 | } 160 | self, err := os.Executable() 161 | if err != nil { 162 | return nil, err 163 | } 164 | cwd, err := os.Getwd() 165 | if err != nil { 166 | return nil, err 167 | } 168 | args := []string{ 169 | "-namespace", ns, 170 | "-id", id, 171 | "-address", containerdAddress, 172 | } 173 | if debug { 174 | args = append(args, "-debug") 175 | } 176 | cmd := exec.Command(self, args...) 177 | cmd.Dir = cwd 178 | cmd.Env = append(os.Environ(), "GOMAXPROCS=4") 179 | cmd.SysProcAttr = &syscall.SysProcAttr{ 180 | Setpgid: true, 181 | } 182 | return cmd, nil 183 | } 184 | 185 | func readSpec() (*spec, error) { 186 | f, err := os.Open(oci.ConfigFilename) 187 | if err != nil { 188 | return nil, err 189 | } 190 | defer f.Close() 191 | var s spec 192 | if err := json.NewDecoder(f).Decode(&s); err != nil { 193 | return nil, err 194 | } 195 | return &s, nil 196 | } 197 | 198 | type shimSocket struct { 199 | addr string 200 | s *net.UnixListener 201 | f *os.File 202 | } 203 | 204 | func (s *shimSocket) Close() { 205 | if s.s != nil { 206 | s.s.Close() 207 | } 208 | if s.f != nil { 209 | s.f.Close() 210 | } 211 | _ = shim.RemoveSocket(s.addr) 212 | } 213 | 214 | func newShimSocket(ctx context.Context, path, id string, debug bool) (*shimSocket, error) { 215 | address, err := shim.SocketAddress(ctx, path, id, debug) 216 | if err != nil { 217 | return nil, err 218 | } 219 | socket, err := shim.NewSocket(address) 220 | if err != nil { 221 | // the only time where this would happen is if there is a bug and the socket 222 | // was not cleaned up in the cleanup method of the shim or we are using the 223 | // grouping functionality where the new process should be run with the same 224 | // shim as an existing container 225 | if !shim.SocketEaddrinuse(err) { 226 | return nil, fmt.Errorf("create new shim socket: %w", err) 227 | } 228 | if !debug && shim.CanConnect(address) { 229 | return &shimSocket{addr: address}, errdefs.ErrAlreadyExists 230 | } 231 | if err := shim.RemoveSocket(address); err != nil { 232 | return nil, fmt.Errorf("remove pre-existing socket: %w", err) 233 | } 234 | if socket, err = shim.NewSocket(address); err != nil { 235 | return nil, fmt.Errorf("try create new shim socket 2x: %w", err) 236 | } 237 | } 238 | s := &shimSocket{ 239 | addr: address, 240 | s: socket, 241 | } 242 | f, err := socket.File() 243 | if err != nil { 244 | s.Close() 245 | return nil, err 246 | } 247 | s.f = f 248 | return s, nil 249 | } 250 | 251 | func NewManager(name string) shim.Manager { 252 | return manager{name: name} 253 | } 254 | 255 | // manager is implemented based on https://github.com/containerd/containerd/blob/v2.0.0/cmd/containerd-shim-runc-v2/manager/manager_linux.go 256 | type manager struct { 257 | name string 258 | containerdAddress string 259 | containerdTTRPCAddress string 260 | env []string 261 | runtimePaths sync.Map 262 | } 263 | 264 | func (m manager) Name() string { 265 | return m.name 266 | } 267 | 268 | func (m manager) Start(ctx context.Context, id string, opts shim.StartOpts) (_ shim.BootstrapParams, retErr error) { 269 | var params shim.BootstrapParams 270 | params.Version = 3 271 | params.Protocol = "ttrpc" 272 | 273 | cmd, err := newCommand(ctx, id, opts.Address, opts.TTRPCAddress, opts.Debug) 274 | if err != nil { 275 | return params, err 276 | } 277 | grouping := id 278 | spec, err := readSpec() 279 | if err != nil { 280 | return params, err 281 | } 282 | for _, group := range groupLabels { 283 | if groupID, ok := spec.Annotations[group]; ok { 284 | grouping = groupID 285 | break 286 | } 287 | } 288 | 289 | var sockets []*shimSocket 290 | defer func() { 291 | if retErr != nil { 292 | for _, s := range sockets { 293 | s.Close() 294 | } 295 | } 296 | }() 297 | 298 | s, err := newShimSocket(ctx, opts.Address, grouping, false) 299 | if err != nil { 300 | if errdefs.IsAlreadyExists(err) { 301 | params.Address = s.addr 302 | return params, nil 303 | } 304 | return params, err 305 | } 306 | sockets = append(sockets, s) 307 | cmd.ExtraFiles = append(cmd.ExtraFiles, s.f) 308 | 309 | if opts.Debug { 310 | s, err = newShimSocket(ctx, opts.Address, grouping, true) 311 | if err != nil { 312 | return params, err 313 | } 314 | sockets = append(sockets, s) 315 | cmd.ExtraFiles = append(cmd.ExtraFiles, s.f) 316 | } 317 | 318 | goruntime.LockOSThread() 319 | if os.Getenv("SCHED_CORE") != "" { 320 | if err := schedcore.Create(schedcore.ProcessGroup); err != nil { 321 | return params, fmt.Errorf("enable sched core support: %w", err) 322 | } 323 | } 324 | 325 | if err := cmd.Start(); err != nil { 326 | return params, err 327 | } 328 | 329 | goruntime.UnlockOSThread() 330 | 331 | defer func() { 332 | if retErr != nil { 333 | cmd.Process.Kill() 334 | } 335 | }() 336 | // make sure to wait after start 337 | go cmd.Wait() 338 | 339 | if err := shim.AdjustOOMScore(cmd.Process.Pid); err != nil { 340 | return params, fmt.Errorf("failed to adjust OOM score for shim: %w", err) 341 | } 342 | 343 | params.Address = sockets[0].addr 344 | return params, nil 345 | } 346 | 347 | func (m manager) Stop(ctx context.Context, id string) (shim.StopStatus, error) { 348 | cwd, err := os.Getwd() 349 | if err != nil { 350 | return shim.StopStatus{}, err 351 | } 352 | 353 | path := filepath.Join(filepath.Dir(cwd), id) 354 | // ns, err := namespaces.NamespaceRequired(ctx) 355 | // if err != nil { 356 | // return shim.StopStatus{}, err 357 | // } 358 | // runtime, err := runc.ReadRuntime(path) 359 | // if err != nil { 360 | // return shim.StopStatus{}, err 361 | // } 362 | // opts, err := runc.ReadOptions(path) 363 | // if err != nil { 364 | // return shim.StopStatus{}, err 365 | // } 366 | // root := process.RuncRoot 367 | // if opts != nil && opts.Root != "" { 368 | // root = opts.Root 369 | // } 370 | 371 | // r := process.NewRunc(root, path, ns, runtime, false) 372 | // if err := r.Delete(ctx, id, &runcC.DeleteOpts{ 373 | // Force: true, 374 | // }); err != nil { 375 | // log.G(ctx).WithError(err).Warn("failed to remove runc container") 376 | // } 377 | if err := mount.UnmountRecursive(filepath.Join(path, "rootfs"), 0); err != nil { 378 | log.G(ctx).WithError(err).Warn("failed to cleanup rootfs mount") 379 | } 380 | // pid, err := runcC.ReadPidFile(filepath.Join(path, process.InitPidFile)) 381 | // if err != nil { 382 | // log.G(ctx).WithError(err).Warn("failed to read init pid file") 383 | // } 384 | return shim.StopStatus{ 385 | ExitedAt: time.Now(), 386 | ExitStatus: 128 + int(unix.SIGKILL), 387 | //Pid: pid, 388 | }, nil 389 | } 390 | 391 | func (m manager) Info(ctx context.Context, optionsR io.Reader) (*apitypes.RuntimeInfo, error) { 392 | info := &types.RuntimeInfo{ 393 | Name: m.name, 394 | } 395 | return info, nil 396 | } 397 | 398 | // RegisterTTRPC allows TTRPC services to be registered with the underlying server 399 | func (s *service) RegisterTTRPC(server *ttrpc.Server) error { 400 | taskAPI.RegisterTTRPCTaskService(server, s) 401 | return nil 402 | } 403 | 404 | // Create a new initial process and container with the underlying OCI runtime 405 | func (s *service) Create(ctx context.Context, r *taskAPI.CreateTaskRequest) (_ *taskAPI.CreateTaskResponse, err error) { 406 | s.mu.Lock() 407 | defer s.mu.Unlock() 408 | 409 | logrus.Infof("creating %s", r.ID) 410 | 411 | container, err := wasmtime.NewContainer(ctx, s.platform, r, s.ec) 412 | if err != nil { 413 | return nil, err 414 | } 415 | 416 | s.containers[r.ID] = container 417 | 418 | s.send(&eventstypes.TaskCreate{ 419 | ContainerID: r.ID, 420 | Bundle: r.Bundle, 421 | Rootfs: r.Rootfs, 422 | IO: &eventstypes.TaskIO{ 423 | Stdin: r.Stdin, 424 | Stdout: r.Stdout, 425 | Stderr: r.Stderr, 426 | Terminal: r.Terminal, 427 | }, 428 | Checkpoint: r.Checkpoint, 429 | Pid: uint32(container.Pid()), 430 | }) 431 | 432 | return &taskAPI.CreateTaskResponse{ 433 | Pid: uint32(container.Pid()), 434 | }, nil 435 | } 436 | 437 | // Start a process 438 | func (s *service) Start(ctx context.Context, r *taskAPI.StartRequest) (*taskAPI.StartResponse, error) { 439 | logrus.Infof("starting %s", r.ID) 440 | container, err := s.getContainer(r.ID) 441 | if err != nil { 442 | return nil, err 443 | } 444 | 445 | // hold the send lock so that the start events are sent before any exit events in the error case 446 | s.eventSendMu.Lock() 447 | p, err := container.Start(ctx, r) 448 | if err != nil { 449 | s.eventSendMu.Unlock() 450 | return nil, errgrpc.ToGRPC(err) 451 | } 452 | //if err := s.ep.Add(container.ID, container.Cgroup()); err != nil { 453 | // logrus.WithError(err).Error("add cg to OOM monitor") 454 | //} 455 | switch r.ExecID { 456 | case "": 457 | s.send(&eventstypes.TaskStart{ 458 | ContainerID: container.ID, 459 | Pid: uint32(p.Pid()), 460 | }) 461 | default: 462 | s.send(&eventstypes.TaskExecStarted{ 463 | ContainerID: container.ID, 464 | ExecID: r.ExecID, 465 | Pid: uint32(p.Pid()), 466 | }) 467 | } 468 | s.eventSendMu.Unlock() 469 | return &taskAPI.StartResponse{ 470 | Pid: uint32(p.Pid()), 471 | }, nil 472 | } 473 | 474 | // Delete the initial process and container 475 | func (s *service) Delete(ctx context.Context, r *taskAPI.DeleteRequest) (*taskAPI.DeleteResponse, error) { 476 | container, err := s.getContainer(r.ID) 477 | if err != nil { 478 | return nil, err 479 | } 480 | p, err := container.Delete(ctx, r) 481 | if err != nil { 482 | return nil, errgrpc.ToGRPC(err) 483 | } 484 | // if we deleted our init task, close the platform and send the task delete event 485 | if r.ExecID == "" { 486 | s.mu.Lock() 487 | logrus.WithField("id", r.ID).Info("deleting container") 488 | delete(s.containers, r.ID) 489 | hasContainers := len(s.containers) > 0 490 | s.mu.Unlock() 491 | if s.platform != nil && !hasContainers { 492 | s.platform.Close() 493 | } 494 | s.send(&eventstypes.TaskDelete{ 495 | ContainerID: container.ID, 496 | Pid: uint32(p.Pid()), 497 | ExitStatus: uint32(p.ExitStatus()), 498 | ExitedAt: protobuf.ToTimestamp(p.ExitedAt()), 499 | }) 500 | } 501 | return &taskAPI.DeleteResponse{ 502 | ExitStatus: uint32(p.ExitStatus()), 503 | ExitedAt: protobuf.ToTimestamp(p.ExitedAt()), 504 | Pid: uint32(p.Pid()), 505 | }, nil 506 | } 507 | 508 | // Exec an additional process inside the container 509 | func (s *service) Exec(ctx context.Context, r *taskAPI.ExecProcessRequest) (*ptypes.Empty, error) { 510 | container, err := s.getContainer(r.ID) 511 | if err != nil { 512 | return nil, err 513 | } 514 | if container.ProcessExists(r.ExecID) { 515 | return nil, errgrpc.ToGRPCf(errdefs.ErrAlreadyExists, "id %s", r.ExecID) 516 | } 517 | process, err := container.Exec(ctx, r) 518 | if err != nil { 519 | return nil, errgrpc.ToGRPC(err) 520 | } 521 | 522 | s.send(&eventstypes.TaskExecAdded{ 523 | ContainerID: container.ID, 524 | ExecID: process.ID(), 525 | }) 526 | return empty, nil 527 | } 528 | 529 | // ResizePty of a process 530 | func (s *service) ResizePty(ctx context.Context, r *taskAPI.ResizePtyRequest) (*ptypes.Empty, error) { 531 | container, err := s.getContainer(r.ID) 532 | if err != nil { 533 | return nil, err 534 | } 535 | if err := container.ResizePty(ctx, r); err != nil { 536 | return nil, errgrpc.ToGRPC(err) 537 | } 538 | return empty, nil 539 | } 540 | 541 | // State returns runtime state information for a process 542 | func (s *service) State(ctx context.Context, r *taskAPI.StateRequest) (*taskAPI.StateResponse, error) { 543 | container, err := s.getContainer(r.ID) 544 | if err != nil { 545 | return nil, err 546 | } 547 | p, err := container.Process(r.ExecID) 548 | if err != nil { 549 | return nil, err 550 | } 551 | st, err := p.Status(ctx) 552 | if err != nil { 553 | return nil, err 554 | } 555 | status := task.Status_UNKNOWN 556 | switch st { 557 | case "created": 558 | status = task.Status_CREATED 559 | case "running": 560 | status = task.Status_RUNNING 561 | case "stopped": 562 | status = task.Status_STOPPED 563 | case "paused": 564 | status = task.Status_PAUSED 565 | case "pausing": 566 | status = task.Status_PAUSING 567 | } 568 | sio := p.Stdio() 569 | return &taskAPI.StateResponse{ 570 | ID: p.ID(), 571 | Bundle: container.Bundle, 572 | Pid: uint32(p.Pid()), 573 | Status: status, 574 | Stdin: sio.Stdin, 575 | Stdout: sio.Stdout, 576 | Stderr: sio.Stderr, 577 | Terminal: sio.Terminal, 578 | ExitStatus: uint32(p.ExitStatus()), 579 | ExitedAt: protobuf.ToTimestamp(p.ExitedAt()), 580 | }, nil 581 | } 582 | 583 | // Pause the container 584 | func (s *service) Pause(ctx context.Context, r *taskAPI.PauseRequest) (*ptypes.Empty, error) { 585 | container, err := s.getContainer(r.ID) 586 | if err != nil { 587 | return nil, err 588 | } 589 | if err := container.Pause(ctx); err != nil { 590 | return nil, errgrpc.ToGRPC(err) 591 | } 592 | s.send(&eventstypes.TaskPaused{ 593 | ContainerID: container.ID, 594 | }) 595 | return empty, nil 596 | } 597 | 598 | // Resume the container 599 | func (s *service) Resume(ctx context.Context, r *taskAPI.ResumeRequest) (*ptypes.Empty, error) { 600 | container, err := s.getContainer(r.ID) 601 | if err != nil { 602 | return nil, err 603 | } 604 | if err := container.Resume(ctx); err != nil { 605 | return nil, errgrpc.ToGRPC(err) 606 | } 607 | s.send(&eventstypes.TaskResumed{ 608 | ContainerID: container.ID, 609 | }) 610 | return empty, nil 611 | } 612 | 613 | // Kill a process with the provided signal 614 | func (s *service) Kill(ctx context.Context, r *taskAPI.KillRequest) (*ptypes.Empty, error) { 615 | container, err := s.getContainer(r.ID) 616 | if err != nil { 617 | return nil, err 618 | } 619 | if err := container.Kill(ctx, r); err != nil { 620 | return nil, errgrpc.ToGRPC(err) 621 | } 622 | return empty, nil 623 | } 624 | 625 | // Pids returns all pids inside the container 626 | func (s *service) Pids(ctx context.Context, r *taskAPI.PidsRequest) (*taskAPI.PidsResponse, error) { 627 | container, err := s.getContainer(r.ID) 628 | if err != nil { 629 | return nil, err 630 | } 631 | pids, err := s.getContainerPids(ctx, container) 632 | if err != nil { 633 | return nil, errgrpc.ToGRPC(err) 634 | } 635 | var processes []*task.ProcessInfo 636 | for _, pid := range pids { 637 | pInfo := task.ProcessInfo{ 638 | Pid: pid, 639 | } 640 | for _, p := range container.ExecdProcesses() { 641 | if p.Pid() == int(pid) { 642 | d := &options.ProcessDetails{ 643 | ExecID: p.ID(), 644 | } 645 | a, err := typeurl.MarshalAnyToProto(d) 646 | if err != nil { 647 | return nil, fmt.Errorf("failed to marshal process %d info: %w", pid, err) 648 | } 649 | pInfo.Info = a 650 | break 651 | } 652 | } 653 | processes = append(processes, &pInfo) 654 | } 655 | return &taskAPI.PidsResponse{ 656 | Processes: processes, 657 | }, nil 658 | } 659 | 660 | // CloseIO of a process 661 | func (s *service) CloseIO(ctx context.Context, r *taskAPI.CloseIORequest) (*ptypes.Empty, error) { 662 | container, err := s.getContainer(r.ID) 663 | if err != nil { 664 | return nil, err 665 | } 666 | if err := container.CloseIO(ctx, r); err != nil { 667 | return nil, err 668 | } 669 | return empty, nil 670 | } 671 | 672 | // Checkpoint the container 673 | func (s *service) Checkpoint(ctx context.Context, r *taskAPI.CheckpointTaskRequest) (*ptypes.Empty, error) { 674 | container, err := s.getContainer(r.ID) 675 | if err != nil { 676 | return nil, err 677 | } 678 | if err := container.Checkpoint(ctx, r); err != nil { 679 | return nil, errgrpc.ToGRPC(err) 680 | } 681 | return empty, nil 682 | } 683 | 684 | // Update a running container 685 | func (s *service) Update(ctx context.Context, r *taskAPI.UpdateTaskRequest) (*ptypes.Empty, error) { 686 | container, err := s.getContainer(r.ID) 687 | if err != nil { 688 | return nil, err 689 | } 690 | if err := container.Update(ctx, r); err != nil { 691 | return nil, errgrpc.ToGRPC(err) 692 | } 693 | return empty, nil 694 | } 695 | 696 | // Wait for a process to exit 697 | func (s *service) Wait(ctx context.Context, r *taskAPI.WaitRequest) (*taskAPI.WaitResponse, error) { 698 | container, err := s.getContainer(r.ID) 699 | if err != nil { 700 | return nil, err 701 | } 702 | p, err := container.Process(r.ExecID) 703 | if err != nil { 704 | return nil, errgrpc.ToGRPC(err) 705 | } 706 | p.Wait() 707 | 708 | return &taskAPI.WaitResponse{ 709 | ExitStatus: uint32(p.ExitStatus()), 710 | ExitedAt: protobuf.ToTimestamp(p.ExitedAt()), 711 | }, nil 712 | } 713 | 714 | // Connect returns shim information such as the shim's pid 715 | func (s *service) Connect(ctx context.Context, r *taskAPI.ConnectRequest) (*taskAPI.ConnectResponse, error) { 716 | var pid int 717 | if container, err := s.getContainer(r.ID); err == nil { 718 | pid = container.Pid() 719 | } 720 | return &taskAPI.ConnectResponse{ 721 | ShimPid: uint32(os.Getpid()), 722 | TaskPid: uint32(pid), 723 | }, nil 724 | } 725 | 726 | func (s *service) Shutdown(ctx context.Context, r *taskAPI.ShutdownRequest) (*ptypes.Empty, error) { 727 | s.mu.Lock() 728 | // return out if the shim is still servicing containers 729 | if len(s.containers) > 0 { 730 | s.mu.Unlock() 731 | return empty, nil 732 | } 733 | s.shutdown.Shutdown() 734 | close(s.events) 735 | return empty, nil 736 | } 737 | 738 | func (s *service) Stats(ctx context.Context, r *taskAPI.StatsRequest) (*taskAPI.StatsResponse, error) { 739 | container, err := s.getContainer(r.ID) 740 | if err != nil { 741 | return nil, err 742 | } 743 | cg := container.Cgroup() 744 | if cg == nil { 745 | return nil, errgrpc.ToGRPCf(errdefs.ErrNotFound, "cgroup does not exist") 746 | } 747 | stats, err := cg.Stat(cgroup1.IgnoreNotExist) 748 | if err != nil { 749 | return nil, err 750 | } 751 | data, err := typeurl.MarshalAny(stats) 752 | if err != nil { 753 | return nil, err 754 | } 755 | return &taskAPI.StatsResponse{ 756 | Stats: typeurl.MarshalProto(data), 757 | }, nil 758 | } 759 | 760 | func (s *service) send(evt interface{}) { 761 | s.events <- evt 762 | } 763 | 764 | func (s *service) sendL(evt interface{}) { 765 | s.eventSendMu.Lock() 766 | s.events <- evt 767 | s.eventSendMu.Unlock() 768 | } 769 | 770 | // TODO: provide way to check processes 771 | func (s *service) processExits() { 772 | for e := range s.ec { 773 | s.checkProcesses(e) 774 | } 775 | } 776 | 777 | func (s *service) checkProcesses(e wasmtime.Exit) { 778 | s.mu.Lock() 779 | defer s.mu.Unlock() 780 | 781 | for _, container := range s.containers { 782 | if container.HasPid(e.Pid) { 783 | //shouldKillAll, err := shouldKillAllOnExit(container.Bundle) 784 | //if err != nil { 785 | // log.G(s.context).WithError(err).Error("failed to check shouldKillAll") 786 | //} 787 | 788 | for _, p := range container.All() { 789 | if p.Pid() == e.Pid { 790 | //if shouldKillAll { 791 | // if ip, ok := p.(*proc.Init); ok { 792 | // // Ensure all children are killed 793 | // if err := ip.KillAll(s.context); err != nil { 794 | // logrus.WithError(err).WithField("id", ip.ID()). 795 | // Error("failed to kill init's children") 796 | // } 797 | // } 798 | //} 799 | p.SetExited(e.Status) 800 | s.sendL(&eventstypes.TaskExit{ 801 | ContainerID: container.ID, 802 | ID: p.ID(), 803 | Pid: uint32(e.Pid), 804 | ExitStatus: uint32(e.Status), 805 | ExitedAt: protobuf.ToTimestamp(p.ExitedAt()), 806 | }) 807 | return 808 | } 809 | } 810 | return 811 | } 812 | } 813 | } 814 | 815 | func shouldKillAllOnExit(bundlePath string) (bool, error) { 816 | //var bundleSpec specs.Spec 817 | //bundleConfigContents, err := ioutil.ReadFile(filepath.Join(bundlePath, "config.json")) 818 | //if err != nil { 819 | // return false, err 820 | //} 821 | //json.Unmarshal(bundleConfigContents, &bundleSpec) 822 | 823 | //if bundleSpec.Linux != nil { 824 | // for _, ns := range bundleSpec.Linux.Namespaces { 825 | // if ns.Type == specs.PIDNamespace && ns.Path == "" { 826 | // return false, nil 827 | // } 828 | // } 829 | //} 830 | 831 | return true, nil 832 | } 833 | 834 | func (s *service) getContainerPids(ctx context.Context, container *wasmtime.Container) ([]uint32, error) { 835 | // TODO: Return type capable of getting pids 836 | return []uint32{}, nil 837 | //container, err := s.getContainer(id) 838 | //if err != nil { 839 | // return nil, err 840 | //} 841 | //p, err := container.Process("") 842 | //if err != nil { 843 | // return nil, errgrpc.ToGRPC(err) 844 | //} 845 | //ps, err := p.(*proc.Init).Runtime().Ps(ctx, id) 846 | //if err != nil { 847 | // return nil, err 848 | //} 849 | //pids := make([]uint32, 0, len(ps)) 850 | //for _, pid := range ps { 851 | // pids = append(pids, uint32(pid)) 852 | //} 853 | //return pids, nil 854 | } 855 | 856 | func (s *service) forward(ctx context.Context, publisher shim.Publisher) { 857 | ns, _ := namespaces.Namespace(ctx) 858 | ctx = namespaces.WithNamespace(context.Background(), ns) 859 | for e := range s.events { 860 | ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 861 | err := publisher.Publish(ctx, wasmtime.GetTopic(e), e) 862 | cancel() 863 | if err != nil { 864 | logrus.WithError(err).Error("post event") 865 | } 866 | } 867 | publisher.Close() 868 | } 869 | 870 | func (s *service) getContainer(id string) (*wasmtime.Container, error) { 871 | s.mu.Lock() 872 | container := s.containers[id] 873 | s.mu.Unlock() 874 | if container == nil { 875 | return nil, errgrpc.ToGRPCf(errdefs.ErrNotFound, "container not created") 876 | } 877 | return container, nil 878 | } 879 | 880 | // initialize a single epoll fd to manage our consoles. `initPlatform` should 881 | // only be called once. 882 | func (s *service) initPlatform() error { 883 | if s.platform != nil { 884 | return nil 885 | } 886 | p, err := wasmtime.NewPlatform() 887 | if err != nil { 888 | return err 889 | } 890 | s.platform = p 891 | return nil 892 | } 893 | -------------------------------------------------------------------------------- /wasmtime/container.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | /* 5 | Copyright The containerd Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | package wasmtime 21 | 22 | import ( 23 | "context" 24 | "encoding/json" 25 | "fmt" 26 | "io" 27 | "io/ioutil" 28 | "net/url" 29 | "os" 30 | "os/exec" 31 | "path/filepath" 32 | "sync" 33 | "syscall" 34 | "time" 35 | 36 | "github.com/containerd/cgroups/v3/cgroup1" 37 | "github.com/containerd/console" 38 | taskAPI "github.com/containerd/containerd/api/runtime/task/v3" 39 | "github.com/containerd/containerd/v2/core/mount" 40 | "github.com/containerd/containerd/v2/pkg/namespaces" 41 | "github.com/containerd/containerd/v2/pkg/stdio" 42 | "github.com/containerd/errdefs" 43 | "github.com/opencontainers/runtime-spec/specs-go" 44 | "github.com/pkg/errors" 45 | "github.com/sirupsen/logrus" 46 | ) 47 | 48 | type Exit struct { 49 | Pid int 50 | Status int 51 | } 52 | 53 | // NewContainer returns a new runc container 54 | func NewContainer(ctx context.Context, platform stdio.Platform, r *taskAPI.CreateTaskRequest, ec chan<- Exit) (c *Container, err error) { 55 | //ns, err := namespaces.NamespaceRequired(ctx) 56 | //if err != nil { 57 | // return nil, errors.Wrap(err, "create namespace") 58 | //} 59 | 60 | //var opts options.Options 61 | //if r.Options != nil { 62 | // v, err := typeurl.UnmarshalAny(r.Options) 63 | // if err != nil { 64 | // return nil, err 65 | // } 66 | // // TODO: Use custom options type 67 | // opts = *v.(*options.Options) 68 | //} 69 | 70 | //if err := WriteRuntime(r.Bundle, opts.BinaryName); err != nil { 71 | // return nil, err 72 | //} 73 | 74 | b, err := ioutil.ReadFile(filepath.Join(r.Bundle, "config.json")) 75 | if err != nil { 76 | return nil, errors.Wrap(err, "failed to read spec") 77 | } 78 | 79 | var spec specs.Spec 80 | if err := json.Unmarshal(b, &spec); err != nil { 81 | return nil, errors.Wrap(err, "failed to unmarshal spec") 82 | } 83 | 84 | if spec.Process == nil { 85 | return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "no process specification") 86 | } 87 | 88 | var rootRemap string 89 | 90 | rootfs := "" 91 | for _, m := range r.Rootfs { 92 | if m.Type == "bind" { 93 | continue 94 | } 95 | if rootfs == "" { 96 | rootfs = filepath.Join(r.Bundle, "rootfs") 97 | if err := os.MkdirAll(rootfs, 0711); err != nil { 98 | return nil, err 99 | } 100 | } 101 | } 102 | 103 | defer func() { 104 | if err != nil && rootfs != "" { 105 | if err2 := mount.UnmountAll(rootfs, 0); err2 != nil { 106 | logrus.WithError(err2).Warn("failed to cleanup rootfs mount") 107 | } 108 | } 109 | }() 110 | 111 | if rootfs != "" { 112 | for _, rm := range r.Rootfs { 113 | m := &mount.Mount{ 114 | Type: rm.Type, 115 | Source: rm.Source, 116 | Options: rm.Options, 117 | } 118 | if err := m.Mount(rootfs); err != nil { 119 | return nil, errors.Wrapf(err, "failed to mount rootfs component %v", m) 120 | } 121 | } 122 | } else if len(r.Rootfs) > 0 { 123 | rootfs = r.Rootfs[0].Source 124 | } else if spec.Root != nil && spec.Root.Path != "" { 125 | rootfs = spec.Root.Path 126 | } else { 127 | return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "no root provided") 128 | } 129 | rootRemap = fmt.Sprintf("%s::/", rootfs) // --dir (wasmtime v27) 130 | if len(spec.Process.Args) > 0 { 131 | // TODO: bound this 132 | spec.Process.Args[0] = filepath.Join(rootfs, spec.Process.Args[0]) 133 | } 134 | 135 | p := &Process{ 136 | id: r.ID, 137 | stdio: stdio.Stdio{ 138 | Stdin: r.Stdin, 139 | Stdout: r.Stdout, 140 | Stderr: r.Stderr, 141 | Terminal: r.Terminal, 142 | }, 143 | remaps: []string{ 144 | rootRemap, 145 | }, 146 | exited: make(chan struct{}), 147 | ec: ec, 148 | env: spec.Process.Env, 149 | args: spec.Process.Args, 150 | } 151 | 152 | container := &Container{ 153 | ID: r.ID, 154 | Bundle: r.Bundle, 155 | process: p, 156 | processes: make(map[string]*Process), 157 | } 158 | 159 | pid := p.Pid() 160 | if pid > 0 { 161 | cg, err := cgroup1.Load(cgroup1.PidPath(pid)) 162 | if err != nil { 163 | logrus.WithError(err).Errorf("loading cgroup for %d", pid) 164 | container.cgroup = cg 165 | } 166 | } 167 | logrus.Infof("process created: %#v", p) 168 | 169 | return container, nil 170 | } 171 | 172 | //// ReadRuntime reads the runtime information from the path 173 | //func ReadRuntime(path string) (string, error) { 174 | // data, err := ioutil.ReadFile(filepath.Join(path, "runtime")) 175 | // if err != nil { 176 | // return "", err 177 | // } 178 | // return string(data), nil 179 | //} 180 | // 181 | //// WriteRuntime writes the runtime information into the path 182 | //func WriteRuntime(path, runtime string) error { 183 | // return ioutil.WriteFile(filepath.Join(path, "runtime"), []byte(runtime), 0600) 184 | //} 185 | 186 | // Container for operating on a runc container and its processes 187 | type Container struct { 188 | mu sync.Mutex 189 | 190 | // ID of the container 191 | ID string 192 | // Bundle path 193 | Bundle string 194 | // Root Remap 195 | RootRemap string 196 | 197 | ec chan<- Exit 198 | cgroup cgroup1.Cgroup 199 | process *Process 200 | processes map[string]*Process 201 | } 202 | 203 | // All processes in the container 204 | func (c *Container) All() (o []*Process) { 205 | c.mu.Lock() 206 | defer c.mu.Unlock() 207 | 208 | for _, p := range c.processes { 209 | o = append(o, p) 210 | } 211 | if c.process != nil { 212 | o = append(o, c.process) 213 | } 214 | return o 215 | } 216 | 217 | // ExecdProcesses added to the container 218 | func (c *Container) ExecdProcesses() (o []*Process) { 219 | c.mu.Lock() 220 | defer c.mu.Unlock() 221 | for _, p := range c.processes { 222 | o = append(o, p) 223 | } 224 | return o 225 | } 226 | 227 | // Pid of the main process of a container 228 | func (c *Container) Pid() int { 229 | c.mu.Lock() 230 | defer c.mu.Unlock() 231 | return c.process.Pid() 232 | } 233 | 234 | // Cgroup of the container 235 | func (c *Container) Cgroup() cgroup1.Cgroup { 236 | c.mu.Lock() 237 | defer c.mu.Unlock() 238 | return c.cgroup 239 | } 240 | 241 | // CgroupSet sets the cgroup to the container 242 | func (c *Container) CgroupSet(cg cgroup1.Cgroup) { 243 | c.mu.Lock() 244 | c.cgroup = cg 245 | c.mu.Unlock() 246 | } 247 | 248 | // Process returns the process by id 249 | func (c *Container) Process(id string) (*Process, error) { 250 | c.mu.Lock() 251 | defer c.mu.Unlock() 252 | if id == "" { 253 | if c.process == nil { 254 | return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "container must be created") 255 | } 256 | return c.process, nil 257 | } 258 | p, ok := c.processes[id] 259 | if !ok { 260 | return nil, errors.Wrapf(errdefs.ErrNotFound, "process does not exist %s", id) 261 | } 262 | return p, nil 263 | } 264 | 265 | // ProcessExists returns true if the process by id exists 266 | func (c *Container) ProcessExists(id string) bool { 267 | c.mu.Lock() 268 | defer c.mu.Unlock() 269 | _, ok := c.processes[id] 270 | return ok 271 | } 272 | 273 | // ProcessAdd adds a new process to the container 274 | func (c *Container) ProcessAdd(process *Process) { 275 | c.mu.Lock() 276 | defer c.mu.Unlock() 277 | c.processes[process.ID()] = process 278 | } 279 | 280 | // ProcessRemove removes the process by id from the container 281 | func (c *Container) ProcessRemove(id string) { 282 | c.mu.Lock() 283 | defer c.mu.Unlock() 284 | delete(c.processes, id) 285 | } 286 | 287 | // Start a container process 288 | func (c *Container) Start(ctx context.Context, r *taskAPI.StartRequest) (*Process, error) { 289 | logrus.Info("starting") 290 | p, err := c.Process(r.ExecID) 291 | if err != nil { 292 | return nil, err 293 | } 294 | logrus.Info("got process %#v", p) 295 | if err := p.Start(ctx); err != nil { 296 | return nil, err 297 | } 298 | 299 | logrus.Info("done starting", p) 300 | if c.Cgroup() == nil && p.Pid() > 0 { 301 | cg, err := cgroup1.Load(cgroup1.PidPath(p.Pid())) 302 | if err != nil { 303 | logrus.WithError(err).Errorf("loading cgroup for %d", p.Pid()) 304 | } 305 | c.cgroup = cg 306 | } 307 | logrus.Info("returning process", p) 308 | return p, nil 309 | } 310 | 311 | // Delete the container or a process by id 312 | func (c *Container) Delete(ctx context.Context, r *taskAPI.DeleteRequest) (*Process, error) { 313 | p, err := c.Process(r.ExecID) 314 | if err != nil { 315 | return nil, err 316 | } 317 | if err := p.Delete(ctx); err != nil { 318 | return nil, err 319 | } 320 | if r.ExecID != "" { 321 | c.ProcessRemove(r.ExecID) 322 | } 323 | return p, nil 324 | } 325 | 326 | // Exec an additional process 327 | func (c *Container) Exec(ctx context.Context, r *taskAPI.ExecProcessRequest) (*Process, error) { 328 | return nil, errors.Wrap(errdefs.ErrNotImplemented, "exec not implemented") 329 | } 330 | 331 | // Pause the container 332 | func (c *Container) Pause(ctx context.Context) error { 333 | return errors.Wrap(errdefs.ErrNotImplemented, "pause not implemented") 334 | } 335 | 336 | // Resume the container 337 | func (c *Container) Resume(ctx context.Context) error { 338 | return errors.Wrap(errdefs.ErrNotImplemented, "resume not implemented") 339 | } 340 | 341 | // ResizePty of a process 342 | func (c *Container) ResizePty(ctx context.Context, r *taskAPI.ResizePtyRequest) error { 343 | p, err := c.Process(r.ExecID) 344 | if err != nil { 345 | return err 346 | } 347 | ws := console.WinSize{ 348 | Width: uint16(r.Width), 349 | Height: uint16(r.Height), 350 | } 351 | return p.Resize(ws) 352 | } 353 | 354 | // Kill a process 355 | func (c *Container) Kill(ctx context.Context, r *taskAPI.KillRequest) error { 356 | p, err := c.Process(r.ExecID) 357 | if err != nil { 358 | return err 359 | } 360 | return p.Kill(ctx, r.Signal, r.All) 361 | } 362 | 363 | // CloseIO of a process 364 | func (c *Container) CloseIO(ctx context.Context, r *taskAPI.CloseIORequest) error { 365 | p, err := c.Process(r.ExecID) 366 | if err != nil { 367 | return err 368 | } 369 | if stdin := p.Stdin(); stdin != nil { 370 | if err := stdin.Close(); err != nil { 371 | return errors.Wrap(err, "close stdin") 372 | } 373 | } 374 | return nil 375 | } 376 | 377 | // Checkpoint the container 378 | func (c *Container) Checkpoint(ctx context.Context, r *taskAPI.CheckpointTaskRequest) error { 379 | return errors.Wrap(errdefs.ErrNotImplemented, "checkpoint not implemented") 380 | } 381 | 382 | // Update the resource information of a running container 383 | func (c *Container) Update(ctx context.Context, r *taskAPI.UpdateTaskRequest) error { 384 | return errors.Wrap(errdefs.ErrNotImplemented, "update not implemented") 385 | } 386 | 387 | // HasPid returns true if the container owns a specific pid 388 | func (c *Container) HasPid(pid int) bool { 389 | if c.Pid() == pid { 390 | return true 391 | } 392 | for _, p := range c.All() { 393 | if p.Pid() == pid { 394 | return true 395 | } 396 | } 397 | return false 398 | } 399 | 400 | type Process struct { 401 | mu sync.Mutex 402 | 403 | id string 404 | pid int 405 | exitStatus int 406 | exitTime time.Time 407 | stdio stdio.Stdio 408 | stdin io.Closer 409 | process *os.Process 410 | exited chan struct{} 411 | ec chan<- Exit 412 | 413 | remaps []string 414 | env []string 415 | args []string 416 | 417 | waitError error 418 | } 419 | 420 | func (p *Process) ID() string { 421 | return p.id 422 | } 423 | 424 | func (p *Process) Pid() int { 425 | return p.pid 426 | } 427 | 428 | func (p *Process) ExitStatus() int { 429 | p.mu.Lock() 430 | defer p.mu.Unlock() 431 | return p.exitStatus 432 | } 433 | 434 | func (p *Process) ExitedAt() time.Time { 435 | p.mu.Lock() 436 | defer p.mu.Unlock() 437 | return p.exitTime 438 | } 439 | 440 | func (p *Process) Stdin() io.Closer { 441 | p.mu.Lock() 442 | defer p.mu.Unlock() 443 | return p.stdin 444 | } 445 | 446 | func (p *Process) Stdio() stdio.Stdio { 447 | return p.stdio 448 | } 449 | 450 | func (p *Process) Status(context.Context) (string, error) { 451 | select { 452 | case <-p.exited: 453 | default: 454 | p.mu.Lock() 455 | running := p.process != nil 456 | p.mu.Unlock() 457 | if running { 458 | return "running", nil 459 | } 460 | return "created", nil 461 | } 462 | 463 | return "stopped", nil 464 | } 465 | 466 | func (p *Process) Wait() { 467 | <-p.exited 468 | } 469 | 470 | func (p *Process) Resize(ws console.WinSize) error { 471 | return nil 472 | } 473 | 474 | func (p *Process) Start(ctx context.Context) (err error) { 475 | var args []string 476 | for _, rm := range p.remaps { 477 | args = append(args, "--dir="+rm) 478 | } 479 | for _, env := range p.env { 480 | args = append(args, "--env="+env) 481 | } 482 | args = append(args, p.args...) 483 | cmd := exec.Command("wasmtime", args...) 484 | 485 | var in io.Closer 486 | var closers []io.Closer 487 | if p.stdio.Stdin != "" { 488 | stdin, err := os.OpenFile(p.stdio.Stdin, os.O_RDONLY, 0) 489 | if err != nil { 490 | return errors.Wrapf(err, "unable to open stdin: %s", p.stdio.Stdin) 491 | } 492 | defer func() { 493 | if err != nil { 494 | stdin.Close() 495 | } 496 | }() 497 | cmd.Stdin = stdin 498 | in = stdin 499 | closers = append(closers, stdin) 500 | } 501 | 502 | u, err := url.Parse(p.stdio.Stdout) 503 | if err != nil { 504 | return fmt.Errorf("unable to parse stdout uri: %w", err) 505 | } 506 | if u.Scheme == "" { 507 | u.Scheme = "fifo" 508 | } 509 | switch u.Scheme { 510 | case "fifo": 511 | if p.stdio.Stdout != "" { 512 | ou, err := url.Parse(p.stdio.Stdout) 513 | if err != nil { 514 | return fmt.Errorf("fifo: unable to parse stdout uri: %w", err) 515 | } 516 | stdout, err := os.OpenFile(ou.Path, os.O_WRONLY, 0) 517 | if err != nil { 518 | return errors.Wrapf(err, "unable to open stdout: %s", p.stdio.Stdout) 519 | } 520 | defer func() { 521 | if err != nil { 522 | stdout.Close() 523 | } 524 | }() 525 | cmd.Stdout = stdout 526 | closers = append(closers, stdout) 527 | } 528 | 529 | if p.stdio.Stderr != "" { 530 | eu, err := url.Parse(p.stdio.Stderr) 531 | if err != nil { 532 | return fmt.Errorf("fifo: unable to parse stderr uri: %w", err) 533 | } 534 | stderr, err := os.OpenFile(eu.Path, os.O_WRONLY, 0) 535 | if err != nil { 536 | return errors.Wrapf(err, "unable to open stderr: %s", p.stdio.Stderr) 537 | } 538 | defer func() { 539 | if err != nil { 540 | stderr.Close() 541 | } 542 | }() 543 | cmd.Stderr = stderr 544 | closers = append(closers, stderr) 545 | } 546 | case "binary": 547 | // bianary scheme is implemented based on https://github.com/containerd/containerd/blob/v2.0.0/cmd/containerd-shim-runc-v2/process/io.go#L247 548 | ns, err := namespaces.NamespaceRequired(ctx) 549 | if err != nil { 550 | return err 551 | } 552 | 553 | out, err := newPipe() 554 | if err != nil { 555 | return fmt.Errorf("failed to create stdout pipes: %w", err) 556 | } 557 | cmd.Stdout = out.w 558 | closers = append(closers, out) 559 | 560 | serr, err := newPipe() 561 | if err != nil { 562 | return fmt.Errorf("failed to create stderr pipes: %w", err) 563 | } 564 | cmd.Stderr = serr.w 565 | closers = append(closers, serr) 566 | 567 | r, w, err := os.Pipe() 568 | if err != nil { 569 | return err 570 | } 571 | closers = append(closers, r, w) 572 | 573 | cmd := NewBinaryCmd(u, p.id, ns) 574 | cmd.ExtraFiles = append(cmd.ExtraFiles, out.r, serr.r, w) 575 | // don't need to register this with the reaper or wait when 576 | // running inside a shim 577 | if err := cmd.Start(); err != nil { 578 | return fmt.Errorf("failed to start binary process: %w", err) 579 | } 580 | closers = append(closers, &closerFunc{f: cmd.Process.Kill}) 581 | 582 | // close our side of the pipe after start 583 | if err := w.Close(); err != nil { 584 | return fmt.Errorf("failed to close write pipe after start: %w", err) 585 | } 586 | 587 | // wait for the logging binary to be ready 588 | b := make([]byte, 1) 589 | if _, err := r.Read(b); err != nil && err != io.EOF { 590 | return fmt.Errorf("failed to read from logging binary: %w", err) 591 | } 592 | } 593 | 594 | p.mu.Lock() 595 | if p.process != nil { 596 | return errors.Wrap(errdefs.ErrFailedPrecondition, "already running") 597 | } 598 | if err := cmd.Start(); err != nil { 599 | p.mu.Unlock() 600 | return err 601 | } 602 | p.process = cmd.Process 603 | p.stdin = in 604 | p.mu.Unlock() 605 | 606 | go func() { 607 | waitStatus, err := p.process.Wait() 608 | p.mu.Lock() 609 | p.exitTime = time.Now() 610 | if err != nil { 611 | p.exitStatus = -1 612 | logrus.WithError(err).Errorf("wait returned error") 613 | } else if waitStatus != nil { 614 | // TODO: Make this cross platform 615 | p.exitStatus = int(waitStatus.Sys().(syscall.WaitStatus)) 616 | } 617 | p.mu.Unlock() 618 | 619 | close(p.exited) 620 | 621 | p.ec <- Exit{ 622 | Pid: p.pid, 623 | Status: p.exitStatus, 624 | } 625 | 626 | for _, c := range closers { 627 | c.Close() 628 | } 629 | }() 630 | 631 | return nil 632 | } 633 | 634 | func (p *Process) Delete(context.Context) error { 635 | return nil 636 | } 637 | 638 | func (p *Process) Kill(context.Context, uint32, bool) error { 639 | p.mu.Lock() 640 | running := p.process != nil 641 | p.mu.Unlock() 642 | 643 | if !running { 644 | return errors.New("not started") 645 | } 646 | 647 | return p.process.Kill() 648 | } 649 | 650 | func (p *Process) SetExited(status int) { 651 | p.mu.Lock() 652 | defer p.mu.Unlock() 653 | p.exitStatus = status 654 | } 655 | 656 | func newPipe() (*pipe, error) { 657 | r, w, err := os.Pipe() 658 | if err != nil { 659 | return nil, err 660 | } 661 | return &pipe{ 662 | r: r, 663 | w: w, 664 | }, nil 665 | } 666 | 667 | type pipe struct { 668 | r *os.File 669 | w *os.File 670 | } 671 | 672 | func (p *pipe) Close() error { 673 | var result []error 674 | 675 | if err := p.w.Close(); err != nil { 676 | result = append(result, fmt.Errorf("pipe: failed to close write pipe: %w", err)) 677 | } 678 | 679 | if err := p.r.Close(); err != nil { 680 | result = append(result, fmt.Errorf("pipe: failed to close read pipe: %w", err)) 681 | } 682 | 683 | if len(result) > 0 { 684 | retErr := fmt.Errorf("error during closing pipe") 685 | for _, e := range result { 686 | retErr = fmt.Errorf("%w, %w", retErr, e) 687 | } 688 | return retErr 689 | } 690 | 691 | return nil 692 | } 693 | 694 | type closerFunc struct { 695 | f func() error 696 | } 697 | 698 | func (c *closerFunc) Close() error { 699 | return c.f() 700 | } 701 | 702 | // NewBinaryCmd returns a Cmd to be used to start a logging binary. 703 | // The Cmd is generated from the provided uri, and the container ID and 704 | // namespace are appended to the Cmd environment. 705 | // ported from https://github.com/containerd/containerd/blob/v2.0.0/cmd/containerd-shim-runc-v2/process/io_util.go#L28 706 | func NewBinaryCmd(binaryURI *url.URL, id, ns string) *exec.Cmd { 707 | var args []string 708 | for k, vs := range binaryURI.Query() { 709 | args = append(args, k) 710 | if len(vs) > 0 { 711 | args = append(args, vs[0]) 712 | } 713 | } 714 | 715 | cmd := exec.Command(binaryURI.Path, args...) 716 | 717 | cmd.Env = append(cmd.Env, 718 | "CONTAINER_ID="+id, 719 | "CONTAINER_NAMESPACE="+ns, 720 | ) 721 | 722 | return cmd 723 | } 724 | -------------------------------------------------------------------------------- /wasmtime/platform.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | /* 4 | Copyright The containerd Authors. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package wasmtime 20 | 21 | import ( 22 | "context" 23 | "errors" 24 | "fmt" 25 | "io" 26 | "net/url" 27 | "sync" 28 | "syscall" 29 | 30 | "github.com/containerd/console" 31 | "github.com/containerd/containerd/v2/pkg/stdio" 32 | "github.com/containerd/fifo" 33 | ) 34 | 35 | var bufPool = sync.Pool{ 36 | New: func() interface{} { 37 | // setting to 4096 to align with PIPE_BUF 38 | // http://man7.org/linux/man-pages/man7/pipe.7.html 39 | buffer := make([]byte, 4096) 40 | return &buffer 41 | }, 42 | } 43 | 44 | // NewPlatform returns a linux platform for use with I/O operations 45 | func NewPlatform() (stdio.Platform, error) { 46 | epoller, err := console.NewEpoller() 47 | if err != nil { 48 | return nil, fmt.Errorf("failed to initialize epoller: %w", err) 49 | } 50 | go epoller.Wait() 51 | return &linuxPlatform{ 52 | epoller: epoller, 53 | }, nil 54 | } 55 | 56 | type linuxPlatform struct { 57 | epoller *console.Epoller 58 | } 59 | 60 | func (p *linuxPlatform) CopyConsole(ctx context.Context, console console.Console, id, stdin, stdout, stderr string, wg *sync.WaitGroup) (cons console.Console, retErr error) { 61 | if p.epoller == nil { 62 | return nil, errors.New("uninitialized epoller") 63 | } 64 | 65 | epollConsole, err := p.epoller.Add(console) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | var cwg sync.WaitGroup 71 | if stdin != "" { 72 | in, err := fifo.OpenFifo(context.Background(), stdin, syscall.O_RDONLY|syscall.O_NONBLOCK, 0) 73 | if err != nil { 74 | return nil, err 75 | } 76 | cwg.Add(1) 77 | go func() { 78 | cwg.Done() 79 | bp := bufPool.Get().(*[]byte) 80 | defer bufPool.Put(bp) 81 | io.CopyBuffer(epollConsole, in, *bp) 82 | // we need to shutdown epollConsole when pipe broken 83 | epollConsole.Shutdown(p.epoller.CloseConsole) 84 | epollConsole.Close() 85 | in.Close() 86 | }() 87 | } 88 | 89 | uri, err := url.Parse(stdout) 90 | if err != nil { 91 | return nil, fmt.Errorf("unable to parse stdout uri: %w", err) 92 | } 93 | 94 | switch uri.Scheme { 95 | case "binary": 96 | return nil, fmt.Errorf("binary scheme is unsupported") 97 | default: 98 | outw, err := fifo.OpenFifo(ctx, stdout, syscall.O_WRONLY, 0) 99 | if err != nil { 100 | return nil, err 101 | } 102 | outr, err := fifo.OpenFifo(ctx, stdout, syscall.O_RDONLY, 0) 103 | if err != nil { 104 | return nil, err 105 | } 106 | wg.Add(1) 107 | cwg.Add(1) 108 | go func() { 109 | cwg.Done() 110 | buf := bufPool.Get().(*[]byte) 111 | defer bufPool.Put(buf) 112 | io.CopyBuffer(outw, epollConsole, *buf) 113 | 114 | outw.Close() 115 | outr.Close() 116 | wg.Done() 117 | }() 118 | cwg.Wait() 119 | } 120 | 121 | return epollConsole, nil 122 | } 123 | 124 | func (p *linuxPlatform) ShutdownConsole(ctx context.Context, cons console.Console) error { 125 | if p.epoller == nil { 126 | return errors.New("uninitialized epoller") 127 | } 128 | epollConsole, ok := cons.(*console.EpollConsole) 129 | if !ok { 130 | return fmt.Errorf("expected EpollConsole, got %#v", cons) 131 | } 132 | return epollConsole.Shutdown(p.epoller.CloseConsole) 133 | } 134 | 135 | func (p *linuxPlatform) Close() error { 136 | return p.epoller.Close() 137 | } 138 | -------------------------------------------------------------------------------- /wasmtime/util.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | /* 5 | Copyright The containerd Authors. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | package wasmtime 21 | 22 | import ( 23 | "github.com/containerd/containerd/api/events" 24 | "github.com/containerd/containerd/v2/core/runtime" 25 | "github.com/sirupsen/logrus" 26 | ) 27 | 28 | // GetTopic converts an event from an interface type to the specific 29 | // event topic id 30 | func GetTopic(e interface{}) string { 31 | switch e.(type) { 32 | case *events.TaskCreate: 33 | return runtime.TaskCreateEventTopic 34 | case *events.TaskStart: 35 | return runtime.TaskStartEventTopic 36 | case *events.TaskOOM: 37 | return runtime.TaskOOMEventTopic 38 | case *events.TaskExit: 39 | return runtime.TaskExitEventTopic 40 | case *events.TaskDelete: 41 | return runtime.TaskDeleteEventTopic 42 | case *events.TaskExecAdded: 43 | return runtime.TaskExecAddedEventTopic 44 | case *events.TaskExecStarted: 45 | return runtime.TaskExecStartedEventTopic 46 | case *events.TaskPaused: 47 | return runtime.TaskPausedEventTopic 48 | case *events.TaskResumed: 49 | return runtime.TaskResumedEventTopic 50 | case *events.TaskCheckpointed: 51 | return runtime.TaskCheckpointedEventTopic 52 | default: 53 | logrus.Warnf("no topic for type %#v", e) 54 | } 55 | return runtime.TaskUnknownTopic 56 | } 57 | --------------------------------------------------------------------------------