From 4b172cb5d8e9dc8d96f7c73fc84e0d7aa1b8b5ab Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 8 May 2022 13:17:54 +0200 Subject: [PATCH] feat(cli): `vieter build` now builds a single repo & uploads build logs --- src/build/build.v | 56 +++++++++++++++++++++++++---------------- src/build/cli.v | 8 ++++-- src/docker/containers.v | 29 +++++++++++++++++++-- 3 files changed, 68 insertions(+), 25 deletions(-) diff --git a/src/build/build.v b/src/build/build.v index 6f033e6..9505171 100644 --- a/src/build/build.v +++ b/src/build/build.v @@ -73,10 +73,17 @@ pub fn create_build_image(base_image string) ?string { return image.id } +struct BuildResult { + start_time time.Time + end_time time.Time + exit_code int + logs string +} + // build_repo builds, packages & publishes a given Arch package based on the // provided GitRepo. The base image ID should be of an image previously created -// by create_build_image. -pub fn build_repo(address string, api_key string, base_image_id string, repo &db.GitRepo) ? { +// by create_build_image. It returns the logs of the container. +pub fn build_repo(address string, api_key string, base_image_id string, repo &db.GitRepo) ?BuildResult { build_arch := os.uname().machine // TODO what to do with PKGBUILDs that build multiple packages? @@ -107,43 +114,50 @@ pub fn build_repo(address string, api_key string, base_image_id string, repo &db id := docker.create_container(c) ? docker.start_container(id) ? + mut data := docker.inspect_container(id) ? + // This loop waits until the container has stopped, so we can remove it after for { - data := docker.inspect_container(id) ? - if !data.state.running { break } time.sleep(1 * time.second) + + data = docker.inspect_container(id) ? } + logs := docker.get_container_logs(id) ? + docker.remove_container(id) ? + + return BuildResult{ + start_time: data.state.start_time + end_time: data.state.end_time + exit_code: data.state.exit_code + logs: logs + } } // build builds every Git repo in the server's list. -fn build(conf Config) ? { +fn build(conf Config, repo_id int) ? { + c := client.new(conf.address, conf.api_key) + repo := c.get_git_repo(repo_id) ? + build_arch := os.uname().machine - // We get the repos map from the Vieter instance - repos := client.new(conf.address, conf.api_key).get_git_repos() ? - - // We filter out any repos that aren't allowed to be built on this - // architecture - filtered_repos := repos.filter(it.arch.map(it.value).contains(build_arch)) - - // No point in doing work if there's no repos present - if filtered_repos.len == 0 { - return - } - // First, we create a base image which has updated repos n stuff + println('Creating base image...') image_id := create_build_image(conf.base_image) ? - for repo in filtered_repos { - build_repo(conf.address, conf.api_key, image_id, repo) ? - } + println('Running build...') + res := build_repo(conf.address, conf.api_key, image_id, repo) ? - // Finally, we remove the builder image + // Remove the builder image + println('Removing build image...') docker.remove_image(image_id) ? + + // Upload the build log to the Vieter instance + println('Uploading logs to Vieter...') + c.add_build_log(repo.id, res.start_time, res.end_time, build_arch, res.exit_code, res.logs) ? } diff --git a/src/build/cli.v b/src/build/cli.v index 0131396..5247e87 100644 --- a/src/build/cli.v +++ b/src/build/cli.v @@ -14,12 +14,16 @@ pub: pub fn cmd() cli.Command { return cli.Command{ name: 'build' - description: 'Run the build process.' + required_args: 1 + usage: 'id' + description: 'Build the repository with the given ID.' execute: fn (cmd cli.Command) ? { config_file := cmd.flags.get_string('config-file') ? conf := env.load(config_file) ? - build(conf) ? + id := cmd.args[0].int() + + build(conf, id) ? } } } diff --git a/src/docker/containers.v b/src/docker/containers.v index 8134370..63095a6 100644 --- a/src/docker/containers.v +++ b/src/docker/containers.v @@ -2,6 +2,8 @@ module docker import json import net.urllib +import regex +import time struct Container { id string [json: Id] @@ -49,13 +51,28 @@ pub fn start_container(id string) ?bool { } struct ContainerInspect { -pub: +pub mut: state ContainerState [json: State] } struct ContainerState { pub: running bool [json: Running] + status string [json: Status] + exit_code int [json: ExitCode] + // These use a rather specific format so they have to be parsed later + start_time_str string [json: StartedAt] + end_time_str string [json: FinishedAt] +pub mut: + start_time time.Time [skip] + end_time time.Time [skip] +} + +fn docker_timestamp_to_time(s string) ?time.Time { + parts := s.split('.') + clipped := parts[0] + '.' + parts[1][..3] + + return time.parse_rfc3339(clipped) } // inspect_container returns the result of inspecting a container with a given @@ -67,7 +84,15 @@ pub fn inspect_container(id string) ?ContainerInspect { return error('Failed to inspect container.') } - return json.decode(ContainerInspect, res.text) or {} + mut data := json.decode(ContainerInspect, res.text) ? + + data.state.start_time = docker_timestamp_to_time(data.state.start_time_str) ? + + if data.state.status == "exited" { + data.state.end_time = docker_timestamp_to_time(data.state.end_time_str) ? + } + + return data } // remove_container removes a container with a given ID.