Compare commits

..

3 Commits

14 changed files with 81 additions and 116 deletions

View File

@ -17,12 +17,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* CLI commands for searching the AUR & directly adding packages * CLI commands for searching the AUR & directly adding packages
* HTTP routes for removing packages, arch-repos & repos * HTTP routes for removing packages, arch-repos & repos
* All endpoints serving files now support HTTP byte range requests * All endpoints serving files now support HTTP byte range requests
* Better CLI UX
* When adding targets, the ID of the created target is returned
* The `-r` flag only shows raw data of action
* When adding a target, only ID is shown and not surrounding text
* Tabled output returns a tab-separated list (easy to script using
`cut`)
### Changed ### Changed
@ -33,12 +27,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
repository will be cloned with the default branch repository will be cloned with the default branch
* Build containers now explicitely set the PATH variable * Build containers now explicitely set the PATH variable
* Refactor of web framework * Refactor of web framework
* API endpoints now return id of newly created entries
* Repo POST requests now return information on published package
* `api` can no longer be used as a repository name
* CLI client now allows setting values to an empty value
* API endpoints now return id of newly created entries
* Repo POST requests now return information on published package
* `api` can no longer be used as a repository name * `api` can no longer be used as a repository name
* CLI client now allows setting values to an empty value * CLI client now allows setting values to an empty value

View File

@ -39,7 +39,7 @@ pub fn (c &Client) get_build_log_content(id int) ?string {
} }
// add_build_log adds a new build log to the server. // add_build_log adds a new build log to the server.
pub fn (c &Client) add_build_log(target_id int, start_time time.Time, end_time time.Time, arch string, exit_code int, content string) ?Response<int> { pub fn (c &Client) add_build_log(target_id int, start_time time.Time, end_time time.Time, arch string, exit_code int, content string) ?Response<string> {
params := { params := {
'target': target_id.str() 'target': target_id.str()
'startTime': start_time.unix_time().str() 'startTime': start_time.unix_time().str()
@ -48,7 +48,7 @@ pub fn (c &Client) add_build_log(target_id int, start_time time.Time, end_time t
'exitCode': exit_code.str() 'exitCode': exit_code.str()
} }
data := c.send_request_with_body<int>(Method.post, '/api/v1/logs', params, content)? data := c.send_request_with_body<string>(Method.post, '/api/v1/logs', params, content)?
return data return data
} }

View File

@ -49,9 +49,9 @@ pub struct NewTarget {
} }
// add_target adds a new target to the server. // add_target adds a new target to the server.
pub fn (c &Client) add_target(t NewTarget) ?Response<int> { pub fn (c &Client) add_target(t NewTarget) ?Response<string> {
params := models.params_from<NewTarget>(t) params := models.params_from<NewTarget>(t)
data := c.send_request<int>(Method.post, '/api/v1/targets', params)? data := c.send_request<string>(Method.post, '/api/v1/targets', params)?
return data return data
} }

View File

@ -5,11 +5,6 @@ import strings
import cli import cli
import os import os
// tabbed_table returns a simple textual table, with tabs as separators.
pub fn tabbed_table(data [][]string) string {
return data.map(it.join('\t')).join('\n')
}
// pretty_table converts a list of string data into a pretty table. Many thanks // pretty_table converts a list of string data into a pretty table. Many thanks
// to @hungrybluedev in the Vlang Discord for providing this code! // to @hungrybluedev in the Vlang Discord for providing this code!
// https://ptb.discord.com/channels/592103645835821068/592106336838352923/970278787143045192 // https://ptb.discord.com/channels/592103645835821068/592106336838352923/970278787143045192

View File

