Skip to content

Commit

Permalink
Merge pull request #1 from Michionlion/feature/initial-implementation
Browse files Browse the repository at this point in the history
Initial implementation
  • Loading branch information
Michionlion authored Jun 22, 2023
2 parents 4ffa1aa + 9ab39b0 commit 417afb5
Show file tree
Hide file tree
Showing 14 changed files with 1,000 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/build
/.github
61 changes: 61 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Check Quality

on: [push, pull_request, workflow_call]

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Python 3.11
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install Poetry
uses: abatilo/actions-poetry@v2
with:
poetry-version: 1.5.1
- name: Setup Poetry
run: |
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
poetry env info
- name: Install system dependencies
run: sudo apt install libev-dev
- name: Install dependencies
run: poetry install --no-interaction --no-ansi
- name: Lint code
run: poetry run task lint

test:
name: Test
needs: lint
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Python 3.11
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install Poetry
uses: abatilo/actions-poetry@v2
with:
poetry-version: 1.5.1
- name: Setup Poetry
run: |
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
poetry env info
- name: Install system dependencies
run: sudo apt install libev-dev
- name: Install dependencies
run: poetry install --no-interaction --no-ansi
- name: Build docker image
run: poetry run task docker
- name: Build example docker image
run: poetry run task pre_example
# - name: Test example docker image
# run: poetry run task test_example
47 changes: 47 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Docker

on:
release:
types: [published]
workflow_dispatch:

jobs:
test:
uses: ./.github/workflows/main.yml
push:
needs: test
runs-on: ubuntu-latest
environment: dockerhub
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ vars.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: michionlion/microwrap
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
push: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Debug
run: |
docker ps -a
docker images -a
# VERSION=$(echo "${{ github.event.release.tag_name }}" | sed -e 's/^v//')
# type=raw,value=latest,enable={{is_default_branch}}
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Compilation temporary products
build/
*.c
*.so

# Manual test files
/microwrap.json
/test.sh
/microwrap.err
/microwrap.log
17 changes: 17 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Build microwrap
FROM python:3.11-buster as build

# Install dependencies
WORKDIR /microwrap
RUN apt update && apt install -y build-essential libev-dev
COPY . /microwrap
RUN pip install poetry
RUN poetry install
RUN poetry run task compile

# Create microwrap image
FROM python:3.11-slim-buster

COPY --from=build /microwrap/build/microwrap /usr/bin/microwrap

ENTRYPOINT [ "microwrap" ]
66 changes: 51 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,69 @@
# MicroWrap

MicroWrap is a base container image designed to streamline and simplify the process of developing and deploying microservices through the use of containerization.
MicroWrap is a base container image designed to streamline and simplify the process of developing and deploying microservices through the use of containerization. Using it is as simple as writing a Dockerfile for your application, and including a `microwrap.json` configuration; see the example below.

MicroWrap functions as an executable wrapper, abstracting the complexities of network communication and service execution away from the application itself. It consists of an HTTP server that listens for incoming HTTP requests and translates those HTTP requests to command-line invocations of the wrapped executable. The translation process supports parameters -- URL parameters embedded in the request will become `--option value` strings passed to the wrapped executable. The standard output of the wrapped executable will be returned as the body of the response to the triggering request. For example, the request `GET http://localhost/execute?option1=test2&flag1` would trigger the invocation `/executable/path --option1 "test2" --flag1`, and the standard output would be returned as the body of the response to the `GET` HTTP request.
```Dockerfile
# Create an image using microwrap as the base to serve as our runtime image
FROM michionlion/microwrap:latest
# Configure microwrap
COPY example/microwrap.json /microwrap.json
# Upload executable to expose as a service
COPY example/version.sh /version.sh
```

MicroWrap functions as an executable wrapper, abstracting the complexities of network communication and service execution away from the application itself. It consists of an HTTP server that listens for incoming HTTP requests and translates those HTTP requests to command-line invocations of the wrapped executable. The translation process supports parameters -- URL parameters embedded in the request will become `--option value` strings passed to the wrapped executable. The standard output of the wrapped executable will be returned as the body of the response to the triggering request.

As an example, suppose the following request was made to a container running microwrap.

```shell
http GET http://$HOST:$PORT/start?option1=test2&flag1
```

The base container image MicroWrap defines should be used as the base image for a further Dockerfile build, which can specify mounting locations, compile or upload the executable to be wrapped, and configure MicroWrap. An example container image using MicroWrap to run a version service is defined in this repository at `example/Dockerfile`.
This request would trigger microwrap to execute its configured executable as follows.

```shell
/executable/path --option1 "test2" --flag1
```

The standard output of the execution would be returned as the body of the response to the `GET` HTTP request, and if the executable exits with a non-zero return code, an HTTP 500 Internal Server Error is returned (with the body being the concatenated standard output and standard error streams).

## Usage

To make your application a containerized service, you will need to write a Dockerfile that builds an image. This image can then be used in many different containerized environments, such as Docker, OpenShift, Kubernetes, and others. The Dockerfile for your application needs to accomplish two tasks: allow execution of your program, and configure MicroWrap. To allow your program to execute, the Dockerfile should install dependencies that your program needs, compile your program, and configure the runtime environment so that your program can execute. Additionally, you may want to prepare mount points for any folders that may need to be accessed by your program for external reading/writing, in the case that such input/output is needed.
To make your application a containerized service, you will need to write a Dockerfile that builds an image. This image can then be used in many different containerized environments, such as Docker, OpenShift, Kubernetes, and others. The Dockerfile for your application needs to accomplish two tasks: allow execution of your program, and configure MicroWrap. To allow your program to execute, the Dockerfile should install dependencies that your program needs, compile your program, and configure the runtime environment so that your program can execute. Additionally, you may want to prepare mount points for any folders that may need to be accessed by your program for external reading/writing, in the case that such input/output is needed. An example (which specifically uses a Java program, but is applicable to many different languages and technologies) is given in the `example/` directory of this repository.

