feat(plugin): add source package builder
Some checks failed
Go / build (.exe, 386, windows, windows-386) (push) Has been cancelled
Go / build (.exe, amd64, windows, windows-amd64) (push) Has been cancelled
Go / build (.exe, arm64, windows, windows-arm64) (push) Has been cancelled
Go / build (386, freebsd, freebsd-386) (push) Has been cancelled
Go / build (386, linux, linux-386) (push) Has been cancelled
Go / build (386, netbsd, netbsd-386) (push) Has been cancelled
Go / build (386, openbsd, openbsd-386) (push) Has been cancelled
Go / build (386, plan9, plan9-386) (push) Has been cancelled
Go / build (amd64, darwin, darwin-amd64) (push) Has been cancelled
Go / build (amd64, dragonfly, dragonfly-amd64) (push) Has been cancelled
Go / build (amd64, freebsd, freebsd-amd64) (push) Has been cancelled
Go / build (amd64, illumos, illumos-amd64) (push) Has been cancelled
Go / build (amd64, linux, linux-amd64) (push) Has been cancelled
Go / build (amd64, netbsd, netbsd-amd64) (push) Has been cancelled
Go / build (amd64, openbsd, openbsd-amd64) (push) Has been cancelled
Go / build (amd64, plan9, plan9-amd64) (push) Has been cancelled
Go / build (amd64, solaris, solaris-amd64) (push) Has been cancelled
Go / build (arm, 6, linux, linux-armv6) (push) Has been cancelled
Go / build (arm, 7, linux, linux-armv7) (push) Has been cancelled
Go / build (arm, freebsd, freebsd-arm) (push) Has been cancelled
Go / build (arm, netbsd, netbsd-arm) (push) Has been cancelled
Go / build (arm, openbsd, openbsd-arm) (push) Has been cancelled
Go / build (arm, plan9, plan9-arm) (push) Has been cancelled
Go / build (arm64, darwin, darwin-arm64) (push) Has been cancelled
Go / build (arm64, freebsd, freebsd-arm64) (push) Has been cancelled
Go / build (arm64, linux, linux-arm64) (push) Has been cancelled
Go / build (arm64, netbsd, netbsd-arm64) (push) Has been cancelled
Go / build (arm64, openbsd, openbsd-arm64) (push) Has been cancelled
Go / build (loong64, linux, linux-loong64) (push) Has been cancelled
Go / build (mips, linux, linux-mips) (push) Has been cancelled
Go / build (mips64, linux, linux-mips64) (push) Has been cancelled
Go / build (mips64le, linux, linux-mips64le) (push) Has been cancelled
Go / build (mipsle, linux, linux-mipsle) (push) Has been cancelled
Go / build (ppc64, aix, aix-ppc64) (push) Has been cancelled
Go / build (ppc64, linux, linux-ppc64) (push) Has been cancelled
Go / build (ppc64, openbsd, openbsd-ppc64) (push) Has been cancelled
Go / build (ppc64le, linux, linux-ppc64le) (push) Has been cancelled
Go / build (riscv64, freebsd, freebsd-riscv64) (push) Has been cancelled
Go / build (riscv64, linux, linux-riscv64) (push) Has been cancelled
Go / build (riscv64, openbsd, openbsd-riscv64) (push) Has been cancelled
Go / build (s390x, linux, linux-s390x) (push) Has been cancelled
Go / merge-artifacts (push) Has been cancelled
Docker Image / docker (push) Has been cancelled

This commit is contained in:
2026-06-26 10:49:55 +08:00
parent ae8706f8c8
commit eae2319328
20 changed files with 2174 additions and 16 deletions

View File