@ -133,9 +133,7 @@ pub fn cmd() cli.Command {
] ]
} }
raw := cmd.flags.get_bool('raw')? list(conf, filter)?
list(conf, filter, raw)?
} }
}, },
cli.Command{ cli.Command{
@ -169,31 +167,27 @@ pub fn cmd() cli.Command {
} }
// print_log_list prints a list of logs. // print_log_list prints a list of logs.
fn print_log_list(logs []BuildLog, raw bool) ? { fn print_log_list(logs []BuildLog) ? {
data := logs.map([it.id.str(), it.target_id.str(), it.start_time.local().str(), data := logs.map([it.id.str(), it.target_id.str(), it.start_time.local().str(),
it.exit_code.str()]) it.exit_code.str()])
if raw { println(console.pretty_table(['id', 'target', 'start time', 'exit code'], data)?)
println(console.tabbed_table(data))
} else {
println(console.pretty_table(['id', 'target', 'start time', 'exit code'], data)?)
}
} }
// list prints a list of all build logs. // list prints a list of all build logs.
fn list(conf Config, filter BuildLogFilter, raw bool) ? { fn list(conf Config, filter BuildLogFilter) ? {
c := client.new(conf.address, conf.api_key) c := client.new(conf.address, conf.api_key)
logs := c.get_build_logs(filter)?.data logs := c.get_build_logs(filter)?.data
print_log_list(logs, raw)? print_log_list(logs)?
} }
// list prints a list of all build logs for a given target. // list prints a list of all build logs for a given target.
fn list_for_target(conf Config, target_id int, raw bool) ? { fn list_for_target(conf Config, target_id int) ? {
c := client.new(conf.address, conf.api_key) c := client.new(conf.address, conf.api_key)
logs := c.get_build_logs_for_target(target_id)?.data logs := c.get_build_logs_for_target(target_id)?.data
print_log_list(logs, raw)? print_log_list(logs)?
} }
// info print the detailed info for a given build log. // info print the detailed info for a given build log.

View File

@ -60,9 +60,7 @@ pub fn cmd() cli.Command {
filter.repo = repo filter.repo = repo
} }
raw := cmd.flags.get_bool('raw')? list(conf, filter)?
list(conf, filter, raw)?
} }
}, },
cli.Command{ cli.Command{
@ -94,9 +92,7 @@ pub fn cmd() cli.Command {
branch: cmd.flags.get_string('branch') or { '' } branch: cmd.flags.get_string('branch') or { '' }
} }
raw := cmd.flags.get_bool('raw')? add(conf, t)?
add(conf, t, raw)?
} }
}, },
cli.Command{ cli.Command{
@ -197,28 +193,20 @@ pub fn cmd() cli.Command {
// ID. If multiple or none are found, an error is raised. // ID. If multiple or none are found, an error is raised.
// list prints out a list of all repositories. // list prints out a list of all repositories.
fn list(conf Config, filter TargetFilter, raw bool) ? { fn list(conf Config, filter TargetFilter) ? {
c := client.new(conf.address, conf.api_key) c := client.new(conf.address, conf.api_key)
repos := c.get_targets(filter)? repos := c.get_targets(filter)?
data := repos.map([it.id.str(), it.kind, it.url, it.repo]) data := repos.map([it.id.str(), it.kind, it.url, it.repo])
if raw { println(console.pretty_table(['id', 'kind', 'url', 'repo'], data)?)
println(console.tabbed_table(data))
} else {
println(console.pretty_table(['id', 'kind', 'url', 'repo'], data)?)
}
} }
// add adds a new repository to the server's list. // add adds a new repository to the server's list.
fn add(conf Config, t &NewTarget, raw bool) ? { fn add(conf Config, t &NewTarget) ? {
c := client.new(conf.address, conf.api_key) c := client.new(conf.address, conf.api_key)
res := c.add_target(t)? res := c.add_target(t)?
if raw { println(res.message)
println(res.data)
} else {
println('Target added with id $res.data')
}
} }
// remove removes a repository from the server's list. // remove removes a repository from the server's list.

View File

@ -79,14 +79,10 @@ pub fn (db &VieterDb) get_build_log(id int) ?BuildLog {
} }
// add_build_log inserts the given BuildLog into the database. // add_build_log inserts the given BuildLog into the database.
pub fn (db &VieterDb) add_build_log(log BuildLog) int { pub fn (db &VieterDb) add_build_log(log BuildLog) {
sql db.conn { sql db.conn {
insert log into BuildLog insert log into BuildLog
} }
inserted_id := db.conn.last_id() as int
return inserted_id
} }
// delete_build_log delete the BuildLog with the given ID from the database. // delete_build_log delete the BuildLog with the given ID from the database.

View File

@ -38,14 +38,10 @@ pub fn (db &VieterDb) get_target(target_id int) ?Target {
} }
// add_target inserts the given target into the database. // add_target inserts the given target into the database.
pub fn (db &VieterDb) add_target(repo Target) int { pub fn (db &VieterDb) add_target(repo Target) {
sql db.conn { sql db.conn {
insert repo into Target insert repo into Target
} }
inserted_id := db.conn.last_id() as int
return inserted_id
} }
// delete_target deletes the target with the given id from the database. // delete_target deletes the target with the given id from the database.

View File

@ -24,13 +24,6 @@ fn main() {
global: true global: true
default_value: [os.expand_tilde_to_home('~/.vieterrc')] default_value: [os.expand_tilde_to_home('~/.vieterrc')]
}, },
cli.Flag{
flag: cli.FlagType.bool
name: 'raw'
abbrev: 'r'
description: 'Only output minimal information (no formatted tables, etc.)'
global: true
},
] ]
commands: [ commands: [
server.cmd(), server.cmd(),

View File

@ -23,9 +23,8 @@ pub:
pub struct RepoAddResult { pub struct RepoAddResult {
pub: pub:
name string added bool [required]
version string pkg &package.Pkg [required]
archs []string
} }
// new creates a new RepoGroupManager & creates the directories as needed // new creates a new RepoGroupManager & creates the directories as needed
@ -54,10 +53,10 @@ pub fn (r &RepoGroupManager) add_pkg_from_path(repo string, pkg_path string) ?Re
return error('Failed to read package file: $err.msg()') return error('Failed to read package file: $err.msg()')
} }
archs := r.add_pkg_in_repo(repo, pkg)? added := r.add_pkg_in_repo(repo, pkg)?
// If the add was successful, we move the file to the packages directory // If the add was successful, we move the file to the packages directory
for arch in archs { for arch in added {
repo_pkg_path := os.real_path(os.join_path(r.pkg_dir, repo, arch)) repo_pkg_path := os.real_path(os.join_path(r.pkg_dir, repo, arch))
dest_path := os.join_path_single(repo_pkg_path, pkg.filename()) dest_path := os.join_path_single(repo_pkg_path, pkg.filename())
@ -72,9 +71,8 @@ pub fn (r &RepoGroupManager) add_pkg_from_path(repo string, pkg_path string) ?Re
os.rm(pkg_path)? os.rm(pkg_path)?
return RepoAddResult{ return RepoAddResult{
name: pkg.info.name added: added.len > 0
version: pkg.info.version pkg: &pkg
archs: archs
} }
} }
@ -89,9 +87,11 @@ fn (r &RepoGroupManager) add_pkg_in_repo(repo string, pkg &package.Pkg) ?[]strin
// A package not of arch 'any' can be handled easily by adding it to the // A package not of arch 'any' can be handled easily by adding it to the
// respective repo // respective repo
if pkg.info.arch != 'any' { if pkg.info.arch != 'any' {
r.add_pkg_in_arch_repo(repo, pkg.info.arch, pkg)? if r.add_pkg_in_arch_repo(repo, pkg.info.arch, pkg)? {
return [pkg.info.arch]
return [pkg.info.arch] } else {
return []
}
} }
mut arch_repos := []string{} mut arch_repos := []string{}
@ -113,22 +113,25 @@ fn (r &RepoGroupManager) add_pkg_in_repo(repo string, pkg &package.Pkg) ?[]strin
arch_repos << r.default_arch arch_repos << r.default_arch
} }
// Add the package to each found architecture mut added := []string{}
// NOTE: if any of these fail, the function fails. This means the user does
// not know which arch-repositories did succeed in adding the package, if // We add the package to each repository. If any of the repositories
// any. // return true, the result of the function is also true.
for arch in arch_repos { for arch in arch_repos {
r.add_pkg_in_arch_repo(repo, arch, pkg)? if r.add_pkg_in_arch_repo(repo, arch, pkg)? {
added << arch
}
} }
return arch_repos return added
} }
// add_pkg_in_arch_repo is the function that actually adds a package to a given // add_pkg_in_arch_repo is the function that actually adds a package to a given
// arch-repo. It records the package's data in the arch-repo's desc & files // arch-repo. It records the package's data in the arch-repo's desc & files
// files, and afterwards updates the db & files archives to reflect these // files, and afterwards updates the db & files archives to reflect these
// changes. // changes. The function returns false if the package was already present in
fn (r &RepoGroupManager) add_pkg_in_arch_repo(repo string, arch string, pkg &package.Pkg) ? { // the repo, and true otherwise.
fn (r &RepoGroupManager) add_pkg_in_arch_repo(repo string, arch string, pkg &package.Pkg) ?bool {
pkg_dir := os.join_path(r.repos_dir, repo, arch, '$pkg.info.name-$pkg.info.version') pkg_dir := os.join_path(r.repos_dir, repo, arch, '$pkg.info.name-$pkg.info.version')
// Remove the previous version of the package, if present // Remove the previous version of the package, if present
@ -148,4 +151,6 @@ fn (r &RepoGroupManager) add_pkg_in_arch_repo(repo string, arch string, pkg &pac
} }
r.sync(repo, arch)? r.sync(repo, arch)?
return true
} }

