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

Add image building and tagged testing to CI #350

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Changes from 6 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
96bbc24
build containers in runtests script
HenryL27 Apr 11, 2024
42683d3
fix buildscript wc weirdness
HenryL27 Apr 11, 2024
36facda
use docker registry caching for image builds
HenryL27 Apr 11, 2024
79cb6d6
remove post-build exit
HenryL27 Apr 12, 2024
f9d266f
put test logs in s3
HenryL27 Apr 15, 2024
6e5122d
specify arch in s3 path for results
HenryL27 Apr 15, 2024
49536db
address pr comments. only build images for machines arch, and push af…
HenryL27 Apr 18, 2024
bb69c94
allow untracked files when deciding whether to checkout
HenryL27 Apr 18, 2024
db82883
use exit code for checkout_main_if_new
HenryL27 Apr 18, 2024
fe3ad44
make git status checks more robust with --porcelain
HenryL27 Apr 29, 2024
1783048
Merge branch 'main' into it-automation
HenryL27 Apr 29, 2024
6122bde
address style comments (mostly)
HenryL27 May 3, 2024
2ed3ffa
Merge branch 'it-automation' of github.com:aryn-ai/sycamore into it-a…
HenryL27 May 3, 2024
82fb8e6
add args to specify which parts of integration script to run. also re…
HenryL27 May 3, 2024
1666e46
address more pr comments
HenryL27 May 7, 2024
fdd90b9
change -r to --recursive because aws cli is verbose
HenryL27 May 8, 2024
bbbb1c1
check that test-output exists before trying to move it
HenryL27 May 8, 2024
f1247c7
aws cli doesnt do globs either
HenryL27 May 8, 2024
67f958f
add ssh orchestration for building and testing on multiple arches
HenryL27 May 10, 2024
cab5ebb
syntax
HenryL27 May 10, 2024
bc6d094
test flags correctly
HenryL27 May 10, 2024
4b34844
fix regex match
HenryL27 May 10, 2024
2cb85e1
tell docker to build for both platforms
HenryL27 May 10, 2024
6091289
kill the right port forward process
HenryL27 May 10, 2024
fed8b44
run remote tests asynchronously
HenryL27 May 10, 2024
3e0c027
Merge branch 'it-automation' of github.com:aryn-ai/sycamore into it-a…
HenryL27 May 10, 2024
a9eba5c
dont prompt about whether to prune networks, just prune them
HenryL27 May 11, 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
89 changes: 82 additions & 7 deletions apps/integration/integration/automation/runtests.sh
Original file line number Diff line number Diff line change
@@ -1,30 +1,52 @@
#!/bin/bash

TAG="$1"
[[ -z "${TAG}" ]] && TAG="latest_rc"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you, but quotes aren't necessary in square brackets. This file isn't consistent, if you care.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do TAG="integration_tests" by default.


NOW="$(date +"%Y-%m-%d_%H_%M")"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No seconds? At the very least it could be good for debugging.

ARCH="amd64"
[[ "$(uname -m)" = "arm64" ]] && ARCH="arm64"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it bad to just do ARCH=$(uname -m)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want amd64 instead of X86_64 which is what I get on those machines. I don't anticipate trying to run this on android or raspberry pi or (god forbid) powerpc


RUNDIR="apps/integration/runs/${NOW}"
GIT_LOGFILE="${RUNDIR}/git.log"
DOCKER_LOGFILE="${RUNDIR}/docker.log"
POETRY_LOGFILE="${RUNDIR}/poetry.log"
PYTEST_LOGFILE="${RUNDIR}/pytest.log"
QUERY_LOGFILE="${RUNDIR}/test_queries.log"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd just rename these to _LOG for brevity. It's no less inaccurate. These are really paths, but nobody wants to read DOCKER_LOGFILE_PATH. Anyway, a log is a file by default.


