feat(cli): `vieter build` now builds a single repo & uploads build logs

main
Jef Roosens 2022-05-08 13:17:54 +02:00
parent 27aa215eff
commit 4b172cb5d8
Signed by untrusted user: Jef Roosens
GPG Key ID: B75D4F293C7052DB
3 changed files with 68 additions and 25 deletions

View File

@ -73,10 +73,17 @@ pub fn create_build_image(base_image string) ?string {
return image.id 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 // 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 // provided GitRepo. The base image ID should be of an image previously created
// by create_build_image. // 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) ? { pub fn build_repo(address string, api_key string, base_image_id string, repo &db.GitRepo) ?BuildResult {
build_arch := os.uname().machine build_arch := os.uname().machine
// TODO what to do with PKGBUILDs that build multiple packages? // 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) ? id := docker.create_container(c) ?
docker.start_container(id) ? docker.start_container(id) ?
mut data := docker.inspect_container(id) ?
// This loop waits until the container has stopped, so we can remove it after // This loop waits until the container has stopped, so we can remove it after
for { for {
data := docker.inspect_container(id) ?
if !data.state.running { if !data.state.running {
break break
} }
time.sleep(1 * time.second) time.sleep(1 * time.second)
data = docker.inspect_container(id) ?
} }
logs := docker.get_container_logs(id) ?
docker.remove_container(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. // 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 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 // First, we create a base image which has updated repos n stuff
println('Creating base image...')
image_id := create_build_image(conf.base_image) ? image_id := create_build_image(conf.base_image) ?
for repo in filtered_repos { println('Running build...')
build_repo(conf.address, conf.api_key, image_id, repo) ? 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) ? 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) ?
} }

View File

@ -14,12 +14,16 @@ pub:
pub fn cmd() cli.Command { pub fn cmd() cli.Command {
return cli.Command{ return cli.Command{
name: 'build' 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) ? { execute: fn (cmd cli.Command) ? {
config_file := cmd.flags.get_string('config-file') ? config_file := cmd.flags.get_string('config-file') ?
conf := env.load<Config>(config_file) ? conf := env.load<Config>(config_file) ?
build(conf) ? id := cmd.args[0].int()
build(conf, id) ?
} }
} }
} }

View File

@ -2,6 +2,8 @@ module docker
import json import json
import net.urllib import net.urllib
import regex
import time
struct Container { struct Container {
id string [json: Id] id string [json: Id]
@ -49,13 +51,28 @@ pub fn start_container(id string) ?bool {
} }
struct ContainerInspect { struct ContainerInspect {
pub: pub mut:
state ContainerState [json: State] state ContainerState [json: State]
} }
struct ContainerState { struct ContainerState {
pub: pub:
running bool [json: Running] 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 // 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 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. // remove_container removes a container with a given ID.