diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2020-09-27 22:50:31 +0200 |
---|---|---|
committer | Matthias Beyer <matthias.beyer@atos.net> | 2020-10-12 15:14:17 +0200 |
commit | b542789c87e2acdc1de3d52bd0670ace325a87d3 (patch) | |
tree | 13e3817c59f1db797b71f8c13c07a4a826c6b651 |
Initial import
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Cargo.toml | 24 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | config.toml | 35 | ||||
-rw-r--r-- | shell.nix | 33 | ||||
-rw-r--r-- | src/config/mod.rs | 81 | ||||
-rw-r--r-- | src/main.rs | 51 | ||||
-rw-r--r-- | src/package/mod.rs | 5 | ||||
-rw-r--r-- | src/package/package.rs | 95 | ||||
-rw-r--r-- | src/package/util.rs | 25 | ||||
-rw-r--r-- | src/phase/mod.rs | 14 | ||||
-rw-r--r-- | src/util/mod.rs | 77 |
12 files changed, 445 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2a5c2b7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "yabos" +version = "0.1.0" +authors = ["Matthias Beyer <mail@beyermatthias.de>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.3" +tracing = "0.1" +toml = "0.5" +serde = "1" +config = "0.9" +anyhow = "1" +getset = "0.1" + +url = { version = "2", features = ["serde"] } +tokio = { version = "0.2", features = ["full"] } +shiplift = { git = "https://github.com/softprops/shiplift", branch = "master" } + +[dev-dependencies] +env_logger = "0.7" + diff --git a/README.md b/README.md new file mode 100644 index 0000000..80a2760 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# YABOS + +Yet Another Build Orchestration System diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..456b4ac --- /dev/null +++ b/config.toml @@ -0,0 +1,35 @@ +# repository of package definitions +repository = "/tmp/path" + +# Phases which can be configured in the packages + +# This also defines the _order_ in which the phases are executed +# Each phase gets a pre_ and a post_ phase added automatically. +# So for [ "foo", "bar" ], the phases are executed in this order: +# pre_foo +# foo +# post_foo +# pre_bar +# bar +# post_bar +# +# Phases which are not listed here are not executed at all. +available_phases = [ "unpack", "patch", "configure", "build", "fixup", "pack" ] + +[docker] +# Images which can be used to build +# images not listed here are automatically rejected +images = [ "debian:bullseye" ] + +# List of docker endpoints +[[docker.endpoints]] +name = "testhostname" +uri = "http://0.0.0.0:8095" +endpoint_type = "http" + + +[containers] +# environment variables which are allowed during container start +# This way, errors (typos) when passing environment to a build can be prevented +allowed_env = [ "PATH" ] + diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..341c15b --- /dev/null +++ b/shell.nix @@ -0,0 +1,33 @@ +{ ... }: + +let + moz_overlay = import ( + builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz + ); + + pkgs = import <nixpkgs> { overlays = [ moz_overlay ]; }; +in +pkgs.mkShell { + buildInputs = with pkgs; [ + rustChannels.stable.rust-std + rustChannels.stable.rust + rustChannels.stable.rustc + rustChannels.stable.cargo + + cmake + curl + gcc + openssl + pkgconfig + which + zlib + ]; + + shellHook = '' + alias docker='docker --host=tcp://localhost:8095' + ''; + + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang}/lib"; +} + + diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..1c29817 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,81 @@ +use std::path::PathBuf; +use std::fmt::Debug; +use std::ops::Deref; + +use anyhow::Result; +use getset::Getters; +use serde::Deserialize; + +use crate::phase::PhaseName; +use crate::util::EnvironmentVariableName; +use crate::util::docker::ImageName; + +#[derive(Debug, Getters, Deserialize)] +pub struct NotValidatedConfiguration { + #[getset(get = "pub")] + repository: PathBuf, + + #[getset(get = "pub")] + docker: DockerConfig, + + #[getset(get = "pub")] + containers: ContainerConfig, + + #[getset(get = "pub")] + available_phases: Vec<PhaseName>, +} + +impl NotValidatedConfiguration { + pub fn validate(self) -> Result<Configuration> { + unimplemented!() + } +} + +#[derive(Debug)] +pub struct Configuration(NotValidatedConfiguration); + +impl Deref for Configuration { + type Target = NotValidatedConfiguration; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + + +#[derive(Debug, Getters, Deserialize)] +pub struct DockerConfig { + + #[getset(get = "pub")] + images: Vec<ImageName>, + + #[getset(get = "pub")] + endpoints: Vec<Endpoint>, +} + +#[derive(Debug, Getters, Deserialize)] +pub struct ContainerConfig { + #[getset(get = "pub")] + allowed_env: Vec<EnvironmentVariableName>, +} + + +#[derive(Debug, Getters, Deserialize)] +pub struct Endpoint { + #[getset(get = "pub")] + name: String, + + #[getset(get = "pub")] + uri: String, + + #[getset(get = "pub")] + endpoint_type: EndpointType, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub enum EndpointType { + Socket, + Http, +} + + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b9abb1d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,51 @@ +#[macro_use] extern crate log; + +use std::str::FromStr; +use anyhow::Result; +use anyhow::Error; + +mod util; +mod package; +mod phase; +mod config; +use crate::config::DockerConfig; +use crate::config::Endpoint; +use crate::config::EndpointType; + +#[tokio::main] +async fn main() -> Result<()> { + let mut config = ::config::Config::default(); + config + .merge(::config::File::with_name("config"))? + .merge(::config::Environment::with_prefix("YABOS"))?; + // Add in settings from the environment (with a prefix of YABOS) + // Eg.. `YABOS_DEBUG=1 ./target/app` would set the `debug` key + // + + let docker_config = config.get::<DockerConfig>("docker")?; + + let iter = docker_config + .endpoints() + .iter() + .map(|ep| { + match ep.endpoint_type() { + EndpointType::Http => { + shiplift::Uri::from_str(ep.uri()) + .map(|uri| shiplift::Docker::host(uri)) + .map_err(Error::from) + } + + EndpointType::Socket => { + Ok(shiplift::Docker::unix(ep.uri())) + } + } + }); + + for d in iter { + let v = d?.version().await?; + println!("Docker: {}", v.version); + println!("API : {}", v.api_version); + } + + Ok(()) +} diff --git a/src/package/mod.rs b/src/package/mod.rs new file mode 100644 index 0000000..783bf21 --- /dev/null +++ b/src/package/mod.rs @@ -0,0 +1,5 @@ +mod package; +pub use package::*; + +mod util; +pub use util::*; diff --git a/src/package/package.rs b/src/package/package.rs new file mode 100644 index 0000000..ff5cdd9 --- /dev/null +++ b/src/package/package.rs @@ -0,0 +1,95 @@ +use std::path::PathBuf; +use std::collections::HashMap; + +use url::Url; +use getset::Getters; +use serde::Deserialize; + +use crate::phase::{PhaseName, Phase}; +use crate::package::util::*; +use crate::util::docker::ImageName; + +#[derive(Debug, Deserialize, Getters)] +pub struct Package { + #[getset(get = "pub")] + name: PackageName, + + #[getset(get = "pub")] + version: PackageVersion, + + #[getset(get = "pub")] + version_is_semver: bool, + + #[getset(get = "pub")] + source_url: Url, + + #[getset(get = "pub")] + source_hash: SourceHash, + + #[getset(get = "pub")] + system_dependencies: Vec<SystemDependency>, + + #[getset(get = "pub")] + #[serde(skip_serializing_if = "Option::is_none")] + system_dependencies_script: Option<PathBuf>, + + #[getset(get = "pub")] + build_dependencies: Vec<BuildDependency>, + + #[getset(get = "pub")] + #[serde(skip_serializing_if = "Option::is_none")] + build_dependencies_script: Option<PathBuf>, + + #[getset(get = "pub")] + dependencies: Vec<Dependency>, + + #[getset(get = "pub")] + #[serde(skip_serializing_if = "Option::is_none")] + dependencies_script: Option<PathBuf>, + + #[getset(get = "pub")] + patches: Vec<PathBuf>, + + #[getset(get = "pub")] + #[serde(skip_serializing_if = "Option::is_none")] + environment: Option<HashMap<String, String>>, + + #[getset(get = "pub")] + #[serde(skip_serializing_if = "Option::is_none")] + flags: Option<PackageFlags>, + + #[getset(get = "pub")] + #[serde(skip_serializing_if = "Option::is_none")] + deny_on_images: Option<Vec<ImageName>>, + + #[getset(get = "pub")] + #[serde(skip_serializing_if = "Option::is_none")] + phases: Option<HashMap<PhaseName, Phase>>, +} + +#[derive(Debug, Deserialize)] +pub struct SourceHash { + #[serde(rename = "type")] + hashtype: HashType, + + #[serde(rename = "hash")] + value: HashValue, +} + +#[derive(Debug, Deserialize)] +pub enum HashType { + #[serde(rename = "sha1")] + Sha1, + + #[serde(rename = "sha256")] + Sha256, + + #[serde(rename = "sha512")] + Sha512, +} + +#[derive(Debug, Deserialize)] +pub struct PackageFlags { + build_parallel: bool, +} + diff --git a/src/package/util.rs b/src/package/util.rs new file mode 100644 index 0000000..08f55fe --- /dev/null +++ b/src/package/util.rs @@ -0,0 +1,25 @@ +//! Utility types for the package definitions +//! +//! These types exist only for the purpose of strong typing +//! and cannot do anything special. + +use serde::Deserialize; + +#[derive(Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct PackageName(String); + +#[derive(Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct PackageVersion(String); + +#[derive(Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct SystemDependency(String); + +#[derive(Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct BuildDependency(String); + +#[derive(Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct Dependency(String); + +#[derive(Deserialize, Debug, Hash, Eq, PartialEq)] +pub struct HashValue(String); + diff --git a/src/phase/mod.rs b/src/phase/mod.rs new file mode 100644 index 0000000..44581bb --- /dev/null +++ b/src/phase/mod.rs @@ -0,0 +1,14 @@ +use std::path::PathBuf; +use std::collections::HashMap; + +use serde::Deserialize; + +#[derive(Debug, Deserialize, Eq, PartialEq, Hash)] +pub struct PhaseName(String); + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub enum Phase { + Path(PathBuf), + Text(String), +} + diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..84ea114 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,77 @@ +use serde::Deserialize; + +#[derive(Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub struct EnvironmentVariableName(String); + +pub mod docker { + use anyhow::Result; + use anyhow::anyhow; + use serde::Deserialize; + + #[derive(Deserialize, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] + pub struct ImageName(String); + + /// Check whether a string is a valid docker tag name + /// + /// From the docker spec: + /// + /// > A tag name must be valid ASCII and may contain lowercase and uppercase letters, digits, + /// > underscores, periods and dashes. A tag name may not start with a period or a dash and may + /// > contain a maximum of 128 characters. + /// + /// Returns Ok(()) if `s` is a valid docker tag name, otherwise an explanatory error message + pub fn is_valid_tag_name(s: &str) -> Result<()> { + let valid_chars = s.chars().all(|c| { + c == '_' || + c == ':' || + c == '-' || + c.is_ascii_alphanumeric() + }); + + if !valid_chars { + return Err(anyhow!("Invalid characters")) + } + + if s.chars().count() > 128 { + return Err(anyhow!("Too long")) + } + + + if s.chars().next().map(|c| c == '.' || c == '-').unwrap_or(false) { + return Err(anyhow!("Starts with invalid character")) + } + + Ok(()) + } +} + +#[cfg(test)] +mod docker_test { + extern crate env_logger; + fn setup_logging() { + let _ = env_logger::try_init(); + } + + use super::docker::*; + + #[test] + fn is_valid_tag_name_test_1() { + setup_logging(); + let test = |s| { + debug!("check if valid: '{}'", s); + let e = is_valid_tag_name(s); + debug!("Result = {:?}", e); + e + }; + + assert!(test("foo").is_ok()); + assert!(test("foo:bar").is_ok()); + assert!(test("foo123").is_ok()); + assert!(test("1f23oo").is_ok()); + assert!(test(":foo").is_ok()); + assert!(test(".foo").is_err()); + assert!(test("-foo").is_err()); + } + +} + |