Rework the docker image and bootstrap tooling

The old docker image installed nginx, mysql, and refstack in an all in
one container. This is not the preferred way to do containers as each
container should run a single process instead. Additionally the old
builds relied on PYTHONPATH munging rather than properly installing the
refstack project.

We can fix that by splitting up the services into per process
containers. Refstack now runs in a dedicated container based on
opendev's python-builder and python-base staged builds. We then run a
MariaDB along side refstack using docker-compose. A later addition can
add in a webserver and SSL termination.

Note that the end goal here is to have something we can deploy in
OpenDev using ansible and docker-compose to manage the refstack service.
This needs a lot of cleanup, but this records a working point in time.

Change-Id: I9d1c0a1b78a826266ffa67b0f58381a39a8ea89a
This commit is contained in:
Clark Boylan 2020-01-30 11:23:36 -08:00
parent b1403ead09
commit e1cdc4972d
18 changed files with 134 additions and 336 deletions

View File

@ -1,12 +0,0 @@
*.egg*
*.py[cod]
.coverage
.testrepository/
.tox/
.venv/
AUTHORS
ChangeLog
build/
cover/
dist
.git/

37
Dockerfile Normal file
View File

@ -0,0 +1,37 @@
# Copyright (c) 2019 Red Hat, Inc.
# Copyright (c) 2020 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
FROM opendevorg/python-builder as builder
COPY . /tmp/src
RUN /tmp/src/tools/install-js-tools.sh
RUN assemble
RUN cd /tmp/src && yarn install
FROM opendevorg/python-base as refstack
COPY --from=builder /output/ /output
COPY --from=builder /tmp/src/refstack-ui/app/ /refstack-ui/app
COPY --from=builder /tmp/src/docker/entrypoint.sh /usr/bin/entrypoint
# TODO this should be fixed probably through proper js packaging
RUN rm /refstack-ui/app/assets/lib
COPY --from=builder /tmp/src/node_modules/@bower_components/ /refstack-ui/app/assets/lib
RUN /output/install-from-bindep \
&& rm -rf /output
ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["pecan", "serve", "/usr/local/lib/python3.7/site-packages/refstack/api/config.py"]

View File

@ -1,8 +1,3 @@
# This is a cross-platform list tracking distribution packages needed for install and tests;
# see https://docs.openstack.org/infra/bindep/ for additional information.
mysql-client [platform:dpkg]
mysql-server [platform:dpkg]
postgresql
postgresql-client [platform:dpkg]
gcc [compile test]

25
docker-compose.yaml Normal file
View File

@ -0,0 +1,25 @@
# Version 2 is the latest that is supported by docker-compose in
# Ubuntu Xenial.
version: '2'
services:
mariadb:
image: docker.io/library/mariadb:10.4
restart: always
environment:
MYSQL_ROOT_PASSWORD: "secret"
MYSQL_DATABASE: "refstack"
MYSQL_USER: "refstack"
MYSQL_PASSWORD: "refstack"
refstack-api:
depends_on:
- mariadb
image: opendevorg/refstack:latest
restart: always
ports:
- "8000:8000"
volumes:
# These paths must be fully rooted apparently. Update them to your
# repo location.
- /home/clark/src/openstack/refstack/docker/refstack.conf:/etc/refstack.conf
- /home/clark/src/openstack/refstack/docker/config.json:/refstack-ui/app/config.json

View File

