Skip to content

Commit

Permalink
add docker multistage build
Browse files Browse the repository at this point in the history
Author: Michael Unknown <michael_knoerzer@gmx.net>

- Updated m.css dependency (TheLartians#151)
- correct hash
- eof newline
- add boost, make boost work in docker, solve merge conflict
- add docker ci/cd
- add docker multistage build
- solve conflict, add branchto ci
- delete old ci file
- fix cmake-format
- fix errors in CI workflows
  • Loading branch information
ClausKlein committed Aug 6, 2023
1 parent b66dba3 commit 5acb757
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 47 deletions.
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/build*
/.vscode
/cpm_modules
.DS_Store
/.github
/standalone/Dockerfile
94 changes: 94 additions & 0 deletions .github/workflows/docker-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: run Docker CD

on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
tag:
description: "Test scenario tag"
required: true
default: "test"
set-latest-tag:
description: "Also set the 'latest' tag with this run? (y/n)"
required: true
default: "n"

env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules

jobs:
build:
name: CD build docker and push to DockerHub
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: handle manual workflow start and prepare docker image tag(s)
id: docker-tags
shell: bash
run: |
if [[ "x${{ github.event.inputs.tag }}" != "x" ]]; then
echo "Workflow started via workflow_dispatch! Parameters: tag=${{ github.event.inputs.tag }}, set-latest-tag=${{ github.event.inputs.set-latest-tag }}"
tag="${{ github.event.inputs.tag }}"
else
echo "Workflow started via push with tag! Complete tag: ${GITHUB_REF:10}"
tag="${GITHUB_REF:11}"
fi
if [[ "x${{ github.event.inputs.set-latest-tag }}" == "xy" || "x${{ github.event.inputs.tag }}" == "x" ]]; then
tags="${{ secrets.DOCKERHUB_USERNAME }}/greeter-webapi:$tag, ${{ secrets.DOCKERHUB_USERNAME }}/greeter-webapi:latest"
echo "Docker image release tags: $tags"
else
tags="${{ secrets.DOCKERHUB_USERNAME }}/greeter-webapi:$tag"
echo "Docker image release tag: $tags"
fi
echo ::set-output name=tags::$tags
#
# configure and build in GitHub CI as smoke test

# speed up configure step by installing boost as system lib, also use Ninja for faster builds
- name: speed up configure and build
shell: bash
run: sudo apt-get update && sudo apt-get install -y libboost-all-dev ninja-build

# use GitHub cache to cache dependencies
- uses: actions/cache@v2
with:
path: "**/cpm_modules"
key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }}

- name: configure
run: cmake -S standalone -B build -G Ninja -D CMAKE_BUILD_TYPE=Release

- name: build
run: cmake --build build

#
# end GitHub CI local build

- name: set up Docker Buildx for multi-platform support
uses: docker/setup-buildx-action@v1

- name: set up QEMU for multi-platform support
uses: docker/setup-qemu-action@v1

- name: login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: build Docker image and push to DockerHub
uses: docker/build-push-action@v2
with:
file: ./standalone/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.docker-tags.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
63 changes: 63 additions & 0 deletions .github/workflows/docker-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: run Docker CI

on:
push:
branches:
- main
- master
- add-docker-build
pull_request:
branches:
- main
- master

env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules

jobs:
build:
name: CI build docker
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

#
# configure and build in GitHub CI as smoke test

# speed up configure step by installing boost as system lib, also use Ninja for faster builds
- name: speed up configure and build
shell: bash
run: sudo apt-get update && sudo apt-get install -y libboost-all-dev ninja-build

# use GitHub cache to cache dependencies
- uses: actions/cache@v2
with:
path: "**/cpm_modules"
key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }}

- name: configure
run: cmake -S standalone -B build -G Ninja -D CMAKE_BUILD_TYPE=Release

- name: build
run: cmake --build build

#
# end GitHub CI local build

- name: set up Docker Buildx for multi-platform support
uses: docker/setup-buildx-action@v1

- name: set up QEMU for multi-platform support
uses: docker/setup-qemu-action@v1

# build image but do NOT push to DockerHub
- name: build Docker image
uses: docker/build-push-action@v2
with:
file: ./standalone/Dockerfile
platforms: linux/amd64,linux/arm64
push: false
tags: ${{ secrets.DOCKERHUB_USERNAME }}/greeter-webapi:ci
cache-from: type=gha
cache-to: type=gha,mode=max
3 changes: 3 additions & 0 deletions .github/workflows/standalone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ jobs:
run: |
cd build
ctest --build-config Debug
- name: run
run: ./build/GreeterStandalone
43 changes: 41 additions & 2 deletions standalone/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
cmake_minimum_required(VERSION 3.21...3.27)

project(GreeterStandalone LANGUAGES CXX)
project(
GreeterStandalone
DESCRIPTION "A standalone minimal webapi application using the Crow framework"
LANGUAGES CXX
)

