Files
sub2api-cn-relay-manager/internal/provision/rollback_service.go
phamnazage-jpg 85d495dd16 feat(control-plane): harden host-scoped reconcile and acceptance evidence
- add batch-scoped reconcile_runs persistence and queries
- route batch detail and reconcile writes through batch_id/host_id
- refresh production boards with host-scope acceptance artifacts
- include latest real-host acceptance evidence for self_service and subscription
2026-05-18 22:22:22 +08:00

120 lines
3.8 KiB
Go

package provision
import (
"context"
"errors"
"fmt"
"sub2api-cn-relay-manager/internal/host/sub2api"
"sub2api-cn-relay-manager/internal/pack"
"sub2api-cn-relay-manager/internal/store/sqlite"
)
type rollbackHost interface {
ListManagedResources(ctx context.Context, req sub2api.ListManagedResourcesRequest) (sub2api.ManagedResourceSnapshot, error)
DeleteAccount(ctx context.Context, accountID string) error
DeletePlan(ctx context.Context, planID string) error
DeleteChannel(ctx context.Context, channelID string) error
DeleteGroup(ctx context.Context, groupID string) error
}
type RollbackRequest struct {
Provider pack.ProviderManifest
}
type RollbackReport struct {
AccountsDeleted int
PlansDeleted int
ChannelsDeleted int
GroupsDeleted int
}
type RollbackService struct {
host rollbackHost
}
func NewRollbackService(host rollbackHost) *RollbackService {
return &RollbackService{host: host}
}
func (s *RollbackService) Rollback(ctx context.Context, req RollbackRequest) (RollbackReport, error) {
if s.host == nil {
return RollbackReport{}, fmt.Errorf("rollback host is required")
}
names := SuggestResourceNames(req.Provider)
snapshot, err := s.host.ListManagedResources(ctx, sub2api.ListManagedResourcesRequest{
GroupName: names.Group,
ChannelName: names.Channel,
PlanName: names.Plan,
AccountNamePrefix: SuggestAccountNamePrefix(req.Provider),
})
if err != nil {
return RollbackReport{}, fmt.Errorf("list managed resources: %w", err)
}
return s.rollbackNamedResources(ctx, snapshot)
}
func (s *RollbackService) RollbackStoredResources(ctx context.Context, resources []sqlite.ManagedResource) (RollbackReport, error) {
if s.host == nil {
return RollbackReport{}, fmt.Errorf("rollback host is required")
}
return s.rollbackNamedResources(ctx, namedResourceSnapshotFromStored(resources))
}
func (s *RollbackService) rollbackNamedResources(ctx context.Context, snapshot sub2api.ManagedResourceSnapshot) (RollbackReport, error) {
var report RollbackReport
var errs []error
for index := len(snapshot.Accounts) - 1; index >= 0; index-- {
if err := s.host.DeleteAccount(ctx, snapshot.Accounts[index].ID); err != nil {
errs = append(errs, fmt.Errorf("delete account %s: %w", snapshot.Accounts[index].ID, err))
continue
}
report.AccountsDeleted++
}
for index := len(snapshot.Plans) - 1; index >= 0; index-- {
if err := s.host.DeletePlan(ctx, snapshot.Plans[index].ID); err != nil {
errs = append(errs, fmt.Errorf("delete plan %s: %w", snapshot.Plans[index].ID, err))
continue
}
report.PlansDeleted++
}
for index := len(snapshot.Channels) - 1; index >= 0; index-- {
if err := s.host.DeleteChannel(ctx, snapshot.Channels[index].ID); err != nil {
errs = append(errs, fmt.Errorf("delete channel %s: %w", snapshot.Channels[index].ID, err))
continue
}
report.ChannelsDeleted++
}
for index := len(snapshot.Groups) - 1; index >= 0; index-- {
if err := s.host.DeleteGroup(ctx, snapshot.Groups[index].ID); err != nil {
errs = append(errs, fmt.Errorf("delete group %s: %w", snapshot.Groups[index].ID, err))
continue
}
report.GroupsDeleted++
}
if len(errs) > 0 {
return report, errors.Join(errs...)
}
return report, nil
}
func namedResourceSnapshotFromStored(resources []sqlite.ManagedResource) sub2api.ManagedResourceSnapshot {
snapshot := sub2api.ManagedResourceSnapshot{}
for _, resource := range resources {
ref := sub2api.NamedResource{ID: resource.HostResourceID, Name: resource.ResourceName}
switch resource.ResourceType {
case "account":
snapshot.Accounts = append(snapshot.Accounts, ref)
case "plan":
snapshot.Plans = append(snapshot.Plans, ref)
case "channel":
snapshot.Channels = append(snapshot.Channels, ref)
case "group":
snapshot.Groups = append(snapshot.Groups, ref)
}
}
return snapshot
}