@ -1,49 +0,0 @@
FROM ubuntu:16.04
EXPOSE 443
ENV DEBIAN_FRONTEND noninteractive
ENV PYTHONPATH=/home/dev/refstack \
SQL_DIR=/home/dev/mysql
ENV REFSTACK_MYSQL_URL="mysql+pymysql://root@localhost/refstack?unix_socket=${SQL_DIR}/mysql.socket&charset=utf8"
ADD /docker/scripts/* /usr/bin/
ADD . /refstack
RUN apt update -y \
&& apt upgrade -y
RUN apt install -y curl \
sudo \
&& groupadd dev \
&& useradd -g dev -s /bin/bash -d /home/dev -m dev \
&& ( umask 226 && echo "dev ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/50_dev ) \
&& curl -sL https://deb.nodesource.com/setup_8.x -o /tmp/setup_8.x.sh \
&& sudo bash /tmp/setup_8.x.sh \
&& apt install -y git \
libffi-dev \
libmysqlclient-dev \
mysql-client \
mysql-server \
nginx \
nodejs \
python-dev \
python-pip \
python3-dev \
sudo \
vim \
wget \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/mysql/* \
&& rm -rf /etc/nginx/sites-enabled/default \
&& npm install -g yarn \
&& pip install virtualenv tox httpie
USER dev
RUN echo "cd /home/dev/refstack" >> /home/dev/.bashrc \
&& echo "alias activate='source /home/dev/refstack/.venv/bin/activate'" >> /home/dev/.bashrc \
&& echo "alias mysql='mysql --no-defaults -S ${SQL_DIR}/mysql.socket'" >> /home/dev/.bashrc \
&& start.sh \
&& api-init-db
CMD start.sh -s

1
docker/config.json Normal file
View File

@ -0,0 +1 @@
{"refstackApiUrl": "http://127.0.0.1:8000/v1"}

4
docker/entrypoint.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
set -e
refstack-manage upgrade --revision head
$@

16
docker/refstack.conf Normal file
View File

@ -0,0 +1,16 @@
[DEFAULT]
debug = true
verbose = true
ui_url = http://127.0.0.1:8000
[api]
static_root = /refstack-ui/app
template_path = /refstack-ui/app
app_dev_mode = true
api_url = http://127.0.0.1:8000
[database]
connection = "mysql+pymysql://refstack:refstack@mariadb/refstack?charset=utf8"
[osid]
openstack_openid_endpoint = https://172.17.42.1:8443/accounts/openid2

View File

@ -1,2 +0,0 @@
#!/bin/bash
/home/dev/refstack/.venv/bin/python /home/dev/refstack/bin/refstack-manage --log-file /dev/null version 2>/dev/null

View File

@ -1,7 +0,0 @@
#!/bin/bash
[[ ${DEBUG_MODE} ]] && set -x
mysql --no-defaults -S ${SQL_DIR}/mysql.socket -e 'CREATE DATABASE refstack;'
mysql --no-defaults -S ${SQL_DIR}/mysql.socket -e 'set @@global.show_compatibility_56=ON;'
cd /home/dev/refstack
.venv/bin/python bin/refstack-manage upgrade --revision head

View File

@ -1,5 +0,0 @@
#!/bin/bash
[[ ${DEBUG_MODE} ]] && set -x
echo "Syncing project files..."
rsync -avr /refstack /home/dev --exclude-from '/refstack/.gitignore' > /dev/null && \
echo "Done!"

View File

@ -1,5 +0,0 @@
#!/bin/bash
[[ ${DEBUG_MODE} ]] && set -x
api-sync
cd /home/dev/refstack
.venv/bin/pecan serve refstack/api/config.py

View File

@ -1,78 +0,0 @@
#!/bin/bash
wait_for_line () {
while read line; do
echo "$line" | grep -q "$1" && break
done < "$2"
# Read the fifo for ever otherwise process would block
cat "$2" >/dev/null &
}
build_tmpl () {
TEMPLATE="$1"
TARGET="$2"
cat ${TEMPLATE} | \
while read LINE; do
NEWLINE=$(eval echo ${LINE})
[[ ! -z "$NEWLINE" ]] && echo ${NEWLINE}
done > ${TARGET}
}
start_mysql () {
# Start MySQL process for tests
[ ! -d ${SQL_DIR} ] && mkdir ${SQL_DIR}
sudo chown dev:dev ${SQL_DIR}
rm -rf ${SQL_DIR}/out && mkfifo ${SQL_DIR}/out
rm -rf ${SQL_DIR}/mysql.socket
# On systems like Fedora here's where mysqld can be found
PATH=$PATH:/usr/libexec
mysqld --no-defaults --datadir=${SQL_DIR} --pid-file=${SQL_DIR}/mysql.pid \
--socket=${SQL_DIR}/mysql.socket --skip-networking \
--skip-grant-tables &> ${SQL_DIR}/out &
# Wait for MySQL to start listening to connections
wait_for_line "mysqld: ready for connections." ${SQL_DIR}/out
}
build_refstack_env () {
api-sync
cd /home/dev/refstack
[ ! -d .venv ] && virtualenv .venv
.venv/bin/pip install -r requirements.txt
#Install some dev tools
.venv/bin/pip install pymysql httpie
cd /home/dev/refstack
yarn
build_tmpl /refstack/docker/templates/config.json.tmpl /home/dev/refstack/refstack-ui/app/config.json
build_tmpl /refstack/docker/templates/refstack.conf.tmpl /home/dev/refstack.conf
sudo cp /home/dev/refstack.conf /etc
}
start_nginx () {
[ ! -d /etc/nginx/certificates ] && sudo mkdir /etc/nginx/certificates
sudo cp /refstack/docker/nginx/refstack_dev.key /etc/nginx/certificates
sudo cp /refstack/docker/nginx/refstack_dev.crt /etc/nginx/certificates
sudo cp /refstack/docker/nginx/refstack-site.conf /etc/nginx/sites-enabled/
sudo nginx
}
while getopts ":s" opt; do
case ${opt} in
s) SLEEP=true;;
esac
done
[[ ${DEBUG_MODE} ]] && set -x
touch /tmp/is-not-ready
start_mysql
start_nginx
build_refstack_env
rm -rf /tmp/is-not-ready
if [[ ${SLEEP} ]]; then
set +x
sleep 1024d
fi

View File

@ -1 +0,0 @@
{\\"refstackApiUrl\\": \\"https://${REFSTACK_HOST:-127.0.0.1}/v1\\"}

View File

@ -1,16 +0,0 @@
[DEFAULT]
debug = true
verbose = true
ui_url = https://${REFSTACK_HOST:-127.0.0.1}
[api]
static_root = /home/dev/refstack/refstack-ui/app
template_path = /home/dev/refstack/refstack-ui/app
app_dev_mode = true
api_url = https://${REFSTACK_HOST:-127.0.0.1}
[database]
connection = ${REFSTACK_MYSQL_URL}
[osid]
openstack_openid_endpoint = https://172.17.42.1:8443/accounts/openid2

View File

@ -1,6 +1,6 @@
SQLAlchemy>=0.8.3
alembic==0.5.0
beaker==1.6.5.post1
alembic
beaker
beautifulsoup4
cryptography>=1.0,!=1.3.0 # BSD/Apache-2.0
docutils>=0.11
@ -15,3 +15,4 @@ requests-cache>=0.4.9
jsonschema>=2.0.0,<3.0.0
PyJWT>=1.0.1 # MIT
WebOb>=1.7.1 # MIT
PyMySQL>=0.6.2,!=0.6.4

View File

@ -1,153 +0,0 @@
#!/bin/bash
TAG=$(BRANCH=$(git status -bs| grep "##" | awk '{print $2}'); echo ${BRANCH##*/})
IMAGE="refstack:${TAG}"
CONTAINER="refstack_${TAG}"
PROJ_DIR=$(git rev-parse --show-toplevel)
function usage () {
set +x
echo "Usage: $0 [OPTIONS] [COMMAND]"
echo "Build '${IMAGE}' image if it is does not exist."
echo "Run '${CONTAINER}' container and execute COMMAND in it."
echo "Default COMMAND is 'api-up'"
echo "If container '${CONTAINER}' exists (running or stopped) it will be reused."
echo "If you want to get access to your local RefStack not only from localhost, "
echo "please specify public RefStack host:port in env[REFSTACK_HOST]."
echo "You can customize RefStack API config by editing docker/refstack.conf.tmpl."
echo "It is bash template. You can use \${SOME_ENV_VARIABLE} in it."
echo "Default is 127.0.0.1:443"
echo ""
echo " -r Force delete '${CONTAINER}' container and run it again."
echo " Main usecase for it - updating config from templates"
echo " -b Force delete '${IMAGE}' image and build it again"
echo " Main usecase for it - force build new python/js env"
echo " -i Run container with isolated MySQL data."
echo " By default MySQL data stores in refstack_data_DATA-BASE-REVISON container"
echo " It reuses if such container exists. If you want to drop DB data, just execute"
echo " sudo docker rm refstack_data_DATA-BASE-REVISON"
echo " -d Turn on debug information"
echo " -h Print this usage message"
echo ""
echo ""
echo "Using examples:"
echo ""
echo "Run RefStack API:"
echo "$ ./run-in-docker"
echo ""
echo "Run RefStack API by hands:"
echo "$ ./run-in-docker bash"
echo "$ activate"
echo "$ pecan serve refstack/api/config.py"
echo ""
echo "Open shell in container:"
echo "$ ./run-in-docker bash"
echo ""
echo "Open mysql console in container:"
echo "$ ./run-in-docker bash"
echo "$ mysql"
}
build_image () {
sudo docker rm -f ${CONTAINER}
PREV_ID=$(sudo docker images refstack | grep ${TAG} | awk '{print $3}')
echo "Try to build ${IMAGE} image"
sudo docker build -t ${IMAGE} -f ${PROJ_DIR}/docker/Dockerfile ${PROJ_DIR} || exit $?
NEW_ID=$(sudo docker images refstack | grep ${TAG} | awk '{print $3}')
if [[ ${PREV_ID} ]] && [[ ! ${PREV_ID} == ${NEW_ID} ]]; then
sudo docker rmi -f ${PREV_ID} && echo "Previous image removed"
fi
}
wait_ready() {
while true; do
echo "Wait while container is not ready"
sudo docker exec ${CONTAINER} [ ! -e /tmp/is-not-ready ] && \
echo "Container ${CONTAINER} is running!" && break
sleep 1
done
}
run_container (){
echo "Stop all other refstack containers"
for id in $(sudo docker ps -q); do
NAME=$(sudo docker inspect --format='{{.Name}}' $id)
if [[ ${NAME} == /refstack_* ]] && [[ ! ${NAME} == "/${CONTAINER}" ]]; then
echo "Stopped container ${NAME}" && sudo docker stop $id
fi
done
if [[ $(sudo docker ps -a | grep "${CONTAINER}") ]]; then
echo "Container ${CONTAINER} exists it is reused"
sudo docker start ${CONTAINER}
wait_ready
else
echo "Try to run container ${CONTAINER}"
sudo docker run -d \
-e REFSTACK_HOST=${REFSTACK_HOST:-127.0.0.1} \
-e DEBUG_MODE=${DEBUG_MODE} \
-v ${PROJ_DIR}:/refstack:ro -p 443:443 --name ${CONTAINER} \
${IMAGE} start.sh -s
wait_ready
if [[ ! ${ISOLATED_DB} ]]; then
DB_VERSION=$(sudo docker exec -it ${CONTAINER} api-db-version)
DB_CONTAINER=refstack_data_${DB_VERSION::-1}
sudo docker rm -f ${CONTAINER}
if [[ ! $(sudo docker ps -a | grep "${DB_CONTAINER}") ]]; then
sudo docker run -v /home/dev/mysql --name ${DB_CONTAINER} ubuntu /bin/true
echo "Container with mysql data ${DB_CONTAINER} created"
sudo docker run -d \
-e REFSTACK_HOST=${REFSTACK_HOST:-127.0.0.1} \
-e DEBUG_MODE=${DEBUG_MODE} \
-v ${PROJ_DIR}:/refstack:ro --volumes-from ${DB_CONTAINER} -p 443:443 \
--name ${CONTAINER} ${IMAGE}
wait_ready
sudo docker exec ${CONTAINER} api-init-db
echo "DB init done"
else
sudo docker run -d \
-e REFSTACK_HOST=${REFSTACK_HOST:-127.0.0.1} \
-e DEBUG_MODE=${DEBUG_MODE} \
-v ${PROJ_DIR}:/refstack:ro --volumes-from ${DB_CONTAINER} -p 443:443 \
--name ${CONTAINER} ${IMAGE}
echo "Container with mysql data ${DB_CONTAINER} attached to ${CONTAINER}"
wait_ready
fi
fi
fi
}
COMMAND=""
while [[ $1 ]]
do
case "$1" in
-h) usage
exit 0;;
-r) echo "Try to remove old ${CONTAINER} container"
sudo docker rm -f ${CONTAINER}
shift;;
-i) echo "Run container with isolated MySQL data."
echo "By default MySQL data stores in refstack_data_[DATA-BASE-REVISON] container"
echo "It reuses if such container exists. If you want to drop DB data, just execute"
echo "sudo docker rm ${DB_CONTAINER}"
ISOLATED_DB=true
shift;;
-b) FORCE_BUILD=true
shift;;
-d) DEBUG_MODE=true
shift;;
*) COMMAND="${COMMAND} $1"
shift;;
esac
done
[[ ${DEBUG_MODE} ]] && set -x
#Build proper image if it does not exist of force rebuild fired
if [[ ${FORCE_BUILD} ]] || [[ ! $(sudo docker images refstack | grep ${TAG}) ]]; then
build_image
fi
#Run or start(if it exists) proper container
[[ ! $(sudo docker ps | grep ${CONTAINER}) ]] && run_container
sudo docker exec -it ${CONTAINER} ${COMMAND:-api-up}

