feat: initial daemon implementation
This commit is contained in:
		
							
								
								
									
										123
									
								
								daemon/db/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								daemon/db/db.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| package db | ||||
|  | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
|  | ||||
| 	_ "github.com/mattn/go-sqlite3" | ||||
| ) | ||||
|  | ||||
| // Change on ANY database update | ||||
| const currentDatabaseVersion = 1 | ||||
|  | ||||
| const versionField = "db_version" | ||||
|  | ||||
| func createMetadataTable(db *sql.DB) (err error) { | ||||
| 	_, err = db.Exec(` | ||||
| 	CREATE TABLE IF NOT EXISTS metadata ( | ||||
| 		id	INTEGER PRIMARY KEY, | ||||
| 		key	TEXT UNIQUE, | ||||
| 		value	TEXT | ||||
| 	)`) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func metaChkValue(db *sql.DB, key string) (exist bool, err error) { | ||||
| 	sql := "SELECT EXISTS(SELECT id FROM metadata WHERE key = $1)" | ||||
| 	stmt, err := db.Prepare(sql) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer stmt.Close() | ||||
|  | ||||
| 	err = stmt.QueryRow(key).Scan(&exist) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func metaGetValue(db *sql.DB, key string) (value string, err error) { | ||||
| 	stmt, err := db.Prepare("SELECT value FROM metadata " + | ||||
| 		"WHERE key = $1") | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer stmt.Close() | ||||
|  | ||||
| 	err = stmt.QueryRow(key).Scan(&value) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func metaSetValue(db *sql.DB, key, value string) (err error) { | ||||
| 	stmt, err := db.Prepare("INSERT OR REPLACE INTO metadata " + | ||||
| 		"(key, value) VALUES ($1, $2)") | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer stmt.Close() | ||||
|  | ||||
| 	_, err = stmt.Exec(key, value) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func getVersion(db *sql.DB) (version int, err error) { | ||||
| 	s, err := metaGetValue(db, versionField) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	version, err = strconv.Atoi(s) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func createSchema(db *sql.DB) (err error) { | ||||
| 	err = createMetadataTable(db) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = createJobTable(db) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = createRepoTable(db) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func OpenDatabase(path string) (db *sql.DB, err error) { | ||||
| 	db, err = sql.Open("sqlite3", path) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	db.SetMaxOpenConns(1) | ||||
|  | ||||
| 	exists, _ := metaChkValue(db, versionField) | ||||
| 	if !exists { | ||||
| 		err = createSchema(db) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		err = metaSetValue(db, versionField, | ||||
| 			strconv.Itoa(currentDatabaseVersion)) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	version, err := getVersion(db) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if version != currentDatabaseVersion { | ||||
| 		err = fmt.Errorf("database is not supported (%d instead of %d)", | ||||
| 			version, currentDatabaseVersion) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										22
									
								
								daemon/db/db_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								daemon/db/db_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| package db | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestOpenDatabase(t *testing.T) { | ||||
| 	file, err := os.CreateTemp("", "temp-sqlite.db") | ||||
| 	assert.Nil(t, err) | ||||
| 	defer os.Remove(file.Name()) | ||||
|  | ||||
| 	db, err := OpenDatabase(file.Name()) | ||||
| 	assert.Nil(t, err) | ||||
| 	db.Close() | ||||
|  | ||||
| 	db, err = OpenDatabase(file.Name()) | ||||
| 	assert.Nil(t, err) | ||||
| 	db.Close() | ||||
| } | ||||
							
								
								
									
										136
									
								
								daemon/db/job.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								daemon/db/job.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| package db | ||||
|  | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"encoding/json" | ||||
|  | ||||
| 	"code.dumpstack.io/tools/out-of-tree/api" | ||||
| ) | ||||
|  | ||||
| func createJobTable(db *sql.DB) (err error) { | ||||
| 	_, err = db.Exec(` | ||||
| 	CREATE TABLE IF NOT EXISTS job ( | ||||
| 		id		INTEGER PRIMARY KEY, | ||||
| 		uuid		TEXT, | ||||
| 		repo		TEXT, | ||||
| 		"commit"	TEXT, | ||||
| 		params		TEXT, | ||||
| 		config		TEXT, | ||||
| 		target		TEXT, | ||||
| 		status		TEXT DEFAULT "new" | ||||
| 	)`) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func AddJob(db *sql.DB, job *api.Job) (err error) { | ||||
| 	stmt, err := db.Prepare(`INSERT INTO job (uuid, repo, "commit", params, config, target) ` + | ||||
| 		`VALUES ($1, $2, $3, $4, $5, $6);`) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	defer stmt.Close() | ||||
|  | ||||
| 	config := api.Marshal(job.Artifact) | ||||
| 	target := api.Marshal(job.Target) | ||||
|  | ||||
| 	res, err := stmt.Exec(job.UUID, job.RepoName, job.Commit, job.Params, | ||||
| 		config, target, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	job.ID, err = res.LastInsertId() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func UpdateJob(db *sql.DB, job api.Job) (err error) { | ||||
| 	stmt, err := db.Prepare(`UPDATE job SET uuid=$1, repo=$2, "commit"=$3, params=$4, ` + | ||||
| 		`config=$5, target=$6, status=$7 WHERE id=$8`) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer stmt.Close() | ||||
|  | ||||
| 	config := api.Marshal(job.Artifact) | ||||
| 	target := api.Marshal(job.Target) | ||||
|  | ||||
| 	_, err = stmt.Exec(job.UUID, job.RepoName, job.Commit, job.Params, | ||||
| 		config, target, | ||||
| 		job.Status, job.ID) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func Jobs(db *sql.DB) (jobs []api.Job, err error) { | ||||
| 	stmt, err := db.Prepare(`SELECT id, uuid, repo, "commit", params, config, target, status FROM job`) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	defer stmt.Close() | ||||
|  | ||||
| 	rows, err := stmt.Query() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
|  | ||||
| 	for rows.Next() { | ||||
| 		var job api.Job | ||||
| 		var config, target []byte | ||||
| 		err = rows.Scan(&job.ID, &job.UUID, &job.RepoName, &job.Commit, &job.Params, &config, &target, &job.Status) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		err = json.Unmarshal(config, &job.Artifact) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		err = json.Unmarshal(target, &job.Target) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		jobs = append(jobs, job) | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func Job(db *sql.DB, uuid string) (job api.Job, err error) { | ||||
| 	stmt, err := db.Prepare(`SELECT id, uuid, repo, "commit", ` + | ||||
| 		`params, config, target, status ` + | ||||
| 		`FROM job WHERE uuid=$1`) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer stmt.Close() | ||||
|  | ||||
| 	err = stmt.QueryRow(uuid).Scan(&job.ID, &job.UUID, | ||||
| 		&job.RepoName, &job.Commit, &job.Params, | ||||
| 		&job.Artifact, &job.Target, &job.Status) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func JobStatus(db *sql.DB, uuid string) (st api.Status, err error) { | ||||
| 	stmt, err := db.Prepare(`SELECT status FROM job ` + | ||||
| 		`WHERE uuid=$1`) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer stmt.Close() | ||||
|  | ||||
| 	err = stmt.QueryRow(uuid).Scan(&st) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										55
									
								
								daemon/db/job_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								daemon/db/job_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| package db | ||||
|  | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"os" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	"code.dumpstack.io/tools/out-of-tree/api" | ||||
| ) | ||||
|  | ||||
| func testCreateJobTable(t *testing.T) (file *os.File, db *sql.DB) { | ||||
| 	file, err := os.CreateTemp("", "temp-sqlite.db") | ||||
| 	assert.Nil(t, err) | ||||
| 	// defer os.Remove(file.Name()) | ||||
|  | ||||
| 	db, err = sql.Open("sqlite3", file.Name()) | ||||
| 	assert.Nil(t, err) | ||||
| 	// defer db.Close() | ||||
|  | ||||
| 	db.SetMaxOpenConns(1) | ||||
|  | ||||
| 	err = createJobTable(db) | ||||
| 	assert.Nil(t, err) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func TestJobTable(t *testing.T) { | ||||
| 	file, db := testCreateJobTable(t) | ||||
| 	defer db.Close() | ||||
| 	defer os.Remove(file.Name()) | ||||
|  | ||||
| 	job := api.Job{ | ||||
| 		RepoName: "testname", | ||||
| 		Commit:   "test", | ||||
| 		Params:   "none", | ||||
| 	} | ||||
|  | ||||
| 	err := AddJob(db, &job) | ||||
| 	assert.Nil(t, err) | ||||
|  | ||||
| 	job.Params = "changed" | ||||
|  | ||||
| 	err = UpdateJob(db, job) | ||||
| 	assert.Nil(t, err) | ||||
|  | ||||
| 	jobs, err := Jobs(db) | ||||
| 	assert.Nil(t, err) | ||||
|  | ||||
| 	assert.Equal(t, 1, len(jobs)) | ||||
|  | ||||
| 	assert.Equal(t, job.Params, jobs[0].Params) | ||||
| } | ||||
							
								
								
									
										61
									
								
								daemon/db/repo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								daemon/db/repo.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| package db | ||||
|  | ||||
| import ( | ||||
| 	"database/sql" | ||||
|  | ||||
| 	"code.dumpstack.io/tools/out-of-tree/api" | ||||
| ) | ||||
|  | ||||
| func createRepoTable(db *sql.DB) (err error) { | ||||
| 	_, err = db.Exec(` | ||||
| 	CREATE TABLE IF NOT EXISTS repo ( | ||||
| 		id	INTEGER PRIMARY KEY, | ||||
| 		name	TEXT UNIQUE | ||||
| 	)`) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func AddRepo(db *sql.DB, repo *api.Repo) (err error) { | ||||
| 	stmt, err := db.Prepare(`INSERT INTO repo (name) ` + | ||||
| 		`VALUES ($1);`) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	defer stmt.Close() | ||||
|  | ||||
| 	res, err := stmt.Exec(repo.Name) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	repo.ID, err = res.LastInsertId() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func Repos(db *sql.DB) (repos []api.Repo, err error) { | ||||
| 	stmt, err := db.Prepare(`SELECT id, name FROM repo`) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	defer stmt.Close() | ||||
|  | ||||
| 	rows, err := stmt.Query() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
|  | ||||
| 	for rows.Next() { | ||||
| 		var repo api.Repo | ||||
| 		err = rows.Scan(&repo.ID, &repo.Name) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		repos = append(repos, repo) | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										46
									
								
								daemon/db/repo_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								daemon/db/repo_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| package db | ||||
|  | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"os" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | ||||
| 	"code.dumpstack.io/tools/out-of-tree/api" | ||||
| ) | ||||
|  | ||||
| func testCreateRepoTable(t *testing.T) (file *os.File, db *sql.DB) { | ||||
| 	file, err := os.CreateTemp("", "temp-sqlite.db") | ||||
| 	assert.Nil(t, err) | ||||
| 	// defer os.Remove(tempDB.Name()) | ||||
|  | ||||
| 	db, err = sql.Open("sqlite3", file.Name()) | ||||
| 	assert.Nil(t, err) | ||||
| 	// defer db.Close() | ||||
|  | ||||
| 	db.SetMaxOpenConns(1) | ||||
|  | ||||
| 	err = createRepoTable(db) | ||||
| 	assert.Nil(t, err) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func TestRepoTable(t *testing.T) { | ||||
| 	file, db := testCreateRepoTable(t) | ||||
| 	defer db.Close() | ||||
| 	defer os.Remove(file.Name()) | ||||
|  | ||||
| 	repo := api.Repo{Name: "testname"} | ||||
|  | ||||
| 	err := AddRepo(db, &repo) | ||||
| 	assert.Nil(t, err) | ||||
|  | ||||
| 	repos, err := Repos(db) | ||||
| 	assert.Nil(t, err) | ||||
|  | ||||
| 	assert.Equal(t, 1, len(repos)) | ||||
|  | ||||
| 	assert.Equal(t, repo, repos[0]) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user