View File

@ -19,7 +19,7 @@ fn (mut app App) v1_get_logs() web.Result {
} }
logs := app.db.get_build_logs(filter) logs := app.db.get_build_logs(filter)
return app.json(.ok, new_data_response(logs)) return app.json(http.Status.ok, new_data_response(logs))
} }
// v1_get_single_log returns the build log with the given id. // v1_get_single_log returns the build log with the given id.
@ -27,7 +27,7 @@ fn (mut app App) v1_get_logs() web.Result {
fn (mut app App) v1_get_single_log(id int) web.Result { fn (mut app App) v1_get_single_log(id int) web.Result {
log := app.db.get_build_log(id) or { return app.not_found() } log := app.db.get_build_log(id) or { return app.not_found() }
return app.json(.ok, new_data_response(log)) return app.json(http.Status.ok, new_data_response(log))
} }
// v1_get_log_content returns the actual build log file for the given id. // v1_get_log_content returns the actual build log file for the given id.
@ -95,8 +95,7 @@ fn (mut app App) v1_post_log() web.Result {
exit_code: exit_code exit_code: exit_code
} }
// id of newly created log app.db.add_build_log(log)
log_id := app.db.add_build_log(log)
repo_logs_dir := os.join_path(app.conf.data_dir, logs_dir_name, target_id.str(), arch) repo_logs_dir := os.join_path(app.conf.data_dir, logs_dir_name, target_id.str(), arch)
@ -123,5 +122,5 @@ fn (mut app App) v1_post_log() web.Result {
return app.status(http.Status.length_required) return app.status(http.Status.length_required)
} }
return app.json(.ok, new_data_response(log_id)) return app.json(http.Status.ok, new_response('Logs added successfully.'))
} }