if(PROJECT_IS_TOP_LEVEL AND CMAKE_BUILD_TYPE STREQUAL "Release")
set(CMAKE_SKIP_INSTALL_RULES
Expand All @@ -19,13 +23,47 @@ include(../cmake/tools.cmake)

include(../cmake/CPM.cmake)

#TODO(CK): add_compile_definitions(BOOST_ASIO_NO_DEPRECATED)

# Crow needs Boost 1.72 and does not provide CPM.cmake integration itself, so we have to get Boost
# first
find_package(Boost 1.72 QUIET)
if(NOT Boost_FOUND)
# Use CPM.cmake to get Boost from the official repo if not provided as system lib
message(STATUS "GreeterStandalone: Boost system lib NOT found")
CPMAddPackage(
NAME Boost
GITHUB_REPOSITORY boostorg/boost
GIT_TAG boost-1.78.0
VERSION 1.78.0
)
# Ugly workaround: Boost cmake support is still experimental, the Boost::boost target is not
# provided if downloaded via FetchContent_declare / CPM.cmake. Crow uses it for asio, so we fake
# the Boost:boost target as asio
if(NOT TARGET Boost::boost)
add_library(Boost::boost INTERFACE IMPORTED)
target_link_libraries(Boost::boost INTERFACE Boost::asio)
endif()
else()
message(STATUS "GreeterStandalone: Boost system lib found")
endif()
# add Crow
CPMAddPackage(
NAME Crow
GITHUB_REPOSITORY CrowCpp/Crow
GIT_TAG v1.0+5
VERSION 1.0.0
OPTIONS "CROW_INSTALL OFF"
)

CPMAddPackage(
GITHUB_REPOSITORY jarro2783/cxxopts
VERSION 3.1.1
SYSTEM ON # used in case of cmake v3.25
OPTIONS "CXXOPTS_BUILD_EXAMPLES NO" "CXXOPTS_BUILD_TESTS NO" "CXXOPTS_ENABLE_INSTALL YES"
)

# get the Greeter lib
CPMAddPackage(
NAME Greeter
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..
Expand All @@ -40,7 +78,8 @@ add_executable(${PROJECT_NAME} ${sources})
if(CMAKE_DEBUG_POSTFIX)
set_property(TARGET ${PROJECT_NAME} PROPERTY DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
endif()
target_link_libraries(${PROJECT_NAME} Greeter::Greeter cxxopts::cxxopts)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
target_link_libraries(${PROJECT_NAME} Greeter::Greeter cxxopts::cxxopts Crow::Crow)
set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ${OPTION_ENABLE_UNITY})

# --- Test it ---
Expand Down
21 changes: 21 additions & 0 deletions standalone/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# build
FROM buildpack-deps:bullseye as webapp-build
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
cmake \
ninja-build \
# configure/build with Boost as system lib - this should be orders of magnitude faster to configure than
# downloading via CPM.cmake while Boost's CMake support is still experimental
libboost-all-dev \
;
COPY . .
RUN cmake -S standalone -B build -G Ninja -D CMAKE_BUILD_TYPE=Release
RUN cmake --build build


# deploy
FROM debian:bullseye-slim as webapp-run
WORKDIR /app
COPY --from=webapp-build /build/GreeterStandalone .
CMD ["./GreeterStandalone"]
85 changes: 40 additions & 45 deletions standalone/source/main.cpp
Original file line number Diff line number Diff line change
@@ -1,53 +1,48 @@
#include <crow.h>
#include <greeter/greeter.h>
#include <greeter/version.h>

#include <cxxopts.hpp>
#include <iostream>
#include <string>
#include <unordered_map>

auto main(int argc, char** argv) -> int {
const std::unordered_map<std::string, greeter::LanguageCode> languages{
{"en", greeter::LanguageCode::EN},
{"de", greeter::LanguageCode::DE},
{"es", greeter::LanguageCode::ES},
{"fr", greeter::LanguageCode::FR},
};

cxxopts::Options options(*argv, "A program to welcome the world!");

std::string language;
std::string name;

// clang-format off
options.add_options()
("h,help", "Show help")
("v,version", "Print the current version number")
("n,name", "Name to greet", cxxopts::value(name)->default_value("World"))
("l,lang", "Language code to use", cxxopts::value(language)->default_value("en"))
;
// clang-format on

auto result = options.parse(argc, argv);

if (result["help"].as<bool>()) {
std::cout << options.help() << std::endl;
return 0;
}

if (result["version"].as<bool>()) {
std::cout << "Greeter, version " << GREETER_VERSION << std::endl;
return 0;
}

auto langIt = languages.find(language);
if (langIt == languages.end()) {
std::cerr << "unknown language code: " << language << std::endl;
return 1;
}

greeter::Greeter greeter(name);
std::cout << greeter.greet(langIt->second) << std::endl;

return 0;
int main() {
crow::SimpleApp app;

CROW_ROUTE(app, "/hello")
([](const crow::request& req) {
// check params
std::cout << "Params: " << req.url_params << "\n";
std::cout << "The key 'language' was "
<< (req.url_params.get("language") == nullptr ? "not " : "") << "found.\n";

if (req.url_params.get("language") == nullptr) {
// return bad request
return crow::response(400, "please provide a 'language' argument");
}
const auto language = req.url_params.get("language");

// see if langauge was found
const std::unordered_map<std::string, greeter::LanguageCode> languages{
{"en", greeter::LanguageCode::EN},
{"de", greeter::LanguageCode::DE},
{"es", greeter::LanguageCode::ES},
{"fr", greeter::LanguageCode::FR},
};
const auto langIt = languages.find(language);
if (langIt == languages.end()) {
// return bad request
std::cout << "Greeting for language '" << language << "' is not available\n";
return crow::response(400, "language not recognized");
}

const greeter::Greeter greeter("Crow & Greeter");
std::cout << "Greeting for language '" << language << "' is available, returning message\n";
const auto message = greeter.greet(langIt->second);

crow::json::wvalue ret({{"answer", message}});
return crow::response(200, ret);
});

app.port(3080).multithreaded().run();
}

0 comments on commit 5acb757

Please sign in to comment.