Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] add CI tool for docker health check and initial ci workflow #4

Closed
wants to merge 123 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
1ff089f
add minio and some jinja2 function testing
stavros-k Mar 29, 2024
1ee0473
use yaml
stavros-k Mar 29, 2024
9108bf7
add some goodies
stavros-k Mar 29, 2024
42bdcea
fix few things
stavros-k Apr 1, 2024
3c8022a
add util
stavros-k Apr 1, 2024
6ddf675
more snipepts
stavros-k Apr 1, 2024
762fa8e
new mock
stavros-k Apr 1, 2024
3e898f2
MINIO_VOLUMES
stavros-k Apr 1, 2024
ec58a5a
add to_yaml
stavros-k Apr 2, 2024
57ff346
resources
stavros-k Apr 2, 2024
1ec0940
test docker
stavros-k Apr 3, 2024
38f8d7a
add wf
stavros-k Apr 3, 2024
66a7d9e
test
stavros-k Apr 3, 2024
789cfe0
typo
stavros-k Apr 3, 2024
f265fa3
hmm
stavros-k Apr 3, 2024
452ddd3
checkout
stavros-k Apr 3, 2024
6003b5a
addreq
stavros-k Apr 3, 2024
ce9d7ca
order
stavros-k Apr 3, 2024
2846200
yaml
stavros-k Apr 3, 2024
0529c3b
pyyaml
stavros-k Apr 3, 2024
901c78b
test health
stavros-k Apr 3, 2024
d09b07d
test
stavros-k Apr 3, 2024
a86312b
order of flag
stavros-k Apr 3, 2024
4844e20
check again
stavros-k Apr 3, 2024
1eba062
split steps
stavros-k Apr 3, 2024
0505413
random name
stavros-k Apr 3, 2024
5b1d8b3
cleanup
stavros-k Apr 3, 2024
e6ddaf9
add some comments
stavros-k Apr 3, 2024
d42de1c
account for exited but not because it was fatal
stavros-k Apr 3, 2024
2c494f4
more info
stavros-k Apr 3, 2024
b6aa73a
add inspect data for failed container
stavros-k Apr 4, 2024
7cdcf12
test
stavros-k Apr 4, 2024
8ff38ed
make it look better
stavros-k Apr 4, 2024
60e4456
better handling of containers without HC
stavros-k Apr 4, 2024
4b197c3
more data on the output
stavros-k Apr 4, 2024
8e31f8b
bump dep and add todo
stavros-k Apr 4, 2024
5e6c982
more fixes
stavros-k Apr 4, 2024
7703d3d
move into dir
stavros-k Apr 4, 2024
099df69
flag order
stavros-k Apr 4, 2024
41304d9
todos
stavros-k Apr 4, 2024
4f96ac7
codesplit
stavros-k Apr 4, 2024
052b630
check if docker on ci supports that
stavros-k Apr 4, 2024
d799ebd
test args
stavros-k Apr 8, 2024
66424dd
clean
stavros-k Apr 8, 2024
e2b939d
make sure its string
stavros-k Apr 8, 2024
afd98e2
echo files
stavros-k Apr 8, 2024
4cc4773
echo files
stavros-k Apr 8, 2024
e8f0611
dir names
stavros-k Apr 8, 2024
094c7dd
match apps
stavros-k Apr 8, 2024
21c232c
list files
stavros-k Apr 8, 2024
e70e26a
dedupe
stavros-k Apr 8, 2024
14f9784
test
stavros-k Apr 8, 2024
bc11fd2
hmm
stavros-k Apr 8, 2024
dc61a89
set
stavros-k Apr 8, 2024
504e717
single line
stavros-k Apr 8, 2024
f269617
test
stavros-k Apr 8, 2024
aa1bc21
test
stavros-k Apr 8, 2024
e163298
test
stavros-k Apr 8, 2024
35c2094
jmm
stavros-k Apr 8, 2024
76b3f51
logging
stavros-k Apr 8, 2024
0c1cbaa
whops
stavros-k Apr 8, 2024
e953466
which one
stavros-k Apr 8, 2024
3fccaf0
hmm
stavros-k Apr 8, 2024
1b05da5
:D
stavros-k Apr 8, 2024
ef0ff55
duh
stavros-k Apr 8, 2024
71f2593
whoops
stavros-k Apr 8, 2024
93f0c31
safer
stavros-k Apr 8, 2024
d49584c
lower default
stavros-k Apr 8, 2024
d7d74cd
small imrpovements
stavros-k Apr 9, 2024
003123d
test json output
stavros-k Apr 9, 2024
855063a
improve tool
stavros-k Apr 9, 2024
36ce4e9
typo
stavros-k Apr 9, 2024
faeb88b
check output
stavros-k Apr 9, 2024
055f3c8
typo
stavros-k Apr 9, 2024
03a99fb
hmm
stavros-k Apr 9, 2024
55e2da2
get the ci values from fs not from changed files
stavros-k Apr 9, 2024
ece5ff3
hmm
stavros-k Apr 9, 2024
cda0581
fix
stavros-k Apr 9, 2024
c3ef11e
fixes for gh matrix
stavros-k Apr 9, 2024
2df224c
whops
stavros-k Apr 9, 2024
5123d19
typo
stavros-k Apr 9, 2024
af48a0e
hmm
stavros-k Apr 9, 2024
3ba8e1c
hmm
stavros-k Apr 9, 2024
c16909c
hmm
stavros-k Apr 9, 2024
79586ba
x
stavros-k Apr 9, 2024
234ba1e
we dont need to get ci values so early
stavros-k Apr 9, 2024
aff51c6
fix typo
stavros-k Apr 9, 2024
cd92af3
fix some func names
stavros-k Apr 10, 2024
5723d90
test https
stavros-k Apr 10, 2024
ecab636
check for non empty avlues
stavros-k Apr 10, 2024
28e74e4
try thi
stavros-k Apr 10, 2024
7716b5e
fix check
stavros-k Apr 10, 2024
ecb85ab
revert but leave the warning
stavros-k Apr 10, 2024
a8a526c
ignore some cases
stavros-k Apr 10, 2024
db22282
add some logging
stavros-k Apr 10, 2024
7fb3183
typo and single lines
stavros-k Apr 10, 2024
36ab9ee
see actual logs -.-
stavros-k Apr 10, 2024
3bd8c60
insecure
stavros-k Apr 10, 2024
b3381d1
hmm
stavros-k Apr 10, 2024
b12a4c9
try now
stavros-k Apr 10, 2024
11e32d9
better handling
stavros-k Apr 11, 2024
05732d1
cleanup
stavros-k Apr 11, 2024
37350fd
improve errors
stavros-k Apr 29, 2024
622f37e
simplify
stavros-k Apr 29, 2024
ea9315f
stop checking early if its unhealthy
stavros-k Apr 29, 2024
0a0f959
flatten
stavros-k Apr 29, 2024
de7a111
pretty
stavros-k Apr 29, 2024
148cbb2
simplify even more
stavros-k Apr 29, 2024
a7d0177
more cleanup
stavros-k Apr 29, 2024
fa3dafc
local config
stavros-k Apr 29, 2024
2098847
remove minio from this branch
stavros-k Apr 30, 2024
7b4d1b5
update snippets
stavros-k May 1, 2024
c7024fd
relative imports
stavros-k May 1, 2024
658efff
Update utils.py
stavros-k May 2, 2024
9d4b1b4
Update utils.py
stavros-k May 2, 2024
6c5807f
Update utils.py
stavros-k May 2, 2024
5615b63
split
stavros-k May 2, 2024
f8d8fc4
cleanup after app run
stavros-k May 2, 2024
a19dec8
unused params
stavros-k May 2, 2024
27185a3
add path val
stavros-k May 2, 2024
3e89498
func
stavros-k May 2, 2024
e75d966
validate paths
stavros-k May 2, 2024
b6d7b7f
update structure
stavros-k May 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions .github/workflows/install-docker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Test App Install