View File

@ -14,7 +14,7 @@ fn (mut app App) v1_get_targets() web.Result {
} }
repos := app.db.get_targets(filter) repos := app.db.get_targets(filter)
return app.json(.ok, new_data_response(repos)) return app.json(http.Status.ok, new_data_response(repos))
} }
// v1_get_single_target returns the information for a single target. // v1_get_single_target returns the information for a single target.
@ -22,7 +22,7 @@ fn (mut app App) v1_get_targets() web.Result {
fn (mut app App) v1_get_single_target(id int) web.Result { fn (mut app App) v1_get_single_target(id int) web.Result {
repo := app.db.get_target(id) or { return app.not_found() } repo := app.db.get_target(id) or { return app.not_found() }
return app.json(.ok, new_data_response(repo)) return app.json(http.Status.ok, new_data_response(repo))
} }
// v1_post_target creates a new target from the provided query string. // v1_post_target creates a new target from the provided query string.
@ -45,9 +45,9 @@ fn (mut app App) v1_post_target() web.Result {
return app.json(http.Status.bad_request, new_response('Invalid kind.')) return app.json(http.Status.bad_request, new_response('Invalid kind.'))
} }
id := app.db.add_target(new_repo) app.db.add_target(new_repo)
return app.json(http.Status.ok, new_data_response(id)) return app.json(http.Status.ok, new_response('Repo added successfully.'))
} }
// v1_delete_target removes a given target from the server's list. // v1_delete_target removes a given target from the server's list.
@ -55,7 +55,7 @@ fn (mut app App) v1_post_target() web.Result {
fn (mut app App) v1_delete_target(id int) web.Result { fn (mut app App) v1_delete_target(id int) web.Result {
app.db.delete_target(id) app.db.delete_target(id)
return app.status(.ok) return app.json(http.Status.ok, new_response('Repo removed successfully.'))
} }
// v1_patch_target updates a target's data with the given query params. // v1_patch_target updates a target's data with the given query params.
@ -69,5 +69,5 @@ fn (mut app App) v1_patch_target(id int) web.Result {
app.db.update_target_archs(id, arch_objs) app.db.update_target_archs(id, arch_objs)
} }
return app.status(.ok) return app.json(http.Status.ok, new_response('Repo updated successfully.'))
} }

