Skip to content

Commit 7cb0ebf

Browse files
committed
First iteration of builder image
0 parents  commit 7cb0ebf

File tree

14 files changed

+482
-0
lines changed

14 files changed

+482
-0
lines changed

Dockerfile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# daeploy/s2i-python
2+
FROM python:3.8-slim
3+
4+
LABEL maintainer="Viking Analytics AB"
5+
6+
ENV BUILDER_VERSION 1.0
7+
8+
# Set labels used in OpenShift to describe the builder image
9+
LABEL io.k8s.description="Platform for building daeploy images with reduced size" \
10+
io.k8s.display-name="Daeploy python builder" \
11+
io.openshift.expose-services="8080:http" \
12+
io.openshift.tags="builder,daeploy,python." \
13+
io.openshift.s2i.scripts-url="image://.s2i/bin"
14+
15+
# Setup virtualenv
16+
RUN python -m venv /opt/venv
17+
ENV PATH="/opt/venv/bin:$PATH"
18+
19+
# Copy the S2I scripts to the image
20+
COPY ./s2i/bin/ .s2i/bin
21+
22+
# TODO: Set the default port for applications built using this image
23+
EXPOSE 8080
24+
25+
# TODO: Set the default CMD for the image
26+
CMD [".s2i/bin/usage"]

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
IMAGE_NAME = daeploy/s2i-python
2+
3+
.PHONY: build
4+
build:
5+
docker build -t $(IMAGE_NAME) .
6+
7+
.PHONY: test
8+
test:
9+
docker build -t $(IMAGE_NAME)-candidate .
10+
IMAGE_NAME=$(IMAGE_NAME)-candidate test/run

