Skip to content

Commit 5476ff4

Browse files
committed
enclave_build: define and implement image manager trait
For future generalization of both Docker and OCI methods of build, the `ImageManager` trait will be used to extract useful information from the container image details. Currently added the OCI implementation of the trait and it can be used to pull, inspect and extract expressions from an image just like the Docker case. The same implementation will be added for Docker images. enclave_build: add unit test for image manager Added unit tests for image manager operations. Signed-off-by: Calin-Alexandru Coman <[email protected]> Signed-off-by: Raul-Ovidiu Moldovan <[email protected]>
1 parent 3dad5a0 commit 5476ff4

File tree

4 files changed

+123
-20
lines changed

4 files changed

+123
-20
lines changed

enclave_build/src/docker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use base64::{engine::general_purpose, Engine as _};
54
use crate::{EnclaveBuildError, Result};
5+
use base64::{engine::general_purpose, Engine as _};
66
use futures::stream::StreamExt;
77
use log::{debug, info};
88
use serde_json::{json, Value};

enclave_build/src/image.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use crate::{EnclaveBuildError, Result};
55
use serde::{de::DeserializeOwned, Deserialize, Serialize};
66
use std::io::Read;
7+
use tempfile::NamedTempFile;
78

89
use sha2::Digest;
910

@@ -53,6 +54,40 @@ impl ImageDetails {
5354
pub fn config(&self) -> &ImageConfiguration {
5455
&self.config
5556
}
57+
58+
/// Extracts from the image and returns the CMD and ENV expressions (in this order).
59+
///
60+
/// If there are no CMD expressions found, it tries to locate the ENTRYPOINT command.
61+
/// The returned result will contain the files needed for the YAML generator.
62+
pub fn extract_expressions(&self) -> Result<(NamedTempFile, NamedTempFile)> {
63+
// Get the expressions from the image
64+
let config_section = self
65+
.config
66+
.config()
67+
.as_ref()
68+
.ok_or(EnclaveBuildError::ConfigError)?;
69+
70+
let cmd = config_section.cmd();
71+
let env = config_section.env();
72+
let entrypoint = config_section.entrypoint();
73+
74+
// If no CMD instructions are found, try to locate an ENTRYPOINT command
75+
let (cmd, env) = match (cmd, env, entrypoint) {
76+
(Some(cmd), Some(env), _) => Ok((cmd.to_vec(), env.to_vec())),
77+
(_, Some(env), Some(entrypoint)) => Ok((entrypoint.to_vec(), env.to_vec())),
78+
(_, _, Some(entrypoint)) => Ok((entrypoint.to_vec(), Vec::new())),
79+
(_, _, _) => Err(EnclaveBuildError::ExtractError(
80+
"Failed to locate ENTRYPOINT".to_string(),
81+
)),
82+
}?;
83+
84+
let cmd_file = crate::docker::write_config(cmd)
85+
.map_err(|err| EnclaveBuildError::ExtractError(format!("{:?}", err)))?;
86+
let env_file = crate::docker::write_config(env)
87+
.map_err(|err| EnclaveBuildError::ExtractError(format!("{:?}", err)))?;
88+
89+
Ok((cmd_file, env_file))
90+
}
5691
}
5792

5893
/// URIs that are missing a domain will be converted to a reference using the Docker defaults.

enclave_build/src/image_manager.rs

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,25 @@
44

55
use std::convert::TryFrom;
66

7+
use log::warn;
78
use oci_distribution::Reference;
9+
use tempfile::NamedTempFile;
810

911
use crate::image::ImageDetails;
1012
use crate::storage::OciStorage;
1113
use crate::{EnclaveBuildError, Result};
1214

