Services built with Rust have some fantastic runtime qualities for serverless/container scenarios:
low resource footprint
quick startup time
zero garbage collection
However these things come with a trade off as build times are not ideal for rapid application development.
roche addresses this short coming by providing a function as a service pattern for tide that reduces build times to seconds and enables developers to focus on business logic allowing for the speedy delivery of blazing fast and energy efficient software.
It leverages the nesting feature of tide so all that is required to be developed is a handler while the application infrastructure is provided by prebuilt docker containers.
Once the base images are downloaded build times are around 5s for debug and 30s for Release.
Roche is intended to target knative environments but it can also be used to build standard docker containers.
There are other opensource tools in this space that provide similar functionality.
As they mature converging may be appropriate but currently they have differing constraints and target audiences.
roche can be used either as a command line tool or a Rust
crate. We will look at prebuilt releases first
and then the different options for building from source.
Installing roche is relatively easy if you already have Rust and Cargo
installed. You just have to type this snippet in your terminal:
cargo install roche
This will fetch the source code for the latest release from
Crates.io and compile it. If you didn't choose the rustup option
you may have to add Cargo's bin directory to your PATH.
Run roche help in your terminal to verify if it works. Congratulations, you
have installed roche!
The git version contains all
the latest bug-fixes and features, that will be released in the next version on
Crates.io, if you can't wait until the next release. You can build the git
version yourself. Open your terminal and navigate to the directory of you
choice. We need to clone the git repository and then build it with Cargo.
git clone --depth=1 https://github.com/roche-rs/roche.git
cd roche
cargo build --release
The executable roche will be in the ./target/release folder, this should be
added to the path.
The init command is used to generate the initial boilerplate which can range from a simple function file to a predefined template or a full custom rust project. This documentation will step through each one of those scenarios to highlight how to use the init subcommand and options.
To see the complete list of options are available from the commandline by using
If you a looking for a quick end to end guide to get up and running then please refer to the simple tutorial.
This section aims to outline some of the detail behind that tutorial.
To generate a simple function the init command is used like this:
roche init
When using the init command without any parameters, a function file will be created for you in the current directory:
.
├── functions.rs
The functions.rs file is the bear minimum required to develop a running application and contains the following code:
#![allow(unused)]
fn main() {
pub fn handler() -> tide::Server<()> {
let mut api = tide::new();
api.at("/").get(|_| async { Ok("Hello, World!") });
api
}
}
This code can be modified as required and the base image has the following libraries imported as defined in the default repository.
async-std = { version = "1.6.5", features = ["attributes"] }
tide = "0.15.0"
serde = "1.0.117"
serde_urlencoded = "0.7.0"
surf = "2.1.0"
dotenv = "0.15.0"
uuid = { version = "0.8", features = ["v4"] }
To see how these libraries can be used to larger functionality try the functions available in the example library.
When you're ready you can move onto the build step.
The src directory contains the functions.rs that is used to build the service. It also contains a lib.rs
file which provides a sample implementation that can be used to test the service.
The Cargo.toml is for the test library. It is not used by the service.
The README.md contains specific instructions for the template
roche init https://github.com/roche-rs/default.git --branch main --name roche-sample
Roche supports all the options of a cargo generate project.
This enables you to enable you to create your own templates without having to modify the main roche project.
That said if you would like your project to be a predefined template then PRs are welcome.
The branch option allows you to select which branch to choose the default is main if it isn't supplied.
The name option autocompletes the name prompt to enable automation.
The src directory contains the functions.rs that is used to build the service. It also contains a lib.rs
file which provides a sample implementation that can be used to test the service.
The Cargo.toml is for the test library. It is not used by the service.
The README.md contains specific instructions for the template
If you a looking for a quick end to end guide to get up and running then please refer to the simple tutorial.
This section aims to outline some of the detail behind that tutorial.
In the default scenario build can be executed from either the project root or the same folder with the function file in.
roche build
docker username config
By default roche will try and obtain your current docker or podman user to generate the build tag.
Unfortunately older versions of docker on non-linux environments don't output complete docker info.
This can be resolved by either supplying a --tag option or setting a DOCKER_USERNAME environment variable.
Roche supports environment files.
Simply create a .env file in the same location as the functions.rs and it will be included in the development build.
See the env example for more details.
If you a looking for a quick end to end guide to get up and running then please refer to the simple tutorial.
This section aims to outline some of the detail behind that tutorial.
In the default scenario release can be executed from either the project root or the same folder with the function file in.
roche release
you must ensure that you are logged into docker or podman if you don't provide a --tag option
While Roche supports environment files for dev builds they are not included in the release build.
This is to prevent sensitive information being managed incorrectly in production environments.
The gen command is used to generate a Dockerfile that can be shipped with your function.
This enables CI/CD scenarios where the preference is not to allow the roche cli to proliferate outside of development environments.
See the complete list of options for gen by executing the following
This will create the following Dockerfile which is identical to the one created during the release command.
FROM quay.io/roche/default:1.0.0 as builder
COPY functions.rs /app-build/src/app
RUN cargo build --release
FROM quay.io/roche/alpine:3.12
RUN apk add --no-cache libgcc
RUN addgroup -S rocheuser && adduser -S rocheuser -G rocheuser
WORKDIR "/app"
COPY --from=builder --chown=rocheuser /app-build/run.sh /app-build/Cargo.toml /app-build/target/release/roche-service ./
USER rocheuser
ENV PORT 8080
EXPOSE 8080
For more information on how this file see the release documentation.
If you are using a custom baseimage or a custom runtime then these can be overridden using the appropriate options.
roche gen --buildimage quay.io/roche/default:1.0.0 --runtime quay.io/roche/alpine:3.12
By default the Dockerfile is configured to expect the functions.rs file to be in the same folder.
This of course is modifiable by editing the location of the functions.rs in the COPY line.
For example you may wish to have the Dockerfile in the root of your project so the line could be edited as follows
The test command copies the functions.rs and lib.rs from the local project to a docker image
and executes cargo test --lib in the running container with the output of the test is displayed on the commandline.
See the complete list of options for test by executing the following
It's unlikely that you will need these options as the test image tag is generated automatically and the test image should be avaiable from the template project.
However options to overide them are provided for usage consistency.
roche test -l quay.io/roche/dev-default:1.4.0 -t mytestimage
Notes
If you create a default project a sample integration test is provided.
Once built the project can be tested using the following steps:
Run the container
docker run -p 8080:8080 -t nameofyourbuild
From the template project folder run the cargo test against the container.
cargo test --test '*'
The --test '*' flag is designed to ignore the tests in the lib and only run the tests in the tests folder.
This tutorial will walk you through modifiying the default project from the default surf request to return some JSON instead.
It is intended to get you orientated with the more rust-like developer flow for roche.
We are going to replace the contents of the src/functions.rs with the following code:
#![allow(unused)]
fn main() {
use tide::prelude::*; // Pulls in the json! macro.
pub fn handler() -> tide::Server<()> {
let mut api = tide::new();
api.at("/animals").get(|_| async {
Ok(json!({
"meta": { "count": 2 },
"animals": [
{ "type": "cat", "name": "chashu" },
{ "type": "cat", "name": "nori" }
]
}))
});
api
}
}
3. Run the test
Now run the tests with roche test to see if we have broken anything.
roche test
...
running 1 test
test run_lib ... FAILED
failures:
---- run_lib stdout ----
thread 'run_lib' panicked at 'assertion failed: resp_string.contains("httpbin.org")', src/lib.rs:11:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
run_lib
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
As expected the test fails.
4. Redefine test
We have broken the tests the response string no longer contains httpbin.org :(
So let's update the src/lib.rs file so that the test pass.
As we are now returning JSON we can use a more formal type to test against.
The TideTestingExt library gives some nice utility features that allow us to exercise routes without running a full http server.
As the container is now so pre-fetched retests will be a lot faster.
Note that the integration tests are ignored. This is by design as they exercise the http endpoint.
The integration tests where the environment where the app is deployed.
There will be a further tutorial on this when we look at an end to end knative workflow.
5. Prepare for CI/CD
Roche supports building releases from the desktop with the release option.
However it might be preferable to integrate the code in a more formal CI/CD pipeline.
As we don't want to introduce the roche cli into the CI/CD pipeline it's possible to generate the Dockerfile and include it with your project. This can then be picked up easily by the CI tool.
Just cd into the src folder and run roche gen.
cd src
roche gen
This generates the following document that can be modified to suit the CI requirements.
FROM quay.io/roche/default:1.4.0 as builder
COPY functions.rs /app-build/src
COPY lib.rs /app-build/src
RUN cargo build --release && cargo test --lib --release
FROM quay.io/roche/alpine-libgcc:3.12
RUN addgroup -S rocheuser && adduser -S rocheuser -G rocheuser
WORKDIR "/app"
COPY --from=builder --chown=rocheuser /app-build/run.sh /app-build/Cargo.toml /app-build/target/release/roche-service ./
USER rocheuser
ENV PORT 8080
EXPOSE 8080
CMD ["./run.sh"]
6. That's it!
Hopefully you now have a feel for how roche development with tide can work in a more rust project manner.
.rocherc
.rocherc contains the images that are used to build the services.
These images store the intialised builds that reduce build times and provide an initial implementation to get up and running with.
Cargo.toml
This Cargo.toml is not used to run the builds but is supplied so that code completion and external integrations tests can be developed.
README.md
The description of the project and how to use it.
src/.env
The .env file is used by the build and test commands to ensure the environent is configured correctly.
The values in here can be modified to suite your local development environment.
But the .env file is not shipped in a release build (See 12 Factor Apps) but is passed to the build stages so that tests can run.
src/functions.rs
This contains a some sample code to create a City struct to illustrate creating an Object Document Mapper with wither.
Note the use of the model key word in the struct definintion.
Once the docker instance is up and running you can run the tests with
roche test
Running roche test copies the lib.rs and the function.rs to the test_build_image specified in .rocherc.
Roche then runs cargo test --lib in the container and the output is provided on the console.
roche test
DOCKER_USERNAME environment variable not found trying to docker cli
No tag provided using roche/test-mongodb-sample
Roche: Sent file to builder for -troche/test-mongodb-sample
Downloading crates ...
Downloaded tide-testing v0.1.2
Compiling tide-testing v0.1.2
Compiling roche-mongodb-service v0.1.0 (/app-build)
Finished test [unoptimized + debuginfo] target(s) in 11.94s
Running target/debug/deps/roche_mongodb_service-372fa7f25cd4aa41
Roche: Build complete for -troche/test-mongodb-sample
STEP 1: FROM quay.io/roche/dev-mongodb:1.0.0
STEP 2: COPY . /app-build/src/
--> c27648fb9c7
STEP 3: RUN cargo test --lib
running 1 test
test run_lib ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
STEP 4: COMMIT roche/test-mongodb-sample
--> 2e6d1366404
2e6d13664048224fa41a024430dd2c513b15c331c294de28cbdf673e090c08d8
Notice that the DOCKER_USERNAME is resolved by roche. If you wish to override this behaviour that a DOCKER_USERNAME=yourusername can be added to .rocherc.
The test also generates a docker image called roche/test-mongodb-sample just incase it fails and more investigation of the build is required.
In the same folder as the .rocherc run in order to create a Dockerfile.
roche gen
You will notice that the file refers to the release image quay.io/roche/mongodb:1.0.0 as builder.
It also builds and tests the code in release mode and if sucessful it will copy the exe into a minimum configuration image to keep the size of the final artifact to a minimum.