## Configuration

1. **Host** This is the host name to bind the server to; it defaults to `"0.0.0.0"` and should rarely need to be changed.
1. **Port** This is the port to bind the server to; it defaults to `80` and can be changed if needed.
1. **Executable Path** This is the location of the executable file that will be executed per request. It should be an executable file in your image.
2. **Max Active Requests** This is the number of wrapped-executable invocations to allow at one time; any requests beyond this number will be queued for future invocation. Specify `-1` for no limit.
3. **Allowed Parameters** This is a list of URL parameters that will be passed through as command-line options to the wrapped executable. Any other parameters will be ignored.
4. **Default Parameters** This is an object which is mapped to `--attribute value` strings passed to the wrapped executable that can be overridden by URL parameters. Values that are `true` and `false` will not map to `"true"` or `"false"`, but instead value-less `--flag` and `--no-flag` (for an attribute named `flag`) strings; values that are `null` or the empty string `""` will cause the parameter to be ignored.
1. **Concurrent** Whether to allow multiple requests to execute invocations concurrently; if `false`, only one invocation will be handled at a time.
1. **Allowed Parameters** This is a list of URL parameters that will be passed through as command-line options to the wrapped executable. Any other parameters will be ignored.
1. **Default Parameters** This is an object which is mapped to `--attribute value` strings passed to the wrapped executable that can be overridden by URL parameters. Values that are `true` will not map to `"true"`, but instead a value-less `--flag` (for an attribute named `flag`) string; values that are `null`, `false`, or the empty string `""` will cause the parameter to be ignored.

These configuration parameters should be specified in the `/microwrap.json` configuration file in your image:

```json
{
"executablePath": "/root/program.sh",
"maxActiveRequests": 1,
"allowedParameters": ["option1", "flag1"],
"defaultParameters": {
"option1": "defaultValue1",
"flag1": false,
"flag2": true
}
"host": "0.0.0.0",
"port": 8080,
"concurrent": false,
"executablePath": "/root/program.sh",
"allowedParameters": ["option1", "flag1"],
"defaultParameters": {
"option1": "defaultValue1",
"alwaysonflag": true
}
}
```

## Future Work

- Named invocations and status checking
- Requires specific endpoints (no more "any endpoint -> invocation").
- `http://$HOST:$PORT/start/name?` Start an invocation named `name`; supports invocation parameters.
- `http://$HOST:$PORT/stop/name` Stop a running invocation named `name`.
- `http://$HOST:$PORT/running/name` Check if an invocation named `name` is running.
- `http://$HOST:$PORT/running` Get list of all running invocations.
- Progress reporting
- Need to have standard progress reporting by wrapped executable.
- Maybe a json file that the executable writes whenever and the `/progress` endpoint returns the contents of that file when called?
22 changes: 14 additions & 8 deletions example/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
# Build the service executable in a "development" container
FROM alpine:3.18 as build

RUN apk add --no-cache openjdk11
FROM debian:buster as build
# Add Java 11 JDK
RUN apt update && apt install -y openjdk-11-jdk
# Copy source code to container
COPY Version.java /Version.java
# Compile source code
RUN javac Version.java

# Create a new image with microwrap as the base to serve as our runtime image
FROM michionlion/microwrap:0.1
# Install runtime dependency
RUN apk add --no-cache openjre11
# Create an image using microwrap as the base to serve as our runtime image
FROM michionlion/microwrap:latest
# Install Java 11 JRE
RUN apt update && apt install -y openjdk-11-jre && apt clean && rm -rf /var/lib/apt/lists/*
# Configure microwrap
COPY microwrap.json /microwrap.json
# Copy the executable from the build image
# Copy executable (a script to run Version.class)
COPY version.sh /version.sh
# Copy compiled code from the build image
COPY --from=build /Version.class /Version.class

EXPOSE 80
2 changes: 0 additions & 2 deletions example/Version.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
package example;

public class Version {
public static void main(String[] args) {
var VERSION = "1.0.0";
Expand Down
4 changes: 3 additions & 1 deletion example/microwrap.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"host": "0.0.0.0",
"port": 80,
"concurrent": false,
"executablePath": "/version.sh",
"maxActiveRequests": -1,
"allowedParameters": [
"include-build"
],
Expand Down
10 changes: 4 additions & 6 deletions example/version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

USE_JAVA=true

if [ "$USE_JAVA" == "true" ]; then
java Version.class
if [ "$USE_JAVA" = "true" ] && [ -f Version.class ]; then
java -classpath . Version "$@"
else
VERSION="1.0.0"

if [ "$1" == "--include-build" ]; then
VERSION="2.0.0"
if [ "$1" = "--include-build" ]; then
VERSION="$VERSION-b4"
fi

echo "$VERSION"
fi
2 changes: 2 additions & 0 deletions microwrap/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""MicroWrap translates HTTP requests to invocations of an arbitrary executable."""
from microwrap import microwrap as application
Loading

0 comments on commit 417afb5

Please sign in to comment.