@@ -5,6 +5,7 @@ import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"time"
)
@@ -120,6 +121,9 @@ func (r Repository) UpsertDesired(ctx context.Context, actor, pluginID, artifact
if artifact.PluginID != pluginID {
return PluginRecord{}, errors.New("artifact plugin_id does not match")
}
if artifact.ArtifactType != ArtifactTypeBinary {
return PluginRecord{}, errors.New("desired artifact must be a binary artifact")
}
now := r.now().Unix()
tx, err := r.db.BeginTx(ctx, nil)
@@ -266,6 +270,207 @@ func (r Repository) UpdateArtifactStatus(ctx context.Context, artifactID, status
return err
}
func (r Repository) CreateBuild(ctx context.Context, build BuildRecord) (BuildRecord, error) {
now := r.now().Unix()
build.CreatedAt = now
build.UpdatedAt = now
if build.Status == "" {
build.Status = BuildStatusQueued
}
_, err := r.db.ExecContext(ctx, `
INSERT INTO plugin_builds(
plugin_id, source_id, artifact_id, status, builder_type, builder_image, builder_version,
go_version, go_os, go_arch, go_amd64, go_arm64, cgo_enabled, build_tags,
sdk_module, sdk_version, go_proxy, go_no_sumdb, go_private, vendor_required,
source_sha256, artifact_sha256, module_summary_json, go_version_m_json, abi_fingerprint,
log_summary, metadata_json, error, started_at, ended_at, duration_ms, created_by, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
build.PluginID, build.SourceID, build.ArtifactID, build.Status, build.BuilderType, build.BuilderImage, build.BuilderVersion,
build.GoVersion, build.GOOS, build.GOARCH, build.GOAMD64, build.GOARM64, build.CGOEnabled, build.BuildTags,
build.SDKModule, build.SDKVersion, build.GOPROXY, build.GONOSUMDB, build.GOPRIVATE, boolInt(build.VendorRequired),
build.SourceSHA256, build.ArtifactSHA256, defaultJSONArray(build.ModuleSummary), defaultJSONObject(build.GoVersionM), build.ABIFingerprint,
build.LogSummary, defaultJSONObject(build.MetadataJSON), build.Error, build.StartedAt, build.EndedAt, build.DurationMS, build.CreatedBy, build.CreatedAt, build.UpdatedAt)
if err != nil {
return BuildRecord{}, err
}
id, err := lastInsertID(ctx, r.db)
if err != nil {
return BuildRecord{}, err
}
return r.Build(ctx, id)
}
func (r Repository) Build(ctx context.Context, id int64) (BuildRecord, error) {
row := r.db.QueryRowContext(ctx, `
SELECT id, plugin_id, source_id, artifact_id, status, builder_type, builder_image, builder_version,
go_version, go_os, go_arch, go_amd64, go_arm64, cgo_enabled, build_tags,
sdk_module, sdk_version, go_proxy, go_no_sumdb, go_private, vendor_required,
source_sha256, artifact_sha256, module_summary_json, go_version_m_json, abi_fingerprint,
log_summary, metadata_json, error, started_at, ended_at, duration_ms, created_by, created_at, updated_at
FROM plugin_builds WHERE id = ?`, id)
return scanBuild(row)
}
func (r Repository) ListBuilds(ctx context.Context, pluginID string) ([]BuildRecord, error) {
query := `
SELECT id, plugin_id, source_id, artifact_id, status, builder_type, builder_image, builder_version,
go_version, go_os, go_arch, go_amd64, go_arm64, cgo_enabled, build_tags,
sdk_module, sdk_version, go_proxy, go_no_sumdb, go_private, vendor_required,
source_sha256, artifact_sha256, module_summary_json, go_version_m_json, abi_fingerprint,
log_summary, metadata_json, error, started_at, ended_at, duration_ms, created_by, created_at, updated_at
FROM plugin_builds`
var args []any
if pluginID != "" {
query += ` WHERE plugin_id = ?`
args = append(args, pluginID)
}
query += ` ORDER BY created_at DESC, id DESC`
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var builds []BuildRecord
for rows.Next() {
build, err := scanBuild(rows)
if err != nil {
return nil, err
}
builds = append(builds, build)
}
return builds, rows.Err()
}
func (r Repository) MarkBuildRunning(ctx context.Context, id int64) error {
now := r.now().Unix()
_, err := r.db.ExecContext(ctx, `UPDATE plugin_builds SET status = ?, started_at = ?, updated_at = ? WHERE id = ? AND status = ?`,
BuildStatusRunning, now, now, id, BuildStatusQueued)
return err
}
func (r Repository) FinishBuild(ctx context.Context, build BuildRecord) error {
now := r.now().Unix()
if build.EndedAt == 0 {
build.EndedAt = now
}
if build.StartedAt > 0 && build.DurationMS == 0 {
build.DurationMS = (build.EndedAt - build.StartedAt) * 1000
}
_, err := r.db.ExecContext(ctx, `
UPDATE plugin_builds SET
artifact_id = ?, status = ?, builder_image = ?, builder_version = ?, go_version = ?,
go_os = ?, go_arch = ?, go_amd64 = ?, go_arm64 = ?, cgo_enabled = ?, build_tags = ?,
sdk_module = ?, sdk_version = ?, go_proxy = ?, go_no_sumdb = ?, go_private = ?, vendor_required = ?,
source_sha256 = ?, artifact_sha256 = ?, module_summary_json = ?, go_version_m_json = ?,
abi_fingerprint = ?, log_summary = ?, metadata_json = ?, error = ?, ended_at = ?, duration_ms = ?, updated_at = ?
WHERE id = ?`,
build.ArtifactID, build.Status, build.BuilderImage, build.BuilderVersion, build.GoVersion,
build.GOOS, build.GOARCH, build.GOAMD64, build.GOARM64, build.CGOEnabled, build.BuildTags,
build.SDKModule, build.SDKVersion, build.GOPROXY, build.GONOSUMDB, build.GOPRIVATE, boolInt(build.VendorRequired),
build.SourceSHA256, build.ArtifactSHA256, defaultJSONArray(build.ModuleSummary), defaultJSONObject(build.GoVersionM),
build.ABIFingerprint, build.LogSummary, defaultJSONObject(build.MetadataJSON), build.Error, build.EndedAt, build.DurationMS, now,
build.ID)
return err
}
func (r Repository) CancelBuild(ctx context.Context, id int64, actor string) (BuildRecord, error) {
now := r.now().Unix()
res, err := r.db.ExecContext(ctx, `
UPDATE plugin_builds
SET status = ?, error = ?, ended_at = ?, updated_at = ?
WHERE id = ? AND status IN (?, ?)`,
BuildStatusCanceled, "build canceled by "+actor, now, now, id, BuildStatusQueued, BuildStatusRunning)
if err != nil {
return BuildRecord{}, err
}
if rows, _ := res.RowsAffected(); rows == 0 {
return r.Build(ctx, id)
}
return r.Build(ctx, id)
}
func (r Repository) RetryBuild(ctx context.Context, id int64, actor string) (BuildRecord, error) {
previous, err := r.Build(ctx, id)
if err != nil {
return BuildRecord{}, err
}
if previous.Status != BuildStatusFailed && previous.Status != BuildStatusCanceled {
return BuildRecord{}, fmt.Errorf("build status %q cannot be retried", previous.Status)
}
previous.ID = 0
previous.ArtifactID = ""
previous.ArtifactSHA256 = ""
previous.Status = BuildStatusQueued
previous.LogSummary = ""
previous.Error = ""
previous.StartedAt = 0
previous.EndedAt = 0
previous.DurationMS = 0
previous.CreatedBy = actor
return r.CreateBuild(ctx, previous)
}
func (r Repository) ReferencedArtifactIDs(ctx context.Context) (map[string]bool, error) {
refs := make(map[string]bool)
rows, err := r.db.QueryContext(ctx, `
SELECT desired_artifact_id, active_artifact_id, loaded_artifact_id
FROM plugins
WHERE desired_state <> 'deleted'`)
if err != nil {
return nil, err
}
for rows.Next() {
var desired, active, loaded string
if err := rows.Scan(&desired, &active, &loaded); err != nil {
rows.Close()
return nil, err
}
for _, id := range []string{desired, active, loaded} {
if id != "" {
refs[id] = true
}
}
}
if err := rows.Close(); err != nil {
return nil, err
}
rows, err = r.db.QueryContext(ctx, `SELECT artifact_id FROM plugin_config_snapshots WHERE artifact_id <> ''`)
if err != nil {
return nil, err
}
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
rows.Close()
return nil, err
}
if id != "" {
refs[id] = true
}
}
if err := rows.Close(); err != nil {
return nil, err
}
rows, err = r.db.QueryContext(ctx, `SELECT artifact_id FROM plugin_builds WHERE artifact_id <> '' AND status = ?`, BuildStatusSucceeded)
if err != nil {
return nil, err
}
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
rows.Close()
return nil, err
}
if id != "" {
refs[id] = true
}
}
if err := rows.Close(); err != nil {
return nil, err
}
return refs, nil
}
func (r Repository) RecordOperation(ctx context.Context, pluginID, artifactID, operation, status, actor, message string, metadata any) error {
metadataJSON, err := marshalDefaultObject(metadata)
if err != nil {
@@ -322,6 +527,20 @@ func scanPluginRow(row rowScanner, plugin *PluginRecord) error {
)
}
func scanBuild(row rowScanner) (BuildRecord, error) {
var build BuildRecord
var vendorRequired int
err := row.Scan(
&build.ID, &build.PluginID, &build.SourceID, &build.ArtifactID, &build.Status, &build.BuilderType, &build.BuilderImage, &build.BuilderVersion,
&build.GoVersion, &build.GOOS, &build.GOARCH, &build.GOAMD64, &build.GOARM64, &build.CGOEnabled, &build.BuildTags,
&build.SDKModule, &build.SDKVersion, &build.GOPROXY, &build.GONOSUMDB, &build.GOPRIVATE, &vendorRequired,
&build.SourceSHA256, &build.ArtifactSHA256, &build.ModuleSummary, &build.GoVersionM, &build.ABIFingerprint,
&build.LogSummary, &build.MetadataJSON, &build.Error, &build.StartedAt, &build.EndedAt, &build.DurationMS, &build.CreatedBy, &build.CreatedAt, &build.UpdatedAt,
)
build.VendorRequired = vendorRequired != 0
return build, err
}
func marshalDefaultObject(value any) (string, error) {
if value == nil {
return "{}", nil
@@ -335,3 +554,33 @@ func marshalDefaultObject(value any) (string, error) {
}
return string(data), nil
}
func defaultJSONObject(value string) string {
if value == "" || !json.Valid([]byte(value)) {
return "{}"
}
return value
}
func defaultJSONArray(value string) string {
if value == "" || !json.Valid([]byte(value)) {
return "[]"
}
return value
}
func boolInt(value bool) int {
if value {
return 1
}
return 0
}
func lastInsertID(ctx context.Context, db *sql.DB) (int64, error) {
row := db.QueryRowContext(ctx, `SELECT last_insert_rowid()`)
var id int64
if err := row.Scan(&id); err != nil {
return 0, err
}
return id, nil
}