1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//  UNPACK.rs
//    by Lut99
//
//  Created:
//    28 Mar 2023, 10:26:05
//  Last edited:
//    28 Mar 2023, 10:58:36
//  Auto updated?
//    Yes
//
//  Description:
//!   Implements functions that can unpack internal files.
//

use std::fs;
use std::path::{Path, PathBuf};

use brane_cfg::info::Info as _;
use brane_cfg::node::{NodeConfig, NodeKind};
use log::{debug, info};

pub use crate::errors::UnpackError as Error;
use crate::spec::ResolvableNodeKind;


/***** LIBRARY *****/
/// Unpacks the target Docker Compose file that we embedded in this executable.
///
/// # Arguments
/// - `kind`: The NodeKind that determines the specific file to unpack to.
/// - `fix_dirs`: Whether to fix missing directories.
/// - `path`: The path to write the new file to.
/// - `node_config_path`: The path to the `node.yml` file.
///
/// # Errors
/// This function errors if we failed to read the `node.yml` file, or failed to write the builtin one.
pub fn compose(kind: ResolvableNodeKind, fix_dirs: bool, path: impl AsRef<Path>, node_config_path: impl AsRef<Path>) -> Result<(), Error> {
    let path: &Path = path.as_ref();
    let node_config_path: &Path = node_config_path.as_ref();
    info!("Extracting Docker Compose file for '{}' to '{}'", kind, path.display());

    // Resolve the kind, if necessary
    let kind: NodeKind = match kind.0 {
        Some(kind) => kind,
        None => {
            debug!("Resolving node kind using '{}'...", node_config_path.display());

            // Load the node config file to resolve the kind
            let node_config: NodeConfig = match NodeConfig::from_path(node_config_path) {
                Ok(config) => config,
                Err(err) => {
                    return Err(Error::NodeConfigError { err });
                },
            };

            // Return the kind
            node_config.node.kind()
        },
    };

    // Resolve the path
    let path: PathBuf = path.to_string_lossy().replace("$NODE", &kind.to_string()).into();

    // Check if the target directory exists
    if let Some(parent) = path.parent() {
        debug!("Asserting target directory '{}' exists...", parent.display());

        // Assert it exists
        if !parent.exists() {
            // Either fix or fail
            if fix_dirs {
                if let Err(err) = fs::create_dir_all(parent) {
                    return Err(Error::TargetDirCreateError { path: parent.into(), err });
                }
            } else {
                return Err(Error::TargetDirNotFound { path: parent.into() });
            }
        }

        // Assert it is a directory
        if !parent.is_dir() {
            return Err(Error::TargetDirNotADir { path: parent.into() });
        }
    }

    // Get the correct file
    let compose: &str = match kind {
        NodeKind::Central => include_str!("../../docker-compose-central.yml"),
        NodeKind::Worker => include_str!("../../docker-compose-worker.yml"),
        NodeKind::Proxy => include_str!("../../docker-compose-proxy.yml"),
    };

    // Attempt to write it
    debug!("Writing file to '{}'...", path.display());
    if let Err(err) = fs::write(&path, compose) {
        return Err(Error::FileWriteError { what: "Docker Compose", path, err });
    }

    // OK, done
    Ok(())
}