Skip to main content

aws_lite_rs/api/
efs.rs

1//! AWS Elastic File System (EFS) API client.
2//!
3//! Thin wrapper over generated ops.
4//!
5//! Needed by AWS CIS benchmark checks:
6//!   - CIS 3.3.1 (aws_efs_encryption): verify that all EFS file systems have
7//!     encryption at rest enabled (`Encrypted: true`).
8
9use crate::{
10    AwsHttpClient, Result,
11    ops::efs::EfsOps,
12    types::efs::{DescribeFileSystemsResponse, FileSystemDescription},
13};
14
15/// Client for the Amazon Elastic File System API.
16pub struct EfsClient<'a> {
17    ops: EfsOps<'a>,
18}
19
20impl<'a> EfsClient<'a> {
21    /// Create a new EFS client.
22    pub(crate) fn new(client: &'a AwsHttpClient) -> Self {
23        Self {
24            ops: EfsOps::new(client),
25        }
26    }
27
28    // ── File Systems ───────────────────────────────────────────────────
29
30    /// Return the first page of EFS file system descriptions.
31    ///
32    /// Pass `marker` from a previous response to paginate.
33    pub async fn describe_file_systems(
34        &self,
35        marker: Option<&str>,
36    ) -> Result<DescribeFileSystemsResponse> {
37        self.ops
38            .describe_file_systems("", marker.unwrap_or(""), "", "")
39            .await
40    }
41
42    /// Return all EFS file systems in the current region, following pagination.
43    ///
44    /// CIS 3.3.1: every file system should have `Encrypted == true`.
45    pub async fn list_all_file_systems(&self) -> Result<Vec<FileSystemDescription>> {
46        let mut all = Vec::new();
47        let mut marker: Option<String> = None;
48
49        loop {
50            let resp = self
51                .ops
52                .describe_file_systems("", marker.as_deref().unwrap_or(""), "", "")
53                .await?;
54            all.extend(resp.file_systems);
55            match resp.next_marker {
56                Some(m) if !m.is_empty() => marker = Some(m),
57                _ => break,
58            }
59        }
60
61        Ok(all)
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use serde_json::json;
68
69    #[tokio::test]
70    async fn test_describe_file_systems_returns_list() {
71        let mut mock = crate::MockClient::new();
72        mock.expect_get("/2015-02-01/file-systems")
73            .returning_json(json!({
74                "FileSystems": [
75                    {
76                        "OwnerId": "123456789012",
77                        "FileSystemId": "fs-0123456789abcdef0",
78                        "FileSystemArn": "arn:aws:elasticfilesystem:us-east-1:123456789012:file-system/fs-0123456789abcdef0",
79                        "CreationTime": "2024-01-15T10:00:00Z",
80                        "LifeCycleState": "available",
81                        "Name": "my-efs",
82                        "NumberOfMountTargets": 2,
83                        "PerformanceMode": "generalPurpose",
84                        "Encrypted": true,
85                        "KmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/mrk-abc123",
86                        "ThroughputMode": "bursting",
87                        "Tags": [{"Key": "Name", "Value": "my-efs"}]
88                    }
89                ]
90            }))
91            .times(1);
92
93        let client = crate::AwsHttpClient::from_mock(mock);
94        let efs = client.efs();
95        let resp = efs.describe_file_systems(None).await.unwrap();
96        assert_eq!(resp.file_systems.len(), 1);
97        let fs = &resp.file_systems[0];
98        assert_eq!(fs.file_system_id, "fs-0123456789abcdef0");
99        assert_eq!(fs.encrypted, Some(true));
100        assert_eq!(fs.life_cycle_state, "available");
101        assert_eq!(fs.tags.len(), 1);
102        assert_eq!(fs.tags[0].key, "Name");
103    }
104
105    #[tokio::test]
106    async fn test_list_all_file_systems_paginates() {
107        let mut mock = crate::MockClient::new();
108        // Both calls start with "/2015-02-01/file-systems" — use a sequence so the
109        // second call (with Marker=page2) gets the second response.
110        mock.expect_get("/2015-02-01/file-systems")
111            .returning_json_sequence(vec![
112                json!({
113                    "FileSystems": [{
114                        "OwnerId": "123456789012",
115                        "FileSystemId": "fs-aaa",
116                        "CreationTime": "2024-01-15T10:00:00Z",
117                        "LifeCycleState": "available",
118                        "NumberOfMountTargets": 0,
119                        "PerformanceMode": "generalPurpose",
120                        "Encrypted": false,
121                        "Tags": []
122                    }],
123                    "NextMarker": "page2"
124                }),
125                json!({
126                    "FileSystems": [{
127                        "OwnerId": "123456789012",
128                        "FileSystemId": "fs-bbb",
129                        "CreationTime": "2024-01-15T11:00:00Z",
130                        "LifeCycleState": "available",
131                        "NumberOfMountTargets": 1,
132                        "PerformanceMode": "generalPurpose",
133                        "Encrypted": true,
134                        "Tags": []
135                    }]
136                }),
137            ])
138            .times(2);
139
140        let client = crate::AwsHttpClient::from_mock(mock);
141        let all = client.efs().list_all_file_systems().await.unwrap();
142        assert_eq!(all.len(), 2);
143        assert_eq!(all[0].file_system_id, "fs-aaa");
144        assert_eq!(all[0].encrypted, Some(false));
145        assert_eq!(all[1].file_system_id, "fs-bbb");
146        assert_eq!(all[1].encrypted, Some(true));
147    }
148
149    #[tokio::test]
150    async fn test_describe_file_systems_empty() {
151        let mut mock = crate::MockClient::new();
152        mock.expect_get("/2015-02-01/file-systems")
153            .returning_json(json!({"FileSystems": []}))
154            .times(1);
155
156        let client = crate::AwsHttpClient::from_mock(mock);
157        let resp = client.efs().describe_file_systems(None).await.unwrap();
158        assert!(resp.file_systems.is_empty());
159    }
160}