forked from vieter-v/vieter
				
			Initial part of repos API (SEGFAULTS) [CI SKIP]
							parent
							
								
									92ad0c51eb
								
							
						
					
					
						commit
						e13252d368
					
				|  | @ -18,3 +18,6 @@ test/ | ||||||
| 
 | 
 | ||||||
| # V compiler directory | # V compiler directory | ||||||
| v/ | v/ | ||||||
|  | 
 | ||||||
|  | # gdb log file | ||||||
|  | gdb.txt | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										13
									
								
								Makefile
								
								
								
								
							|  | @ -16,7 +16,17 @@ vieter: $(SOURCES) | ||||||
| .PHONY: debug | .PHONY: debug | ||||||
| debug: dvieter | debug: dvieter | ||||||
| dvieter: $(SOURCES) | dvieter: $(SOURCES) | ||||||
| 	$(V) -keepc -cg -cc gcc -o dvieter $(SRC_DIR) | 	$(V_PATH) -showcc -keepc -cg -o dvieter $(SRC_DIR) | ||||||
|  | 
 | ||||||
|  | .PHONY: gdb | ||||||
|  | gdb: dvieter | ||||||
|  | 	 VIETER_API_KEY=test \
 | ||||||
|  | 		VIETER_DOWNLOAD_DIR=data/downloads \
 | ||||||
|  | 		VIETER_REPO_DIR=data/repo \
 | ||||||
|  | 		VIETER_PKG_DIR=data/pkgs \
 | ||||||
|  | 		VIETER_LOG_LEVEL=DEBUG \
 | ||||||
|  | 		VIETER_REPOS_FILE=data/repos.json \
 | ||||||
|  | 		gdb --args ./dvieter | ||||||
| 
 | 
 | ||||||
| # Optimised production build
 | # Optimised production build
 | ||||||
| .PHONY: prod | .PHONY: prod | ||||||
|  | @ -39,6 +49,7 @@ run: vieter | ||||||
| 		VIETER_REPO_DIR=data/repo \
 | 		VIETER_REPO_DIR=data/repo \
 | ||||||
| 		VIETER_PKG_DIR=data/pkgs \
 | 		VIETER_PKG_DIR=data/pkgs \
 | ||||||
| 		VIETER_LOG_LEVEL=DEBUG \
 | 		VIETER_LOG_LEVEL=DEBUG \
 | ||||||
|  | 		VIETER_REPOS_FILE=data/repos.json \
 | ||||||
| 		./vieter server | 		./vieter server | ||||||
| 
 | 
 | ||||||
| .PHONY: run-prod | .PHONY: run-prod | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ import rand | ||||||
| import time | import time | ||||||
| import os | import os | ||||||
| import json | import json | ||||||
| import git | import server | ||||||
| import env | import env | ||||||
| 
 | 
 | ||||||