README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
2+
# Creating a basic S2I builder image
3+
4+
## Getting started
5+
6+
### Files and Directories
7+
8+
| File | Required? | Description |
9+
|------------------------|-----------|--------------------------------------------------------------|
10+
| Dockerfile | Yes | Defines the base builder image |
11+
| s2i/bin/assemble | Yes | Script that builds the application |
12+
| s2i/bin/usage | No | Script that prints the usage of the builder |
13+
| s2i/bin/run | Yes | Script that runs the application |
14+
| s2i/bin/save-artifacts | No | Script for incremental builds that saves the built artifacts |
15+
| test/run | No | Test script for the builder image |
16+
| test/test-app | Yes | Test application source code |
17+
18+
#### Dockerfile
19+
20+
Create a *Dockerfile* that installs all of the necessary tools and libraries that are needed to build and run our application. This file will also handle copying the s2i scripts into the created image.
21+
22+
#### S2I scripts
23+
24+
##### assemble
25+
26+
Create an *assemble* script that will build our application, e.g.:
27+
28+
- build python modules
29+
- bundle install ruby gems
30+
- setup application specific configuration
31+
32+
The script can also specify a way to restore any saved artifacts from the previous image.
33+
34+
##### run
35+
36+
Create a *run* script that will start the application.
37+
38+
##### save-artifacts (optional)
39+
40+
Create a *save-artifacts* script which allows a new build to reuse content from a previous version of the application image.
41+
42+
##### usage (optional)
43+
44+
Create a *usage* script that will print out instructions on how to use the image.
45+
46+
##### Make the scripts executable
47+
48+
Make sure that all of the scripts are executable by running *chmod +x s2i/bin/**
49+
50+
#### Create the builder image
51+
52+
The following command will create a builder image named daeploy/s2i-python based on the Dockerfile that was created previously.
53+
54+
```bash
55+
docker build -t daeploy/s2i-python .
56+
```
57+
58+
The builder image can also be created by using the *make* command since a *Makefile* is included.
59+
60+
Once the image has finished building, the command *s2i usage daeploy/s2i-python* will print out the help info that was defined in the *usage* script.
61+
62+
#### Testing the builder image
63+
64+
The builder image can be tested using the following commands:
65+
66+
```bash
67+
docker build -t daeploy/s2i-python-candidate .
68+
IMAGE_NAME=daeploy/s2i-python-candidate test/run
69+
```
70+
71+
The builder image can also be tested by using the *make test* command since a *Makefile* is included.
72+
73+
#### Creating the application image
74+
75+
The application image combines the builder image with your applications source code, which is served using whatever application is installed via the *Dockerfile*, compiled using the *assemble* script, and run using the *run* script.
76+
The following command will create the application image:
77+
78+
```bash
79+
s2i build test/test-app daeploy/s2i-python daeploy/s2i-python-app
80+
---> Building and installing application from source...
81+
```
82+
83+
Using the logic defined in the *assemble* script, s2i will now create an application image using the builder image as a base and including the source code from the test/test-app directory.
84+
85+
#### Running the application image
86+
87+
Running the application image is as simple as invoking the docker run command:
88+
89+
```bash
90+
docker run -d -p 8080:8080 daeploy/s2i-python-app
91+
```
92+
93+
The application, which consists of a simple static web page, should now be accessible at [http://localhost:8080](http://localhost:8080).
94+
95+
#### Using the saved artifacts script
96+
97+
Rebuilding the application using the saved artifacts can be accomplished using the following command:
98+
99+
```bash
100+
s2i build --incremental=true test/test-app nginx-centos7 nginx-app
101+
---> Restoring build artifacts...
102+
---> Building and installing application from source...
103+
```
104+
105+
This will run the *save-artifacts* script which includes the custom code to backup the currently running application source, rebuild the application image, and then re-deploy the previously saved source using the *assemble* script.

s2i/bin/assemble

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/bash
2+
#
3+
# S2I assemble script for the 'daeploy/s2i-python' image.
4+
# The 'assemble' script builds your application source so that it is ready to run.
5+
#
6+
# For more information refer to the documentation:
7+
# https://github.com/openshift/source-to-image/blob/master/docs/builder_image.md
8+
#
9+
10+
# If the 'daeploy/s2i-python' assemble script is executed with the '-h' flag, print the usage.
11+
if [[ "$1" == "-h" ]]; then
12+
exec /usr/lib/s2i/usage
13+
fi
14+
15+
echo "---> Installing application source ..."
16+
mv /tmp/src/* "$HOME"
17+
18+
# Install dependencies
19+
echo "---> Upgrading pip and setuptools"
20+
pip install -U pip setuptools
21+
22+
cd $HOME
23+
if [[ -f requirements.txt ]]; then
24+
echo "---> Installing dependencies..."
25+
python -m pip install -r requirements.txt
26+
fi

s2i/bin/run

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/bin/bash
2+
#
3+
# S2I run script for the 'daeploy/s2i-python' image.
4+
# The run script executes the server that runs your application.
5+
#
6+
# For more information see the documentation:
7+
# https://github.com/openshift/source-to-image/blob/master/docs/builder_image.md
8+
#
9+
10+
cd $HOME
11+
12+
if [ -z "$APP_SCRIPT" ] && [ -z "$APP_FILE" ] && [ -z "$APP_MODULE" ]; then
13+
# Set default values for APP_SCRIPT and APP_FILE only when all three APP_
14+
# variables are not defined by user. This prevents a situation when
15+
# APP_MODULE is defined to app:application but the app.py file is found as the
16+
# APP_FILE and then executed by Python instead of gunicorn.
17+
APP_SCRIPT="app.sh"
18+
APP_SCRIPT_DEFAULT=1
19+
APP_FILE="app.py"
20+
APP_FILE_DEFAULT=1
21+
fi
22+
23+
if [ ! -z "$APP_SCRIPT" ]; then
24+
if [[ -f "$APP_SCRIPT" ]]; then
25+
echo "---> Running application from script ($APP_SCRIPT) ..."
26+
if [[ "$APP_SCRIPT" != /* ]]; then
27+
APP_SCRIPT="$HOME/$APP_SCRIPT"
28+
fi
29+
"$APP_SCRIPT"
30+
elif [[ -z "$APP_SCRIPT_DEFAULT" ]]; then
31+
echo "ERROR: file '$APP_SCRIPT' not found." && exit 1
32+
fi
33+
fi
34+
35+
if [ ! -z "$APP_FILE" ]; then
36+
if [[ -f "$APP_FILE" ]]; then
37+
echo "---> Running application from Python script ($APP_FILE) ..."
38+
python "$HOME/$APP_FILE"
39+
elif [[ -z "$APP_FILE_DEFAULT" ]]; then
40+
echo "ERROR: file '$APP_FILE' not found." && exit 1
41+
fi
42+
fi

s2i/bin/usage

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
cat <<EOF
3+
This is the daeploy/s2i-python S2I image:
4+
To use it, install S2I: https://github.com/openshift/source-to-image
5+
6+
Sample invocation:
7+
8+
s2i build <source code path/URL> daeploy/s2i-python <application image>
9+
10+
You can then run the resulting image via:
11+
docker run <application image>
12+
EOF

test/run

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/bin/bash
2+
#
3+
# The 'run' performs a simple test that verifies the S2I image.
4+
# The main focus here is to exercise the S2I scripts.
5+
#
6+
# For more information see the documentation:
7+
# https://github.com/openshift/source-to-image/blob/master/docs/builder_image.md
8+
#
9+
# IMAGE_NAME specifies a name of the candidate image used for testing.
10+
# The image has to be available before this script is executed.
11+
#
12+
IMAGE_NAME=${IMAGE_NAME-daeploy/s2i-python-candidate}
13+
14+
# Determining system utility executables (darwin compatibility check)
15+
READLINK_EXEC="readlink -zf"
16+
MKTEMP_EXEC="mktemp --suffix=.cid"
17+
if [[ "$OSTYPE" =~ 'darwin' ]]; then
18+
READLINK_EXEC="readlink"
19+
MKTEMP_EXEC="mktemp"
20+
! type -a "greadlink" &>"/dev/null" || READLINK_EXEC="greadlink"
21+
! type -a "gmktemp" &>"/dev/null" || MKTEMP_EXEC="gmktemp"
22+
fi
23+
24+
_dir="$(dirname "${BASH_SOURCE[0]}")"
25+
test_dir="$($READLINK_EXEC ${_dir} || echo ${_dir})"
26+
image_dir=$($READLINK_EXEC ${test_dir}/.. || echo ${test_dir}/..)
27+
scripts_url="${image_dir}/.s2i/bin"
28+
cid_file=$($MKTEMP_EXEC -u)
29+
30+
# Since we built the candidate image locally, we don't want S2I to attempt to pull
31+
# it from Docker hub
32+
s2i_args="--pull-policy=never --loglevel=2"
33+
34+
# Port the image exposes service to be tested
35+
test_port=8080
36+
37+
image_exists() {
38+
docker inspect $1 &>/dev/null
39+
}
40+
41+
container_exists() {
42+
image_exists $(cat $cid_file)
43+
}
44+
45+
container_ip() {
46+
docker inspect --format="{{(index .NetworkSettings.Ports \"$test_port/tcp\" 0).HostIp }}" $(cat $cid_file) | sed 's/0.0.0.0/localhost/'
47+
}
48+
49+
container_port() {
50+
docker inspect --format="{{(index .NetworkSettings.Ports \"$test_port/tcp\" 0).HostPort }}" "$(cat "${cid_file}")"
51+
}
52+
53+
run_s2i_build() {
54+
s2i build --incremental=true ${s2i_args} ${test_dir}/test-app ${IMAGE_NAME} ${IMAGE_NAME}-testapp
55+
}
56+
57+
prepare() {
58+
if ! image_exists ${IMAGE_NAME}; then
59+
echo "ERROR: The image ${IMAGE_NAME} must exist before this script is executed."
60+
exit 1
61+
fi
62+
# s2i build requires the application is a valid 'Git' repository
63+
pushd ${test_dir}/test-app >/dev/null
64+
git init
65+
git config user.email "build@localhost" && git config user.name "builder"
66+
git add -A && git commit -m "Sample commit"
67+
popd >/dev/null
68+
run_s2i_build
69+
}
70+
71+
run_test_application() {
72+
docker run --rm --cidfile=${cid_file} -p ${test_port}:${test_port} ${IMAGE_NAME}-testapp
73+
}
74+
75+
cleanup() {
76+
if [ -f $cid_file ]; then
77+
if container_exists; then
78+
docker stop $(cat $cid_file)
79+
fi
80+
fi
81+
if image_exists ${IMAGE_NAME}-testapp; then
82+
docker rmi ${IMAGE_NAME}-testapp
83+
fi
84+
}
85+
86+
check_result() {
87+
local result="$1"
88+
if [[ "$result" != "0" ]]; then
89+
echo "S2I image '${IMAGE_NAME}' test FAILED (exit code: ${result})"
90+
cleanup
91+
exit $result
92+
fi
93+
}
94+
95+
wait_for_cid() {
96+
local max_attempts=10
97+
local sleep_time=1
98+
local attempt=1
99+
local result=1
100+
while [ $attempt -le $max_attempts ]; do
101+
[ -f $cid_file ] && break
102+
echo "Waiting for container to start..."
103+
attempt=$(( $attempt + 1 ))
104+
sleep $sleep_time
105+
done
106+
}
107+
108+
test_usage() {
109+
echo "Testing 's2i usage'..."
110+
s2i usage ${s2i_args} ${IMAGE_NAME} &>/dev/null
111+
}
112+
113+
test_connection() {
114+
echo "Testing HTTP connection (http://$(container_ip):$(container_port))"
115+
local max_attempts=10
116+
local sleep_time=1
117+
local attempt=1
118+
local result=1
119+
while [ $attempt -le $max_attempts ]; do
120+
echo "Sending GET request to http://$(container_ip):$(container_port)/"
121+
response_code=$(curl -s -w %{http_code} -o /dev/null http://$(container_ip):$(container_port)/)
122+
status=$?
123+
if [ $status -eq 0 ]; then
124+
if [ $response_code -eq 200 ]; then
125+
result=0
126+
fi
127+
break
128+
fi
129+
attempt=$(( $attempt + 1 ))
130+
sleep $sleep_time
131+
done
132+
return $result
133+
}
134+
135+
# Build the application image twice to ensure the 'save-artifacts' and
136+
# 'restore-artifacts' scripts are working properly
137+
prepare
138+
run_s2i_build
139+
check_result $?
140+
141+
# Verify the 'usage' script is working properly
142+
test_usage
143+
check_result $?
144+
145+
# Verify that the HTTP connection can be established to test application container
146+
run_test_application &
147+
148+
# Wait for the container to write its CID file
149+
wait_for_cid
150+
151+
test_connection
152+
check_result $?
153+
154+
cleanup

test/test-app/.s2i/environment

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
APP_FILE = service.py

test/test-app/.s2iignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# In this file you can add files/folders to ignore when deploying
2+
# Works similar to a .gitignore file
3+
4+
# Ignore the .git folder to not only use committed code
5+
.git/
6+
7+
# Test directory is not needed in production
8+
tests/

0 commit comments

Comments
 (0)