on:
pull_request: {}

jobs:
changed-files:
name: Get changed apps
runs-on: ubuntu-latest
outputs:
changed-apps: ${{ steps.changed-apps.outputs.changed-apps }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get changed files
id: changed-files-json
uses: tj-actions/changed-files@v44
with:
json: true
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: "1.22.2"
- name: Get changed apps
id: changed-apps
env:
CHANGED_FILES: ${{ steps.changed-files-json.outputs.all_changed_files }}
run: |
echo $CHANGED_FILES
out=$(go run ./tools/get-changed-apps/cmd/main.go)
echo "changed-apps=${out}" >> $GITHUB_OUTPUT
echo $out

test-apps:
name: Test apps
needs: changed-files
runs-on: ubuntu-latest
strategy:
matrix:
app: ${{ fromJson(needs.changed-files.outputs.changed-apps) }}
steps:
- name: Environment Information
run: |
echo "====== Docker Info ======"
docker info
echo "========================="
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: "1.22.2"
- name: Checkout
uses: actions/checkout@v4
- name: Install Dependencies
shell: bash
run: |
pip install -r requirements.txt
- name: Install Go Dependencies
run: |
cd tools/docker-health-check
go mod download
go build -o ../../check_health cmd/main.go
chmod +x ../../check_health
- name: Run app
shell: bash
run: |
for values_file in ix-dev/${{ matrix.app }}/ci/*.yaml; do
echo "Testing [$values_file]"
sudo ./main.py $values_file ix-dev/${{ matrix.app }} > docker-compose.yaml

# Print the parsed, by compose, template
# (as it will also remove empty lines)
docker compose -f docker-compose.yaml config

PROJECT_NAME="$(openssl rand -hex 12)"

docker compose -p $PROJECT_NAME -f docker-compose.yaml up -d

./check_health --project $PROJECT_NAME --files docker-compose.yaml

docker compose -p $PROJECT_NAME -f docker-compose.yaml down --remove-orphans --volumes
docker compose -p $PROJECT_NAME -f docker-compose.yaml rm --force --stop --volumes

# Clean up the test directory used by the app
echo "Cleaning up /mnt/test directory"
sudo rm -r /mnt/test || echo "Failed to clean up /mnt/test"
done
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__
ix-dev/**/rendered
10 changes: 10 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"files.associations": {
"*.yaml": "jinja-yaml"
},
"[jinja-yaml]": {
"editor.defaultFormatter": null,
"editor.formatOnSave": false,
"editor.formatOnPaste": false
}
}
30 changes: 30 additions & 0 deletions copy_lib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

