feat(release):完成双 release 运维闭环
bat-rust / Build and test Rust (push) Canceled after 0s
bat-rust / Build and test Go API (push) Canceled after 0s

This commit is contained in:
2026-09-12 11:08:04 +08:00
parent 8a77502272
commit 8d57a63697
27 changed files with 3635 additions and 222 deletions
+135
View File
@@ -182,6 +182,117 @@ type pageParam struct {
Limit int `json:"limit"`
}
// ReleaseListParams selects one Rust-owned release namespace.
type ReleaseListParams struct {
Channel string `json:"channel,omitempty"`
}
// ReleaseDistributionParams selects a verified release for distribution.
type ReleaseDistributionParams struct {
Channel string `json:"channel,omitempty"`
ReleaseID string `json:"release_id,omitempty"`
Offset int `json:"offset,omitempty"`
Limit int `json:"limit,omitempty"`
}
// ReleaseCleanupParams controls the dry-run/execute cleanup pair.
type ReleaseCleanupParams struct {
Execute bool `json:"execute,omitempty"`
PlanID string `json:"plan_id,omitempty"`
}
// ReleaseSummary mirrors Rust's dual-release historical summary.
type ReleaseSummary struct {
Channel string `json:"channel"`
ID string `json:"id"`
Path string `json:"path"`
SourceOfficialReleaseID string `json:"source_official_release_id,omitempty"`
CreatedUnixSeconds *uint64 `json:"created_unix_seconds,omitempty"`
PublishedUnixSeconds *uint64 `json:"published_unix_seconds,omitempty"`
Current bool `json:"current"`
CurrentPointerValid bool `json:"current_pointer_valid"`
RollbackAvailable bool `json:"rollback_available"`
Stale bool `json:"stale"`
Damaged bool `json:"damaged"`
Referenced bool `json:"referenced"`
Unknown bool `json:"unknown"`
Lifecycle string `json:"lifecycle"`
ManifestContractStatus string `json:"manifest_contract_status"`
ArtifactIntegrityStatus string `json:"artifact_integrity_status"`
DistributionIntegrityStatus string `json:"distribution_integrity_status"`
Legacy bool `json:"legacy"`
RollbackPreviousReleaseID string `json:"rollback_previous_release_id,omitempty"`
Diagnostics []string `json:"diagnostics,omitempty"`
}
// ReleaseStatusReport is the unified official/localized release view.
type ReleaseStatusReport struct {
Status string `json:"status"`
StatusCode string `json:"status_code"`
OfficialCurrentReleaseID string `json:"official_current_release_id,omitempty"`
LocalizedCurrentReleaseID string `json:"localized_current_release_id,omitempty"`
LocalizedSourceOfficialID string `json:"localized_source_official_release_id,omitempty"`
CurrentReleasesMatch bool `json:"current_releases_match"`
DefaultDistributionChannel string `json:"default_distribution_channel"`
OfficialDistributionReady bool `json:"official_distribution_ready"`
LocalizedDistributionReady bool `json:"localized_distribution_ready"`
Releases []ReleaseSummary `json:"releases"`
}
// ReleaseListReport is the filtered historical release response.
type ReleaseListReport struct {
Status string `json:"status"`
StatusCode string `json:"status_code"`
Channel string `json:"channel,omitempty"`
Releases []ReleaseSummary `json:"releases"`
}
// ReleaseDistributionEntry is one Rust-verified resource manifest entry.
type ReleaseDistributionEntry struct {
URL string `json:"url"`
Destination string `json:"destination"`
Bytes uint64 `json:"bytes"`
BLAKE3 string `json:"blake3"`
}
// ReleaseDistributionPage is a typed page for one selected release.
type ReleaseDistributionPage struct {
Available bool `json:"available"`
Channel string `json:"channel"`
ReleaseID string `json:"release_id,omitempty"`
ResourceRoot string `json:"resource_root,omitempty"`
SourceOfficialReleaseID string `json:"source_official_release_id,omitempty"`
Current bool `json:"current"`
Status string `json:"status"`
StatusCode string `json:"status_code"`
ArtifactIntegrityStatus string `json:"artifact_integrity_status"`
Total int `json:"total"`
Offset int `json:"offset"`
Limit int `json:"limit"`
Entries []ReleaseDistributionEntry `json:"entries"`
Diagnostics []string `json:"diagnostics,omitempty"`
}
// ReleaseCleanupEntry is one retained or removable cleanup observation.
type ReleaseCleanupEntry struct {
Channel string `json:"channel"`
ID string `json:"id"`
Path string `json:"path"`
Candidate bool `json:"candidate"`
RetainReasons []string `json:"retain_reasons,omitempty"`
BlockingReferences []string `json:"blocking_references,omitempty"`
}
// ReleaseCleanupReport is the dry-run or execute result.
type ReleaseCleanupReport struct {
Execute bool `json:"execute"`
PlanID string `json:"plan_id"`
Revalidated bool `json:"revalidated"`
Entries []ReleaseCleanupEntry `json:"entries"`
Removed []string `json:"removed"`
Diagnostics []string `json:"diagnostics,omitempty"`
}
type tailParam struct {
Tail int `json:"tail"`
}
@@ -981,6 +1092,30 @@ func (c *Client) CatalogDiff(ctx context.Context) (json.RawMessage, error) {
return c.rawData(ctx, "catalog.diff", nil)
}
func (c *Client) ReleaseStatus(ctx context.Context) (*ReleaseStatusReport, error) {
var out ReleaseStatusReport
_, err := c.Call(ctx, "release.status", nil, &out)
return &out, err
}
func (c *Client) ReleaseList(ctx context.Context, params ReleaseListParams) (*ReleaseListReport, error) {
var out ReleaseListReport
_, err := c.Call(ctx, "release.list", params, &out)
return &out, err
}
func (c *Client) ReleaseDistribution(ctx context.Context, params ReleaseDistributionParams) (*ReleaseDistributionPage, error) {
var out ReleaseDistributionPage
_, err := c.Call(ctx, "release.distribution", params, &out)
return &out, err
}
func (c *Client) ReleaseCleanup(ctx context.Context, params ReleaseCleanupParams) (*ReleaseCleanupReport, error) {
var out ReleaseCleanupReport
_, err := c.Call(ctx, "release.cleanup", params, &out)
return &out, err
}
func (c *Client) CatalogRefresh(ctx context.Context, force bool) (*TaskAccepted, error) {
var out TaskAccepted
_, err := c.Call(ctx, "catalog.refresh", boolParam{Force: force}, &out)
+49
View File
@@ -88,6 +88,55 @@ func TestResourceRepairQueuesTask(t *testing.T) {
}
}
func TestReleaseDistributionUsesTypedRPCContract(t *testing.T) {
client := newTestClient(t, func(t *testing.T, req testRequest) testResponse {
if req.Method != "release.distribution" {
t.Fatalf("method = %s", req.Method)
}
var params ReleaseDistributionParams
if err := json.Unmarshal(req.Params, &params); err != nil {
t.Fatalf("decode params: %v", err)
}
if params.Channel != "localized" || params.ReleaseID != "localized-1" || params.Offset != 2 || params.Limit != 10 {
t.Fatalf("params = %#v", params)
}
return testResponse{
Result: testEnvelope{
OK: true,
Status: "ok",
Data: map[string]any{
"available": true,
"channel": "localized",
"release_id": "localized-1",
"resource_root": "/tmp/localized",
"total": 3,
"offset": 2,
"limit": 10,
"entries": []any{map[string]any{
"url": "https://example.invalid/data.bin",
"destination": "host/data.bin",
"bytes": 4,
"blake3": "abcd",
}},
},
},
}
})
page, err := client.ReleaseDistribution(context.Background(), ReleaseDistributionParams{
Channel: "localized",
ReleaseID: "localized-1",
Offset: 2,
Limit: 10,
})
if err != nil {
t.Fatalf("ReleaseDistribution error: %v", err)
}
if !page.Available || page.ResourceRoot != "/tmp/localized" || len(page.Entries) != 1 {
t.Fatalf("page = %#v", page)
}
}
func TestDaemonRestartSendsControlMethod(t *testing.T) {
client := newTestClient(t, func(t *testing.T, req testRequest) testResponse {
if req.Method != "daemon.restart" {