diff --git a/.woodpecker/.deploy.yml b/.woodpecker/.deploy.yml index 8cf3907..56e6c2f 100644 --- a/.woodpecker/.deploy.yml +++ b/.woodpecker/.deploy.yml @@ -10,8 +10,6 @@ pipeline: webhook: image: chewingbever/vlang:latest secrets: - - webhook_1 - - webhook_2 + - webhook commands: - - curl -XPOST -s "$WEBHOOK_1" - - curl -XPOST -s "$WEBHOOK_2" + - curl -XPOST -s "$WEBHOOK" diff --git a/src/build.v b/src/build.v index 51de64c..fc1fe6f 100644 --- a/src/build.v +++ b/src/build.v @@ -2,6 +2,7 @@ module main import docker import encoding.base64 +import rand import time import json import server @@ -10,10 +11,17 @@ import net.http const container_build_dir = '/build' -const build_image_repo = 'vieter-build' +fn build() ? { + conf := env.load() ? -fn create_build_image() ?string { - commands := [ + // We get the repos list from the Vieter instance + mut req := http.new_request(http.Method.get, '$conf.address/api/repos', '') ? + req.add_custom_header('X-Api-Key', conf.api_key) ? + + res := req.do() ? + repos := json.decode([]server.GitRepo, res.text) ? + + mut commands := [ // Update repos & install required packages 'pacman -Syu --needed --noconfirm base-devel git' // Add a non-root user to run makepkg @@ -26,11 +34,31 @@ fn create_build_image() ?string { 'mkdir /build', 'chown -R builder:builder /build', ] + + // Each repo gets a unique UUID to avoid naming conflicts when cloning + mut uuids := []string{} + + for repo in repos { + mut uuid := rand.uuid_v4() + + // Just to be sure we don't have any collisions + for uuids.contains(uuid) { + uuid = rand.uuid_v4() + } + + uuids << uuid + + commands << "su builder -c 'git clone --single-branch --depth 1 --branch $repo.branch $repo.url /build/$uuid'" + commands << 'su builder -c \'cd /build/$uuid && makepkg -s --noconfirm --needed && for pkg in \$(ls -1 *.pkg*); do curl -XPOST -T "\${pkg}" -H "X-API-KEY: \$API_KEY" $conf.address/publish; done\'' + } + + // We convert the list of commands into a base64 string, which then gets + // passed to the container as an env var cmds_str := base64.encode_str(commands.join('\n')) c := docker.NewContainer{ image: 'archlinux:latest' - env: ['BUILD_SCRIPT=$cmds_str'] + env: ['BUILD_SCRIPT=$cmds_str', 'API_KEY=$conf.api_key'] entrypoint: ['/bin/sh', '-c'] cmd: ['echo \$BUILD_SCRIPT | base64 -d | /bin/sh -e'] } @@ -53,76 +81,5 @@ fn create_build_image() ?string { time.sleep(5000000000) } - // Finally, we create the image from the container - // As the tag, we use the epoch value - tag := time.sys_mono_now().str() - image := docker.create_image_from_container(id, 'vieter-build', tag) ? docker.remove_container(id) ? - - return image.id -} - -fn build() ? { - conf := env.load() ? - - // We get the repos list from the Vieter instance - mut req := http.new_request(http.Method.get, '$conf.address/api/repos', '') ? - req.add_custom_header('X-Api-Key', conf.api_key) ? - - res := req.do() ? - repos := json.decode([]server.GitRepo, res.text) ? - - // No point in doing work if there's no repos present - if repos.len == 0 { - return - } - - // First, we create a base image which has updated repos n stuff - image_id := create_build_image() ? - - for repo in repos { - // TODO what to do with PKGBUILDs that build multiple packages? - commands := [ - 'git clone --single-branch --depth 1 --branch $repo.branch $repo.url repo', - 'cd repo', - 'makepkg --nobuild --nodeps', - 'source PKGBUILD', - // The build container checks whether the package is already present on the server - 'curl --head --fail $conf.address/\$pkgname-\$pkgver-\$pkgrel-\$(uname -m).pkg.tar.zst && exit 0', - 'MAKEFLAGS="-j\$(nproc)" makepkg -s --noconfirm --needed && for pkg in \$(ls -1 *.pkg*); do curl -XPOST -T "\$pkg" -H "X-API-KEY: \$API_KEY" $conf.address/publish; done', - ] - - // We convert the list of commands into a base64 string, which then gets - // passed to the container as an env var - cmds_str := base64.encode_str(commands.join('\n')) - - c := docker.NewContainer{ - image: '$image_id' - env: ['BUILD_SCRIPT=$cmds_str', 'API_KEY=$conf.api_key'] - entrypoint: ['/bin/sh', '-c'] - cmd: ['echo \$BUILD_SCRIPT | base64 -d | /bin/bash -e'] - work_dir: '/build' - user: 'builder:builder' - } - - id := docker.create_container(c) ? - docker.start_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 - } - - // Wait for 5 seconds - time.sleep(5000000000) - } - - docker.remove_container(id) ? - } - - // Finally, we remove the builder image - docker.remove_image(image_id) ? } diff --git a/src/docker/containers.v b/src/docker/containers.v index d0f5a4d..a6df345 100644 --- a/src/docker/containers.v +++ b/src/docker/containers.v @@ -20,8 +20,6 @@ pub struct NewContainer { entrypoint []string [json: Entrypoint] cmd []string [json: Cmd] env []string [json: Env] - work_dir string [json: WorkingDir] - user string [json: User] } struct CreatedContainer { diff --git a/src/docker/docker.v b/src/docker/docker.v index a6f7640..e0dbf7d 100644 --- a/src/docker/docker.v +++ b/src/docker/docker.v @@ -91,3 +91,8 @@ pub fn request_with_json(method string, url urllib.URL, data &T) ?http.Respon return request_with_body(method, url, 'application/json', body) } + +// pull_image pulls tries to pull the image for the given image & tag +pub fn pull_image(image string, tag string) ?http.Response { + return request('POST', urllib.parse('/v1.41/images/create?fromImage=$image&tag=$tag') ?) +} diff --git a/src/docker/images.v b/src/docker/images.v deleted file mode 100644 index e94ceca..0000000 --- a/src/docker/images.v +++ /dev/null @@ -1,34 +0,0 @@ -module docker - -import net.http -import net.urllib -import json - -struct Image { -pub: - id string [json: Id] -} - -// pull_image pulls tries to pull the image for the given image & tag -pub fn pull_image(image string, tag string) ?http.Response { - return request('POST', urllib.parse('/v1.41/images/create?fromImage=$image&tag=$tag') ?) -} - -// create_image_from_container creates a new image from a container with the -// given repo & tag, given the container's ID. -pub fn create_image_from_container(id string, repo string, tag string) ?Image { - res := request('POST', urllib.parse('/v1.41/commit?container=$id&repo=$repo&tag=$tag') ?) ? - - if res.status_code != 201 { - return error('Failed to create image from container.') - } - - return json.decode(Image, res.text) or {} -} - -// remove_image removes the image with the given ID. -pub fn remove_image(id string) ?bool { - res := request('DELETE', urllib.parse('/v1.41/images/$id') ?) ? - - return res.status_code == 200 -} diff --git a/src/server/routes.v b/src/server/routes.v index 0090666..7a0ee38 100644 --- a/src/server/routes.v +++ b/src/server/routes.v @@ -6,7 +6,6 @@ import repo import time import rand import util -import net.http // healthcheck just returns a string, but can be used to quickly check if the // server is still responsive. @@ -16,7 +15,7 @@ pub fn (mut app App) healthcheck() web.Result { } // get_root handles a GET request for a file on the root -['/:filename'; get; head] +['/:filename'; get] fn (mut app App) get_root(filename string) web.Result { mut full_path := '' @@ -28,15 +27,6 @@ fn (mut app App) get_root(filename string) web.Result { full_path = os.join_path_single(app.repo.pkg_dir, filename) } - // Scuffed way to respond to HEAD requests - if app.req.method == http.Method.head { - if os.exists(full_path) { - return app.ok('') - } - - return app.not_found() - } - return app.file(full_path) }