Implement a Rust worker that supports an unmodified host.

This commit changes the CLI for the Rust worker to support an unmodified Azure
Functions Host.

Additionally, documentation has been updated.

A Dockerfile has been added to easily build Docker images for the examples so
they can be deployed to Azure.
This commit is contained in:
Peter Huene 2018-07-23 16:36:03 -07:00
parent 418eda3b2e
commit 5715ffd4a9
No known key found for this signature in database
GPG Key ID: E1D265D820213D6A
13 changed files with 391 additions and 229 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
target/
Cargo.lock
.vscode/
.git/

3
.gitignore vendored
View File

@ -9,7 +9,4 @@ Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# Example generated script root directories
examples/**/root
.vscode/

15
Dockerfile Normal file
View File

@ -0,0 +1,15 @@
FROM peterhuene/azure-functions-rs-ci:latest AS build-env
COPY . /src
ARG EXAMPLE
RUN if [ -z "$EXAMPLE" ]; then echo "The EXAMPLE argument is required."; exit 1; fi \
&& cd /src/examples/$EXAMPLE \
&& cargo run --release -- init --worker-path /usr/local/bin/rust_worker --script-root /home/site/wwwroot
FROM microsoft/azure-functions-dotnet-core2.0:dev-nightly
COPY --from=build-env ["/usr/local/bin/rust_worker", "/usr/local/bin/rust_worker"]
COPY --from=build-env ["/home/site/wwwroot", "/home/site/wwwroot"]
COPY --from=build-env ["/src/azure-functions/worker.config.json", "/azure-functions-host/workers/rust/worker.config.json"]

View File

