From 758a61d708d7a1e57967217513e5b8ba08cfcbc5 Mon Sep 17 00:00:00 2001 From: devkelley <105753233+devkelley@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:31:34 -0700 Subject: [PATCH] Container support for freyja_apps (#9) * Add containerization for freyja_apps * Minor README changes * update get_uri to use variables rather than consts * Resolve fmt warnings * Fix grammar error in comment * removed syntax ref in Dockerfile * Updated readme's to be more concise * updated toolchain to specific version * Fix whitespace errors * fixed bad link * fixed spacing issue --- .freyja/config/grpc_proxy_config.json | 3 + Cargo.lock | 11 ++ Cargo.toml | 1 + Dockerfile.freyja_apps | 73 +++++++++++++ container/README.md | 30 ++++++ container/config/docker.env | 9 ++ container/config/podman.env | 9 ++ .../digital_twin/ibeji_adapter/Cargo.toml | 5 +- .../digital_twin/ibeji_adapter/src/config.rs | 25 +++++ .../ibeji_adapter/src/ibeji_adapter.rs | 21 ++-- freyja_apps/e2e/README.md | 2 +- freyja_apps/ibeji_adapter/Cargo.toml | 23 ++++ freyja_apps/ibeji_adapter/README.md | 102 ++++++++++++++++++ freyja_apps/ibeji_adapter/src/main.rs | 13 +++ freyja_apps/in_memory/Cargo.toml | 5 +- freyja_apps/in_memory/README.md | 72 ++++++++++++- freyja_apps/template/README.md | 2 +- rust-toolchain.toml | 2 +- 18 files changed, 395 insertions(+), 13 deletions(-) create mode 100644 .freyja/config/grpc_proxy_config.json create mode 100644 Dockerfile.freyja_apps create mode 100644 container/README.md create mode 100644 container/config/docker.env create mode 100644 container/config/podman.env create mode 100644 freyja_apps/ibeji_adapter/Cargo.toml create mode 100644 freyja_apps/ibeji_adapter/README.md create mode 100644 freyja_apps/ibeji_adapter/src/main.rs diff --git a/.freyja/config/grpc_proxy_config.json b/.freyja/config/grpc_proxy_config.json new file mode 100644 index 0000000..0d2f8a9 --- /dev/null +++ b/.freyja/config/grpc_proxy_config.json @@ -0,0 +1,3 @@ +{ + "consumer_address": "0.0.0.0:60010" +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 687cbb6..847d6b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -605,6 +605,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "freyja-ibeji-adapter-app" +version = "0.1.0" +dependencies = [ + "freyja", + "ibeji-adapter", + "in-memory-mock-cloud-adapter", + "in-memory-mock-mapping-client", + "tokio", +] + [[package]] name = "freyja-in-memory-app" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a4c6ba4..471d221 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "cloud_connectors/azure/proto-build", "freyja_adapters/cloud/azure_cloud_connector_adapter", "freyja_adapters/digital_twin/ibeji_adapter", + "freyja_apps/ibeji_adapter", "freyja_apps/in_memory", "freyja_apps/e2e", ] diff --git a/Dockerfile.freyja_apps b/Dockerfile.freyja_apps new file mode 100644 index 0000000..e70c8c8 --- /dev/null +++ b/Dockerfile.freyja_apps @@ -0,0 +1,73 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/engine/reference/builder/ + +################################################################################ +# Create a stage for building the application. + +ARG RUST_VERSION=1.72.1 +FROM docker.io/library/rust:${RUST_VERSION}-slim-bullseye AS build +ARG APP_NAME=freyja-in-memory-app +WORKDIR /sdv + +COPY ./ . + +# Add Build dependencies. +RUN apt update && apt upgrade -y && apt install -y \ + libssl-dev \ + pkg-config \ + protobuf-compiler + +# Check that APP_NAME argument is valid. +RUN sanitized=$(echo "${APP_NAME}" | tr -dc '^[a-zA-Z_0-9-]+$'); \ +[ "$sanitized" = "${APP_NAME}" ] || { \ + echo "ARG 'APP_NAME' is invalid. APP_NAME='${APP_NAME}' sanitized='${sanitized}'"; \ + exit 1; \ +} + +# Build the application with the 'containerize' feature. +RUN cargo build --release -p "${APP_NAME}" --features containerize + +# Copy the built application to working directory. +RUN cp ./target/release/"${APP_NAME}" /sdv/service + +################################################################################ +# Create a new stage for running the application that contains the minimal +# runtime dependencies for the application. This often uses a different base +# image from the build stage where the necessary files are copied from the build +# stage. +# +# The example below uses the debian bullseye image as the foundation for running the app. +# By specifying the "bullseye-slim" tag, it will also use whatever happens to be the +# most recent version of that tag when you build your Dockerfile. If +# reproducability is important, consider using a digest +# (e.g., debian@sha256:ac707220fbd7b67fc19b112cee8170b41a9e97f703f588b2cdbbcdcecdd8af57). +FROM docker.io/library/debian:bullseye-slim AS final + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +WORKDIR /sdv + +# Copy the executable from the "build" stage. +COPY --from=build /sdv/service /sdv/ +COPY --from=build /sdv/target/release/build/ /sdv/target/release/build/ + +ENV FREYJA_HOME=/sdv/.freyja + +# What the container should run when it is started. +CMD ["/sdv/service"] diff --git a/container/README.md b/container/README.md new file mode 100644 index 0000000..4c42e89 --- /dev/null +++ b/container/README.md @@ -0,0 +1,30 @@ +# Container Examples + +This document describes how to utilize the provided Dockerfiles for containerization. + +## Dockerfile Selection + +### Freyja Apps + +To containerize the [Example Freyja Apps](../freyja_apps/), use +[Dockerfile.freyja_apps](../Dockerfile.freyja_apps). This dockerfile defaults to the +[In Memory Example Application](../freyja_apps/in_memory/). + +Currently the following example applications can be containerized: + +- [In Memory Example Application](../freyja_apps/in_memory/) +- [Ibeji Adapter Example Application](../freyja_apps/ibeji_adapter) + +Each supported application has a README describing the steps to containerize the application. + +### Freyja Cloud Connectors + +Coming soon! + +### Env Files + +To run an application in Docker, ensure that the [docker.env](./config/docker.env) is passed in +when running the container. + +To run an application in Podman, ensure that the [podman.env](./config/podman.env) is passed in +when running the container. diff --git a/container/config/docker.env b/container/config/docker.env new file mode 100644 index 0000000..56bafc6 --- /dev/null +++ b/container/config/docker.env @@ -0,0 +1,9 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +# DNS name used by the container to communicate with host. +HOST_GATEWAY=host.docker.internal + +# Alias for localhost to be replaced by HOST_GATEWAY if run in a container. +LOCALHOST_ALIAS=0.0.0.0 diff --git a/container/config/podman.env b/container/config/podman.env new file mode 100644 index 0000000..1abc4a3 --- /dev/null +++ b/container/config/podman.env @@ -0,0 +1,9 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +# DNS name used by the container to communicate with host. +HOST_GATEWAY=host.containers.internal + +# Alias for localhost to be replaced by HOST_GATEWAY if run in a container. +LOCALHOST_ALIAS=0.0.0.0 diff --git a/freyja_adapters/digital_twin/ibeji_adapter/Cargo.toml b/freyja_adapters/digital_twin/ibeji_adapter/Cargo.toml index 4f97adc..fa7c2ef 100644 --- a/freyja_adapters/digital_twin/ibeji_adapter/Cargo.toml +++ b/freyja_adapters/digital_twin/ibeji_adapter/Cargo.toml @@ -28,4 +28,7 @@ tonic = { workspace = true } tower = { workspace = true } [build-dependencies] -freyja-build-common = { workspace = true } \ No newline at end of file +freyja-build-common = { workspace = true } + +[features] +containerize = [] diff --git a/freyja_adapters/digital_twin/ibeji_adapter/src/config.rs b/freyja_adapters/digital_twin/ibeji_adapter/src/config.rs index 7c476e0..b6613e3 100644 --- a/freyja_adapters/digital_twin/ibeji_adapter/src/config.rs +++ b/freyja_adapters/digital_twin/ibeji_adapter/src/config.rs @@ -2,7 +2,32 @@ // Licensed under the MIT license. // SPDX-License-Identifier: MIT +#![allow(unused_imports)] + use serde::{Deserialize, Serialize}; +use std::env; + +/// If feature 'containerize' is set, it will modify a localhost uri to point to container's +/// localhost DNS alias. Otherwise, returns the uri as a String. +/// +/// # Arguments +/// * `uri` - The uri to potentially modify. +pub fn get_uri(uri: &str) -> Result> { + #[cfg(feature = "containerize")] + let uri = { + // Container env variable names. + let host_gateway_env_var: &str = "HOST_GATEWAY"; + let host_alias_env_var: &str = "LOCALHOST_ALIAS"; + + // Return an error if container env variables are not set. + let host_gateway = env::var(host_gateway_env_var)?; + let host_alias = env::var(host_alias_env_var)?; + + uri.replace(&host_alias, &host_gateway) + }; + + Ok(uri.to_string()) +} /// Configuration for the Ibeji Adapter. /// Supports two different schemas based on the service discovery method. diff --git a/freyja_adapters/digital_twin/ibeji_adapter/src/ibeji_adapter.rs b/freyja_adapters/digital_twin/ibeji_adapter/src/ibeji_adapter.rs index b4b8826..4208da5 100644 --- a/freyja_adapters/digital_twin/ibeji_adapter/src/ibeji_adapter.rs +++ b/freyja_adapters/digital_twin/ibeji_adapter/src/ibeji_adapter.rs @@ -22,7 +22,7 @@ use freyja_contracts::{ entity::Entity, }; -use crate::config::{ChariottDiscoverRequest, Config}; +use crate::config::{self, ChariottDiscoverRequest, Config}; const CONFIG_FILE_STEM: &str = "ibeji_adapter_config"; const GET_OPERATION: &str = "Get"; @@ -43,10 +43,12 @@ impl IbejiAdapter { chariott_service_discovery_uri: &str, chariott_discovery_request: ChariottDiscoverRequest, ) -> Result { - let mut service_registry_client = - ServiceRegistryClient::connect(String::from(chariott_service_discovery_uri)) - .await - .map_err(DigitalTwinAdapterError::communication)?; + let chariott_uri = + config::get_uri(chariott_service_discovery_uri).map_err(DigitalTwinAdapterError::io)?; + + let mut service_registry_client = ServiceRegistryClient::connect(chariott_uri) + .await + .map_err(DigitalTwinAdapterError::communication)?; let discover_request = Request::new(DiscoverRequest { namespace: chariott_discovery_request.namespace, @@ -120,11 +122,14 @@ impl DigitalTwinAdapter for IbejiAdapter { } }; + let invehicle_digital_twin_uri = config::get_uri(&invehicle_digital_twin_service_uri) + .map_err(DigitalTwinAdapterError::io)?; + let client = futures::executor::block_on(async { execute_with_retry( max_retries, Duration::from_millis(retry_interval_ms), - || InvehicleDigitalTwinClient::connect(invehicle_digital_twin_service_uri.clone()), + || InvehicleDigitalTwinClient::connect(invehicle_digital_twin_uri.clone()), Some(String::from("Connection retry for connecting to Ibeji")), ) .await @@ -193,12 +198,14 @@ impl DigitalTwinAdapter for IbejiAdapter { String::from(GET_OPERATION) }; + let entity_uri = config::get_uri(&endpoint.uri).map_err(DigitalTwinAdapterError::io)?; + let entity = Entity { id: entity_id, description: Some(entity_access_info.description), name: Some(entity_access_info.name), operation, - uri: endpoint.uri, + uri: entity_uri, protocol: endpoint.protocol, }; diff --git a/freyja_apps/e2e/README.md b/freyja_apps/e2e/README.md index dc8bfbc..a9952c7 100644 --- a/freyja_apps/e2e/README.md +++ b/freyja_apps/e2e/README.md @@ -18,4 +18,4 @@ To build and run the application, follow these steps: cargo run -p freyja-e2e-app - This will rebuild the application as necessary and then run it. Note that running Cargo commands without specifying the `-p` argument might target every package in the workspace! When working with this repository it's recommended to use the `-p` argument with Cargo commands. + This will rebuild the `freyja-e2e-app` application as necessary and then run it. diff --git a/freyja_apps/ibeji_adapter/Cargo.toml b/freyja_apps/ibeji_adapter/Cargo.toml new file mode 100644 index 0000000..79d25df --- /dev/null +++ b/freyja_apps/ibeji_adapter/Cargo.toml @@ -0,0 +1,23 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# SPDX-License-Identifier: MIT + +[package] +name = "freyja-ibeji-adapter-app" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +# These two dependencies are required for anyone implementing a Freyja application +freyja = { workspace = true } +tokio = { workspace = true, features = ["macros"] } + +# Put any dependencies that you need for your adapters down here. +# This samples utilizes the in-memory mock adapters. +in-memory-mock-cloud-adapter = { workspace = true } +ibeji-adapter = { workspace = true } +in-memory-mock-mapping-client = { workspace = true } + +[features] +containerize = ["ibeji-adapter/containerize"] diff --git a/freyja_apps/ibeji_adapter/README.md b/freyja_apps/ibeji_adapter/README.md new file mode 100644 index 0000000..f43aa85 --- /dev/null +++ b/freyja_apps/ibeji_adapter/README.md @@ -0,0 +1,102 @@ +# Ibeji Adapter Freyja Example Application + +This Freyja Example Application utilizes the [Ibeji Digital Twin Adapter](../../freyja_adapters/digital_twin/ibeji_adapter/) and an in-memory mock cloud connector adapter to show a minimal connected local example of how to retrieve data from the vehicle. + +## Build and Run + +To build and run the application, follow these steps: + +1. (Optional) If necessary, author configuration overrides for the [`InMemoryMockMappingClient`](https://github.com/eclipse-ibeji/freyja/tree/main/mapping_clients/in_memory_mock_mapping_client). Refer to the adapter README files for instructions on how to do this. This repository provides overrides in the [`.freyja`](../../.freyja/) directory that can be used with the [`mixed` sample provided by Ibeji](https://github.com/eclipse-ibeji/ibeji/tree/main/samples/mixed). + +1. Set the `$FREYJA_HOME` environment variable. If you are using the provided overrides, you can run the following command to set the variable: + + export FREYJA_HOME={path-to-repo-root}/.freyja + + Alternatively, you can set the variable in a [Cargo configuration file](https://doc.rust-lang.org/cargo/reference/config.html) to only enable the variable while running a Cargo command. + +1. Run the following from the repo root: + + cargo run -p freyja-ibeji-adapter-app + + This will rebuild the `freyja-ibeji-adapter-app` application as necessary and then run it. + +## Containerize the Ibeji Adapter Freyja Example Application + +To build and run the application in a container, follow the steps under [Docker](#docker) or +[Podman](#podman). Ensure that the `$FREYJA_HOME` environment variable is set. + +### Docker + +#### Prerequisites + +[Install Docker](https://docs.docker.com/engine/install/) + +#### Running in Docker + +To run the service in a Docker container: + +1. Run the following command in the project's root directory to build the docker container from the +Dockerfile: + + ```shell + docker build -t freyja_ibeji_adapter --build-arg APP_NAME=freyja-ibeji-adapter-app -f Dockerfile.freyja_apps . + ``` + + The `APP_NAME` build arg needs to be set as `Dockerfile.freyja_apps` defaults to the + [In Memory Example Application](../in_memory/). + +1. Once the container has been built, start the container in interactive mode with the following +command in the project's root directory: + + ```shell + docker run -v ${FREYJA_HOME}:/sdv/.freyja --name freyja_ibeji_adapter -p 60010:60010 --env-file=./container/config/docker.env --add-host=host.docker.internal:host-gateway -it --rm freyja_ibeji_adapter + ``` + + `-v` mounts the `$FREYJA_HOME` path set above in the container allowing the application to use + the provided overrides for the mixed sample in Ibeji. + +1. To detach from the container, enter: + + Ctrl + p, Ctrl + q + +1. To stop the container, enter: + + ```shell + docker stop freyja_ibeji_adapter + ``` + +### Podman + +#### Prerequisites + +[Install Podman](https://podman.io/docs/installation) + +#### Running in Podman + +To run the service in a Podman container: + +1. Run the following command in the project's root directory to build the podman container from the +Dockerfile: + + ```shell + podman build -t freyja_ibeji_adapter --build-arg=APP_NAME=freyja-ibeji-adapter-app -f Dockerfile.freyja_apps . + ``` + + The `APP_NAME` build arg needs to be set as `Dockerfile.freyja_apps` defaults to the + [In Memory Example Application](../in_memory/). + +1. Once the container has been built, start the container with the following command in the +project's root directory: + + ```shell + podman run --mount=type=bind,src=${FREYJA_HOME},dst=/sdv/.freyja,ro=true -p 60010:60010 --env-file=./container/config/podman.env --network=slirp4netns:allow_host_loopback=true localhost/freyja_ibeji_adapter + ``` + + `-v` mounts the `$FREYJA_HOME` path set above in the container allowing the application to use + the provided overrides for the mixed sample in Ibeji. + +1. To stop the container, run: + + ```shell + podman ps -f ancestor=localhost/freyja_ibeji_adapter:latest --format="{{.Names}}" | xargs podman stop + ``` diff --git a/freyja_apps/ibeji_adapter/src/main.rs b/freyja_apps/ibeji_adapter/src/main.rs new file mode 100644 index 0000000..595df94 --- /dev/null +++ b/freyja_apps/ibeji_adapter/src/main.rs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// SPDX-License-Identifier: MIT + +use ibeji_adapter::ibeji_adapter::IbejiAdapter; +use in_memory_mock_cloud_adapter::in_memory_mock_cloud_adapter::InMemoryMockCloudAdapter; +use in_memory_mock_mapping_client::in_memory_mock_mapping_client::InMemoryMockMappingClient; + +freyja::freyja_main! { + IbejiAdapter, + InMemoryMockCloudAdapter, + InMemoryMockMappingClient +} diff --git a/freyja_apps/in_memory/Cargo.toml b/freyja_apps/in_memory/Cargo.toml index 68da7d5..0d16d1d 100644 --- a/freyja_apps/in_memory/Cargo.toml +++ b/freyja_apps/in_memory/Cargo.toml @@ -17,4 +17,7 @@ tokio = { workspace = true, features = ["macros"] } # This samples utilizes the in-memory mock adapters. in-memory-mock-cloud-adapter = { workspace = true } in-memory-mock-digital-twin-adapter = { workspace = true } -in-memory-mock-mapping-client = { workspace = true } \ No newline at end of file +in-memory-mock-mapping-client = { workspace = true } + +[features] +containerize = [] diff --git a/freyja_apps/in_memory/README.md b/freyja_apps/in_memory/README.md index d9098b5..9162fb5 100644 --- a/freyja_apps/in_memory/README.md +++ b/freyja_apps/in_memory/README.md @@ -10,8 +10,78 @@ To build and run the application, run the following from the repo root: cargo run -p freyja-in-memory-app ``` -This will rebuild the application as necessary and then run it. Note that running Cargo commands without specifying the `-p` argument might target every package in the workspace! When working with this repository it's recommended to use the `-p` argument with Cargo commands. +This will rebuild the `freyja-in-memory-app` application as necessary and then run it. ## Supported Versions This template is integrated with [revision `a07b033`](https://github.com/eclipse-ibeji/freyja/commit/a07b03349d23b14d0c215ace341e05d9e4e5195e) (authored 2023-11-09) of the Freyja main project. + +## Containerize the In Memory Freyja Example Application + +To build and run the application in a container, follow the steps under [Docker](#docker) or +[Podman](#podman). This is not a very useful example but shows how the application can be +containerized. + +### Docker + +#### Prerequisites + +[Install Docker](https://docs.docker.com/engine/install/) + +#### Running in Docker + +To run the service in a Docker container: + +1. Run the following command in the project's root directory to build the docker container from the +Dockerfile: + + ```shell + docker build -t freyja_in_memory -f Dockerfile.freyja_apps . + ``` + +1. Once the container has been built, start the container in interactive mode with the following +command in the project's root directory: + + ```shell + docker run --name freyja_in_memory --env-file=./container/config/docker.env -it --rm freyja_in_memory + ``` + +1. To detach from the container, enter: + + Ctrl + p, Ctrl + q + +1. To stop the container, enter: + + ```shell + docker stop freyja_in_memory + ``` + +### Podman + +#### Prerequisites + +[Install Podman](https://podman.io/docs/installation) + +#### Running in Podman + +To run the service in a Podman container: + +1. Run the following command in the project's root directory to build the podman container from the +Dockerfile: + + ```shell + podman build -t freyja_in_memory -f Dockerfile.freyja_apps . + ``` + +1. Once the container has been built, start the container with the following command in the +project's root directory: + + ```shell + podman run --env-file=./container/config/podman.env localhost/freyja_in_memory + ``` + +1. To stop the container, run: + + ```shell + podman ps -f ancestor=localhost/freyja_in_memory:latest --format="{{.Names}}" | xargs podman stop + ``` diff --git a/freyja_apps/template/README.md b/freyja_apps/template/README.md index 208399a..3583676 100644 --- a/freyja_apps/template/README.md +++ b/freyja_apps/template/README.md @@ -19,4 +19,4 @@ To create your own Freyja application, you can copy this template and make the f 1. Import your adapters with `use` statements. 1. Change the type names in the `freyja_main!` macro invocation to your imported types. Order matters here, so make sure you list your adapters in the same order as they are presented in the template. -To build and run your application, you can use Cargo commands such as `cargo run`. +To build and run your application, you can use Cargo commands such as `cargo run`. When working with this repository it's recommended to use the `-p` argument followed by the application package name with Cargo commands. Note that running Cargo commands without specifying the `-p` argument might target every package in the workspace! diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 31578d3..82ccade 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "stable" \ No newline at end of file +channel = "1.72.1" \ No newline at end of file