- 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
120 lines
3.8 KiB
Go
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
|
|
}
|