@ -10,7 +10,7 @@ in [Rust](https://www.rust-lang.org/en-US/).
## Disclaimer
Althougth the maintainer of this repository is a Microsoft employee, this project is not an official Microsoft product
Although the maintainer of this repository is a Microsoft employee, this project is not an official Microsoft product
and is not an endorsement of any future product offering from Microsoft.
This project is simply a labor of love by a developer who would like to see the Rust ecosystem flourish.
@ -57,9 +57,9 @@ The current list of supported bindings:
| Rust Type | Azure Functions Binding |
|-------------------------------------------|-------------------------|
| `azure_functions::bindings::HttpRequest` | HTTP Trigger |
| `azure_functions::bindings::HttpResponse` | HTTP Output |
| `azure_functions::bindings::QueueMessage` | Output Queue Message |
| `azure_functions::bindings::HttpResponse` | Output HTTP Response |
| `azure_functions::bindings::QueueTrigger` | Queue Trigger |
| `azure_functions::bindings::QueueMessage` | Output Queue Message |
| `azure_functions::bindings::TimerInfo` | Timer Trigger |
| `azure_functions::Context`* | Invocation Context |
@ -88,8 +88,7 @@ This repository is split into multiple Rust crates:
* [azure-functions-shared](https://github.com/peterhuene/azure-functions-rs/tree/master/azure-functions-shared) - The `azure-functions-shared` crate that defines types and functions that are shared between the `azure-functions-codegen` and `azure-functions` crates.
* Note: the `azure-functions-shared/protobuf` directory is the git submodule for [Azure Functions Language Worker Protocol](https://github.com/Azure/azure-functions-language-worker-protobuf).
* [azure-functions-shared-codegen](https://github.com/peterhuene/azure-functions-rs/tree/master/azure-functions-shared-codegen) - The `azure-functions-shared-codegen` crate that defines the procedural macros used by the shared `azure-functions-shared` crate.
* [examples/http](https://github.com/peterhuene/azure-functions-rs/tree/master/examples/http) - An example of an HTTP-triggered function.
* [examples/timer](https://github.com/peterhuene/azure-functions-rs/tree/master/examples/timer) - An example of a timer-triggered function.
* [examples](https://github.com/peterhuene/azure-functions-rs/tree/master/examples) - The directory containing example Azure Functions.
## Prerequisites
@ -127,3 +126,57 @@ cargo test
```
Right now there are only doc tests, but more tests are coming soon.
## Deploying to Azure Functions
Deploying to Azure Functions is best accomplished with a Docker image for your Rust Azure Functions application.
Copy this content to a `Dockerfile` at the root of your source:
```docker
FROM peterhuene/azure-functions-rs-ci:latest AS build-env
COPY . /src
RUN cargo run --release -- init --worker-path /usr/local/bin/rust_worker --script-root /home/site/wwwroot
FROM microsoft/azure-functions-dotnet-core2.0:dev-nightly
COPY --from=build-env ["/usr/local/bin/rust_worker", "/usr/local/bin/rust_worker"]
COPY --from=build-env ["/home/site/wwwroot", "/home/site/wwwroot"]
RUN mkdir /azure-functions-host/workers/rust \
&& curl https://gist.githubusercontent.com/peterhuene/00ba85ed18bb42437355f63829f2471e/raw/9d29d3b8eaf01e1d2d44e7df2a569a9730fbafa3/worker.config.json > /azure-functions-host/workers/rust/worker.config.json
```
Add a `.dockerignore` at the root of your source with the following contents:
```
target/
Cargo.lock
.vscode/
.git/
```
Build the Docker image:
```
docker build -t $IMAGE:latest .
```
Where `$IMAGE` is the name of the tag for the image (e.g. `peterhuene/azure-functions-rs-example`).
Push the image to a repository:
```
docker push $IMAGE
```
Create the Function App in [Azure](https://portal.azure.com) using the Docker "OS", specifying the image that was pushed:
![Azure Portal](docs/images/create-function-app.png)
Add a new setting for `WEBSITES_ENABLE_APP_SERVICE_STORAGE` under `Application Settings` and set it to `false`.
This will enable the Docker image itself to provide the service storage (i.e. script root and worker).
Finally, restart the Function App. After the application has initialized again, your Rust Azure Functions should be displayed in the Azure Portal.

View File

@ -1,149 +1,71 @@
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};
use clap::{App, Arg, SubCommand};
pub fn create_app<'a, 'b>() -> App<'a, 'b> {
App::new("Azure Functions Language Worker for Rust")
.version(env!("CARGO_PKG_VERSION"))
.about("Provides an Azure Functions 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"),
.subcommand(
SubCommand::with_name("init")
.about("Initializes the Rust language worker and script root.")
.arg(
Arg::with_name("worker_path")
.long("worker-path")
.value_name("WORKER_PATH")
.help("The path to place the Rust language worker.")
.required(true),
)
.arg(
Arg::with_name("script_root")
.long("script-root")
.value_name("SCRIPT_ROOT")
.help("The directory to create the script root.")
.required(true),
)
)
.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."),
.subcommand(
SubCommand::with_name("run")
.about("Runs the Rust language worker.")
.arg(
Arg::with_name("worker_config")
.value_name("WORKER_CONFIG")
.help("The path to the Rust worker configuration file.")
.required(false)
.index(1)
)
.arg(
Arg::with_name("host")
.long("host")
.value_name("HOST")
.help("The hostname of the Azure Functions Host.")
.required(true),
)
.arg(
Arg::with_name("port")
.long("port")
.value_name("PORT")
.help("The port of the Azure Functions Host.")
.required(true),
)
.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.")
.required(true),
)
.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)
.required(true),
)
.arg(
Arg::with_name("max_message_length")
.long("grpcMaxMessageLength")
.value_name("MAXIMUM")
.help("The maximum message length to use for gRPC messages.")
)
)
}
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 the 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
));
}
}

View File

@ -5,8 +5,8 @@
//! The following Azure Functions trigger bindings are supported:
//!
//! * [HTTP triggers](bindings/struct.HttpRequest.html)
//! * [Timer triggers](bindings/struct.TimerInfo.html)
//! * [Queue triggers](bindings/struct.QueueTrigger.html)
//! * [Timer triggers](bindings/struct.TimerInfo.html)
//!
//! The following Azure Functions output bindings are supported:
//!
@ -30,10 +30,9 @@
//! log = "0.4.2"
//! ```
//!
//! Azure Functions are implemented by applying a trigger attribute to a Rust function.
//! Azure Functions are implemented by applying a `#[func]` attribute to a Rust function.
//!
//! For example, let's create `src/greet.rs` that implements a HTTP triggered function by
//! applying the `func` attribute:
//! For example, let's create `src/greet.rs` that implements a HTTP triggered function:
//!
//! ```rust
//! # #![feature(use_extern_macros)] extern crate azure_functions;
@ -74,19 +73,19 @@
//! }
//! ```
//!
//! Run the application with the `--create <root>` option, where `<root>` is the path to
//! the desired Azure Functions application root directory:
//! Initialize the application with the `init` command, where `$AzureWebJobsScriptRoot` is
//! the desired Azure Functions script root directory:
//!
//! ```bash
//! $ export AzureWebJobsScriptRoot=path-to-root
//! $ cargo run -q -- --create $AzureWebJobsScriptRoot
//! $ cargo run -q -- init --worker-path /tmp/example/rust_worker --script-root /tmp/example/root
//! ```
//!
//! Run the Azure Functions Host:
//! Run the [Azure Functions Host](https://github.com/azure/azure-functions-host):
//!
//! ```bash
//! $ cd azure-functions-host/src/WebJobs.Script.WebHost
//! $ dotnet run
//! $ PATH=/tmp/example:$PATH AzureWebJobsScriptRoot=/tmp/example/root dotnet run
//! ```
//!
//! The above Azure Function can be invoked with `http://localhost:5000/api/greet?name=John`.
@ -135,33 +134,118 @@ pub use azure_functions_shared::Context;
use futures::Future;
use registry::Registry;
use serde::Serialize;
use serde_json::Serializer;
use std::env::{current_dir, current_exe};
use std::fs;
use std::path::Path;
use std::sync::{Arc, Mutex};
#[doc(hidden)]
pub fn worker_main(args: impl Iterator<Item = String>, functions: &[&'static codegen::Function]) {
let matches = cli::create_app().get_matches_from(args);
let registry = Arc::new(Mutex::new(Registry::new(functions)));
fn initialize_app(worker_path: &str, script_root: &str, registry: Arc<Mutex<Registry>>) {
const FUNCTION_FILE: &'static str = "function.json";
const RUST_SCRIPT_FILE: &'static str = "run.rs";
if let Some(root) = matches.value_of("create") {
cli::generate_functions_app(root, registry);
return;
let script_root = current_dir()
.expect("failed to get current directory")
.join(script_root);
if script_root.exists() {
println!(
"Using existing Azure Functions application at '{}'.",
script_root.display()
);
} else {
println!(
"Creating Azure Functions application at '{}'.",
script_root.display()
);
fs::create_dir_all(&script_root).expect(&format!(
"Failed to create Azure Functions application directory '{}'",
script_root.display()
));
}
let client = rpc::Client::new(
matches
.value_of("worker_id")
.expect("A worker id is required.")
.to_owned(),
matches
.value_of("max_message_length")
.map(|len| len.parse::<i32>().expect("Invalid maximum message length")),
);
let host_json = script_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()));
}
let host = matches.value_of("host").expect("A host is required.");
let port = matches
.value_of("port")
.map(|port| port.parse::<u32>().expect("Invalid port number"))
.expect("Port number is required.");
let worker_dir = Path::new(worker_path)
.parent()
.expect("expected to get a parent of the worker path");
fs::create_dir_all(&worker_dir).expect(&format!(
"Failed to create directory for worker executable '{}'",
worker_dir.display()
));
println!("Copying current worker executable to '{}'.", worker_path);
fs::copy(
current_exe().expect("Failed to determine the path to the current executable"),
worker_path,
).expect("Failed to copy worker executable");
for entry in fs::read_dir(&script_root).expect("failed to read script root directory") {
let path = script_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 = script_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 the 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
));
}
}
fn run_worker<'a>(
worker_id: &str,
host: &str,
port: u32,
max_message_length: Option<i32>,
registry: Arc<Mutex<Registry>>,
) {
let client = rpc::Client::new(worker_id.to_string(), max_message_length);
println!("Connecting to Azure Functions host at {}:{}.", host, port);
@ -178,3 +262,43 @@ pub fn worker_main(args: impl Iterator<Item = String>, functions: &[&'static cod
.wait()
.unwrap();
}
#[doc(hidden)]
pub fn worker_main(args: impl Iterator<Item = String>, functions: &[&'static codegen::Function]) {
let matches = cli::create_app().get_matches_from(args);
let registry = Arc::new(Mutex::new(Registry::new(functions)));
if let Some(matches) = matches.subcommand_matches("init") {
initialize_app(
matches
.value_of("worker_path")
.expect("A binary path is required."),
matches
.value_of("script_root")
.expect("A script root is required."),
registry,
);
return;
}
if let Some(matches) = matches.subcommand_matches("run") {
run_worker(
matches
.value_of("worker_id")
.expect("A worker id is required."),
matches.value_of("host").expect("A host is required."),
matches
.value_of("port")
.map(|port| port.parse::<u32>().expect("Invalid port number"))
.expect("A port number is required."),
matches
.value_of("max_message_length")
.map(|len| len.parse::<i32>().expect("Invalid maximum message length")),
registry,
);
return;
}
cli::create_app().print_help().unwrap();
println!();
}

View File

@ -0,0 +1,9 @@
{
"Description":{
"Language": "Rust",
"Extension": ".rs",
"DefaultExecutablePath": "rust_worker",
"DefaultWorkerPath": "worker.config.json",
"Arguments": ["run"]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

View File

@ -6,4 +6,4 @@ authors = ["Peter Huene <peterhuene@protonmail.com>"]
[dependencies]
azure-functions = { version = "0.1.4", path = "../../azure-functions" }
log = "0.4.2"
serde_json = "1.0.21"
serde_json = "1.0.21"

View File

@ -4,7 +4,7 @@ This package is an example of a simple HTTP-triggered Azure Function.
## Example function implementation
The example anonymous, HTTP-triggered Azure Function:
The example HTTP-triggered Azure Function:
```rust
use azure_functions::bindings::{HttpRequest, HttpResponse};
@ -22,7 +22,7 @@ pub fn greet(context: &Context, req: &HttpRequest) -> HttpResponse {
}
```
# Running the example
# Running the example locally
## Prerequisites
@ -41,27 +41,40 @@ rustup default nightly
The Azure Functions Host is implemented with .NET Core, so download and install a [.NET Core SDK](https://www.microsoft.com/net/download).
### Custom fork of Azure Functions Host
### Azure Functions Host
Currently, the Azure Functions Host does not support the Rust language worker. Until that time, Azure Functions written in Rust must be executed locally using a [fork of the Azure Functions Host that does](https://github.com/peterhuene/azure-functions-host/tree/rust-worker-provider).
Run the following command to clone the fork:
Clone the Azure Functions Host from GitHub:
```
git clone -b rust-worker-provider git@github.com:peterhuene/azure-functions-host.git
git clone git@github.com:azure/azure-functions-host.git
```
## Create the script root
Run the following command to create the "script root" for the example:
Use `dotnet` to build the Azure Functions Host:
```
cargo run -q -- --create root
cd azure-functions-host/src/WebJobs.Script.WebHost
dotnet build
```
This will build and run the sample to create the "script root" containing the Rust worker and the example Azure Function metadata.
## Register the Rust language worker
Remember the path to the root directory from this step as it will be needed for running the Azure Functions Host below.
The Azure Functions Host uses JSON configuration files to register language workers.
Create the configuration file to register the Rust language worker:
```
mkdir azure-functions-host/src/WebJobs.Script.WebHost/bin/Debug/netcoreapp2.1/workers/rust
cp azure-functions-rs/azure-functions/worker.config.json azure-functions-host/src/WebJobs.Script.WebHost/bin/Debug/netcoreapp2.1/workers/rust
```
## Initialize the example application
Run the following command to build and initialize the Rust Azure Functions application:
```
cd azure-functions-rs/examples/http
cargo run --release -- init --worker-path /tmp/http-example/rust_worker --script-root /tmp/http-example/root
```
## Start the Azure Functions Host
@ -69,15 +82,11 @@ Run the following commands to start the Azure Functions Host:
```
cd azure-functions-host/src/WebJobs.Script.WebHost
AzureWebJobsScriptRoot=$SCRIPT_ROOT_PATH dotnet run
PATH=/tmp/http-example:$PATH AzureWebJobsScriptRoot=/tmp/http-example/root dotnet run
```
Where `$SCRIPT_ROOT_PATH` above represents the path to the root directory created from running `cargo run` above.
_Note: the syntax above works on macOS and Linux; on Windows, set the `AzureWebJobsScriptRoot` environment variable before running `dotnet run`._
_Note: if using bindings that require storage (such as timer triggers), you must set the `AzureWebJobsStorage` environment variable to an Azure Storage connection string._
## Invoke the `greet` function
The easiest way to invoke the function is to use `curl`:

View File

@ -36,7 +36,7 @@ pub fn queue_with_output(trigger: &QueueTrigger) -> QueueMessage {
}
```
# Running the example
# Running the example locally
## Prerequisites
@ -55,27 +55,40 @@ rustup default nightly
The Azure Functions Host is implemented with .NET Core, so download and install a [.NET Core SDK](https://www.microsoft.com/net/download).
### Custom fork of Azure Functions Host
### Azure Functions Host
Currently, the Azure Functions Host does not support the Rust language worker. Until that time, Azure Functions written in Rust must be executed locally using a [fork of the Azure Functions Host that does](https://github.com/peterhuene/azure-functions-host/tree/rust-worker-provider).
Run the following command to clone the fork:
Clone the Azure Functions Host from GitHub:
```
git clone -b rust-worker-provider git@github.com:peterhuene/azure-functions-host.git
git clone git@github.com:azure/azure-functions-host.git
```
## Create the script root
Run the following command to create the "script root" for the example:
Use `dotnet` to build the Azure Functions Host:
```
cargo run -q -- --create root
cd azure-functions-host/src/WebJobs.Script.WebHost
dotnet build
```
This will build and run the sample to create the "script root" containing the Rust worker and the example Azure Function metadata.
## Register the Rust language worker
Remember the path to the root directory from this step as it will be needed for running the Azure Functions Host below.
The Azure Functions Host uses JSON configuration files to register language workers.
Create the configuration file to register the Rust language worker:
```
mkdir azure-functions-host/src/WebJobs.Script.WebHost/bin/Debug/netcoreapp2.1/workers/rust
cp azure-functions-rs/azure-functions/worker.config.json azure-functions-host/src/WebJobs.Script.WebHost/bin/Debug/netcoreapp2.1/workers/rust
```
## Initialize the example application
Run the following command to build and initialize the Rust Azure Functions application:
```
cd azure-functions-rs/examples/queue
cargo run --release -- init --worker-path /tmp/queue-example/rust_worker --script-root /tmp/queue-example/root
```
## Start the Azure Functions Host
@ -83,10 +96,10 @@ Run the following commands to start the Azure Functions Host:
```
cd azure-functions-host/src/WebJobs.Script.WebHost
AzureWebJobsScriptRoot=$SCRIPT_ROOT AzureWebJobsStorage=$CONNECTION_STRING dotnet run
PATH=/tmp/queue-example:$PATH AzureWebJobsScriptRoot=/tmp/queue-example/root AzureWebJobsStorage=$CONNECTION_STRING dotnet run
```
Where `$SCRIPT_ROOT` above represents the path to the root directory created from running `cargo run` above and `$CONNECTION_STRING` is the Azure Storage connection string the Azure Functions host should use.
Where `$CONNECTION_STRING` is the Azure Storage connection string the Azure Functions host should use.
_Note: the syntax above works on macOS and Linux; on Windows, set the environment variables before running `dotnet run`._
@ -110,5 +123,8 @@ info: Function.queue[0]
Executed 'Functions.queue' (Succeeded, Id=01912ed1-83aa-4ac7-ae2a-9b2b1ae80830)
```
Likewise, to invoke the `queue_with_output` function, post a message to the `echo-in` queue. After the function invokes,
you should see the same message posted back to the `echo-out` queue.
## Invoke the `queue_with_output` function
To invoke the `queue_with_output` function, post a message to the `echo-in` queue.
After the function invokes, you should see the same message posted back to the `echo-out` queue.

View File

@ -4,5 +4,5 @@ version = "0.1.0"
authors = ["Peter Huene <peterhuene@protonmail.com>"]
[dependencies]
azure-functions = { version = "0.1.3", path = "../../azure-functions" }
azure-functions = { version = "0.1.4", path = "../../azure-functions" }
log = "0.4.2"

View File

@ -18,7 +18,7 @@ pub fn timer(info: &TimerInfo) {
}
```
# Running the example
# Running the example locally
## Prerequisites
@ -37,27 +37,40 @@ rustup default nightly
The Azure Functions Host is implemented with .NET Core, so download and install a [.NET Core SDK](https://www.microsoft.com/net/download).
### Custom fork of Azure Functions Host
### Azure Functions Host
Currently, the Azure Functions Host does not support the Rust language worker. Until that time, Azure Functions written in Rust must be executed locally using a [fork of the Azure Functions Host that does](https://github.com/peterhuene/azure-functions-host/tree/rust-worker-provider).
Run the following command to clone the fork:
Clone the Azure Functions Host from GitHub:
```
git clone -b rust-worker-provider git@github.com:peterhuene/azure-functions-host.git
git clone git@github.com:azure/azure-functions-host.git
```
## Create the script root
Run the following command to create the "script root" for the example:
Use `dotnet` to build the Azure Functions Host:
```
cargo run -q -- --create root
cd azure-functions-host/src/WebJobs.Script.WebHost
dotnet build
```
This will build and run the sample to create the "script root" containing the Rust worker and the example Azure Function metadata.
## Register the Rust language worker
Remember the path to the root directory from this step as it will be needed for running the Azure Functions Host below.
The Azure Functions Host uses JSON configuration files to register language workers.
Create the configuration file to register the Rust language worker:
```
mkdir azure-functions-host/src/WebJobs.Script.WebHost/bin/Debug/netcoreapp2.1/workers/rust
cp azure-functions-rs/azure-functions/worker.config.json azure-functions-host/src/WebJobs.Script.WebHost/bin/Debug/netcoreapp2.1/workers/rust
```
## Initialize the example application
Run the following command to build and initialize the Rust Azure Functions application:
```
cd azure-functions-rs/examples/timer
cargo run --release -- init --worker-path /tmp/timer-example/rust_worker --script-root /tmp/timer-example/root
```
## Start the Azure Functions Host
@ -65,10 +78,10 @@ Run the following commands to start the Azure Functions Host:
```
cd azure-functions-host/src/WebJobs.Script.WebHost
AzureWebJobsScriptRoot=$SCRIPT_ROOT AzureWebJobsStorage=$CONNECTION_STRING dotnet run
PATH=/tmp/timer-example:$PATH AzureWebJobsScriptRoot=/tmp/timer-example/root AzureWebJobsStorage=$CONNECTION_STRING dotnet run
```
Where `$SCRIPT_ROOT` above represents the path to the root directory created from running `cargo run` above and `$CONNECTION_STRING` is the Azure Storage connection string the Azure Functions host should use.
Where `$CONNECTION_STRING` is the Azure Storage connection string the Azure Functions host should use.
_Note: the syntax above works on macOS and Linux; on Windows, set the environment variables before running `dotnet run`._