Skip to content

Commit 51d3740

Browse files
authored
Create a deb package (#25)
This adds the necessary configuration and CI to build the CLI as a deb package. This is accomplished using dh-virtualenv, which allows us to package Python dependencies in a virtual environment. Using dh-virtualenv not only allows us to use our own versions of Python dependencies, it also isolates our dependencies from the rest of the system. This allows us to have our own isolated installation of Docker Compose as a dependency, whose version we can update at our own pace. The deb also obsoletes the need to install curl and rsync for the user, since they can now be expressed as package dependencies. However, Docker is currently still being installed for the user. We could consider using Ubuntu's version of Docker if we're comfortable with supporting an older version. This includes an overhaul of our configuration system, so that different distributions of the CLI can be distributed with different defaults. Defaults are now provided by a defaults.yaml file. The deb package is given its own custom defaults.yaml that is configured to use system package locations like /var/lib/brainframe instead of user-installed locations like /var/local/brainframe. Unfortunately, dh-virtualenv requires that we have a setup.py because it runs python3 setup.py install to add the application code to the virtual environment. I've tried to keep the setup.py as minimal as possible, shelling out to Poetry when I can. This change is, admittedly, not immediately useful on its own. We would need a PPA or our own repository to distribute this deb to users, and updated instructions to go along with it. We will probably want to provide a script similar to get-docker.sh that handles all the installation steps for the user so that we can preserve our single-line installation experience. See #13, which this PR does not resolve on its own.
1 parent 0b6e8c8 commit 51d3740

24 files changed

Lines changed: 830 additions & 197 deletions

.circleci/config.yml

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
11
version: 2.1
22

33
parameters:
4-
poetry_version:
4+
poetry-version:
55
type: string
66
default: "1.1.4"
77

8+
9+
executors:
10+
focal:
11+
docker:
12+
- image: cimg/base:stable-20.04
13+
bionic:
14+
docker:
15+
- image: cimg/base:stable-18.04
16+
17+
818
workflows:
919
lint:
1020
jobs:
1121
- lint
22+
deb:
23+
jobs:
24+
- build-deb:
25+
matrix:
26+
parameters:
27+
os: [bionic, focal]
28+
- test-installation-deb:
29+
requires:
30+
- build-deb
1231
deploy:
1332
jobs:
14-
- upload_to_pypi:
33+
- upload-to-pypi:
1534
filters:
1635
# Ignore any commit on any branch by default
1736
branches:
@@ -21,60 +40,114 @@ workflows:
2140
only: /^v.+\..+\..+/
2241
tests:
2342
jobs:
24-
- test_installation
43+
- test-installation-source
2544

2645

2746
jobs:
2847
lint:
2948
docker:
30-
- image: circleci/python:3.6
49+
- image: cimg/python:3.6
3150
steps:
3251
- checkout
33-
- run: "curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py
34-
| python3 - --version << pipeline.parameters.poetry_version >>"
3552
- run: poetry install --no-root
3653
- run: poetry run isort --check .
3754
- run: poetry run black --check .
3855
- run: poetry run mypy -p "brainframe.cli"
39-
upload_to_pypi:
56+
57+
build-deb:
58+
parameters:
59+
os:
60+
type: executor
61+
executor: << parameters.os >>
62+
steps:
63+
- run: sudo apt-get update
64+
- run: sudo apt-get install -y software-properties-common dpkg-dev devscripts equivs
65+
- run: sudo add-apt-repository ppa:jyrki-pulliainen/dh-virtualenv
66+
- run: sudo apt-get install -y dh-virtualenv
67+
- install-poetry
68+
- checkout
69+
- run: cp -r package/debian .
70+
- run: cp package/setup.py .
71+
- run: cp package/system_package_defaults.yaml brainframe/cli/defaults.yaml
72+
- run: sudo mk-build-deps --install debian/control
73+
- run: poetry export --output requirements.txt
74+
- run: dpkg-buildpackage --unsigned-source --unsigned-changes --build=binary
75+
- run: mkdir dist
76+
- run: |
77+
export CODENAME=$(lsb_release --codename --short)
78+
mv ../brainframe-cli*.deb dist/brainframe-cli-${CODENAME}.deb
79+
- store_artifacts:
80+
path: dist
81+
- persist_to_workspace:
82+
root: .
83+
paths:
84+
- dist/*
85+
86+
test-installation-deb:
87+
machine:
88+
image: ubuntu-2004:202010-01
89+
steps:
90+
- run: sudo apt-get update
91+
- attach_workspace:
92+
at: /tmp/workspace
93+
- run: sudo apt-get install /tmp/workspace/dist/brainframe-cli-focal.deb
94+
- run: sudo brainframe install --noninteractive
95+
- run: sudo brainframe compose up -d
96+
- assert-core-container-running
97+
- assert-core-responds-to-http
98+
- run: sudo brainframe compose down
99+
100+
upload-to-pypi:
40101
docker:
41-
- image: circleci/python:3.6
102+
- image: cimg/python:3.6
42103
environment:
43104
POETRY_HTTP_BASIC_PYPI_USERNAME: __token__
44-
45105
steps:
46106
- checkout
47-
- run: "curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py
48-
| python3 - --version << pipeline.parameters.poetry_version >>"
49107
- run: poetry build
50108
- run: POETRY_HTTP_BASIC_PYPI_PASSWORD=${PYPI_PASSWORD} poetry publish
51-
test_installation:
109+
110+
test-installation-source:
52111
machine:
53112
image: ubuntu-2004:202010-01
54113
steps:
55114
- run: sudo apt-get update
56115
- run: sudo apt-get install python3 python3-dev curl git -y
57116
- checkout
58-
- run: "curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py
59-
| python3 - --version << pipeline.parameters.poetry_version >>"
60-
- run: echo "export PATH=$PATH:$HOME/.poetry/bin" >> $BASH_ENV
117+
- install-poetry
61118
- run: sudo $(which poetry) install
62119
- run: sudo $(which poetry) run brainframe install --noninteractive
63120
- run: sudo $(which poetry) run brainframe compose up -d
121+
- assert-core-container-running
122+
- assert-core-responds-to-http
123+
- run: sudo $(which poetry) run brainframe compose down
124+
125+
commands:
126+
install-poetry:
127+
steps:
128+
- run: sudo apt-get update && sudo apt-get install -y python3-pip
129+
- run: >
130+
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py
131+
| python3 - --version << pipeline.parameters.poetry-version >>
132+
# Add to the PATH
133+
- run: echo 'export PATH=$HOME/.poetry/bin:$PATH' >> $BASH_ENV
134+
assert-core-container-running:
135+
steps:
64136
- run:
65-
name: Check BrainFrame containers are running
137+
name: Check BrainFrame core container is running
66138
command: |
67139
until docker container inspect -f {{.State.Running}} $(docker ps -q -f name="brainframe_core") >/dev/null 2>/dev/null; do
68140
sleep 0.1;
69141
done;
70142
echo "BrainFrame core container is running."
71143
no_output_timeout: 1m
144+
assert-core-responds-to-http:
145+
steps:
72146
- run:
73-
name: Check BrainFrame core service is up
147+
name: Check BrainFrame core responds to HTTP requests
74148
command: |
75149
until curl -f http://localhost/api/version >/dev/null 2>/dev/null; do
76150
sleep 0.1;
77151
done;
78152
echo "BrainFrame core service is up."
79153
no_output_timeout: 1m
80-
- run: sudo $(which poetry) run brainframe compose down

brainframe/cli/commands/backup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
import i18n
77
from brainframe.cli import (
8+
config,
89
dependencies,
910
docker_compose,
10-
env_vars,
1111
os_utils,
1212
print_utils,
1313
)
@@ -19,8 +19,8 @@
1919

2020
@command("backup")
2121
def backup():
22-
install_path = env_vars.install_path.get()
23-
data_path = env_vars.data_path.get()
22+
install_path = config.install_path.value
23+
data_path = config.data_path.value
2424

2525
args = _parse_args(data_path)
2626

brainframe/cli/commands/compose.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import sys
22

3-
import i18n
4-
from brainframe.cli import docker_compose, env_vars, os_utils, print_utils
3+
from brainframe.cli import config, docker_compose
54

65
from .utils import command
76

87

98
@command("compose")
109
def compose():
11-
install_path = env_vars.install_path.get()
10+
install_path = config.install_path.value
1211
docker_compose.assert_installed(install_path)
1312
docker_compose.run(install_path, sys.argv[2:])

brainframe/cli/commands/info.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from argparse import ArgumentParser
22

33
import i18n
4-
from brainframe.cli import docker_compose, env_vars, print_utils
4+
from brainframe.cli import config, docker_compose, print_utils
55

66
from .utils import command, subcommand_parse_args
77

@@ -10,15 +10,15 @@
1010
def info():
1111
args = _parse_args()
1212

13-
docker_compose.assert_installed(env_vars.install_path.get())
13+
docker_compose.assert_installed(config.install_path.value)
1414

1515
server_version = docker_compose.check_existing_version(
16-
env_vars.install_path.get()
16+
config.install_path.value
1717
)
1818

1919
fields = {
20-
"install_path": env_vars.install_path.get(),
21-
"data_path": env_vars.data_path.get(),
20+
"install_path": config.install_path.value,
21+
"data_path": config.data_path.value,
2222
"server_version": server_version,
2323
}
2424

brainframe/cli/commands/install.py

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
import i18n
55
from brainframe.cli import (
6+
config,
67
dependencies,
78
docker_compose,
8-
env_vars,
99
os_utils,
1010
print_utils,
1111
)
@@ -32,9 +32,6 @@ def install():
3232
# Check all dependencies
3333
dependencies.curl.ensure(args.noninteractive, args.install_curl)
3434
dependencies.docker.ensure(args.noninteractive, args.install_docker)
35-
dependencies.docker_compose.ensure(
36-
args.noninteractive, args.install_docker_compose
37-
)
3835

3936
_, _, download_version = docker_compose.check_download_version()
4037
print_utils.translate("install.install-version", version=download_version)
@@ -55,8 +52,8 @@ def install():
5552
# Ask the user if they want to specify special paths for installation
5653
print_utils.translate(
5754
"install.default-paths",
58-
default_install_path=env_vars.install_path.default,
59-
default_data_path=env_vars.data_path.default,
55+
default_install_path=config.install_path.default,
56+
default_data_path=config.data_path.default,
6057
)
6158
use_default_paths = print_utils.ask_yes_no(
6259
"install.ask-use-default-paths",
@@ -66,22 +63,21 @@ def install():
6663
if args.noninteractive:
6764
install_path = args.install_path
6865
elif use_default_paths:
69-
install_path = env_vars.install_path.default
66+
install_path = config.install_path.default
7067
else:
7168
install_path = print_utils.ask_path(
72-
"install.ask-brainframe-install-path",
73-
env_vars.install_path.default,
69+
"install.ask-brainframe-install-path", config.install_path.default,
7470
)
7571
install_path.mkdir(exist_ok=True, parents=True)
7672

7773
# Set up the data path
7874
if args.noninteractive:
7975
data_path = args.data_path
8076
elif use_default_paths:
81-
data_path = env_vars.data_path.default
77+
data_path = config.data_path.default
8278
else:
8379
data_path = print_utils.ask_path(
84-
"install.ask-data-path", env_vars.data_path.default
80+
"install.ask-data-path", config.data_path.default
8581
)
8682
data_path.mkdir(exist_ok=True, parents=True)
8783

@@ -120,15 +116,15 @@ def install():
120116
# Recommend to the user to add their custom paths to environment variables
121117
# so that future invocations of the program will know where to look.
122118
if (
123-
install_path != env_vars.install_path.default
124-
or data_path != env_vars.data_path.default
119+
install_path != config.install_path.default
120+
or data_path != config.data_path.default
125121
):
126122
print()
127123
print_utils.translate("install.set-custom-directory-env-vars")
128124
print(
129125
f"\n"
130-
f'export {env_vars.install_path.name}="{install_path}"\n'
131-
f'export {env_vars.data_path.name}="{data_path}"\n'
126+
f'export {config.install_path.name}="{install_path}"\n'
127+
f'export {config.data_path.name}="{data_path}"\n'
132128
)
133129

134130

@@ -146,25 +142,20 @@ def _parse_args():
146142
parser.add_argument(
147143
"--install-path",
148144
type=Path,
149-
default=env_vars.install_path.default,
145+
default=config.install_path.default,
150146
help=i18n.t("install.install-path-help"),
151147
)
152148
parser.add_argument(
153149
"--data-path",
154150
type=Path,
155-
default=env_vars.data_path.default,
151+
default=config.data_path.default,
156152
help=i18n.t("install.data-path-help"),
157153
)
158154
parser.add_argument(
159155
"--install-docker",
160156
action="store_true",
161157
help=i18n.t("install.install-docker-help"),
162158
)
163-
parser.add_argument(
164-
"--install-docker-compose",
165-
action="store_true",
166-
help=i18n.t("install.install-docker-compose-help"),
167-
)
168159
parser.add_argument(
169160
"--install-curl",
170161
action="store_true",

brainframe/cli/commands/update.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from argparse import ArgumentParser
22

33
import i18n
4-
from brainframe.cli import docker_compose, env_vars, print_utils
4+
from brainframe.cli import config, docker_compose, print_utils
55
from packaging import version
66

77
from .utils import command, subcommand_parse_args
@@ -11,7 +11,7 @@
1111
def update():
1212
args = _parse_args()
1313

14-
install_path = env_vars.install_path.get()
14+
install_path = config.install_path.value
1515

1616
docker_compose.assert_installed(install_path)
1717

0 commit comments

Comments
 (0)