Skip to main content

containerd_client/
lib.rs

1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17#![cfg_attr(feature = "docs", doc = include_str!("../README.md"))]
18// No way to derive Eq with tonic :(
19// See https://github.com/hyperium/tonic/issues/1056
20#![allow(clippy::derive_partial_eq_without_eq)]
21
22pub use tonic;
23
24/// Generated `containerd.types` types.
25pub mod types {
26    tonic::include_proto!("containerd.types");
27
28    pub mod v1 {
29        tonic::include_proto!("containerd.v1.types");
30    }
31    pub mod transfer {
32        tonic::include_proto!("containerd.types.transfer");
33    }
34}
35
36/// Generated `google.rpc` types, containerd services typically use some of these types.
37pub mod google {
38    #[allow(rustdoc::broken_intra_doc_links)]
39    pub mod rpc {
40        tonic::include_proto!("google.rpc");
41    }
42}
43
44/// Generated `containerd.services.*` services.
45pub mod services {
46    #[allow(clippy::tabs_in_doc_comments)]
47    #[allow(rustdoc::invalid_rust_codeblocks)]
48    #[allow(rustdoc::invalid_html_tags)]
49    pub mod v1 {
50        tonic::include_proto!("containerd.services.containers.v1");
51        tonic::include_proto!("containerd.services.content.v1");
52        tonic::include_proto!("containerd.services.diff.v1");
53        tonic::include_proto!("containerd.services.events.v1");
54        tonic::include_proto!("containerd.services.images.v1");
55        tonic::include_proto!("containerd.services.introspection.v1");
56        tonic::include_proto!("containerd.services.leases.v1");
57        tonic::include_proto!("containerd.services.namespaces.v1");
58        tonic::include_proto!("containerd.services.streaming.v1");
59        tonic::include_proto!("containerd.services.tasks.v1");
60        tonic::include_proto!("containerd.services.transfer.v1");
61
62        // Sandbox services (Controller and Store) don't make it clear that they are for sandboxes.
63        // Wrap these into a sub module to make the names more clear.
64        pub mod sandbox {
65            tonic::include_proto!("containerd.services.sandbox.v1");
66        }
67
68        // Snapshot's `Info` conflicts with Content's `Info`, so wrap it into a separate sub module.
69        pub mod snapshots {
70            tonic::include_proto!("containerd.services.snapshots.v1");
71        }
72
73        tonic::include_proto!("containerd.services.version.v1");
74    }
75}
76
77/// Generated event types.
78pub mod events {
79    tonic::include_proto!("containerd.events");
80}
81
82/// Connect creates a unix channel to containerd GRPC socket.
83///
84/// This helper intended to be used in conjunction with [Tokio](https://tokio.rs) runtime.
85#[cfg(feature = "connect")]
86pub async fn connect(
87    path: impl AsRef<std::path::Path>,
88) -> Result<tonic::transport::Channel, tonic::transport::Error> {
89    use tonic::transport::Endpoint;
90
91    let path = path.as_ref().to_path_buf();
92
93    // Taken from https://github.com/hyperium/tonic/blob/71fca362d7ffbb230547f23b3f2fb75c414063a8/examples/src/uds/client.rs#L21-L28
94    // There will ignore this uri because uds do not use it
95    // and make connection with UnixStream::connect.
96    let channel = Endpoint::try_from("http://[::]")?
97        .connect_with_connector(tower::service_fn(move |_| {
98            let path = path.clone();
99
100            async move {
101                #[cfg(unix)]
102                {
103                    Ok::<_, std::io::Error>(hyper_util::rt::TokioIo::new(
104                        tokio::net::UnixStream::connect(path).await?,
105                    ))
106                }
107
108                #[cfg(windows)]
109                {
110                    let client = tokio::net::windows::named_pipe::ClientOptions::new()
111                        .open(&path)
112                        .map_err(|e| std::io::Error::from(e))?;
113
114                    Ok::<_, std::io::Error>(hyper_util::rt::TokioIo::new(client))
115                }
116            }
117        }))
118        .await?;
119
120    Ok(channel)
121}
122
123use prost::{Message, Name};
124use prost_types::Any;
125
126// to_any provides a helper to match the current use of the protobuf "fullname" trait
127// in the Go code on the gRPC server side in containerd when handling matching of Any
128// types to registered types on the server. Further discussion on future direction
129// of typeurl in this issue: https://github.com/containerd/rust-extensions/issues/362
130pub fn to_any<T: Message + Name>(m: &T) -> Any {
131    let mut anyt = Any::from_msg(m).unwrap();
132    anyt.type_url = T::full_name();
133    anyt
134}
135
136/// Help to inject namespace into request.
137///
138/// To use this macro, the `tonic::Request` is needed.
139#[macro_export]
140macro_rules! with_namespace {
141    ($req:expr, $ns:expr) => {{
142        let mut req = Request::new($req);
143        let md = req.metadata_mut();
144        // https://github.com/containerd/containerd/blob/main/pkg/namespaces/grpc.go#L27
145        md.insert("containerd-namespace", $ns.parse().unwrap());
146        req
147    }};
148}
149
150use services::v1::{
151    containers_client::ContainersClient,
152    content_client::ContentClient,
153    diff_client::DiffClient,
154    events_client::EventsClient,
155    images_client::ImagesClient,
156    introspection_client::IntrospectionClient,
157    leases_client::LeasesClient,
158    namespaces_client::NamespacesClient,
159    sandbox::{controller_client::ControllerClient, store_client::StoreClient},
160    snapshots::snapshots_client::SnapshotsClient,
161    streaming_client::StreamingClient,
162    tasks_client::TasksClient,
163    transfer_client::TransferClient,
164    version_client::VersionClient,
165};
166use tonic::transport::{Channel, Error};
167
168/// Client to containerd's APIs.
169pub struct Client {
170    channel: Channel,
171}
172
173impl From<Channel> for Client {
174    fn from(value: Channel) -> Self {
175        Self { channel: value }
176    }
177}
178
179impl Client {
180    /// Create a new client from UDS socket.
181    #[cfg(feature = "connect")]
182    pub async fn from_path(path: impl AsRef<std::path::Path>) -> Result<Self, Error> {
183        let channel = connect(path).await?;
184        Ok(Self { channel })
185    }
186
187    /// Access to the underlying Tonic channel.
188    #[inline]
189    pub fn channel(&self) -> Channel {
190        self.channel.clone()
191    }
192
193    /// Version service.
194    #[inline]
195    pub fn version(&self) -> VersionClient<Channel> {
196        VersionClient::new(self.channel())
197    }
198
199    /// Task service client.
200    #[inline]
201    pub fn tasks(&self) -> TasksClient<Channel> {
202        TasksClient::new(self.channel())
203    }
204
205    /// Transfer service client.
206    #[inline]
207    pub fn transfer(&self) -> TransferClient<Channel> {
208        TransferClient::new(self.channel())
209    }
210
211    /// Sandbox store client.
212    #[inline]
213    pub fn sandbox_store(&self) -> StoreClient<Channel> {
214        StoreClient::new(self.channel())
215    }
216
217    /// Streaming services client.
218    #[inline]
219    pub fn streaming(&self) -> StreamingClient<Channel> {
220        StreamingClient::new(self.channel())
221    }
222
223    /// Sandbox controller client.
224    #[inline]
225    pub fn sandbox_controller(&self) -> ControllerClient<Channel> {
226        ControllerClient::new(self.channel())
227    }
228
229    /// Snapshots service.
230    #[inline]
231    pub fn snapshots(&self) -> SnapshotsClient<Channel> {
232        SnapshotsClient::new(self.channel())
233    }
234
235    /// Namespaces service.
236    #[inline]
237    pub fn namespaces(&self) -> NamespacesClient<Channel> {
238        NamespacesClient::new(self.channel())
239    }
240
241    /// Leases service.
242    #[inline]
243    pub fn leases(&self) -> LeasesClient<Channel> {
244        LeasesClient::new(self.channel())
245    }
246
247    /// Intropection service.
248    #[inline]
249    pub fn introspection(&self) -> IntrospectionClient<Channel> {
250        IntrospectionClient::new(self.channel())
251    }
252
253    /// Image service.
254    #[inline]
255    pub fn images(&self) -> ImagesClient<Channel> {
256        ImagesClient::new(self.channel())
257    }
258
259    /// Event service.
260    #[inline]
261    pub fn events(&self) -> EventsClient<Channel> {
262        EventsClient::new(self.channel())
263    }
264
265    /// Diff service.
266    #[inline]
267    pub fn diff(&self) -> DiffClient<Channel> {
268        DiffClient::new(self.channel())
269    }
270
271    /// Content service.
272    #[inline]
273    pub fn content(&self) -> ContentClient<Channel> {
274        ContentClient::new(self.channel())
275    }
276
277    /// Container service.
278    #[inline]
279    pub fn containers(&self) -> ContainersClient<Channel> {
280        ContainersClient::new(self.channel())
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use prost_types::Any;
287
288    use crate::events::ContainerCreate;
289
290    #[test]
291    fn any_roundtrip() {
292        let original = ContainerCreate {
293            id: "test".to_string(),
294            image: "test".to_string(),
295            runtime: None,
296        };
297
298        let any = Any::from_msg(&original).expect("should not fail to encode");
299        let decoded: ContainerCreate = any.to_msg().expect("should not fail to decode");
300
301        assert_eq!(original, decoded)
302    }
303}