View File

@ -6,7 +6,8 @@ import repo
import time import time
import rand import rand
import util import util
import web.response { new_data_response, new_response } import net.http
import web.response { new_response }
// healthcheck just returns a string, but can be used to quickly check if the // healthcheck just returns a string, but can be used to quickly check if the
// server is still responsive. // server is still responsive.
@ -70,7 +71,7 @@ fn (mut app App) put_package(repo string) web.Result {
util.reader_to_file(mut app.reader, length.int(), pkg_path) or { util.reader_to_file(mut app.reader, length.int(), pkg_path) or {
app.lwarn("Failed to upload '$pkg_path'") app.lwarn("Failed to upload '$pkg_path'")
return app.status(.internal_server_error) return app.json(http.Status.internal_server_error, new_response('Failed to upload file.'))
} }
sw.stop() sw.stop()
@ -79,7 +80,7 @@ fn (mut app App) put_package(repo string) web.Result {
app.lwarn('Tried to upload package without specifying a Content-Length.') app.lwarn('Tried to upload package without specifying a Content-Length.')
// length required // length required
return app.status(.length_required) return app.status(http.Status.length_required)
} }
res := app.repo.add_pkg_from_path(repo, pkg_path) or { res := app.repo.add_pkg_from_path(repo, pkg_path) or {
@ -87,10 +88,18 @@ fn (mut app App) put_package(repo string) web.Result {
os.rm(pkg_path) or { app.lerror("Failed to remove download '$pkg_path': $err.msg()") } os.rm(pkg_path) or { app.lerror("Failed to remove download '$pkg_path': $err.msg()") }
return app.status(.internal_server_error) return app.json(http.Status.internal_server_error, new_response('Failed to add package.'))
} }
app.linfo("Added '$res.name-$res.version' to '$repo (${res.archs.join(',')})'.") if !res.added {
os.rm(pkg_path) or { app.lerror("Failed to remove download '$pkg_path': $err.msg()") }
return app.json(.ok, new_data_response(res)) app.lwarn("Duplicate package '$res.pkg.full_name()' in repo '$repo'.")
return app.json(http.Status.bad_request, new_response('File already exists.'))
}
app.linfo("Added '$res.pkg.full_name()' to repo '$repo ($res.pkg.info.arch)'.")
return app.json(http.Status.ok, new_response('Package added successfully.'))
} }

