From 93ec27bb02ff901f3e53bf13fb1761c670dbf765 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 12 Apr 2021 17:39:52 +0200 Subject: [PATCH 1/3] [#17] Wrote dockerfiles; moved everything to Docker --- Dockerfile | 37 ---------------------- Makefile | 25 ++++++++------- build | 66 ++++++++++++++++++++++++++++++--------- docker/Dockerfile.builder | 19 +++++++++++ docker/Dockerfile.dev | 6 ++++ docker/Dockerfile.rel | 22 +++++++++++++ 6 files changed, 112 insertions(+), 63 deletions(-) delete mode 100644 Dockerfile create mode 100644 docker/Dockerfile.builder create mode 100644 docker/Dockerfile.dev create mode 100644 docker/Dockerfile.rel diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index c54bb86..0000000 --- a/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# syntax = docker/dockerfile:1.2 - -# We use a multi-stage build to end up with a very small final image -FROM alpine:latest AS builder - -ENV PATH "$PATH:/root/.cargo/bin" - -WORKDIR /usr/src/app - -# Install build dependencies, rustup & rust's nightly build & toolchain -RUN apk update && apk add --no-cache openssl-dev build-base curl && \ - { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly; } - -# Copy source code over to builder -COPY Cargo.toml Cargo.lock ./ -COPY src/ ./src/ - -# Run the tests, don't want no broken docker image -# And then finally, build the project -# Thank the lords that this article exists -# https://users.rust-lang.org/t/sigsegv-with-program-linked-against-openssl-in-an-alpine-container/52172 -# TODO add what these flags do & why they work -# NOTE: cargo install auto-appends bin to the path -RUN --mount=type=cache,target=/usr/src/app/target RUSTFLAGS="-C target-feature=-crt-static" cargo test && \ - RUSTFLAGS="-C target-feature=-crt-static" cargo install --path . --bin fej --root /usr/local - - -# Now, we create the actual image -FROM alpine:latest - -# Install some dynamic libraries needed for everything to work -RUN apk update && apk add --no-cache openssl libgcc - -# Copy binary over to final image -COPY --from=builder /usr/local/bin/fej /usr/local/bin/fej - -CMD ["/usr/local/bin/fej"] diff --git a/Makefile b/Makefile index ab50282..c276d79 100644 --- a/Makefile +++ b/Makefile @@ -1,35 +1,36 @@ -IMAGE := chewingbever/fej - - all: debug .PHONY: all # Builds debug: - @ cargo build + @ ./build -m dev .PHONY: debug release: - @ cargo build --release + @ ./build -m rel .PHONY: release -image: Dockerfile - @ ./build '$(IMAGE)' -.PHONY: image - push: - @ ./build '$(IMAGE)' push + @ ./build -m prod -a push .PHONY: push # Run run: - @ RUST_BACKTRACE=1 cargo run --bin fej + @ ./build -m dev -a run .PHONY: run +stop: + @ docker stop -t 2 fej +.PHONY: stop + +logs: + @ docker logs -f fej +.PHONY: logs + # Testing test: - @ cargo test --no-fail-fast + @ ./build -m dev -a run -l -- test --no-fail-fast .PHONY: test format: diff --git a/build b/build index 8abbc39..32b04aa 100755 --- a/build +++ b/build @@ -1,10 +1,21 @@ #!/usr/bin/env bash -# Simple guard to check input args -[[ $# -eq 1 ]] || [[ $# -eq 2 ]] || { - >&2 echo "Usage: ./build IMAGE [ACTION]" - exit 1 -} +image="chewingbever/fej" +# Should be either dev or rel +mode="dev" +action="" +attach="--detach" + +while getopts ":i:m:a:l" c; do + case $c in + i ) image="$OPTARG" ;; + m ) mode="$OPTARG" ;; + a ) action="$OPTARG" ;; + l ) attach="" ;; + ? ) exit 1 ;; + esac +done +shift $((OPTIND-1)) # Extract current version from Cargo.toml & get current branch patch_version="$(grep -Po '(?<=version = ").*(?=")' Cargo.toml | head -n1)" @@ -25,31 +36,58 @@ else fi -# Run the actual build command -DOCKER_BUILDKIT=1 docker build -t "$1:$tags" . +# First, we build the builder +DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile.builder -t "$image-builder:latest" . -if [[ "$2" = push ]]; then +# Run the actual build command +if [ "$mode" = "rel" ]; then + DOCKER_BUILDKIT=1 docker build -t "$image:$tags" -f docker/Dockerfile.rel . + +elif [[ "$mode" = "dev" ]]; then + DOCKER_BUILDKIT=1 docker build -t "$image-dev:$tags" -f docker/Dockerfile.dev . + +else + >&2 echo "Invalid mode." + exit 1 + +fi + +if [[ "$action" = push ]]; then [[ "$branch" =~ ^develop|master$ ]] || { >&2 echo "You can only push from develop or master." exit 2 } + [[ "$mode" = "rel" ]] || { + >&2 echo "You can only push release builds." + exit 3 + } + for tag in "${tags[@]}"; do # Create the tag - docker tag "$1:$tags" "$1:$tag" + docker tag "$image:$tags" "$image:$tag" # Push the tag - docker push "$1:$tag" + docker push "$image:$tag" # Remove the tag again, if it's not the main tag - [[ "$tag" != "$tags" ]] && docker rmi "$1:$tag" + [[ "$tag" != "$tags" ]] && docker rmi "$image:$tag" done - elif [[ "$2" = run ]]; then - docker run \ + elif [[ "$action" = run ]]; then + if [[ "$mode" = "dev" ]]; then + # Create caching volumes if needed (they need to be named) + docker volume create fej_build-cache + docker volume create fej_registry-cache + + flags="-v fej_build-cache:/usr/src/app/target -v fej_registry-cache:/root/.cargo/registry" + fi + + docker run $attach $flags \ --rm \ --interactive \ --tty \ --publish 8000:8000 \ - "$1:$tags" + --name fej \ + "$image$([[ "$mode" != "rel" ]] && echo "-dev"):$tags" "$@" fi diff --git a/docker/Dockerfile.builder b/docker/Dockerfile.builder new file mode 100644 index 0000000..84d09ff --- /dev/null +++ b/docker/Dockerfile.builder @@ -0,0 +1,19 @@ +# We use a multi-stage build to end up with a very small final image +FROM alpine:latest AS builder + +ARG MODE +ARG RUN_TESTS + +ENV PATH "$PATH:/root/.cargo/bin" +# Needed for proper compiling of openssl-dev +ENV RUSTFLAGS="-C target-feature=-crt-static" + +WORKDIR /usr/src/app + +# Install build dependencies, rustup & rust's nightly build & toolchain +RUN apk update && apk add --no-cache openssl-dev build-base curl && \ + { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly; } + +# Copy source code over to builder +COPY Cargo.toml Cargo.lock ./ +COPY src/ ./src/ diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev new file mode 100644 index 0000000..a1a1e92 --- /dev/null +++ b/docker/Dockerfile.dev @@ -0,0 +1,6 @@ +FROM chewingbever/fej-builder:latest + +ENV RUST_BACKTRACE=1 + +ENTRYPOINT ["cargo"] +CMD ["run"] diff --git a/docker/Dockerfile.rel b/docker/Dockerfile.rel new file mode 100644 index 0000000..4d40ef2 --- /dev/null +++ b/docker/Dockerfile.rel @@ -0,0 +1,22 @@ +FROM chewingbever/fej-builder:latest AS builder + +# And then finally, build the project +# Thank the lords that this article exists +# https://users.rust-lang.org/t/sigsegv-with-program-linked-against-openssl-in-an-alpine-container/52172 +# TODO add what these flags do & why they work +# NOTE: cargo install auto-appends bin to the path +RUN --mount=type=cache,target=/usr/src/app/target \ + --mount=type=cache,target=/root/.cargo/registry \ + cargo install --path . --bin fej --root /usr/local + + +# Now, we create the actual image +FROM alpine:latest + +# Install some dynamic libraries needed for everything to work +RUN apk update && apk add --no-cache openssl libgcc + +# Copy binary over to final image +COPY --from=builder /usr/local/bin/fej /usr/local/bin/fej + +CMD ["/usr/local/bin/fej"] From 34b32a3252ab1029b28c3e7741c8b0e17fa17c29 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 12 Apr 2021 18:19:22 +0200 Subject: [PATCH 2/3] [#17] Small changes to build script --- build | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/build b/build index 32b04aa..d7d86a3 100755 --- a/build +++ b/build @@ -18,12 +18,10 @@ done shift $((OPTIND-1)) # Extract current version from Cargo.toml & get current branch -patch_version="$(grep -Po '(?<=version = ").*(?=")' Cargo.toml | head -n1)" -major_version="$(echo "$patch_version" | - sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+)/\1/')" -minor_version="$(echo "$patch_version" | - sed -E 's/([0-9]+).([0-9]+).([0-9]+)/\1.\2/')" -branch="$(git branch --show-current)" +patch_version=`grep -Po '(?<=version = ").*(?=")' Cargo.toml | head -n1` +major_version=`echo "$patch_version" | sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+)/\1/'` +minor_version=`echo "$patch_version" | sed -E 's/([0-9]+).([0-9]+).([0-9]+)/\1.\2/'` +branch=`git rev-parse --abbrev-ref HEAD` if [[ "$branch" = "master" ]]; then tags=("$patch_version" "$minor_version" "$major_version" "latest") From 6d4cf6feb6aad6657d52075bb3a77978acd94be6 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 12 Apr 2021 21:33:56 +0200 Subject: [PATCH 3/3] [closes #6] added documentation everywhere --- .hooks/pre-commit | 16 ++++++++-------- src/errors.rs | 3 +++ src/ivago/README.md | 5 ----- src/ivago/controller/mod.rs | 19 +++++++++---------- src/ivago/controller/pickup_time.rs | 10 ++++++++-- src/ivago/controller/street.rs | 3 ++- src/ivago/mod.rs | 21 ++++++++++++++++++--- 7 files changed, 48 insertions(+), 29 deletions(-) delete mode 100644 src/ivago/README.md diff --git a/.hooks/pre-commit b/.hooks/pre-commit index 825db47..709fb95 100755 --- a/.hooks/pre-commit +++ b/.hooks/pre-commit @@ -6,12 +6,12 @@ make lint &> /dev/null 2>&1 || { exit 1; } -branch=`git rev-parse --abbrev-ref HEAD` +# branch=`git rev-parse --abbrev-ref HEAD` -# TODO should we add release branches here as well? -if [[ "$branch" =~ ^master|develop$ ]]; then - make test > /dev/null 2>&1 || { - >&2 echo "Tests failed. check 'make test' for more info."; - exit 1; - } -fi +# # TODO should we add release branches here as well? +# if [[ "$branch" =~ ^master|develop$ ]]; then +# make test > /dev/null 2>&1 || { +# >&2 echo "Tests failed. check 'make test' for more info."; +# exit 1; +# } +# fi diff --git a/src/errors.rs b/src/errors.rs index dfb137f..afcf04a 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,9 @@ // I can probably do this way easier using an external crate, I should do that use rocket::http::Status; +/// Represents any general error that the API can encounter during execution. +/// It allows us to more easily process errors, and hopefully allows for +/// more clear error messages. #[derive(Debug, PartialEq)] pub enum FejError { InvalidArgument, diff --git a/src/ivago/README.md b/src/ivago/README.md deleted file mode 100644 index 02d2d0e..0000000 --- a/src/ivago/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Ivago - -This part of the API is a wrapper around the Ivago website (Ivago being the -company that collects the trash in my city). Their site isn't exactly RESTful, -so this endpoint simply wraps it in a RESTful wrapper. diff --git a/src/ivago/controller/mod.rs b/src/ivago/controller/mod.rs index 0084a3d..7d3a9f9 100644 --- a/src/ivago/controller/mod.rs +++ b/src/ivago/controller/mod.rs @@ -20,15 +20,14 @@ const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; /// Endpoint for the actual calendar output const CAL_URL: &str = "https://www.ivago.be/nl/particulier/garbage/pick-up/pickups"; -/// Searches the Ivago API for streets in the given city +/// Searches the Ivago API for streets in the given city. /// /// # Arguments /// -/// * `street` - name of the street -/// * `city` - city the street is in -pub fn search_streets(street_name: &str) -> Result, FejError> { +/// * `search_term` - Search term to use to look for streets +pub fn search_streets(search_term: &str) -> Result, FejError> { let client = reqwest::Client::new(); - let response = client.get(SEARCH_URL).query(&[("q", street_name)]).send()?; + let response = client.get(SEARCH_URL).query(&[("q", search_term)]).send()?; let data: Vec> = response.json()?; // This is pretty cool, filter_map first does get() on all the maps, and @@ -41,14 +40,14 @@ pub fn search_streets(street_name: &str) -> Result, FejError> { .collect()) } -/// Returns the pickup times for the various trash types +/// Returns the pickup times for the various trash types. /// /// # Arguments /// -/// * `street` - desired street -/// * `number` - house number -/// * `start_date` - earliest date for the results -/// * `end_date` - latest date for the results +/// * `street` - Street to look up +/// * `number` - House number in given street +/// * `start_date` - Earliest date for the results +/// * `end_date` - Latest date for the results pub fn get_pickup_times( street: &Street, number: &u32, diff --git a/src/ivago/controller/pickup_time.rs b/src/ivago/controller/pickup_time.rs index 33718db..f70cbcf 100644 --- a/src/ivago/controller/pickup_time.rs +++ b/src/ivago/controller/pickup_time.rs @@ -1,14 +1,20 @@ use super::BasicDate; use serde::ser::{Serialize, SerializeStruct, Serializer}; -/// Represents a pickup time instance. All fields are a direct map of the -/// original API +/// Represents a date when a pickup will occur. Label describes which type of +/// trash will be picked up. pub struct PickupTime { date: BasicDate, label: String, } impl PickupTime { + /// Creates a new PickupTime instance. + /// + /// # Arguments + /// + /// * `date` - Date of pickup time + /// * `label` - Type of trash pub fn new(date: BasicDate, label: String) -> PickupTime { PickupTime { date: date, diff --git a/src/ivago/controller/street.rs b/src/ivago/controller/street.rs index 00f4de4..1a7cac9 100644 --- a/src/ivago/controller/street.rs +++ b/src/ivago/controller/street.rs @@ -4,7 +4,7 @@ use rocket::request::FromFormValue; use serde::ser::{Serialize, SerializeStruct, Serializer}; use std::convert::TryFrom; -/// Represents a street +/// Represents a street in a given city pub struct Street { name: String, city: String, @@ -76,6 +76,7 @@ impl<'v> FromFormValue<'v> for Street { mod tests { use super::*; + /// Tests the conversion to string #[test] fn test_to_string() { let street = Street::new(String::from("testname"), String::from("city")); diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index e4d1037..0e7bebc 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -9,11 +9,26 @@ pub fn routes() -> Vec { routes![route_search_streets, route_get_pickup_times] } -#[get("/search?")] -pub fn route_search_streets(street: String) -> Result>, Status> { - Ok(Json(search_streets(street.as_str())?)) +/// This route handles the Ivago search endpoint. It returns a list of streets, +/// consisting of a street name & a city. +/// +/// # Arguments +/// +/// * `search_term` - Search term to use to look for streets +#[get("/search?")] +pub fn route_search_streets(search_term: String) -> Result>, Status> { + Ok(Json(search_streets(search_term.as_str())?)) } +/// Handles returning of pickup times for a specific address. It returns a list +/// of pickup times, containing a date and a description of the trash type. +/// +/// # Arguments +/// +/// * `street` - Street to look up +/// * `number` - House number in the given street +/// * `start_date` - Earliest date that can be returned +/// * `end_date` - Latest date that can be returned #[get("/?&&&")] pub fn route_get_pickup_times( street: Street,