main() {
if [[ ! -d ".git" ]]; then
echo "Error: please run this script from sycamore root!" >&2
exit 1
fi
mkdir -p "${RUNDIR}"
echo "Building/testing tag ${TAG}" >&2
echo "Get the newest git commits" >&2
checkout_main_if_new
local should_run=$?
if [[ $should_run ]]; then
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can shorten this to if [[ checkout_main_if_new ]]; then

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can ditch the square brackets entirely.

$ mytrue() {
>   return 0
> }
$ if mytrue; then
>   echo yes
> fi
yes

echo "Changes detected. Running Tests" >&2
poetry install
build_containers
runtests
handle_outputs
poetry install > "${POETRY_LOGFILE}" 2>&1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're going to need || die "poetry install failed"
and similar on all the remaining bits.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, I always want to get to the handle_outputs, so I don't think I want to die. I think I can && all these things together to get the correct behavior though.

poetry install > "${POETRY_LOGFILE}" 2>&1 \
&& build_containers > "${DOCKER_LOGFILE}" 2>&1 \
&& runtests > "${PYTEST_LOGFILE}" 2>&1

build_containers > "${DOCKER_LOGFILE}" 2>&1
runtests > "${PYTEST_LOGFILE}" 2>&1
local passed_tests=$?
handle_outputs passed_tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a $?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you know it! (exit codes don't do anything weird when they become variables, right?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the number just carries across the assignment faithfully. It's when used as a conditional that zero becomes true.

else
echo "No changes detected. Skipping integration tests" >&2
fi
}

error() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should call this die for accuracy and consistency with our other scripts and general convention.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leaving as error but changing so it doesn't exit. I basically never want this script to exit early

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's just cleanup you're worried about, you could also look into trap with EXIT.

echo "ERROR: $@"
exit 1
}

