Skip to content

Commit 2013a7b

Browse files
authored
Feature add gha ci workflow and support tox based testing (#3)
* feat: Add support to tox, arm64, and a CI flow **What** - Add support for arm64 builds, this requires a switch to miniforge for conda. This is a community effort in conda to support arm, which is not fully suppported by Anaconda co. - Add support for testing with tox - Add two sample test functions that demonstrate support for numpy - Add CI workflow using github actions to verify that the same functions build as expected. Signed-off-by: Lucas Roesler <[email protected]>
1 parent dbec998 commit 2013a7b

32 files changed

+449
-24
lines changed

.github/workflows/test.yaml

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: test
2+
3+
on:
4+
push:
5+
branches:
6+
- '*'
7+
pull_request:
8+
branches:
9+
- '*'
10+
11+
jobs:
12+
build-test-functions:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@master
16+
with:
17+
fetch-depth: 0
18+
- name: Get faas-cli
19+
run: curl -sLSf https://cli.openfaas.com | sudo sh
20+
- name: Set up Go
21+
uses: actions/setup-go@v2
22+
with:
23+
go-version: ^1.15
24+
- name: Setup git-semver
25+
run: GO111MODULE=on go get github.com/mdomke/git-semver/[email protected]
26+
- name: Set up Docker Buildx
27+
uses: docker/setup-buildx-action@v1
28+
- name: Get TAG
29+
id: get_tag
30+
run: echo ::set-output name=tag::$(git-semver | tr '+' '.')
31+
- name: Get Repo Owner
32+
id: get_repo_owner
33+
run: >
34+
echo ::set-output name=repo_owner::$(echo ${{ github.repository_owner }} |
35+
tr '[:upper:]' '[:lower:]')
36+
- name: Debug variables
37+
run: |
38+
echo "repo_owner: ${{ steps.get_repo_owner.outputs.repo_owner }}"
39+
echo "tag: ${{ steps.get_tag.outputs.tag }}"
40+
echo "repo: ${{ github.repository }}"
41+
echo "git-describe: $(git describe --tags --always)"
42+
- name: Publish functions
43+
run: >
44+
DOCKER_BUILDKIT=1
45+
OWNER="${{ steps.get_repo_owner.outputs.repo_owner }}"
46+
TAG="${{ steps.get_tag.outputs.tag }}"
47+
faas-cli build
48+
--parallel 2
49+
--disable-stack-pull

pydatascience-test/__init__.py

Whitespace-only changes.

pydatascience-test/core/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Put common utilities and generalized logic here
2+
3+
A basic implementation for reading mounted secrets is provided, as an example.

pydatascience-test/core/__init__.py

Whitespace-only changes.

pydatascience-test/core/utils.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
3+
def getSecret(secret_name: str) -> str:
4+
"""load secret value from the openfaas secret folder
5+
Args:
6+
secret_name (str): name of the secret
7+
"""
8+
with open("/var/openfaas/secrets/" + secret_name, "r") as file:
9+
return file.read()

pydatascience-test/handler.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import json
2+
import os
3+
4+
import numpy as np
5+
6+
# from .core import utils
7+
8+
9+
function_root = os.environ.get("function_root")
10+
11+
# Now pre-load the model, e.g.
12+
# from .core import model
13+
14+
15+
def handle(req: bytes) -> str:
16+
"""handle a request to the function
17+
Args:
18+
req (bytes): request body
19+
"""
20+
21+
return json.dumps({
22+
"echo": req,
23+
"random": np.random.random_sample(),
24+
})

pydatascience-test/handler_test.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import json
2+
from .handler import handle
3+
4+
# Test your handler here
5+
6+
# To disable testing, you can set the build_arg `TEST_ENABLED=false` on the CLI or in your stack.yml
7+
# https://docs.openfaas.com/reference/yaml/#function-build-args-build-args
8+
9+
def test_handle():
10+
raw_output = handle("input")
11+
output = json.loads(raw_output)
12+
13+
assert output.get("echo") == "input"
14+
assert 0.0 <= output.get("random") <= 1.0

pydatascience-test/requirements.txt

Whitespace-only changes.

pydatascience-test/tox.ini

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# To disable testing, you can set the build_arg `TEST_ENABLED=false` on the CLI or in your stack.yml
2+
# https://docs.openfaas.com/reference/yaml/#function-build-args-build-args
3+
4+
# Additionally, you can replace the content of this file with
5+
# [tox]
6+
# skipsdist = true
7+
8+
# You can also edit, remove, or add additional test steps
9+
# by editing, removing, or adding new testenv sections
10+
11+
12+
# find out more about tox: https://tox.readthedocs.io/en/latest/
13+
[tox]
14+
envlist = lint,test
15+
skipsdist = true
16+
17+
[testenv:test]
18+
deps =
19+
flask
20+
pytest
21+
-r ../requirements.txt
22+
-rrequirements.txt
23+
commands =
24+
# run unit tests with pytest
25+
# https://docs.pytest.org/en/stable/
26+
# configure by adding a pytest.ini to your handler
27+
pytest
28+
29+
[testenv:lint]
30+
deps =
31+
flake8
32+
commands =
33+
flake8 .
34+
35+
[flake8]
36+
count = true
37+
max-line-length = 127
38+
max-complexity = 10
39+
statistics = true
40+
# stop the build if there are Python syntax errors or undefined names
41+
select = E9,F63,F7,F82
42+
show-source = true

pydatascience-test/train.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
Put your model training here
3+
"""

pydatascience-web-test/__init__.py

Whitespace-only changes.

pydatascience-web-test/core/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Put common utilities and generalized logic here
2+
3+
A basic implementation for reading mounted secrets is provided, as an example.

pydatascience-web-test/core/__init__.py

Whitespace-only changes.

pydatascience-web-test/core/utils.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
3+
def getSecret(secret_name: str) -> str:
4+
"""load secret value from the openfaas secret folder
5+
Args:
6+
secret_name (str): name of the secret
7+
"""
8+
with open("/var/openfaas/secrets/" + secret_name, "r") as file:
9+
return file.read()

pydatascience-web-test/handler.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
import json
3+
import os
4+
5+
from flask import Request
6+
import numpy as np
7+
8+
# from .core import utils
9+
10+
11+
function_root = os.environ.get("function_root")
12+
13+
# Now pre-load the model, e.g.
14+
# from .core import model
15+
16+
17+
def handle(req: Request):
18+
"""handle a request to the function.
19+
20+
Your response is immediately passed to the caller, unmodified.
21+
This allows you full control of the response, e.g. you can set
22+
the status code by returning a tuple (str, int). A detailed
23+
description of how responses are handled is found here:
24+
25+
http://flask.pocoo.org/docs/1.0/quickstart/#about-responses
26+
27+
Args:
28+
req (Request): Flask request object
29+
"""
30+
31+
return json.dumps({
32+
"echo": req.get_data().decode('utf-8'),
33+
"random": np.random.random_sample(),
34+
})
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import json
2+
from .handler import handle
3+
4+
import flask
5+
6+
app = flask.Flask(__name__)
7+
8+
9+
# Test your handler here
10+
11+
# To disable testing, you can set the build_arg `TEST_ENABLED=false` on the CLI or in your stack.yml
12+
# https://docs.openfaas.com/reference/yaml/#function-build-args-build-args
13+
14+
def test_handle():
15+
with app.test_request_context('/', data="input"):
16+
assert flask.request.path == '/'
17+
raw_output = handle(flask.request)
18+
output = json.loads(raw_output)
19+
20+
assert output.get("echo") == "input"
21+
assert 0.0 <= output.get("random") <= 1.0

pydatascience-web-test/requirements.txt

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
tox==3.*
2+
flake8
3+
pytest

pydatascience-web-test/tox.ini

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# To disable testing, you can set the build_arg `TEST_ENABLED=false` on the CLI or in your stack.yml
2+
# https://docs.openfaas.com/reference/yaml/#function-build-args-build-args
3+
4+
# Additionally, you can replace the content of this file with
5+
# [tox]
6+
# skipsdist = true
7+
8+
# You can also edit, remove, or add additional test steps
9+
# by editing, removing, or adding new testenv sections
10+
11+
12+
# find out more about tox: https://tox.readthedocs.io/en/latest/
13+
[tox]
14+
envlist = lint,test
15+
skipsdist = true
16+
17+
[testenv:test]
18+
deps =
19+
flask
20+
pytes
21+
-r ../requirements.txt
22+
-rrequirements.txt
23+
commands =
24+
# run unit tests with pytest
25+
# https://docs.pytest.org/en/stable/
26+
# configure by adding a pytest.ini to your handler
27+
pytest
28+
29+
[testenv:lint]
30+
deps =
31+
flake8
32+
commands =
33+
flake8 .
34+
35+
[flake8]
36+
count = true
37+
max-line-length = 127
38+
max-complexity = 10
39+
statistics = true
40+
# stop the build if there are Python syntax errors or undefined names
41+
select = E9,F63,F7,F82
42+
show-source = true

pydatascience-web-test/train.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
Put your model training here
3+
"""

stack.yml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: 1.0
2+
provider:
3+
name: openfaas
4+
gateway: http://127.0.0.1:8080
5+
6+
functions:
7+
pydatascience-test:
8+
lang: pydatascience
9+
handler: ./pydatascience-test
10+
image: ghcr.io/${OWNER:-lucasroesler}/pydatascience-test:${TAG:-latest}
11+
pydatascience-web-test:
12+
lang: pydatascience-web
13+
handler: ./pydatascience-web-test
14+
image: pydatascience-web-test:latest
15+

template/pydatascience-web/Dockerfile

+25-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
ARG watchdog_version=0.7.7
2-
FROM openfaas/of-watchdog:${watchdog_version} as watchdog
3-
FROM python:3-slim
1+
ARG watchdog_version=0.8.4
2+
ARG python_version=3.9
3+
FROM --platform=${TARGETPLATFORM:-linux/amd64} ghcr.io/openfaas/of-watchdog:${watchdog_version} as watchdog
4+
FROM --platform=${TARGETPLATFORM:-linux/amd64} python:${python_version}-slim
45

56
COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog
67
RUN chmod +x /usr/bin/fwatchdog
@@ -17,9 +18,10 @@ ENV HOME /home/app
1718
ENV PATH=$HOME/conda/bin:$PATH
1819

1920
RUN apt-get update \
20-
&& apt-get -y install curl bzip2 ${ADDITIONAL_PACKAGE} \
21-
&& curl -sSL https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -o /tmp/miniconda.sh \
22-
&& chown app /tmp/miniconda.sh \
21+
&& apt-get -y install curl bzip2 ${ADDITIONAL_PACKAGE}
22+
23+
RUN curl -sSL "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" -o /tmp/miniforge.sh \
24+
&& chown app /tmp/miniforge.sh \
2325
&& apt-get -qq -y remove curl \
2426
&& apt-get -qq -y autoremove \
2527
&& apt-get autoclean \
@@ -29,22 +31,34 @@ RUN apt-get update \
2931
WORKDIR /home/app/
3032
USER app
3133

32-
RUN bash /tmp/miniconda.sh -bfp $HOME/conda \
34+
RUN bash /tmp/miniforge.sh -bfp $HOME/conda \
3335
&& conda install -y python=3 \
3436
&& conda update conda \
3537
&& conda clean --all --yes \
36-
&& rm -rf /tmp/miniconda.sh
38+
&& rm -rf /tmp/miniforge.sh
3739

3840
COPY requirements.txt .
39-
RUN conda install --file requirements.txt -c ${CHANNEL}
41+
RUN conda install --file requirements.txt -c ${CHANNEL} \
42+
&& pip install tox-current-env
4043
COPY index.py .
4144

4245
RUN mkdir -p function
4346
RUN touch ./function/__init__.py
4447

4548
WORKDIR /home/app/function/
46-
COPY function/requirements.txt .
47-
RUN conda install --file requirements.txt -c ${CHANNEL}
49+
COPY function/requirements*.txt ./
50+
RUN conda install --file requirements.txt --file requirements_test.txt -c ${CHANNEL}
51+
52+
53+
COPY function/ .
54+
55+
ARG TEST_COMMAND="tox --current-env"
56+
ARG TEST_ENABLED=true
57+
RUN if [ "x$TEST_ENABLED" = "xfalse" ]; then \
58+
echo "skipping tests";\
59+
else \
60+
eval "$TEST_COMMAND"; \
61+
fi
4862

4963
WORKDIR /home/app/
5064
COPY function/ ./function
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
from .handler import handle
3+
4+
# Test your handler here
5+
6+
# To disable testing, you can set the build_arg `TEST_ENABLED=false` on the CLI or in your stack.yml
7+
# https://docs.openfaas.com/reference/yaml/#function-build-args-build-args
8+
9+
def test_handle():
10+
# assert handle("input") == "input"
11+
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
tox==3.*
2+
flake8
3+
pytest

0 commit comments

Comments
 (0)