├── .github └── workflows │ ├── build.yml │ └── main.yml ├── .gitignore ├── .gitmodules ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── builtin └── functions │ └── verify-macaroon │ ├── .cargo │ └── config.toml │ ├── Cargo.toml │ └── src │ └── main.rs ├── crates ├── cli │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── archive.rs │ │ ├── commands │ │ ├── bind.rs │ │ ├── burn.rs │ │ ├── cast │ │ │ ├── mod.rs │ │ │ ├── ruby.rs │ │ │ ├── rust.rs │ │ │ └── wasm │ │ │ │ ├── wasi_snapshot_preview1.command.wasm │ │ │ │ └── wasi_snapshot_preview1.reactor.wasm │ │ ├── host.rs │ │ ├── init.rs │ │ ├── make.rs │ │ ├── mod.rs │ │ ├── move.rs │ │ ├── nuke.rs │ │ ├── pack.rs │ │ ├── push.rs │ │ └── user.rs │ │ ├── main.rs │ │ └── templates │ │ ├── mod.rs │ │ └── project.rs ├── core │ ├── Cargo.toml │ ├── README.md │ ├── guest │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── macros │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src │ │ │ │ └── lib.rs │ │ └── src │ │ │ ├── assemblylift.rs │ │ │ ├── command.rs │ │ │ ├── jwt.rs │ │ │ ├── lib.rs │ │ │ ├── opa.rs │ │ │ └── secrets.rs │ ├── io │ │ ├── README.md │ │ ├── common │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src │ │ │ │ ├── constants.rs │ │ │ │ └── lib.rs │ │ └── guest │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src │ │ │ └── lib.rs │ ├── iomod │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── guest │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src │ │ │ │ └── lib.rs │ │ ├── iomod.capnp │ │ └── src │ │ │ ├── lib.rs │ │ │ ├── macros.rs │ │ │ ├── package.rs │ │ │ └── registry.rs │ ├── src │ │ ├── buffers.rs │ │ ├── jwt │ │ │ ├── README.md │ │ │ ├── error.rs │ │ │ ├── jwt.rs │ │ │ ├── keyset.rs │ │ │ └── mod.rs │ │ ├── lib.rs │ │ ├── policy_manager.rs │ │ ├── threader.rs │ │ └── wasm │ │ │ ├── cache.rs │ │ │ └── mod.rs │ └── wit │ │ ├── assemblylift │ │ └── assemblylift.wit │ │ ├── jwt │ │ └── jwt.wit │ │ ├── opa │ │ └── opa.wit │ │ └── secrets │ │ └── secrets.wit ├── generator │ ├── Cargo.toml │ └── src │ │ ├── context.rs │ │ ├── lib.rs │ │ ├── projectfs.rs │ │ ├── providers │ │ ├── api_gateway │ │ │ ├── mod.rs │ │ │ └── templates │ │ │ │ ├── api_impl.tf.handlebars │ │ │ │ └── api_inst.tf.handlebars │ │ ├── aws_lambda │ │ │ ├── mod.rs │ │ │ └── templates │ │ │ │ ├── function_impl.tf.handlebars │ │ │ │ ├── service_impl.tf.handlebars │ │ │ │ └── service_inst.tf.handlebars │ │ ├── ecr │ │ │ ├── mod.rs │ │ │ └── templates │ │ │ │ ├── ecr_impl.tf.handlebars │ │ │ │ ├── ecr_inst.tf.handlebars │ │ │ │ └── ecr_inst_root.tf.handlebars │ │ ├── gloo │ │ │ ├── mod.rs │ │ │ └── templates │ │ │ │ ├── api_impl.tf.handlebars │ │ │ │ └── api_inst.tf.handlebars │ │ ├── kubernetes │ │ │ ├── mod.rs │ │ │ └── templates │ │ │ │ ├── function.dockerfile.handlebars │ │ │ │ ├── function_impl.tf.handlebars │ │ │ │ ├── service_impl.tf.handlebars │ │ │ │ └── service_inst.tf.handlebars │ │ ├── mod.rs │ │ └── route53 │ │ │ ├── mod.rs │ │ │ └── templates │ │ │ ├── dns_impl.tf.handlebars │ │ │ ├── dns_impl_apigw.tf.handlebars │ │ │ ├── dns_impl_gloo.tf.handlebars │ │ │ ├── dns_impl_root.tf.handlebars │ │ │ ├── dns_inst.tf.handlebars │ │ │ └── dns_inst_root.tf.handlebars │ │ ├── templates │ │ └── context.tf.handlebars │ │ └── toml │ │ ├── asml.rs │ │ ├── mod.rs │ │ └── service.rs ├── runtimes │ ├── aws-lambda │ │ ├── guest │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src │ │ │ │ └── lib.rs │ │ └── host │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src │ │ │ ├── abi.rs │ │ │ └── main.rs │ ├── components │ │ └── wasi-secrets │ │ │ └── in-memory │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ └── lib.rs │ └── hyper │ │ ├── Cargo.toml │ │ └── src │ │ ├── abi.rs │ │ ├── launcher.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ └── runner.rs └── tools │ ├── Cargo.toml │ └── src │ ├── cmctl.rs │ ├── glooctl.rs │ ├── kubectl.rs │ ├── lib.rs │ └── terraform.rs ├── docker ├── asml-hyper-debian ├── asml-lambda-alpine ├── asml-lambda-default └── asml-openfaas-alpine ├── docs ├── AssemblyLift_logo.png ├── README.md ├── cli-transpiler.md ├── core-abi.md ├── core-buffers.md ├── core-threader.md ├── lang-ruby.md ├── lang-rust.md ├── provider-apigw-amz.md ├── provider-apigw-gloo.md ├── provider-dns-route53.md ├── provider-service-aws.md ├── provider-service-k8s.md ├── rt-hyper.md └── rt-lambda.md └── examples ├── .gitignore ├── README.md ├── custom-domain ├── .gitignore ├── assemblylift.toml └── services │ └── example │ ├── echo │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs │ └── service.toml └── hello-world ├── .gitignore ├── assemblylift.toml └── services └── hello ├── ruby └── handler.rb ├── rust ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs └── service.toml /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Verify Build 2 | 3 | on: 4 | pull_request: 5 | branches: [ mainline, v0.4.0-beta ] 6 | 7 | jobs: 8 | 9 | build-lambda-default: 10 | name: 'AWS Lambda Runtime' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: 'Checkout' 14 | uses: actions/checkout@v2 15 | with: 16 | submodules: recursive 17 | - name: 'Build' 18 | run: docker build -t assemblylift/asml-lambda-default . --file docker/asml-lambda-default 19 | 20 | build-hyper: 21 | name: 'Hyper Runtime' 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: 'Checkout' 25 | uses: actions/checkout@v2 26 | with: 27 | submodules: recursive 28 | - name: 'Build' 29 | run: docker build -t assemblylift/hyper-debian . --file docker/asml-hyper-debian 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/target/** 3 | .DS_Store 4 | bootstrap 5 | bootstrap.zip 6 | *_capnp.rs 7 | .idea/ 8 | ayo-test 9 | 10 | # for future use, ignore folders that match pattern: dev-test.* 11 | dev-test.* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkoro/assemblylift/a4f9094b0ed07eb813c9d2f5893a5a23dbca9a08/.gitmodules -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at conduct@akkoro.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Learning the codebase 2 | Take a look at the [design docs](docs/README.md). 3 | 4 | # Proposing a change 5 | If there's a feature you'd like to implement, please start a [discussion](https://github.com/akkoro/assemblylift/discussions) describing 6 | your proposal before moving forward with a PR. This helps ensure that changes stay in scope and work isn't duplicated. 🙂 7 | 8 | Changes don't have to be limited to new features! Feel free to propose something if there is code you think could be 9 | refactored to be clearer, documentation you'd like to contribute, CI/CD enhancements, etc. 10 | 11 | # Getting your change merged 12 | Please use [forking workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow) to open a new Pull Request. 13 | 14 | # Reporting a bug 15 | Please [open an issue](https://github.com/akkoro/assemblylift/issues/new?labels=bug). 16 | 17 | # Guidelines 18 | Remember to abide by the [Contributor Covenant](CODE_OF_CONDUCT.md). 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "crates/runtimes/aws-lambda/host", 5 | "crates/runtimes/aws-lambda/guest", 6 | "crates/runtimes/hyper", 7 | "crates/runtimes/components/wasi-secrets/in-memory", 8 | # "builtin/functions/verify-macaroon", 9 | "crates/generator", 10 | "crates/tools", 11 | "crates/core", 12 | "crates/core/guest", 13 | "crates/core/guest/macros", 14 | "crates/core/iomod", 15 | "crates/core/iomod/guest", 16 | "crates/core/io/guest", 17 | "crates/core/io/common", 18 | "crates/cli" 19 | ] 20 | exclude = ["examples"] 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | The AssemblyLift project is being rebooted... stay tuned! 6 | 7 | -------------------------------------------------------------------------------- /builtin/functions/verify-macaroon/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "wasm32-wasi" 3 | -------------------------------------------------------------------------------- /builtin/functions/verify-macaroon/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-builtins-verify-macaroon" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | base64 = "0.20" 8 | direct-executor = "0.3.0" 9 | serde = "1" 10 | serde_json = "1" 11 | asml_core = { version = "0.4.0-alpha", package = "assemblylift-core-guest" } 12 | assemblylift_core_io_guest = { version = "0.4.0-alpha", package = "assemblylift-core-io-guest" } 13 | macaroon = { version = "0.1", package = "macaroon-asml-fork" } 14 | secretsmanager = { version = "0.1", package = "assemblylift-iomod-secretsmanager-guest" } 15 | -------------------------------------------------------------------------------- /builtin/functions/verify-macaroon/src/main.rs: -------------------------------------------------------------------------------- 1 | use macaroon::{Macaroon, MacaroonKey, Verifier}; 2 | 3 | use asml_core::*; 4 | 5 | use secretsmanager; 6 | use secretsmanager::structs::*; 7 | 8 | #[handler] 9 | async fn main() { 10 | let event: serde_json::Value = 11 | serde_json::from_str(&ctx.input).expect("could not parse function input as JSON"); 12 | 13 | let identity = event["identitySource"].as_array().unwrap()[0] 14 | .as_str() 15 | .unwrap() 16 | .to_string(); 17 | let macaroon = match Macaroon::deserialize(&identity) { 18 | Ok(m) => m, 19 | Err(err) => { 20 | FunctionContext::log(err.to_string()); 21 | return FunctionContext::success("{\"isAuthorized\":false}".to_string()); 22 | } 23 | }; 24 | let verifier = Verifier::default(); 25 | let user_key = get_user_key( 26 | std::str::from_utf8(macaroon.identifier().0.as_slice()) 27 | .unwrap() 28 | .to_string(), 29 | ) 30 | .await 31 | .unwrap(); 32 | 33 | match verifier.verify( 34 | &macaroon, 35 | &MacaroonKey::generate(user_key.as_str().as_bytes()), 36 | vec![], 37 | ) { 38 | Ok(_) => FunctionContext::success("{\"isAuthorized\":true}".to_string()), 39 | Err(_) => FunctionContext::success("{\"isAuthorized\":false}".to_string()), 40 | } 41 | } 42 | 43 | async fn get_user_key(user_id: String) -> Result { 44 | let mut get_secret_req = GetSecretValueRequest::default(); 45 | get_secret_req.secret_id = format!("echopod/user/{}", &user_id); 46 | match secretsmanager::get_secret_value(get_secret_req).await { 47 | Ok(res) => Ok(res.secret_string.unwrap()), 48 | Err(err) => Err(err.to_string()), 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /crates/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-cli" 3 | version = "0.4.0-beta.0" 4 | description = "The AssemblyLift Command Line Interface" 5 | authors = ["Akkoro and the AssemblyLift contributors "] 6 | edition = "2018" 7 | license-file = "../LICENSE.md" 8 | repository = "https://github.com/akkoro/assemblylift" 9 | readme = "README.md" 10 | exclude = ["resources/**"] 11 | 12 | [[bin]] 13 | name = "asml" 14 | path = "src/main.rs" 15 | 16 | [dependencies] 17 | anyhow = "1" 18 | base64 = "0.13" 19 | bytes = "1.1" 20 | clap = "2.33" 21 | dialoguer = "0.10" 22 | flate2 = "1" 23 | handlebars = "3.5" 24 | itertools = "0.10" 25 | jsonpath_lib = "0.3.0" 26 | once_cell = "1.7" 27 | path_abs = "0.5" 28 | serde = { version = "1", features = ["derive", "rc"] } 29 | serde_json = "1" 30 | sha2 = "0.10" 31 | tar = "0.4" 32 | toml = "0.5" 33 | tracing = "0.1" 34 | tracing-subscriber = "0.3" 35 | walkdir = "2.3" 36 | z85 = "3" 37 | zip = "0.6" 38 | 39 | wasmtime = "18.0" 40 | 41 | assemblylift-core = { version = "0.4.0-beta.0", path = "../core" } 42 | assemblylift-core-iomod = { version = "0.4.0-beta.0", path = "../core/iomod" } 43 | assemblylift-generator = { path = "../generator" } 44 | assemblylift-hyper-runtime = { version = "0.4.0-beta.0", path = "../runtimes/hyper" } 45 | assemblylift-tools = { path = "../tools" } 46 | 47 | registry_common = { version = "0.1", package = "asml-iomod-registry-common" } 48 | 49 | [dependencies.reqwest] 50 | version = "0.11" 51 | features = ["blocking", "json"] 52 | -------------------------------------------------------------------------------- /crates/cli/README.md: -------------------------------------------------------------------------------- 1 | assemblylift-cli 2 | ---------------- 3 | 4 | The AssemblyLift `asml` CLI. 5 | 6 | Visit https://assemblylift.akkoro.io for more info about AssemblyLift. 7 | -------------------------------------------------------------------------------- /crates/cli/src/archive.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::fs; 3 | use std::io::prelude::*; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use zip; 7 | use zip::write::FileOptions; 8 | 9 | #[derive(Debug)] 10 | pub struct ArchiveError { 11 | why: String, 12 | } 13 | 14 | impl std::error::Error for ArchiveError {} 15 | 16 | impl fmt::Display for ArchiveError { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | write!(f, "{}", self.why) 19 | } 20 | } 21 | 22 | pub fn zip_dirs( 23 | dirs_in: Vec, 24 | file_out: impl AsRef, 25 | exclude_files_named: Vec<&str>, 26 | ) -> Result<(), ()> { 27 | use walkdir::WalkDir; 28 | 29 | let file = match fs::File::create(&file_out) { 30 | Ok(file) => file, 31 | Err(why) => panic!("could not create zip archive: {}", why.to_string()), 32 | }; 33 | 34 | let mut zip = zip::ZipWriter::new(file); 35 | let options = FileOptions::default().compression_method(zip::CompressionMethod::Stored); 36 | 37 | for dir in dirs_in { 38 | for entry in WalkDir::new(dir.clone()).into_iter().filter_map(|e| e.ok()) { 39 | if exclude_files_named 40 | .iter() 41 | .find(|&f| f == &entry.file_name().to_str().unwrap()) 42 | .is_none() 43 | { 44 | let mut root_dir_up = dir.clone(); 45 | root_dir_up.pop(); 46 | let zip_path = entry 47 | .path() 48 | .to_str() 49 | .unwrap() 50 | .replace(root_dir_up.to_str().unwrap(), ""); 51 | 52 | let mut file_bytes = match fs::read(&entry.path()) { 53 | Ok(bytes) => bytes, 54 | Err(_) => continue, 55 | }; 56 | 57 | zip.start_file(&format!("{}", zip_path), options) 58 | .expect("could not create zip archive"); 59 | zip.write_all(file_bytes.as_mut_slice()) 60 | .expect("could not create zip archive"); 61 | } 62 | } 63 | } 64 | 65 | println!("🗜 > Wrote zip artifact {}", file_out.as_ref().display()); 66 | 67 | Ok(()) 68 | } 69 | 70 | pub fn unzip(bytes_in: &[u8], out_dir: &str) -> Result<(), ArchiveError> { 71 | println!("🗜 > Unzipping archive in {}...", out_dir); 72 | let reader = std::io::Cursor::new(bytes_in); 73 | let mut archive = zip::ZipArchive::new(reader).unwrap(); 74 | archive.extract(out_dir).unwrap(); 75 | Ok(()) 76 | } 77 | -------------------------------------------------------------------------------- /crates/cli/src/commands/bind.rs: -------------------------------------------------------------------------------- 1 | use assemblylift_tools::terraform::Terraform; 2 | use clap::ArgMatches; 3 | 4 | pub fn command(matches: Option<&ArgMatches>) { 5 | let _matches = match matches { 6 | Some(matches) => matches, 7 | _ => panic!("could not get matches for bind command"), 8 | }; 9 | 10 | let tf = Terraform::default(); 11 | tf.init(); 12 | tf.apply(); 13 | } 14 | -------------------------------------------------------------------------------- /crates/cli/src/commands/burn.rs: -------------------------------------------------------------------------------- 1 | use assemblylift_generator::{ 2 | projectfs::{self, Project}, 3 | toml::{asml, service}, 4 | }; 5 | use clap::ArgMatches; 6 | use dialoguer::Confirm; 7 | 8 | pub fn command(matches: Option<&ArgMatches>) { 9 | let matches = match matches { 10 | Some(matches) => matches, 11 | _ => panic!("could not get matches for make command"), 12 | }; 13 | 14 | let manifest = match projectfs::locate_asml_manifest() { 15 | Some(manifest) => manifest, 16 | None => panic!("could not find assemblylift.toml in tree"), 17 | }; 18 | let mut manifest_dir = manifest.1.clone(); 19 | manifest_dir.pop(); 20 | 21 | let project = Project::new(manifest.0.project.name.clone(), Some(manifest_dir.clone())); 22 | 23 | let mut resource_type: Option<&str> = None; 24 | let mut resource_name: Option<&str> = None; 25 | for el in matches 26 | .values_of("resource") 27 | .expect("must specify either 'service', 'function' as an argument to burn") 28 | { 29 | if resource_type.is_none() { 30 | resource_type = Some(el); 31 | continue; 32 | } 33 | if resource_name.is_none() { 34 | resource_name = Some(el); 35 | continue; 36 | } 37 | } 38 | 39 | match resource_type { 40 | Some("service") => { 41 | let service_name = resource_name.unwrap(); 42 | if Confirm::new() 43 | .with_prompt(format!( 44 | "Are you sure you want to destroy service \"{}\"?\nThis is PERMANENT!", 45 | service_name 46 | )) 47 | .interact() 48 | .unwrap() 49 | { 50 | let mut manifest: asml::Manifest = manifest.0.clone(); 51 | manifest.remove_service(service_name); 52 | manifest 53 | .write(manifest_dir.clone()) 54 | .expect("could not write assemblylift.toml"); 55 | 56 | let mut service_dir = manifest_dir.clone(); 57 | service_dir.push("services"); 58 | service_dir.push(service_name); 59 | std::fs::remove_dir_all(service_dir).expect("could not remove service directory"); 60 | } 61 | } 62 | 63 | Some("function") => { 64 | let resource_name = resource_name.unwrap().to_string(); 65 | let function_name: Vec<&str> = resource_name.split(".").collect(); 66 | if function_name.len() != 2 { 67 | panic!("syntax is `burn function .`") 68 | } 69 | 70 | if Confirm::new() 71 | .with_prompt(format!( 72 | "Are you sure you want to destroy function \"{}\"\nThis is PERMANENT!", 73 | resource_name 74 | )) 75 | .interact() 76 | .unwrap() 77 | { 78 | let service_dir = project.service_dir(function_name[0].into()).dir().clone(); 79 | let mut manifest_file = service_dir.clone(); 80 | manifest_file.push("service.toml"); 81 | let mut service_manifest = service::Manifest::read(&manifest_file).unwrap(); 82 | service_manifest.remove_function(function_name[1]); 83 | service_manifest.write(service_dir.clone()).unwrap(); 84 | std::fs::remove_dir_all( 85 | &*project 86 | .service_dir(function_name[0].into()) 87 | .function_dir(function_name[1].into()), 88 | ) 89 | .expect("could not remove service directory"); 90 | } 91 | } 92 | 93 | Some(_) => { 94 | panic!("must specify either 'service', or 'function', as an argument to burn") 95 | } 96 | None => panic!("must specify either 'service', or 'function' as an argument to burn"), 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /crates/cli/src/commands/cast/wasm/wasi_snapshot_preview1.command.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkoro/assemblylift/a4f9094b0ed07eb813c9d2f5893a5a23dbca9a08/crates/cli/src/commands/cast/wasm/wasi_snapshot_preview1.command.wasm -------------------------------------------------------------------------------- /crates/cli/src/commands/cast/wasm/wasi_snapshot_preview1.reactor.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkoro/assemblylift/a4f9094b0ed07eb813c9d2f5893a5a23dbca9a08/crates/cli/src/commands/cast/wasm/wasi_snapshot_preview1.reactor.wasm -------------------------------------------------------------------------------- /crates/cli/src/commands/host.rs: -------------------------------------------------------------------------------- 1 | use clap::ArgMatches; 2 | use tracing::Level; 3 | use tracing_subscriber::FmtSubscriber; 4 | 5 | use assemblylift_core_iomod::registry::{registry_channel, spawn_registry}; 6 | use assemblylift_hyper_runtime::spawn_runtime; 7 | 8 | pub fn command(_matches: Option<&ArgMatches>) { 9 | let subscriber = FmtSubscriber::builder() 10 | .with_max_level(Level::DEBUG) 11 | .finish(); 12 | 13 | tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); 14 | 15 | let (registry_tx, registry_rx) = registry_channel(8); 16 | spawn_registry(registry_rx).expect("unable to spawn IOmod registry"); 17 | spawn_runtime(registry_tx); 18 | } 19 | -------------------------------------------------------------------------------- /crates/cli/src/commands/init.rs: -------------------------------------------------------------------------------- 1 | use std::process; 2 | 3 | use assemblylift_generator::projectfs::Project; 4 | use clap::ArgMatches; 5 | use handlebars::to_json; 6 | use serde_json::value::{Map, Value as Json}; 7 | 8 | use crate::templates::project::{ROOT_DOCUMENTS, SERVICE_DOCUMENTS}; 9 | use crate::templates::write_documents; 10 | 11 | pub fn command(matches: Option<&ArgMatches>) { 12 | let matches = match matches { 13 | Some(matches) => matches, 14 | _ => panic!("could not get matches for init command"), 15 | }; 16 | 17 | let default_service_name = "my-service"; 18 | let default_function_name = "my-function"; 19 | let project_name = matches.value_of("project_name").unwrap(); 20 | // let function_language = matches.value_of("language").unwrap(); 21 | 22 | let project = Project::new(project_name.parse().unwrap(), None); 23 | 24 | { 25 | let data = &mut Map::::new(); 26 | data.insert( 27 | "project_name".to_string(), 28 | to_json(project_name.to_string()), 29 | ); 30 | data.insert( 31 | "default_service_name".to_string(), 32 | to_json(default_service_name.to_string()), 33 | ); 34 | write_documents(&project.dir(), (*ROOT_DOCUMENTS).clone().as_ref(), data); 35 | } 36 | 37 | { 38 | let data = &mut Map::::new(); 39 | data.insert( 40 | "service_name".to_string(), 41 | to_json(default_service_name.to_string()), 42 | ); 43 | // data.insert( 44 | // "function_language".to_string(), 45 | // to_json(function_language.to_string()), 46 | // ); 47 | write_documents( 48 | &project 49 | .service_dir(String::from(default_service_name)) 50 | .dir(), 51 | (*SERVICE_DOCUMENTS).clone().as_ref(), 52 | data, 53 | ); 54 | } 55 | 56 | // match function_language { 57 | // "rust" => { 58 | // assert_prereqs(); 59 | // 60 | // std::fs::create_dir_all(format!( 61 | // "{}/src", 62 | // project 63 | // .service_dir(String::from(default_service_name)) 64 | // .function_dir(String::from(default_function_name)) 65 | // .to_str() 66 | // .unwrap() 67 | // )) 68 | // .unwrap(); 69 | // 70 | // let data = &mut Map::::new(); 71 | // data.insert( 72 | // "function_name".to_string(), 73 | // to_json(default_function_name.to_string()), 74 | // ); 75 | // write_documents( 76 | // &project 77 | // .service_dir(String::from(default_service_name)) 78 | // .function_dir(String::from(default_function_name)), 79 | // (*RUST_FUNCTION_DOCUMENTS).clone().as_ref(), 80 | // data, 81 | // ); 82 | // } 83 | // "ruby" => { 84 | // write_documents( 85 | // &project 86 | // .service_dir(String::from(default_service_name)) 87 | // .function_dir(String::from(default_function_name)), 88 | // (*RUBY_FUNCTION_DOCUMENTS).clone().as_ref(), 89 | // &mut Map::::new(), 90 | // ); 91 | // } 92 | // unknown => panic!("unsupported language: {}", unknown), 93 | // } 94 | 95 | println!("\r\n✅ Done! Your project root is: {:?}", project.dir()); 96 | } 97 | 98 | fn check_rust_prereqs() -> bool { 99 | let cargo_version = process::Command::new("cargo").arg("--version").output(); 100 | 101 | match cargo_version { 102 | Ok(_version) => true, 103 | Err(_) => { 104 | println!("ERROR: missing Cargo!"); 105 | false 106 | } 107 | } 108 | } 109 | 110 | fn assert_prereqs() { 111 | if !check_rust_prereqs() { 112 | panic!("missing system dependencies") 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /crates/cli/src/commands/make.rs: -------------------------------------------------------------------------------- 1 | use assemblylift_generator::projectfs::{self, Project}; 2 | use assemblylift_generator::toml::{asml, service}; 3 | use clap::ArgMatches; 4 | use handlebars::to_json; 5 | use serde_json::value::{Map, Value as Json}; 6 | 7 | use crate::templates::project::{ 8 | RUBY_FUNCTION_DOCUMENTS, RUST_FUNCTION_DOCUMENTS, SERVICE_DOCUMENTS, 9 | }; 10 | use crate::templates::write_documents; 11 | 12 | pub fn command(matches: Option<&ArgMatches>) { 13 | let matches = match matches { 14 | Some(matches) => matches, 15 | _ => panic!("could not get matches for make command"), 16 | }; 17 | 18 | let manifest = match projectfs::locate_asml_manifest() { 19 | Some(manifest) => manifest, 20 | None => panic!("could not find assemblylift.toml in tree"), 21 | }; 22 | let mut manifest_dir = manifest.1.clone(); 23 | manifest_dir.pop(); 24 | 25 | let project = Project::new(manifest.0.project.name.clone(), Some(manifest_dir.clone())); 26 | 27 | let mut resource_type: Option<&str> = None; 28 | let mut resource_name: Option<&str> = None; 29 | for el in matches.values_of("resource").unwrap() { 30 | if resource_type.is_none() { 31 | resource_type = Some(el); 32 | continue; 33 | } 34 | if resource_name.is_none() { 35 | resource_name = Some(el); 36 | continue; 37 | } 38 | } 39 | 40 | match resource_type { 41 | Some("service") => { 42 | let data = &mut Map::::new(); 43 | data.insert( 44 | "service_name".to_string(), 45 | to_json(resource_name.unwrap().to_string()), 46 | ); 47 | let path = project 48 | .service_dir(String::from(resource_name.unwrap())) 49 | .dir(); 50 | write_documents(&path, (*SERVICE_DOCUMENTS).clone().as_ref(), data); 51 | 52 | let mut manifest: asml::Manifest = manifest.0.clone(); 53 | manifest.add_service(resource_name.unwrap()); 54 | manifest 55 | .write(manifest_dir.clone()) 56 | .expect("could not write assemblylift.toml"); 57 | } 58 | 59 | Some("function") => { 60 | let language = matches.value_of("language").unwrap_or("rust"); 61 | let resource_name = resource_name.unwrap().to_string(); 62 | let function_name: Vec<&str> = resource_name.split(".").collect(); 63 | if function_name.len() != 2 { 64 | panic!("syntax is `make function .`") 65 | } 66 | 67 | let service_dir = project.service_dir(function_name[0].into()).dir().clone(); 68 | let mut manifest_file = service_dir.clone(); 69 | manifest_file.push("service.toml"); 70 | let mut service_manifest = service::Manifest::read(&manifest_file).unwrap(); 71 | service_manifest.add_function(function_name[1], language); 72 | service_manifest.write(service_dir.clone()).unwrap(); 73 | 74 | match language { 75 | "rust" => { 76 | let data = &mut Map::::new(); 77 | data.insert("function_name".to_string(), to_json(function_name[1])); 78 | let path = project 79 | .service_dir(String::from(function_name[0])) 80 | .function_dir(String::from(function_name[1])); 81 | write_documents(&path, (*RUST_FUNCTION_DOCUMENTS).clone().as_ref(), data); 82 | } 83 | "ruby" => { 84 | let path = project 85 | .service_dir(String::from(function_name[0])) 86 | .function_dir(String::from(function_name[1])); 87 | write_documents( 88 | &path, 89 | (*RUBY_FUNCTION_DOCUMENTS).clone().as_ref(), 90 | &mut Map::::new(), 91 | ); 92 | } 93 | lang => panic!("function language `{}` is not supported", lang), 94 | } 95 | } 96 | 97 | Some(_) => panic!("must specify either 'service' or 'function' as an argument to make"), 98 | None => panic!("must specify either 'service' or 'function' as an argument to make"), 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /crates/cli/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bind; 2 | pub mod burn; 3 | pub mod cast; 4 | pub mod host; 5 | pub mod init; 6 | pub mod make; 7 | pub mod r#move; 8 | pub mod nuke; 9 | pub mod pack; 10 | pub mod push; 11 | pub mod user; 12 | -------------------------------------------------------------------------------- /crates/cli/src/commands/nuke.rs: -------------------------------------------------------------------------------- 1 | use assemblylift_tools::terraform; 2 | use clap::ArgMatches; 3 | use dialoguer::Confirm; 4 | 5 | pub fn command(_matches: Option<&ArgMatches>) { 6 | if Confirm::new() 7 | .with_prompt( 8 | "Are you sure you want to destroy ALL provisioned infrastructure?\nThis is PERMANENT!", 9 | ) 10 | .interact() 11 | .unwrap() 12 | { 13 | let tf = terraform::Terraform::default(); 14 | tf.destroy(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /crates/cli/src/commands/pack.rs: -------------------------------------------------------------------------------- 1 | use clap::ArgMatches; 2 | 3 | use assemblylift_core_iomod::package::IomodManifest; 4 | 5 | use crate::archive; 6 | 7 | pub fn command(matches: Option<&ArgMatches>) { 8 | let matches = match matches { 9 | Some(matches) => matches, 10 | _ => panic!("could not get matches for cast command"), 11 | }; 12 | 13 | match matches.subcommand() { 14 | ("iomod", matches) => command_iomod(matches), 15 | _ => println!( 16 | "{}", 17 | "missing subcommand. try `asml pack help` for options." 18 | ), 19 | } 20 | } 21 | 22 | fn command_iomod(matches: Option<&ArgMatches>) { 23 | let matches = match matches { 24 | Some(matches) => matches, 25 | _ => panic!("could not get matches for cast command"), 26 | }; 27 | 28 | let cwd = std::env::current_dir().expect("unable to determine the current working directory"); 29 | let mut manifest_path = cwd.clone(); 30 | manifest_path.push("iomod.toml"); 31 | 32 | let manifest = IomodManifest::read(&manifest_path).expect(&format!( 33 | "could not read iomod manifest from {:?}", 34 | manifest_path 35 | )); 36 | 37 | let entrypoint = manifest.process.entrypoint; 38 | let mut binary_path = cwd.clone(); 39 | binary_path.push(entrypoint); 40 | 41 | // verify that the entrypoint exists before we pack it 42 | std::fs::metadata(binary_path.clone()) 43 | .expect(&format!("could not stat {:?}", binary_path.clone())); 44 | 45 | let out_path = matches.value_of("out").unwrap(); // unwrap: this arg is required 46 | 47 | archive::zip_dirs(vec![cwd], out_path, vec![]).expect("zip_dir failed during pack"); 48 | } 49 | -------------------------------------------------------------------------------- /crates/cli/src/commands/user.rs: -------------------------------------------------------------------------------- 1 | use clap::ArgMatches; 2 | 3 | pub fn command(matches: Option<&ArgMatches>) { 4 | let matches = match matches { 5 | Some(matches) => matches, 6 | _ => panic!("could not get matches for cast command"), 7 | }; 8 | 9 | match matches.subcommand() { 10 | ("login", matches) => command_login(matches), 11 | _ => println!( 12 | "{}", 13 | "missing subcommand. try `asml pack help` for options." 14 | ), 15 | } 16 | } 17 | 18 | fn command_login(_matches: Option<&ArgMatches>) {} 19 | -------------------------------------------------------------------------------- /crates/cli/src/templates/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod project; 2 | 3 | use std::io::Write; 4 | use std::path::PathBuf; 5 | use std::{fs, io, path}; 6 | 7 | use handlebars::Handlebars; 8 | use serde_json::value::{Map as SerdeMap, Value as Json}; 9 | 10 | pub struct Document { 11 | pub file_name: &'static str, 12 | pub document: String, 13 | } 14 | 15 | pub fn write_documents(path: &PathBuf, docs: &Vec, data: &mut SerdeMap) { 16 | let mut reg = Handlebars::new(); 17 | for doc in docs { 18 | reg.register_template_string(doc.file_name, doc.document.clone()) 19 | .unwrap(); 20 | 21 | let render = reg.render(doc.file_name, &data).unwrap(); 22 | let mut path = PathBuf::from(path); 23 | path.push(doc.file_name); 24 | write_to_file(&*path, render).unwrap(); 25 | } 26 | } 27 | 28 | fn write_to_file(path: &path::Path, contents: String) -> Result<(), io::Error> { 29 | fs::create_dir_all(path.parent().unwrap())?; 30 | 31 | let mut file = match fs::File::create(path) { 32 | Err(why) => panic!( 33 | "couldn't create file {}: {}", 34 | path.display(), 35 | why.to_string() 36 | ), 37 | Ok(file) => file, 38 | }; 39 | 40 | println!("📄 > Wrote {}", path.display()); 41 | file.write_all(contents.as_bytes()) 42 | } 43 | -------------------------------------------------------------------------------- /crates/cli/src/templates/project.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use once_cell::sync::Lazy; 4 | 5 | use crate::templates::Document; 6 | 7 | static ROOT_GITIGNORE: &str = r#".asml/ 8 | .terraform/ 9 | .DS_Store 10 | net/ 11 | **/target/ 12 | **/build/ 13 | "#; 14 | 15 | static ASSEMBLYLIFT_TOML: &str = r#"[project] 16 | name = "{{project_name}}" 17 | 18 | # Platforms define an environment in which to deploy providers. 19 | # [[platforms]] 20 | # id = "my-id" # A unique identifier for this platform 21 | # name = "platform-name" # Name of the platform, one of "aws" or "kubernetes" 22 | # options = {} # Map of options to pass to the platform 23 | 24 | [[services]] 25 | name = "{{default_service_name}}" # Must correspond to a service in the services/ directory 26 | provider = { name = "my-provider", platform_id = "my-id" } # `platform_id` is the `id` of one of the platforms defined above 27 | # registry_id = "my-registry-id" # Optional; needed for Service providers which deploy using containers 28 | # domain_name = "my.example-domain.com" # Optional; needed if deploying service with a Domain name 29 | 30 | # [[registries]] 31 | # id = "my-registry-id" 32 | # provider = { name = "my-provider", platform_id = "my-id" } 33 | 34 | # [[domains]] 35 | # dns_name = "my.example-domain.com" # An existing hosted zone with the DNS Provider specified 36 | # [domains.provider] 37 | # name = "my-provider" 38 | # platform_id = "my-id" 39 | # options = {} # Map of options to pass to the provider 40 | 41 | "#; 42 | 43 | pub static ROOT_DOCUMENTS: Lazy>> = Lazy::new(|| { 44 | Arc::new(Vec::from([ 45 | Document { 46 | file_name: "assemblylift.toml", 47 | document: String::from(ASSEMBLYLIFT_TOML), 48 | }, 49 | Document { 50 | file_name: ".gitignore", 51 | document: String::from(ROOT_GITIGNORE), 52 | }, 53 | ])) 54 | }); 55 | 56 | static SERVICE_TOML: &str = r#"[gateway] 57 | provider = { name = "{{service_name}}" } 58 | 59 | #[[functions]] 60 | #name = "rusty-fn" # Must correspond to a function in the service's functions/ directory 61 | #language = "rust" # Kind of source code for function; one of "rust" or "ruby" 62 | #http = { verb = "GET", path = "/rustyfn" } # HTTP route to the function from the Service's Gateway 63 | #environment = { var1 = "val1" } # Map of environment variables to pass to the function 64 | "#; 65 | 66 | pub static SERVICE_DOCUMENTS: Lazy>> = Lazy::new(|| { 67 | Arc::new(Vec::from([Document { 68 | file_name: "service.toml", 69 | document: String::from(SERVICE_TOML), 70 | }])) 71 | }); 72 | 73 | static FUNCTION_CARGO_TOML: &str = r#"[package] 74 | name = "{{function_name}}" 75 | version = "0.0.0" 76 | edition = "2021" 77 | 78 | [dependencies] 79 | serde = "1" 80 | serde_json = "1" 81 | assemblylift-core-guest = { version = "0.4.0-beta" } 82 | # You can omit core-io-guest if you will not be using IOmods in your function 83 | assemblylift-core-io-guest = { version = "0.4.0-beta" } 84 | "#; 85 | 86 | static FUNCTION_MAIN_RS: &str = r#"use assemblylift_core_guest::*; 87 | 88 | #[handler] 89 | async fn main() { 90 | // `ctx` is a value injected by the `handler` attribute macro 91 | let event: serde_json::Value = match serde_json::from_slice(&ctx.input) { 92 | Ok(val) => val, 93 | Err(err) => return FunctionContext::failure(format!("could not parse function input as JSON: {}", err.to_string())); 94 | }; 95 | 96 | FunctionContext::success("\"Function returned OK!\"".to_string()); 97 | } 98 | "#; 99 | 100 | static FUNCTION_HANDLER_RB: &str = r#"require 'asml' 101 | require 'base64' 102 | require 'json' 103 | 104 | def main(input) 105 | # TODO implement your function code here! 106 | Asml.success(JSON.generate(input.to_s)) 107 | end 108 | 109 | main(JSON.parse(Asml.get_function_input())) 110 | "#; 111 | 112 | pub static RUST_FUNCTION_DOCUMENTS: Lazy>> = Lazy::new(|| { 113 | Arc::new(Vec::from([ 114 | Document { 115 | file_name: "Cargo.toml", 116 | document: String::from(FUNCTION_CARGO_TOML), 117 | }, 118 | Document { 119 | file_name: "src/main.rs", 120 | document: String::from(FUNCTION_MAIN_RS), 121 | }, 122 | ])) 123 | }); 124 | 125 | pub static RUBY_FUNCTION_DOCUMENTS: Lazy>> = Lazy::new(|| { 126 | Arc::new(Vec::from([Document { 127 | file_name: "handler.rb", 128 | document: String::from(FUNCTION_HANDLER_RB), 129 | }])) 130 | }); 131 | -------------------------------------------------------------------------------- /crates/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-core" 3 | version = "0.4.0-beta.0" 4 | description = "AssemblyLift core library" 5 | authors = ["Akkoro and the AssemblyLift contributors "] 6 | edition = "2018" 7 | license-file = "../LICENSE.md" 8 | repository = "https://github.com/akkoro/assemblylift" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | anyhow = "1.0" 13 | base64 = "0.21" 14 | bincode = "1.3" 15 | crossbeam-channel = "0.5" 16 | itertools = "0.10" 17 | once_cell = "1.4" 18 | opa = { version = "0.10.0-dev", git = "https://github.com/dotxlem/opa-rs.git", rev = "19f4836" } 19 | regex = "1.7" 20 | reqwest = { version = "0.11", features = ["blocking", "json"] } 21 | ring = "0.16" 22 | serde = "1" 23 | serde_json = "1" 24 | tokio = { version = "1.4", features = ["full"] } 25 | tracing = "0.1" 26 | uuid = { version = "1.3", features = ["v4", "fast-rng"] } 27 | 28 | wasmtime = { version = "18.0", features = ["default", "component-model"] } 29 | wasmtime-wasi = { version = "18.0", features = ["preview2"] } 30 | 31 | wat = "1.0.85" 32 | wast = "70.0.2" 33 | wasm-encoder = "0.41" 34 | wit-component = "0.20.1" 35 | wit-parser = "0.13.1" 36 | 37 | assemblylift-core-iomod = { path = "./iomod" } 38 | 39 | [dev-dependencies] 40 | tokio = {version = "1", features = ["macros", "rt-multi-thread"]} 41 | tokio-test = "0.4" 42 | -------------------------------------------------------------------------------- /crates/core/README.md: -------------------------------------------------------------------------------- 1 | assemblylift-core 2 | ----------------- 3 | 4 | AssemblyLift core library 5 | -------------------------------------------------------------------------------- /crates/core/guest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-core-guest" 3 | version = "0.4.0-beta.0" 4 | description = "AssemblyLift core WASM guest library" 5 | authors = ["Akkoro and the AssemblyLift contributors "] 6 | edition = "2018" 7 | license-file = "../../LICENSE.md" 8 | repository = "https://github.com/akkoro/assemblylift" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | clap = { version = "4", features = ["cargo"] } 13 | direct-executor = "0.3" 14 | serde = { version = "1", features = ["derive"] } 15 | serde_json = "1" 16 | wit-bindgen = "0.15" 17 | 18 | assemblylift-core-guest-macros = { version = "0.4.0-beta.0", path = "./macros" } 19 | -------------------------------------------------------------------------------- /crates/core/guest/README.md: -------------------------------------------------------------------------------- 1 | assemblylift-core-guest 2 | ------------------------ 3 | 4 | AssemblyLift core WASM guest library 5 | -------------------------------------------------------------------------------- /crates/core/guest/macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-core-guest-macros" 3 | version = "0.4.0-beta.0" 4 | description = "Procedural macros for assemblylift-core-guest" 5 | authors = ["Akkoro and the AssemblyLift contributors "] 6 | edition = "2018" 7 | license-file = "../../../LICENSE.md" 8 | repository = "https://github.com/akkoro/assemblylift" 9 | readme = "README.md" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | proc-macro2 = "1.0" 16 | quote = "1.0" 17 | syn = { version = "1.0", features = ["full"] } 18 | -------------------------------------------------------------------------------- /crates/core/guest/macros/README.md: -------------------------------------------------------------------------------- 1 | assemblylift-core-guest-macros 2 | ------------------------------ 3 | -------------------------------------------------------------------------------- /crates/core/guest/macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use quote::{quote, quote_spanned}; 2 | use syn::{parse2, ItemFn}; 3 | 4 | #[proc_macro_attribute] 5 | pub fn handler( 6 | _args: proc_macro::TokenStream, 7 | stream: proc_macro::TokenStream, 8 | ) -> proc_macro::TokenStream { 9 | let input: ItemFn = parse2(stream.into()).expect("could not parse token stream"); 10 | let block_statements = &input.block.stmts; 11 | let name = &input.sig.ident; 12 | let _ret = &input.sig.output; 13 | 14 | if name != "main" { 15 | return proc_macro::TokenStream::from(quote_spanned! { name.span() => 16 | compile_error!("only the main function can be tagged with #[handler]"), 17 | }); 18 | } 19 | 20 | proc_macro::TokenStream::from(quote! { 21 | use assemblylift_core_guest::asml_rt; 22 | use assemblylift_core_guest::direct_executor; 23 | // use assemblylift_core_guest::export_command; 24 | use assemblylift_core_guest::FunctionContext; 25 | // use assemblylift_core_guest::command::Command; 26 | use assemblylift_core_guest::wit_bindgen; 27 | // struct Cmd; 28 | // impl Command for Cmd { 29 | // fn run() -> Result<(), ()> { 30 | // Ok(__handler(FunctionContext { input: asml_rt::get_input() })) 31 | // } 32 | // } 33 | // export_command!(Cmd); 34 | fn __handler(ctx: FunctionContext) { 35 | direct_executor::run_spinning(async { 36 | #(#block_statements)* 37 | }); 38 | } 39 | fn main() { 40 | __handler(FunctionContext { input: asml_rt::get_input() }) 41 | } 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /crates/core/guest/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate assemblylift_core_guest_macros; 2 | 3 | use std::collections::HashMap; 4 | use std::fmt; 5 | 6 | pub use direct_executor; 7 | use serde::{Deserialize, Serialize}; 8 | pub use wit_bindgen; 9 | 10 | pub use assemblylift::akkoro::assemblylift::asml_io; 11 | pub use assemblylift::akkoro::assemblylift::asml_rt; 12 | pub use assemblylift_core_guest_macros::handler; 13 | // pub use command::wasi; 14 | 15 | pub mod assemblylift; 16 | // pub mod command; 17 | pub mod jwt; 18 | pub mod opa; 19 | pub mod secrets; 20 | 21 | pub struct FunctionContext { 22 | pub input: Vec, 23 | } 24 | 25 | // TODO success and failure should take bytes as args, not string 26 | impl FunctionContext { 27 | pub fn log(message: String) { 28 | asml_rt::log(asml_rt::LogLevel::Info, clap::crate_name!(), &message) 29 | } 30 | 31 | pub fn success(response: String) { 32 | asml_rt::success(&response.as_bytes().to_vec()) 33 | } 34 | 35 | pub fn failure(response: String) { 36 | asml_rt::failure(&response.as_bytes().to_vec()) 37 | } 38 | } 39 | 40 | pub type StatusCode = u16; 41 | #[derive(Serialize, Deserialize)] 42 | pub struct HttpResponse { 43 | #[serde(rename = "isBase64Encoded")] 44 | is_base64_encoded: bool, 45 | #[serde(rename = "statusCode")] 46 | status_code: StatusCode, 47 | headers: HashMap, 48 | body: String, 49 | } 50 | 51 | #[derive(Serialize, Deserialize)] 52 | pub struct HttpError { 53 | pub code: StatusCode, 54 | pub desc: String, 55 | pub message: String, 56 | } 57 | 58 | #[derive(Copy, Clone, Debug, Serialize, Deserialize)] 59 | pub enum HttpErrorCode { 60 | NotFound = 404, 61 | FunctionError = 520, 62 | } 63 | 64 | impl fmt::Display for HttpErrorCode { 65 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 66 | match self { 67 | HttpErrorCode::NotFound => write!(f, "Missing Resource"), 68 | HttpErrorCode::FunctionError => write!(f, "Function Error"), 69 | } 70 | } 71 | } 72 | 73 | impl HttpResponse { 74 | pub fn ok( 75 | body: String, 76 | content_type: Option, 77 | is_base64_encoded: bool, 78 | gzip: bool, 79 | ) -> Self { 80 | let mut headers = HashMap::default(); 81 | headers.insert( 82 | "content-type".to_string(), 83 | content_type.unwrap_or_else(|| String::from("application/json")), 84 | ); 85 | if gzip { 86 | headers.insert("content-encoding".to_string(), "gzip".to_string()); 87 | } 88 | 89 | Self { 90 | status_code: 200, 91 | is_base64_encoded, 92 | headers, 93 | body, 94 | } 95 | } 96 | 97 | pub fn error(message: String, code: HttpErrorCode) -> Self { 98 | let mut headers = HashMap::default(); 99 | headers.insert( 100 | String::from("content-type"), 101 | String::from("application/json"), 102 | ); 103 | 104 | Self { 105 | status_code: code as StatusCode, 106 | is_base64_encoded: false, 107 | headers, 108 | body: serde_json::to_string(&HttpError { 109 | code: code as StatusCode, 110 | desc: code.to_string(), 111 | message, 112 | }) 113 | .unwrap(), 114 | } 115 | } 116 | } 117 | 118 | #[macro_export] 119 | macro_rules! http_ok { 120 | ($response:expr) => { 121 | FunctionContext::success( 122 | serde_json::to_string(&HttpResponse::ok( 123 | serde_json::to_string(&$response).unwrap(), 124 | None, 125 | false, 126 | false, 127 | )) 128 | .unwrap(), 129 | ); 130 | }; 131 | 132 | ($response:expr, $type:expr, $isb64:expr, $isgzip:expr) => { 133 | FunctionContext::success( 134 | serde_json::to_string(&HttpResponse::ok($response, $type, $isb64, $isgzip)).unwrap(), 135 | ); 136 | }; 137 | } 138 | 139 | #[macro_export] 140 | macro_rules! http_error { 141 | ($message:expr) => { 142 | FunctionContext::success( 143 | serde_json::to_string(&HttpResponse::error($message, HttpErrorCode::FunctionError)) 144 | .unwrap(), 145 | ); 146 | }; 147 | } 148 | 149 | #[macro_export] 150 | macro_rules! http_not_found { 151 | ($resource_name:expr) => { 152 | FunctionContext::success( 153 | serde_json::to_string(&HttpResponse::error( 154 | format!("missing resource {:?}", $resource_name), 155 | HttpErrorCode::NotFound, 156 | )) 157 | .unwrap(), 158 | ); 159 | }; 160 | } 161 | -------------------------------------------------------------------------------- /crates/core/io/README.md: -------------------------------------------------------------------------------- 1 | assemblylift-core-event 2 | ---------------------- 3 | 4 | AssemblyLift core event library 5 | -------------------------------------------------------------------------------- /crates/core/io/common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-core-io-common" 3 | version = "0.3.0" 4 | description = "AssemblyLift core event common library" 5 | authors = ["Akkoro and the AssemblyLift contributors "] 6 | edition = "2018" 7 | license-file = "../../../LICENSE.md" 8 | repository = "https://github.com/akkoro/assemblylift" 9 | readme = "README.md" 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | serde = { version = "1.0", features = ["derive"] } 14 | -------------------------------------------------------------------------------- /crates/core/io/common/README.md: -------------------------------------------------------------------------------- 1 | assemblylift-core-event-common 2 | ------------------------------ 3 | 4 | AssemblyLift core event common library. Provides functionality common to both the 5 | host and guest event implementations. 6 | -------------------------------------------------------------------------------- /crates/core/io/common/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const IO_BUFFER_SIZE_BYTES: usize = 32768; 2 | pub const FUNCTION_INPUT_BUFFER_SIZE: usize = 8192; 3 | -------------------------------------------------------------------------------- /crates/core/io/common/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod constants; 2 | -------------------------------------------------------------------------------- /crates/core/io/guest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-core-io-guest" 3 | version = "0.4.0-beta.0" 4 | description = "AssemblyLift core event WASM guest library" 5 | authors = ["Akkoro and the AssemblyLift contributors "] 6 | edition = "2018" 7 | license-file = "../../../LICENSE.md" 8 | repository = "https://github.com/akkoro/assemblylift" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | serde = "1" 13 | serde_json = "1" 14 | futures = "0.3" 15 | lazy_static = "1.4" 16 | 17 | assemblylift-core-guest = { version = "0.4.0-beta.0", path = "../../guest" } 18 | -------------------------------------------------------------------------------- /crates/core/io/guest/README.md: -------------------------------------------------------------------------------- 1 | assemblylift-core-event-guest 2 | ----------------------------- 3 | 4 | AssemblyLift core event WASM guest library 5 | -------------------------------------------------------------------------------- /crates/core/io/guest/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::marker::PhantomData; 3 | use std::pin::Pin; 4 | use std::task::{Context, Poll, Waker}; 5 | 6 | use serde::{de::DeserializeOwned, Deserialize}; 7 | 8 | use assemblylift_core_guest::asml_rt::LogLevel; 9 | use assemblylift_core_guest::{asml_io, asml_rt}; 10 | 11 | #[derive(Clone)] 12 | /// A handle implementing `std::future::Future` for an in-flight IOmod call 13 | pub struct Io<'a, R> { 14 | pub id: u32, 15 | waker: Box>, 16 | _phantom: PhantomData<&'a R>, 17 | } 18 | 19 | impl<'a, R: Deserialize<'a>> Io<'_, R> { 20 | pub fn new(id: u32) -> Self { 21 | Io { 22 | id, 23 | waker: Box::new(None), 24 | _phantom: PhantomData, 25 | } 26 | } 27 | } 28 | 29 | impl<'a, R> Future for Io<'_, R> 30 | where 31 | R: DeserializeOwned, 32 | { 33 | type Output = R; 34 | 35 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 36 | match asml_io::poll(self.id) { 37 | Ok(res) => Poll::Ready( 38 | read_response::(std::str::from_utf8(&*res).unwrap()).unwrap(), 39 | ), 40 | _ => { 41 | self.waker = Box::new(Some(cx.waker().clone())); 42 | Poll::Pending 43 | } 44 | } 45 | } 46 | } 47 | 48 | fn read_response<'a, T>(res: &str) -> Option 49 | where 50 | T: DeserializeOwned, 51 | { 52 | match serde_json::from_str(res) { 53 | Ok(response) => Some(response), 54 | Err(why) => { 55 | asml_rt::log(LogLevel::Error, "core::io::read_response", &why.to_string()); 56 | None 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/core/iomod/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-core-iomod" 3 | version = "0.4.0-beta.0" 4 | description = "AssemblyLift core IOmod library" 5 | authors = ["Akkoro and the AssemblyLift contributors "] 6 | edition = "2018" 7 | license-file = "../../LICENSE.md" 8 | repository = "https://github.com/akkoro/assemblylift" 9 | readme = "README.md" 10 | 11 | build = "build.rs" 12 | 13 | [dependencies] 14 | tokio = { version = "1.4", features = ["macros", "net", "sync", "rt", "rt-multi-thread"] } 15 | tokio-util = { version = "0.6", features = ["compat"] } 16 | futures = "0.3" 17 | futures-util = "0.3" 18 | once_cell = "1.4" 19 | lazy_static = "1.4" 20 | serde = "1" 21 | paste = "1" 22 | toml = "0.5" 23 | capnp = "0.15" 24 | capnp-rpc = "0.15" 25 | tracing = "0.1" 26 | 27 | assemblylift_core_io_common = { version = "0.3", package = "assemblylift-core-io-common", path = "../io/common" } 28 | 29 | [build-dependencies] 30 | rustc_version = "0.4" 31 | capnpc = "0.15" 32 | -------------------------------------------------------------------------------- /crates/core/iomod/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkoro/assemblylift/a4f9094b0ed07eb813c9d2f5893a5a23dbca9a08/crates/core/iomod/README.md -------------------------------------------------------------------------------- /crates/core/iomod/build.rs: -------------------------------------------------------------------------------- 1 | use rustc_version; 2 | 3 | fn main() { 4 | let version = rustc_version::version().unwrap(); 5 | println!("cargo:rustc-env=RUSTC_VERSION={}", version); 6 | 7 | capnpc::CompilerCommand::new() 8 | .output_path("src") 9 | .file("iomod.capnp") 10 | .run() 11 | .unwrap(); 12 | } 13 | -------------------------------------------------------------------------------- /crates/core/iomod/guest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-core-iomod-guest" 3 | version = "0.4.0-alpha.0" 4 | description = "AssemblyLift core IOmod guest library" 5 | authors = ["Akkoro and the AssemblyLift contributors "] 6 | edition = "2018" 7 | license-file = "../../../LICENSE.md" 8 | repository = "https://github.com/akkoro/assemblylift" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | -------------------------------------------------------------------------------- /crates/core/iomod/guest/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkoro/assemblylift/a4f9094b0ed07eb813c9d2f5893a5a23dbca9a08/crates/core/iomod/guest/README.md -------------------------------------------------------------------------------- /crates/core/iomod/guest/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod macros { 2 | #[macro_export] 3 | macro_rules! iomod { 4 | ($org:ident.$namespace:ident.$name:ident) => { 5 | use assemblylift_core_io_guest::{Io, IO_BUFFER}; 6 | 7 | static IOMOD_ORG: &'static str = std::stringify!($org); 8 | static IOMOD_NAMESPACE: &'static str = std::stringify!($namespace); 9 | static IOMOD_NAME: &'static str = std::stringify!($name); 10 | 11 | extern "C" { 12 | fn __asml_abi_invoke( 13 | name_ptr: *const u8, 14 | name_len: usize, 15 | input_ptr: *const u8, 16 | input_len: usize, 17 | ) -> i32; 18 | } 19 | }; 20 | } 21 | 22 | #[macro_export] 23 | macro_rules! call { 24 | ($name:ident, $input:ty => $output:ty) => { 25 | pub fn $name<'a>(input: $input) -> Io<'a, $output> { 26 | use serde_json; 27 | 28 | let name = std::stringify!($name); 29 | let method_path = 30 | format!("{}.{}.{}.{}", IOMOD_ORG, IOMOD_NAMESPACE, IOMOD_NAME, name); 31 | 32 | let ioid: i32; 33 | unsafe { 34 | let serialized: Box> = Box::from(serde_json::to_vec(&input).unwrap()); 35 | ioid = crate::__asml_abi_invoke( 36 | method_path.as_ptr(), 37 | method_path.len(), 38 | serialized.as_ptr(), 39 | serialized.len(), 40 | ); 41 | } 42 | 43 | match ioid { 44 | -1 => panic!("unable to invoke fn {}", name), 45 | _ => Io::<$output>::new(ioid as u32), 46 | } 47 | } 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /crates/core/iomod/iomod.capnp: -------------------------------------------------------------------------------- 1 | @0xdefbefb7e7579c48; 2 | 3 | interface Agent { 4 | invoke @0 (coordinates: Text, input: Data) -> (result: Data); 5 | } 6 | 7 | interface Iomod { 8 | invoke @0 (coordinates: Text, input: Data) -> (result: Data); 9 | } 10 | 11 | interface Registry { 12 | register @0 (coordinates: Text, iomod: Iomod); 13 | } 14 | -------------------------------------------------------------------------------- /crates/core/iomod/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Host-side IOmod RPC protocol implementation 2 | 3 | use std::cell::RefCell; 4 | use std::collections::HashMap; 5 | use std::rc::Rc; 6 | 7 | use capnp::capability::Promise; 8 | use capnp::{Error, ErrorKind}; 9 | use futures::future::BoxFuture; 10 | use futures_util::TryFutureExt; 11 | use tokio::sync::mpsc; 12 | 13 | use crate::iomod_capnp::{agent, iomod}; 14 | 15 | pub mod iomod_capnp; 16 | pub mod macros; 17 | pub mod package; 18 | pub mod registry; 19 | 20 | pub struct CallRequest { 21 | pub coords: String, 22 | pub input: Vec, 23 | pub responder: mpsc::Sender, 24 | } 25 | 26 | pub struct CallResponse { 27 | pub coords: String, 28 | pub payload: Vec, 29 | } 30 | 31 | pub type CallChannel = (mpsc::Sender, mpsc::Receiver); 32 | 33 | pub type Call = fn(Vec) -> F; 34 | 35 | pub struct CallPtr 36 | where 37 | F: std::future::Future> + Send, 38 | { 39 | call: Call, 40 | } 41 | 42 | impl CallPtr 43 | where 44 | F: std::future::Future> + Send, 45 | { 46 | pub fn new(call: Call) -> Self { 47 | Self { call } 48 | } 49 | } 50 | 51 | pub struct CallMap<'a> { 52 | pub map: HashMap<&'a str, CallPtr>>>, 53 | } 54 | 55 | impl<'a> CallMap<'a> { 56 | pub fn new() -> Self { 57 | Self { 58 | map: HashMap::default(), 59 | } 60 | } 61 | 62 | pub fn get(&self, coords: String, with_input: Vec) -> BoxFuture<'a, Vec> { 63 | let call = self.map[coords.as_str()].call; 64 | call(with_input) 65 | } 66 | } 67 | 68 | pub struct Iomod { 69 | tx: mpsc::Sender, 70 | } 71 | 72 | impl Iomod { 73 | pub fn new(tx: mpsc::Sender) -> Self { 74 | Self { tx } 75 | } 76 | } 77 | 78 | impl iomod::Server for Iomod { 79 | fn invoke( 80 | &mut self, 81 | params: iomod::InvokeParams, 82 | mut results: iomod::InvokeResults, 83 | ) -> Promise<(), Error> { 84 | let tx = self.tx.clone(); 85 | 86 | Promise::from_future(async move { 87 | let coords = params.get().unwrap().get_coordinates().unwrap().to_owned(); 88 | let input = params.get().unwrap().get_input().unwrap(); 89 | 90 | let mut channel: (mpsc::Sender, mpsc::Receiver) = 91 | mpsc::channel(100); 92 | 93 | tx.send(CallRequest { 94 | coords, 95 | input: Vec::from(input), 96 | responder: channel.0.clone(), 97 | }) 98 | .and_then(|_| async move { 99 | // wait for response from executor thread 100 | if let Some(response) = channel.1.recv().await { 101 | results.get().set_result(response.payload.as_slice()); 102 | } 103 | 104 | Ok(()) 105 | }) 106 | .or_else(|why| async move { 107 | Err(capnp::Error { 108 | kind: ErrorKind::Failed, 109 | description: why.to_string(), 110 | }) 111 | }) 112 | .await 113 | }) 114 | } 115 | } 116 | 117 | pub struct Agent { 118 | iomod_client: Rc>, 119 | } 120 | 121 | impl Agent { 122 | pub fn new(iomod_client: Rc>) -> Self { 123 | Self { iomod_client } 124 | } 125 | } 126 | 127 | impl agent::Server for Agent { 128 | fn invoke( 129 | &mut self, 130 | params: agent::InvokeParams, 131 | mut results: agent::InvokeResults, 132 | ) -> Promise<(), capnp::Error> { 133 | let client = self.iomod_client.clone(); 134 | 135 | Promise::from_future(async move { 136 | let mut invoke = client.borrow_mut().invoke_request(); 137 | invoke 138 | .get() 139 | .set_coordinates(params.get().unwrap().get_coordinates().unwrap()); 140 | invoke 141 | .get() 142 | .set_input(params.get().unwrap().get_input().unwrap()); 143 | 144 | let invoke_response = invoke.send().promise.await.unwrap(); 145 | results 146 | .get() 147 | .set_result(invoke_response.get().unwrap().get_result().unwrap()); 148 | 149 | Ok(()) 150 | }) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /crates/core/iomod/src/macros.rs: -------------------------------------------------------------------------------- 1 | pub static CORE_VERSION: &str = env!("CARGO_PKG_VERSION"); 2 | pub static RUSTC_VERSION: &str = env!("RUSTC_VERSION"); 3 | 4 | #[macro_export] 5 | macro_rules! iomod { 6 | ($ip:expr, $org:ident.$ns:ident.$name:ident => $calls:tt) => { 7 | use assemblylift_core_iomod::iomod_capnp::*; 8 | use assemblylift_core_iomod::{ 9 | Call, CallChannel, CallMap, CallPtr, CallRequest, CallResponse, Iomod, 10 | }; 11 | use capnp_rpc::{rpc_twoparty_capnp, twoparty, RpcSystem}; 12 | use futures::{AsyncReadExt, FutureExt}; 13 | use tokio::net::TcpStream; 14 | use tokio::sync::mpsc; 15 | 16 | let org = stringify!($org); 17 | let ns = stringify!($ns); 18 | let name = stringify!($name); 19 | 20 | let iomod_coords = format!("{}.{}.{}", org, ns, name); 21 | println!("Starting AssemblyLift IO module {}", iomod_coords); 22 | 23 | let mut call_map: CallMap = $crate::__calls!($calls); 24 | let mut call_channel: CallChannel = mpsc::channel(100); 25 | 26 | let stream = TcpStream::connect(format!("{}:13555", $ip)).await.unwrap(); 27 | stream.set_nodelay(true).unwrap(); 28 | 29 | let (reader, writer) = tokio_util::compat::TokioAsyncReadCompatExt::compat(stream).split(); 30 | 31 | let rpc_network = Box::new(twoparty::VatNetwork::new( 32 | reader, 33 | writer, 34 | rpc_twoparty_capnp::Side::Client, 35 | Default::default(), 36 | )); 37 | 38 | let mut rpc_system = RpcSystem::new(rpc_network, None); 39 | let registry: registry::Client = rpc_system.bootstrap(rpc_twoparty_capnp::Side::Server); 40 | 41 | let local = tokio::task::LocalSet::new(); 42 | local 43 | .run_until(async move { 44 | let rpc_task = tokio::task::spawn_local(Box::pin(rpc_system.map(|_| ()))); 45 | 46 | let mut register = registry.register_request(); 47 | register 48 | .get() 49 | .set_iomod(capnp_rpc::new_client(Iomod::new(call_channel.0.clone()))); 50 | register.get().set_coordinates(iomod_coords.as_str()); 51 | register.send().promise.await.unwrap(); 52 | 53 | let call_task = tokio::task::spawn_local(async move { 54 | while let Some(mut call) = call_channel.1.recv().await { 55 | let coords = call.coords.as_str(); 56 | let call_ptr = call_map.get(String::from(coords), call.input); 57 | 58 | let response = call_ptr.await; 59 | 60 | if let Err(why) = call 61 | .responder 62 | .send(CallResponse { 63 | coords: String::from(coords), 64 | payload: response, 65 | }) 66 | .await 67 | { 68 | println!("ERROR {}", why) 69 | } 70 | } 71 | }); 72 | 73 | let (_, _) = tokio::join!(rpc_task, call_task); 74 | }) 75 | .await; 76 | }; 77 | } 78 | 79 | #[macro_export] 80 | #[doc(hidden)] 81 | macro_rules! __calls { 82 | ({ $( $call_name:ident => $call:expr ),* $(,)? }) => {{ 83 | let mut call_map = CallMap::new(); 84 | $( 85 | let call_name = stringify!($call_name); 86 | call_map.map.insert(call_name, CallPtr::new($call)); 87 | )* 88 | call_map 89 | }}; 90 | } 91 | -------------------------------------------------------------------------------- /crates/core/iomod/src/package.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Deserialize)] 4 | pub struct IomodManifest { 5 | pub iomod: ManifestHeader, 6 | pub process: Process, 7 | } 8 | 9 | impl IomodManifest { 10 | pub fn read(path: &std::path::PathBuf) -> Result { 11 | match std::fs::read_to_string(path) { 12 | Ok(contents) => Ok(Self::from(contents)), 13 | Err(why) => Err(std::io::Error::new( 14 | std::io::ErrorKind::Other, 15 | why.to_string(), 16 | )), 17 | } 18 | } 19 | } 20 | 21 | impl From for IomodManifest { 22 | fn from(string: String) -> Self { 23 | match toml::from_str(&string) { 24 | Ok(manifest) => manifest, 25 | Err(why) => panic!("error parsing IomodManifest: {}", why.to_string()), 26 | } 27 | } 28 | } 29 | 30 | #[derive(Deserialize)] 31 | pub struct ManifestHeader { 32 | pub coordinates: String, 33 | pub version: String, 34 | } 35 | 36 | #[derive(Deserialize)] 37 | pub struct Process { 38 | pub entrypoint: String, 39 | pub arguments: Option>, 40 | } 41 | -------------------------------------------------------------------------------- /crates/core/src/buffers.rs: -------------------------------------------------------------------------------- 1 | //! AssemblyLift WASM Buffers 2 | //! See [core-buffers doc](../../docs/core-buffers.md) for more details 3 | 4 | use std::collections::HashMap; 5 | 6 | pub struct IoBuffer { 7 | buffers: HashMap>, 8 | } 9 | 10 | impl IoBuffer { 11 | pub fn new() -> Self { 12 | Self { 13 | buffers: Default::default(), 14 | } 15 | } 16 | 17 | pub fn set(&mut self, ioid: usize, bytes: Vec) -> usize { 18 | self.buffers.insert(ioid, bytes.clone()); 19 | bytes.len() 20 | } 21 | 22 | pub fn get(&self, ioid: usize) -> Vec { 23 | self.buffers.get(&ioid).unwrap().clone() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /crates/core/src/jwt/README.md: -------------------------------------------------------------------------------- 1 | This is a fork of https://github.com/jfbilodeau/jwks-client; included here directly to avoid 2 | another git Cargo dependency while we see if the changes will be accepted upstream :) 3 | -------------------------------------------------------------------------------- /crates/core/src/jwt/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | use std::fmt; 3 | 4 | #[derive(Debug, PartialEq)] 5 | pub struct Error { 6 | /// Debug message associated with error 7 | pub msg: &'static str, 8 | pub typ: Type, 9 | } 10 | 11 | impl Display for Error { 12 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 13 | write!(f, "{:?}: {}", self.typ, self.msg) 14 | } 15 | } 16 | 17 | impl std::error::Error for Error { 18 | } 19 | 20 | /// Type of error encountered 21 | #[derive(Debug, PartialEq)] 22 | pub enum Type { 23 | /// Token is invalid 24 | /// For example, the format of the token is not "HEADER.PAYLOAD.SIGNATURE" 25 | Invalid, 26 | /// Token has expired 27 | Expired, 28 | /// Not Before (nbf) is set and it's too early to use the token 29 | Early, 30 | /// Problem with certificate 31 | Certificate, 32 | /// Problem with key 33 | Key, 34 | /// Could not download key set 35 | Connection, 36 | /// Problem with JWT header 37 | Header, 38 | /// Problem with JWT payload 39 | Payload, 40 | /// Problem with JWT signature 41 | Signature, 42 | /// Internal problem (Signals a serious bug or fatal error) 43 | Internal, 44 | } 45 | 46 | pub(crate) fn err(msg: &'static str, typ: Type) -> Error { 47 | Error { msg, typ } 48 | } 49 | 50 | pub(crate) fn err_inv(msg: &'static str) -> Error { 51 | err(msg, Type::Invalid) 52 | } 53 | 54 | pub(crate) fn err_exp(msg: &'static str) -> Error { 55 | err(msg, Type::Expired) 56 | } 57 | 58 | pub(crate) fn err_nbf(msg: &'static str) -> Error { 59 | err(msg, Type::Early) 60 | } 61 | 62 | pub(crate) fn err_cer(msg: &'static str) -> Error { 63 | err(msg, Type::Certificate) 64 | } 65 | 66 | pub(crate) fn err_key(msg: &'static str) -> Error { 67 | err(msg, Type::Key) 68 | } 69 | 70 | pub(crate) fn err_con(msg: &'static str) -> Error { 71 | err(msg, Type::Connection) 72 | } 73 | 74 | pub(crate) fn err_hea(msg: &'static str) -> Error { 75 | err(msg, Type::Header) 76 | } 77 | 78 | pub(crate) fn err_pay(msg: &'static str) -> Error { 79 | err(msg, Type::Payload) 80 | } 81 | 82 | pub(crate) fn err_sig(msg: &'static str) -> Error { 83 | err(msg, Type::Signature) 84 | } 85 | 86 | pub(crate) fn err_int(msg: &'static str) -> Error { 87 | err(msg, Type::Internal) 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests {} 92 | -------------------------------------------------------------------------------- /crates/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use wasmtime::AsContextMut; 2 | 3 | pub mod buffers; 4 | pub mod jwt; 5 | pub mod policy_manager; 6 | pub mod threader; 7 | pub mod wasm; 8 | 9 | pub trait RuntimeAbi: SecretsAbi 10 | where 11 | S: Clone + Send + Sized + 'static, 12 | { 13 | fn success(status_tx: crate::wasm::StatusTx, response: Vec, request_id: Option); 14 | fn failure(status_tx: crate::wasm::StatusTx, response: Vec, request_id: Option); 15 | } 16 | 17 | pub trait SecretsAbi: KeysAbi { 18 | fn get_secret(id: String) -> anyhow::Result>; 19 | fn set_secret(id: String, value: Vec, key_id: Option) -> anyhow::Result<()>; 20 | } 21 | 22 | pub trait KeysAbi { 23 | fn encrypt(id: String, plaintext: Vec) -> anyhow::Result>; 24 | fn decrypt(id: String, ciphertext: Vec) -> anyhow::Result>; 25 | } 26 | -------------------------------------------------------------------------------- /crates/core/src/policy_manager.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use anyhow::anyhow; 4 | use tracing::{debug, error}; 5 | 6 | pub struct PolicyManager { 7 | policies: BTreeMap, 8 | } 9 | 10 | impl PolicyManager { 11 | pub fn new() -> Self { 12 | Self { 13 | policies: Default::default(), 14 | } 15 | } 16 | 17 | pub fn eval(&mut self, policy_id: String, data: String, input: String) -> anyhow::Result { 18 | let policy = match self.policies.get_mut(&*policy_id) { 19 | Some(policy) => policy, 20 | None => return Err(anyhow!("no policy to eval with id={}", policy_id)), 21 | }; 22 | policy.set_data(&serde_json::from_str::(&data).unwrap())?; 23 | let eps = policy 24 | .entrypoints() 25 | .map(|s| s.to_string()) 26 | .collect::>(); 27 | let entrypoint = eps.get(0).expect("no entrypoint in module").replace("/", "."); 28 | debug!("evaluating entrypoint {} in policy {}", entrypoint, &policy_id); 29 | let result: serde_json::Value = match policy.eval(&entrypoint, &serde_json::from_str::(&input).unwrap()) { 30 | Ok(r) => r, 31 | Err(e) => { 32 | error!("{}", e.to_string()); 33 | return Err(anyhow!(e.to_string())); 34 | } 35 | }; 36 | 37 | Ok(result.to_string()) 38 | } 39 | 40 | pub fn load_policy_bundle( 41 | &mut self, 42 | policy_id: String, 43 | bundle_bytes: &[u8], 44 | ) -> anyhow::Result> { 45 | let bundle = match opa::bundle::Bundle::from_bytes(bundle_bytes) { 46 | Ok(bundle) => bundle, 47 | Err(_) => return Err(anyhow!("invalid bundle")), 48 | }; 49 | let policy = opa::wasm::Opa::new().on_println(|s| println!("OPA {:?}", s)).build_from_bundle(&bundle).unwrap(); 50 | let entrypoints = policy 51 | .entrypoints() 52 | .map(|s| s.to_string()) 53 | .collect::>(); 54 | self.policies.insert(policy_id, policy); 55 | 56 | Ok(entrypoints) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/core/src/threader.rs: -------------------------------------------------------------------------------- 1 | //! The Threader Runtime 2 | //! "Threader" is the interface between the Wasmtime runtime and the IOmod RPC network. 3 | //! See [core-threader doc](../../docs/core-threader.md) for more details. 4 | 5 | use std::collections::HashMap; 6 | use std::sync::{Arc, Mutex}; 7 | 8 | use tokio::sync::mpsc; 9 | 10 | use assemblylift_core_iomod::registry::{RegistryChannelMessage, RegistryTx}; 11 | 12 | use super::buffers::IoBuffer; 13 | use super::wasm::asml_io; 14 | 15 | pub type IoId = u32; 16 | 17 | pub struct Threader { 18 | io_memory: Arc>, 19 | registry_tx: RegistryTx, 20 | _phantom: std::marker::PhantomData, 21 | } 22 | 23 | impl Threader 24 | where 25 | S: Clone + Send + Sized + 'static, 26 | { 27 | /// Create a new Threader instance with the provided sender `tx` 28 | pub fn new(tx: RegistryTx) -> Self { 29 | Threader { 30 | io_memory: Arc::new(Mutex::new(IoMemory::new())), 31 | registry_tx: tx, 32 | _phantom: std::marker::PhantomData::default(), 33 | } 34 | } 35 | 36 | /// Issue an unused IOID for a new IOmod call 37 | pub fn next_ioid(&mut self) -> Option { 38 | match self.io_memory.clone().lock() { 39 | Ok(mut memory) => memory.next_id(), 40 | Err(_) => None, 41 | } 42 | } 43 | 44 | /// Poll the runtime for the completion status of call associated with `ioid` 45 | pub fn poll(&mut self, ioid: IoId) -> Option> { 46 | match self.io_memory.clone().lock() { 47 | Ok(memory) => match memory.poll(ioid) { 48 | true => Some(memory.buffer.get(ioid as usize)), 49 | false => None, 50 | }, 51 | Err(_) => None, 52 | } 53 | } 54 | 55 | /// Invoke the IOmod call at `method_path` with `method_input`, and assign it id `ioid`. 56 | /// A task is spawned on the tokio runtime which runs until the IOmod call responds. 57 | pub fn invoke( 58 | &mut self, 59 | method_path: &str, 60 | method_input: Vec, 61 | ioid: IoId, 62 | ) -> Result<(), asml_io::IoError> { 63 | let io_memory = self.io_memory.clone(); 64 | 65 | let coords = method_path.split(".").collect::>(); 66 | if coords.len() != 4 { 67 | tracing::error!("io invoke failed: malformed module coordinates"); 68 | return Err(asml_io::IoError::InvalidCoords); 69 | } 70 | 71 | let iomod_coords = format!("{}.{}.{}", coords[0], coords[1], coords[2]); 72 | let method_name = format!("{}", coords[3]); 73 | 74 | let registry_tx = self.registry_tx.clone(); 75 | let (local_tx, mut local_rx) = mpsc::channel(100); 76 | 77 | tokio::spawn(async move { 78 | registry_tx 79 | .send(RegistryChannelMessage { 80 | iomod_coords, 81 | method_name, 82 | payload_type: "IOMOD_REQUEST", 83 | payload: method_input, 84 | responder: Some(local_tx.clone()), 85 | }) 86 | .await 87 | .unwrap(); 88 | }); 89 | 90 | tokio::spawn(async move { 91 | if let Some(response) = local_rx.recv().await { 92 | io_memory 93 | .lock() 94 | .unwrap() 95 | .handle_response(response.payload, ioid); 96 | } 97 | }); 98 | 99 | Ok(()) 100 | } 101 | } 102 | 103 | struct IoMemory { 104 | next_id: IoId, 105 | buffer: IoBuffer, 106 | io_status: HashMap, 107 | } 108 | 109 | impl IoMemory { 110 | fn new() -> Self { 111 | IoMemory { 112 | next_id: 1, // id 0 is reserved (null) 113 | buffer: IoBuffer::new(), 114 | io_status: Default::default(), 115 | } 116 | } 117 | 118 | fn next_id(&mut self) -> Option { 119 | let next_id = self.next_id.clone(); 120 | self.next_id += 1; 121 | self.io_status.insert(next_id, false); 122 | Some(next_id) 123 | } 124 | 125 | fn poll(&self, ioid: IoId) -> bool { 126 | match self.io_status.get(&ioid) { 127 | Some(status) => *status, 128 | None => false, 129 | } 130 | } 131 | 132 | fn handle_response(&mut self, response: Vec, ioid: IoId) { 133 | self.buffer.set(ioid as usize, response.clone()); 134 | self.io_status.insert(ioid, true); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /crates/core/src/wasm/cache.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use anyhow::anyhow; 4 | use serde::{Serialize, de::DeserializeOwned}; 5 | use tracing::debug; 6 | 7 | pub struct Cache { 8 | data: BTreeMap>, 9 | } 10 | 11 | impl Cache { 12 | pub fn new() -> Self { 13 | Self { 14 | data: Default::default(), 15 | } 16 | } 17 | 18 | pub fn put(&mut self, key: &str, object: &T) -> anyhow::Result<()> { 19 | debug!("storing cache object @ {}", key); 20 | let encoded: Vec = bincode::serialize(object) 21 | .map_err(|e| anyhow!(e.to_string()))?; 22 | self.data.insert(key.to_string(), encoded); 23 | Ok(()) 24 | } 25 | 26 | pub fn get(&self, key: &str) -> anyhow::Result> { 27 | let encoded = match self.data.get(key) { 28 | Some(e) => { 29 | debug!("cache HIT getting object @ {}", key); 30 | e 31 | }, 32 | None => { 33 | debug!("cache MISS getting object @ {}", key); 34 | return Ok(None) 35 | }, 36 | }; 37 | let decoded: T = bincode::deserialize(&encoded) 38 | .map_err(|e| anyhow!(e.to_string()))?; 39 | Ok(Some(decoded)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/core/wit/assemblylift/assemblylift.wit: -------------------------------------------------------------------------------- 1 | package akkoro:assemblylift; 2 | 3 | interface asml-io { 4 | type ioid = u32; 5 | 6 | enum io-error { 7 | coords-not-found, 8 | invalid-coords, 9 | invalid-ioid, 10 | } 11 | 12 | enum poll-error { 13 | not-ready, 14 | invalid-ioid, 15 | } 16 | 17 | invoke: func(path: string, input: string) -> result; 18 | poll: func(ioid: ioid) -> result, poll-error>; 19 | } 20 | 21 | interface asml-rt { 22 | type bytes = list; 23 | 24 | enum log-level { 25 | debug, 26 | trace, 27 | info, 28 | warn, 29 | error 30 | } 31 | 32 | success: func(response: bytes); 33 | failure: func(response: bytes); 34 | log: func(level: log-level, context: string, message: string); 35 | get-input: func() -> list; 36 | } 37 | 38 | world assemblylift { 39 | import asml-io; 40 | import asml-rt; 41 | } 42 | -------------------------------------------------------------------------------- /crates/core/wit/jwt/jwt.wit: -------------------------------------------------------------------------------- 1 | package akkoro:jwt; 2 | 3 | interface decoder { 4 | enum jwt-error { 5 | invalid-token, 6 | invalid-jwks, 7 | } 8 | 9 | record validation-params { 10 | iss: string, 11 | aud: string, 12 | } 13 | 14 | record verify-result { 15 | valid: bool, 16 | } 17 | 18 | decode-verify: func(token: string, jwks: string, params: validation-params) -> result; 19 | } 20 | 21 | world jwt { 22 | import decoder; 23 | } 24 | -------------------------------------------------------------------------------- /crates/core/wit/opa/opa.wit: -------------------------------------------------------------------------------- 1 | package akkoro:opa; 2 | 3 | interface module { 4 | enum policy-error { 5 | invalid-wasm, 6 | no-entrypoint, 7 | } 8 | 9 | record policy { 10 | id: string, 11 | entrypoints: list, 12 | } 13 | 14 | new-policy: func(bytes: list) -> result; 15 | eval: func(id: string, data: string, input: string) -> string; 16 | } 17 | 18 | world opa { 19 | import module; 20 | } 21 | -------------------------------------------------------------------------------- /crates/core/wit/secrets/secrets.wit: -------------------------------------------------------------------------------- 1 | package akkoro:secrets; 2 | 3 | /// Provides interface to a secrets store 4 | interface secret-storage { 5 | // TODO this should be a capability handle or `resource` 6 | type key = string; 7 | 8 | record secret { 9 | id: string, 10 | value: option>, 11 | // TODO metadata and caps 12 | } 13 | 14 | /// Return the secret value associated with the handle 15 | get-secret-value: func(id: string) -> result; 16 | 17 | /// Set the secret value associated with the handle 18 | set-secret-value: func(id: string, value: list, key: key) -> result; 19 | 20 | enum error { 21 | success, 22 | invalid-argument, 23 | forbidden 24 | } 25 | } 26 | 27 | world secrets { 28 | import secret-storage; 29 | } 30 | -------------------------------------------------------------------------------- /crates/generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-generator" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1" 8 | clap = { version = "4.4", features = ["cargo"] } 9 | handlebars = "4.4" 10 | itertools = "0.11" 11 | jsonpath_lib = "0.3.0" 12 | # once_cell = "1.18" 13 | path_abs = "0.5" 14 | serde = { version = "1.0", features = ["derive"] } 15 | serde_json = "1.0" 16 | toml = "0.7" 17 | typetag = "0.2" 18 | walkdir = "2.4" 19 | 20 | assemblylift-tools = { path = "../tools" } 21 | -------------------------------------------------------------------------------- /crates/generator/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::Write, path::PathBuf}; 2 | 3 | use handlebars::handlebars_helper; 4 | 5 | pub mod context; 6 | pub mod projectfs; 7 | pub mod providers; 8 | pub mod toml; 9 | 10 | handlebars_helper!(concat: |s1: String, s2: String| format!("{}{}", s1, s2)); 11 | handlebars_helper!(snake_case: |s: String| s.replace("-", "_").replace(".", "_")); 12 | 13 | pub type CastResult = std::result::Result; 14 | pub type Map = std::collections::HashMap; 15 | pub type StringMap = Map; 16 | pub type Options = StringMap; 17 | 18 | #[derive(Debug)] 19 | pub struct CastError(pub String); 20 | 21 | #[derive(Clone, Debug, PartialEq)] 22 | pub enum ContentType { 23 | HCL, 24 | Dockerfile, 25 | } 26 | 27 | /// A `Fragment` is the output of a `cast` operation. Its contents may be part of, or the entirety of, 28 | /// some document which will be output at `write_path`. 29 | #[derive(Debug, Clone)] 30 | pub struct Fragment { 31 | pub content_type: ContentType, 32 | pub content: String, 33 | pub write_path: PathBuf, 34 | } 35 | 36 | impl Fragment { 37 | pub fn write(&self) -> Result<(), CastError> { 38 | let prefix = self 39 | .write_path 40 | .parent() 41 | .ok_or(CastError("write_path has no parent".into()))?; 42 | std::fs::create_dir_all(prefix).map_err(|e| { 43 | CastError(format!( 44 | "could not create directories {}: {}", 45 | prefix.to_string_lossy(), 46 | e.to_string() 47 | )) 48 | })?; 49 | let mut fout = File::create(&self.write_path).map_err(|e| { 50 | CastError(format!( 51 | "could not create file {}: {}", 52 | self.write_path.to_string_lossy(), 53 | e.to_string() 54 | )) 55 | })?; 56 | fout.write_all(self.content.as_bytes()) 57 | .map_err(|e| CastError(format!("could not write contents: {}", e.to_string())))?; 58 | println!("📄 > Wrote {}", self.write_path.to_string_lossy()); 59 | 60 | Ok(()) 61 | } 62 | } 63 | 64 | pub fn concat_cast(accum: CastResult>, v: CastResult>) -> CastResult> { 65 | let mut out = Vec::new(); 66 | out.append(&mut accum?); 67 | out.append(&mut v?); 68 | Ok(out) 69 | } 70 | -------------------------------------------------------------------------------- /crates/generator/src/projectfs.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::{Path, PathBuf}; 3 | use std::rc::Rc; 4 | 5 | use path_abs::{PathAbs, PathDir}; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use super::toml; 9 | 10 | #[derive(Serialize, Deserialize)] 11 | pub struct Project { 12 | pub name: String, 13 | project_path: PathBuf, 14 | service_path: PathBuf, 15 | } 16 | 17 | pub struct ServiceDir { 18 | dir: PathBuf, // FIXME why is this boxed?? 19 | } 20 | 21 | impl ServiceDir { 22 | pub fn new(dir: PathBuf) -> Self { 23 | ServiceDir { dir } 24 | } 25 | 26 | pub fn dir(&self) -> PathBuf { 27 | self.dir.clone() 28 | } 29 | 30 | pub fn function_dir(&self, name: String) -> PathBuf { 31 | PathBuf::from(format!( 32 | "{}/functions/{}", 33 | self.dir.clone().into_os_string().into_string().unwrap(), 34 | name 35 | )) 36 | } 37 | } 38 | 39 | pub struct NetDir { 40 | dir: PathBuf, 41 | } 42 | 43 | impl NetDir { 44 | pub fn new(project_path: PathBuf) -> Self { 45 | let mut net_path = project_path; 46 | net_path.push("net"); 47 | Self { dir: net_path } 48 | } 49 | 50 | pub fn service_dir(&self, name: &str) -> ServiceDir { 51 | let path = PathBuf::from(&*format!( 52 | "{}/services/{}", 53 | self.dir.clone().into_os_string().into_string().unwrap(), 54 | name 55 | )); 56 | ServiceDir::new(path) 57 | } 58 | 59 | pub fn runtime_dir(&self) -> PathBuf { 60 | PathBuf::from(&*format!( 61 | "{}/runtime", 62 | self.dir.clone().into_os_string().into_string().unwrap(), 63 | )) 64 | } 65 | } 66 | 67 | impl Project { 68 | pub fn new(name: String, project_path: Option) -> Rc { 69 | let project_path = match project_path { 70 | Some(path) => { 71 | if !Path::exists(&*path.clone()) { 72 | fs::create_dir(path.clone()).expect(&*format!( 73 | "could not create dir {}", 74 | path.clone().into_os_string().into_string().unwrap() 75 | )); 76 | } 77 | PathBuf::from( 78 | PathAbs::from( 79 | PathDir::new(path.clone()) 80 | .expect(&*format!("couldn't make PathDir for {:?}", path.clone())), 81 | ) 82 | .as_path(), 83 | ) 84 | } 85 | 86 | None => { 87 | let path = format!("./{}", name); 88 | if !Path::exists(path.as_ref()) { 89 | fs::create_dir(path.clone()) 90 | .expect(&*format!("could not create dir {}", path.clone())); 91 | } 92 | PathBuf::from( 93 | PathAbs::from(PathDir::new(path.clone()).unwrap()).as_path(), 94 | ) 95 | } 96 | }; 97 | 98 | let path = format!( 99 | "{}/services", 100 | project_path.clone().into_os_string().into_string().unwrap() 101 | ); 102 | if !Path::exists(path.as_ref()) { 103 | fs::create_dir(path.clone()).expect(&*format!("could not create dir {}", path.clone())); 104 | } 105 | let service_path = PathBuf::from( 106 | PathAbs::from(PathDir::new(path.clone()).unwrap()).as_path(), 107 | ); 108 | 109 | Rc::new(Self { 110 | name, 111 | project_path, 112 | service_path, 113 | }) 114 | } 115 | 116 | pub fn service_dir(&self, name: String) -> ServiceDir { 117 | let path = PathBuf::from(&*format!( 118 | "{}/{}", 119 | self.service_path 120 | .clone() 121 | .into_os_string() 122 | .into_string() 123 | .unwrap(), 124 | name 125 | )); 126 | ServiceDir::new(path) 127 | } 128 | 129 | pub fn net_dir(&self) -> NetDir { 130 | NetDir::new(self.project_path.clone()) 131 | } 132 | 133 | pub fn dir(&self) -> PathBuf { 134 | self.project_path.clone() 135 | } 136 | } 137 | 138 | pub fn locate_asml_manifest() -> Option<(toml::asml::Manifest, PathBuf)> { 139 | use walkdir::WalkDir; 140 | 141 | let mut path: Option = None; 142 | for entry in WalkDir::new(".").into_iter().filter_map(|e| e.ok()) { 143 | let file = entry.file_name().to_string_lossy(); 144 | if file.eq_ignore_ascii_case("assemblylift.toml") { 145 | path = Some(PathBuf::from(file.into_owned())); 146 | break; 147 | } 148 | } 149 | 150 | match path { 151 | Some(path) => { 152 | let canonical_path = fs::canonicalize(path.clone()).unwrap(); 153 | Some(( 154 | toml::asml::Manifest::read(&PathBuf::from(canonical_path.clone())) 155 | .expect("could not read assemblylift.toml"), 156 | PathBuf::from(canonical_path.clone()), 157 | )) 158 | } 159 | None => None, 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /crates/generator/src/providers/api_gateway/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use handlebars::Handlebars; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::{ 8 | context::Service, providers::aws_lambda, snake_case, CastResult, ContentType, Fragment, Options, 9 | }; 10 | 11 | use super::{ 12 | GatewayProvider, ContainerRegistryProvider, DnsProvider, FunctionProvider, Provider, 13 | ServiceProvider, Platform, 14 | }; 15 | 16 | pub fn provider_name() -> String { 17 | "aws-apigw".into() 18 | } 19 | 20 | #[derive(Serialize, Deserialize)] 21 | pub struct ApiGatewayProvider { 22 | #[serde(default = "provider_name")] 23 | name: String, 24 | options: Options, 25 | platform: Option, 26 | } 27 | 28 | impl ApiGatewayProvider { 29 | pub fn new(options: Options, platform: Option) -> Box { 30 | Box::new(Self { 31 | name: provider_name(), 32 | options, 33 | platform, 34 | }) 35 | } 36 | } 37 | 38 | #[typetag::serde] 39 | impl Provider for ApiGatewayProvider { 40 | fn name(&self) -> String { 41 | self.name.clone() 42 | } 43 | 44 | fn platform(&self) -> Option { 45 | self.platform.clone() 46 | } 47 | 48 | fn compatible_platforms(&self) -> Vec { 49 | vec!["aws".into()] 50 | } 51 | 52 | fn options(&self) -> Options { 53 | self.options.clone() 54 | } 55 | 56 | fn set_option(&mut self, key: &str, value: &str) { 57 | self.options.insert(key.into(), value.into()).unwrap(); 58 | } 59 | 60 | fn boot(&self) -> Result<()> { 61 | Ok(()) 62 | } 63 | 64 | fn is_booted(&self) -> bool { 65 | true 66 | } 67 | 68 | fn as_service_provider(&self) -> Result<&dyn ServiceProvider> { 69 | Err(anyhow!("{} is not a ServiceProvider", self.name())) 70 | } 71 | 72 | fn as_function_provider(&self) -> Result<&dyn FunctionProvider> { 73 | Err(anyhow!("{} is not a FunctionProvider", self.name())) 74 | } 75 | 76 | fn as_gateway_provider(&self) -> Result<&dyn GatewayProvider> { 77 | Ok(self) 78 | } 79 | 80 | fn as_dns_provider(&self) -> Result<&dyn DnsProvider> { 81 | Err(anyhow!("{} is not a DnsProvider", self.name())) 82 | } 83 | 84 | fn as_container_registry_provider(&self) -> Result<&dyn ContainerRegistryProvider> { 85 | Err(anyhow!( 86 | "{} is not a ContainerRegistryProvider", 87 | self.name() 88 | )) 89 | } 90 | } 91 | 92 | impl GatewayProvider for ApiGatewayProvider { 93 | fn cast_service(&self, service: &Service) -> CastResult> { 94 | let mut fragments: Vec = Vec::new(); 95 | 96 | let mut hbs = Handlebars::new(); 97 | hbs.register_helper("snake_case", Box::new(snake_case)); 98 | hbs.register_template_string("root", include_str!("templates/api_impl.tf.handlebars")) 99 | .unwrap(); 100 | 101 | let api_fragment = Fragment { 102 | content_type: ContentType::HCL, 103 | content: hbs.render("root", &service.as_json().unwrap()).unwrap(), 104 | write_path: PathBuf::from(format!( 105 | "net/services/{}/infra/{}/api.tf", 106 | service.name, 107 | self.name(), 108 | )), 109 | }; 110 | 111 | fragments.append(&mut vec![api_fragment]); 112 | 113 | Ok(fragments) 114 | } 115 | 116 | fn compatible_service_providers(&self) -> Vec { 117 | vec![aws_lambda::provider_name()] 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /crates/generator/src/providers/api_gateway/templates/api_impl.tf.handlebars: -------------------------------------------------------------------------------- 1 | variable project_name { 2 | type = string 3 | } 4 | 5 | variable project_path { 6 | type = string 7 | } 8 | 9 | variable service_name { 10 | type = string 11 | } 12 | 13 | terraform { 14 | required_providers { 15 | aws = { 16 | source = "hashicorp/aws" 17 | version = "~> 5.0" 18 | } 19 | } 20 | } 21 | 22 | data aws_region current {} 23 | data aws_caller_identity current {} 24 | 25 | resource aws_apigatewayv2_api http_api { 26 | provider = aws 27 | name = "asml-${var.project_name}-${var.service_name}" 28 | protocol_type = "HTTP" 29 | } 30 | 31 | resource aws_apigatewayv2_stage default { 32 | provider = aws 33 | api_id = aws_apigatewayv2_api.http_api.id 34 | name = "$default" 35 | auto_deploy = true 36 | } 37 | 38 | {{#if this.domain}} 39 | resource aws_apigatewayv2_api_mapping {{snake_case name}} { 40 | provider = aws 41 | api_id = aws_apigatewayv2_api.http_api.id 42 | domain_name = "{{#unless this.is_root}}${var.service_name}.{{/unless}}{{#unless this.domain.map_to_root}}${var.project_name}.{{/unless}}{{this.domain.dns_name}}" 43 | stage = aws_apigatewayv2_stage.default.name 44 | } 45 | {{/if}} 46 | 47 | {{#each functions}} 48 | {{#if this.authorizer}}resource {{snake_case this.name}}_aws_apigatewayv2_authorizer {{this.authorizer.id}} { 49 | provider = aws 50 | 51 | api_id = aws_apigatewayv2_api.http_api.id 52 | authorizer_type = "{{#if (eq this.authorizer.type "iam")}}REQUEST{{else}}{{this.authorizer.type}}{{/if}}" 53 | identity_sources = ["$request.header.Authorization"] 54 | name = "{{this.authorizer.id}}" 55 | 56 | {{#if this.authorizer.jwt_config}}jwt_configuration { 57 | audience = {{{this.authorizer.jwt_config.audience}}} 58 | issuer = "{{this.authorizer.jwt_config.issuer}}" 59 | }{{/if}} 60 | } 61 | {{/if}} 62 | 63 | resource aws_apigatewayv2_route {{snake_case this.name}}_function_route { 64 | provider = aws 65 | 66 | api_id = aws_apigatewayv2_api.http_api.id 67 | route_key = "{{this.http.verb}} {{this.http.path}}" 68 | target = "integrations/${aws_apigatewayv2_integration.{{snake_case this.name}}_function_integration.id}" 69 | 70 | {{#if this.authorizer}} 71 | authorization_type = "{{#if (eq this.authorizer.type "iam")}}AWS_IAM{{else}}{{this.authorizer.type}}{{/if}}" 72 | authorizer_id = aws_apigatewayv2_authorizer.{{this.authorizer.id}}.id 73 | {{#if this.authorizer.scopes}}authorization_scopes = [{{#each this.authorizer.scopes}} 74 | {{this}},{{/each}} 75 | ]{{/if}} 76 | {{else}}authorization_type = "NONE"{{/if}} 77 | } 78 | 79 | resource aws_apigatewayv2_integration {{snake_case this.name}}_function_integration { 80 | provider = aws 81 | 82 | api_id = aws_apigatewayv2_api.http_api.id 83 | integration_type = "AWS_PROXY" 84 | payload_format_version = "2.0" 85 | 86 | connection_type = "INTERNET" 87 | integration_method = "POST" 88 | integration_uri = "arn:aws:apigateway:${data.aws_region.current.name}:lambda:path/2015-03-31/functions/arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:function:asml-${var.project_name}-${var.service_name}-{{this.name}}/invocations" 89 | } 90 | 91 | resource aws_lambda_permission {{snake_case this.name}}_function_lambda_permission { 92 | provider = aws 93 | 94 | action = "lambda:InvokeFunction" 95 | function_name = "asml-${var.project_name}-${var.service_name}-{{this.name}}" 96 | principal = "apigateway.amazonaws.com" 97 | 98 | source_arn = "${aws_apigatewayv2_api.http_api.execution_arn}/*" 99 | } 100 | {{/each}} 101 | 102 | output api_id { 103 | value = aws_apigatewayv2_api.http_api.id 104 | } 105 | -------------------------------------------------------------------------------- /crates/generator/src/providers/api_gateway/templates/api_inst.tf.handlebars: -------------------------------------------------------------------------------- 1 | module {{snake_case name}}_api { 2 | source = "./services/{{name}}/infra/{{provider.name}}" 3 | 4 | project_name = "{{project_name}}" 5 | project_path = "{{project_path}}" 6 | service_name = "{{name}}" 7 | 8 | providers = { 9 | {{platform.name}} = {{platform.name}}.{{platform.id}} 10 | } 11 | 12 | depends_on = [module.{{snake_case name}}_service{{#if api.domain}}, module.{{snake_case name}}_dns{{/if}}] 13 | } 14 | -------------------------------------------------------------------------------- /crates/generator/src/providers/aws_lambda/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use handlebars::Handlebars; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::{ 8 | concat_cast, 9 | context::{Function, Service}, 10 | snake_case, CastResult, ContentType, Fragment, Options, 11 | }; 12 | 13 | use super::{ 14 | GatewayProvider, ContainerRegistryProvider, DnsProvider, FunctionProvider, Platform, Provider, 15 | ServiceProvider, 16 | }; 17 | 18 | pub fn provider_name() -> String { 19 | "aws-lambda".into() 20 | } 21 | 22 | #[derive(Serialize, Deserialize)] 23 | pub struct AwsLambdaProvider { 24 | #[serde(default = "provider_name")] 25 | name: String, 26 | // #[serde(default = "platform_name")] 27 | // platform: String, 28 | options: Options, 29 | platform: Option, 30 | } 31 | 32 | impl AwsLambdaProvider { 33 | pub fn new(options: Options, platform: Option) -> Box { 34 | Box::new(Self { 35 | name: provider_name(), 36 | options, 37 | platform, 38 | }) 39 | } 40 | } 41 | 42 | #[typetag::serde] 43 | impl Provider for AwsLambdaProvider { 44 | fn name(&self) -> String { 45 | self.name.clone() 46 | } 47 | 48 | fn platform(&self) -> Option { 49 | self.platform.clone() 50 | } 51 | 52 | fn compatible_platforms(&self) -> Vec { 53 | vec!["aws".into()] 54 | } 55 | 56 | fn options(&self) -> Options { 57 | self.options.clone() 58 | } 59 | 60 | fn set_option(&mut self, key: &str, value: &str) { 61 | self.options.insert(key.into(), value.into()).unwrap(); 62 | } 63 | 64 | fn boot(&self) -> Result<()> { 65 | let runtime_url = &*format!( 66 | "http://public.assemblylift.akkoro.io/runtime/{}/aws-lambda/bootstrap.zip", 67 | // clap::crate_version!(), 68 | "0.4.0-beta.0" 69 | ); 70 | 71 | std::fs::create_dir_all("./.asml/runtime").unwrap(); 72 | assemblylift_tools::download_to_path(runtime_url, "./.asml/runtime/bootstrap.zip")?; 73 | 74 | // FIXME handle errors 75 | Ok(()) 76 | } 77 | 78 | fn is_booted(&self) -> bool { 79 | std::path::Path::new("./.asml/runtime/bootstrap.zip").exists() 80 | } 81 | 82 | fn as_service_provider(&self) -> Result<&dyn ServiceProvider> { 83 | Ok(self) 84 | } 85 | 86 | fn as_function_provider(&self) -> Result<&dyn FunctionProvider> { 87 | Ok(self) 88 | } 89 | 90 | fn as_gateway_provider(&self) -> Result<&dyn GatewayProvider> { 91 | Err(anyhow!("{} is not a GatewayProvider", self.name())) 92 | } 93 | 94 | fn as_dns_provider(&self) -> Result<&dyn DnsProvider> { 95 | Err(anyhow!("{} is not a DnsProvider", self.name())) 96 | } 97 | 98 | fn as_container_registry_provider(&self) -> Result<&dyn ContainerRegistryProvider> { 99 | Err(anyhow!( 100 | "{} is not a ContainerRegistryProvider", 101 | self.name() 102 | )) 103 | } 104 | } 105 | 106 | impl ServiceProvider for AwsLambdaProvider { 107 | fn cast_service(&self, service: &Service) -> CastResult> { 108 | let mut fragments: Vec = Vec::new(); 109 | 110 | let mut function_fragments = service 111 | .functions 112 | .iter() 113 | .map(|function| self.as_function_provider().unwrap().cast_function(function)) 114 | .reduce(concat_cast) 115 | .unwrap()?; 116 | 117 | let mut hbs = Handlebars::new(); 118 | hbs.register_helper("snake_case", Box::new(snake_case)); 119 | hbs.register_template_string("root", include_str!("templates/service_impl.tf.handlebars")) 120 | .unwrap(); 121 | 122 | let service_fragment = Fragment { 123 | content_type: ContentType::HCL, 124 | content: hbs.render("root", &service.as_json().unwrap()).unwrap(), 125 | write_path: PathBuf::from(format!( 126 | "net/services/{}/infra/{}/service.tf", 127 | service.name, 128 | self.name(), 129 | )), 130 | }; 131 | 132 | fragments.append(&mut vec![service_fragment]); 133 | fragments.append(&mut function_fragments); 134 | 135 | Ok(fragments) 136 | } 137 | } 138 | 139 | impl FunctionProvider for AwsLambdaProvider { 140 | fn cast_function(&self, function: &Function) -> CastResult> { 141 | let mut fragments: Vec = Vec::new(); 142 | 143 | let mut hbs = Handlebars::new(); 144 | hbs.register_template_string( 145 | "root", 146 | include_str!("templates/function_impl.tf.handlebars"), 147 | ) 148 | .unwrap(); 149 | 150 | let function_fragment = Fragment { 151 | content_type: ContentType::HCL, 152 | content: hbs.render("root", &function.as_json().unwrap()).unwrap(), 153 | write_path: PathBuf::from(format!( 154 | "net/services/{}/infra/{}/functions/{}/infra/function.tf", 155 | function.service_name, 156 | self.name(), 157 | function.name, 158 | )), 159 | }; 160 | 161 | fragments.append(&mut vec![function_fragment]); 162 | 163 | Ok(fragments) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /crates/generator/src/providers/aws_lambda/templates/function_impl.tf.handlebars: -------------------------------------------------------------------------------- 1 | variable project_name { 2 | type = string 3 | } 4 | 5 | variable project_path { 6 | type = string 7 | } 8 | 9 | variable service_name { 10 | type = string 11 | } 12 | 13 | variable function_name { 14 | type = string 15 | } 16 | 17 | variable handler_name { 18 | type = string 19 | } 20 | 21 | variable runtime_environment { 22 | type = string 23 | } 24 | 25 | variable payload_bucket { 26 | type = string 27 | default = "" 28 | } 29 | 30 | variable timeout { 31 | type = number 32 | default = 5 33 | } 34 | 35 | variable size { 36 | type = number 37 | default = 1769 38 | } 39 | 40 | variable env_vars { 41 | type = map(string) 42 | default = {} 43 | } 44 | 45 | variable runtime_layer_arn { 46 | type = string 47 | } 48 | 49 | variable iomod_layer_arn { 50 | type = string 51 | default = "" 52 | } 53 | 54 | variable ruby_layer_arn { 55 | type = string 56 | default = null 57 | } 58 | 59 | terraform { 60 | required_providers { 61 | aws = { 62 | source = "hashicorp/aws" 63 | version = "~> 5.0" 64 | } 65 | } 66 | } 67 | 68 | locals { 69 | layers = var.ruby_layer_arn == null ? [var.runtime_layer_arn] : [var.runtime_layer_arn, var.ruby_layer_arn] 70 | } 71 | 72 | {{#if has_large_payload}}resource aws_s3_object asml_function_payload { 73 | provider = aws 74 | key = "${var.function_name}.zip" 75 | bucket = var.payload_bucket 76 | source = "${var.project_path}/net/services/${var.service_name}/functions/${var.function_name}/${var.function_name}.zip" 77 | etag = filemd5("${var.project_path}/net/services/${var.service_name}/functions/${var.function_name}/${var.function_name}.zip") 78 | }{{/if}} 79 | 80 | resource aws_lambda_function asml_function { 81 | provider = aws 82 | 83 | function_name = "asml-${var.project_name}-${var.service_name}-${var.function_name}" 84 | role = aws_iam_role.asml_lambda_iam_role.arn 85 | runtime = "provided" 86 | handler = var.handler_name 87 | timeout = var.timeout 88 | memory_size = var.size 89 | 90 | {{#if has_large_payload}} 91 | s3_key = "${var.function_name}.zip" 92 | s3_bucket = var.payload_bucket 93 | {{else}} 94 | filename = "${var.project_path}/net/services/${var.service_name}/functions/${var.function_name}/${var.function_name}.zip" 95 | {{/if}} 96 | 97 | environment { 98 | variables = merge({ 99 | ASML_FUNCTION_ENV = var.runtime_environment 100 | }, var.env_vars) 101 | } 102 | 103 | layers = local.layers 104 | 105 | source_code_hash = filebase64sha256("${var.project_path}/net/services/${var.service_name}/functions/${var.function_name}/${var.function_name}.zip") 106 | } 107 | 108 | resource aws_iam_role asml_lambda_iam_role { 109 | provider = aws 110 | name = "asml-${var.project_name}-${var.service_name}-${var.function_name}" 111 | 112 | assume_role_policy = < String { 15 | "ecr".into() 16 | } 17 | 18 | #[derive(Serialize, Deserialize)] 19 | pub struct EcrProvider { 20 | #[serde(default = "provider_name")] 21 | name: String, 22 | options: Options, 23 | platform: Option, 24 | } 25 | 26 | impl EcrProvider { 27 | pub fn new(options: Options, platform: Option) -> Box { 28 | Box::new(Self { 29 | name: provider_name(), 30 | options, 31 | platform, 32 | }) 33 | } 34 | } 35 | 36 | #[typetag::serde] 37 | impl Provider for EcrProvider { 38 | fn name(&self) -> String { 39 | self.name.clone() 40 | } 41 | 42 | fn platform(&self) -> Option { 43 | self.platform.clone() 44 | } 45 | 46 | fn compatible_platforms(&self) -> Vec { 47 | vec!["aws".into()] 48 | } 49 | 50 | fn options(&self) -> Options { 51 | self.options.clone() 52 | } 53 | 54 | fn set_option(&mut self, key: &str, value: &str) { 55 | self.options.insert(key.into(), value.into()).unwrap(); 56 | } 57 | 58 | fn boot(&self) -> Result<()> { 59 | Ok(()) 60 | } 61 | 62 | fn is_booted(&self) -> bool { 63 | true 64 | } 65 | 66 | fn as_service_provider(&self) -> Result<&dyn ServiceProvider> { 67 | Err(anyhow!("{} is not a ServiceProvider", self.name())) 68 | } 69 | 70 | fn as_function_provider(&self) -> Result<&dyn FunctionProvider> { 71 | Err(anyhow!("{} is not a FunctionProvider", self.name())) 72 | } 73 | 74 | fn as_gateway_provider(&self) -> Result<&dyn GatewayProvider> { 75 | Err(anyhow!("{} is not a GatewayProvider", self.name())) 76 | } 77 | 78 | fn as_dns_provider(&self) -> Result<&dyn DnsProvider> { 79 | Err(anyhow!("{} is not a DnsProvider", self.name())) 80 | } 81 | 82 | fn as_container_registry_provider(&self) -> Result<&dyn ContainerRegistryProvider> { 83 | Ok(self) 84 | } 85 | } 86 | 87 | impl ContainerRegistryProvider for EcrProvider { 88 | fn cast_service(&self, service: &Service) -> CastResult> { 89 | if self.platform().is_none() { 90 | return Err(CastError(format!("ContainerRegistryProvider `{}` requires a Platform", self.name()))); 91 | } 92 | 93 | let mut hbs = Handlebars::new(); 94 | hbs.register_helper("snake_case", Box::new(snake_case)); 95 | hbs.register_template_string("service", include_str!("templates/ecr_impl.tf.handlebars")) 96 | .unwrap(); 97 | 98 | let service_fragment = Fragment { 99 | content_type: ContentType::HCL, 100 | content: hbs.render("service", &service.as_json().unwrap()).unwrap(), 101 | write_path: PathBuf::from(format!( 102 | "net/services/{}/infra/{}/containers.tf", 103 | service.name, 104 | self.name(), 105 | )), 106 | }; 107 | 108 | Ok(vec![service_fragment]) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /crates/generator/src/providers/ecr/templates/ecr_impl.tf.handlebars: -------------------------------------------------------------------------------- 1 | variable project_name { 2 | type = string 3 | } 4 | 5 | variable service_name { 6 | type = string 7 | } 8 | 9 | terraform { 10 | required_providers { 11 | aws = { 12 | source = "hashicorp/aws" 13 | version = "~> 5.0" 14 | } 15 | } 16 | } 17 | 18 | {{#each functions}} 19 | resource aws_ecr_repository {{snake_case this.name}} { 20 | provider = aws 21 | name = "asml/${var.project_name}/${var.service_name}/{{this.name}}" 22 | } 23 | {{/each}} 24 | 25 | output function_urls { 26 | value = { 27 | {{#each functions}} 28 | "{{this.name}}" = aws_ecr_repository.{{snake_case this.name}}.repository_url 29 | {{/each}} 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/generator/src/providers/ecr/templates/ecr_inst.tf.handlebars: -------------------------------------------------------------------------------- 1 | module {{snake_case name}}_container_registry { 2 | source = "./services/{{name}}/infra/{{container_registry.provider.name}}" 3 | 4 | project_name = "{{project_name}}" 5 | service_name = "{{name}}" 6 | 7 | providers = { 8 | {{platform.name}} = {{platform.name}}.{{platform.id}} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /crates/generator/src/providers/ecr/templates/ecr_inst_root.tf.handlebars: -------------------------------------------------------------------------------- 1 | data aws_ecr_authorization_token {{snake_case id}}_token { 2 | provider = aws.{{platform.id}} 3 | } 4 | 5 | locals { 6 | {{snake_case id}}_registry_credentials = { 7 | username = data.aws_ecr_authorization_token.{{snake_case id}}_token.user_name 8 | password = data.aws_ecr_authorization_token.{{snake_case id}}_token.password 9 | auth_token = data.aws_ecr_authorization_token.{{snake_case id}}_token.authorization_token 10 | proxy_endpoint = data.aws_ecr_authorization_token.{{snake_case id}}_token.proxy_endpoint 11 | } 12 | } 13 | 14 | provider docker { 15 | alias = "{{id}}" 16 | registry_auth { 17 | address = data.aws_ecr_authorization_token.{{snake_case id}}_token.proxy_endpoint 18 | password = data.aws_ecr_authorization_token.{{snake_case id}}_token.password 19 | username = data.aws_ecr_authorization_token.{{snake_case id}}_token.user_name 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crates/generator/src/providers/gloo/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, path::PathBuf}; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use handlebars::Handlebars; 5 | use jsonpath_lib::Selector; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use assemblylift_tools::{glooctl::GlooCtl, kubectl::KubeCtl}; 9 | 10 | use crate::{ 11 | context::Service, providers::kubernetes, snake_case, CastError, CastResult, ContentType, 12 | Fragment, Options, 13 | }; 14 | 15 | use super::{ 16 | GatewayProvider, ContainerRegistryProvider, DnsProvider, FunctionProvider, Provider, 17 | ServiceProvider, Platform, 18 | }; 19 | 20 | pub fn provider_name() -> String { 21 | "gloo".into() 22 | } 23 | 24 | #[derive(Serialize, Deserialize)] 25 | pub struct GlooProvider { 26 | #[serde(default = "provider_name")] 27 | name: String, 28 | options: Options, 29 | platform: Option, 30 | } 31 | 32 | impl GlooProvider { 33 | pub fn new(options: Options, platform: Option) -> Box { 34 | Box::new(Self { 35 | name: provider_name(), 36 | options, 37 | platform, 38 | }) 39 | } 40 | 41 | pub fn gloo_proxy_ip(&self) -> Option { 42 | let mut labels = HashMap::new(); 43 | labels.insert("gloo".to_string(), "gateway-proxy".to_string()); 44 | let kubectl = KubeCtl::default_with_config( 45 | self.platform.as_ref().unwrap().options.get("config_path").unwrap().into(), 46 | ); 47 | let gateways = kubectl 48 | .get_in_namespace("services", "gloo-system", Some(labels)) 49 | .unwrap(); 50 | let mut selector = Selector::new(); 51 | let v: Vec = selector 52 | .str_path("$.items[0].status.loadBalancer.ingress[0].ip") 53 | .unwrap() 54 | .value(&gateways) 55 | .select_as() 56 | .unwrap(); 57 | match v.len() > 0 { 58 | true => Some(v[0].clone()), 59 | false => None, 60 | } 61 | } 62 | } 63 | 64 | #[typetag::serde] 65 | impl Provider for GlooProvider { 66 | fn name(&self) -> String { 67 | self.name.clone() 68 | } 69 | 70 | fn platform(&self) -> Option { 71 | self.platform.clone() 72 | } 73 | 74 | fn compatible_platforms(&self) -> Vec { 75 | vec!["kubernetes".into()] 76 | } 77 | 78 | fn options(&self) -> Options { 79 | self.options.clone() 80 | } 81 | 82 | fn set_option(&mut self, key: &str, value: &str) { 83 | self.options.insert(key.into(), value.into()); 84 | } 85 | 86 | fn boot(&self) -> Result<()> { 87 | let kubeconfig = self.platform.as_ref().unwrap().options.get("config_path").unwrap(); 88 | Ok(GlooCtl::default_with_config(kubeconfig.into()).install_gateway()) 89 | } 90 | 91 | fn is_booted(&self) -> bool { 92 | self.gloo_proxy_ip().is_some() 93 | } 94 | 95 | fn as_service_provider(&self) -> Result<&dyn ServiceProvider> { 96 | Err(anyhow!("{} is not a ServiceProvider", self.name())) 97 | } 98 | 99 | fn as_function_provider(&self) -> Result<&dyn FunctionProvider> { 100 | Err(anyhow!("{} is not a FunctionProvider", self.name())) 101 | } 102 | 103 | fn as_gateway_provider(&self) -> Result<&dyn GatewayProvider> { 104 | Ok(self) 105 | } 106 | 107 | fn as_dns_provider(&self) -> Result<&dyn DnsProvider> { 108 | Err(anyhow!("{} is not a DnsProvider", self.name())) 109 | } 110 | 111 | fn as_container_registry_provider(&self) -> Result<&dyn ContainerRegistryProvider> { 112 | Err(anyhow!( 113 | "{} is not a ContainerRegistryProvider", 114 | self.name() 115 | )) 116 | } 117 | } 118 | 119 | impl GatewayProvider for GlooProvider { 120 | fn cast_service(&self, service: &Service) -> CastResult> { 121 | let mut svc: Service = service.into(); 122 | svc.gateway.provider.set_option( 123 | "__cluster_ip", 124 | &self 125 | .gloo_proxy_ip() 126 | .ok_or(CastError("no IP found for gloo gateway".into()))?, 127 | ); 128 | 129 | let mut fragments: Vec = Vec::new(); 130 | 131 | let mut hbs = Handlebars::new(); 132 | hbs.register_helper("snake_case", Box::new(snake_case)); 133 | hbs.register_template_string("root", include_str!("templates/api_impl.tf.handlebars")) 134 | .unwrap(); 135 | 136 | let api_fragment = Fragment { 137 | content_type: ContentType::HCL, 138 | content: hbs.render("root", &svc.as_json().unwrap()).unwrap(), 139 | write_path: PathBuf::from(format!( 140 | "net/services/{}/infra/{}/api.tf", 141 | service.name, 142 | self.name(), 143 | )), 144 | }; 145 | 146 | fragments.append(&mut vec![api_fragment]); 147 | 148 | Ok(fragments) 149 | } 150 | 151 | fn compatible_service_providers(&self) -> Vec { 152 | vec![kubernetes::provider_name()] 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /crates/generator/src/providers/gloo/templates/api_impl.tf.handlebars: -------------------------------------------------------------------------------- 1 | variable project_name { 2 | type = string 3 | } 4 | 5 | variable project_path { 6 | type = string 7 | } 8 | 9 | variable service_name { 10 | type = string 11 | } 12 | 13 | terraform { 14 | required_providers { 15 | kubernetes = { 16 | source = "hashicorp/kubernetes" 17 | version = ">= 2.24.0" 18 | } 19 | } 20 | } 21 | 22 | {{#if this.domain}} 23 | locals { 24 | domain_name = "{{#unless this.is_root}}${var.service_name}.{{/unless}}{{#unless this.domain.map_to_root}}${var.project_name}.{{/unless}}{{this.domain.dns_name}}" 25 | } 26 | {{/if}} 27 | 28 | data kubernetes_service gloo_proxy { 29 | provider = kubernetes 30 | metadata { 31 | name = "gateway-proxy" 32 | namespace = "gloo-system" 33 | } 34 | } 35 | 36 | resource kubernetes_manifest gloo_virtualservice { 37 | provider = kubernetes 38 | manifest = { 39 | apiVersion = "gateway.solo.io/v1" 40 | kind = "VirtualService" 41 | 42 | metadata = { 43 | name = var.service_name 44 | namespace = "asml-${var.project_name}-${var.service_name}" 45 | } 46 | 47 | spec = { 48 | virtualHost = { 49 | domains = [{{#if this.domain}}local.domain_name{{else}}data.kubernetes_service.gloo_proxy.status.0.load_balancer.0.ingress.0.ip{{/if}}] 50 | routes = [ 51 | {{#each functions}}{ 52 | matchers = [ 53 | { 54 | exact = "{{this.http.path}}" 55 | } 56 | ] 57 | routeAction = { 58 | single = { 59 | upstream = { 60 | name = "asml-${var.project_name}-${var.service_name}-{{this.name}}-5543" 61 | namespace = "gloo-system" 62 | } 63 | } 64 | } 65 | }, 66 | {{/each}}] 67 | } 68 | {{#if this.domain}}sslConfig = { 69 | secretRef = { 70 | name = "asml-${var.project_name}-${var.service_name}-tls" 71 | namespace = "asml-${var.project_name}-${var.service_name}" 72 | } 73 | sniDomains = [local.domain_name] 74 | }{{/if}} 75 | } 76 | } 77 | } 78 | {{#if this.domain}} 79 | resource kubernetes_manifest gloo_virtualservice_http { 80 | provider = kubernetes 81 | manifest = { 82 | apiVersion = "gateway.solo.io/v1" 83 | kind = "VirtualService" 84 | 85 | metadata = { 86 | name = "${var.service_name}-http" 87 | namespace = "asml-${var.project_name}-${var.service_name}" 88 | } 89 | 90 | spec = { 91 | virtualHost = { 92 | domains = [local.domain_name] 93 | routes = [ 94 | { 95 | matchers = [ 96 | { 97 | prefix = "/" 98 | } 99 | ] 100 | redirectAction = { 101 | hostRedirect = local.domain_name 102 | httpsRedirect = true 103 | } 104 | } 105 | ] 106 | } 107 | } 108 | } 109 | } 110 | {{/if}} 111 | -------------------------------------------------------------------------------- /crates/generator/src/providers/gloo/templates/api_inst.tf.handlebars: -------------------------------------------------------------------------------- 1 | module {{snake_case name}}_api { 2 | source = "./services/{{name}}/infra/{{provider.name}}" 3 | 4 | project_name = "{{project_name}}" 5 | project_path = "{{project_path}}" 6 | service_name = "{{name}}" 7 | 8 | providers = { 9 | {{platform.name}} = {{platform.name}}.{{platform.id}} 10 | } 11 | 12 | depends_on = [module.{{snake_case name}}_service{{#if api.domain}}, module.{{snake_case name}}_dns{{/if}}] 13 | } 14 | -------------------------------------------------------------------------------- /crates/generator/src/providers/kubernetes/templates/function.dockerfile.handlebars: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/akkoro/assemblylift/hyper-debian:{{runtime_version}} 2 | ENV ASML_WASM_MODULE_NAME {{handler_name}} 3 | ENV ASML_FUNCTION_COORDINATES {{coordinates}} 4 | ENV ASML_FUNCTION_PRECOMPILED {{precompiled}} 5 | ENV ASML_FUNCTION_ENV {{runtime_environment}} 6 | ADD ./services/{{service_name}}/functions/{{name}}/{{handler_name}} /opt/assemblylift/projects/{{project_name}}/services/{{service_name}}/{{handler_name}} 7 | {{#if (eq language "ruby")}} 8 | ENV ASML_FUNCTION_BIND_PATHS /usr/bin/ruby-wasm32-wasi/src=/src,/usr/bin/ruby-wasm32-wasi/usr=/usr 9 | COPY ./runtime/ruby/3.3.0-dev/ruby-wasm32-wasi /usr/bin/ruby-wasm32-wasi 10 | COPY ./services/{{service_name}}/functions/{{name}}/rubysrc/* /usr/bin/ruby-wasm32-wasi/src/ 11 | {{/if}} 12 | -------------------------------------------------------------------------------- /crates/generator/src/providers/kubernetes/templates/function_impl.tf.handlebars: -------------------------------------------------------------------------------- 1 | variable project_name { 2 | type = string 3 | } 4 | 5 | variable project_path { 6 | type = string 7 | } 8 | 9 | variable service_name { 10 | type = string 11 | } 12 | 13 | variable function_name { 14 | type = string 15 | } 16 | 17 | variable handler_name { 18 | type = string 19 | } 20 | 21 | variable repository_url { 22 | type = string 23 | } 24 | 25 | variable env_vars { 26 | type = map(string) 27 | default = {} 28 | } 29 | 30 | terraform { 31 | required_providers { 32 | kubernetes = { 33 | source = "hashicorp/kubernetes" 34 | version = ">= 2.24.0" 35 | } 36 | 37 | docker = { 38 | source = "kreuzwerker/docker" 39 | version = ">= 3.0.2" 40 | } 41 | } 42 | } 43 | 44 | {{#if (eq language "ruby")}} 45 | data archive_file rubysrc { 46 | type = "zip" 47 | source_dir = "${var.project_path}/net/services/${var.service_name}/functions/${var.function_name}/rubysrc" 48 | output_path = "${var.project_path}/net/services/${var.service_name}/functions/${var.function_name}/rubysrc.zip" 49 | } 50 | {{/if}} 51 | 52 | resource random_id image_src { 53 | byte_length = 8 54 | keepers = { 55 | dockerfile_hash = filebase64sha256("${var.project_path}/net/services/${var.service_name}/functions/${var.function_name}/Dockerfile") 56 | wasm_hash = filebase64sha256("${var.project_path}/net/services/${var.service_name}/functions/${var.function_name}/${var.handler_name}") 57 | {{#if (eq language "ruby")}}rubysrc_hash = data.archive_file.rubysrc.output_sha{{/if}} 58 | } 59 | } 60 | 61 | resource docker_registry_image function_image { 62 | provider = docker 63 | name = docker_image.function_image.name 64 | keep_remotely = true 65 | } 66 | 67 | resource docker_image function_image { 68 | provider = docker 69 | name = "${var.repository_url}:${random_id.image_src.hex}" 70 | 71 | build { 72 | context = "${var.project_path}/net" 73 | dockerfile = "services/${var.service_name}/functions/${var.function_name}/Dockerfile" 74 | pull_parent = true 75 | force_remove = true 76 | } 77 | } 78 | 79 | resource kubernetes_deployment deployment { 80 | provider = kubernetes 81 | depends_on = [docker_registry_image.function_image] 82 | metadata { 83 | name = "${var.function_name}" 84 | namespace = "asml-${var.project_name}-${var.service_name}" 85 | labels = { 86 | asml_function = "${var.function_name}" 87 | asml_service = "${var.service_name}" 88 | } 89 | } 90 | 91 | spec { 92 | replicas = 1 93 | 94 | selector { 95 | match_labels = { 96 | asml_function = "${var.function_name}" 97 | asml_service = "${var.service_name}" 98 | } 99 | } 100 | 101 | template { 102 | metadata { 103 | labels = { 104 | asml_function = "${var.function_name}" 105 | asml_service = "${var.service_name}" 106 | } 107 | } 108 | 109 | spec { 110 | image_pull_secrets { 111 | name = "registry-credentials" 112 | } 113 | container { 114 | image = docker_registry_image.function_image.name 115 | name = "asml-${var.service_name}-${var.function_name}" 116 | port { 117 | container_port = 5543 118 | } 119 | port { 120 | container_port = 13555 121 | } 122 | dynamic "env" { 123 | for_each = var.env_vars 124 | content { 125 | name = env.key 126 | value = env.value 127 | } 128 | } 129 | env { 130 | name = "ASML_CPU_COMPAT_MODE" 131 | value = "{{this.cpu_compat_mode}}" 132 | } 133 | } 134 | {{#each iomods}} 135 | container { 136 | image = "{{this.image}}" 137 | name = "{{this.name}}" 138 | } 139 | {{/each}} 140 | } 141 | } 142 | } 143 | } 144 | 145 | resource kubernetes_service service { 146 | provider = kubernetes 147 | 148 | metadata { 149 | name = "${var.function_name}" 150 | namespace = "asml-${var.project_name}-${var.service_name}" 151 | } 152 | 153 | spec { 154 | selector = { 155 | asml_function = "${var.function_name}" 156 | asml_service = "${var.service_name}" 157 | } 158 | type = "ClusterIP" 159 | port { 160 | port = 5543 161 | target_port = 5543 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /crates/generator/src/providers/kubernetes/templates/service_impl.tf.handlebars: -------------------------------------------------------------------------------- 1 | # AssemblyLift Service 2 | # Name: {{name}} 3 | 4 | variable project_name { 5 | type = string 6 | } 7 | 8 | variable project_path { 9 | type = string 10 | } 11 | 12 | variable service_name { 13 | type = string 14 | } 15 | 16 | variable registry_credentials { 17 | type = map(string) 18 | } 19 | 20 | variable function_urls { 21 | type = map(string) 22 | } 23 | 24 | terraform { 25 | required_providers { 26 | kubernetes = { 27 | source = "hashicorp/kubernetes" 28 | version = ">= 2.24.0" 29 | } 30 | 31 | docker = { 32 | source = "kreuzwerker/docker" 33 | version = ">= 3.0.2" 34 | } 35 | } 36 | } 37 | 38 | resource kubernetes_namespace service_namespace { 39 | provider = kubernetes 40 | metadata { 41 | name = "asml-${var.project_name}-${var.service_name}" 42 | } 43 | } 44 | 45 | resource kubernetes_secret dockerconfig { 46 | provider = kubernetes 47 | metadata { 48 | name = "registry-credentials" 49 | namespace = "asml-${var.project_name}-${var.service_name}" 50 | } 51 | data = { 52 | ".dockerconfigjson" = jsonencode({ 53 | auths = { 54 | (var.registry_credentials.proxy_endpoint) = { 55 | "username" = var.registry_credentials.username 56 | "password" = var.registry_credentials.password 57 | "auth" = var.registry_credentials.auth_token 58 | } 59 | } 60 | }) 61 | } 62 | type = "kubernetes.io/dockerconfigjson" 63 | 64 | depends_on = [kubernetes_namespace.service_namespace] 65 | } 66 | 67 | {{#if this.domain}} 68 | resource kubernetes_manifest cert_issuer { 69 | provider = kubernetes 70 | manifest = { 71 | apiVersion = "cert-manager.io/v1" 72 | kind = "ClusterIssuer" 73 | 74 | metadata = { 75 | name = "asml-${var.project_name}-${var.service_name}-letsencrypt" 76 | } 77 | 78 | spec = { 79 | acme = { 80 | server = "{{#if (eq this.domain.provider.options.cm_letsencrypt_env "staging")}}https://acme-staging-v02.api.letsencrypt.org/directory{{else}}https://acme-v02.api.letsencrypt.org/directory{{/if}}" 81 | email = "{{this.domain.provider.options.cm_acme_email}}" 82 | 83 | privateKeySecretRef = { 84 | name = "asml-letsencrypt" 85 | } 86 | 87 | solvers = [ 88 | { 89 | dns01 = { 90 | route53 = { 91 | region = "{{this.domain.provider.platform.options.region}}" 92 | {{#if this.domain.provider.options.cm_aws_credentials}} 93 | accessKeyIDSecretRef = { 94 | name = "{{this.domain.provider.options.cm_aws_credentials}}" 95 | key = "aws_access_key_id" 96 | } 97 | secretAccessKeySecretRef = { 98 | name = "{{this.domain.provider.options.cm_aws_credentials}}" 99 | key = "aws_secret_access_key" 100 | } 101 | {{/if}} 102 | } 103 | } 104 | } 105 | ] 106 | } 107 | } 108 | } 109 | } 110 | {{/if}} 111 | 112 | {{#each functions}} 113 | module asml_function_{{this.name}} { 114 | source = "./functions/{{this.name}}/infra" 115 | 116 | project_name = var.project_name 117 | project_path = var.project_path 118 | service_name = var.service_name 119 | function_name = "{{this.name}}" 120 | handler_name = "{{this.handler_name}}" 121 | repository_url = var.function_urls["{{this.name}}"] 122 | 123 | {{#if this.environment_variables}}env_vars = { 124 | {{#each this.environment_variables}}{{@key}} = "{{this}}", 125 | {{/each}} 126 | }{{/if}} 127 | 128 | depends_on = [kubernetes_namespace.service_namespace] 129 | } 130 | {{/each}} 131 | -------------------------------------------------------------------------------- /crates/generator/src/providers/kubernetes/templates/service_inst.tf.handlebars: -------------------------------------------------------------------------------- 1 | module {{snake_case name}}_service { 2 | source = "./services/{{name}}/infra/{{provider.name}}" 3 | 4 | project_name = "{{project_name}}" 5 | project_path = "{{project_path}}" 6 | service_name = "{{name}}" 7 | 8 | function_urls = module.{{snake_case name}}_container_registry.function_urls 9 | registry_credentials = local.{{container_registry.id}}_registry_credentials 10 | 11 | providers = { 12 | {{platform.name}} = {{platform.name}}.{{platform.id}} 13 | docker = docker.{{container_registry.id}} 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /crates/generator/src/providers/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | pub mod api_gateway; 5 | pub mod aws_lambda; 6 | pub mod ecr; 7 | pub mod gloo; 8 | pub mod kubernetes; 9 | pub mod route53; 10 | 11 | use crate::{ 12 | context::{Domain, Function, Service}, 13 | CastResult, Fragment, Options, StringMap, 14 | }; 15 | 16 | use self::{ 17 | api_gateway::ApiGatewayProvider, aws_lambda::AwsLambdaProvider, ecr::EcrProvider, 18 | gloo::GlooProvider, kubernetes::KubernetesProvider, route53::Route53Provider, 19 | }; 20 | 21 | #[derive(Serialize, Deserialize, Clone)] 22 | pub struct Platform { 23 | pub id: String, 24 | pub name: String, 25 | pub options: StringMap, 26 | } 27 | 28 | impl From<&crate::toml::asml::Platform> for Platform { 29 | fn from(value: &crate::toml::asml::Platform) -> Self { 30 | Self { 31 | id: value.id.clone(), 32 | name: value.name.clone(), 33 | options: value.options.clone(), 34 | } 35 | } 36 | } 37 | 38 | #[typetag::serde(tag = "provider")] 39 | pub trait Provider { 40 | fn name(&self) -> String; 41 | fn platform(&self) -> Option; 42 | fn compatible_platforms(&self) -> Vec; 43 | fn options(&self) -> Options; 44 | fn set_option(&mut self, key: &str, value: &str); 45 | fn boot(&self) -> Result<()>; 46 | fn is_booted(&self) -> bool; 47 | // fn validate(&self) -> bool; 48 | fn as_service_provider(&self) -> Result<&dyn ServiceProvider>; 49 | fn as_function_provider(&self) -> Result<&dyn FunctionProvider>; 50 | fn as_gateway_provider(&self) -> Result<&dyn GatewayProvider>; 51 | fn as_dns_provider(&self) -> Result<&dyn DnsProvider>; 52 | fn as_container_registry_provider(&self) -> Result<&dyn ContainerRegistryProvider>; 53 | } 54 | 55 | pub trait ServiceProvider: Provider { 56 | fn cast_service(&self, service: &Service) -> CastResult>; 57 | } 58 | 59 | pub trait FunctionProvider: Provider { 60 | fn cast_function(&self, function: &Function) -> CastResult>; 61 | } 62 | 63 | pub trait GatewayProvider: Provider { 64 | fn cast_service(&self, service: &Service) -> CastResult>; 65 | fn compatible_service_providers(&self) -> Vec; 66 | } 67 | 68 | pub trait DnsProvider: Provider { 69 | fn cast_domain(&self, domain: &Domain) -> CastResult>; 70 | fn cast_service(&self, service: &Service) -> CastResult>; 71 | fn compatible_gateway_providers(&self) -> Vec; 72 | } 73 | 74 | pub trait ContainerRegistryProvider: Provider { 75 | fn cast_service(&self, service: &Service) -> CastResult>; 76 | } 77 | 78 | pub struct ProviderFactory; 79 | 80 | impl ProviderFactory { 81 | pub fn new_provider(name: &str, options: Options, platform: Option) -> Result> { 82 | match name { 83 | _ if name == api_gateway::provider_name() => Ok(ApiGatewayProvider::new(options, platform)), 84 | _ if name == aws_lambda::provider_name() => Ok(AwsLambdaProvider::new(options, platform)), 85 | _ if name == ecr::provider_name() => Ok(EcrProvider::new(options, platform)), 86 | _ if name == gloo::provider_name() => Ok(GlooProvider::new(options, platform)), 87 | _ if name == kubernetes::provider_name() => Ok(KubernetesProvider::new(options, platform)), 88 | _ if name == route53::provider_name() => Ok(Route53Provider::new(options, platform)), 89 | _ => Err(anyhow!("unrecognized provider named {}", name)), 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/generator/src/providers/route53/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use handlebars::Handlebars; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::{ 8 | context::{Domain, Service}, 9 | providers::{api_gateway, gloo, Provider}, 10 | snake_case, CastResult, ContentType, Fragment, Options, 11 | }; 12 | 13 | use super::{ 14 | GatewayProvider, ContainerRegistryProvider, DnsProvider, FunctionProvider, ServiceProvider, Platform, 15 | }; 16 | 17 | pub fn provider_name() -> String { 18 | "route53".into() 19 | } 20 | 21 | #[derive(Serialize, Deserialize)] 22 | pub struct Route53Provider { 23 | #[serde(default = "provider_name")] 24 | name: String, 25 | options: Options, 26 | platform: Option, 27 | } 28 | 29 | impl Route53Provider { 30 | pub fn new(options: Options, platform: Option) -> Box { 31 | Box::new(Self { 32 | name: provider_name(), 33 | options, 34 | platform, 35 | }) 36 | } 37 | } 38 | 39 | #[typetag::serde] 40 | impl Provider for Route53Provider { 41 | fn name(&self) -> String { 42 | provider_name() 43 | } 44 | 45 | fn platform(&self) -> Option { 46 | self.platform.clone() 47 | } 48 | 49 | fn compatible_platforms(&self) -> Vec { 50 | vec!["aws".into()] 51 | } 52 | 53 | fn options(&self) -> Options { 54 | self.options.clone() 55 | } 56 | 57 | fn set_option(&mut self, key: &str, value: &str) { 58 | self.options.insert(key.into(), value.into()).unwrap(); 59 | } 60 | 61 | fn boot(&self) -> Result<()> { 62 | Ok(()) 63 | } 64 | 65 | fn is_booted(&self) -> bool { 66 | true 67 | } 68 | 69 | fn as_service_provider(&self) -> Result<&dyn ServiceProvider> { 70 | Err(anyhow!("{} is not a ServiceProvider", self.name())) 71 | } 72 | 73 | fn as_function_provider(&self) -> Result<&dyn FunctionProvider> { 74 | Err(anyhow!("{} is not a FunctionProvider", self.name())) 75 | } 76 | 77 | fn as_gateway_provider(&self) -> Result<&dyn GatewayProvider> { 78 | Err(anyhow!("{} is not a GatewayProvider", self.name())) 79 | } 80 | 81 | fn as_dns_provider(&self) -> Result<&dyn DnsProvider> { 82 | Ok(self) 83 | } 84 | 85 | fn as_container_registry_provider(&self) -> Result<&dyn ContainerRegistryProvider> { 86 | Err(anyhow!( 87 | "{} is not a ContainerRegistryProvider", 88 | self.name() 89 | )) 90 | } 91 | } 92 | 93 | impl DnsProvider for Route53Provider { 94 | fn cast_domain(&self, domain: &Domain) -> CastResult> { 95 | let mut hbs = Handlebars::new(); 96 | hbs.register_helper("snake_case", Box::new(snake_case)); 97 | hbs.register_template_string( 98 | "root", 99 | include_str!("templates/dns_impl_root.tf.handlebars"), 100 | ) 101 | .unwrap(); 102 | 103 | let root_fragment = Fragment { 104 | content_type: ContentType::HCL, 105 | content: hbs.render("root", &domain.as_json().unwrap()).unwrap(), 106 | write_path: PathBuf::from(format!( 107 | "net/infra/{}/{}/dns.tf", 108 | self.name(), 109 | domain.dns_name 110 | )), 111 | }; 112 | 113 | Ok(vec![root_fragment]) 114 | } 115 | 116 | fn cast_service(&self, service: &Service) -> CastResult> { 117 | let mut hbs = Handlebars::new(); 118 | hbs.register_helper("snake_case", Box::new(snake_case)); 119 | hbs.register_template_string("service", include_str!("templates/dns_impl.tf.handlebars")) 120 | .unwrap(); 121 | hbs.register_template_string( 122 | &api_gateway::provider_name(), 123 | include_str!("templates/dns_impl_apigw.tf.handlebars"), 124 | ) 125 | .unwrap(); 126 | hbs.register_template_string( 127 | &gloo::provider_name(), 128 | include_str!("templates/dns_impl_gloo.tf.handlebars"), 129 | ) 130 | .unwrap(); 131 | 132 | let service_fragment = Fragment { 133 | content_type: ContentType::HCL, 134 | content: hbs.render("service", &service.as_json().unwrap()).unwrap(), 135 | write_path: PathBuf::from(format!( 136 | "net/services/{}/infra/{}/dns.tf", 137 | service.name, 138 | self.name(), 139 | )), 140 | }; 141 | 142 | Ok(vec![service_fragment]) 143 | } 144 | 145 | fn compatible_gateway_providers(&self) -> Vec { 146 | vec![api_gateway::provider_name(), gloo::provider_name()] 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /crates/generator/src/providers/route53/templates/dns_impl.tf.handlebars: -------------------------------------------------------------------------------- 1 | variable project_name { 2 | type = string 3 | } 4 | 5 | variable service_name { 6 | type = string 7 | } 8 | 9 | variable zone_id { 10 | type = string 11 | } 12 | 13 | locals { 14 | domain_name = "{{#unless this.is_root}}${var.service_name}.{{/unless}}{{#unless this.domain.map_to_root}}${var.project_name}.{{/unless}}{{this.domain.dns_name}}" 15 | } 16 | 17 | {{> (lookup this.gateway.provider "name") }} 18 | -------------------------------------------------------------------------------- /crates/generator/src/providers/route53/templates/dns_impl_apigw.tf.handlebars: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 5.0" 6 | } 7 | } 8 | } 9 | 10 | resource aws_acm_certificate {{snake_case name}} { 11 | provider = aws 12 | domain_name = local.domain_name 13 | validation_method = "DNS" 14 | } 15 | 16 | resource aws_acm_certificate_validation {{snake_case name}} { 17 | provider = aws 18 | certificate_arn = aws_acm_certificate.{{snake_case name}}.arn 19 | validation_record_fqdns = [for record in aws_route53_record.{{snake_case name}}_validation : record.fqdn] 20 | } 21 | 22 | resource aws_route53_record {{snake_case name}}_validation { 23 | provider = aws 24 | for_each = { 25 | for dvo in aws_acm_certificate.{{snake_case name}}.domain_validation_options : dvo.domain_name => { 26 | name = dvo.resource_record_name 27 | record = dvo.resource_record_value 28 | type = dvo.resource_record_type 29 | } 30 | } 31 | 32 | allow_overwrite = true 33 | name = each.value.name 34 | records = [each.value.record] 35 | ttl = 60 36 | type = each.value.type 37 | zone_id = var.zone_id 38 | } 39 | 40 | resource aws_apigatewayv2_domain_name {{snake_case name}} { 41 | provider = aws 42 | domain_name = local.domain_name 43 | 44 | domain_name_configuration { 45 | certificate_arn = aws_acm_certificate.{{snake_case name}}.arn 46 | endpoint_type = "REGIONAL" 47 | security_policy = "TLS_1_2" 48 | } 49 | 50 | depends_on = [aws_acm_certificate_validation.{{snake_case name}}] 51 | } 52 | 53 | resource aws_route53_record {{snake_case name}} { 54 | provider = aws 55 | zone_id = var.zone_id 56 | name = local.domain_name 57 | type = "A" 58 | 59 | alias { 60 | name = aws_apigatewayv2_domain_name.{{snake_case name}}.domain_name_configuration[0].target_domain_name 61 | zone_id = aws_apigatewayv2_domain_name.{{snake_case name}}.domain_name_configuration[0].hosted_zone_id 62 | evaluate_target_health = false 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/generator/src/providers/route53/templates/dns_impl_gloo.tf.handlebars: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 5.0" 6 | } 7 | 8 | kubernetes = { 9 | source = "hashicorp/kubernetes" 10 | version = ">= 2.24.0" 11 | } 12 | } 13 | } 14 | 15 | resource kubernetes_manifest certificate { 16 | provider = kubernetes 17 | manifest = { 18 | apiVersion = "cert-manager.io/v1" 19 | kind = "Certificate" 20 | 21 | metadata = { 22 | name = local.domain_name 23 | namespace = "asml-${var.project_name}-${var.service_name}" 24 | } 25 | 26 | spec = { 27 | secretName = "asml-${var.project_name}-${var.service_name}-tls" 28 | issuerRef = { 29 | kind = "ClusterIssuer" 30 | name = "asml-${var.project_name}-${var.service_name}-letsencrypt" 31 | } 32 | dnsNames = [local.domain_name] 33 | } 34 | } 35 | } 36 | 37 | data kubernetes_service gloo_proxy { 38 | provider = kubernetes 39 | metadata { 40 | name = "gateway-proxy" 41 | namespace = "gloo-system" 42 | } 43 | } 44 | 45 | resource aws_route53_record record { 46 | provider = aws 47 | zone_id = var.zone_id 48 | name = local.domain_name 49 | type = "A" 50 | ttl = "300" 51 | records = [data.kubernetes_service.gloo_proxy.status.0.load_balancer.0.ingress.0.ip] 52 | } 53 | -------------------------------------------------------------------------------- /crates/generator/src/providers/route53/templates/dns_impl_root.tf.handlebars: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~> 5.0" 6 | } 7 | } 8 | } 9 | 10 | data aws_route53_zone {{snake_case dns_name}} { 11 | provider = aws 12 | name = "{{dns_name}}" 13 | } 14 | 15 | output zone_id { 16 | value = data.aws_route53_zone.{{snake_case dns_name}}.zone_id 17 | } 18 | -------------------------------------------------------------------------------- /crates/generator/src/providers/route53/templates/dns_inst.tf.handlebars: -------------------------------------------------------------------------------- 1 | module {{snake_case name}}_dns { 2 | source = "./services/{{name}}/infra/{{this.domain.provider.name}}" 3 | 4 | project_name = "{{project_name}}" 5 | service_name = "{{name}}" 6 | zone_id = module.{{snake_case this.domain.dns_name}}_dns.zone_id 7 | 8 | providers = { 9 | {{platform.name}} = {{platform.name}}.{{platform.id}} 10 | {{#if (ne this.domain.provider.platform.name platform.name)}}{{this.domain.provider.platform.name}} = {{this.domain.provider.platform.name}}.{{this.domain.provider.platform.id}}{{/if}} 11 | } 12 | 13 | depends_on = [module.{{snake_case name}}_service] 14 | } 15 | -------------------------------------------------------------------------------- /crates/generator/src/providers/route53/templates/dns_inst_root.tf.handlebars: -------------------------------------------------------------------------------- 1 | module {{snake_case dns_name}}_dns { 2 | source = "./infra/{{{provider.name}}}/{{dns_name}}" 3 | 4 | providers = { 5 | {{platform.name}} = {{platform.name}}.{{platform.id}} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /crates/generator/src/templates/context.tf.handlebars: -------------------------------------------------------------------------------- 1 | # AssemblyLift Project 2 | # Name: {{project.name}} 3 | 4 | {{#each platforms}} 5 | provider {{this.name}} { 6 | alias = "{{this.id}}" 7 | {{#each this.options}} 8 | {{@key}} = "{{this}}" 9 | {{/each}} 10 | } 11 | {{/each}} 12 | 13 | {{#if registries}} 14 | terraform { 15 | required_providers { 16 | docker = { 17 | source = "kreuzwerker/docker" 18 | } 19 | } 20 | } 21 | {{/if}} 22 | 23 | {{#each registries}} 24 | {{> (concat (lookup this.provider "name") "-root") platform=this.provider.platform }} 25 | {{/each}} 26 | 27 | {{#each domains}} 28 | {{> (concat (lookup this.provider "name") "-root") platform=this.provider.platform }} 29 | {{/each}} 30 | 31 | {{#each services}} 32 | {{> (lookup this.domain.provider "name") 33 | platform=this.provider.platform 34 | project_name=../project.name 35 | }} 36 | 37 | {{> (lookup this.container_registry.provider "name") 38 | platform=this.container_registry.provider.platform 39 | project_name=../project.name 40 | }} 41 | 42 | {{> (lookup this.provider "name") 43 | platform=this.provider.platform 44 | provider=this.provider 45 | project_name=../project.name 46 | project_path=../project.path 47 | }} 48 | 49 | {{> (lookup this.gateway.provider "name") 50 | platform=this.provider.platform 51 | provider=this.gateway.provider 52 | service_provider=this.provider 53 | project_name=../project.name 54 | project_path=../project.path 55 | }} 56 | {{/each}} 57 | -------------------------------------------------------------------------------- /crates/generator/src/toml/asml.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::path::PathBuf; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use super::Provider; 7 | use crate::Options; 8 | 9 | #[derive(Serialize, Deserialize, Clone, Debug)] 10 | pub struct Manifest { 11 | pub project: Project, 12 | #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")] 13 | pub platforms: Vec, 14 | #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")] 15 | pub services: Vec, 16 | authorizers: Option>, 17 | domains: Option>, 18 | registries: Option>, 19 | pub terraform: Option, 20 | } 21 | 22 | #[derive(Serialize, Deserialize, Clone, Debug)] 23 | pub struct Project { 24 | pub name: String, 25 | } 26 | 27 | #[derive(Serialize, Deserialize, Clone, Debug)] 28 | pub struct Platform { 29 | pub id: String, 30 | pub name: String, 31 | pub options: Options, 32 | } 33 | 34 | #[derive(Serialize, Deserialize, Clone, Debug)] 35 | pub struct Terraform { 36 | pub state_bucket_name: String, 37 | pub lock_table_name: String, 38 | } 39 | 40 | /* Represents a reference by name to a service (toml::service::Manifest) */ 41 | #[derive(Serialize, Deserialize, Clone, Debug)] 42 | pub struct ServiceRef { 43 | pub name: String, 44 | pub provider: Provider, 45 | pub registry_id: Option, 46 | pub domain_name: Option, 47 | pub is_root: Option, 48 | } 49 | 50 | #[derive(Serialize, Deserialize, Clone, Debug)] 51 | pub struct Registry { 52 | pub id: String, 53 | pub provider: Provider, 54 | } 55 | 56 | #[derive(Serialize, Deserialize, Clone, Debug)] 57 | pub struct Domain { 58 | pub dns_name: String, 59 | #[serde(default)] 60 | pub map_to_root: bool, 61 | pub provider: Provider, 62 | } 63 | 64 | #[derive(Serialize, Deserialize, Clone, Debug)] 65 | pub struct HttpAuth { 66 | pub id: String, 67 | pub auth_type: String, 68 | pub issuer: Option, 69 | pub audience: Option>, 70 | pub scopes: Option>, 71 | } 72 | 73 | impl Manifest { 74 | pub fn read(file: &PathBuf) -> Result { 75 | match std::fs::read_to_string(file) { 76 | Ok(contents) => Ok(Self::from(contents)), 77 | Err(why) => Err(io::Error::new(io::ErrorKind::Other, why.to_string())), 78 | } 79 | } 80 | 81 | pub fn write(&self, mut path: PathBuf) -> Result<(), io::Error> { 82 | path.push("assemblylift.toml"); 83 | let contents = Into::::into(self.clone()); 84 | std::fs::write(path, contents) 85 | } 86 | 87 | pub fn add_service(&mut self, resource_name: &str) { 88 | let mut services = Vec::new(); 89 | for svc in self.services.clone() { 90 | services.push(svc); 91 | } 92 | services.push(ServiceRef { 93 | name: resource_name.into(), 94 | provider: Provider { 95 | name: "my-provider".into(), 96 | options: Default::default(), 97 | platform_id: None 98 | }, 99 | registry_id: None, 100 | domain_name: None, 101 | is_root: None, 102 | }); 103 | self.services = services; 104 | } 105 | 106 | pub fn remove_service(&mut self, resource_name: &str) { 107 | let mut services = Vec::new(); 108 | for svc in self.services.iter().filter(|s| s.name != resource_name) { 109 | services.push(svc.clone()); 110 | } 111 | self.services = services; 112 | } 113 | 114 | pub fn rename_service(&mut self, old_name: &str, new_name: &str) { 115 | let mut services = Vec::new(); 116 | for svc in self.services.clone() { 117 | if svc.name == old_name { 118 | services.push(ServiceRef { 119 | name: new_name.into(), 120 | provider: svc.provider, 121 | registry_id: svc.registry_id, 122 | domain_name: svc.domain_name, 123 | is_root: svc.is_root, 124 | 125 | }); 126 | } else { 127 | services.push(svc); 128 | } 129 | } 130 | self.services = services; 131 | } 132 | 133 | pub fn authorizers(&self) -> Vec { 134 | match self.authorizers.as_ref() { 135 | Some(auth) => auth.clone(), 136 | None => Vec::new(), 137 | } 138 | } 139 | 140 | pub fn domains(&self) -> Vec { 141 | match self.domains.as_ref() { 142 | Some(domains) => domains.clone(), 143 | None => Vec::new(), 144 | } 145 | } 146 | 147 | pub fn registries(&self) -> Vec { 148 | match self.registries.as_ref() { 149 | Some(registries) => registries.clone(), 150 | None => Vec::new(), 151 | } 152 | } 153 | } 154 | 155 | impl From for Manifest { 156 | fn from(string: String) -> Self { 157 | match toml::from_str(&string) { 158 | Ok(manifest) => manifest, 159 | Err(why) => panic!("error parsing Manifest: {}", why.to_string()), 160 | } 161 | } 162 | } 163 | 164 | impl Into for Manifest { 165 | fn into(self) -> String { 166 | toml::to_string(&self).expect("unable to serialize TOML") 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /crates/generator/src/toml/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use super::StringMap; 4 | 5 | pub mod asml; 6 | pub mod service; 7 | 8 | #[derive(Serialize, Deserialize, Clone, Debug, Default)] 9 | pub struct Provider { 10 | pub name: String, 11 | #[serde(skip_serializing_if = "StringMap::is_empty", default)] 12 | pub options: StringMap, 13 | pub platform_id: Option, 14 | } 15 | -------------------------------------------------------------------------------- /crates/generator/src/toml/service.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::path::PathBuf; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use super::Provider; 7 | use super::StringMap; 8 | 9 | #[derive(Serialize, Deserialize, Clone)] 10 | pub struct Manifest { 11 | pub gateway: Gateway, 12 | #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")] 13 | pub functions: Functions, 14 | pub iomod: Option, 15 | } 16 | 17 | impl Manifest { 18 | pub fn read(file: &PathBuf) -> Result { 19 | match std::fs::read_to_string(file) { 20 | Ok(contents) => Ok(Self::from(contents)), 21 | Err(why) => Err(io::Error::new(io::ErrorKind::Other, why.to_string())), 22 | } 23 | } 24 | 25 | pub fn write(&self, mut path: PathBuf) -> Result<(), io::Error> { 26 | path.push("service.toml"); 27 | std::fs::write(path, Into::::into(self.clone())) 28 | } 29 | 30 | // pub fn service(&self) -> Service { 31 | // self.service.clone() 32 | // } 33 | 34 | // pub fn functions(&self) -> Functions { 35 | // self.functions.clone() 36 | // } 37 | 38 | pub fn iomods(&self) -> Iomods { 39 | match &self.iomod { 40 | Some(iomod) => iomod.dependencies.clone(), 41 | None => Iomods::new(), 42 | } 43 | } 44 | 45 | // pub fn rename(&mut self, new_name: &str) { 46 | // let svc = self.service.clone(); 47 | // let new_svc = Service { 48 | // name: new_name.to_string(), 49 | // registry_id: svc.registry_id.clone(), 50 | // provider: svc.provider.clone(), 51 | // }; 52 | // self.service = new_svc; 53 | // } 54 | 55 | pub fn add_function(&mut self, resource_name: &str, language: &str) { 56 | let mut functions = Vec::new(); 57 | for fun in &self.functions { 58 | functions.push(fun.clone()); 59 | } 60 | let fun = Function { 61 | name: resource_name.to_string(), 62 | registry: None, 63 | language: Some(language.into()), 64 | http: None, 65 | authorizer_id: None, 66 | timeout_seconds: None, 67 | size_mb: None, 68 | cpu_compat_mode: None, 69 | precompile: None, 70 | environment: None, 71 | }; 72 | functions.push(fun); 73 | self.functions = functions; 74 | } 75 | 76 | pub fn remove_function(&mut self, resource_name: &str) { 77 | let mut functions = Vec::new(); 78 | for svc in self.functions.iter().filter(|f| f.name != resource_name) { 79 | functions.push(svc.clone()); 80 | } 81 | self.functions = functions; 82 | } 83 | 84 | pub fn rename_function(&mut self, old_name: &str, new_name: &str) { 85 | let mut to_rename = self 86 | .functions 87 | .iter() 88 | .find(|f| f.name == old_name) 89 | .unwrap() 90 | .clone(); 91 | to_rename.name = new_name.into(); 92 | self.remove_function(old_name); 93 | let mut functions = Vec::new(); 94 | for fun in &self.functions { 95 | functions.push(fun.clone()); 96 | } 97 | functions.push(to_rename); 98 | self.functions = functions; 99 | } 100 | } 101 | 102 | impl From for Manifest { 103 | fn from(string: String) -> Self { 104 | match toml::from_str(&string) { 105 | Ok(manifest) => manifest, 106 | Err(why) => panic!("error parsing ServiceManifest: {}", why.to_string()), 107 | } 108 | } 109 | } 110 | 111 | impl Into for Manifest { 112 | fn into(self) -> String { 113 | toml::to_string(&self).expect("unable to serialize TOML") 114 | } 115 | } 116 | 117 | pub type Functions = Vec; 118 | pub type Iomods = Vec; 119 | 120 | #[derive(Serialize, Deserialize, Clone)] 121 | pub struct Gateway { 122 | pub provider: Provider, 123 | } 124 | 125 | #[derive(Serialize, Deserialize, Clone)] 126 | pub struct HttpFunction { 127 | pub verb: String, 128 | pub path: String, 129 | } 130 | 131 | #[derive(Serialize, Deserialize, Clone)] 132 | pub struct Function { 133 | pub name: String, 134 | pub registry: Option, 135 | pub language: Option, 136 | pub authorizer_id: Option, 137 | pub timeout_seconds: Option, 138 | pub size_mb: Option, 139 | pub cpu_compat_mode: Option, 140 | pub precompile: Option, 141 | pub http: Option, 142 | pub environment: Option>, 143 | } 144 | 145 | #[derive(Serialize, Deserialize, Clone)] 146 | pub struct Iomod { 147 | pub dependencies: Vec, 148 | } 149 | 150 | #[derive(Serialize, Deserialize, Clone)] 151 | pub struct Dependency { 152 | pub version: String, 153 | pub coordinates: String, 154 | } 155 | -------------------------------------------------------------------------------- /crates/runtimes/aws-lambda/guest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-awslambda-guest" 3 | version = "0.4.0-alpha.0" 4 | description = "AssemblyLift AWS Lambda WASM guest library" 5 | authors = ["Akkoro and the AssemblyLift contributors "] 6 | edition = "2018" 7 | license-file = "../../../LICENSE.md" 8 | repository = "https://github.com/akkoro/assemblylift" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | serde = "1" 13 | serde_json = "1" 14 | direct-executor = "0.3.0" 15 | assemblylift_core_guest = { version = "0.4.0-alpha.0", package = "assemblylift-core-guest", path = "../../../core/guest" } 16 | assemblylift_core_io_guest = { version = "0.4.0-alpha.0", package = "assemblylift-core-io-guest", path = "../../../core/io/guest" } 17 | -------------------------------------------------------------------------------- /crates/runtimes/aws-lambda/guest/README.md: -------------------------------------------------------------------------------- 1 | assemblylift-awslambda-guest 2 | --------------------------- 3 | 4 | AssemblyLift AWS Lambda WASM guest library 5 | -------------------------------------------------------------------------------- /crates/runtimes/aws-lambda/guest/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate assemblylift_core_guest; 2 | extern crate assemblylift_core_io_guest; 3 | 4 | use std::collections::HashMap; 5 | use std::fmt::Debug; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Serialize, Deserialize, Clone, Debug)] 10 | pub struct ApiGatewayEvent { 11 | pub resource: String, 12 | pub path: String, 13 | #[serde(rename = "httpMethod")] 14 | pub http_method: String, 15 | pub headers: HashMap, 16 | #[serde(rename = "queryStringParameters")] 17 | pub query_string_parameters: Option>, 18 | #[serde(rename = "pathParameters")] 19 | pub path_parameters: Option>, 20 | #[serde(rename = "stageVariables")] 21 | pub stage_variables: Option>, 22 | #[serde(rename = "requestContext")] 23 | pub request_context: Option, 24 | pub body: Option, 25 | } 26 | 27 | pub type StatusCode = u16; 28 | #[derive(Serialize, Deserialize)] 29 | pub struct ApiGatewayResponse { 30 | #[serde(rename = "isBase64Encoded")] 31 | is_base64_encoded: bool, 32 | #[serde(rename = "statusCode")] 33 | status_code: StatusCode, 34 | headers: HashMap, 35 | body: String, 36 | } 37 | 38 | #[derive(Serialize, Deserialize)] 39 | pub struct ApiGatewayError { 40 | pub code: StatusCode, 41 | pub desc: String, 42 | pub message: String, 43 | } 44 | 45 | #[derive(Copy, Clone, Debug, Serialize, Deserialize)] 46 | pub enum ApiGatewayErrorCode { 47 | NotFound = 404, 48 | FunctionError = 520, 49 | } 50 | 51 | #[derive(Serialize, Deserialize, Clone, Debug)] 52 | pub struct ApiGatewayRequestContext { 53 | pub authorizer: Option, 54 | pub identity: Option, 55 | } 56 | 57 | #[derive(Serialize, Deserialize, Clone, Debug)] 58 | pub struct ApiGatewayRequestContextAuthorizer { 59 | pub claims: Option>, 60 | pub scopes: Option>, 61 | } 62 | 63 | #[derive(Serialize, Deserialize, Clone, Debug)] 64 | pub struct ApiGatewayRequestContextIdentity { 65 | #[serde(rename = "accessKey")] 66 | pub access_key: Option, 67 | #[serde(rename = "accountId")] 68 | pub account_id: Option, 69 | pub caller: Option, 70 | #[serde(rename = "cognitoAmr")] 71 | pub cognito_amr: Option, 72 | #[serde(rename = "cognitoAuthenticationProvider")] 73 | pub cognito_authentication_provider: Option, 74 | #[serde(rename = "cognitoAuthenticationType")] 75 | pub cognito_authentication_type: Option, 76 | #[serde(rename = "cognitoIdentityId")] 77 | pub cognito_identity_id: Option, 78 | #[serde(rename = "cognitoIdentityPoolId")] 79 | pub cognito_identity_pool_id: Option, 80 | #[serde(rename = "principalOrgId")] 81 | pub principal_org_id: Option, 82 | #[serde(rename = "sourceIp")] 83 | pub source_ip: String, 84 | pub user: Option, 85 | #[serde(rename = "userAgent")] 86 | pub user_agent: Option, 87 | #[serde(rename = "userArn")] 88 | pub user_arn: Option, 89 | } 90 | 91 | // #[macro_export] 92 | // macro_rules! handler { 93 | // ($context:ident: $type:ty, $async_handler:expr) => { 94 | // #[no_mangle] 95 | // pub fn handler() -> i32 { 96 | // use assemblylift_core_io_guest; 97 | // use assemblylift_core_io_guest::get_time; 98 | // use direct_executor; 99 | // 100 | // let client = AwsLambdaClient::new(); 101 | // let mut fib = std::io::BufReader::new(assemblylift_core_io_guest::FunctionInputBuffer::new()); 102 | // 103 | // let event = match serde_json::from_reader(fib) { 104 | // Ok(event) => event, 105 | // Err(why) => { 106 | // AwsLambdaClient::console_log(format!( 107 | // "ERROR deserializing Lambda Event: {}", 108 | // why.to_string() 109 | // )); 110 | // return -1; 111 | // } 112 | // }; 113 | // 114 | // let $context: $type = LambdaContext { client, event, _phantom: std::marker::PhantomData }; 115 | // 116 | // direct_executor::run_spinning($async_handler); 117 | // 118 | // 0 119 | // } 120 | // }; 121 | // } 122 | -------------------------------------------------------------------------------- /crates/runtimes/aws-lambda/host/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-awslambda-host" 3 | version = "0.4.0-beta.0" 4 | description = "AssemblyLift AWS Lambda runtime" 5 | authors = ["Akkoro and the AssemblyLift contributors "] 6 | edition = "2021" 7 | license-file = "../../../LICENSE.md" 8 | repository = "https://github.com/akkoro/assemblylift" 9 | readme = "README.md" 10 | 11 | [[bin]] 12 | name = "bootstrap" 13 | path = "src/main.rs" 14 | 15 | [dependencies] 16 | anyhow = "1" 17 | clap = { version = "3.0", features = ["cargo"] } 18 | lambda_runtime = "0.8" 19 | serde_json = "1" 20 | toml = "0.5" 21 | tokio = { version = "1.4", features = ["macros", "sync", "rt", "rt-multi-thread"] } 22 | tracing = "0.1" 23 | tracing-subscriber = "0.3" 24 | zip = "0.6" 25 | 26 | assemblylift_core = { version = "0.4.0-beta.0", package = "assemblylift-core", path = "../../../core" } 27 | assemblylift_core_iomod = { version = "0.4.0-beta.0", package = "assemblylift-core-iomod", path = "../../../core/iomod" } 28 | assemblylift-wasi-secrets-in-memory = { path = "../../../runtimes/components/wasi-secrets/in-memory" } 29 | -------------------------------------------------------------------------------- /crates/runtimes/aws-lambda/host/README.md: -------------------------------------------------------------------------------- 1 | assemblylift-awslambda-host 2 | --------------------------- 3 | 4 | AssemblyLift AWS Lambda runtime 5 | -------------------------------------------------------------------------------- /crates/runtimes/aws-lambda/host/src/abi.rs: -------------------------------------------------------------------------------- 1 | use assemblylift_core::wasm::StatusTx; 2 | use assemblylift_core::{KeysAbi, RuntimeAbi, SecretsAbi}; 3 | use assemblylift_wasi_secrets_in_memory::InMemorySecrets; 4 | 5 | #[derive(Clone)] 6 | pub enum Status { 7 | Success((Option, serde_json::Value)), 8 | Failure((Option, serde_json::Value)), 9 | } 10 | 11 | pub struct Abi; 12 | 13 | impl KeysAbi for Abi { 14 | fn encrypt(id: String, plaintext: Vec) -> anyhow::Result> { 15 | InMemorySecrets::encrypt(id, plaintext) 16 | } 17 | 18 | fn decrypt(id: String, ciphertext: Vec) -> anyhow::Result> { 19 | InMemorySecrets::decrypt(id, ciphertext) 20 | } 21 | } 22 | 23 | impl SecretsAbi for Abi { 24 | fn get_secret(id: String) -> anyhow::Result> { 25 | // TODO detect secret manager from id, e.g. AWS should be an ARN 26 | // may need to enforce prefixes for other managers e.g. vault/adfuuid-deadb33f-adfd 27 | InMemorySecrets::get_secret(id) 28 | } 29 | 30 | fn set_secret(id: String, value: Vec, key_id: Option) -> anyhow::Result<()> { 31 | InMemorySecrets::set_secret(id, value, key_id) 32 | } 33 | } 34 | 35 | impl RuntimeAbi for Abi { 36 | fn success(status_tx: StatusTx, response: Vec, request_id: Option) { 37 | let response = serde_json::from_slice(response.as_slice()).unwrap(); 38 | status_tx 39 | .send(Status::Success((request_id, response))) 40 | .unwrap(); 41 | } 42 | 43 | fn failure(status_tx: StatusTx, response: Vec, request_id: Option) { 44 | let response = serde_json::from_slice(response.as_slice()).unwrap(); 45 | status_tx 46 | .send(Status::Failure((request_id, response))) 47 | .unwrap(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/runtimes/components/wasi-secrets/in-memory/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-wasi-secrets-in-memory" 3 | version = "0.0.0" 4 | description = "A simple in-memory secrets manager to develop wasi-secrets" 5 | edition = "2021" 6 | 7 | [dependencies] 8 | anyhow = "1" 9 | chacha20poly1305 = "0.10" 10 | once_cell = "1.17" 11 | rand = "0.8" 12 | tracing = "0.1" 13 | 14 | assemblylift-core = { version = "0.4.0-beta.0", path = "../../../../core" } 15 | -------------------------------------------------------------------------------- /crates/runtimes/components/wasi-secrets/in-memory/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::sync::Mutex; 3 | 4 | use anyhow::anyhow; 5 | use chacha20poly1305::aead::Aead; 6 | use chacha20poly1305::{ChaCha20Poly1305, Key, KeyInit, Nonce}; 7 | use once_cell::sync::Lazy; 8 | use rand::RngCore; 9 | use tracing::info; 10 | 11 | use assemblylift_core::{KeysAbi, SecretsAbi}; 12 | 13 | static KEYS: Lazy>> = Lazy::new(|| { 14 | let mut map = BTreeMap::new(); 15 | map.insert( 16 | "default".to_string(), 17 | b"This key is not secure\0\0\0\0\0\0\0\0\0\0", 18 | ); 19 | Mutex::new(map) 20 | }); 21 | static SECRETS: Lazy)>>> = 22 | Lazy::new(|| Mutex::new(BTreeMap::new())); 23 | 24 | pub struct InMemorySecrets; 25 | 26 | impl KeysAbi for InMemorySecrets { 27 | fn encrypt(id: String, plaintext: Vec) -> anyhow::Result> { 28 | info!("encrypting with key_id={}", &id); 29 | 30 | let mut rng = rand::thread_rng(); 31 | let mut nonce_bytes: [u8; 12] = [0; 12]; 32 | rng.fill_bytes(&mut nonce_bytes); 33 | 34 | let keys = KEYS.lock().unwrap(); 35 | let key_bytes = keys.get(&*id).unwrap(); 36 | let key = Key::from_slice(*key_bytes); 37 | let cipher = ChaCha20Poly1305::new(&key); 38 | let nonce = Nonce::from(nonce_bytes); 39 | 40 | let encrypted = cipher 41 | .encrypt(&nonce, &*plaintext) 42 | .map_err(|e| anyhow!(e.to_string()))?; 43 | 44 | let mut ret: Vec = Vec::new(); 45 | ret.extend(nonce_bytes); 46 | ret.extend(encrypted); 47 | 48 | Ok(ret) 49 | } 50 | 51 | fn decrypt(id: String, ciphertext: Vec) -> anyhow::Result> { 52 | info!("decrypting with key_id={}", &id); 53 | 54 | let raw_data: &[u8] = ciphertext.as_ref(); 55 | if raw_data.len() <= 44 { 56 | return Err(anyhow!("Encrypted data too short")); 57 | } 58 | 59 | let mut nonce_bytes: [u8; 12] = [0; 12]; 60 | nonce_bytes.clone_from_slice(&raw_data[..12]); 61 | 62 | let mut sealed: Vec = Vec::new(); 63 | sealed.extend(&raw_data[12..]); 64 | 65 | let keys = KEYS.lock().unwrap(); 66 | let key_bytes = keys.get(&*id).unwrap(); 67 | let key = Key::from_slice(key_bytes.as_ref()); 68 | let cipher = ChaCha20Poly1305::new(&key); 69 | let nonce = Nonce::from(nonce_bytes); 70 | 71 | let decrypted = cipher 72 | .decrypt(&nonce, sealed.as_ref()) 73 | .map_err(|e| anyhow!(e.to_string()))?; 74 | 75 | Ok(decrypted) 76 | } 77 | } 78 | 79 | impl SecretsAbi for InMemorySecrets { 80 | fn get_secret(id: String) -> anyhow::Result> { 81 | info!("retrieving secret id={}", &id); 82 | // TODO detect secret manager from id, e.g. AWS should be an ARN 83 | // may need to enforce prefixes for other managers e.g. vault/adfuuid-deadb33f-adfd 84 | let secrets = SECRETS.lock().unwrap(); 85 | let secret_pair = secrets.get(&id.clone()).unwrap(); 86 | Ok(Self::decrypt(secret_pair.0.clone(), secret_pair.1.clone())?) 87 | } 88 | 89 | fn set_secret(id: String, value: Vec, key_id: Option) -> anyhow::Result<()> { 90 | info!("storing secret id={}", &id); 91 | let key_id = key_id.unwrap_or("default".to_string()); 92 | let ciphertext = Self::encrypt(key_id.clone(), value)?; 93 | SECRETS 94 | .lock() 95 | .unwrap() 96 | .insert(id.clone(), (key_id.clone(), ciphertext)); 97 | Ok(()) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /crates/runtimes/hyper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-hyper-runtime" 3 | version = "0.4.0-beta.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "assemblylift-hyper-runtime" 8 | path = "src/main.rs" 9 | 10 | [lib] 11 | path = "src/lib.rs" 12 | 13 | [dependencies] 14 | anyhow = "1" 15 | base64 = "0.13" 16 | chrono = "0.4" 17 | clap = { version = "4", features = ["cargo"] } 18 | crossbeam-channel = "0.5" 19 | crossbeam-utils = "0.8" 20 | hyper = { version = "0.14", features = ["full"] } 21 | once_cell = "1" 22 | serde = "1" 23 | serde_json = "1" 24 | timer = "0.2" 25 | tokio = { version = "1", features = ["full"] } 26 | tracing = "0.1" 27 | tracing-subscriber = "0.3" 28 | url = "2.3" 29 | zip = "0.6" 30 | 31 | assemblylift-core = { version = "0.4.0-beta.0", path = "../../core" } 32 | assemblylift-core-iomod = { version = "0.4.0-beta.0", path = "../../core/iomod" } 33 | assemblylift-wasi-secrets-in-memory = { path = "../../runtimes/components/wasi-secrets/in-memory" } 34 | -------------------------------------------------------------------------------- /crates/runtimes/hyper/src/abi.rs: -------------------------------------------------------------------------------- 1 | use tracing::error; 2 | 3 | use assemblylift_core::wasm::StatusTx; 4 | use assemblylift_core::{KeysAbi, RuntimeAbi, SecretsAbi}; 5 | use assemblylift_wasi_secrets_in_memory::InMemorySecrets; 6 | 7 | use crate::Status; 8 | 9 | pub struct Abi; 10 | 11 | impl KeysAbi for Abi { 12 | fn encrypt(id: String, plaintext: Vec) -> anyhow::Result> { 13 | InMemorySecrets::encrypt(id, plaintext) 14 | } 15 | 16 | fn decrypt(id: String, ciphertext: Vec) -> anyhow::Result> { 17 | InMemorySecrets::decrypt(id, ciphertext) 18 | } 19 | } 20 | 21 | impl SecretsAbi for Abi { 22 | fn get_secret(id: String) -> anyhow::Result> { 23 | // TODO detect secret manager from id, e.g. AWS should be an ARN 24 | // may need to enforce prefixes for other managers e.g. vault/adfuuid-deadb33f-adfd 25 | InMemorySecrets::get_secret(id) 26 | } 27 | 28 | fn set_secret(id: String, value: Vec, key_id: Option) -> anyhow::Result<()> { 29 | InMemorySecrets::set_secret(id, value, key_id) 30 | } 31 | } 32 | 33 | impl RuntimeAbi for Abi { 34 | fn success(status_tx: StatusTx, response: Vec, _request_id: Option) { 35 | std::thread::spawn(move || { 36 | if let Err(e) = status_tx.send(Status::Success(response)) { 37 | error!("could not send status: {:?}", e.to_string()) 38 | } 39 | }); 40 | } 41 | 42 | fn failure(status_tx: StatusTx, response: Vec, _request_id: Option) { 43 | std::thread::spawn(move || { 44 | if let Err(e) = status_tx.send(Status::Failure(response)) { 45 | error!("could not send status: {:?}", e.to_string()) 46 | } 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/runtimes/hyper/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use assemblylift_core_iomod::registry::RegistryTx; 4 | 5 | use crate::launcher::Launcher; 6 | use crate::runner::Runner; 7 | 8 | pub mod abi; 9 | pub mod launcher; 10 | pub mod runner; 11 | 12 | #[derive(Debug, Clone)] 13 | pub enum Status { 14 | Exited(i32), 15 | Success(Vec), 16 | Failure(Vec), 17 | } 18 | 19 | pub fn spawn_runtime(registry_tx: RegistryTx) { 20 | // Mapped to /tmp inside the WASM module 21 | std::fs::create_dir_all("/tmp/asmltmp").expect("could not create /tmp/asmltmp"); 22 | 23 | crossbeam_utils::thread::scope(|s| { 24 | let runner = Arc::new(Mutex::new(Runner::::new(registry_tx))); 25 | let tx = { runner.clone().lock().unwrap().sender() }; 26 | 27 | let r = runner.clone(); 28 | s.spawn(move |_| r.lock().unwrap().spawn()); 29 | 30 | s.spawn(move |_| { 31 | let mut launcher = Launcher::new(); 32 | launcher.spawn(tx); 33 | }); 34 | }) 35 | .unwrap(); 36 | } 37 | -------------------------------------------------------------------------------- /crates/runtimes/hyper/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use clap::crate_version; 4 | use tracing::{info, Level}; 5 | use tracing_subscriber::FmtSubscriber; 6 | 7 | use assemblylift_core_iomod::registry; 8 | use assemblylift_core_iomod::registry::registry_channel; 9 | use assemblylift_hyper_runtime::spawn_runtime; 10 | 11 | fn main() { 12 | let default_level = "info".to_string(); 13 | let log_level = Level::from_str( 14 | std::env::args() 15 | .collect::>() 16 | .get(1) 17 | .unwrap_or(&default_level), 18 | ) 19 | .unwrap_or(Level::INFO); 20 | let subscriber = FmtSubscriber::builder().with_max_level(log_level).finish(); 21 | 22 | tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); 23 | 24 | info!( 25 | "Starting AssemblyLift Hyper HTTP runtime v{}", 26 | crate_version!() 27 | ); 28 | 29 | let (registry_tx, registry_rx) = registry_channel(32); 30 | registry::spawn_registry(registry_rx).unwrap(); 31 | 32 | spawn_runtime(registry_tx) 33 | } 34 | -------------------------------------------------------------------------------- /crates/tools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "assemblylift-tools" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1" 10 | flate2 = "1" 11 | itertools = "0.10" 12 | serde_json = "1" 13 | tar = "0.4" 14 | zip = "0.6" 15 | 16 | [dependencies.reqwest] 17 | version = "0.11" 18 | features = ["blocking", "json"] 19 | -------------------------------------------------------------------------------- /crates/tools/src/cmctl.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::process::Command; 3 | 4 | use crate::Tool; 5 | 6 | pub struct CmCtl { 7 | cmd: String, 8 | path: String, 9 | kubeconfig: Option, 10 | } 11 | 12 | impl Default for CmCtl { 13 | fn default() -> Self { 14 | CmCtl::new("cmctl", ".asml/bin", None) 15 | } 16 | } 17 | 18 | impl CmCtl { 19 | pub fn new(name: &str, path: &str, kubeconfig: Option) -> Self { 20 | let s = Self { 21 | cmd: name.into(), 22 | path: path.into(), 23 | kubeconfig, 24 | }; 25 | crate::fetch(&s).unwrap(); 26 | s 27 | } 28 | 29 | pub fn default_with_config(kubeconfig: String) -> Self { 30 | Self::new("cmctl", ".asml/bin", Some(kubeconfig)) 31 | } 32 | 33 | pub fn install(&self) { 34 | println!("Installing cert-manager"); 35 | let kubeconfig = match &self.kubeconfig { 36 | Some(cfg) => vec!["--kubeconfig", &cfg], 37 | None => Vec::default(), 38 | }; 39 | self.command() 40 | .args(kubeconfig) 41 | .args(vec!["x", "install"]) 42 | .output() 43 | .expect("cmctl could not install cert-manager"); 44 | } 45 | } 46 | 47 | impl Tool for CmCtl { 48 | fn command_name(&self) -> &str { 49 | self.cmd.as_str() 50 | } 51 | 52 | fn command_path(&self) -> PathBuf { 53 | Path::new(&format!("{}/{}", self.path, self.cmd)).into() 54 | } 55 | 56 | fn command(&self) -> Command { 57 | Command::new(self.command_path()) 58 | } 59 | 60 | fn path(&self) -> &str { 61 | self.path.as_str() 62 | } 63 | 64 | fn fetch_url(&self) -> &str { 65 | #[cfg(target_os = "linux")] 66 | return "https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cmctl-linux-amd64.tar.gz"; 67 | #[cfg(target_os = "macos")] 68 | return "https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cmctl-darwin-amd64.tar.gz"; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/tools/src/glooctl.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::process::Command; 3 | 4 | use crate::Tool; 5 | 6 | pub struct GlooCtl { 7 | cmd: String, 8 | path: String, 9 | kubeconfig: Option, 10 | } 11 | 12 | impl Default for GlooCtl { 13 | fn default() -> Self { 14 | GlooCtl::new("glooctl", ".asml/bin", None) 15 | } 16 | } 17 | 18 | impl GlooCtl { 19 | pub fn new(name: &str, path: &str, kubeconfig: Option) -> Self { 20 | let s = Self { 21 | cmd: name.into(), 22 | path: path.into(), 23 | kubeconfig, 24 | }; 25 | crate::fetch(&s).unwrap(); 26 | s 27 | } 28 | 29 | pub fn default_with_config(kubeconfig: String) -> Self { 30 | Self::new("glooctl", ".asml/bin", Some(kubeconfig)) 31 | } 32 | 33 | pub fn install_gateway(&self) { 34 | println!("Installing Gloo API Gateway"); 35 | let mut args = vec!["install", "gateway"]; 36 | if let Some(cfg_path) = &self.kubeconfig { 37 | args.append(&mut vec!["--kubeconfig", &cfg_path]); 38 | } 39 | self.command() 40 | .args(args) 41 | .output() 42 | .expect("glooctl could not install gloo gateway"); 43 | } 44 | 45 | #[allow(dead_code)] 46 | pub fn uninstall_gateway(&self) { 47 | println!("Uninstalling Gloo API Gateway"); 48 | self.command() 49 | .args(vec!["uninstall", "gateway"]) 50 | .output() 51 | .expect("glooctl could not uninstall gloo gateway"); 52 | } 53 | } 54 | 55 | impl Tool for GlooCtl { 56 | fn command_name(&self) -> &str { 57 | self.cmd.as_str() 58 | } 59 | 60 | fn command_path(&self) -> PathBuf { 61 | Path::new(&format!("{}/{}", self.path, self.cmd)).into() 62 | } 63 | 64 | fn command(&self) -> Command { 65 | Command::new(self.command_path()) 66 | } 67 | 68 | fn path(&self) -> &str { 69 | self.path.as_str() 70 | } 71 | 72 | fn fetch_url(&self) -> &str { 73 | #[cfg(target_os = "linux")] 74 | return "https://github.com/solo-io/gloo/releases/download/v1.15.18/glooctl-linux-amd64"; 75 | #[cfg(target_os = "macos")] 76 | return "https://github.com/solo-io/gloo/releases/download/v1.15.18/glooctl-darwin-amd64"; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/tools/src/kubectl.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::path::{Path, PathBuf}; 3 | use std::process::{Command, Stdio}; 4 | 5 | // use itertools::Itertools; 6 | use serde_json::Value; 7 | 8 | use crate::Tool; 9 | 10 | pub struct KubeCtl { 11 | cmd: String, 12 | path: String, 13 | kubeconfig: Option, 14 | } 15 | 16 | impl Default for KubeCtl { 17 | fn default() -> Self { 18 | KubeCtl::new("kubectl", ".asml/bin", None) 19 | } 20 | } 21 | 22 | impl KubeCtl { 23 | pub fn new(name: &str, path: &str, kubeconfig: Option) -> Self { 24 | let s = Self { 25 | cmd: name.into(), 26 | path: path.into(), 27 | kubeconfig, 28 | }; 29 | crate::fetch(&s).unwrap(); 30 | s 31 | } 32 | 33 | pub fn default_with_config(kubeconfig: String) -> Self { 34 | Self::new("kubectl", ".asml/bin", Some(kubeconfig)) 35 | } 36 | 37 | pub fn get_in_namespace( 38 | &self, 39 | kind: &str, 40 | ns: &str, 41 | labels: Option>, 42 | ) -> Result { 43 | let label_args = labels 44 | .unwrap_or(Default::default()) 45 | .into_iter() 46 | .map(|l| { 47 | vec![ 48 | "-l".to_string(), 49 | format!("{}={}", l.0.to_owned(), l.1.to_owned()), 50 | ] 51 | }) 52 | .reduce(|accum, mut v| { 53 | let mut a = accum; 54 | a.append(&mut v); 55 | a 56 | }) 57 | .unwrap_or(Default::default()); 58 | let kubeconfig = match &self.kubeconfig { 59 | Some(cfg) => vec![format!("--kubeconfig={}", cfg)], 60 | None => Vec::default(), 61 | }; 62 | let child = self 63 | .command() 64 | .args(kubeconfig) 65 | .args(vec!["get", kind]) 66 | .args(vec!["-n", ns]) 67 | .args(label_args) 68 | .args(vec!["-o", "json"]) 69 | .stdin(Stdio::piped()) 70 | .stdout(Stdio::piped()) 71 | .spawn() 72 | .unwrap(); 73 | let output = child.wait_with_output().unwrap(); 74 | let json = std::str::from_utf8(&*output.stdout).unwrap(); 75 | Ok(serde_json::from_str(json).unwrap()) 76 | } 77 | } 78 | 79 | impl Tool for KubeCtl { 80 | fn command_name(&self) -> &str { 81 | self.cmd.as_str() 82 | } 83 | 84 | fn command_path(&self) -> PathBuf { 85 | Path::new(&format!("{}/{}", self.path, self.cmd)).into() 86 | } 87 | 88 | fn command(&self) -> Command { 89 | Command::new(self.command_path()) 90 | } 91 | 92 | fn path(&self) -> &str { 93 | self.path.as_str() 94 | } 95 | 96 | fn fetch_url(&self) -> &str { 97 | #[cfg(target_os = "linux")] 98 | return "https://dl.k8s.io/release/v1.28.2/bin/linux/amd64/kubectl"; 99 | #[cfg(target_os = "macos")] 100 | return "https://dl.k8s.io/release/v1.28.2/bin/darwin/amd64/kubectl"; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crates/tools/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | use std::os::unix::fs::PermissionsExt; 3 | use std::path::{Path, PathBuf}; 4 | use std::process::Command; 5 | 6 | use anyhow::anyhow; 7 | use flate2::read::GzDecoder; 8 | 9 | pub mod cmctl; 10 | pub mod glooctl; 11 | pub mod kubectl; 12 | pub mod terraform; 13 | 14 | pub trait Tool { 15 | fn command_name(&self) -> &str; 16 | fn command_path(&self) -> PathBuf; 17 | fn command(&self) -> Command; 18 | fn path(&self) -> &str; 19 | fn fetch_url(&self) -> &str; 20 | } 21 | 22 | pub fn fetch(tool: &T) -> anyhow::Result<()> 23 | where 24 | T: Tool + Sized, 25 | { 26 | if !tool.command_path().exists() { 27 | println!("🔧 > Fetching tool {}", tool.command_name()); 28 | 29 | std::fs::create_dir_all(tool.path().clone()).unwrap(); 30 | let bytes = download_to_bytes(tool.fetch_url()) 31 | .expect(&*format!("could not download {}", tool.command_name())); 32 | 33 | if tool.fetch_url().contains(".tar.gz") { 34 | // FIXME this leans on the assumption that the only gzipped tool we fetch is cmctl 35 | let tar = GzDecoder::new(bytes.as_slice()); 36 | let mut ar = tar::Archive::new(tar); 37 | ar.entries() 38 | .expect("cmctl archive is empty") 39 | .find(|e| e.as_ref().unwrap().path().unwrap().file_name().unwrap() == "cmctl") 40 | .expect("cmctl not found in archive") 41 | .unwrap() 42 | .unpack(tool.command_path()) 43 | .map_err(|err| anyhow!("could not unpack cmctl: {}", err.to_string()))?; 44 | } else if tool.fetch_url().contains(".zip") { 45 | // FIXME this leans on the assumption that the only zipped tool we fetch is terraform 46 | unzip_terraform(bytes, tool.command_path().to_str().unwrap())?; 47 | } else { 48 | std::fs::write(tool.command_path(), bytes) 49 | .map_err(|err| anyhow!("could not unpack {}: {}", tool.command_name(), err.to_string()))?; 50 | } 51 | 52 | let mut perms = std::fs::metadata(tool.command_path()) 53 | .unwrap() 54 | .permissions(); 55 | perms.set_mode(0o755); 56 | std::fs::set_permissions(tool.command_path(), perms).map_err(|err| anyhow!( 57 | "could not set {:?} binary executable (octal 755) permissions: {}", 58 | tool.command_path(), 59 | err.to_string(), 60 | ) 61 | )?; 62 | } 63 | 64 | Ok(()) 65 | } 66 | 67 | pub fn download_to_bytes(url: T) -> anyhow::Result> { 68 | println!("⏬ > Downloading object from {}...", url.as_str()); 69 | match reqwest::blocking::get(url.clone()) { 70 | Ok(mut response) => { 71 | if !response.status().is_success() { 72 | return Err(anyhow!("unable to download file from {}", url.as_str())); 73 | } 74 | let mut response_buffer = Vec::new(); 75 | if let Err(err) = response.read_to_end(&mut response_buffer) { 76 | return Err(anyhow!(err)); 77 | } 78 | 79 | Ok(response_buffer) 80 | } 81 | Err(err) => Err(anyhow!(err)), 82 | } 83 | } 84 | 85 | pub fn download_to_path>( 86 | url: T, 87 | to: P, 88 | ) -> anyhow::Result<()> { 89 | match download_to_bytes(url) { 90 | Ok(bytes) => { 91 | if let Err(err) = std::fs::write(to, bytes) { 92 | return Err(anyhow!(err)); 93 | } 94 | 95 | Ok(()) 96 | } 97 | Err(err) => Err(anyhow!(err)), 98 | } 99 | } 100 | 101 | fn unzip_terraform(bytes_in: Vec, out_dir: &str) -> anyhow::Result<()> { 102 | let reader = std::io::Cursor::new(bytes_in); 103 | let mut archive = zip::ZipArchive::new(reader).unwrap(); 104 | let mut file_out = archive.by_name("terraform").unwrap(); 105 | 106 | let mut outfile = std::fs::File::create(out_dir).unwrap(); 107 | match std::io::copy(&mut file_out, &mut outfile) { 108 | Ok(_) => Ok(()), 109 | Err(why) => Err(anyhow!(why.to_string())), 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crates/tools/src/terraform.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::process::{Command, Stdio}; 3 | 4 | use crate::Tool; 5 | 6 | pub struct Terraform { 7 | cmd: String, 8 | path: String, 9 | } 10 | 11 | impl Default for Terraform { 12 | fn default() -> Self { 13 | Terraform::new("terraform", ".asml/bin") 14 | } 15 | } 16 | 17 | impl Terraform { 18 | pub fn new(name: &str, path: &str) -> Self { 19 | let s = Self { 20 | cmd: name.into(), 21 | path: path.into(), 22 | }; 23 | crate::fetch(&s).unwrap(); 24 | s 25 | } 26 | 27 | pub fn init(&self) { 28 | let mut terraform_result = self.command() 29 | .arg("-chdir=./net") 30 | .arg("init") 31 | .stdout(Stdio::inherit()) 32 | .stderr(Stdio::inherit()) 33 | .spawn() 34 | .unwrap(); 35 | 36 | match terraform_result.wait() { 37 | Ok(_) => {} 38 | Err(_) => {} 39 | } 40 | } 41 | 42 | pub fn plan(&self) { 43 | let mut terraform_result = self.command() 44 | .arg("-chdir=./net") 45 | .arg("plan") 46 | .arg("-out=./plan") 47 | .arg("-state=../terraform.tfstate") 48 | .stdout(Stdio::inherit()) 49 | .stderr(Stdio::inherit()) 50 | .spawn() 51 | .unwrap(); 52 | 53 | match terraform_result.wait() { 54 | Ok(_) => {} 55 | Err(_) => {} 56 | } 57 | } 58 | 59 | pub fn apply(&self) { 60 | let mut terraform_result = self.command() 61 | .arg("-chdir=./net") 62 | .arg("apply") 63 | .arg("-state=../terraform.tfstate") 64 | .stdout(Stdio::inherit()) 65 | .stderr(Stdio::inherit()) 66 | .spawn() 67 | .unwrap(); 68 | 69 | match terraform_result.wait() { 70 | Ok(_) => {} 71 | Err(_) => {} 72 | } 73 | } 74 | 75 | pub fn destroy(&self) { 76 | let mut terraform_result = self.command() 77 | .arg("-chdir=./net") 78 | .arg("destroy") 79 | .arg("-state=../terraform.tfstate") 80 | .stdout(Stdio::inherit()) 81 | .stderr(Stdio::inherit()) 82 | .spawn() 83 | .unwrap(); 84 | 85 | match terraform_result.wait() { 86 | Ok(_) => {} 87 | Err(_) => {} 88 | } 89 | } 90 | } 91 | 92 | impl Tool for Terraform { 93 | fn command_name(&self) -> &str { 94 | self.cmd.as_str() 95 | } 96 | 97 | fn command_path(&self) -> PathBuf { 98 | Path::new(&format!("{}/{}", self.path, self.cmd)).into() 99 | } 100 | 101 | fn command(&self) -> Command { 102 | Command::new(self.command_path()) 103 | } 104 | 105 | fn path(&self) -> &str { 106 | self.path.as_str() 107 | } 108 | 109 | fn fetch_url(&self) -> &str { 110 | #[cfg(target_os = "linux")] 111 | return "https://releases.hashicorp.com/terraform/1.4.6/terraform_1.4.6_linux_amd64.zip"; 112 | #[cfg(target_os = "macos")] 113 | return "https://releases.hashicorp.com/terraform/1.4.6/terraform_1.4.6_darwin_amd64.zip"; 114 | #[cfg(target_os = "freebsd")] 115 | return "https://releases.hashicorp.com/terraform/1.4.6/terraform_1.4.6_freebsd_amd64.zip"; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /docker/asml-hyper-debian: -------------------------------------------------------------------------------- 1 | # Builder 2 | #FROM rust:1.66-alpine3.16 as builder 3 | FROM rust:1.74-buster as builder 4 | #RUN rustup target add x86_64-unknown-linux-musl 5 | #RUN apk --no-cache add capnproto-dev musl-dev 6 | RUN apt-get update && apt-get install build-essential capnproto -y 7 | ENV USER=root 8 | 9 | WORKDIR /usr/src/assemblylift 10 | COPY . . 11 | 12 | #RUN cd ./runtimes/hyper && cargo install --target x86_64-unknown-linux-musl --path . 13 | RUN cd ./crates/runtimes/hyper && cargo install --path . 14 | 15 | 16 | # Runner Image 17 | #FROM alpine:3.16 as runner 18 | FROM debian:buster-slim as runner 19 | #RUN apk --no-cache add curl ca-certificates \ 20 | # && addgroup -S app && adduser -S -g app app 21 | RUN apt-get update && apt-get install ca-certificates openssl -y 22 | ENV USER=app 23 | 24 | COPY --from=builder /usr/local/cargo/bin/assemblylift-hyper-runtime /usr/bin/assemblylift-hyper-runtime 25 | RUN chmod +x /usr/bin/assemblylift-hyper-runtime 26 | 27 | # HTTP 28 | EXPOSE 5543 29 | # CAPNP 30 | EXPOSE 13555 31 | 32 | CMD ["assemblylift-hyper-runtime"] 33 | -------------------------------------------------------------------------------- /docker/asml-lambda-alpine: -------------------------------------------------------------------------------- 1 | ## Build image 2 | FROM ubuntu:20.10 AS builder 3 | 4 | RUN apt-get update && apt-get install build-essential zlib1g-dev libffi-dev libtinfo-dev curl libssl-dev libxml2-dev pkg-config wget xz-utils -y 5 | 6 | RUN wget https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.1/clang+llvm-11.0.1-x86_64-linux-gnu-ubuntu-20.10.tar.xz 7 | RUN tar -xvf clang+llvm-11.0.1-x86_64-linux-gnu-ubuntu-20.10.tar.xz && mv clang+llvm-11.0.1-x86_64-linux-gnu-ubuntu-20.10 llvm110 8 | RUN /llvm110/bin/clang-11 --version 9 | RUN rm clang+llvm-11.0.1-x86_64-linux-gnu-ubuntu-20.10.tar.xz 10 | 11 | RUN wget https://capnproto.org/capnproto-c++-0.8.0.tar.gz && tar zxf capnproto-c++-0.8.0.tar.gz && \ 12 | cd capnproto-c++-0.8.0 && ./configure && make -j6 check && make install 13 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y 14 | 15 | WORKDIR /usr/src/assemblylift 16 | COPY . . 17 | 18 | RUN LLVM_SYS_110_PREFIX=/llvm110 $HOME/.cargo/bin/cargo build --release 19 | 20 | 21 | ## Prod image 22 | FROM frolvlad/alpine-glibc:glibc-2.32 23 | 24 | COPY --from=builder /usr/src/assemblylift/target/release/bootstrap / 25 | ENTRYPOINT /bootstrap 26 | -------------------------------------------------------------------------------- /docker/asml-lambda-default: -------------------------------------------------------------------------------- 1 | FROM amazonlinux:2018.03 2 | 3 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y 4 | RUN yum install diffutils gcc72 gcc72-c++ openssl-devel pkg-config capnproto -y 5 | 6 | RUN curl -O https://capnproto.org/capnproto-c++-0.10.3.tar.gz && tar zxf capnproto-c++-0.10.3.tar.gz && \ 7 | cd capnproto-c++-0.10.3 && ./configure && make -j6 check && make install 8 | 9 | WORKDIR /usr/src/assemblylift 10 | COPY . . 11 | 12 | RUN cd ./crates/runtimes/aws-lambda/host && $HOME/.cargo/bin/cargo build --release 13 | 14 | CMD cat crates/cli/Cargo.toml | grep version -m 1 | awk '{print $3}' | sed 's/"//g' 15 | -------------------------------------------------------------------------------- /docker/asml-openfaas-alpine: -------------------------------------------------------------------------------- 1 | # Builder 2 | FROM openfaas/of-watchdog:0.8.2 as watchdog 3 | FROM rust:1.59-alpine as builder 4 | RUN rustup target add x86_64-unknown-linux-musl 5 | RUN apk --no-cache add capnproto-dev musl-dev 6 | ENV USER=root 7 | 8 | WORKDIR /usr/src/assemblylift 9 | COPY . . 10 | 11 | RUN cd ./runtimes/openfaas && cargo install --target x86_64-unknown-linux-musl --path . 12 | 13 | # Runner Image 14 | FROM alpine:3.15 as runner 15 | RUN apk --no-cache add curl ca-certificates \ 16 | && addgroup -S app && adduser -S -g app app 17 | ENV USER=app 18 | 19 | COPY --from=builder /usr/local/cargo/bin/assemblylift-openfaas /usr/bin/assemblylift-openfaas 20 | RUN chmod +x /usr/bin/assemblylift-openfaas 21 | 22 | COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog 23 | RUN chmod +x /usr/bin/fwatchdog 24 | 25 | ENV fprocess="assemblylift-openfaas" 26 | ENV mode="http" 27 | ENV upstream_url="http://127.0.0.1:3000" 28 | 29 | HEALTHCHECK --interval=3s CMD [ -e /tmp/.lock ] || exit 1 30 | 31 | # HTTP 32 | EXPOSE 8080 33 | # RPC 34 | EXPOSE 13555 35 | 36 | CMD ["fwatchdog"] 37 | -------------------------------------------------------------------------------- /docs/AssemblyLift_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkoro/assemblylift/a4f9094b0ed07eb813c9d2f5893a5a23dbca9a08/docs/AssemblyLift_logo.png -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | AssemblyLift Design Docs 2 | ======================== 3 | > This documentation is related to developing AssemblyLift. For documentation related to using AssemblyLift in practice, 4 | > see [the user docs](https://docs.assemblylift.akkoro.io). 5 | 6 | The Design Docs are intended to capture details and explanations of various concepts within the AssemblyLift source code. 7 | 8 | ## CLI 9 | * [Project Transpiler & Infra. Providers](cli-transpiler.md) 10 | 11 | ## Function Runtimes 12 | * [AWS Lambda](rt-lambda.md) 13 | * [Hyper](rt-hyper.md) 14 | 15 | ## Function Languages 16 | * [Rust](lang-rust.md) 17 | * [Ruby](lang-ruby.md) 18 | 19 | ## IO Modules [TODO] 20 | 21 | ## Providers [TODO] 22 | * API 23 | * [Amazon API Gateway](provider-apigw-gloo.md) 24 | * [Gloo API Gateway](provider-apigw-amz.md) 25 | * DNS 26 | * [Amazon Route53](provider-dns-route53.md) 27 | * Service 28 | * [AWS Lambda](provider-service-aws.md) 29 | * [Kubernetes](provider-service-k8s.md) 30 | 31 | ## WebAssembly Core 32 | * [AssemblyLift ABI](core-abi.md) 33 | * [Function Buffers](core-buffers.md) 34 | * [Threader](core-threader.md) 35 | -------------------------------------------------------------------------------- /docs/cli-transpiler.md: -------------------------------------------------------------------------------- 1 | The Transpiler and Providers 2 | ----------------------------- 3 | 4 | The AssemblyLift CLI is at its core a wrapper around a TOML-to-HCL pipeline, contained in the [`transpiler`](../cli/src/transpiler) 5 | and [`providers`](../cli/src/providers) modules. It looks like a transpiler in the loosest sense of the word so that's what it 6 | got named, but _generator_ might be a better word. 7 | 8 | When the `cast` command is invoked, a new [`Context`](../cli/src/transpiler/context.rs) is constructed from the project's 9 | manifests. Context is analogous to an immutable state of the project. 10 | [The `Context` object itself is `Castable`](../cli/src/transpiler/context.rs#L183), and serves as the entrypoint 11 | for casting each [`Provider`](../cli/src/providers/mod.rs#L57). The cast proceeds for each _unique_ service provider (or DNS provider), where each provider 12 | may operate on the entire context or require a "selector". The results (i.e. the [`Artifact`s](../cli/src/transpiler/mod.rs#L51)) from the provider `cast` are concatenated and written 13 | to their respective locations in the `net/` directory (e.g. `net/plan.tf`). 14 | 15 | It's up to each provider to correctly/fully implement the functionality implied by each definition in the `Context`; there's 16 | currently no mechanism to verify the output (if one is even possible). It _is_ required that a `Provider` implements `Castable`, 17 | `Bindable`, and `Bootable`. 18 | 19 | The providers that are currently implemented generate HCL and YAML using embedded moustache/handlebars templates 20 | (for which there is a trait, [`Template`](../cli/src/transpiler/mod.rs#L40)). 21 | 22 | ### The boot step 23 | The `boot` step is currently used only by the [`k8s`](../cli/src/providers/k8s.rs) provider, but exists in general to provide a means to configure the 24 | target environment in some way that is prerequisite to deployment during `bind`. The [`gloo`](../cli/src/providers/gloo/mod.rs) API provider for example uses 25 | `boot` to install `certificate-manager` on the target cluster. 26 | 27 | The `boot` step is invoked for each provider prior to the provider's `bind` step. 28 | 29 | > It should be noted that the Gloo _Gateway_ is actually installed on `cast` for the `k8s` provider, so that the 30 | > CRD's are available to Terraform when planning the K8s manifests. There's probably a cleaner way to do that :) 31 | 32 | ### The bind step 33 | Each `Provider` implements `bind` -- however at the moment the only binding operation is the `terraform apply`. _Apply_ 34 | is executed independent of `Context` as the last step of the `bind` command, since there is only a singular plan file. 35 | It may be worth it (or necessary) to refactor this to a unique plan-per-provider! -------------------------------------------------------------------------------- /docs/core-abi.md: -------------------------------------------------------------------------------- 1 | AssemblyLift WASM ABI 2 | --------------------- 3 | 4 | AssemblyLift provides its own ABI to WebAssembly modules to complement standard ABIs like WASI. AssemblyLift's ABI provides 5 | specific functions for interacting with the Function runtime (e.g. responding with a 'success' message to Lambda), as 6 | well as functions for interacting with IOmods. 7 | 8 | AssemblyLift Function modules also use the ABI to receive function input, rather than relying on _stdin_ from WASI. 9 | This was originally because _stdin_ wasn't available, but remains in favour of Asml's static buffers 10 | (see [core-buffers](core-buffers.md)). 11 | 12 | AssemblyLift does not currently support mapping socket access using WASI. AssemblyLift's policy for 13 | the time being is to keep net access restricted to higher-level APIs via IOmods. 14 | 15 | ```rust 16 | // IO 17 | fn __asml_abi_io_invoke(name_ptr: *const u8, name_len: usize, input_ptr: *const u8, input_len: usize) -> i32; 18 | fn __asml_abi_io_poll(id: u32) -> i32; 19 | fn __asml_abi_io_len(id: u32) -> u32; 20 | fn __asml_abi_io_load(id: u32) -> i32; 21 | fn __asml_abi_io_next() -> i32; 22 | 23 | // System clock 24 | fn __asml_abi_clock_time_get() -> u64; 25 | 26 | // Runtime 27 | fn __asml_abi_runtime_log(ptr: *const u8, len: usize); 28 | fn __asml_abi_runtime_success(ptr: *const u8, len: usize); 29 | 30 | // Function Input 31 | fn __asml_abi_input_start() -> i32; 32 | fn __asml_abi_input_next() -> i32; 33 | fn __asml_abi_input_length_get() -> u64; 34 | ``` 35 | > The `io` group of functions are used to poll for and read responses from IOmod calls. 36 | > The system clock is not really needed anymore; it exists because AssemblyLit predates WASI :) 37 | 38 | -------------------------------------------------------------------------------- /docs/core-buffers.md: -------------------------------------------------------------------------------- 1 | AssemblyLift WASM IO Buffers 2 | ---------------------------- 3 | 4 | AssemblyLift WASM guests and their host pass data thru linear buffers defined in each guest. There are [two buffers](../core/src/buffers.rs), 5 | the Function Input Buffer which is exactly what it sounds like, and the IO Buffer which contains responses to IOmod calls. 6 | 7 | Each buffer implements `PagedWasmBuffer`, which is designed to wrap the `start`/`next`/`length` ABI calls corresponding 8 | to each buffer. 9 | 10 | Buffer paging is implemented to allow a guest-side buffer to be reasonably small, while allowing the backing buffer on 11 | the host side to be arbitrarily (in theory) large. The maximum request payload size for AWS Lambda for example is 10MB, 12 | but we don't want to keep a 10MB static buffer in the guest. We _also_ don't have the luxury of a dynamically allocated 13 | buffer for this purpose. Thus, paging! 14 | 15 | The Function Input Buffer has three ABI functions for "load first page", "next page", and "input length" operations. 16 | The IO Buffer is similar, however it allows swapping _between_ buffers with a `load` function which both sets the buffer 17 | index (by IOID) and loads its first page. 18 | 19 | The WASM guest must export the following functions which must return a pointer to each buffer to the host: 20 | ```rust 21 | fn __asml_guest_get_io_buffer_pointer() -> *const u8; 22 | fn __asml_guest_get_function_input_buffer_pointer() -> *const u8; 23 | ``` 24 | 25 | How this is accomplished is language-dependant. Rust requires each guest to pull in a crate which will provide definitions. 26 | For Ruby these calls are embedded in the interpreter. 27 | 28 | -------------------------------------------------------------------------------- /docs/core-threader.md: -------------------------------------------------------------------------------- 1 | IO Threader 2 | -------- 3 | 4 | [_Threader_](../core/src/threader.rs) is responsible for handling IOmod calls from WASM modules and invoking the 5 | corresponding module call. As well it is responsible for tracking the status of each in-flight call, and managing the 6 | responses in the [IO Buffer](core-buffers.md). 7 | 8 | Threader maintains its own [Tokio](https://crates.io/crates/tokio) async runtime, separate from the runtime which 9 | executes WebAssembly. 10 | 11 | TODO IO documents, IOIDs, WasmerEnv dependency 12 | -------------------------------------------------------------------------------- /docs/lang-ruby.md: -------------------------------------------------------------------------------- 1 | Ruby 2 | ---- 3 | 4 | AssemblyLift's support for Ruby is built on a [slightly customized fork of Ruby 3.1](https://github.com/akkoro/ruby/tree/assemblylift). 5 | The forked build includes an extra [built-in gem called `asml`](https://github.com/akkoro/ruby/tree/assemblylift/ext/asml), 6 | which provides an implementation of the [AssemblyLift ABI](core-abi.md). 7 | 8 | Ruby Functions are packaged with a prebuilt Ruby interpreter compiled to WASM, along with the contents of the Function 9 | source directory. The interpreter is hardcoded to load & execute a script called `handler.rb`, which may `require_relative` 10 | other Ruby source in the directory. If you wish to use a Gem in your Function, it must be vendored into the source directory 11 | with Bundler `bundle install --path=./vendor/gems` (get Bundler with `gem install bundler`). Gems **must not** depend on 12 | any native C code. 13 | 14 | Ruby Functions require the `ASML_FUNCTION_ENV` environment variable to be set to either `ruby-docker` or `ruby-lambda`. 15 | Each will map the `src` and `usr` directories of the Ruby environment to the loaded WASM module [via WASI](../core/src/wasm.rs#L66), 16 | the difference being from where they are mapped. 17 | -------------------------------------------------------------------------------- /docs/lang-rust.md: -------------------------------------------------------------------------------- 1 | Rust 2 | ---- 3 | 4 | The Rust programming language has first-class support for WebAssembly as a build target. AssemblyLift Functions written 5 | in Rust are compiled using the `wasm32-wasi` target. 6 | 7 | Rust language guests must import the crates `assemblylift-core-guest` and `assemblylift-core-io-guest`. 8 | -------------------------------------------------------------------------------- /docs/provider-apigw-amz.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkoro/assemblylift/a4f9094b0ed07eb813c9d2f5893a5a23dbca9a08/docs/provider-apigw-amz.md -------------------------------------------------------------------------------- /docs/provider-apigw-gloo.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkoro/assemblylift/a4f9094b0ed07eb813c9d2f5893a5a23dbca9a08/docs/provider-apigw-gloo.md -------------------------------------------------------------------------------- /docs/provider-dns-route53.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkoro/assemblylift/a4f9094b0ed07eb813c9d2f5893a5a23dbca9a08/docs/provider-dns-route53.md -------------------------------------------------------------------------------- /docs/provider-service-aws.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkoro/assemblylift/a4f9094b0ed07eb813c9d2f5893a5a23dbca9a08/docs/provider-service-aws.md -------------------------------------------------------------------------------- /docs/provider-service-k8s.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkoro/assemblylift/a4f9094b0ed07eb813c9d2f5893a5a23dbca9a08/docs/provider-service-k8s.md -------------------------------------------------------------------------------- /docs/rt-hyper.md: -------------------------------------------------------------------------------- 1 | Hyper Runtime 2 | ------------- 3 | 4 | The Hyper runtime is meant to run inside a "generic" environment -- nonspecific to any particular vendor. 5 | As indicated by its name, it's based on the [Hyper HTTP crate](https://crates.io/crates/hyper) for the Rust programming 6 | language. 7 | 8 | The runtime uses [`crossbeam`](https://crates.io/crates/crossbeam-utils) to spawn two threads, for an HTTP server and a 9 | WASM module runner. The server listens for traffic on port `5543` and forwards HTTP requests to guests using the shape: 10 | ```rust 11 | struct LauncherRequest { 12 | method: String, 13 | headers: BTreeMap, 14 | body_encoding: String, 15 | body: Option, 16 | } 17 | ``` 18 | where `body_encoding` is currently always `base64` (but probably shouldn't be :)). 19 | 20 | The response from the guest via `success` is returned as the body of an HTTP 200 response. A guest error is returned as 21 | an HTTP 500. 22 | 23 | The runtime requires the `ASML_WASM_MODULE_NAME` environment variable to be set to the filename of the module; the module 24 | is expected to be in the `/opt/assemblylift` directory (i.e. `/opt/assemblylift/$ASML_WASM_MODULE_NAME`). 25 | -------------------------------------------------------------------------------- /docs/rt-lambda.md: -------------------------------------------------------------------------------- 1 | AWS Lambda Runtime 2 | ------------------ 3 | 4 | The Lambda runtime is designed to work with the Lambda [Custom Runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). 5 | It conforms with the Lambda [execution environment](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), 6 | e.g. deployed as a binary named `bootstrap` and working with the custom runtime environment variables like `LAMBDA_TASK_ROOT`. 7 | 8 | WebAssembly modules are invoked in response to a new event, which is found by polling the "next event" API. 9 | 10 | Requests are processed in order -- modules are not run in parallel. 11 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | .asml/* 2 | *.hcl 3 | *.tfstate* 4 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | AssemblyLift Examples 2 | ---- 3 | -------------------------------------------------------------------------------- /examples/custom-domain/.gitignore: -------------------------------------------------------------------------------- 1 | .asml/ 2 | .terraform/ 3 | .DS_Store 4 | net/ 5 | **/target/ 6 | **/build/ 7 | -------------------------------------------------------------------------------- /examples/custom-domain/assemblylift.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "custom-domain" 3 | 4 | [[services]] 5 | name = "example" 6 | 7 | [[domains]] 8 | dns_name = "example.com" 9 | # map_to_root = true # Set to omit project name from URL 10 | [domains.provider] 11 | name = "route53" 12 | [domains.provider.options] 13 | aws_region = "us-east-1" 14 | -------------------------------------------------------------------------------- /examples/custom-domain/services/example/echo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "echo" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = "1" 8 | serde_json = "1" 9 | assemblylift-core-guest = { version = "0.4.0-beta.0", path = "../../../../../core/guest" } 10 | -------------------------------------------------------------------------------- /examples/custom-domain/services/example/echo/src/main.rs: -------------------------------------------------------------------------------- 1 | use assemblylift_core_guest::*; 2 | 3 | #[handler] 4 | async fn main() { 5 | // `ctx` is a value injected by the `handler` attribute macro 6 | let event: serde_json::Value = serde_json::from_slice(&ctx.input) 7 | .expect("could not parse function input as JSON"); 8 | 9 | FunctionContext::success("Function returned OK!".to_string()); 10 | } 11 | -------------------------------------------------------------------------------- /examples/custom-domain/services/example/service.toml: -------------------------------------------------------------------------------- 1 | [service] 2 | name = "example" 3 | 4 | [service.provider] 5 | name = "aws-lambda" 6 | 7 | [api] 8 | domain_name = "example.com" 9 | 10 | [[api.functions]] 11 | name = "echo" 12 | language = "rust" 13 | http = { verb = "GET", path = "/hello" } 14 | -------------------------------------------------------------------------------- /examples/hello-world/.gitignore: -------------------------------------------------------------------------------- 1 | .asml/ 2 | .terraform/ 3 | .DS_Store 4 | net/ 5 | **/target/ 6 | **/build/ 7 | -------------------------------------------------------------------------------- /examples/hello-world/assemblylift.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "hello-world" 3 | 4 | [[services]] 5 | name = "hello" 6 | -------------------------------------------------------------------------------- /examples/hello-world/services/hello/ruby/handler.rb: -------------------------------------------------------------------------------- 1 | require 'asml' 2 | require 'base64' 3 | require 'json' 4 | 5 | def main(input) 6 | # TODO implement your function code here! 7 | Asml.log("Received function input: " + input.to_s) 8 | Asml.success(JSON.generate("Hello world!")) 9 | end 10 | 11 | main(JSON.parse(Asml.get_function_input())) 12 | -------------------------------------------------------------------------------- /examples/hello-world/services/hello/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = "1" 8 | serde_json = "1" 9 | assemblylift-core-guest = { version = "0.4.0-beta.0", path = "../../../../../core/guest" } 10 | -------------------------------------------------------------------------------- /examples/hello-world/services/hello/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | use assemblylift_core_guest::*; 2 | 3 | #[handler] 4 | async fn main() { 5 | // `ctx` is a value injected by the `handler` attribute macro 6 | let event: serde_json::Value = serde_json::from_slice(&ctx.input) 7 | .expect("could not parse function input as JSON"); 8 | FunctionContext::log(format!("Received function input: {:?}", event)); 9 | FunctionContext::success("\"Hello world!\"".to_string()); 10 | } 11 | -------------------------------------------------------------------------------- /examples/hello-world/services/hello/service.toml: -------------------------------------------------------------------------------- 1 | [service] 2 | name = "hello" 3 | 4 | [service.provider] 5 | name = "aws-lambda" 6 | 7 | [service.provider.options] 8 | aws_region = "us-east-1" 9 | 10 | [[api.functions]] 11 | name = "rust" 12 | language = "rust" 13 | http = { verb = "GET", path = "/rust" } 14 | 15 | [[api.functions]] 16 | name = "ruby" 17 | language = "ruby" 18 | http = { verb = "GET", path = "/ruby" } 19 | --------------------------------------------------------------------------------