forked from vieter-v/vieter
				
			Merge pull request 'Search targets using API' (#332) from Chewing_Bever/vieter:search-targets into dev
Reviewed-on: vieter-v/vieter#332remotes/1761201518397485255/dev
						commit
						b5ff50066b
					
				|  | @ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | ||||||
| 
 | 
 | ||||||
| ## [Unreleased](https://git.rustybever.be/vieter-v/vieter/src/branch/dev) | ## [Unreleased](https://git.rustybever.be/vieter-v/vieter/src/branch/dev) | ||||||
| 
 | 
 | ||||||
|  | ### Added | ||||||
|  | 
 | ||||||
| * Metrics endpoint for Prometheus integration | * Metrics endpoint for Prometheus integration | ||||||
|  | * Search in list of targets using API & CLI | ||||||
|  | * Allow filtering targets by arch value | ||||||
| 
 | 
 | ||||||
| ## [0.5.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.5.0) | ## [0.5.0](https://git.rustybever.be/vieter-v/vieter/src/tag/0.5.0) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -55,6 +55,8 @@ Parameter | Description | ||||||
| limit | Maximum amount of results to return. | limit | Maximum amount of results to return. | ||||||
| offset | Offset of results. | offset | Offset of results. | ||||||
| repo | Limit results to targets that publish to the given repo. | repo | Limit results to targets that publish to the given repo. | ||||||
|  | query | Only return targets that have this substring in their URL, path or branch. | ||||||
|  | arch | Only return targets that publish to this arch. | ||||||
| 
 | 
 | ||||||
| ## Get specific target | ## Get specific target | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,6 +40,17 @@ pub fn cmd() cli.Command { | ||||||
| 						description: 'Only return targets that publish to this repo.' | 						description: 'Only return targets that publish to this repo.' | ||||||
| 						flag: cli.FlagType.string | 						flag: cli.FlagType.string | ||||||
| 					}, | 					}, | ||||||
|  | 					cli.Flag{ | ||||||
|  | 						name: 'query' | ||||||
|  | 						abbrev: 'q' | ||||||
|  | 						description: 'Search string to filter targets by.' | ||||||
|  | 						flag: cli.FlagType.string | ||||||
|  | 					}, | ||||||
|  | 					cli.Flag{ | ||||||
|  | 						name: 'arch' | ||||||
|  | 						description: 'Only list targets that build for this arch.' | ||||||
|  | 						flag: cli.FlagType.string | ||||||
|  | 					}, | ||||||
| 				] | 				] | ||||||
| 				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')! | ||||||
|  | @ -62,6 +73,16 @@ pub fn cmd() cli.Command { | ||||||
| 						filter.repo = repo | 						filter.repo = repo | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
|  | 					query := cmd.flags.get_string('query')! | ||||||
|  | 					if query != '' { | ||||||
|  | 						filter.query = query | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					arch := cmd.flags.get_string('arch')! | ||||||
|  | 					if arch != '' { | ||||||
|  | 						filter.arch = arch | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
| 					raw := cmd.flags.get_bool('raw')! | 					raw := cmd.flags.get_bool('raw')! | ||||||
| 
 | 
 | ||||||
| 					list(conf, filter, raw)! | 					list(conf, filter, raw)! | ||||||
|  |  | ||||||
|  | @ -1,25 +1,6 @@ | ||||||
| module db | module db | ||||||
| 
 | 
 | ||||||
| import models { Target, TargetArch, TargetFilter } | import models { Target, TargetArch } | ||||||
| 
 |  | ||||||
| // get_targets returns all targets in the database. |  | ||||||
| pub fn (db &VieterDb) get_targets(filter TargetFilter) []Target { |  | ||||||
| 	// This seems to currently be blocked by a bug in the ORM, I'll have to ask |  | ||||||
| 	// around. |  | ||||||
| 	if filter.repo != '' { |  | ||||||
| 		res := sql db.conn { |  | ||||||
| 			select from Target where repo == filter.repo order by id limit filter.limit offset filter.offset |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return res |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	res := sql db.conn { |  | ||||||
| 		select from Target order by id limit filter.limit offset filter.offset |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return res |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| // get_target tries to return a specific target. | // get_target tries to return a specific target. | ||||||
| pub fn (db &VieterDb) get_target(target_id int) ?Target { | pub fn (db &VieterDb) get_target(target_id int) ?Target { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,129 @@ | ||||||
|  | module db | ||||||
|  | 
 | ||||||
|  | import models { Target, TargetFilter } | ||||||
|  | import sqlite | ||||||
|  | 
 | ||||||
|  | // Iterator providing a filtered view into the list of targets currently stored | ||||||
|  | // in the database. It replaces functionality usually performed in the database | ||||||
|  | // using SQL queries that can't currently be used due to missing stuff in V's | ||||||
|  | // ORM. | ||||||
|  | pub struct TargetsIterator { | ||||||
|  | 	conn        sqlite.DB | ||||||
|  | 	filter      TargetFilter | ||||||
|  | 	window_size int = 32 | ||||||
|  | mut: | ||||||
|  | 	window       []Target | ||||||
|  | 	window_index u64 | ||||||
|  | 	// Offset in entire list of unfiltered targets | ||||||
|  | 	offset int | ||||||
|  | 	// Offset in filtered list of targets | ||||||
|  | 	filtered_offset u64 | ||||||
|  | 	started         bool | ||||||
|  | 	done            bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // targets returns an iterator allowing filtered access to the list of targets. | ||||||
|  | pub fn (db &VieterDb) targets(filter TargetFilter) TargetsIterator { | ||||||
|  | 	window_size := 32 | ||||||
|  | 
 | ||||||
|  | 	return TargetsIterator{ | ||||||
|  | 		conn: db.conn | ||||||
|  | 		filter: filter | ||||||
|  | 		window: []Target{cap: window_size} | ||||||
|  | 		window_size: window_size | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // advance_window moves the sliding window over the filtered list of targets | ||||||
|  | // until it either reaches the end of the list of targets, or has encountered a | ||||||
|  | // non-empty window. | ||||||
|  | fn (mut ti TargetsIterator) advance_window() { | ||||||
|  | 	for { | ||||||
|  | 		ti.window = sql ti.conn { | ||||||
|  | 			select from Target order by id limit ti.window_size offset ti.offset | ||||||
|  | 		} | ||||||
|  | 		ti.offset += ti.window.len | ||||||
|  | 
 | ||||||
|  | 		if ti.window.len == 0 { | ||||||
|  | 			ti.done = true | ||||||
|  | 
 | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ti.filter.repo != '' { | ||||||
|  | 			ti.window = ti.window.filter(it.repo == ti.filter.repo) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ti.filter.arch != '' { | ||||||
|  | 			ti.window = ti.window.filter(it.arch.any(it.value == ti.filter.arch)) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ti.filter.query != '' { | ||||||
|  | 			ti.window = ti.window.filter(it.url.contains(ti.filter.query) | ||||||
|  | 				|| it.path.contains(ti.filter.query) || it.branch.contains(ti.filter.query)) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// We break out of the loop once we found a non-empty window | ||||||
|  | 		if ti.window.len > 0 { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // next returns the next target, if possible. | ||||||
|  | pub fn (mut ti TargetsIterator) next() ?Target { | ||||||
|  | 	if ti.done { | ||||||
|  | 		return none | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// The first call to `next` will cause the sliding window to move to where | ||||||
|  | 	// the requested offset starts | ||||||
|  | 	if !ti.started { | ||||||
|  | 		ti.advance_window() | ||||||
|  | 
 | ||||||
|  | 		// Skip all matched targets until the requested offset | ||||||
|  | 		for !ti.done && ti.filtered_offset + u64(ti.window.len) <= ti.filter.offset { | ||||||
|  | 			ti.filtered_offset += u64(ti.window.len) | ||||||
|  | 			ti.advance_window() | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if ti.done { | ||||||
|  | 			return none | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		left_inside_window := ti.filter.offset - ti.filtered_offset | ||||||
|  | 		ti.window_index = left_inside_window | ||||||
|  | 		ti.filtered_offset += left_inside_window | ||||||
|  | 
 | ||||||
|  | 		ti.started = true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return_value := ti.window[ti.window_index] | ||||||
|  | 
 | ||||||
|  | 	ti.window_index++ | ||||||
|  | 	ti.filtered_offset++ | ||||||
|  | 
 | ||||||
|  | 	// Next call will be past the requested offset | ||||||
|  | 	if ti.filter.limit > 0 && ti.filtered_offset == ti.filter.offset + ti.filter.limit { | ||||||
|  | 		ti.done = true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Ensure the next call has a new valid window | ||||||
|  | 	if ti.window_index == u64(ti.window.len) { | ||||||
|  | 		ti.advance_window() | ||||||
|  | 		ti.window_index = 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return return_value | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // collect consumes the entire iterator & returns the result as an array. | ||||||
|  | pub fn (mut ti TargetsIterator) collect() []Target { | ||||||
|  | 	mut out := []Target{} | ||||||
|  | 
 | ||||||
|  | 	for t in ti { | ||||||
|  | 		out << t | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | @ -73,4 +73,6 @@ pub mut: | ||||||
| 	limit  u64 = 25 | 	limit  u64 = 25 | ||||||
| 	offset u64 | 	offset u64 | ||||||
| 	repo   string | 	repo   string | ||||||
|  | 	query  string | ||||||
|  | 	arch   string | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,9 +11,9 @@ fn (mut app App) v1_get_targets() web.Result { | ||||||
| 	filter := models.from_params<TargetFilter>(app.query) or { | 	filter := models.from_params<TargetFilter>(app.query) or { | ||||||
| 		return app.json(.bad_request, new_response('Invalid query parameters.')) | 		return app.json(.bad_request, new_response('Invalid query parameters.')) | ||||||
| 	} | 	} | ||||||
| 	targets := app.db.get_targets(filter) | 	mut iter := app.db.targets(filter) | ||||||
| 
 | 
 | ||||||
| 	return app.json(.ok, new_data_response(targets)) | 	return app.json(.ok, new_data_response(iter.collect())) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // v1_get_single_target returns the information for a single target. | // v1_get_single_target returns the information for a single target. | ||||||
|  |  | ||||||
|  | @ -31,18 +31,9 @@ pub mut: | ||||||
| // init_job_queue populates a fresh job queue with all the targets currently | // init_job_queue populates a fresh job queue with all the targets currently | ||||||
| // stored in the database. | // stored in the database. | ||||||
| fn (mut app App) init_job_queue() ! { | fn (mut app App) init_job_queue() ! { | ||||||
| 	// Initialize build queues | 	for target in app.db.targets(limit: 0) { | ||||||
| 	mut targets := app.db.get_targets(limit: 25) |  | ||||||
| 	mut i := u64(0) |  | ||||||
| 
 |  | ||||||
| 	for targets.len > 0 { |  | ||||||
| 		for target in targets { |  | ||||||
| 		app.job_queue.insert_all(target)! | 		app.job_queue.insert_all(target)! | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 		i += 25 |  | ||||||
| 		targets = app.db.get_targets(limit: 25, offset: i) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // server starts the web server & starts listening for requests | // server starts the web server & starts listening for requests | ||||||
|  |  | ||||||
|  | @ -12,5 +12,5 @@ address = "http://localhost:8000" | ||||||
| api_update_frequency = 2 | api_update_frequency = 2 | ||||||
| image_rebuild_frequency = 1 | image_rebuild_frequency = 1 | ||||||
| max_concurrent_builds = 3 | max_concurrent_builds = 3 | ||||||
| max_log_age = 64 | # max_log_age = 64 | ||||||
| collect_metrics = true | collect_metrics = true | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue