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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
pub mod error;
pub mod schema;
pub mod spec;

mod archive;
mod compile;

use self::error::{Error, Result};
use self::schema::Schema;
use self::spec::Spec;
pub use compile::Release;
use kct_helper::{io, json};
use serde_json::Value;
use std::path::PathBuf;
use tempfile::TempDir;

const SCHEMA_FILE: &str = "values.schema.json";
const SPEC_FILE: &str = "kcp.json";
const VALUES_FILE: &str = "values.json";
const MAIN_FILE: &str = "templates/main.jsonnet";

#[derive(Debug)]
pub struct Package {
	pub root: PathBuf,
	pub main: PathBuf,
	pub spec: Spec,
	pub schema: Option<Schema>,
	pub values: Option<Value>,
	pub brownfield: Option<TempDir>,
}

/// Associated functions
impl Package {
	pub fn from_path(root: PathBuf) -> Result<Self> {
		let (root, brownfield) = match root.extension() {
			None => (root, None),
			Some(_) => {
				let brownfield = TempDir::new()
					.expect("Unable to create temporary directory to unpack your KCP");
				let unarchived = PathBuf::from(brownfield.path());

				archive::unarchive(&root, &unarchived).map_err(|_err| Error::InvalidFormat)?;

				(unarchived, Some(brownfield))
			}
		};

		let spec = {
			let mut path = root.clone();
			path.push(SPEC_FILE);

			if path.exists() {
				Spec::from_path(path)?
			} else {
				return Err(Error::NoSpec);
			}
		};

		let schema = {
			let mut path = root.clone();
			path.push(SCHEMA_FILE);

			if path.exists() {
				Some(Schema::from_path(path)?)
			} else {
				None
			}
		};

		let values = {
			let mut path = root.clone();
			path.push(VALUES_FILE);

			if path.exists() {
				let contents = io::from_file(&path).map_err(|_err| Error::InvalidValues)?;
				Some(serde_json::from_str(&contents).map_err(|_err| Error::InvalidValues)?)
			} else {
				None
			}
		};

		let main = {
			let mut path = root.clone();
			path.push(MAIN_FILE);

			if path.exists() {
				path
			} else {
				return Err(Error::NoMain);
			}
		};

		validate_values(&schema, &values)?;

		Ok(Package {
			root,
			main,
			spec,
			schema,
			values,
			brownfield,
		})
	}
}

/// Methods
impl Package {
	pub fn archive(self, dest: &PathBuf) -> std::result::Result<PathBuf, String> {
		let name = format!("{}_{}", self.spec.name, self.spec.version);
		archive::archive(&name, &self.root, dest)
	}

	pub fn compile(self, values: Option<Value>, release: Option<Release>) -> Result<Value> {
		let values = match (&self.values, &values) {
			(Some(defaults), Some(values)) => {
				let mut merged = defaults.to_owned();
				json::merge(&mut merged, values);

				Some(merged)
			}
			(None, Some(values)) => Some(values.to_owned()),
			(Some(defaults), None) => Some(defaults.to_owned()),
			_ => None,
		};

		validate_values(&self.schema, &values)?;

		compile::compile(self, values.unwrap_or(Value::Null), release)
	}
}

fn validate_values(schema: &Option<Schema>, values: &Option<Value>) -> Result<()> {
	let (schema, values) = match (&schema, &values) {
		(None, None) => return Ok(()),
		(None, Some(_)) => return Err(Error::NoSchema),
		(Some(_), None) => return Err(Error::NoValues),
		(Some(schema), Some(value)) => (schema, value),
	};

	if values.is_object() && schema.validate(&values) {
		Ok(())
	} else {
		println!("Values = {}", values);
		Err(Error::InvalidValues)
	}
}