checkout_main_if_new() {
old_sha="$(git rev-parse HEAD)"
git fetch origin main >&2
git fetch origin main > "${GIT_LOGFILE}"
new_sha="$(git rev-parse FETCH_HEAD)"
if [[ "${old_sha}" != "${new_sha}" ]]; then
git pull origin main >&2
git pull --rebase origin main >> "${GIT_LOGFILE}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check that git status is clean.

[[ $(git status | grep -c 'nothing to commit, working tree clean') = 1 ]] || die "Working tree not clean"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since I dont want to die, does
{ echo "Working tree not clean" > "${GIT_LOGFILE}" && return 1; }
do the right thing after the ||?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I need it to be okay with untracked files, bc I create them in apps/integration/runs.
will make this a grep -c -e 'nothing to commit, working tree clean' -e 'nothing added to commit but untracked files present'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not rely on specific English-language messages. How about running git status --porcelain | grep -vF '??' and using -z to check for no output.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively I can just add apps/integration/runs to the gitignore and then take the simpler -z git status --procelain you gave me earlier

return 0
else
return 1
Expand All @@ -33,20 +55,73 @@ checkout_main_if_new() {

build_containers() {
echo "Yep, definitely building containers. That's what this function does" >&2
docker-build-hub apps/crawler/crawler/http/Dockerfile
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to build and push to docker hub or only build to local? This seems like it's going to generate a lot of transit data. Moreover, given the way you're doing the testing, if we want to test on arm it would be a separate build/test run and then the two builds would clash.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'd find it convenient that reasonably up-to-date images are easily available to test with. Having them in DockerHub seems like the lowest-friction way.

docker-build-hub apps/crawler/crawler/s3/Dockerfile
docker-build-hub apps/importer/Dockerfile.buildx
docker-build-hub apps/opensearch/Dockerfile
docker-build-hub apps/jupyter/Dockerfile.buildx --build-arg=TAG="${TAG}"
docker-build-hub apps/demo-ui/Dockerfile.buildx
docker-build-hub apps/remote-processor-service/Dockerfile.buildx
}

handle_outputs() {
echo "Yep, definitely handling test outputs. That's what this function does" >&2
local passed_tests="$1"
mv test-output.log "${QUERY_LOGFILE}"
[[ ${passed_tests} = 0 ]] && touch "${RUNDIR}/passed"
[[ ${passed_tests} != 0 ]] && touch "${RUNDIR}/failed"
aws s3 cp -r "${RUNDIR}" "s3://sycamore-ci/${ARCH}"
}

runtests() {
docker system prune -f --volumes
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems risky since if someone runs it it will prune other stuff they might want; It also means that we get rid of the old volumes; it's also not guaranteed to clean things up if it's still in use.

I'd instead suggest that we generate a unique run id (use the date stamp?). and do the docker compose -p up. If we tag the images in the same way, then if there's a problem someone could go back and debug against the exact set of images. This would imply we don't want to push to docker hub since it's a lot of stuff.
That guarantees you're getting

docker compose up reset
poetry run pytest apps/integration/ -p integration.conftest --noconftest --docker-tag latest_rc
poetry run pytest apps/integration/ -p integration.conftest --noconftest --docker-tag "${TAG}"
# this is a complicated command, so: ^ ^ ^ test against containers tagged latest_rc
# | don't load conftest at pytest runtime; it's already loaded
# load conftest with plugins, to capture the custom command line arg --docker-tag
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just reformat this as:

# -p           - load conftest with plugins, to capture the custom command line arg --docker-tag
# --noconftest - don't load conftest at pytest runtime; it's already loaded
# --docker-tag - specify tag of containers to test

Then you don't need to keep updating the ASCII art if the above code changes. And, you get more horizontal space for the explanations.

return $?
}

docker-build-hub() {
local docker_file="$1"
[[ -n "${docker_file}" ]] || error "missing ${docker_file}"
local repo_name="$(_docker-repo-name "${docker_file}")"
[[ -n "${repo_name}" ]] || error "empty repo name"
shift

local platform=linux/amd64,linux/arm64
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want this? I would think we would build/test for the local platform.

echo
echo "Building in sycamore and pushing to docker hub with repo name '${repo_name}'"
docker buildx build "$(_docker-build-args)" -t "${repo_name}:${TAG}" -f "${docker_file}" --platform ${platform} \
--cache-to type=registry,ref="${repo_name}:build-cache",mode=max \
--cache-from type=registry,ref="${repo_name}:build-cache" \
"$@" --push . || error "buildx failed"
echo "Successfully built in $dir using docker file $docker_file"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does $dir get a value?

}

_docker-repo-name() {
local docker_file="$1"
echo "Finding repo name in: ${docker_file}" >&2
local repo_name="$(grep '^# Repo name: ' "${docker_file}" | awk '{print $4}')"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more robust approach might be:

local repo_name=$(sed -nr -e 's/^#\s?Repo\s+name:\s*(\w+)/\1/ip' "${docker_file}" | head -n 1)

It would save you from multiple copies and variations in formatting. It goes well with your check that the word count is one, below. In assignment context, you don't need the quotes around $(...) command substitution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, my pipe to head -n 1 may be unnecessary if you're checking for a single word right after.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to be very robust here. We control the input file.

if (( $(wc -w <<< ${repo_name}) != 1 )); then
echo "Unable to find repo name in ${docker_file}" 1>&2
exit 1
fi
echo "${repo_name}"
}

_docker-build-args() {
local branch="$(git status | head -1 | grep 'On branch ' | awk '{print $3}')"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

head -n1 is better (fewer warnings). I'd also use grep -i just in case (get it?).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git branch --show-current ?

local rev="$(git rev-parse --short HEAD)"
local date="$(git show -s --format=%ci HEAD | sed 's/ /_/g')"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's preferred to have sed -e <program>.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly hygiene. If the program happens to have something that might get interpreted as a command-line option, -e protects it. Also, you can do multi-expression scripts with multiple -e directives.

local diff=unknown
if [[ $(git status | grep -c 'nothing to commit, working tree clean') = 1 ]]; then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe grep -i but even that seems too dependent on the exact wording of the message. Also, you might be able to use the exit code of grep directly, like:

if git status | grep -iq 'nothing to commit'; then

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to check for the specific message to guarantee the tree is clean (no unexpected files also).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The definition of clean is zero modified files and zero untracked files. The future-proof way to do this is:

if [[ -z $(git status --porcelain) ]]; then

diff=clean
else
diff="pending_changes_$(git diff HEAD | shasum | awk '{print $1}')"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the SHA important? If so, SHA-1 is considered obsolete; so, you'd want shasum -a 256, but they're longer. For a shorter checksum that's OK, try md5sum.

fi
echo "--build-arg=GIT_BRANCH=${branch} --build-arg=GIT_COMMIT=${rev}--${date} --build-arg=GIT_DIFF=${diff}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why double-delimiters between rev and date?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because date has -'s in it; and -- makes it easier to read.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are better formats for date available, for instance ISO 8601 basic.

}

main
Loading