15+
/// Trait which provides an interface for handling an image (OCI or Docker)
16+
pub trait ImageManager {
17+
fn image_name(&self) -> &str;
18+
/// Inspects the image and returns its metadata in the form of a JSON Value
19+
fn inspect_image(&self) -> Result<serde_json::Value>;
20+
/// Returns the architecture of the image
21+
fn architecture(&self) -> Result<String>;
22+
/// Returns two temp files containing the CMD and ENV expressions extracted from the image
23+
fn extract_expressions(&self) -> Result<(NamedTempFile, NamedTempFile)>;
24+
}
25+
1326
pub struct OciImageManager {
1427
/// Name of the container image.
1528
image_name: String,
@@ -25,16 +38,15 @@ impl OciImageManager {
2538
let image_name = normalize_tag(image_name)?;
2639

2740
// The docker daemon is not used, so a local storage needs to be created
28-
let storage =
29-
match OciStorage::get_default_root_path().map_err(|err| eprintln!("{:?}", err)) {
30-
Ok(root_path) => {
31-
// Try to create/read the storage. If the storage could not be created, log the error
32-
OciStorage::new(&root_path)
33-
.map_err(|err| eprintln!("{:?}", err))
34-
.ok()
35-
}
36-
Err(_) => None,
37-
};
41+
let storage = match OciStorage::get_default_root_path().map_err(|err| warn!("{:?}", err)) {
42+
Ok(root_path) => {
43+
// Try to create/read the storage. If the storage could not be created, log the error
44+
OciStorage::new(&root_path)
45+
.map_err(|err| warn!("{:?}", err))
46+
.ok()
47+
}
48+
Err(_) => None,
49+
};
3850

3951
let image_details = Self::fetch_image_details(&image_name, storage).await?;
4052

@@ -60,11 +72,7 @@ impl OciImageManager {
6072

6173
let image_details = if let Some(storage) = local_storage {
6274
// Try to fetch the image from the storage
63-
storage.fetch_image_details(image_name).map_err(|err| {
64-
// Log the fetching error
65-
eprintln!("{:?}", err);
66-
err
67-
})
75+
storage.fetch_image_details(image_name)
6876
} else {
6977
Err(EnclaveBuildError::OciStorageNotFound(
7078
"Local storage missing".to_string(),
@@ -74,15 +82,16 @@ impl OciImageManager {
7482
// If the fetching failed, pull it from remote and store it
7583
match image_details {
7684
Ok(details) => Ok(details),
77-
Err(_) => {
85+
Err(err) => {
86+
warn!("Fetching from storage failed: {}", err);
7887
// The image is not stored, so try to pull and then store it
7988
let image_data = crate::pull::pull_image_data(image_name).await?;
8089

8190
// If the store operation fails, discard error and proceed with getting the details
8291
if let Some(local_storage) = storage.as_mut() {
8392
local_storage
8493
.store_image_data(image_name, &image_data)
85-
.map_err(|err| eprintln!("Failed to store image: {:?}", err))
94+
.map_err(|err| warn!("Failed to store image: {:?}", err))
8695
.ok();
8796
}
8897

@@ -105,8 +114,35 @@ fn normalize_tag(image_name: &str) -> Result<String> {
105114
}
106115
}
107116

117+
impl ImageManager for OciImageManager {
118+
fn image_name(&self) -> &str {
119+
&self.image_name
120+
}
121+
122+
/// Inspect the image and return its description as a JSON String.
123+
fn inspect_image(&self) -> Result<serde_json::Value> {
124+
// Serialize to a serde_json::Value
125+
serde_json::to_value(&self.image_details).map_err(EnclaveBuildError::SerdeError)
126+
}
127+
128+
/// Extracts the CMD and ENV expressions from the image and returns them each in a
129+
/// temporary file
130+
fn extract_expressions(&self) -> Result<(NamedTempFile, NamedTempFile)> {
131+
self.image_details.extract_expressions()
132+
}
133+
134+
/// Returns architecture information of the image.
135+
fn architecture(&self) -> Result<String> {
136+
Ok(format!("{}", self.image_details.config().architecture()))
137+
}
138+
}
139+
108140
#[cfg(test)]
109-
pub mod tests {
141+
mod tests {
142+
use super::*;
143+
use std::fs::File;
144+
use std::io::Read;
145+
110146
use sha2::Digest;
111147

112148
use super::{normalize_tag, OciImageManager};
@@ -177,4 +213,37 @@ pub mod tests {
177213

178214
assert_eq!(&config_hash, IMAGE_HASH);
179215
}
216+
217+
/// Test extracted configuration is as expected
218+
#[tokio::test]
219+
async fn test_config() {
220+
#[cfg(target_arch = "x86_64")]
221+
let image_manager = OciImageManager::new(
222+
"667861386598.dkr.ecr.us-east-1.amazonaws.com/enclaves-samples:vsock-sample-server-x86_64",
223+
).await.unwrap();
224+
#[cfg(target_arch = "aarch64")]
225+
let mut image_manager = OciImageManager::new(
226+
"667861386598.dkr.ecr.us-east-1.amazonaws.com/enclaves-samples:vsock-sample-server-aarch64",
227+
).await.unwrap();
228+
229+
let (cmd_file, env_file) = image_manager.extract_expressions().unwrap();
230+
let mut cmd_file = File::open(cmd_file.path()).unwrap();
231+
let mut env_file = File::open(env_file.path()).unwrap();
232+
233+
let mut cmd = String::new();
234+
cmd_file.read_to_string(&mut cmd).unwrap();
235+
assert_eq!(
236+
cmd,
237+
"/bin/sh\n\
238+
-c\n\
239+
./vsock-sample server --port 5005\n"
240+
);
241+
242+
let mut env = String::new();
243+
env_file.read_to_string(&mut env).unwrap();
244+
assert_eq!(
245+
env,
246+
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n"
247+
);
248+
}
180249
}

enclave_build/src/storage.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,6 @@ impl OciStorage {
277277
err
278278
))
279279
})?
280-
.into_iter()
281280
// Get only the valid directory entries that are valid files and return (name, file) pair
282281
.filter_map(|entry| match entry {
283282
Ok(dir_entry) => match File::open(dir_entry.path()) {

0 commit comments

Comments
 (0)