# Copy the library to the ix-dev directory
train=$1
app_name=$2

if [ -z "$train" ]; then
echo "Usage: $0 <train> <app_name>"
exit 1
fi

if [ -z "$app_name" ]; then
echo "Usage: $0 <app_name>"
exit 1
fi

app_path="ix-dev/$train/$app_name"

if [ ! -d "$app_path" ]; then
echo "App [$app_path] does not exist"
exit 1
fi

# pick the latest version
lib=$(ls -d library/* | sort -V | tail -n 1)
lib_version=$(basename $lib)
rm -rf "$app_path/templates/library/base_v$(sed 's/\./_/g' <<<$lib_version)"
mkdir -p "$app_path/templates/library/$lib_version"
cp -r $lib/* "$app_path/templates/library/$lib_version"
mv "$app_path/templates/library/$lib_version" "$app_path/templates/library/base_v$(sed 's/\./_/g' <<<$lib_version)"
4 changes: 4 additions & 0 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
words:
- CPUS
- Healtcheck
- isready
Empty file added library/1.0.0/__init__.py
Empty file.
74 changes: 74 additions & 0 deletions library/1.0.0/snippets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from . import utils

def health_check(test = "", interval = 10, timeout = 10, retries = 5, start_period = 30):
if not test:
utils.throw_error("Healtcheck: [test] must be set")

return {
"test": test,
"interval": f'{interval}s',
"timeout": f'{timeout}s',
"retries": retries,
"start_period": f'{start_period}s'
}

def curl_test(url):
return f"curl --silent --fail {url}"

def pg_test(user, db, host = "127.0.0.1", port = 5432):
if not user:
utils.throw_error("Postgres container: [user] must be set")

if not db:
utils.throw_error("Postgres container: [db] must be set")

return f"pg_isready -h {host} -p {port} -d {db} -U {user}"

def postgres_uid():
return 999
def postgres_gid():
return 999
def postgres_run_as():
return f"{postgres_uid()}:{postgres_gid()}"

def postgres_environment(user, password, db):
if not user:
utils.throw_error("Postgres container: [user] must be set")

if not password:
utils.throw_error("Postgres container: [password] must be set")

if not db:
utils.throw_error("Postgres container: [db] must be set")

return {
"POSTGRES_USER": user,
"POSTGRES_PASSWORD": password,
"POSTGRES_DB": db
}

def get_default_limits():
return {
"cpus": "2.0",
"memory": "4gb"
}

def get_limits(data):
limits = get_default_limits()

if not data:
return limits

limits.update({
"cpus": str(data.get("limits", limits['cpus']).get("cpus", limits['cpus'])),
"memory": data.get("limits", limits['memory']).get("memory", limits['memory'])
})

return limits

def resources(data = {}):
return {
"resources": {
"limits": get_limits(data)
},
}
85 changes: 85 additions & 0 deletions library/1.0.0/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from . import validations
import secrets
import yaml
import sys
import os

class TemplateException(Exception):
pass

def throw_error(message: str) -> None:
# When throwing a known error, hide the traceback
# This is because the error is also shown in the UI
# and having a traceback makes it hard for user to read
sys.tracebacklimit = 0
raise TemplateException(message)

def get_yaml_opts():
return {
'default_flow_style': False,
'sort_keys': False,
'indent': 2,
}

def to_yaml(data):
return yaml.dump(data, **get_yaml_opts())

def secure_string(length):
return secrets.token_urlsafe(length)


# TODO: maybe extend this with ACLs API?!
def host_path_with_perms(data: dict, root: dict, perms: dict) -> str:
if not data.get('type', ''):
throw_error("Host Path Configuration: Type must be set")

path = ''
if data['type'] == 'host_path':
path = process_host_path(data, root, perms)
elif data['type'] == 'ix_volume':
path = process_ix_volume(data, root)
else:
throw_error(f"Type [{data['type']}] is not supported")

# FIXME: only use this on CI and not on prod
os.makedirs(path, exist_ok=True)

# FIXME: only do such things if user agreed or if its ixVolumes
if perms.get('user') and perms.get('group'):
# Set permissions
os.chown(path, int(perms['user']), int(perms['group']))

return validations.func_validate_path(path)

def process_ix_volume(data: dict, root: dict) -> dict:
path = ''
if not data.get('ix_volume_config', {}):
throw_error("IX Volume Configuration: [ix_volume_config] must be set")

if not data['ix_volume_config'].get('dataset_name', ''):
throw_error("IX Volume Configuration: [ix_volume_config.dataset_name] must be set")

if not root.get('ixVolumes', []):
throw_error("IX Volume Configuration: [ixVolumes] must be set")

for item in root['ixVolumes']:
if not item.get('hostPath', ''):
throw_error("IX Volume Configuration: [ixVolumes] item must contain [hostPath]")

if item['hostPath'].split('/')[-1] == data['ix_volume_config']['dataset_name']:
path = item['hostPath']
break

if not path:
throw_error(f"IX Volume Configuration: [ixVolumes] does not contain dataset with name [{data['ix_volume_config']['dataset_name']}]")

return path

def process_host_path(data: dict) -> str:
if not data.get('host_path_config', {}):
throw_error("Host Path Configuration: [host_path_config] must be set")

if not data['host_path_config'].get('path', ''):
throw_error("Host Path Configuration: [host_path_config.path] must be set")

return data['host_path_config']['path']
43 changes: 43 additions & 0 deletions library/1.0.0/validations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from . import utils
import re

def is_email(email):
return re.match(r"[^@]+@[^@]+\.[^@]+", email)

def must_be_email(email):
if not is_email(email):
utils.throw_error("Value must be an email address")

return email

def must_be_length(value, length):
if len(value) < length:
utils.throw_error(f"Value must be {length} characters long")

return value

def is_password_secure(password):
checks=[
lambda p: len(p) >= 8,
lambda p: re.search("[a-z]", p),
lambda p: re.search("[A-Z]", p),
lambda p: re.search("[0-9]", p),
lambda p: re.search("[!@#$%^&*(),.?\":{}|<>]", p)
]

return all(c(password) for c in checks)

def must_be_password_secure(password):
if not is_password_secure(password):
utils.throw_error("Password must contain at least 8 characters, 1 uppercase letter, 1 lowercase letter, 1 number, and 1 special character")

return password

def validate_path(path):
if not path:
utils.throw_error("Path must be set")

if not path.startswith("/"):
utils.throw_error(f"Path [{path}] must start with [/]")

return ''
50 changes: 50 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
import importlib
import inspect
import yaml
import sys
import os
from jinja2 import Environment, FileSystemLoader

if len(sys.argv) < 3:
raise ValueError("path must be set")

values_path = sys.argv[1]
app_path = sys.argv[2]


if not values_path:
raise ValueError("values path must be set")

if not app_path:
raise ValueError("app path must be set")

if not os.path.exists(values_path):
raise ValueError(f"values path [{values_path}] does not exist")

if not os.path.exists(app_path):
raise ValueError(f"app path [{app_path}] does not exist")

env = Environment(
loader=FileSystemLoader(f"{app_path}/templates"),
)

items = [
importlib.import_module("library.common.validations"),
importlib.import_module("library.common.snippets"),
importlib.import_module("library.common.utils")
]

for item in items:
for name, func in inspect.getmembers(item, inspect.isfunction):
if name.startswith("filter_"):
env.filters[name.replace("filter_", "")] = func
if name.startswith("func_"):
env.globals.update({name.replace("func_", ""): func})

template = env.get_template("docker-compose.yaml.j2")

with open(values_path) as f:
mock = yaml.safe_load(f)

print(template.render(data=mock))
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Jinja2
pyyaml
Loading
Loading