View File

@ -1,6 +1,8 @@
module server module server
import web import web
import net.http
import web.response { new_response }
// delete_package tries to remove the given package. // delete_package tries to remove the given package.
['/:repo/:arch/:pkg'; auth; delete] ['/:repo/:arch/:pkg'; auth; delete]
@ -8,17 +10,17 @@ fn (mut app App) delete_package(repo string, arch string, pkg string) web.Result
res := app.repo.remove_pkg_from_arch_repo(repo, arch, pkg, true) or { res := app.repo.remove_pkg_from_arch_repo(repo, arch, pkg, true) or {
app.lerror('Error while deleting package: $err.msg()') app.lerror('Error while deleting package: $err.msg()')
return app.status(.internal_server_error) return app.json(http.Status.internal_server_error, new_response('Failed to delete package.'))
} }
if res { if res {
app.linfo("Removed package '$pkg' from '$repo/$arch'") app.linfo("Removed package '$pkg' from '$repo/$arch'")
return app.status(.ok) return app.json(http.Status.ok, new_response('Package removed.'))
} else { } else {
app.linfo("Tried removing package '$pkg' from '$repo/$arch', but it doesn't exist.") app.linfo("Tried removing package '$pkg' from '$repo/$arch', but it doesn't exist.")
return app.status(.not_found) return app.json(http.Status.not_found, new_response('Package not found.'))
} }
} }
@ -28,17 +30,17 @@ fn (mut app App) delete_arch_repo(repo string, arch string) web.Result {
res := app.repo.remove_arch_repo(repo, arch) or { res := app.repo.remove_arch_repo(repo, arch) or {
app.lerror('Error while deleting arch-repo: $err.msg()') app.lerror('Error while deleting arch-repo: $err.msg()')
return app.status(.internal_server_error) return app.json(http.Status.internal_server_error, new_response('Failed to delete arch-repo.'))
} }
if res { if res {
app.linfo("Removed arch-repo '$repo/$arch'") app.linfo("Removed '$repo/$arch'")
return app.status(.ok) return app.json(http.Status.ok, new_response('Arch-repo removed.'))
} else { } else {
app.linfo("Tried removing '$repo/$arch', but it doesn't exist.") app.linfo("Tried removing '$repo/$arch', but it doesn't exist.")
return app.status(.not_found) return app.json(http.Status.not_found, new_response('Arch-repo not found.'))
} }
} }
@ -48,16 +50,16 @@ fn (mut app App) delete_repo(repo string) web.Result {
res := app.repo.remove_repo(repo) or { res := app.repo.remove_repo(repo) or {
app.lerror('Error while deleting repo: $err.msg()') app.lerror('Error while deleting repo: $err.msg()')
return app.status(.internal_server_error) return app.json(http.Status.internal_server_error, new_response('Failed to delete repo.'))
} }
if res { if res {
app.linfo("Removed repo '$repo'") app.linfo("Removed '$repo'")
return app.status(.ok) return app.json(http.Status.ok, new_response('Repo removed.'))
} else { } else {
app.linfo("Tried removing '$repo', but it doesn't exist.") app.linfo("Tried removing '$repo', but it doesn't exist.")
return app.status(.not_found) return app.json(http.Status.not_found, new_response('Repo not found.'))
} }
} }