azure-functions 0.1.2

Support for writing Azure Functions in Rust.
use clap::{App, Arg};
use registry::Registry;
use serde::Serialize;
use serde_json::Serializer;
use std::env::{current_dir, current_exe};
use std::fs;
use std::sync::{Arc, Mutex};

pub fn create_app<'a, 'b>() -> App<'a, 'b> {
    App::new("Azure Function Language Worker for Rust")
        .version(env!("CARGO_PKG_VERSION"))
        .about("Provides an Azure Function Worker for functions written in Rust.")
        .arg(
            Arg::with_name("host")
                .long("host")
                .value_name("HOST")
                .help("The hostname of the Azure Functions Host.")
                .conflicts_with("create")
                .required_unless("create"),
        )
        .arg(
            Arg::with_name("port")
                .long("port")
                .value_name("PORT")
                .help("The port of the Azure Functions Host.")
                .conflicts_with("create")
                .required_unless("create"),
        )
        .arg(
            Arg::with_name("worker_id")
                .long("workerId")
                .value_name("WORKER_ID")
                .help("The worker ID to use when registering with the Azure Functions Host.")
                .conflicts_with("create")
                .required_unless("create"),
        )
        .arg(
            Arg::with_name("request_id")
                .long("requestId")
                .value_name("REQUEST_ID")
                .help("The request ID to use when communicating with the Azure Functions Host.")
                .hidden(true)
                .conflicts_with("create")
                .required_unless("create"),
        )
        .arg(
            Arg::with_name("max_message_length")
                .long("grpcMaxMessageLength")
                .value_name("MAXIMUM")
                .help("The maximum message length to use for gRPC messages.")
                .conflicts_with("create")
                .required_unless("create"),
        )
        .arg(
            Arg::with_name("create")
                .long("create")
                .value_name("APP_ROOT")
                .help("Creates the Azure Functions App at the given root directory.\nCannot be used with other options."),
        )
}

pub fn generate_functions_app(root: &str, registry: Arc<Mutex<Registry>>) {
    const FUNCTION_FILE: &'static str = "function.json";
    const RUST_SCRIPT_FILE: &'static str = "run.rs";

    let root = current_dir()
        .expect("failed to get current directory")
        .join(root);

    if root.exists() {
        println!(
            "Using existing Azure Functions application at '{}'.",
            root.display()
        );
    } else {
        println!(
            "Creating Azure Functions application at '{}'.",
            root.display()
        );

        fs::create_dir_all(&root).expect(&format!(
            "Failed to create Azure Functions application directory '{}'",
            root.display()
        ));
    }

    let host_json = root.join("host.json");
    if !host_json.exists() {
        println!(
            "Creating empty host configuration file '{}'.",
            host_json.display()
        );
        fs::write(&host_json, "{}").expect(&format!("Failed to create '{}'", host_json.display()));
    }

    println!("Copying current worker executable.");
    fs::copy(
        current_exe().expect("Failed to determine the path to the current executable"),
        root.join("rust_worker"),
    ).expect("Failed to copy worker executable");

    for entry in fs::read_dir(&root).expect("failed to read script root directory") {
        let path = root.join(entry.expect("failed to read script root entry").path());
        if !path.is_dir() || !path.join(RUST_SCRIPT_FILE).exists() {
            continue;
        }

        println!("Deleting existing function directory '{}'.", path.display());

        fs::remove_dir_all(&path).expect(&format!(
            "Failed to delete function directory '{}",
            path.display()
        ));
    }

    for (name, info) in registry.lock().unwrap().iter() {
        let function_dir = root.join(name);
        fs::create_dir(&function_dir).expect(&format!(
            "Failed to create function directory '{}'",
            function_dir.display()
        ));

        let script_file = function_dir.join(RUST_SCRIPT_FILE);
        println!(
            "Creating script file '{}' for Azure Function '{}'.",
            script_file.display(),
            name
        );
        fs::write(
            &script_file,
            "// This file is intentionally empty.\n// It is needed by Azure Functions Host to register the Azure function."
        ).expect(&format!("Failed to create '{}'", script_file.display()));

        let function_json = function_dir.join(FUNCTION_FILE);
        println!(
            "Creating function configuration file '{}' for Azure Function '{}'.",
            function_json.display(),
            name
        );

        let mut output = fs::File::create(&function_json)
            .expect(&format!("Failed to create '{}'", function_json.display()));
        info.serialize(&mut Serializer::pretty(&mut output))
            .expect(&format!(
                "Failed to serialize metadata for function '{}'",
                name
            ));
    }
}