libcnb/
platform.rs

1use crate::Env;
2use std::fs;
3use std::io;
4use std::path::Path;
5
6/// Represents a Cloud Native Buildpack platform.
7///
8/// Most buildpacks target a generic platform and this library provides a [`crate::generic::GenericPlatform`] for that
9/// use-case. Buildpack authors usually do not need to implement this trait. See
10/// [detection](https://github.com/buildpacks/spec/blob/main/buildpack.md#detection) and
11/// [build](https://github.com/buildpacks/spec/blob/main/buildpack.md#build) in the buildpack
12/// specification for details.
13pub trait Platform
14where
15    Self: Sized,
16{
17    /// Retrieve a [`Env`] reference for convenient access to environment variables which
18    /// all platforms have to provide.
19    fn env(&self) -> &Env;
20
21    /// Initializes the platform from the given platform directory.
22    ///
23    /// # Examples
24    /// ```no_run
25    /// use libcnb::generic::GenericPlatform;
26    /// use libcnb::Platform;
27    /// let platform = GenericPlatform::from_path("/platform").unwrap();
28    /// ```
29    fn from_path(platform_dir: impl AsRef<Path>) -> io::Result<Self>;
30}
31
32/// Initializes a new `Env` based on the given platform directory.
33pub(crate) fn read_platform_env(platform_dir: impl AsRef<Path>) -> std::io::Result<Env> {
34    let env_path = platform_dir.as_ref().join("env");
35    let mut env_vars = Env::new();
36
37    match fs::read_dir(env_path) {
38        Ok(entries) => {
39            for entry in entries {
40                let entry = entry?;
41                let path = entry.path();
42
43                if let Some(file_name) = path.file_name() {
44                    // k8s volume mounts will mount a directory symlink in, so we need to check
45                    // that it's actually a file
46                    if path.is_file() {
47                        let file_contents = fs::read_to_string(&path)?;
48                        env_vars.insert(file_name.to_owned(), file_contents);
49                    }
50                }
51            }
52        }
53        Err(err) => {
54            // don't fail if `<platform>/env` doesn't exist
55            if err.kind() != std::io::ErrorKind::NotFound {
56                return Err(err);
57            }
58        }
59    }
60
61    Ok(env_vars)
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use std::ffi::OsString;
68
69    #[test]
70    fn read_platform_env_reads_correct_env_vars() {
71        let tmpdir = tempfile::tempdir().unwrap();
72        let env_dir = tmpdir.path().join("env");
73        fs::create_dir(&env_dir).unwrap();
74
75        fs::write(env_dir.join("FOO"), "BAR").unwrap();
76        fs::write(env_dir.join("HELLO"), "World!").unwrap();
77
78        let env = read_platform_env(tmpdir.path()).unwrap();
79        assert_eq!(env.get("FOO"), Some(&OsString::from("BAR")));
80        assert_eq!(env.get("HELLO"), Some(&OsString::from("World!")));
81    }
82
83    #[test]
84    fn read_platform_env_handles_directories_in_env_folder() {
85        let tmpdir = tempfile::tempdir().unwrap();
86        let env_dir = tmpdir.path().join("env");
87        fs::create_dir(&env_dir).unwrap();
88        let dummy_dir = env_dir.join("foobar");
89        fs::create_dir(dummy_dir).unwrap();
90        fs::write(env_dir.join("FOO"), "BAR").unwrap();
91
92        let result = read_platform_env(tmpdir.path());
93        assert!(result.is_ok());
94    }
95
96    // this symlink is only supported on unix
97    #[cfg(target_family = "unix")]
98    #[test]
99    fn read_platform_env_handles_directories_via_symlinks() {
100        let tmpdir = tempfile::tempdir().unwrap();
101        let env_dir = tmpdir.path().join("env");
102        fs::create_dir(&env_dir).unwrap();
103        let dummy_dir = env_dir.join("foobar");
104        fs::create_dir(&dummy_dir).unwrap();
105        let dst_symlink = env_dir.join("data");
106        std::os::unix::fs::symlink(&dummy_dir, dst_symlink).unwrap();
107        fs::write(env_dir.join("FOO"), "BAR").unwrap();
108
109        let result = read_platform_env(tmpdir.path());
110        assert!(result.is_ok());
111    }
112
113    #[test]
114    fn read_platform_env_does_not_blow_up_if_platform_env_is_missing() {
115        let tmpdir = tempfile::tempdir().unwrap();
116
117        let result = read_platform_env(tmpdir.path());
118        assert!(result.is_ok());
119    }
120}