Skip to content

Commit

Permalink
add support for sql migrations
Browse files Browse the repository at this point in the history
ezekg committed Dec 8, 2024
1 parent d2f414f commit 63de638
Showing 45 changed files with 439 additions and 290 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -206,11 +206,11 @@ release: release-new release-linux-386 release-linux-amd64 release-linux-arm rel

.PHONY: test
test:
go test -race ./...
go test -v -race ./...

.PHONY: test-integration
test-integration:
go test -tags=integrity ./...
go test -v -tags=integration ./...

.PHONY: test-all
test-all: test test-integration
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -291,7 +291,7 @@ To run regular tests:
make test
```
To run integration tests, tagged with `// +build integrity`:
To run integration tests, tagged with `// +build integration`:
```bash
make test-integration
123 changes: 88 additions & 35 deletions cli/cli.go
Original file line number Diff line number Diff line change
@@ -3,12 +3,16 @@ package cli
import (
"context"
"database/sql"
"errors"
"fmt"
"log"
"log/slog"
"os"
"runtime"

"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
_ "github.com/golang-migrate/migrate/v4/source/file"
"github.com/golang-migrate/migrate/v4/source/iofs"
schema "github.com/keygen-sh/keygen-relay/db"
"github.com/keygen-sh/keygen-relay/internal/cmd"
"github.com/keygen-sh/keygen-relay/internal/config"
@@ -51,6 +55,7 @@ Version:
if err != nil {
return fmt.Errorf("failed to parse 'no-audit' flag: %v", err)
}

cfg.License.EnabledAudit = !disableAudit

// init database connection in PersistentPreRun hook for getting persistent flags
@@ -59,6 +64,7 @@ Version:
store, conn, err = initStore(ctx, cfg)
if err != nil {
slog.Error("failed to initialize store", "error", err)

return err
}

@@ -70,9 +76,11 @@ Version:
if conn != nil {
if err := conn.Close(); err != nil {
slog.Error("failed to close database connection", "error", err)

return err
}
}

return nil
},
}
@@ -98,84 +106,129 @@ Version:
}

func initStore(ctx context.Context, cfg *config.Config) (licenses.Store, *sql.DB, error) {
dbExists := fileExists(cfg.DB.DatabaseFilePath)
dbConn, err := sql.Open("sqlite3", cfg.DB.DatabaseFilePath)

conn, err := sql.Open("sqlite3", cfg.DB.DatabaseFilePath)
if err != nil {
slog.Error("failed to open database", "error", err)

return nil, nil, err
}

if err := dbConn.Ping(); err != nil {
if err := conn.Ping(); err != nil {
slog.Error("failed to connect to database", "error", err)

return nil, nil, err
}

slog.Info("applying database pragmas", "path", cfg.DB.DatabaseFilePath)

// set the journal mode to Write-Ahead Logging for concurrency
if _, err := dbConn.Exec("PRAGMA journal_mode = WAL"); err != nil {
log.Fatalf("failed to set journal_mode pragma: %v", err)
if _, err := conn.Exec("PRAGMA journal_mode = WAL"); err != nil {
slog.Error("failed to set journal_mode pragma", "error", err)

return nil, nil, err
}

// set synchronous mode to NORMAL to better balance performance and safety
if _, err := dbConn.Exec("PRAGMA synchronous = NORMAL"); err != nil {
log.Fatalf("failed to set synchronous pragma: %v", err)
if _, err := conn.Exec("PRAGMA synchronous = NORMAL"); err != nil {
slog.Error("failed to set synchronous pragma", "error", err)

return nil, nil, err
}

// set busy timeout to 5 seconds to avoid lock-related errors
if _, err := dbConn.Exec("PRAGMA busy_timeout = 5000"); err != nil {
log.Fatalf("failed to set busy_timeout pragma: %v", err)
if _, err := conn.Exec("PRAGMA busy_timeout = 5000"); err != nil {
slog.Error("failed to set busy_timeout pragma", "error", err)

return nil, nil, err
}

// set cache size to 20MB for faster data access
if _, err := dbConn.Exec("PRAGMA cache_size = -20000"); err != nil {
log.Fatalf("failed to set cache_size pragma: %v", err)
if _, err := conn.Exec("PRAGMA cache_size = -20000"); err != nil {
slog.Error("failed to set cache_size pragma", "error", err)

return nil, nil, err
}

// enable foreign key constraints
if _, err := dbConn.Exec("PRAGMA foreign_keys = ON"); err != nil {
log.Fatalf("failed to set foreign_keys pragma: %v", err)
if _, err := conn.Exec("PRAGMA foreign_keys = ON"); err != nil {
slog.Error("failed to set foreign_keys pragma", "error", err)

return nil, nil, err
}

// enable auto vacuuming and set it to incremental mode for gradual space reclaiming
if _, err := dbConn.Exec("PRAGMA auto_vacuum = INCREMENTAL"); err != nil {
log.Fatalf("failed to set auto_vacuum pragma: %v", err)
if _, err := conn.Exec("PRAGMA auto_vacuum = INCREMENTAL"); err != nil {
slog.Error("failed to set auto_vacuum pragma", "error", err)

return nil, nil, err
}

// store temporary tables and data in memory for better performance
if _, err := dbConn.Exec("PRAGMA temp_store = MEMORY"); err != nil {
log.Fatalf("failed to set temp_store pragma: %v", err)
if _, err := conn.Exec("PRAGMA temp_store = MEMORY"); err != nil {
slog.Error("failed to set temp_store pragma", "error", err)

return nil, nil, err
}

// set the mmap_size to 2GB for faster read/write access using memory-mapped I/O
if _, err := dbConn.Exec("PRAGMA mmap_size = 2147483648"); err != nil {
log.Fatalf("failed to set mmap_size pragma: %v", err)
if _, err := conn.Exec("PRAGMA mmap_size = 2147483648"); err != nil {
slog.Error("failed to set mmap_size pragma", "error", err)

return nil, nil, err
}

// set the page size to 8KB for balanced memory usage and performance
if _, err := dbConn.Exec("PRAGMA page_size = 8192"); err != nil {
log.Fatalf("failed to set page_size pragma: %v", err)
if _, err := conn.Exec("PRAGMA page_size = 8192"); err != nil {
slog.Error("failed to set page_size pragma", "error", err)

return nil, nil, err
}

if !dbExists {
// apply migrations if database exists otherwise init and apply schema
if ok := schemaExists(conn); ok {
slog.Info("applying database migrations", "path", cfg.DB.DatabaseFilePath)

migrations, err := iofs.New(schema.Migrations, "migrations")
if err != nil {
slog.Error("failed to initialize migrations fs", "error", err)

return nil, nil, err
}

migrator, err := db.NewMigrator(conn, migrations)
if err != nil {
slog.Error("failed to initialize migrations", "error", err)

return nil, nil, err
}

if err := migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
slog.Error("failed to apply migrations", "error", err)

return nil, nil, err
}
} else {
slog.Info("applying database schema", "path", cfg.DB.DatabaseFilePath)

if _, err := dbConn.ExecContext(ctx, schema.SchemaSQL); err != nil {
slog.Error("failed to execute schema", "error", err)
if _, err := conn.ExecContext(ctx, schema.Schema); err != nil {
slog.Error("failed to apply schema", "error", err)

return nil, nil, err
}
}

queries := db.New(dbConn)
return db.NewStore(queries, dbConn), dbConn, nil
queries := db.New(conn)
store := db.NewStore(queries, conn)

return store, conn, nil
}

func fileExists(filename string) bool {
info, err := os.Stat(filename)
if err != nil {
slog.Debug("file does not exist", "filename", filename)
return false
}
return !info.IsDir()
func schemaExists(db *sql.DB) bool {
var t string

// check if any tables exist
query := `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' LIMIT 1`
err := db.QueryRow(query).Scan(&t)

return err == nil
}
75 changes: 75 additions & 0 deletions cli/cli_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//go:build integration
// +build integration

package cli_test

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/keygen-sh/keygen-relay/cli"
"github.com/rogpeppe/go-internal/testscript"
)

func TestMain(m *testing.M) {
code := testscript.RunMain(m, map[string]func() int{
"relay": func() int {
return cli.Run()
},
})

os.Exit(code)
}

func TestIntegration(t *testing.T) {
t.Parallel()

testscript.Run(t, testscript.Params{
Dir: "testdata",
RequireExplicitExec: true,
TestWork: true,
Setup: setup,
})
}

func setup(env *testscript.Env) error {
setupFixtures(env)
setupEnv(env)

return nil
}

func setupFixtures(env *testscript.Env) error {
fixtures := []string{
"license.lic",
"license_2.lic",
}

for _, fixture := range fixtures {
if err := copyFile(filepath.Join("testdata", fixture), filepath.Join(env.WorkDir, fixture)); err != nil {
return fmt.Errorf("failed to copy fixture %s: %w", fixture, err)
}
}

return nil
}

func setupEnv(env *testscript.Env) error {
// TODO(ezekg) make prestine env
return nil
}

func copyFile(src, dst string) error {
input, err := os.ReadFile(src)
if err != nil {
return fmt.Errorf("unable to read file %s: %w", src, err)
}

if err := os.WriteFile(dst, input, 0644); err != nil {
return fmt.Errorf("unable to write file to %s: %w", dst, err)
}

return nil
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# Add the license
# add the license
exec relay add --file license.lic --key 9E32DD-D8CC22-771926-C2D834-C506DC-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788

# Set a port as environment variable
# set a port as environment variable
env PORT=65001

# Start the server with heartbeat disabled
# start the server with heartbeat disabled
exec relay serve --port $PORT --no-heartbeats &server_process_test&

# Wait for the server to start
# wait for the server to start
exec sleep 1

# Claim the license for the first time and check status code directly
# claim the license for the first time and check status code directly
exec curl -s -o /dev/null -w "%{http_code}" -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint
stdout '201'

# Claim the license again with the same fingerprint and check status code directly
# claim the license again with the same fingerprint and check status code directly
exec curl -s -o /dev/null -w "%{http_code}" -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint
stdout '409'

# Kill the process (stop the server)
# kill the process (stop the server)
kill server_process_test
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
# Add the license
# add the license
exec relay add --file license.lic --key 9E32DD-D8CC22-771926-C2D834-C506DC-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788

# Set a port as environment variable
# set a port as environment variable
env PORT=65002

# Start the server with heartbeat disabled
# start the server with heartbeat disabled
exec relay serve --port $PORT --no-heartbeats &server_process_test&

# Wait for the server to start
# wait for the server to start
exec sleep 1

# Claim the license for the first time
# claim the license for the first time
exec curl -s -o /dev/null -w "%{http_code}" -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint
stdout '201'

# Claim the license again with the same fingerprint
# claim the license again with the same fingerprint
exec curl -s -o response.txt -w "%{http_code}" -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint

# Expect a conflict response with status code 409 and error message
# expect a conflict response with status code 409 and error message
stdout '409'

exec grep '{"error":"License claim conflict, heartbeat disabled"}' response.txt
exec grep '{"error":"failed to claim license due to conflict"}' response.txt

# Kill the process (stop the server)
# kill the process (stop the server)
kill server_process_test
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# Add the license
# add the license
exec relay add --file license.lic --key 9E32DD-D8CC22-771926-C2D834-C506DC-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788

# Set a port as environment variable
# set a port as environment variable
env PORT=65003

# Start the server with heartbeat enabled (for extension to work)
# start the server with heartbeat enabled (for extension to work)
exec relay serve --port $PORT &server_process_test&

# Wait for the server to start
# wait for the server to start
exec sleep 1

# Claim a license for the first time
# claim a license for the first time
exec curl -s -o /dev/null -w "%{http_code}" -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint
stdout '201'

# Claim the license again with the same fingerprint to trigger an extension
# claim the license again with the same fingerprint to trigger an extension
exec curl -s -o response.txt -w "%{http_code}" -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint
stdout '202'

# Kill the process (stop the server)
# kill the process (stop the server)
kill server_process_test
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
# Add the first license
# add the first license
exec relay add --file license.lic --key 9E32DD-D8CC22-771926-C2D834-C506DC-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788

# Give a pause between adding licenses
# give a pause between adding licenses
exec sleep 1

# Add the second license
# add the second license
exec relay add --file license_2.lic --key 9A96B8-FD08CD-8C433B-7657C8-8A8655-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788

# Set a port and strategy as environment variables
# set a port and strategy as environment variables
env PORT=65004
env STRATEGY=lifo

# Start the server with FIFO strategy
# start the server with FIFO strategy
exec relay serve --port $PORT --strategy $STRATEGY &server_process_test&

# Wait for the server to start
# wait for the server to start
exec sleep 1

# Claim a license (LIFO: should return the last license)
# claim a license (LIFO: should return the last license)
exec curl -s -o response.txt -w "%{http_code}" -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint

# Expect the first license to be returned
# expect the first license to be returned
exec grep '"license_file":' response.txt
exec grep '"license_key":"9A96B8-FD08CD-8C433B-7657C8-8A8655-V3"' response.txt

# Kill the process (stop the server)
# kill the process (stop the server)
kill server_process_test
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@


# Add the license
# add the license
exec relay add --file license.lic --key 9E32DD-D8CC22-771926-C2D834-C506DC-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788

# Set a port as environment variable
# set a port as environment variable
env PORT=65005

# Start the server with heartbeat disabled
# start the server with heartbeat disabled
exec relay serve --port $PORT &server_process_test&

# Wait for the server to start
# wait for the server to start
exec sleep 1

# Claim a license
# claim a license
exec curl -s -o response.txt -w "%{http_code}" -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint

# Expect a success response with status code 201
# expect a success response with status code 201
stdout '201'

# Check that the response contains license_file and license_key fields (using grep)
# check that the response contains license_file and license_key fields (using grep)
exec grep '"license_file":' response.txt
exec grep '"license_key":' response.txt

# Kill the process (stop the server)
# kill the process (stop the server)
kill server_process_test
16 changes: 16 additions & 0 deletions cli/testdata/claim_no_licenses_available.test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# start the server without adding any licenses
env PORT=65006
exec relay serve --port $PORT &server_process_test&

# wait for the server to start
exec sleep 1

# attempt to claim a license when none are available
exec curl -s -o response.txt -w "%{http_code}" -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint

# expect a gone response with status code 410 and error message
stdout '410'
exec grep 'no licenses available' response.txt

# kill the process (stop the server)
kill server_process_test
10 changes: 5 additions & 5 deletions testdata/cmd_add.txt → cli/testdata/cmd_add.test.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Add the license
# add the license
exec relay add --file license.lic --key 9E32DD-D8CC22-771926-C2D834-C506DC-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788

# Expect output indicating success
stdout 'License added successfully.'
# expect output indicating success
stdout 'license added successfully'

# List licenses to confirm
# list licenses to confirm
exec relay ls --plain

# Expect the license to be listed
# expect the license to be listed
stdout 'dcea31a4-1664-4633-9f52-4a1b0b5ea2ef'
14 changes: 7 additions & 7 deletions testdata/cmd_del.txt → cli/testdata/cmd_del.test.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Add the license
# add the license
exec relay add --file license.lic --key 9E32DD-D8CC22-771926-C2D834-C506DC-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788

# Confirm the license is added
# confirm the license is added
exec relay ls --plain
stdout 'dcea31a4-1664-4633-9f52-4a1b0b5ea2ef'

# Delete the license
# delete the license
exec relay del --license dcea31a4-1664-4633-9f52-4a1b0b5ea2ef

# Expect output indicating success
stdout 'License deleted successfully.'
# expect output indicating success
stdout 'license deleted successfully'

# Verify the license is deleted
# verify the license is deleted
exec relay ls --plain
stdout 'No licenses found.'
stdout 'no licenses found'
18 changes: 9 additions & 9 deletions testdata/cmd_serve.txt → cli/testdata/cmd_serve.test.txt
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
# Add the license
# add the license
exec relay add --file license.lic --key 9E32DD-D8CC22-771926-C2D834-C506DC-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788

# Set a port as environment variable
# set a port as environment variable
env PORT=65007

# Start the server in the background with the environment variable
# start the server in the background with the environment variable
exec relay serve --port $PORT &server_process&

# Wait for the server to start
# wait for the server to start
exec sleep 1

# Claim a license using the port from the environment variable
# claim a license using the port from the environment variable
exec curl -s -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint

# Expect license data in the response
# expect license data in the response
stdout '"license_file":'

# Release the license
# release the license
exec curl -s -X DELETE http://localhost:$PORT/v1/nodes/test_fingerprint

# Expect no content in the response
# expect no content in the response
stdout ''

# Kill the process (stop the server)
# kill the process (stop the server)
kill server_process
6 changes: 3 additions & 3 deletions testdata/cmd_stat.txt → cli/testdata/cmd_stat.test.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Add the license
# add the license
exec relay add --file license.lic --key 9E32DD-D8CC22-771926-C2D834-C506DC-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788

# Get statistics of the license
# get statistics of the license
exec relay stat --license dcea31a4-1664-4633-9f52-4a1b0b5ea2ef --plain

# Expect output containing the license details
# expect output containing the license details
stdout 'dcea31a4-1664-4633-9f52-4a1b0b5ea2ef'
4 changes: 4 additions & 0 deletions cli/testdata/cmd_version.test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
exec relay version

# should use the dev version
stdout '<not set>'
File renamed without changes.
File renamed without changes.
16 changes: 16 additions & 0 deletions cli/testdata/release_license_not_found.test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# start the server
env PORT=65008
exec relay serve --port $PORT &server_process_test&

# wait for the server to start
exec sleep 1

# attempt to release a license that was never claimed
exec curl -s -o response.txt -w "%{http_code}" -X DELETE http://localhost:$PORT/v1/nodes/test_fingerprint

# expect a not found response with status code 404 and error message
stdout '404'
exec grep '{"error":"claim not found"}' response.txt

# kill the process (stop the server)
kill server_process_test
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# Add the license
# add the license
exec relay add --file license.lic --key 9E32DD-D8CC22-771926-C2D834-C506DC-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788

# Set a port as environment variable
# set a port as environment variable
env PORT=65009

# Start the server with heartbeat disabled
# start the server with heartbeat disabled
exec relay serve --port $PORT &server_process_test&

# Wait for the server to start
# wait for the server to start
exec sleep 1

# Claim a license
# claim a license
exec curl -s -o /dev/null -w "%{http_code}" -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint
stdout '201'

# Release the license
# release the license
exec curl -s -o /dev/null -w "%{http_code}" -X DELETE http://localhost:$PORT/v1/nodes/test_fingerprint
stdout '204'

# Kill the process (stop the server)
# kill the process (stop the server)
kill server_process_test
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
# Set a custom database path
# set a custom database path
env DATABASE_PATH=custom_relay.sqlite

# Remove any existing custom database
# remove any existing custom database
rm -f $DATABASE_PATH

# Add a license with a custom database path
# add a license with a custom database path
exec relay add --file license.lic --key 9E32DD-D8CC22-771926-C2D834-C506DC-V3 --public-key e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788 --database $DATABASE_PATH

# Ensure that the custom database is created
# ensure that the custom database is created
exec test -f $DATABASE_PATH

# Set a port as environment variable
# set a port as environment variable
env PORT=65010

# Start the server with custom database path
# start the server with custom database path
exec relay serve --port $PORT --database $DATABASE_PATH &server_process_test&

# Wait for the server to start
# wait for the server to start
exec sleep 1

# Claim a license
# claim a license
exec curl -s -o response.txt -w "%{http_code}" -X PUT http://localhost:$PORT/v1/nodes/test_fingerprint

# Expect a success response (status 201)
# expect a success response (status 201)
stdout '201'

# Kill the process (stop the server)
# kill the process (stop the server)
kill server_process_test

# Clean up the custom database
# clean up the custom database
rm -f $DATABASE_PATH
9 changes: 7 additions & 2 deletions db/embed.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package schema

import _ "embed"
import (
"embed"
)

//go:embed schema.sql
var SchemaSQL string
var Schema string

//go:embed migrations/*.sql
var Migrations embed.FS
Empty file.
Empty file.
10 changes: 7 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -3,10 +3,12 @@ module github.com/keygen-sh/keygen-relay
go 1.23.1

require (
github.com/bmatcuk/doublestar/v4 v4.7.1
github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.1.1
github.com/charmbracelet/lipgloss v0.13.0
github.com/fatih/color v1.17.0
github.com/golang-migrate/migrate/v4 v4.18.1
github.com/gorilla/mux v1.8.1
github.com/keygen-sh/keygen-go/v3 v3.2.0
github.com/lmittmann/tint v1.0.5
@@ -24,8 +26,9 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/keygen-sh/go-update v1.0.0 // indirect
github.com/keygen-sh/jsonapi-go v1.2.1 // indirect
@@ -36,15 +39,16 @@ require (
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/nxadm/tail v1.4.11 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20211102120939-d5a936accd94 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.uber.org/atomic v1.7.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/tools v0.24.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
34 changes: 27 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q=
github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY=
@@ -15,6 +17,7 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
@@ -24,6 +27,10 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@@ -34,11 +41,15 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
@@ -48,6 +59,8 @@ github.com/keygen-sh/jsonapi-go v1.2.1 h1:NTSIAxl2+7S5fPnKgrYwNjQSWbdKRtrFq26SD8
github.com/keygen-sh/jsonapi-go v1.2.1/go.mod h1:8j9vsLiKyJyDqmt8r3tYaYNmXszq2+cFhoO6QdMdAes=
github.com/keygen-sh/keygen-go/v3 v3.2.0 h1:OJqnGtY6z4ZA434kZqfNVHDmSrN5zq4l4XItcB3tECY=
github.com/keygen-sh/keygen-go/v3 v3.2.0/go.mod h1:YoFyryzXEk6XrbT3H8EUUU+JcIJkQu414TA6CvZgS/E=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw=
github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
@@ -69,14 +82,18 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/oasisprotocol/curve25519-voi v0.0.0-20211102120939-d5a936accd94 h1:YXfl+eCNmAQhVbSNQ85bSi1n4qhUBPW8Qq9Rac4pt/s=
github.com/oasisprotocol/curve25519-voi v0.0.0-20211102120939-d5a936accd94/go.mod h1:WUcXjUd98qaCVFb6j8Xc87MsKeMCXDu9Nk8JRJ9SeC8=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -89,8 +106,12 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
@@ -111,6 +132,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
@@ -121,14 +143,12 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
56 changes: 0 additions & 56 deletions integrity_test.go

This file was deleted.

2 changes: 1 addition & 1 deletion internal/cmd/add.go
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ func AddCmd(manager licenses.Manager) *cobra.Command {
return nil
}

output.PrintSuccess(cmd.OutOrStdout(), "License added successfully.")
output.PrintSuccess(cmd.OutOrStdout(), "license added successfully")

return nil
},
4 changes: 2 additions & 2 deletions internal/cmd/add_test.go
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ func TestAddCmd_Success(t *testing.T) {
err := addCmd.Execute()
assert.NoError(t, err)

assert.Contains(t, outBuf.String(), "License added successfully.")
assert.Contains(t, outBuf.String(), "license added successfully")
}

func TestAddCmd_Error(t *testing.T) {
@@ -51,7 +51,7 @@ func TestAddCmd_Error(t *testing.T) {

_ = addCmd.Execute()

assert.Contains(t, errBuf.String(), "Error: failed to add license")
assert.Contains(t, errBuf.String(), "error: failed to add license")
}

func TestAddCmd_MissingRequiredFlags(t *testing.T) {
2 changes: 1 addition & 1 deletion internal/cmd/del.go
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ func DelCmd(manager licenses.Manager) *cobra.Command {
return nil
}

output.PrintSuccess(cmd.OutOrStdout(), "License deleted successfully.")
output.PrintSuccess(cmd.OutOrStdout(), "license deleted successfully")

return nil
},
2 changes: 1 addition & 1 deletion internal/cmd/del_test.go
Original file line number Diff line number Diff line change
@@ -50,7 +50,7 @@ func TestDelCmd_Error(t *testing.T) {

_ = delCmd.Execute()

assert.Contains(t, errBuf.String(), "Error: failed to remove license")
assert.Contains(t, errBuf.String(), "error: failed to remove license")
}

func TestDelCmd_MissingID(t *testing.T) {
4 changes: 2 additions & 2 deletions internal/cmd/ls.go
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ func LsCmd(manager licenses.Manager) *cobra.Command {
}

if len(licensesList) == 0 {
output.PrintSuccess(cmd.OutOrStdout(), "No licenses found.")
output.PrintSuccess(cmd.OutOrStdout(), "no licenses found")

return nil
}
@@ -67,7 +67,7 @@ func LsCmd(manager licenses.Manager) *cobra.Command {
}

if err := renderer.Render(tableRows, columns); err != nil {
output.PrintError(cmd.ErrOrStderr(), fmt.Sprintf("Error rendering table: %v", err))
output.PrintError(cmd.ErrOrStderr(), fmt.Sprintf("error rendering table: %v", err))

return err
}
5 changes: 3 additions & 2 deletions internal/cmd/ls_test.go
Original file line number Diff line number Diff line change
@@ -4,9 +4,10 @@ import (
"bytes"
"context"
"errors"
"testing"

"github.com/keygen-sh/keygen-relay/internal/licenses"
"github.com/keygen-sh/keygen-relay/internal/testutils"
"testing"

"github.com/keygen-sh/keygen-relay/internal/cmd"
"github.com/stretchr/testify/assert"
@@ -51,7 +52,7 @@ func TestLsCmd_NoLicenses(t *testing.T) {
err := lsCmd.Execute()
assert.NoError(t, err)

assert.Contains(t, outBuf.String(), "No licenses found.")
assert.Contains(t, outBuf.String(), "no licenses found")
}

func TestLsCmd_Error(t *testing.T) {
10 changes: 5 additions & 5 deletions internal/cmd/serve.go
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ func ServeCmd(srv server.Server) *cobra.Command {

router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
path, _ := route.GetPathTemplate()
slog.Debug("Route registered", "path", path)
slog.Debug("route registered", "path", path)
return nil
})

@@ -38,15 +38,15 @@ func ServeCmd(srv server.Server) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
noHeartbeats, err := cmd.Flags().GetBool("no-heartbeats")
if err != nil {
output.PrintError(cmd.ErrOrStderr(), fmt.Sprintf("Failed to parse 'no-heartbeats' flag: %v", err))
output.PrintError(cmd.ErrOrStderr(), fmt.Sprintf("failed to parse 'no-heartbeats' flag: %v", err))
return err
}

cfg.EnabledHeartbeat = !noHeartbeats

ttl, err := cmd.Flags().GetDuration("ttl")
if err != nil {
output.PrintError(cmd.ErrOrStderr(), fmt.Sprintf("Failed to parse 'ttl' flag: %v", err))
output.PrintError(cmd.ErrOrStderr(), fmt.Sprintf("failed to parse 'ttl' flag: %v", err))
return err
}

@@ -58,7 +58,7 @@ func ServeCmd(srv server.Server) *cobra.Command {
srv.Manager().Config().Strategy = string(cfg.Strategy)
srv.Manager().Config().ExtendOnHeartbeat = cfg.EnabledHeartbeat

output.PrintSuccess(cmd.OutOrStdout(), "The server is starting")
output.PrintSuccess(cmd.OutOrStdout(), "the server is starting")

if err := srv.Run(); err != nil {
output.PrintError(cmd.ErrOrStderr(), err.Error())
@@ -91,7 +91,7 @@ func ServeCmd(srv server.Server) *cobra.Command {

func validateTTL(ttl time.Duration) error {
if ttl < minTTL {
return fmt.Errorf("TTL value must be at least %s", minTTL)
return fmt.Errorf("time-to-live value must be at least %s", minTTL)
}
return nil
}
8 changes: 4 additions & 4 deletions internal/cmd/serve_test.go
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ func TestServeCmd_Defaults(t *testing.T) {
err := serveCmd.Execute()

assert.NoError(t, err)
assert.Contains(t, output.String(), "The server is starting")
assert.Contains(t, output.String(), "the server is starting")
assert.True(t, mockServer.RunCalled)
assert.Equal(t, 6349, cfg.ServerPort)
assert.Equal(t, 30*time.Second, cfg.TTL)
@@ -77,7 +77,7 @@ func TestServeCmd_Flags(t *testing.T) {
err := serveCmd.Execute()

assert.NoError(t, err)
assert.Contains(t, output.String(), "The server is starting")
assert.Contains(t, output.String(), "the server is starting")
assert.True(t, mockServer.RunCalled)
assert.Equal(t, 9090, cfg.Server.ServerPort)
assert.Equal(t, 1*time.Minute, cfg.Server.TTL)
@@ -137,7 +137,7 @@ func TestServeCmd_RunError(t *testing.T) {

_ = serveCmd.Execute()

assert.Contains(t, output.String(), "Error: failed to start server")
assert.Contains(t, output.String(), "error: failed to start server")

assert.True(t, mockServer.RunCalled)
}
@@ -165,6 +165,6 @@ func TestServeCmd_InvalidTTL(t *testing.T) {

_ = serveCmd.Execute()

assert.Contains(t, output.String(), "TTL value must be at least 30s")
assert.Contains(t, output.String(), "time-to-live value must be at least 30s")
assert.False(t, mockServer.RunCalled)
}
2 changes: 1 addition & 1 deletion internal/cmd/stat.go
Original file line number Diff line number Diff line change
@@ -60,7 +60,7 @@ func StatCmd(manager licenses.Manager) *cobra.Command {
}

if err := renderer.Render(tableRows, columns); err != nil {
output.PrintError(cmd.ErrOrStderr(), fmt.Sprintf("Error rendering table: %v", err))
output.PrintError(cmd.ErrOrStderr(), fmt.Sprintf("error rendering table: %v", err))

return nil
}
42 changes: 42 additions & 0 deletions internal/db/migrator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package db

import (
"database/sql"
"fmt"

"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/sqlite3"
"github.com/golang-migrate/migrate/v4/source"
_ "github.com/mattn/go-sqlite3"
)

type Migrator struct {
migrate *migrate.Migrate
}

func (m Migrator) Up() error {
return m.migrate.Up()
}

func (m Migrator) Down() error {
return m.migrate.Down()
}

func NewMigrator(db *sql.DB, migrations source.Driver) (*Migrator, error) {
instance, err := sqlite3.WithInstance(db, &sqlite3.Config{})
if err != nil {
return nil, err
}

m, err := migrate.NewWithInstance(
"file",
migrations,
"sqlite3",
instance,
)
if err != nil {
return nil, fmt.Errorf("failed to create migrator: %w", err)
}

return &Migrator{migrate: m}, nil
}
18 changes: 9 additions & 9 deletions internal/licenses/manager.go
Original file line number Diff line number Diff line change
@@ -118,7 +118,7 @@ func (m *manager) AttachStore(store Store) {
}

func (m *manager) AddLicense(ctx context.Context, licenseFilePath string, licenseKey string, publicKey string) error {
slog.Debug("Starting to add a new license", "filePath", licenseFilePath)
slog.Debug("starting to add a new license", "filePath", licenseFilePath)

cert, err := m.dataReader(licenseFilePath)
if err != nil {
@@ -131,7 +131,7 @@ func (m *manager) AddLicense(ctx context.Context, licenseFilePath string, licens
return fmt.Errorf("failed to read license file: %w", err)
}

slog.Debug("Successfully read the license file", "filePath", licenseFilePath)
slog.Debug("successfully read the license file", "filePath", licenseFilePath)

lic := m.verifier(cert)
keygen.PublicKey = publicKey
@@ -149,7 +149,7 @@ func (m *manager) AddLicense(ctx context.Context, licenseFilePath string, licens
key := dec.License.Key

if err := m.store.InsertLicense(ctx, id, cert, key); err != nil {
slog.Debug("Failed to insert license", "licenseID", id, "error", err)
slog.Debug("failed to insert license", "licenseID", id, "error", err)

if isUniqueConstraintError(err) {
return fmt.Errorf("license with the provided key already exists")
@@ -171,12 +171,12 @@ func (m *manager) AddLicense(ctx context.Context, licenseFilePath string, licens
}

func (m *manager) RemoveLicense(ctx context.Context, id string) error {
slog.Debug("Starting to remove license", "id", id)
slog.Debug("starting to remove license", "id", id)

err := m.store.DeleteLicenseByIDTx(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("license with ID %s not found", id)
return fmt.Errorf("license %s not found", id)
}
slog.Debug("failed to delete license", "licenseID", id, "error", err)
return fmt.Errorf("failed to delete license: %w", err)
@@ -194,7 +194,7 @@ func (m *manager) RemoveLicense(ctx context.Context, id string) error {
}

func (m *manager) ListLicenses(ctx context.Context) ([]License, error) {
slog.Debug("Fetching licenses")
slog.Debug("fetching licenses")

licenses, err := m.store.GetAllLicenses(ctx)
if err != nil {
@@ -212,13 +212,13 @@ func (m *manager) ListLicenses(ctx context.Context) ([]License, error) {
}

func (m *manager) GetLicenseByID(ctx context.Context, id string) (License, error) {
slog.Debug("Fetching license", "id", id)
slog.Debug("fetching license", "id", id)

license, err := m.store.GetLicenseByID(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
slog.Debug("license not found", "licenseID", id)
return License{}, fmt.Errorf("license with ID %s: %w", id, ErrLicenseNotFound)
return License{}, fmt.Errorf("license %s: %w", id, ErrLicenseNotFound)
}

slog.Debug("failed to fetch license by ID", "licenseID", id, "error", err)
@@ -246,7 +246,7 @@ func (m *manager) ClaimLicense(ctx context.Context, fingerprint string) (*Licens

if err == nil {
if !m.config.ExtendOnHeartbeat { // if heartbeat is disabled, we can't extend the claimed license
slog.Warn("license claim conflict due to heartbeat disabled", "nodeID", node.ID, "Fingerprint", node.Fingerprint)
slog.Warn("failed to claim license due to conflict due to heartbeat disabled", "nodeID", node.ID, "Fingerprint", node.Fingerprint)
return &LicenseOperationResult{Status: OperationStatusConflict}, nil
}

14 changes: 7 additions & 7 deletions internal/licenses/manager_test.go
Original file line number Diff line number Diff line change
@@ -110,20 +110,20 @@ func TestRemoveLicense_Success(t *testing.T) {

// add a license that to be deleted
err := manager.AddLicense(ctx, "test_license.lic", "test_key", "test_public_key")
assert.NoError(t, err, "Failed to add license")
assert.NoError(t, err, "failed to add license")

// check that the license was created
_, err = manager.GetLicenseByID(ctx, "license_test_key")
assert.NoError(t, err, "License should exist")
assert.NoError(t, err, "license should exist")

// remove the license
err = manager.RemoveLicense(ctx, "license_test_key")
assert.NoError(t, err, "Failed to remove license")
assert.NoError(t, err, "failed to remove license")

// check that the license is removed
_, err = manager.GetLicenseByID(context.Background(), "license_test_key")
assert.Error(t, err, "License should not exist after deletion")
assert.Contains(t, err.Error(), "license with ID license_test_key: license not found")
assert.Error(t, err, "license should not exist after deletion")
assert.Contains(t, err.Error(), "license license_test_key: license not found")
}

func TestRemoveLicense_Failure(t *testing.T) {
@@ -146,7 +146,7 @@ func TestRemoveLicense_Failure(t *testing.T) {

err := manager.RemoveLicense(context.Background(), "invalid_id")
assert.Error(t, err)
assert.Contains(t, err.Error(), "license with ID invalid_id not found")
assert.Contains(t, err.Error(), "license invalid_id not found")
}

func TestListLicenses_Success(t *testing.T) {
@@ -228,7 +228,7 @@ func TestGetLicenseByID_Failure(t *testing.T) {

_, err := manager.GetLicenseByID(context.Background(), "invalid_id")
assert.Error(t, err)
assert.Contains(t, err.Error(), "license with ID invalid_id: license not found")
assert.Contains(t, err.Error(), "license invalid_id: license not found")
}

func TestClaimLicense_Success(t *testing.T) {
2 changes: 1 addition & 1 deletion internal/output/output.go
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import (

func PrintError(w io.Writer, message string) {
errorMessage := color.New(color.FgRed, color.Bold).SprintFunc()
fmt.Fprintf(w, "%s: %s\n", errorMessage("Error"), message)
fmt.Fprintf(w, "%s: %s\n", errorMessage("error"), message)
}

func PrintSuccess(w io.Writer, message string) {
18 changes: 9 additions & 9 deletions internal/server/handler.go
Original file line number Diff line number Diff line change
@@ -48,10 +48,10 @@ func (h *handler) ClaimLicense(w http.ResponseWriter, r *http.Request) {

result, err := h.licenseManager.ClaimLicense(r.Context(), fingerprint)
if err != nil {
slog.Error("Failed to claim license", "error", err)
slog.Error("failed to claim license", "error", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "Failed to claim license"})
_ = json.NewEncoder(w).Encode(map[string]string{"error": "failed to claim license"})
return
}

@@ -71,15 +71,15 @@ func (h *handler) ClaimLicense(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusAccepted)
case licenses.OperationStatusConflict:
w.WriteHeader(http.StatusConflict)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "License claim conflict, heartbeat disabled"})
_ = json.NewEncoder(w).Encode(map[string]string{"error": "failed to claim license due to conflict"})
return
case licenses.OperationStatusNoLicensesAvailable:
w.WriteHeader(http.StatusGone)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "No licenses available"})
_ = json.NewEncoder(w).Encode(map[string]string{"error": "no licenses available"})
return
default:
w.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "Unknown claim status"})
_ = json.NewEncoder(w).Encode(map[string]string{"error": "unknown claim status"})
return
}
}
@@ -89,10 +89,10 @@ func (h *handler) ReleaseLicense(w http.ResponseWriter, r *http.Request) {

result, err := h.licenseManager.ReleaseLicense(r.Context(), fingerprint)
if err != nil {
slog.Error("Failed to release license", "error", err)
slog.Error("failed to release license", "error", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "Failed to release license"})
_ = json.NewEncoder(w).Encode(map[string]string{"error": "failed to release license"})
return
}

@@ -103,9 +103,9 @@ func (h *handler) ReleaseLicense(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
case licenses.OperationStatusNotFound:
w.WriteHeader(http.StatusNotFound)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "Claim not found"})
_ = json.NewEncoder(w).Encode(map[string]string{"error": "claim not found"})
default:
w.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "Unknown release status"})
_ = json.NewEncoder(w).Encode(map[string]string{"error": "unknown release status"})
}
}
17 changes: 9 additions & 8 deletions internal/server/handler_test.go
Original file line number Diff line number Diff line change
@@ -4,13 +4,14 @@ import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"

"github.com/gorilla/mux"
"github.com/keygen-sh/keygen-relay/internal/licenses"
"github.com/keygen-sh/keygen-relay/internal/server"
"github.com/keygen-sh/keygen-relay/internal/testutils"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)
@@ -91,7 +92,7 @@ func TestClaimLicense_HeartbeatDisabled_Conflict(t *testing.T) {
router.ServeHTTP(rr, req)

assert.Equal(t, http.StatusConflict, rr.Code)
assert.Contains(t, rr.Body.String(), "License claim conflict, heartbeat disabled")
assert.Contains(t, rr.Body.String(), "failed to claim license due to conflict")
}

func TestClaimLicense_NoLicensesAvailable(t *testing.T) {
@@ -113,7 +114,7 @@ func TestClaimLicense_NoLicensesAvailable(t *testing.T) {
router.ServeHTTP(rr, req)

assert.Equal(t, http.StatusGone, rr.Code)
assert.Contains(t, rr.Body.String(), "No licenses available")
assert.Contains(t, rr.Body.String(), "no licenses available")
}

func TestClaimLicense_InternalServerError(t *testing.T) {
@@ -133,7 +134,7 @@ func TestClaimLicense_InternalServerError(t *testing.T) {
router.ServeHTTP(rr, req)

assert.Equal(t, http.StatusInternalServerError, rr.Code)
assert.Contains(t, rr.Body.String(), "Failed to claim license")
assert.Contains(t, rr.Body.String(), "failed to claim license")
}

func TestReleaseLicense_Success(t *testing.T) {
@@ -177,7 +178,7 @@ func TestReleaseLicense_NotFound(t *testing.T) {
router.ServeHTTP(rr, req)

assert.Equal(t, http.StatusNotFound, rr.Code)
assert.Contains(t, rr.Body.String(), "Claim not found")
assert.Contains(t, rr.Body.String(), "claim not found")
}

func TestReleaseLicense_InternalServerError(t *testing.T) {
@@ -197,5 +198,5 @@ func TestReleaseLicense_InternalServerError(t *testing.T) {
router.ServeHTTP(rr, req)

assert.Equal(t, http.StatusInternalServerError, rr.Code)
assert.Contains(t, rr.Body.String(), "Failed to release license")
assert.Contains(t, rr.Body.String(), "failed to release license")
}
14 changes: 7 additions & 7 deletions internal/server/server.go
Original file line number Diff line number Diff line change
@@ -38,20 +38,20 @@ func (s *server) Run() error {
defer cancel()

address := fmt.Sprintf(":%d", s.config.ServerPort)
slog.Info("Starting server", "port", s.config.ServerPort)
slog.Info("starting server", "port", s.config.ServerPort)

if s.Config().EnabledHeartbeat {
go s.startCullRoutine(ctx)
}

err := http.ListenAndServe(address, s.router)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
slog.Error("Server failed to start", "error", err)
slog.Error("server failed to start", "error", err)
cancel()
return err
}

slog.Info("Server stopped")
slog.Info("server stopped")
return nil
}

@@ -75,14 +75,14 @@ func (s *server) startCullRoutine(ctx context.Context) {
ticker := time.NewTicker(s.config.CullInterval)
defer ticker.Stop()

slog.Debug("Starting cull routine for inactive nodes", "ttl", s.config.TTL, "cullInterval", s.config.CullInterval)
slog.Debug("starting cull routine for inactive nodes", "ttl", s.config.TTL, "cullInterval", s.config.CullInterval)

for {
select {
case <-ticker.C:
s.cull()
case <-ctx.Done():
slog.Debug("Stopping cull routine")
slog.Debug("stopping cull routine")
return
}
}
@@ -92,8 +92,8 @@ func (s *server) cull() {
ctx := context.Background()
err := s.manager.CullInactiveNodes(ctx, s.Config().TTL)
if err != nil {
slog.Error("Failed to cull inactive nodes", "error", err)
slog.Error("failed to cull inactive nodes", "error", err)
} else {
slog.Debug("Successfully culled inactive nodes")
slog.Debug("successfully culled inactive nodes")
}
}
20 changes: 10 additions & 10 deletions internal/testutils/memory_store.go
Original file line number Diff line number Diff line change
@@ -2,36 +2,36 @@ package testutils

import (
"database/sql"
schema "github.com/keygen-sh/keygen-relay/db"
"log"
"testing"

schema "github.com/keygen-sh/keygen-relay/db"
"github.com/keygen-sh/keygen-relay/internal/db"
_ "github.com/mattn/go-sqlite3"
)

func NewMemoryStore(t *testing.T) (*db.Store, *sql.DB) {
dbConn, err := sql.Open("sqlite3", ":memory:?_pragma=foreign_keys(on)")
conn, err := sql.Open("sqlite3", ":memory:?_pragma=foreign_keys(on)")
if err != nil {
t.Fatalf("Failed to open in-memory database: %v", err)
t.Fatalf("failed to open in-memory database: %v", err)
}

_, err = dbConn.Exec("PRAGMA foreign_keys = ON")
_, err = conn.Exec("PRAGMA foreign_keys = ON")
if err != nil {
log.Fatal("Failed to enable foreign keys:", err)
t.Fatalf("failed to enable foreign keys: %v", err)
}

if _, err := dbConn.Exec(schema.SchemaSQL); err != nil {
t.Fatalf("Failed to apply schema: %v", err)
if _, err := conn.Exec(schema.Schema); err != nil {
t.Fatalf("failed to apply schema: %v", err)
}

store := db.NewStore(db.New(dbConn), dbConn)
store := db.NewStore(db.New(conn), conn)

return store, dbConn
return store, conn
}

func CloseMemoryStore(dbConn *sql.DB) {
if err := dbConn.Close(); err != nil {
log.Printf("Failed to close in-memory database connection: %v", err)
log.Printf("failed to close in-memory database connection: %v", err)
}
}
16 changes: 0 additions & 16 deletions testdata/claim_no_licenses_available.txt

This file was deleted.

16 changes: 0 additions & 16 deletions testdata/release_license_not_found.txt

This file was deleted.

0 comments on commit 63de638

Please sign in to comment.