47
tools/install-js-tools.sh Executable file
View File

@ -0,0 +1,47 @@
#!/bin/bash
# Copyright 2017 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
if [ $EUID -ne 0 ] ; then
SUDO='sudo -E'
fi
if type apt-get; then
# Install https transport - otherwise apt-get HANGS on https urls
# Install curl so the curl commands work
# Install gnupg2 so that the apt-key add works
$SUDO apt-get update
$SUDO apt-get install -y apt-transport-https curl gnupg2
# Install recent NodeJS repo
curl -sS https://deb.nodesource.com/gpgkey/nodesource.gpg.key | $SUDO apt-key add -
echo "deb https://deb.nodesource.com/node_10.x bionic main" | $SUDO tee /etc/apt/sources.list.d/nodesource.list
# Install yarn repo
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | $SUDO apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | $SUDO tee /etc/apt/sources.list.d/yarn.list
$SUDO apt-get update
DEBIAN_FRONTEND=noninteractive \
$SUDO apt-get -q --option "Dpkg::Options::=--force-confold" --assume-yes \
install nodejs yarn
elif type yum; then
$SUDO curl https://dl.yarnpkg.com/rpm/yarn.repo -o /etc/yum.repos.d/yarn.repo
$SUDO $(dirname $0)/install-js-repos-rpm.sh
$SUDO yum -y install nodejs yarn
elif type zypper; then
$SUDO zypper install -y nodejs10 npm10
$SUDO npm install yarn
elif type brew; then
brew install nodejs yarn
else
echo "Unsupported platform"
fi