From bd8e1b3477390448e1d8b34553f2c0f613eac537 Mon Sep 17 00:00:00 2001 From: whywaita Date: Thu, 20 Aug 2026 14:21:07 +0900 Subject: [PATCH 1/6] refactor: migrate uuid to Go 1.27 standard library Replace github.com/satori/go.uuid with the standard library uuid package added in Go 1.27. - uuid.FromString -> uuid.Parse - uuid.FromStringOrNil -> uuid.MustParse - uuid.Equal(a, b) -> a == b (UUID is a comparable [16]byte) - uuid.NewV4 / uuid.UUID / uuid.UUID{} map 1:1 Bump go directive 1.25 -> 1.27 and drop the satori/go.uuid module. --- go.mod | 3 +-- go.sum | 2 -- pkg/datastore/interface.go | 2 +- pkg/datastore/memory/memory.go | 4 ++-- pkg/datastore/mysql/job.go | 2 +- pkg/datastore/mysql/job_test.go | 4 ++-- pkg/datastore/mysql/runner.go | 2 +- pkg/datastore/mysql/runner_test.go | 14 +++++++------- pkg/datastore/mysql/target.go | 2 +- pkg/datastore/mysql/target_test.go | 8 ++++---- pkg/metric/scrape_memory.go | 2 +- pkg/runner/util.go | 4 ++-- pkg/starter/starter.go | 2 +- pkg/web/target.go | 4 ++-- pkg/web/target_create.go | 2 +- pkg/web/target_test.go | 2 +- pkg/web/webhook.go | 2 +- 17 files changed, 29 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index a9f0674..5d741cd 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/whywaita/myshoes -go 1.25 +go 1.27 require ( github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 @@ -16,7 +16,6 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.23.2 github.com/r3labs/diff/v2 v2.15.1 - github.com/satori/go.uuid v1.2.0 goji.io v2.0.2+incompatible golang.org/x/oauth2 v0.32.0 golang.org/x/sync v0.18.0 diff --git a/go.sum b/go.sum index 0ae1540..a71cc14 100644 --- a/go.sum +++ b/go.sum @@ -145,8 +145,6 @@ github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/pkg/datastore/interface.go b/pkg/datastore/interface.go index 39b0ccc..84843e1 100644 --- a/pkg/datastore/interface.go +++ b/pkg/datastore/interface.go @@ -9,7 +9,7 @@ import ( "strings" "time" - uuid "github.com/satori/go.uuid" + "uuid" "github.com/whywaita/myshoes/pkg/gh" "github.com/whywaita/myshoes/pkg/logger" diff --git a/pkg/datastore/memory/memory.go b/pkg/datastore/memory/memory.go index 497d585..5a4cccf 100644 --- a/pkg/datastore/memory/memory.go +++ b/pkg/datastore/memory/memory.go @@ -7,7 +7,7 @@ import ( "sync" "time" - uuid "github.com/satori/go.uuid" + "uuid" "github.com/whywaita/myshoes/pkg/datastore" ) @@ -214,7 +214,7 @@ func (m *Memory) ListRunnersByTargetID(ctx context.Context, targetID uuid.UUID) var runners []datastore.Runner for _, r := range m.runners { - if uuid.Equal(r.TargetID, targetID) { + if r.TargetID == targetID { runners = append(runners, r) } } diff --git a/pkg/datastore/mysql/job.go b/pkg/datastore/mysql/job.go index 58d757d..92a778f 100644 --- a/pkg/datastore/mysql/job.go +++ b/pkg/datastore/mysql/job.go @@ -6,8 +6,8 @@ import ( "errors" "fmt" - uuid "github.com/satori/go.uuid" "github.com/whywaita/myshoes/pkg/datastore" + "uuid" ) // EnqueueJob add a job diff --git a/pkg/datastore/mysql/job_test.go b/pkg/datastore/mysql/job_test.go index d98c60f..aef7492 100644 --- a/pkg/datastore/mysql/job_test.go +++ b/pkg/datastore/mysql/job_test.go @@ -10,13 +10,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/jmoiron/sqlx" - uuid "github.com/satori/go.uuid" + "uuid" "github.com/whywaita/myshoes/internal/testutils" "github.com/whywaita/myshoes/pkg/datastore" ) -var testJobID = uuid.FromStringOrNil("1b4e5b7a-e3c1-4829-9cfd-eac4183f2c95") +var testJobID = uuid.MustParse("1b4e5b7a-e3c1-4829-9cfd-eac4183f2c95") func TestMySQL_EnqueueJob(t *testing.T) { testDatastore, teardown := testutils.GetTestDatastore() diff --git a/pkg/datastore/mysql/runner.go b/pkg/datastore/mysql/runner.go index b6761f4..5dc8c33 100644 --- a/pkg/datastore/mysql/runner.go +++ b/pkg/datastore/mysql/runner.go @@ -7,8 +7,8 @@ import ( "fmt" "time" - uuid "github.com/satori/go.uuid" "github.com/whywaita/myshoes/pkg/datastore" + "uuid" ) // CreateRunner add a runner diff --git a/pkg/datastore/mysql/runner_test.go b/pkg/datastore/mysql/runner_test.go index d796aa3..3816961 100644 --- a/pkg/datastore/mysql/runner_test.go +++ b/pkg/datastore/mysql/runner_test.go @@ -11,12 +11,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/jmoiron/sqlx" - uuid "github.com/satori/go.uuid" "github.com/whywaita/myshoes/internal/testutils" "github.com/whywaita/myshoes/pkg/datastore" + "uuid" ) -var testRunnerID = uuid.FromStringOrNil("7943e412-c0ae-4068-ab24-3e71a13fbe53") +var testRunnerID = uuid.MustParse("7943e412-c0ae-4068-ab24-3e71a13fbe53") func TestMySQL_CreateRunner(t *testing.T) { testDatastore, teardown := testutils.GetTestDatastore() @@ -218,14 +218,14 @@ func TestMySQL_ListRunnersNotReturnDeleted(t *testing.T) { RepositoryURL: "https://github.com/octocat/Hello-World", RequestWebhook: "{}", } - input.UUID = uuid.FromStringOrNil(fmt.Sprintf(u, i)) + input.UUID = uuid.MustParse(fmt.Sprintf(u, i)) err := testDatastore.CreateRunner(context.Background(), input) if err != nil { t.Fatalf("failed to create runner: %+v", err) } } - err := testDatastore.DeleteRunner(context.Background(), uuid.FromStringOrNil(fmt.Sprintf(u, 0)), time.Now(), "deleted") + err := testDatastore.DeleteRunner(context.Background(), uuid.MustParse(fmt.Sprintf(u, 0)), time.Now(), "deleted") if err != nil { t.Fatalf("failed to delete runner: %+v", err) } @@ -250,7 +250,7 @@ func TestMySQL_ListRunnersNotReturnDeleted(t *testing.T) { RepositoryURL: "https://github.com/octocat/Hello-World", RequestWebhook: "{}", } - r.UUID = uuid.FromStringOrNil(fmt.Sprintf(u, i)) + r.UUID = uuid.MustParse(fmt.Sprintf(u, i)) want = append(want, r) } @@ -285,7 +285,7 @@ func TestMySQL_ListRunnersLogBySince(t *testing.T) { RepositoryURL: "https://github.com/octocat/Hello-World", RequestWebhook: "{}", } - input.UUID = uuid.FromStringOrNil(fmt.Sprintf(u, i)) + input.UUID = uuid.MustParse(fmt.Sprintf(u, i)) err := testDatastore.CreateRunner(context.Background(), input) if err != nil { t.Fatalf("failed to create runner: %+v", err) @@ -314,7 +314,7 @@ func TestMySQL_ListRunnersLogBySince(t *testing.T) { RepositoryURL: "https://github.com/octocat/Hello-World", RequestWebhook: "{}", } - r.UUID = uuid.FromStringOrNil(fmt.Sprintf(u, i)) + r.UUID = uuid.MustParse(fmt.Sprintf(u, i)) want = append(want, r) } diff --git a/pkg/datastore/mysql/target.go b/pkg/datastore/mysql/target.go index 4a0a5a2..0195b7e 100644 --- a/pkg/datastore/mysql/target.go +++ b/pkg/datastore/mysql/target.go @@ -7,8 +7,8 @@ import ( "fmt" "time" - uuid "github.com/satori/go.uuid" "github.com/whywaita/myshoes/pkg/datastore" + "uuid" ) // CreateTarget create a target diff --git a/pkg/datastore/mysql/target_test.go b/pkg/datastore/mysql/target_test.go index f828b0b..75e3f44 100644 --- a/pkg/datastore/mysql/target_test.go +++ b/pkg/datastore/mysql/target_test.go @@ -11,15 +11,15 @@ import ( "github.com/google/go-cmp/cmp" "github.com/jmoiron/sqlx" - uuid "github.com/satori/go.uuid" + "uuid" "github.com/whywaita/myshoes/internal/testutils" "github.com/whywaita/myshoes/pkg/datastore" ) -var testTargetID = uuid.FromStringOrNil("8a72d42c-372c-4e0d-9c6a-4304d44af137") -var testTargetID2 = uuid.FromStringOrNil("d14ccfea-b123-4ada-974e-bbff0937e9c7") -var testTargetID3 = uuid.FromStringOrNil("5c1816ff-4813-46b0-b1ba-30d135a2f3f5") +var testTargetID = uuid.MustParse("8a72d42c-372c-4e0d-9c6a-4304d44af137") +var testTargetID2 = uuid.MustParse("d14ccfea-b123-4ada-974e-bbff0937e9c7") +var testTargetID3 = uuid.MustParse("5c1816ff-4813-46b0-b1ba-30d135a2f3f5") var testScopeOrg = "octocat" var testScopeRepo = "octocat/hello-world" var testScopeRepo2 = "octocat/hello-world2" diff --git a/pkg/metric/scrape_memory.go b/pkg/metric/scrape_memory.go index d4908e4..20ac3a3 100644 --- a/pkg/metric/scrape_memory.go +++ b/pkg/metric/scrape_memory.go @@ -5,7 +5,7 @@ import ( "fmt" "sync/atomic" - uuid "github.com/satori/go.uuid" + "uuid" "github.com/prometheus/client_golang/prometheus" "github.com/whywaita/myshoes/pkg/config" diff --git a/pkg/runner/util.go b/pkg/runner/util.go index 35ba490..1c1fed2 100644 --- a/pkg/runner/util.go +++ b/pkg/runner/util.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - uuid "github.com/satori/go.uuid" "github.com/whywaita/myshoes/pkg/datastore" + "uuid" ) // ToName convert uuid to runner name @@ -16,7 +16,7 @@ func ToName(u string) string { // ToUUID convert runner name to uuid func ToUUID(name string) (uuid.UUID, error) { u := strings.TrimPrefix(name, "myshoes-") - return uuid.FromString(u) + return uuid.Parse(u) } // ToReason convert status from GitHub to datastore.RunnerStatus diff --git a/pkg/starter/starter.go b/pkg/starter/starter.go index 2c92675..fff6324 100644 --- a/pkg/starter/starter.go +++ b/pkg/starter/starter.go @@ -18,7 +18,6 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - uuid "github.com/satori/go.uuid" "github.com/whywaita/myshoes/internal/util" "github.com/whywaita/myshoes/pkg/config" "github.com/whywaita/myshoes/pkg/datastore" @@ -27,6 +26,7 @@ import ( "github.com/whywaita/myshoes/pkg/runner" "github.com/whywaita/myshoes/pkg/shoes" "github.com/whywaita/myshoes/pkg/starter/safety" + "uuid" ) var ( diff --git a/pkg/web/target.go b/pkg/web/target.go index 7c8ea44..f6e16c5 100644 --- a/pkg/web/target.go +++ b/pkg/web/target.go @@ -10,7 +10,7 @@ import ( "time" "github.com/r3labs/diff/v2" - uuid "github.com/satori/go.uuid" + "uuid" "github.com/whywaita/myshoes/pkg/datastore" "github.com/whywaita/myshoes/pkg/gh" @@ -221,7 +221,7 @@ func handleTargetDelete(w http.ResponseWriter, r *http.Request, ds datastore.Dat func parseReqTargetID(r *http.Request) (uuid.UUID, error) { targetIDStr := pat.Param(r, "id") - targetID, err := uuid.FromString(targetIDStr) + targetID, err := uuid.Parse(targetIDStr) if err != nil { return uuid.UUID{}, fmt.Errorf("failed to parse target id: %w", err) } diff --git a/pkg/web/target_create.go b/pkg/web/target_create.go index 6ed0353..d7c7942 100644 --- a/pkg/web/target_create.go +++ b/pkg/web/target_create.go @@ -9,7 +9,7 @@ import ( "net/http" "time" - uuid "github.com/satori/go.uuid" + "uuid" "github.com/whywaita/myshoes/pkg/config" "github.com/whywaita/myshoes/pkg/datastore" diff --git a/pkg/web/target_test.go b/pkg/web/target_test.go index 0f2baaa..3dd189c 100644 --- a/pkg/web/target_test.go +++ b/pkg/web/target_test.go @@ -13,7 +13,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-github/v80/github" - uuid "github.com/satori/go.uuid" + "uuid" "github.com/whywaita/myshoes/internal/testutils" "github.com/whywaita/myshoes/pkg/datastore" diff --git a/pkg/web/webhook.go b/pkg/web/webhook.go index e221eeb..f4047e5 100644 --- a/pkg/web/webhook.go +++ b/pkg/web/webhook.go @@ -11,7 +11,7 @@ import ( "time" "github.com/google/go-github/v80/github" - uuid "github.com/satori/go.uuid" + "uuid" "github.com/whywaita/myshoes/pkg/config" "github.com/whywaita/myshoes/pkg/datastore" From a7595eddaf84954a78f11c01ade8fbb1c14e3ef9 Mon Sep 17 00:00:00 2001 From: whywaita Date: Thu, 20 Aug 2026 14:23:46 +0900 Subject: [PATCH 2/6] ci: bump Dockerfile builder image to golang:1.27 go.mod now requires Go >= 1.27 (standard library uuid), and the Dockerfile uses GOTOOLCHAIN=local, so the builder image must be 1.27. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b4b9421..a70e1ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25 AS builder +FROM golang:1.27 AS builder WORKDIR /go/src/github.com/whywaita/myshoes From 417ced13c2a4c890d965a232c4b4075c706a6ee4 Mon Sep 17 00:00:00 2001 From: whywaita Date: Thu, 20 Aug 2026 14:30:54 +0900 Subject: [PATCH 3/6] ci: pin staticcheck to v0.8.0-rc.1 for Go 1.27 support staticcheck@latest resolves to v0.7.0 (2026.1), whose bundled x/tools cannot decode Go 1.27's export data (version 4) and fails with an internal error. v0.8.0-rc.1 (2026.2rc1) supports Go 1.27 and passes cleanly on the module. --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b0b578f..422c52d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -25,7 +25,7 @@ jobs: go-version-file: 'go.mod' - name: lint run: | - go install honnef.co/go/tools/cmd/staticcheck@latest + go install honnef.co/go/tools/cmd/staticcheck@v0.8.0-rc.1 staticcheck ./... - name: vet run: | From 23e0caef1f98fb34aeaadfed7cbbe94ba8af0d0e Mon Sep 17 00:00:00 2001 From: whywaita Date: Thu, 20 Aug 2026 14:43:34 +0900 Subject: [PATCH 4/6] fix(datastore/mysql): bind and scan UUIDs as strings with std uuid The standard library uuid.UUID is a [16]byte with no driver.Valuer or sql.Scanner, unlike satori/go.uuid. UUID columns are VARCHAR(36), so: - Writes: pass uuid.UUID.String() when binding (CreateTarget, EnqueueJob). - Reads: scan rows into shadow structs whose UUID columns are strings, then parse back into datastore entities with uuid.Parse. Keeps the domain type as std uuid.UUID (no ripple into memory/web/runner/ starter) and confines DB conversion to the mysql package. --- pkg/datastore/mysql/job.go | 56 +++++++++++++++++++++-- pkg/datastore/mysql/runner.go | 83 ++++++++++++++++++++++++++++++----- pkg/datastore/mysql/target.go | 74 +++++++++++++++++++++++++++---- 3 files changed, 190 insertions(+), 23 deletions(-) diff --git a/pkg/datastore/mysql/job.go b/pkg/datastore/mysql/job.go index 92a778f..6b9a966 100644 --- a/pkg/datastore/mysql/job.go +++ b/pkg/datastore/mysql/job.go @@ -5,15 +5,63 @@ import ( "database/sql" "errors" "fmt" + "time" "github.com/whywaita/myshoes/pkg/datastore" "uuid" ) +// rowJob mirrors datastore.Job but stores the UUID columns (uuid, target_id) as +// strings so that database/sql can scan the VARCHAR(36) columns. The standard +// library uuid.UUID is a [16]byte with no sql.Scanner. +type rowJob struct { + UUID string `db:"uuid"` + GHEDomain sql.NullString `db:"ghe_domain"` + Repository string `db:"repository"` + CheckEventJSON string `db:"check_event"` + TargetID string `db:"target_id"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (r rowJob) job() (datastore.Job, error) { + u, err := uuid.Parse(r.UUID) + if err != nil { + return datastore.Job{}, fmt.Errorf("failed to parse job uuid %q: %w", r.UUID, err) + } + + tid, err := uuid.Parse(r.TargetID) + if err != nil { + return datastore.Job{}, fmt.Errorf("failed to parse target id %q: %w", r.TargetID, err) + } + + return datastore.Job{ + UUID: u, + GHEDomain: r.GHEDomain, + Repository: r.Repository, + CheckEventJSON: r.CheckEventJSON, + TargetID: tid, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + }, nil +} + +func jobsFromRows(rows []rowJob) ([]datastore.Job, error) { + js := make([]datastore.Job, 0, len(rows)) + for _, r := range rows { + j, err := r.job() + if err != nil { + return nil, err + } + js = append(js, j) + } + return js, nil +} + // EnqueueJob add a job func (m *MySQL) EnqueueJob(ctx context.Context, job datastore.Job) error { query := `INSERT INTO jobs(uuid, ghe_domain, repository, check_event, target_id) VALUES (?, ?, ?, ?, ?)` - if _, err := m.Conn.ExecContext(ctx, query, job.UUID, job.GHEDomain, job.Repository, job.CheckEventJSON, job.TargetID.String()); err != nil { + if _, err := m.Conn.ExecContext(ctx, query, job.UUID.String(), job.GHEDomain, job.Repository, job.CheckEventJSON, job.TargetID.String()); err != nil { return fmt.Errorf("failed to execute INSERT query: %w", err) } @@ -29,9 +77,9 @@ func (m *MySQL) EnqueueJob(ctx context.Context, job datastore.Job) error { // ListJobs get all jobs func (m *MySQL) ListJobs(ctx context.Context) ([]datastore.Job, error) { - var jobs []datastore.Job + var rows []rowJob query := `SELECT uuid, ghe_domain, repository, check_event, target_id, created_at, updated_at FROM jobs` - if err := m.Conn.SelectContext(ctx, &jobs, query); err != nil { + if err := m.Conn.SelectContext(ctx, &rows, query); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound } @@ -39,7 +87,7 @@ func (m *MySQL) ListJobs(ctx context.Context) ([]datastore.Job, error) { return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } - return jobs, nil + return jobsFromRows(rows) } // DeleteJob delete a job diff --git a/pkg/datastore/mysql/runner.go b/pkg/datastore/mysql/runner.go index 5dc8c33..102fe97 100644 --- a/pkg/datastore/mysql/runner.go +++ b/pkg/datastore/mysql/runner.go @@ -11,6 +11,63 @@ import ( "uuid" ) +// rowRunner mirrors datastore.Runner but stores the UUID columns (runner_id, +// target_id) as strings so that database/sql can scan the VARCHAR(36) columns. +// The standard library uuid.UUID is a [16]byte with no sql.Scanner. +type rowRunner struct { + UUID string `db:"runner_id"` + ShoesType string `db:"shoes_type"` + IPAddress string `db:"ip_address"` + TargetID string `db:"target_id"` + CloudID string `db:"cloud_id"` + ResourceType datastore.ResourceType `db:"resource_type"` + RunnerUser sql.NullString `db:"runner_user"` + ProviderURL sql.NullString `db:"provider_url"` + RepositoryURL string `db:"repository_url"` + RequestWebhook string `db:"request_webhook"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (r rowRunner) runner() (datastore.Runner, error) { + u, err := uuid.Parse(r.UUID) + if err != nil { + return datastore.Runner{}, fmt.Errorf("failed to parse runner uuid %q: %w", r.UUID, err) + } + + tid, err := uuid.Parse(r.TargetID) + if err != nil { + return datastore.Runner{}, fmt.Errorf("failed to parse target id %q: %w", r.TargetID, err) + } + + return datastore.Runner{ + UUID: u, + ShoesType: r.ShoesType, + IPAddress: r.IPAddress, + TargetID: tid, + CloudID: r.CloudID, + ResourceType: r.ResourceType, + RunnerUser: r.RunnerUser, + ProviderURL: r.ProviderURL, + RepositoryURL: r.RepositoryURL, + RequestWebhook: r.RequestWebhook, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + }, nil +} + +func runnersFromRows(rows []rowRunner) ([]datastore.Runner, error) { + rs := make([]datastore.Runner, 0, len(rows)) + for _, r := range rows { + runner, err := r.runner() + if err != nil { + return nil, err + } + rs = append(rs, runner) + } + return rs, nil +} + // CreateRunner add a runner func (m *MySQL) CreateRunner(ctx context.Context, runner datastore.Runner) error { tx := m.Conn.MustBegin() @@ -42,10 +99,10 @@ func (m *MySQL) CreateRunner(ctx context.Context, runner datastore.Runner) error // ListRunners get a not deleted runners func (m *MySQL) ListRunners(ctx context.Context) ([]datastore.Runner, error) { - var runners []datastore.Runner + var rows []rowRunner query := `SELECT runner.runner_id, detail.shoes_type, detail.ip_address, detail.target_id, detail.cloud_id, detail.created_at, detail.updated_at, detail.resource_type, detail.repository_url, detail.request_webhook, detail.runner_user, detail.provider_url FROM runners_running AS runner JOIN runner_detail AS detail ON runner.runner_id = detail.runner_id` - err := m.Conn.SelectContext(ctx, &runners, query) + err := m.Conn.SelectContext(ctx, &rows, query) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound @@ -54,15 +111,15 @@ func (m *MySQL) ListRunners(ctx context.Context) ([]datastore.Runner, error) { return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } - return runners, nil + return runnersFromRows(rows) } // ListRunnersByTargetID get a not deleted runners that has target_id func (m *MySQL) ListRunnersByTargetID(ctx context.Context, targetID uuid.UUID) ([]datastore.Runner, error) { - var runners []datastore.Runner + var rows []rowRunner query := `SELECT runner.runner_id, detail.shoes_type, detail.ip_address, detail.target_id, detail.cloud_id, detail.created_at, detail.updated_at, detail.resource_type, detail.repository_url, detail.request_webhook, detail.runner_user, detail.provider_url FROM runners_running AS runner JOIN runner_detail AS detail ON runner.runner_id = detail.runner_id WHERE detail.target_id = ?` - err := m.Conn.SelectContext(ctx, &runners, query, targetID) + err := m.Conn.SelectContext(ctx, &rows, query, targetID.String()) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound @@ -71,15 +128,15 @@ func (m *MySQL) ListRunnersByTargetID(ctx context.Context, targetID uuid.UUID) ( return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } - return runners, nil + return runnersFromRows(rows) } // ListRunnersLogBySince ListRunnerLog get a runners since time func (m *MySQL) ListRunnersLogBySince(ctx context.Context, since time.Time) ([]datastore.Runner, error) { - var runners []datastore.Runner + var rows []rowRunner query := `SELECT runner_id, shoes_type, ip_address, target_id, cloud_id, created_at, updated_at, resource_type, repository_url, request_webhook, runner_user, provider_url FROM runner_detail WHERE created_at > ?` - err := m.Conn.SelectContext(ctx, &runners, query, since) + err := m.Conn.SelectContext(ctx, &rows, query, since) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound @@ -88,15 +145,15 @@ func (m *MySQL) ListRunnersLogBySince(ctx context.Context, since time.Time) ([]d return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } - return runners, nil + return runnersFromRows(rows) } // GetRunner get a runner func (m *MySQL) GetRunner(ctx context.Context, id uuid.UUID) (*datastore.Runner, error) { - var r datastore.Runner + var row rowRunner query := `SELECT runner_id, shoes_type, ip_address, target_id, cloud_id, created_at, updated_at, resource_type, repository_url, request_webhook, runner_user, provider_url FROM runner_detail WHERE runner_id = ?` - if err := m.Conn.GetContext(ctx, &r, query, id.String()); err != nil { + if err := m.Conn.GetContext(ctx, &row, query, id.String()); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound } @@ -104,6 +161,10 @@ func (m *MySQL) GetRunner(ctx context.Context, id uuid.UUID) (*datastore.Runner, return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } + r, err := row.runner() + if err != nil { + return nil, err + } return &r, nil } diff --git a/pkg/datastore/mysql/target.go b/pkg/datastore/mysql/target.go index 0195b7e..a9c1da9 100644 --- a/pkg/datastore/mysql/target.go +++ b/pkg/datastore/mysql/target.go @@ -11,6 +11,56 @@ import ( "uuid" ) +// rowTarget mirrors datastore.Target but stores the UUID column as a string so +// that database/sql can scan the VARCHAR(36) uuid column. The standard library +// uuid.UUID is a [16]byte with no sql.Scanner, so it cannot be scanned directly. +type rowTarget struct { + UUID string `db:"uuid"` + Scope string `db:"scope"` + GitHubToken string `db:"github_token"` + TokenExpiredAt time.Time `db:"token_expired_at"` + GHEDomain sql.NullString `db:"ghe_domain"` + ResourceType datastore.ResourceType `db:"resource_type"` + ProviderURL sql.NullString `db:"provider_url"` + Status datastore.TargetStatus `db:"status"` + StatusDescription sql.NullString `db:"status_description"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (r rowTarget) target() (datastore.Target, error) { + u, err := uuid.Parse(r.UUID) + if err != nil { + return datastore.Target{}, fmt.Errorf("failed to parse target uuid %q: %w", r.UUID, err) + } + + return datastore.Target{ + UUID: u, + Scope: r.Scope, + GitHubToken: r.GitHubToken, + TokenExpiredAt: r.TokenExpiredAt, + GHEDomain: r.GHEDomain, + ResourceType: r.ResourceType, + ProviderURL: r.ProviderURL, + Status: r.Status, + StatusDescription: r.StatusDescription, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + }, nil +} + +func targetsFromRows(rows []rowTarget) ([]datastore.Target, error) { + ts := make([]datastore.Target, 0, len(rows)) + for _, r := range rows { + t, err := r.target() + if err != nil { + return nil, err + } + ts = append(ts, t) + } + return ts, nil +} + // CreateTarget create a target func (m *MySQL) CreateTarget(ctx context.Context, target datastore.Target) error { expiredAtRFC3339 := target.TokenExpiredAt.Format("2006-01-02 15:04:05") @@ -19,7 +69,7 @@ func (m *MySQL) CreateTarget(ctx context.Context, target datastore.Target) error if _, err := m.Conn.ExecContext( ctx, query, - target.UUID, + target.UUID.String(), target.Scope, target.GHEDomain, target.GitHubToken, @@ -35,9 +85,9 @@ func (m *MySQL) CreateTarget(ctx context.Context, target datastore.Target) error // GetTarget get a target func (m *MySQL) GetTarget(ctx context.Context, id uuid.UUID) (*datastore.Target, error) { - var t datastore.Target + var row rowTarget query := `SELECT uuid, scope, github_token, token_expired_at, resource_type, provider_url, status, status_description, created_at, updated_at FROM targets WHERE uuid = ?` - if err := m.Conn.GetContext(ctx, &t, query, id.String()); err != nil { + if err := m.Conn.GetContext(ctx, &row, query, id.String()); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound } @@ -45,14 +95,18 @@ func (m *MySQL) GetTarget(ctx context.Context, id uuid.UUID) (*datastore.Target, return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } + t, err := row.target() + if err != nil { + return nil, err + } return &t, nil } // GetTargetByScope get a target from scope func (m *MySQL) GetTargetByScope(ctx context.Context, scope string) (*datastore.Target, error) { - var t datastore.Target + var row rowTarget query := `SELECT uuid, scope, github_token, token_expired_at, resource_type, provider_url, status, status_description, created_at, updated_at FROM targets WHERE scope = ?` - if err := m.Conn.GetContext(ctx, &t, query, scope); err != nil { + if err := m.Conn.GetContext(ctx, &row, query, scope); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound } @@ -60,18 +114,22 @@ func (m *MySQL) GetTargetByScope(ctx context.Context, scope string) (*datastore. return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } + t, err := row.target() + if err != nil { + return nil, err + } return &t, nil } // ListTargets get a all target func (m *MySQL) ListTargets(ctx context.Context) ([]datastore.Target, error) { - var ts []datastore.Target + var rows []rowTarget query := `SELECT uuid, scope, github_token, token_expired_at, resource_type, provider_url, status, status_description, created_at, updated_at FROM targets` - if err := m.Conn.SelectContext(ctx, &ts, query); err != nil { + if err := m.Conn.SelectContext(ctx, &rows, query); err != nil { return nil, fmt.Errorf("failed to SELECT query: %w", err) } - return ts, nil + return targetsFromRows(rows) } // DeleteTarget delete a target From b95ca613e8709d9a4025cd99ae4e59bd5d1b9d77 Mon Sep 17 00:00:00 2001 From: whywaita Date: Thu, 20 Aug 2026 14:49:57 +0900 Subject: [PATCH 5/6] fix(datastore/mysql_test): bind UUIDs as strings in test SQL helpers The sql.*FromSQL test helpers bound uuid.UUID directly to prepared statements, which fails with std uuid (no driver.Valuer/Scanner). Bind uuid.String() and scan into local shadow rows (UUID columns as strings) then parse back into datastore entities. --- pkg/datastore/mysql/job_test.go | 36 +++++++++++++++++-- pkg/datastore/mysql/runner_test.go | 58 +++++++++++++++++++++++++----- pkg/datastore/mysql/target_test.go | 40 +++++++++++++++++++-- 3 files changed, 119 insertions(+), 15 deletions(-) diff --git a/pkg/datastore/mysql/job_test.go b/pkg/datastore/mysql/job_test.go index aef7492..b2e666e 100644 --- a/pkg/datastore/mysql/job_test.go +++ b/pkg/datastore/mysql/job_test.go @@ -206,16 +206,46 @@ func TestMySQL_DeleteJob(t *testing.T) { } } +type sqlJobRow struct { + UUID string `db:"uuid"` + GHEDomain sql.NullString `db:"ghe_domain"` + Repository string `db:"repository"` + CheckEventJSON string `db:"check_event"` + TargetID string `db:"target_id"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (r sqlJobRow) job() (*datastore.Job, error) { + u, err := uuid.Parse(r.UUID) + if err != nil { + return nil, err + } + tid, err := uuid.Parse(r.TargetID) + if err != nil { + return nil, err + } + return &datastore.Job{ + UUID: u, + GHEDomain: r.GHEDomain, + Repository: r.Repository, + CheckEventJSON: r.CheckEventJSON, + TargetID: tid, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + }, nil +} + func getJobFromSQL(testDB *sqlx.DB, id uuid.UUID) (*datastore.Job, error) { - var j datastore.Job + var row sqlJobRow query := `SELECT uuid, ghe_domain, repository, check_event, target_id FROM jobs WHERE uuid = ?` stmt, err := testDB.Preparex(query) if err != nil { return nil, fmt.Errorf("failed to prepare: %w", err) } - err = stmt.Get(&j, id) + err = stmt.Get(&row, id.String()) if err != nil { return nil, fmt.Errorf("failed to get job: %w", err) } - return &j, nil + return row.job() } diff --git a/pkg/datastore/mysql/runner_test.go b/pkg/datastore/mysql/runner_test.go index 3816961..610291e 100644 --- a/pkg/datastore/mysql/runner_test.go +++ b/pkg/datastore/mysql/runner_test.go @@ -462,46 +462,86 @@ func TestMySQL_DeleteRunner(t *testing.T) { } } +type sqlRunnerRow struct { + UUID string `db:"runner_id"` + ShoesType string `db:"shoes_type"` + IPAddress string `db:"ip_address"` + TargetID string `db:"target_id"` + CloudID string `db:"cloud_id"` + ResourceType datastore.ResourceType `db:"resource_type"` + RunnerUser sql.NullString `db:"runner_user"` + ProviderURL sql.NullString `db:"provider_url"` + RepositoryURL string `db:"repository_url"` + RequestWebhook string `db:"request_webhook"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (r sqlRunnerRow) runner() (*datastore.Runner, error) { + u, err := uuid.Parse(r.UUID) + if err != nil { + return nil, err + } + tid, err := uuid.Parse(r.TargetID) + if err != nil { + return nil, err + } + return &datastore.Runner{ + UUID: u, + ShoesType: r.ShoesType, + IPAddress: r.IPAddress, + TargetID: tid, + CloudID: r.CloudID, + ResourceType: r.ResourceType, + RunnerUser: r.RunnerUser, + ProviderURL: r.ProviderURL, + RepositoryURL: r.RepositoryURL, + RequestWebhook: r.RequestWebhook, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + }, nil +} + func getRunnerFromSQL(testDB *sqlx.DB, id uuid.UUID) (*datastore.Runner, error) { - var r datastore.Runner + var row sqlRunnerRow query := `SELECT runner_id, shoes_type, ip_address, target_id, cloud_id, created_at, updated_at, resource_type, repository_url, request_webhook, runner_user, provider_url FROM runner_detail WHERE runner_id = ?` stmt, err := testDB.Preparex(query) if err != nil { return nil, fmt.Errorf("failed to prepare: %w", err) } - err = stmt.Get(&r, id) + err = stmt.Get(&row, id.String()) if err != nil { return nil, fmt.Errorf("failed to get runner: %w", err) } - return &r, nil + return row.runner() } func getRunningRunnerFromSQL(testDB *sqlx.DB, id uuid.UUID) (*datastore.Runner, error) { - var r datastore.Runner + var row sqlRunnerRow query := `SELECT detail.runner_id, shoes_type, ip_address, target_id, cloud_id, detail.created_at, updated_at, detail.resource_type, detail.repository_url, detail.request_webhook FROM runner_detail AS detail JOIN runnesr_running AS running ON detail.runner_id = running.runner_id WHERE detail.runner_id = ?` stmt, err := testDB.Preparex(query) if err != nil { return nil, fmt.Errorf("failed to prepare: %w", err) } - err = stmt.Get(&r, id) + err = stmt.Get(&row, id.String()) if err != nil { return nil, fmt.Errorf("failed to get runner: %w", err) } - return &r, nil + return row.runner() } func getDeletedRunnerFromSQL(testDB *sqlx.DB, id uuid.UUID) (*datastore.Runner, error) { - var r datastore.Runner + var row sqlRunnerRow query := `SELECT detail.runner_id, shoes_type, ip_address, target_id, cloud_id, detail.created_at, updated_at, detail.resource_type, detail.repository_url, detail.request_webhook FROM runner_detail AS detail JOIN runners_deleted AS deleted ON detail.runner_id = deleted.runner_id WHERE detail.runner_id = ?` stmt, err := testDB.Preparex(query) if err != nil { return nil, fmt.Errorf("failed to prepare: %w", err) } - err = stmt.Get(&r, id) + err = stmt.Get(&row, id.String()) if err != nil { return nil, fmt.Errorf("failed to get runner: %w", err) } - return &r, nil + return row.runner() } diff --git a/pkg/datastore/mysql/target_test.go b/pkg/datastore/mysql/target_test.go index 75e3f44..c7e4aad 100644 --- a/pkg/datastore/mysql/target_test.go +++ b/pkg/datastore/mysql/target_test.go @@ -865,16 +865,50 @@ func TestMySQL_UpdateTargetParam(t *testing.T) { } } +type sqlTargetRow struct { + UUID string `db:"uuid"` + Scope string `db:"scope"` + GitHubToken string `db:"github_token"` + TokenExpiredAt time.Time `db:"token_expired_at"` + GHEDomain sql.NullString `db:"ghe_domain"` + ResourceType datastore.ResourceType `db:"resource_type"` + ProviderURL sql.NullString `db:"provider_url"` + Status datastore.TargetStatus `db:"status"` + StatusDescription sql.NullString `db:"status_description"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (r sqlTargetRow) target() (*datastore.Target, error) { + u, err := uuid.Parse(r.UUID) + if err != nil { + return nil, err + } + return &datastore.Target{ + UUID: u, + Scope: r.Scope, + GitHubToken: r.GitHubToken, + TokenExpiredAt: r.TokenExpiredAt, + GHEDomain: r.GHEDomain, + ResourceType: r.ResourceType, + ProviderURL: r.ProviderURL, + Status: r.Status, + StatusDescription: r.StatusDescription, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + }, nil +} + func getTargetFromSQL(testDB *sqlx.DB, uuid uuid.UUID) (*datastore.Target, error) { - var t datastore.Target + var row sqlTargetRow query := `SELECT uuid, scope, ghe_domain, github_token, token_expired_at, resource_type, provider_url, status, status_description, created_at, updated_at FROM targets WHERE uuid = ?` stmt, err := testDB.Preparex(query) if err != nil { return nil, fmt.Errorf("failed to prepare: %w", err) } - err = stmt.Get(&t, uuid) + err = stmt.Get(&row, uuid.String()) if err != nil { return nil, fmt.Errorf("failed to get target: %w", err) } - return &t, nil + return row.target() } From 7feabe1845a531737a119bd1208c85d11d29b1c9 Mon Sep 17 00:00:00 2001 From: whywaita Date: Thu, 20 Aug 2026 17:15:05 +0900 Subject: [PATCH 6/6] refactor(datastore): add datastore.UUID with driver.Valuer/sql.Scanner Implement a datastore.UUID type wrapping std uuid.UUID that implements driver.Valuer and sql.Scanner, and use it for the datastore entity structs and interface. This lets sqlx bind and scan UUID columns directly, so the mysql layer no longer needs shadow structs + string conversion. - New pkg/datastore/uuid.go: datastore.UUID (Valuer + Scanner) - datastore entity structs and Datastore interface use datastore.UUID - mysql: revert to direct queries (CreateTarget/EnqueueJob bind fields as-is) - memory/web/runner/starter/metric: adapted to datastore.UUID - tests updated accordingly Responds to the review comment on PR #278. --- pkg/datastore/interface.go | 34 +++++----- pkg/datastore/memory/memory.go | 32 +++++---- pkg/datastore/mysql/job.go | 61 ++---------------- pkg/datastore/mysql/job_test.go | 42 ++---------- pkg/datastore/mysql/runner.go | 100 ++++++----------------------- pkg/datastore/mysql/runner_test.go | 80 ++++++----------------- pkg/datastore/mysql/target.go | 93 +++++---------------------- pkg/datastore/mysql/target_test.go | 64 +++++------------- pkg/datastore/uuid.go | 45 +++++++++++++ pkg/metric/scrape_memory.go | 6 +- pkg/starter/starter.go | 2 +- pkg/web/target.go | 10 +-- pkg/web/target_create.go | 6 +- pkg/web/target_test.go | 8 +-- pkg/web/webhook.go | 2 +- 15 files changed, 175 insertions(+), 410 deletions(-) create mode 100644 pkg/datastore/uuid.go diff --git a/pkg/datastore/interface.go b/pkg/datastore/interface.go index 84843e1..534e2d3 100644 --- a/pkg/datastore/interface.go +++ b/pkg/datastore/interface.go @@ -9,8 +9,6 @@ import ( "strings" "time" - "uuid" - "github.com/whywaita/myshoes/pkg/gh" "github.com/whywaita/myshoes/pkg/logger" ) @@ -29,27 +27,27 @@ var ( // Datastore is persistent storage type Datastore interface { CreateTarget(ctx context.Context, target Target) error - GetTarget(ctx context.Context, id uuid.UUID) (*Target, error) + GetTarget(ctx context.Context, id UUID) (*Target, error) GetTargetByScope(ctx context.Context, scope string) (*Target, error) ListTargets(ctx context.Context) ([]Target, error) - DeleteTarget(ctx context.Context, id uuid.UUID) error + DeleteTarget(ctx context.Context, id UUID) error // Deprecated: Use datastore.UpdateTargetStatus. - UpdateTargetStatus(ctx context.Context, targetID uuid.UUID, newStatus TargetStatus, description string) error - UpdateToken(ctx context.Context, targetID uuid.UUID, newToken string, newExpiredAt time.Time) error + UpdateTargetStatus(ctx context.Context, targetID UUID, newStatus TargetStatus, description string) error + UpdateToken(ctx context.Context, targetID UUID, newToken string, newExpiredAt time.Time) error - UpdateTargetParam(ctx context.Context, targetID uuid.UUID, newResourceType ResourceType, newProviderURL sql.NullString) error + UpdateTargetParam(ctx context.Context, targetID UUID, newResourceType ResourceType, newProviderURL sql.NullString) error EnqueueJob(ctx context.Context, job Job) error ListJobs(ctx context.Context) ([]Job, error) - DeleteJob(ctx context.Context, id uuid.UUID) error + DeleteJob(ctx context.Context, id UUID) error CreateRunner(ctx context.Context, runner Runner) error ListRunners(ctx context.Context) ([]Runner, error) - ListRunnersByTargetID(ctx context.Context, targetID uuid.UUID) ([]Runner, error) + ListRunnersByTargetID(ctx context.Context, targetID UUID) ([]Runner, error) ListRunnersLogBySince(ctx context.Context, since time.Time) ([]Runner, error) - GetRunner(ctx context.Context, id uuid.UUID) (*Runner, error) - DeleteRunner(ctx context.Context, id uuid.UUID, deletedAt time.Time, reason RunnerStatus) error + GetRunner(ctx context.Context, id UUID) (*Runner, error) + DeleteRunner(ctx context.Context, id UUID, deletedAt time.Time, reason RunnerStatus) error // Lock GetLock(ctx context.Context) error @@ -58,8 +56,8 @@ type Datastore interface { // Target is a target repository that will add auto-scaling runner. type Target struct { - UUID uuid.UUID `db:"uuid" json:"id"` - Scope string `db:"scope" json:"scope"` // repo (:owner/:repo) or org (:organization) + UUID UUID `db:"uuid" json:"id"` + Scope string `db:"scope" json:"scope"` // repo (:owner/:repo) or org (:organization) // deprecated GitHubToken string `db:"github_token" json:"github_token"` TokenExpiredAt time.Time `db:"token_expired_at" json:"token_expired_at"` @@ -107,7 +105,7 @@ func ListTargets(ctx context.Context, ds Datastore) ([]Target, error) { } // UpdateTargetStatus update datastore -func UpdateTargetStatus(ctx context.Context, ds Datastore, targetID uuid.UUID, newStatus TargetStatus, description string) error { +func UpdateTargetStatus(ctx context.Context, ds Datastore, targetID UUID, newStatus TargetStatus, description string) error { target, err := ds.GetTarget(ctx, targetID) if err != nil { return fmt.Errorf("failed to get target: %w", err) @@ -170,11 +168,11 @@ const ( // Job is a runner job type Job struct { - UUID uuid.UUID `db:"uuid"` + UUID UUID `db:"uuid"` GHEDomain sql.NullString `db:"ghe_domain"` Repository string `db:"repository"` // repo (:owner/:repo) CheckEventJSON string `db:"check_event"` - TargetID uuid.UUID `db:"target_id"` + TargetID UUID `db:"target_id"` CreatedAt time.Time `db:"created_at" json:"created_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"` } @@ -198,10 +196,10 @@ func (j *Job) RepoURL() string { // Runner is a runner type Runner struct { - UUID uuid.UUID `db:"runner_id"` + UUID UUID `db:"runner_id"` ShoesType string `db:"shoes_type"` IPAddress string `db:"ip_address"` - TargetID uuid.UUID `db:"target_id"` + TargetID UUID `db:"target_id"` CloudID string `db:"cloud_id"` Deleted bool `db:"deleted"` Status RunnerStatus `db:"status"` diff --git a/pkg/datastore/memory/memory.go b/pkg/datastore/memory/memory.go index 5a4cccf..ba70ca5 100644 --- a/pkg/datastore/memory/memory.go +++ b/pkg/datastore/memory/memory.go @@ -7,25 +7,23 @@ import ( "sync" "time" - "uuid" - "github.com/whywaita/myshoes/pkg/datastore" ) // Memory is implement datastore on-memory type Memory struct { mu *sync.RWMutex - targets map[uuid.UUID]datastore.Target - jobs map[uuid.UUID]datastore.Job - runners map[uuid.UUID]datastore.Runner + targets map[datastore.UUID]datastore.Target + jobs map[datastore.UUID]datastore.Job + runners map[datastore.UUID]datastore.Runner } // New create map func New() (*Memory, error) { m := &sync.RWMutex{} - t := map[uuid.UUID]datastore.Target{} - j := map[uuid.UUID]datastore.Job{} - r := map[uuid.UUID]datastore.Runner{} + t := map[datastore.UUID]datastore.Target{} + j := map[datastore.UUID]datastore.Job{} + r := map[datastore.UUID]datastore.Runner{} return &Memory{ mu: m, @@ -45,7 +43,7 @@ func (m *Memory) CreateTarget(ctx context.Context, target datastore.Target) erro } // GetTarget get a target -func (m *Memory) GetTarget(ctx context.Context, id uuid.UUID) (*datastore.Target, error) { +func (m *Memory) GetTarget(ctx context.Context, id datastore.UUID) (*datastore.Target, error) { m.mu.RLock() defer m.mu.RUnlock() @@ -87,7 +85,7 @@ func (m *Memory) ListTargets(ctx context.Context) ([]datastore.Target, error) { } // DeleteTarget delete a target -func (m *Memory) DeleteTarget(ctx context.Context, id uuid.UUID) error { +func (m *Memory) DeleteTarget(ctx context.Context, id datastore.UUID) error { m.mu.Lock() defer m.mu.Unlock() @@ -96,7 +94,7 @@ func (m *Memory) DeleteTarget(ctx context.Context, id uuid.UUID) error { } // UpdateTargetStatus update status in target -func (m *Memory) UpdateTargetStatus(ctx context.Context, targetID uuid.UUID, newStatus datastore.TargetStatus, description string) error { +func (m *Memory) UpdateTargetStatus(ctx context.Context, targetID datastore.UUID, newStatus datastore.TargetStatus, description string) error { m.mu.Lock() defer m.mu.Unlock() @@ -119,7 +117,7 @@ func (m *Memory) UpdateTargetStatus(ctx context.Context, targetID uuid.UUID, new } // UpdateToken update token in target -func (m *Memory) UpdateToken(ctx context.Context, targetID uuid.UUID, newToken string, newExpiredAt time.Time) error { +func (m *Memory) UpdateToken(ctx context.Context, targetID datastore.UUID, newToken string, newExpiredAt time.Time) error { m.mu.Lock() defer m.mu.Unlock() @@ -135,7 +133,7 @@ func (m *Memory) UpdateToken(ctx context.Context, targetID uuid.UUID, newToken s } // UpdateTargetParam update parameter of target -func (m *Memory) UpdateTargetParam(ctx context.Context, targetID uuid.UUID, newResourceType datastore.ResourceType, newProviderURL string) error { +func (m *Memory) UpdateTargetParam(ctx context.Context, targetID datastore.UUID, newResourceType datastore.ResourceType, newProviderURL string) error { m.mu.Lock() defer m.mu.Unlock() @@ -176,7 +174,7 @@ func (m *Memory) ListJobs(ctx context.Context) ([]datastore.Job, error) { } // DeleteJob delete a job -func (m *Memory) DeleteJob(ctx context.Context, id uuid.UUID) error { +func (m *Memory) DeleteJob(ctx context.Context, id datastore.UUID) error { m.mu.Lock() defer m.mu.Unlock() @@ -208,7 +206,7 @@ func (m *Memory) ListRunners(ctx context.Context) ([]datastore.Runner, error) { } // ListRunnersByTargetID get a not deleted runners that has target_id -func (m *Memory) ListRunnersByTargetID(ctx context.Context, targetID uuid.UUID) ([]datastore.Runner, error) { +func (m *Memory) ListRunnersByTargetID(ctx context.Context, targetID datastore.UUID) ([]datastore.Runner, error) { m.mu.Lock() defer m.mu.Unlock() @@ -238,7 +236,7 @@ func (m *Memory) ListRunnersLogBySince(ctx context.Context, since time.Time) ([] } // GetRunner get a runner -func (m *Memory) GetRunner(ctx context.Context, id uuid.UUID) (*datastore.Runner, error) { +func (m *Memory) GetRunner(ctx context.Context, id datastore.UUID) (*datastore.Runner, error) { m.mu.Lock() defer m.mu.Unlock() @@ -251,7 +249,7 @@ func (m *Memory) GetRunner(ctx context.Context, id uuid.UUID) (*datastore.Runner } // DeleteRunner delete a runner -func (m *Memory) DeleteRunner(ctx context.Context, id uuid.UUID, deletedAt time.Time, reason datastore.RunnerStatus) error { +func (m *Memory) DeleteRunner(ctx context.Context, id datastore.UUID, deletedAt time.Time, reason datastore.RunnerStatus) error { m.mu.Lock() defer m.mu.Unlock() diff --git a/pkg/datastore/mysql/job.go b/pkg/datastore/mysql/job.go index 6b9a966..5af4bd5 100644 --- a/pkg/datastore/mysql/job.go +++ b/pkg/datastore/mysql/job.go @@ -5,63 +5,14 @@ import ( "database/sql" "errors" "fmt" - "time" "github.com/whywaita/myshoes/pkg/datastore" - "uuid" ) -// rowJob mirrors datastore.Job but stores the UUID columns (uuid, target_id) as -// strings so that database/sql can scan the VARCHAR(36) columns. The standard -// library uuid.UUID is a [16]byte with no sql.Scanner. -type rowJob struct { - UUID string `db:"uuid"` - GHEDomain sql.NullString `db:"ghe_domain"` - Repository string `db:"repository"` - CheckEventJSON string `db:"check_event"` - TargetID string `db:"target_id"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` -} - -func (r rowJob) job() (datastore.Job, error) { - u, err := uuid.Parse(r.UUID) - if err != nil { - return datastore.Job{}, fmt.Errorf("failed to parse job uuid %q: %w", r.UUID, err) - } - - tid, err := uuid.Parse(r.TargetID) - if err != nil { - return datastore.Job{}, fmt.Errorf("failed to parse target id %q: %w", r.TargetID, err) - } - - return datastore.Job{ - UUID: u, - GHEDomain: r.GHEDomain, - Repository: r.Repository, - CheckEventJSON: r.CheckEventJSON, - TargetID: tid, - CreatedAt: r.CreatedAt, - UpdatedAt: r.UpdatedAt, - }, nil -} - -func jobsFromRows(rows []rowJob) ([]datastore.Job, error) { - js := make([]datastore.Job, 0, len(rows)) - for _, r := range rows { - j, err := r.job() - if err != nil { - return nil, err - } - js = append(js, j) - } - return js, nil -} - // EnqueueJob add a job func (m *MySQL) EnqueueJob(ctx context.Context, job datastore.Job) error { query := `INSERT INTO jobs(uuid, ghe_domain, repository, check_event, target_id) VALUES (?, ?, ?, ?, ?)` - if _, err := m.Conn.ExecContext(ctx, query, job.UUID.String(), job.GHEDomain, job.Repository, job.CheckEventJSON, job.TargetID.String()); err != nil { + if _, err := m.Conn.ExecContext(ctx, query, job.UUID, job.GHEDomain, job.Repository, job.CheckEventJSON, job.TargetID); err != nil { return fmt.Errorf("failed to execute INSERT query: %w", err) } @@ -77,9 +28,9 @@ func (m *MySQL) EnqueueJob(ctx context.Context, job datastore.Job) error { // ListJobs get all jobs func (m *MySQL) ListJobs(ctx context.Context) ([]datastore.Job, error) { - var rows []rowJob + var jobs []datastore.Job query := `SELECT uuid, ghe_domain, repository, check_event, target_id, created_at, updated_at FROM jobs` - if err := m.Conn.SelectContext(ctx, &rows, query); err != nil { + if err := m.Conn.SelectContext(ctx, &jobs, query); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound } @@ -87,13 +38,13 @@ func (m *MySQL) ListJobs(ctx context.Context) ([]datastore.Job, error) { return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } - return jobsFromRows(rows) + return jobs, nil } // DeleteJob delete a job -func (m *MySQL) DeleteJob(ctx context.Context, id uuid.UUID) error { +func (m *MySQL) DeleteJob(ctx context.Context, id datastore.UUID) error { query := `DELETE FROM jobs WHERE uuid = ?` - if _, err := m.Conn.ExecContext(ctx, query, id.String()); err != nil { + if _, err := m.Conn.ExecContext(ctx, query, id); err != nil { return fmt.Errorf("failed to execute DELETE query: %w", err) } diff --git a/pkg/datastore/mysql/job_test.go b/pkg/datastore/mysql/job_test.go index b2e666e..7bae6f0 100644 --- a/pkg/datastore/mysql/job_test.go +++ b/pkg/datastore/mysql/job_test.go @@ -16,7 +16,7 @@ import ( "github.com/whywaita/myshoes/pkg/datastore" ) -var testJobID = uuid.MustParse("1b4e5b7a-e3c1-4829-9cfd-eac4183f2c95") +var testJobID = datastore.UUID{UUID: uuid.MustParse("1b4e5b7a-e3c1-4829-9cfd-eac4183f2c95")} func TestMySQL_EnqueueJob(t *testing.T) { testDatastore, teardown := testutils.GetTestDatastore() @@ -174,7 +174,7 @@ func TestMySQL_DeleteJob(t *testing.T) { } tests := []struct { - input uuid.UUID + input datastore.UUID want *datastore.Job err bool }{ @@ -206,46 +206,16 @@ func TestMySQL_DeleteJob(t *testing.T) { } } -type sqlJobRow struct { - UUID string `db:"uuid"` - GHEDomain sql.NullString `db:"ghe_domain"` - Repository string `db:"repository"` - CheckEventJSON string `db:"check_event"` - TargetID string `db:"target_id"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` -} - -func (r sqlJobRow) job() (*datastore.Job, error) { - u, err := uuid.Parse(r.UUID) - if err != nil { - return nil, err - } - tid, err := uuid.Parse(r.TargetID) - if err != nil { - return nil, err - } - return &datastore.Job{ - UUID: u, - GHEDomain: r.GHEDomain, - Repository: r.Repository, - CheckEventJSON: r.CheckEventJSON, - TargetID: tid, - CreatedAt: r.CreatedAt, - UpdatedAt: r.UpdatedAt, - }, nil -} - -func getJobFromSQL(testDB *sqlx.DB, id uuid.UUID) (*datastore.Job, error) { - var row sqlJobRow +func getJobFromSQL(testDB *sqlx.DB, id datastore.UUID) (*datastore.Job, error) { + var j datastore.Job query := `SELECT uuid, ghe_domain, repository, check_event, target_id FROM jobs WHERE uuid = ?` stmt, err := testDB.Preparex(query) if err != nil { return nil, fmt.Errorf("failed to prepare: %w", err) } - err = stmt.Get(&row, id.String()) + err = stmt.Get(&j, id.String()) if err != nil { return nil, fmt.Errorf("failed to get job: %w", err) } - return row.job() + return &j, nil } diff --git a/pkg/datastore/mysql/runner.go b/pkg/datastore/mysql/runner.go index 102fe97..81ff9a3 100644 --- a/pkg/datastore/mysql/runner.go +++ b/pkg/datastore/mysql/runner.go @@ -8,84 +8,26 @@ import ( "time" "github.com/whywaita/myshoes/pkg/datastore" - "uuid" ) -// rowRunner mirrors datastore.Runner but stores the UUID columns (runner_id, -// target_id) as strings so that database/sql can scan the VARCHAR(36) columns. -// The standard library uuid.UUID is a [16]byte with no sql.Scanner. -type rowRunner struct { - UUID string `db:"runner_id"` - ShoesType string `db:"shoes_type"` - IPAddress string `db:"ip_address"` - TargetID string `db:"target_id"` - CloudID string `db:"cloud_id"` - ResourceType datastore.ResourceType `db:"resource_type"` - RunnerUser sql.NullString `db:"runner_user"` - ProviderURL sql.NullString `db:"provider_url"` - RepositoryURL string `db:"repository_url"` - RequestWebhook string `db:"request_webhook"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` -} - -func (r rowRunner) runner() (datastore.Runner, error) { - u, err := uuid.Parse(r.UUID) - if err != nil { - return datastore.Runner{}, fmt.Errorf("failed to parse runner uuid %q: %w", r.UUID, err) - } - - tid, err := uuid.Parse(r.TargetID) - if err != nil { - return datastore.Runner{}, fmt.Errorf("failed to parse target id %q: %w", r.TargetID, err) - } - - return datastore.Runner{ - UUID: u, - ShoesType: r.ShoesType, - IPAddress: r.IPAddress, - TargetID: tid, - CloudID: r.CloudID, - ResourceType: r.ResourceType, - RunnerUser: r.RunnerUser, - ProviderURL: r.ProviderURL, - RepositoryURL: r.RepositoryURL, - RequestWebhook: r.RequestWebhook, - CreatedAt: r.CreatedAt, - UpdatedAt: r.UpdatedAt, - }, nil -} - -func runnersFromRows(rows []rowRunner) ([]datastore.Runner, error) { - rs := make([]datastore.Runner, 0, len(rows)) - for _, r := range rows { - runner, err := r.runner() - if err != nil { - return nil, err - } - rs = append(rs, runner) - } - return rs, nil -} - // CreateRunner add a runner func (m *MySQL) CreateRunner(ctx context.Context, runner datastore.Runner) error { tx := m.Conn.MustBegin() queryRunner := `INSERT INTO runners(uuid) VALUES (?)` - if _, err := tx.ExecContext(ctx, queryRunner, runner.UUID.String()); err != nil { + if _, err := tx.ExecContext(ctx, queryRunner, runner.UUID); err != nil { tx.Rollback() return fmt.Errorf("failed to execute INSERT query runners: %w", err) } queryDetail := `INSERT INTO runner_detail(runner_id, shoes_type, ip_address, target_id, cloud_id, resource_type, runner_user, repository_url, request_webhook, provider_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` - if _, err := tx.ExecContext(ctx, queryDetail, runner.UUID.String(), runner.ShoesType, runner.IPAddress, runner.TargetID.String(), runner.CloudID, runner.ResourceType, runner.RunnerUser, runner.RepositoryURL, runner.RequestWebhook, runner.ProviderURL); err != nil { + if _, err := tx.ExecContext(ctx, queryDetail, runner.UUID, runner.ShoesType, runner.IPAddress, runner.TargetID, runner.CloudID, runner.ResourceType, runner.RunnerUser, runner.RepositoryURL, runner.RequestWebhook, runner.ProviderURL); err != nil { tx.Rollback() return fmt.Errorf("failed to execute INSERT query runner_detail: %w", err) } queryRunning := `INSERT INTO runners_running(runner_id) VALUES (?)` - if _, err := tx.ExecContext(ctx, queryRunning, runner.UUID.String()); err != nil { + if _, err := tx.ExecContext(ctx, queryRunning, runner.UUID); err != nil { tx.Rollback() return fmt.Errorf("failed to execute INSERT query runners_running: %w", err) } @@ -99,10 +41,10 @@ func (m *MySQL) CreateRunner(ctx context.Context, runner datastore.Runner) error // ListRunners get a not deleted runners func (m *MySQL) ListRunners(ctx context.Context) ([]datastore.Runner, error) { - var rows []rowRunner + var runners []datastore.Runner query := `SELECT runner.runner_id, detail.shoes_type, detail.ip_address, detail.target_id, detail.cloud_id, detail.created_at, detail.updated_at, detail.resource_type, detail.repository_url, detail.request_webhook, detail.runner_user, detail.provider_url FROM runners_running AS runner JOIN runner_detail AS detail ON runner.runner_id = detail.runner_id` - err := m.Conn.SelectContext(ctx, &rows, query) + err := m.Conn.SelectContext(ctx, &runners, query) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound @@ -111,15 +53,15 @@ func (m *MySQL) ListRunners(ctx context.Context) ([]datastore.Runner, error) { return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } - return runnersFromRows(rows) + return runners, nil } // ListRunnersByTargetID get a not deleted runners that has target_id -func (m *MySQL) ListRunnersByTargetID(ctx context.Context, targetID uuid.UUID) ([]datastore.Runner, error) { - var rows []rowRunner +func (m *MySQL) ListRunnersByTargetID(ctx context.Context, targetID datastore.UUID) ([]datastore.Runner, error) { + var runners []datastore.Runner query := `SELECT runner.runner_id, detail.shoes_type, detail.ip_address, detail.target_id, detail.cloud_id, detail.created_at, detail.updated_at, detail.resource_type, detail.repository_url, detail.request_webhook, detail.runner_user, detail.provider_url FROM runners_running AS runner JOIN runner_detail AS detail ON runner.runner_id = detail.runner_id WHERE detail.target_id = ?` - err := m.Conn.SelectContext(ctx, &rows, query, targetID.String()) + err := m.Conn.SelectContext(ctx, &runners, query, targetID) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound @@ -128,15 +70,15 @@ func (m *MySQL) ListRunnersByTargetID(ctx context.Context, targetID uuid.UUID) ( return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } - return runnersFromRows(rows) + return runners, nil } // ListRunnersLogBySince ListRunnerLog get a runners since time func (m *MySQL) ListRunnersLogBySince(ctx context.Context, since time.Time) ([]datastore.Runner, error) { - var rows []rowRunner + var runners []datastore.Runner query := `SELECT runner_id, shoes_type, ip_address, target_id, cloud_id, created_at, updated_at, resource_type, repository_url, request_webhook, runner_user, provider_url FROM runner_detail WHERE created_at > ?` - err := m.Conn.SelectContext(ctx, &rows, query, since) + err := m.Conn.SelectContext(ctx, &runners, query, since) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound @@ -145,15 +87,15 @@ func (m *MySQL) ListRunnersLogBySince(ctx context.Context, since time.Time) ([]d return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } - return runnersFromRows(rows) + return runners, nil } // GetRunner get a runner -func (m *MySQL) GetRunner(ctx context.Context, id uuid.UUID) (*datastore.Runner, error) { - var row rowRunner +func (m *MySQL) GetRunner(ctx context.Context, id datastore.UUID) (*datastore.Runner, error) { + var r datastore.Runner query := `SELECT runner_id, shoes_type, ip_address, target_id, cloud_id, created_at, updated_at, resource_type, repository_url, request_webhook, runner_user, provider_url FROM runner_detail WHERE runner_id = ?` - if err := m.Conn.GetContext(ctx, &row, query, id.String()); err != nil { + if err := m.Conn.GetContext(ctx, &r, query, id); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound } @@ -161,25 +103,21 @@ func (m *MySQL) GetRunner(ctx context.Context, id uuid.UUID) (*datastore.Runner, return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } - r, err := row.runner() - if err != nil { - return nil, err - } return &r, nil } // DeleteRunner delete a runner -func (m *MySQL) DeleteRunner(ctx context.Context, id uuid.UUID, deletedAt time.Time, reason datastore.RunnerStatus) error { +func (m *MySQL) DeleteRunner(ctx context.Context, id datastore.UUID, deletedAt time.Time, reason datastore.RunnerStatus) error { tx := m.Conn.MustBegin() queryDelete := `DELETE FROM runners_running WHERE runner_id = ?` - if _, err := tx.ExecContext(ctx, queryDelete, id.String()); err != nil { + if _, err := tx.ExecContext(ctx, queryDelete, id); err != nil { tx.Rollback() return fmt.Errorf("failed to execute DELETE query: %w", err) } queryInsert := `INSERT INTO runners_deleted(runner_id, reason) VALUES (?, ?)` - if _, err := tx.ExecContext(ctx, queryInsert, id.String(), reason); err != nil { + if _, err := tx.ExecContext(ctx, queryInsert, id, reason); err != nil { tx.Rollback() return fmt.Errorf("failed to execute INSERT query: %w", err) } diff --git a/pkg/datastore/mysql/runner_test.go b/pkg/datastore/mysql/runner_test.go index 610291e..677b3f0 100644 --- a/pkg/datastore/mysql/runner_test.go +++ b/pkg/datastore/mysql/runner_test.go @@ -16,7 +16,7 @@ import ( "uuid" ) -var testRunnerID = uuid.MustParse("7943e412-c0ae-4068-ab24-3e71a13fbe53") +var testRunnerID = datastore.UUID{UUID: uuid.MustParse("7943e412-c0ae-4068-ab24-3e71a13fbe53")} func TestMySQL_CreateRunner(t *testing.T) { testDatastore, teardown := testutils.GetTestDatastore() @@ -218,14 +218,14 @@ func TestMySQL_ListRunnersNotReturnDeleted(t *testing.T) { RepositoryURL: "https://github.com/octocat/Hello-World", RequestWebhook: "{}", } - input.UUID = uuid.MustParse(fmt.Sprintf(u, i)) + input.UUID = datastore.UUID{UUID: uuid.MustParse(fmt.Sprintf(u, i))} err := testDatastore.CreateRunner(context.Background(), input) if err != nil { t.Fatalf("failed to create runner: %+v", err) } } - err := testDatastore.DeleteRunner(context.Background(), uuid.MustParse(fmt.Sprintf(u, 0)), time.Now(), "deleted") + err := testDatastore.DeleteRunner(context.Background(), datastore.UUID{UUID: uuid.MustParse(fmt.Sprintf(u, 0))}, time.Now(), "deleted") if err != nil { t.Fatalf("failed to delete runner: %+v", err) } @@ -250,7 +250,7 @@ func TestMySQL_ListRunnersNotReturnDeleted(t *testing.T) { RepositoryURL: "https://github.com/octocat/Hello-World", RequestWebhook: "{}", } - r.UUID = uuid.MustParse(fmt.Sprintf(u, i)) + r.UUID = datastore.UUID{UUID: uuid.MustParse(fmt.Sprintf(u, i))} want = append(want, r) } @@ -285,7 +285,7 @@ func TestMySQL_ListRunnersLogBySince(t *testing.T) { RepositoryURL: "https://github.com/octocat/Hello-World", RequestWebhook: "{}", } - input.UUID = uuid.MustParse(fmt.Sprintf(u, i)) + input.UUID = datastore.UUID{UUID: uuid.MustParse(fmt.Sprintf(u, i))} err := testDatastore.CreateRunner(context.Background(), input) if err != nil { t.Fatalf("failed to create runner: %+v", err) @@ -314,7 +314,7 @@ func TestMySQL_ListRunnersLogBySince(t *testing.T) { RepositoryURL: "https://github.com/octocat/Hello-World", RequestWebhook: "{}", } - r.UUID = uuid.MustParse(fmt.Sprintf(u, i)) + r.UUID = datastore.UUID{UUID: uuid.MustParse(fmt.Sprintf(u, i))} want = append(want, r) } @@ -350,7 +350,7 @@ func TestMySQL_GetRunner(t *testing.T) { } tests := []struct { - input uuid.UUID + input datastore.UUID want *datastore.Runner err bool }{ @@ -423,7 +423,7 @@ func TestMySQL_DeleteRunner(t *testing.T) { } tests := []struct { - input uuid.UUID + input datastore.UUID want *datastore.Runner err bool }{ @@ -462,86 +462,46 @@ func TestMySQL_DeleteRunner(t *testing.T) { } } -type sqlRunnerRow struct { - UUID string `db:"runner_id"` - ShoesType string `db:"shoes_type"` - IPAddress string `db:"ip_address"` - TargetID string `db:"target_id"` - CloudID string `db:"cloud_id"` - ResourceType datastore.ResourceType `db:"resource_type"` - RunnerUser sql.NullString `db:"runner_user"` - ProviderURL sql.NullString `db:"provider_url"` - RepositoryURL string `db:"repository_url"` - RequestWebhook string `db:"request_webhook"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` -} - -func (r sqlRunnerRow) runner() (*datastore.Runner, error) { - u, err := uuid.Parse(r.UUID) - if err != nil { - return nil, err - } - tid, err := uuid.Parse(r.TargetID) - if err != nil { - return nil, err - } - return &datastore.Runner{ - UUID: u, - ShoesType: r.ShoesType, - IPAddress: r.IPAddress, - TargetID: tid, - CloudID: r.CloudID, - ResourceType: r.ResourceType, - RunnerUser: r.RunnerUser, - ProviderURL: r.ProviderURL, - RepositoryURL: r.RepositoryURL, - RequestWebhook: r.RequestWebhook, - CreatedAt: r.CreatedAt, - UpdatedAt: r.UpdatedAt, - }, nil -} - -func getRunnerFromSQL(testDB *sqlx.DB, id uuid.UUID) (*datastore.Runner, error) { - var row sqlRunnerRow +func getRunnerFromSQL(testDB *sqlx.DB, id datastore.UUID) (*datastore.Runner, error) { + var r datastore.Runner query := `SELECT runner_id, shoes_type, ip_address, target_id, cloud_id, created_at, updated_at, resource_type, repository_url, request_webhook, runner_user, provider_url FROM runner_detail WHERE runner_id = ?` stmt, err := testDB.Preparex(query) if err != nil { return nil, fmt.Errorf("failed to prepare: %w", err) } - err = stmt.Get(&row, id.String()) + err = stmt.Get(&r, id.String()) if err != nil { return nil, fmt.Errorf("failed to get runner: %w", err) } - return row.runner() + return &r, nil } -func getRunningRunnerFromSQL(testDB *sqlx.DB, id uuid.UUID) (*datastore.Runner, error) { - var row sqlRunnerRow +func getRunningRunnerFromSQL(testDB *sqlx.DB, id datastore.UUID) (*datastore.Runner, error) { + var r datastore.Runner query := `SELECT detail.runner_id, shoes_type, ip_address, target_id, cloud_id, detail.created_at, updated_at, detail.resource_type, detail.repository_url, detail.request_webhook FROM runner_detail AS detail JOIN runnesr_running AS running ON detail.runner_id = running.runner_id WHERE detail.runner_id = ?` stmt, err := testDB.Preparex(query) if err != nil { return nil, fmt.Errorf("failed to prepare: %w", err) } - err = stmt.Get(&row, id.String()) + err = stmt.Get(&r, id.String()) if err != nil { return nil, fmt.Errorf("failed to get runner: %w", err) } - return row.runner() + return &r, nil } -func getDeletedRunnerFromSQL(testDB *sqlx.DB, id uuid.UUID) (*datastore.Runner, error) { - var row sqlRunnerRow +func getDeletedRunnerFromSQL(testDB *sqlx.DB, id datastore.UUID) (*datastore.Runner, error) { + var r datastore.Runner query := `SELECT detail.runner_id, shoes_type, ip_address, target_id, cloud_id, detail.created_at, updated_at, detail.resource_type, detail.repository_url, detail.request_webhook FROM runner_detail AS detail JOIN runners_deleted AS deleted ON detail.runner_id = deleted.runner_id WHERE detail.runner_id = ?` stmt, err := testDB.Preparex(query) if err != nil { return nil, fmt.Errorf("failed to prepare: %w", err) } - err = stmt.Get(&row, id.String()) + err = stmt.Get(&r, id.String()) if err != nil { return nil, fmt.Errorf("failed to get runner: %w", err) } - return row.runner() + return &r, nil } diff --git a/pkg/datastore/mysql/target.go b/pkg/datastore/mysql/target.go index a9c1da9..9d81de5 100644 --- a/pkg/datastore/mysql/target.go +++ b/pkg/datastore/mysql/target.go @@ -8,59 +8,8 @@ import ( "time" "github.com/whywaita/myshoes/pkg/datastore" - "uuid" ) -// rowTarget mirrors datastore.Target but stores the UUID column as a string so -// that database/sql can scan the VARCHAR(36) uuid column. The standard library -// uuid.UUID is a [16]byte with no sql.Scanner, so it cannot be scanned directly. -type rowTarget struct { - UUID string `db:"uuid"` - Scope string `db:"scope"` - GitHubToken string `db:"github_token"` - TokenExpiredAt time.Time `db:"token_expired_at"` - GHEDomain sql.NullString `db:"ghe_domain"` - ResourceType datastore.ResourceType `db:"resource_type"` - ProviderURL sql.NullString `db:"provider_url"` - Status datastore.TargetStatus `db:"status"` - StatusDescription sql.NullString `db:"status_description"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` -} - -func (r rowTarget) target() (datastore.Target, error) { - u, err := uuid.Parse(r.UUID) - if err != nil { - return datastore.Target{}, fmt.Errorf("failed to parse target uuid %q: %w", r.UUID, err) - } - - return datastore.Target{ - UUID: u, - Scope: r.Scope, - GitHubToken: r.GitHubToken, - TokenExpiredAt: r.TokenExpiredAt, - GHEDomain: r.GHEDomain, - ResourceType: r.ResourceType, - ProviderURL: r.ProviderURL, - Status: r.Status, - StatusDescription: r.StatusDescription, - CreatedAt: r.CreatedAt, - UpdatedAt: r.UpdatedAt, - }, nil -} - -func targetsFromRows(rows []rowTarget) ([]datastore.Target, error) { - ts := make([]datastore.Target, 0, len(rows)) - for _, r := range rows { - t, err := r.target() - if err != nil { - return nil, err - } - ts = append(ts, t) - } - return ts, nil -} - // CreateTarget create a target func (m *MySQL) CreateTarget(ctx context.Context, target datastore.Target) error { expiredAtRFC3339 := target.TokenExpiredAt.Format("2006-01-02 15:04:05") @@ -69,7 +18,7 @@ func (m *MySQL) CreateTarget(ctx context.Context, target datastore.Target) error if _, err := m.Conn.ExecContext( ctx, query, - target.UUID.String(), + target.UUID, target.Scope, target.GHEDomain, target.GitHubToken, @@ -84,10 +33,10 @@ func (m *MySQL) CreateTarget(ctx context.Context, target datastore.Target) error } // GetTarget get a target -func (m *MySQL) GetTarget(ctx context.Context, id uuid.UUID) (*datastore.Target, error) { - var row rowTarget +func (m *MySQL) GetTarget(ctx context.Context, id datastore.UUID) (*datastore.Target, error) { + var t datastore.Target query := `SELECT uuid, scope, github_token, token_expired_at, resource_type, provider_url, status, status_description, created_at, updated_at FROM targets WHERE uuid = ?` - if err := m.Conn.GetContext(ctx, &row, query, id.String()); err != nil { + if err := m.Conn.GetContext(ctx, &t, query, id); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound } @@ -95,18 +44,14 @@ func (m *MySQL) GetTarget(ctx context.Context, id uuid.UUID) (*datastore.Target, return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } - t, err := row.target() - if err != nil { - return nil, err - } return &t, nil } // GetTargetByScope get a target from scope func (m *MySQL) GetTargetByScope(ctx context.Context, scope string) (*datastore.Target, error) { - var row rowTarget + var t datastore.Target query := `SELECT uuid, scope, github_token, token_expired_at, resource_type, provider_url, status, status_description, created_at, updated_at FROM targets WHERE scope = ?` - if err := m.Conn.GetContext(ctx, &row, query, scope); err != nil { + if err := m.Conn.GetContext(ctx, &t, query, scope); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, datastore.ErrNotFound } @@ -114,28 +59,24 @@ func (m *MySQL) GetTargetByScope(ctx context.Context, scope string) (*datastore. return nil, fmt.Errorf("failed to execute SELECT query: %w", err) } - t, err := row.target() - if err != nil { - return nil, err - } return &t, nil } // ListTargets get a all target func (m *MySQL) ListTargets(ctx context.Context) ([]datastore.Target, error) { - var rows []rowTarget + var ts []datastore.Target query := `SELECT uuid, scope, github_token, token_expired_at, resource_type, provider_url, status, status_description, created_at, updated_at FROM targets` - if err := m.Conn.SelectContext(ctx, &rows, query); err != nil { + if err := m.Conn.SelectContext(ctx, &ts, query); err != nil { return nil, fmt.Errorf("failed to SELECT query: %w", err) } - return targetsFromRows(rows) + return ts, nil } // DeleteTarget delete a target -func (m *MySQL) DeleteTarget(ctx context.Context, id uuid.UUID) error { +func (m *MySQL) DeleteTarget(ctx context.Context, id datastore.UUID) error { query := `UPDATE targets SET status = "deleted" WHERE uuid = ?` - if _, err := m.Conn.ExecContext(ctx, query, id.String()); err != nil { + if _, err := m.Conn.ExecContext(ctx, query, id); err != nil { return fmt.Errorf("failed to execute DELETE query: %w", err) } @@ -143,9 +84,9 @@ func (m *MySQL) DeleteTarget(ctx context.Context, id uuid.UUID) error { } // UpdateTargetStatus update status in target -func (m *MySQL) UpdateTargetStatus(ctx context.Context, targetID uuid.UUID, newStatus datastore.TargetStatus, description string) error { +func (m *MySQL) UpdateTargetStatus(ctx context.Context, targetID datastore.UUID, newStatus datastore.TargetStatus, description string) error { query := `UPDATE targets SET status = ?, status_description = ? WHERE uuid = ?` - if _, err := m.Conn.ExecContext(ctx, query, newStatus, description, targetID.String()); err != nil { + if _, err := m.Conn.ExecContext(ctx, query, newStatus, description, targetID); err != nil { return fmt.Errorf("failed to execute UPDATE query: %w", err) } @@ -153,9 +94,9 @@ func (m *MySQL) UpdateTargetStatus(ctx context.Context, targetID uuid.UUID, newS } // UpdateToken update token in target -func (m *MySQL) UpdateToken(ctx context.Context, targetID uuid.UUID, newToken string, newExpiredAt time.Time) error { +func (m *MySQL) UpdateToken(ctx context.Context, targetID datastore.UUID, newToken string, newExpiredAt time.Time) error { query := `UPDATE targets SET github_token = ?, token_expired_at = ? WHERE uuid = ?` - if _, err := m.Conn.ExecContext(ctx, query, newToken, newExpiredAt, targetID.String()); err != nil { + if _, err := m.Conn.ExecContext(ctx, query, newToken, newExpiredAt, targetID); err != nil { return fmt.Errorf("failed to execute UPDATE query: %w", err) } @@ -163,9 +104,9 @@ func (m *MySQL) UpdateToken(ctx context.Context, targetID uuid.UUID, newToken st } // UpdateTargetParam update parameter of target -func (m *MySQL) UpdateTargetParam(ctx context.Context, targetID uuid.UUID, newResourceType datastore.ResourceType, newProviderURL sql.NullString) error { +func (m *MySQL) UpdateTargetParam(ctx context.Context, targetID datastore.UUID, newResourceType datastore.ResourceType, newProviderURL sql.NullString) error { query := `UPDATE targets SET resource_type = ?, provider_url = ? WHERE uuid = ?` - if _, err := m.Conn.ExecContext(ctx, query, newResourceType, newProviderURL, targetID.String()); err != nil { + if _, err := m.Conn.ExecContext(ctx, query, newResourceType, newProviderURL, targetID); err != nil { return fmt.Errorf("failed to execute UPDATE query: %w", err) } diff --git a/pkg/datastore/mysql/target_test.go b/pkg/datastore/mysql/target_test.go index c7e4aad..cdea04b 100644 --- a/pkg/datastore/mysql/target_test.go +++ b/pkg/datastore/mysql/target_test.go @@ -17,9 +17,9 @@ import ( "github.com/whywaita/myshoes/pkg/datastore" ) -var testTargetID = uuid.MustParse("8a72d42c-372c-4e0d-9c6a-4304d44af137") -var testTargetID2 = uuid.MustParse("d14ccfea-b123-4ada-974e-bbff0937e9c7") -var testTargetID3 = uuid.MustParse("5c1816ff-4813-46b0-b1ba-30d135a2f3f5") +var testTargetID = datastore.UUID{UUID: uuid.MustParse("8a72d42c-372c-4e0d-9c6a-4304d44af137")} +var testTargetID2 = datastore.UUID{UUID: uuid.MustParse("d14ccfea-b123-4ada-974e-bbff0937e9c7")} +var testTargetID3 = datastore.UUID{UUID: uuid.MustParse("5c1816ff-4813-46b0-b1ba-30d135a2f3f5")} var testScopeOrg = "octocat" var testScopeRepo = "octocat/hello-world" var testScopeRepo2 = "octocat/hello-world2" @@ -170,7 +170,7 @@ func TestMySQL_GetTarget(t *testing.T) { } tests := []struct { - input uuid.UUID + input datastore.UUID want *datastore.Target err bool }{ @@ -477,7 +477,7 @@ func TestMySQL_DeleteTarget(t *testing.T) { } tests := []struct { - input uuid.UUID + input datastore.UUID want *datastore.Target err bool }{ @@ -581,7 +581,7 @@ func TestMySQL_UpdateStatus(t *testing.T) { } for _, test := range tests { - tID := uuid.NewV4() + tID := datastore.UUID{UUID: uuid.NewV4()} if err := testDatastore.CreateTarget(context.Background(), datastore.Target{ UUID: tID, Scope: testScopeRepo, @@ -606,7 +606,7 @@ func TestMySQL_UpdateStatus(t *testing.T) { t.Fatalf("failed to get target from SQL: %+v", err) } if got != nil { - got.UUID = uuid.UUID{} + got.UUID = datastore.UUID{} got.CreatedAt = time.Time{} got.UpdatedAt = time.Time{} } @@ -683,7 +683,7 @@ func TestMySQL_UpdateToken(t *testing.T) { } for _, test := range tests { - tID := uuid.NewV4() + tID := datastore.UUID{UUID: uuid.NewV4()} if err := testDatastore.CreateTarget(context.Background(), datastore.Target{ UUID: tID, Scope: testScopeRepo, @@ -707,7 +707,7 @@ func TestMySQL_UpdateToken(t *testing.T) { t.Fatalf("failed to get target from SQL: %+v", err) } if got != nil { - got.UUID = uuid.UUID{} + got.UUID = datastore.UUID{} got.CreatedAt = time.Time{} got.UpdatedAt = time.Time{} } @@ -825,7 +825,7 @@ func TestMySQL_UpdateTargetParam(t *testing.T) { } for _, test := range tests { - tID := uuid.NewV4() + tID := datastore.UUID{UUID: uuid.NewV4()} if err := testDatastore.CreateTarget(context.Background(), datastore.Target{ UUID: tID, Scope: testScopeRepo, @@ -849,7 +849,7 @@ func TestMySQL_UpdateTargetParam(t *testing.T) { t.Fatalf("failed to get target from SQL: %+v", err) } if got != nil { - got.UUID = uuid.UUID{} + got.UUID = datastore.UUID{} got.CreatedAt = time.Time{} got.UpdatedAt = time.Time{} got.TokenExpiredAt = time.Time{} @@ -865,50 +865,16 @@ func TestMySQL_UpdateTargetParam(t *testing.T) { } } -type sqlTargetRow struct { - UUID string `db:"uuid"` - Scope string `db:"scope"` - GitHubToken string `db:"github_token"` - TokenExpiredAt time.Time `db:"token_expired_at"` - GHEDomain sql.NullString `db:"ghe_domain"` - ResourceType datastore.ResourceType `db:"resource_type"` - ProviderURL sql.NullString `db:"provider_url"` - Status datastore.TargetStatus `db:"status"` - StatusDescription sql.NullString `db:"status_description"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` -} - -func (r sqlTargetRow) target() (*datastore.Target, error) { - u, err := uuid.Parse(r.UUID) - if err != nil { - return nil, err - } - return &datastore.Target{ - UUID: u, - Scope: r.Scope, - GitHubToken: r.GitHubToken, - TokenExpiredAt: r.TokenExpiredAt, - GHEDomain: r.GHEDomain, - ResourceType: r.ResourceType, - ProviderURL: r.ProviderURL, - Status: r.Status, - StatusDescription: r.StatusDescription, - CreatedAt: r.CreatedAt, - UpdatedAt: r.UpdatedAt, - }, nil -} - -func getTargetFromSQL(testDB *sqlx.DB, uuid uuid.UUID) (*datastore.Target, error) { - var row sqlTargetRow +func getTargetFromSQL(testDB *sqlx.DB, id datastore.UUID) (*datastore.Target, error) { + var t datastore.Target query := `SELECT uuid, scope, ghe_domain, github_token, token_expired_at, resource_type, provider_url, status, status_description, created_at, updated_at FROM targets WHERE uuid = ?` stmt, err := testDB.Preparex(query) if err != nil { return nil, fmt.Errorf("failed to prepare: %w", err) } - err = stmt.Get(&row, uuid.String()) + err = stmt.Get(&t, id.String()) if err != nil { return nil, fmt.Errorf("failed to get target: %w", err) } - return row.target() + return &t, nil } diff --git a/pkg/datastore/uuid.go b/pkg/datastore/uuid.go new file mode 100644 index 0000000..3294407 --- /dev/null +++ b/pkg/datastore/uuid.go @@ -0,0 +1,45 @@ +package datastore + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "uuid" +) + +// UUID wraps the standard library uuid.UUID and adds database/sql support +// (driver.Valuer and sql.Scanner). UUID columns are stored as VARCHAR(36), so +// Valuc returns the string form and Scan parses the string back. +type UUID struct { + uuid.UUID +} + +var ( + _ driver.Valuer = UUID{} + _ sql.Scanner = (*UUID)(nil) +) + +// Value implements driver.Valuer. +func (u UUID) Value() (driver.Value, error) { + return u.String(), nil +} + +// Scan implements sql.Scanner. +func (u *UUID) Scan(src interface{}) error { + var s string + switch v := src.(type) { + case string: + s = v + case []byte: + s = string(v) + default: + return fmt.Errorf("cannot scan %T into datastore.UUID", src) + } + + parsed, err := uuid.Parse(s) + if err != nil { + return fmt.Errorf("failed to parse uuid %q: %w", s, err) + } + u.UUID = parsed + return nil +} diff --git a/pkg/metric/scrape_memory.go b/pkg/metric/scrape_memory.go index 20ac3a3..b570956 100644 --- a/pkg/metric/scrape_memory.go +++ b/pkg/metric/scrape_memory.go @@ -5,8 +5,6 @@ import ( "fmt" "sync/atomic" - "uuid" - "github.com/prometheus/client_golang/prometheus" "github.com/whywaita/myshoes/pkg/config" "github.com/whywaita/myshoes/pkg/datastore" @@ -146,13 +144,13 @@ func scrapeStarterValues(ch chan<- prometheus.Metric) error { runner.DeleteRetryCount.Range(func(key, value any) bool { ch <- prometheus.MustNewConstMetric( - memoryRunnerDeleteRetryCount, prometheus.GaugeValue, float64(value.(int)), key.(uuid.UUID).String()) + memoryRunnerDeleteRetryCount, prometheus.GaugeValue, float64(value.(int)), key.(datastore.UUID).String()) return true }) starter.AddInstanceRetryCount.Range(func(key, value any) bool { ch <- prometheus.MustNewConstMetric( - memoryRunnerCreateRetryCount, prometheus.GaugeValue, float64(value.(int)), key.(uuid.UUID).String()) + memoryRunnerCreateRetryCount, prometheus.GaugeValue, float64(value.(int)), key.(datastore.UUID).String()) return true }) diff --git a/pkg/starter/starter.go b/pkg/starter/starter.go index fff6324..51dbf24 100644 --- a/pkg/starter/starter.go +++ b/pkg/starter/starter.go @@ -565,7 +565,7 @@ func enqueueRescueJob(ctx context.Context, workflowJob *github.WorkflowJobEvent, } logger.Logf(false, "rescue pending job: (repo: %s, gh_run_id: %d, gh_job_id: %d)", *repository.HTMLURL, workflowJob.WorkflowJob.GetRunID(), workflowJob.WorkflowJob.GetID()) - jobID := uuid.NewV4() + jobID := datastore.UUID{UUID: uuid.NewV4()} job := datastore.Job{ UUID: jobID, GHEDomain: sql.NullString{ diff --git a/pkg/web/target.go b/pkg/web/target.go index f6e16c5..e287225 100644 --- a/pkg/web/target.go +++ b/pkg/web/target.go @@ -115,7 +115,7 @@ func handleTargetRead(w http.ResponseWriter, r *http.Request, ds datastore.Datas func sanitizeTarget(t datastore.Target) UserTarget { ut := UserTarget{ - UUID: t.UUID, + UUID: t.UUID.UUID, Scope: t.Scope, TokenExpiredAt: t.TokenExpiredAt, ResourceType: t.ResourceType.String(), @@ -219,14 +219,14 @@ func handleTargetDelete(w http.ResponseWriter, r *http.Request, ds datastore.Dat w.WriteHeader(http.StatusNoContent) } -func parseReqTargetID(r *http.Request) (uuid.UUID, error) { +func parseReqTargetID(r *http.Request) (datastore.UUID, error) { targetIDStr := pat.Param(r, "id") targetID, err := uuid.Parse(targetIDStr) if err != nil { - return uuid.UUID{}, fmt.Errorf("failed to parse target id: %w", err) + return datastore.UUID{}, fmt.Errorf("failed to parse target id: %w", err) } - return targetID, nil + return datastore.UUID{UUID: targetID}, nil } // ErrorResponse is error response @@ -248,7 +248,7 @@ func validateUpdateTarget(old, new datastore.Target) error { newv := new for _, t := range []*datastore.Target{&oldv, &newv} { - t.UUID = uuid.UUID{} + t.UUID = datastore.UUID{} // can update variables t.ResourceType = datastore.ResourceTypeUnknown diff --git a/pkg/web/target_create.go b/pkg/web/target_create.go index d7c7942..541e391 100644 --- a/pkg/web/target_create.go +++ b/pkg/web/target_create.go @@ -65,7 +65,7 @@ func handleTargetCreate(w http.ResponseWriter, r *http.Request, ds datastore.Dat } target, err := ds.GetTargetByScope(ctx, t.Scope) - var targetUUID uuid.UUID + var targetUUID datastore.UUID switch { case errors.Is(err, datastore.ErrNotFound): @@ -143,8 +143,8 @@ func isValidScopeAndToken(ctx context.Context, scope, githubPersonalToken string return nil } -func createNewTarget(ctx context.Context, input datastore.Target, ds datastore.Datastore) (*uuid.UUID, error) { - input.UUID = uuid.NewV4() +func createNewTarget(ctx context.Context, input datastore.Target, ds datastore.Datastore) (*datastore.UUID, error) { + input.UUID = datastore.UUID{UUID: uuid.NewV4()} now := time.Now().UTC() input.CreatedAt = now input.UpdatedAt = now diff --git a/pkg/web/target_test.go b/pkg/web/target_test.go index 3dd189c..566e9ea 100644 --- a/pkg/web/target_test.go +++ b/pkg/web/target_test.go @@ -216,7 +216,7 @@ func Test_handleTargetCreate_recreated(t *testing.T) { t.Fatalf("must be response statuscode is 201, but got %d: %+v", code, string(content)) } - got, err := testDatastore.GetTarget(context.Background(), u) + got, err := testDatastore.GetTarget(context.Background(), datastore.UUID{UUID: u}) if err != nil { t.Fatalf("failed to get created target: %+v", err) } @@ -277,7 +277,7 @@ func Test_handleTargetCreate_recreated_update(t *testing.T) { t.Fatalf("must be response statuscode is 201, but got %d: %+v", code, string(content)) } - got, err := testDatastore.GetTarget(context.Background(), u) + got, err := testDatastore.GetTarget(context.Background(), datastore.UUID{UUID: u}) if err != nil { t.Fatalf("failed to get created target: %+v", err) } @@ -614,7 +614,7 @@ func Test_handleTargetDelete(t *testing.T) { { input: targetUUID, want: &datastore.Target{ - UUID: targetUUID, + UUID: datastore.UUID{UUID: targetUUID}, Scope: "repo", GitHubToken: testGitHubAppToken, TokenExpiredAt: testTime, @@ -641,7 +641,7 @@ func Test_handleTargetDelete(t *testing.T) { t.Fatalf("must be response statuscode is 204, but got %d: %+v", code, string(content)) } - got, err := testDatastore.GetTarget(context.Background(), test.input) + got, err := testDatastore.GetTarget(context.Background(), datastore.UUID{UUID: test.input}) if err != nil { t.Fatalf("failed to get target from datastore: %+v", err) } diff --git a/pkg/web/webhook.go b/pkg/web/webhook.go index f4047e5..b631a88 100644 --- a/pkg/web/webhook.go +++ b/pkg/web/webhook.go @@ -167,7 +167,7 @@ func processCheckRun(ctx context.Context, ds datastore.Datastore, repoName, repo return nil } - jobID := uuid.NewV4() + jobID := datastore.UUID{UUID: uuid.NewV4()} j := datastore.Job{ UUID: jobID, GHEDomain: sql.NullString{