| const container_build_dir = '/build' | const container_build_dir = '/build' | ||||||
|  | @ -17,7 +17,7 @@ fn build() ? { | ||||||
| 	// Read in the repos from a json file | 	// Read in the repos from a json file | ||||||
| 	filename := os.join_path_single(conf.repo_dir, 'repos.json') | 	filename := os.join_path_single(conf.repo_dir, 'repos.json') | ||||||
| 	txt := os.read_file(filename) ? | 	txt := os.read_file(filename) ? | ||||||
| 	repos := json.decode([]git.GitRepo, txt) ? | 	repos := json.decode([]server.GitRepo, txt) ? | ||||||
| 
 | 
 | ||||||
| 	mut commands := [ | 	mut commands := [ | ||||||
| 		// Update repos & install required packages | 		// Update repos & install required packages | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ pub: | ||||||
| 	download_dir string | 	download_dir string | ||||||
| 	api_key      string | 	api_key      string | ||||||
| 	repo_dir     string | 	repo_dir     string | ||||||
|  | 	repos_file string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub struct BuildConfig { | pub struct BuildConfig { | ||||||
|  | @ -42,7 +43,10 @@ fn get_env_var(field_name string) ?string { | ||||||
| 		return error('Only one of $env_var_name or $env_file_name can be defined.') | 		return error('Only one of $env_var_name or $env_file_name can be defined.') | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// If it's the env var itself, we return it | 	// If it's the env var itself, we return it. | ||||||
|  | 	// I'm pretty sure this also prevents variable ending in _FILE (e.g. | ||||||
|  | 	// VIETER_LOG_FILE) from being mistakingely read as an _FILE suffixed env | ||||||
|  | 	// var. | ||||||
| 	if env_var != '' { | 	if env_var != '' { | ||||||
| 		return env_var | 		return env_var | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -1,7 +0,0 @@ | ||||||
| module git |  | ||||||
| 
 |  | ||||||
| pub struct GitRepo { |  | ||||||
| pub: |  | ||||||
| 	url    string [required] |  | ||||||
| 	branch string [required] |  | ||||||
| } |  | ||||||
|  | @ -2,17 +2,12 @@ module repo | ||||||
| 
 | 
 | ||||||
| import os | import os | ||||||
| import package | import package | ||||||
| 
 | import util | ||||||
| // Dummy struct to work around the fact that you can only share structs, maps & |  | ||||||
| // arrays |  | ||||||
| pub struct Dummy { |  | ||||||
| 	x int |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| // This struct manages a single repository. | // This struct manages a single repository. | ||||||
| pub struct Repo { | pub struct Repo { | ||||||
| mut: | mut: | ||||||
| 	mutex shared Dummy | 	mutex shared util.Dummy | ||||||
| pub: | pub: | ||||||
| 	// Where to store repository files | 	// Where to store repository files | ||||||
| 	repo_dir string [required] | 	repo_dir string [required] | ||||||
|  |  | ||||||
|  | @ -0,0 +1,100 @@ | ||||||
|  | module server | ||||||
|  | 
 | ||||||
|  | import web | ||||||
|  | import os | ||||||
|  | import json | ||||||
|  | 
 | ||||||
|  | const repos_file = 'repos.json' | ||||||
|  | 
 | ||||||
|  | pub struct GitRepo { | ||||||
|  | pub: | ||||||
|  | 	url    string [required] | ||||||
|  | 	branch string [required] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn read_repos(path string) ?[]GitRepo { | ||||||
|  | 	if !os.exists(path) { | ||||||
|  | 		mut f := os.create(path) ? | ||||||
|  | 
 | ||||||
|  | 		defer { | ||||||
|  | 			f.close() | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		f.write_string('{}') ? | ||||||
|  | 
 | ||||||
|  | 		return [] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	content := os.read_file(path) ? | ||||||
|  | 	return json.decode([]GitRepo, content) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn write_repos(path string, repos []GitRepo) ? { | ||||||
|  | 	mut f := os.create(path) ? | ||||||
|  | 
 | ||||||
|  | 	defer { | ||||||
|  | 		f.close() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	dump(repos) | ||||||
|  | 	value := json.encode(repos) | ||||||
|  | 	f.write_string(value) ? | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ['/api/repos'; get] | ||||||
|  | pub fn (mut app App) get_repos() web.Result { | ||||||
|  | 	if !app.is_authorized() { | ||||||
|  | 		return app.text('Unauthorized.') | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	repos := rlock app.git_mutex { | ||||||
|  | 		read_repos(app.conf.repos_file) or { | ||||||
|  | 			app.lerror('Failed to read repos file.') | ||||||
|  | 
 | ||||||
|  | 			return app.server_error(500) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return app.json(repos) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ['/api/repos'; post] | ||||||
|  | pub fn (mut app App) post_repo() web.Result { | ||||||
|  | 	if !app.is_authorized() { | ||||||
|  | 		return app.text('Unauthorized.') | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if !('url' in app.query && 'branch' in app.query) { | ||||||
|  | 		return app.server_error(400) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	new_repo := GitRepo{ | ||||||
|  | 		url: app.query['url'] | ||||||
|  | 		branch: app.query['branch'] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mut repos := rlock app.git_mutex { | ||||||
|  | 		read_repos(app.conf.repos_file) or { | ||||||
|  | 			app.lerror('Failed to read repos file.') | ||||||
|  | 
 | ||||||
|  | 			return app.server_error(500) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// We need to check for duplicates | ||||||
|  | 	for r in repos { | ||||||
|  | 		if r == new_repo { | ||||||
|  | 			return app.text('Duplicate repository.') | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	repos << new_repo | ||||||
|  | 
 | ||||||
|  | 	lock app.git_mutex { | ||||||
|  | 		write_repos(app.conf.repos_file, repos) or { | ||||||
|  | 			return app.server_error(500) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return app.ok('Repo added successfully.') | ||||||
|  | } | ||||||
|  | @ -7,25 +7,6 @@ import time | ||||||
| import rand | import rand | ||||||
| import util | import util | ||||||
| 
 | 
 | ||||||
| const prefixes = ['B', 'KB', 'MB', 'GB'] |  | ||||||
| 
 |  | ||||||
| // pretty_bytes converts a byte count to human-readable version |  | ||||||
| fn pretty_bytes(bytes int) string { |  | ||||||
| 	mut i := 0 |  | ||||||
| 	mut n := f32(bytes) |  | ||||||
| 
 |  | ||||||
| 	for n >= 1024 { |  | ||||||
| 		i++ |  | ||||||
| 		n /= 1024 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return '${n:.2}${server.prefixes[i]}' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn is_pkg_name(s string) bool { |  | ||||||
| 	return s.contains('.pkg') |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // 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. | ||||||
| ['/health'; get] | ['/health'; get] | ||||||
|  | @ -65,7 +46,7 @@ fn (mut app App) put_package() web.Result { | ||||||
| 			pkg_path = os.join_path_single(app.conf.download_dir, rand.uuid_v4()) | 			pkg_path = os.join_path_single(app.conf.download_dir, rand.uuid_v4()) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		app.ldebug("Uploading $length bytes (${pretty_bytes(length.int())}) to '$pkg_path'.") | 		app.ldebug("Uploading $length bytes (${util.pretty_bytes(length.int())}) to '$pkg_path'.") | ||||||
| 
 | 
 | ||||||
| 		// This is used to time how long it takes to upload a file | 		// This is used to time how long it takes to upload a file | ||||||
| 		mut sw := time.new_stopwatch(time.StopWatchOptions{ auto_start: true }) | 		mut sw := time.new_stopwatch(time.StopWatchOptions{ auto_start: true }) | ||||||
|  |  | ||||||
|  | @ -12,9 +12,11 @@ const port = 8000 | ||||||
| struct App { | struct App { | ||||||
| 	web.Context | 	web.Context | ||||||
| pub: | pub: | ||||||
| 	conf env.ServerConfig [required: web_global] | 	conf env.ServerConfig [required; web_global] | ||||||
| pub mut: | pub mut: | ||||||
| 	repo repo.Repo [required; web_global] | 	repo repo.Repo [required; web_global] | ||||||
|  | 	// This is used to claim the file lock on the repos file | ||||||
|  | 	git_mutex shared util.Dummy | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn server() ? { | pub fn server() ? { | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								src/util.v
								
								
								
								
							
							
						
						
									
										22
									
								
								src/util.v
								
								
								
								
							|  | @ -7,6 +7,14 @@ import crypto.sha256 | ||||||
| 
 | 
 | ||||||
| const reader_buf_size = 1_000_000 | const reader_buf_size = 1_000_000 | ||||||
| 
 | 
 | ||||||
|  | const prefixes = ['B', 'KB', 'MB', 'GB'] | ||||||
|  | 
 | ||||||
|  | // Dummy struct to work around the fact that you can only share structs, maps & | ||||||
|  | // arrays | ||||||
|  | pub struct Dummy { | ||||||
|  | 	x int | ||||||
|  | } | ||||||
|  | 
 | ||||||
| [noreturn] | [noreturn] | ||||||
| pub fn exit_with_message(code int, msg string) { | pub fn exit_with_message(code int, msg string) { | ||||||
| 	eprintln(msg) | 	eprintln(msg) | ||||||
|  | @ -67,3 +75,17 @@ pub fn hash_file(path &string) ?(string, string) { | ||||||
| 
 | 
 | ||||||
| 	return md5sum.checksum().hex(), sha256sum.checksum().hex() | 	return md5sum.checksum().hex(), sha256sum.checksum().hex() | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // pretty_bytes converts a byte count to human-readable version | ||||||
|  | pub fn pretty_bytes(bytes int) string { | ||||||
|  | 	mut i := 0 | ||||||
|  | 	mut n := f32(bytes) | ||||||
|  | 
 | ||||||
|  | 	for n >= 1024 { | ||||||
|  | 		i++ | ||||||
|  | 		n /= 1024 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return '${n:.2}${util.prefixes[i]}' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue