diff --git a/cmd/api/api/instances.go b/cmd/api/api/instances.go index 49350260..7bcde181 100644 --- a/cmd/api/api/instances.go +++ b/cmd/api/api/instances.go @@ -614,6 +614,80 @@ func (s *ApiService) RestoreInstance(ctx context.Context, request oapi.RestoreIn return oapi.RestoreInstance200JSONResponse(instanceToOAPI(*result)), nil } +// PromoteInstanceToTemplate promotes a standby instance into a fork-only template. +// The id parameter can be an instance ID, name, or ID prefix. +// Note: Resolution is handled by ResolveResource middleware. +func (s *ApiService) PromoteInstanceToTemplate(ctx context.Context, request oapi.PromoteInstanceToTemplateRequestObject) (oapi.PromoteInstanceToTemplateResponseObject, error) { + inst := mw.GetResolvedInstance[instances.Instance](ctx) + if inst == nil { + return oapi.PromoteInstanceToTemplate500JSONResponse{ + Code: "internal_error", + Message: "resource not resolved", + }, nil + } + log := logger.FromContext(ctx) + + result, err := s.InstanceManager.PromoteToTemplate(ctx, inst.Id) + if err != nil { + switch { + case errors.Is(err, instances.ErrNotFound): + return oapi.PromoteInstanceToTemplate404JSONResponse{ + Code: "not_found", + Message: "instance not found", + }, nil + case errors.Is(err, instances.ErrInvalidState): + return oapi.PromoteInstanceToTemplate409JSONResponse{ + Code: "invalid_state", + Message: err.Error(), + }, nil + default: + log.ErrorContext(ctx, "failed to promote instance to template", "error", err) + return oapi.PromoteInstanceToTemplate500JSONResponse{ + Code: "internal_error", + Message: "failed to promote instance to template", + }, nil + } + } + return oapi.PromoteInstanceToTemplate200JSONResponse(instanceToOAPI(*result)), nil +} + +// DemoteInstanceTemplate demotes a template back to standby so it can be restored or deleted. +// The id parameter can be an instance ID, name, or ID prefix. +// Note: Resolution is handled by ResolveResource middleware. +func (s *ApiService) DemoteInstanceTemplate(ctx context.Context, request oapi.DemoteInstanceTemplateRequestObject) (oapi.DemoteInstanceTemplateResponseObject, error) { + inst := mw.GetResolvedInstance[instances.Instance](ctx) + if inst == nil { + return oapi.DemoteInstanceTemplate500JSONResponse{ + Code: "internal_error", + Message: "resource not resolved", + }, nil + } + log := logger.FromContext(ctx) + + result, err := s.InstanceManager.DemoteTemplate(ctx, inst.Id) + if err != nil { + switch { + case errors.Is(err, instances.ErrNotFound): + return oapi.DemoteInstanceTemplate404JSONResponse{ + Code: "not_found", + Message: "instance not found", + }, nil + case errors.Is(err, instances.ErrInvalidState): + return oapi.DemoteInstanceTemplate409JSONResponse{ + Code: "invalid_state", + Message: err.Error(), + }, nil + default: + log.ErrorContext(ctx, "failed to demote template", "error", err) + return oapi.DemoteInstanceTemplate500JSONResponse{ + Code: "internal_error", + Message: "failed to demote template", + }, nil + } + } + return oapi.DemoteInstanceTemplate200JSONResponse(instanceToOAPI(*result)), nil +} + // ForkInstance forks an instance from stopped or standby into a new instance. // The id parameter can be an instance ID, name, or ID prefix. // Note: Resolution is handled by ResolveResource middleware. diff --git a/lib/builds/manager_test.go b/lib/builds/manager_test.go index 3b6a3e34..a647a729 100644 --- a/lib/builds/manager_test.go +++ b/lib/builds/manager_test.go @@ -116,6 +116,14 @@ func (m *mockInstanceManager) RestoreInstance(ctx context.Context, id string) (* return nil, nil } +func (m *mockInstanceManager) PromoteToTemplate(ctx context.Context, id string) (*instances.Instance, error) { + return nil, nil +} + +func (m *mockInstanceManager) DemoteTemplate(ctx context.Context, id string) (*instances.Instance, error) { + return nil, nil +} + func (m *mockInstanceManager) RestoreSnapshot(ctx context.Context, id string, snapshotID string, req instances.RestoreSnapshotRequest) (*instances.Instance, error) { return nil, instances.ErrNotSupported } diff --git a/lib/instances/README.md b/lib/instances/README.md index 13ada056..e06622d8 100644 --- a/lib/instances/README.md +++ b/lib/instances/README.md @@ -21,6 +21,7 @@ Manages VM instance lifecycle across multiple hypervisors (Cloud Hypervisor, QEM - `Paused` - VM paused (CH native) - `Shutdown` - VM shutdown, VMM exists (CH native) - `Standby` - No VMM, snapshot exists (can restore) +- `Template` - Standby snapshot promoted to fork-only parent; cannot wake while live forks exist ### Why Config Disk? (configdisk.go) @@ -54,6 +55,8 @@ Manages VM instance lifecycle across multiple hypervisors (Cloud Hypervisor, QEM `metadata.json` also carries controller-owned auto-standby runtime timestamps when that feature is enabled, so idle countdown state can survive Hypeman restarts. +`metadata.json` also stores `IsTemplate`, `ForkOfTemplate`, and `HotPagesPath`. `HotPagesPath` is reserved for the UFFD prefetch path and is cleared on demote. + **Benefits:** - Content-addressable IDs (ULID = time-ordered) - Self-contained: all instance data in one directory @@ -100,6 +103,16 @@ Any State → Stopped 2. Delete all instance data ``` +**PromoteToTemplate / DemoteTemplate:** +``` +Standby → Template (promote) +Template → Standby (demote; refused while live forks exist) +``` +- Promotion is an explicit caller step; forking a Standby does not auto-promote. +- A Template cannot be restored or deleted directly. RestoreInstance on a Template returns an error — callers must DemoteTemplate first. +- Live forks are counted at read time by scanning `ForkOfTemplate` across instances; both Demote and Delete refuse while that count is > 0. +- Forks of a Template are plain instances (Standby by default). They record `ForkOfTemplate = parent.Id` but do not inherit `IsTemplate` or `HotPagesPath`. + ## Snapshot Optimization (standby.go, restore.go) **Reduce snapshot size:** diff --git a/lib/instances/delete.go b/lib/instances/delete.go index 283e0b19..c974ad0e 100644 --- a/lib/instances/delete.go +++ b/lib/instances/delete.go @@ -35,6 +35,16 @@ func (m *manager) deleteInstance( stored := &meta.StoredMetadata log.DebugContext(ctx, "loaded instance", "instance_id", id, "state", inst.State) + if inst.State == StateTemplate { + forks, err := m.countTemplateForks(id) + if err != nil { + return fmt.Errorf("count forks of template %s: %w", id, err) + } + if forks > 0 { + return fmt.Errorf("%w: cannot delete template %s with %d live fork(s); delete forks first", ErrInvalidState, id, forks) + } + } + target, err := m.cancelAndWaitCompressionJob(ctx, m.snapshotJobKeyForInstance(id)) if err != nil { return fmt.Errorf("wait for instance compression to stop: %w", err) diff --git a/lib/instances/fork.go b/lib/instances/fork.go index d3f837dc..1bb4102f 100644 --- a/lib/instances/fork.go +++ b/lib/instances/fork.go @@ -66,6 +66,9 @@ func (m *manager) forkInstance(ctx context.Context, id string, req ForkInstanceR return nil, "", fmt.Errorf("standby source instance: %w", err) } + // Running fork is a one-shot clone that restores the source afterward. + // Promotion is now an explicit caller step, so the running flow simply + // doesn't promote — there's no skip flag to thread anymore. forked, forkErr := m.forkInstanceFromStoppedOrStandby(ctx, id, req, true) if forkErr == nil { if err := m.rotateSourceVsockForRestore(ctx, id, forked.Id); err != nil { @@ -104,14 +107,14 @@ func (m *manager) forkInstance(ctx context.Context, id string, req ForkInstanceR return nil, "", forkErr } return forked, targetState, nil - case StateStopped, StateStandby: + case StateStopped, StateStandby, StateTemplate: forked, err := m.forkInstanceFromStoppedOrStandby(ctx, id, req, false) if err != nil { return nil, "", err } return forked, targetState, nil default: - return nil, "", fmt.Errorf("%w: cannot fork from state %s (must be Stopped or Standby, or Running with from_running=true)", ErrInvalidState, source.State) + return nil, "", fmt.Errorf("%w: cannot fork from state %s (must be Stopped, Standby, or Template, or Running with from_running=true)", ErrInvalidState, source.State) } } @@ -205,10 +208,10 @@ func (m *manager) forkInstanceFromStoppedOrStandby(ctx context.Context, id strin stored := &meta.StoredMetadata switch source.State { - case StateStopped, StateStandby: + case StateStopped, StateStandby, StateTemplate: // allowed default: - return nil, fmt.Errorf("%w: cannot fork from state %s (must be Stopped or Standby)", ErrInvalidState, source.State) + return nil, fmt.Errorf("%w: cannot fork from state %s (must be Stopped, Standby, or Template)", ErrInvalidState, source.State) } if !supportValidated { @@ -250,7 +253,9 @@ func (m *manager) forkInstanceFromStoppedOrStandby(ctx context.Context, id strin }) defer cu.Clean() - if source.State == StateStandby { + fromSnapshot := source.State == StateStandby || source.State == StateTemplate + + if fromSnapshot { if err := m.ensureSnapshotMemoryReady(ctx, m.paths.InstanceSnapshotLatest(id), m.snapshotJobKeyForInstance(id), stored.HypervisorType); err != nil { return nil, fmt.Errorf("prepare standby snapshot for fork: %w", err) } @@ -286,17 +291,22 @@ func (m *manager) forkInstanceFromStoppedOrStandby(ctx context.Context, id strin // phase (Standby for snapshot forks, Stopped for stopped forks) will be // recorded by the appropriate operation when the fork is acted on. forkMeta.Phases.Reset() - switch source.State { - case StateStandby: + if fromSnapshot { forkMeta.Phases.Record(phasetracking.PhaseStandby, now) - case StateStopped: + } else { forkMeta.Phases.Record(phasetracking.PhaseStopped, now) } + // Template-only fields don't carry forward to the fork; the fork is a fresh + // instance regardless of whether the parent is a template. + forkMeta.IsTemplate = false + forkMeta.HotPagesPath = "" + forkMeta.ForkOfTemplate = "" + // Keep the original CID for snapshot-based forks. // Rewriting CID in restored memory snapshots is not reliable across // hypervisors. - if source.State == StateStandby { + if fromSnapshot { forkMeta.VsockCID = stored.VsockCID } else { forkMeta.VsockCID = generateVsockCID(forkID) @@ -309,7 +319,7 @@ func (m *manager) forkInstanceFromStoppedOrStandby(ctx context.Context, id strin forkMeta.MAC = "" } - if source.State == StateStandby { + if fromSnapshot { snapshotConfigPath := m.paths.InstanceSnapshotConfig(forkID) netCfg := (*hypervisor.ForkNetworkConfig)(nil) if forkMeta.NetworkEnabled { @@ -331,6 +341,15 @@ func (m *manager) forkInstanceFromStoppedOrStandby(ctx context.Context, id strin } } + // If the source is already a Template, record the parent linkage so it + // can be counted as a live fork. Live forks are counted at read time by + // scanning ForkOfTemplate across all instances. Plain Standby forks + // don't get this linkage — promotion is an explicit lifecycle step the + // caller must perform via PromoteToTemplate. + if fromSnapshot && stored.IsTemplate { + forkMeta.ForkOfTemplate = stored.Id + } + newMeta := &metadata{StoredMetadata: forkMeta} if err := m.saveMetadata(newMeta); err != nil { return nil, fmt.Errorf("save fork metadata: %w", err) @@ -384,6 +403,10 @@ func resolveForkTargetState(requested State, sourceState State) (State, error) { switch sourceState { case StateRunning, StateStandby, StateStopped: return sourceState, nil + case StateTemplate: + // Forks of a template are plain Standby instances; the fork itself + // is never a template. + return StateStandby, nil default: return "", fmt.Errorf("%w: cannot derive fork target state from source state %s", ErrInvalidState, sourceState) } diff --git a/lib/instances/manager.go b/lib/instances/manager.go index 36e7a627..94bc1e71 100644 --- a/lib/instances/manager.go +++ b/lib/instances/manager.go @@ -39,6 +39,12 @@ type Manager interface { ForkSnapshot(ctx context.Context, snapshotID string, req ForkSnapshotRequest) (*Instance, error) StandbyInstance(ctx context.Context, id string, req StandbyInstanceRequest) (*Instance, error) RestoreInstance(ctx context.Context, id string) (*Instance, error) + // PromoteToTemplate marks a Standby instance as a fork-only Template. + // Requires state == Standby. Idempotent if already a Template. + PromoteToTemplate(ctx context.Context, id string) (*Instance, error) + // DemoteTemplate flips a Template back to Standby so it can be woken or + // deleted. Requires no live forks (instances with ForkOfTemplate == id). + DemoteTemplate(ctx context.Context, id string) (*Instance, error) RestoreSnapshot(ctx context.Context, id string, snapshotID string, req RestoreSnapshotRequest) (*Instance, error) StopInstance(ctx context.Context, id string) (*Instance, error) StartInstance(ctx context.Context, id string, req StartInstanceRequest) (*Instance, error) @@ -435,7 +441,9 @@ func (m *manager) StandbyInstance(ctx context.Context, id string, req StandbyIns return inst, err } -// RestoreInstance restores an instance from standby +// RestoreInstance restores an instance from standby. Templates must be +// demoted via DemoteTemplate first; this method does not auto-demote so +// that the lifecycle remains explicit. func (m *manager) RestoreInstance(ctx context.Context, id string) (*Instance, error) { lock := m.getInstanceLock(id) lock.Lock() @@ -454,6 +462,69 @@ func (m *manager) RestoreInstance(ctx context.Context, id string) (*Instance, er return inst, err } +// PromoteToTemplate marks a Standby instance as a fork-only Template. +// Standby is the only legal source state. Idempotent: re-promoting a +// Template returns it as-is. +func (m *manager) PromoteToTemplate(ctx context.Context, id string) (*Instance, error) { + lock := m.getInstanceLock(id) + lock.Lock() + defer lock.Unlock() + meta, err := m.loadMetadata(id) + if err != nil { + return nil, err + } + inst := m.toInstance(ctx, meta) + if inst.State == StateTemplate { + return &inst, nil + } + if inst.State != StateStandby { + return nil, fmt.Errorf("%w: cannot promote instance in state %s to template (must be Standby)", ErrInvalidState, inst.State) + } + meta.IsTemplate = true + if err := m.saveMetadata(meta); err != nil { + return nil, fmt.Errorf("save metadata after template promote: %w", err) + } + promoted := m.toInstance(ctx, meta) + return &promoted, nil +} + +// DemoteTemplate flips a Template back to Standby so it can be woken or +// deleted. Refuses while any live forks still reference this id via +// ForkOfTemplate. +func (m *manager) DemoteTemplate(ctx context.Context, id string) (*Instance, error) { + lock := m.getInstanceLock(id) + lock.Lock() + defer lock.Unlock() + meta, err := m.loadMetadata(id) + if err != nil { + return nil, err + } + inst := m.toInstance(ctx, meta) + if inst.State == StateStandby { + return &inst, nil + } + if inst.State != StateTemplate { + return nil, fmt.Errorf("%w: cannot demote instance in state %s (must be Template)", ErrInvalidState, inst.State) + } + forks, err := m.countTemplateForks(id) + if err != nil { + return nil, fmt.Errorf("count forks of template %s: %w", id, err) + } + if forks > 0 { + return nil, fmt.Errorf("%w: cannot demote template %s with %d live fork(s); delete forks first", ErrInvalidState, id, forks) + } + if err := StateTemplate.CanTransitionTo(StateStandby); err != nil { + return nil, err + } + meta.IsTemplate = false + meta.HotPagesPath = "" + if err := m.saveMetadata(meta); err != nil { + return nil, fmt.Errorf("save metadata after template demote: %w", err) + } + demoted := m.toInstance(ctx, meta) + return &demoted, nil +} + func (m *manager) RestoreSnapshot(ctx context.Context, id string, snapshotID string, req RestoreSnapshotRequest) (*Instance, error) { lock := m.getInstanceLock(id) lock.Lock() diff --git a/lib/instances/manager_test.go b/lib/instances/manager_test.go index 169237b6..52cbdb93 100644 --- a/lib/instances/manager_test.go +++ b/lib/instances/manager_test.go @@ -1497,12 +1497,17 @@ func TestStateTransitions(t *testing.T) { {"Standby to Paused", StateStandby, StatePaused, false}, {"Shutdown to Stopped", StateShutdown, StateStopped, false}, {"Standby to Stopped", StateStandby, StateStopped, false}, + {"Standby to Template", StateStandby, StateTemplate, false}, + {"Template to Standby", StateTemplate, StateStandby, false}, + {"Template to Stopped", StateTemplate, StateStopped, false}, // Invalid transitions {"Running to Standby", StateRunning, StateStandby, true}, {"Stopped to Running", StateStopped, StateRunning, true}, {"Stopped to Initializing", StateStopped, StateInitializing, true}, {"Standby to Running", StateStandby, StateRunning, true}, {"Initializing to Paused", StateInitializing, StatePaused, true}, + {"Template to Running", StateTemplate, StateRunning, true}, + {"Template to Paused", StateTemplate, StatePaused, true}, } for _, tt := range tests { diff --git a/lib/instances/query.go b/lib/instances/query.go index 2be21939..079c5b03 100644 --- a/lib/instances/query.go +++ b/lib/instances/query.go @@ -72,9 +72,12 @@ func (m *manager) deriveStateWithOptions(ctx context.Context, stored *StoredMeta // 1. Check if socket exists if _, err := os.Stat(stored.SocketPath); err != nil { - // No socket - check for snapshot to distinguish Stopped vs Standby + // No socket - check for snapshot to distinguish Stopped vs Standby/Template m.invalidateCachedHypervisorState(stored.Id) if m.hasSnapshot(stored.DataDir) { + if stored.IsTemplate { + return stateResult{State: StateTemplate} + } return stateResult{State: StateStandby} } return stateResult{State: StateStopped} @@ -822,6 +825,29 @@ func (m *manager) listInstances(ctx context.Context) ([]Instance, error) { return result, nil } +// countTemplateForks returns the number of instances with ForkOfTemplate==templateID. +// Bubbles metadata load errors: this count guards DeleteInstance and +// DemoteTemplate, and silently skipping an unreadable file would risk +// undercounting forks and freeing a template whose pages are still mapped. +func (m *manager) countTemplateForks(templateID string) (int, error) { + files, err := m.listMetadataFiles() + if err != nil { + return 0, err + } + count := 0 + for _, file := range files { + id := filepath.Base(filepath.Dir(file)) + meta, err := m.loadMetadata(id) + if err != nil { + return 0, fmt.Errorf("load metadata for %s while counting forks of %s: %w", id, templateID, err) + } + if meta.ForkOfTemplate == templateID { + count++ + } + } + return count, nil +} + func (m *manager) findInstanceMetadataByExactName(name string) (*metadata, error) { files, err := m.listMetadataFiles() if err != nil { diff --git a/lib/instances/snapshot.go b/lib/instances/snapshot.go index 5c8030e9..7911f445 100644 --- a/lib/instances/snapshot.go +++ b/lib/instances/snapshot.go @@ -263,6 +263,9 @@ func (m *manager) restoreSnapshot(ctx context.Context, id string, snapshotID str if sourceInst.State == StateRunning { return nil, fmt.Errorf("%w: cannot restore snapshot while source is %s", ErrInvalidState, sourceInst.State) } + if sourceInst.State == StateTemplate { + return nil, fmt.Errorf("%w: cannot restore snapshot onto template %s; call DemoteTemplate first", ErrInvalidState, id) + } targetState, err := resolveSnapshotTargetState(rec.Snapshot.Kind, req.TargetState) if err != nil { diff --git a/lib/instances/state.go b/lib/instances/state.go index ba3d41f9..a1afe3ef 100644 --- a/lib/instances/state.go +++ b/lib/instances/state.go @@ -34,8 +34,13 @@ var ValidTransitions = map[State][]State{ StateCreated, // start VMM process }, StateStandby: { - StatePaused, // start VMM + restore (atomic operation) - StateStopped, // delete snapshot + cleanup (terminal) + StatePaused, // start VMM + restore (atomic operation) + StateStopped, // delete snapshot + cleanup (terminal) + StateTemplate, // promote to fork-only parent (gated by fork intent) + }, + StateTemplate: { + StateStandby, // un-promote (allowed only when no live forks) + StateStopped, // delete (allowed only when no live forks) }, // StateUnknown means we failed to determine state - no transitions allowed. // Operations on instances in Unknown state should fail with an error @@ -75,7 +80,7 @@ func (s State) RequiresVMM() bool { switch s { case StateCreated, StateInitializing, StateRunning, StatePaused, StateShutdown: return true - case StateStopped, StateStandby, StateUnknown: + case StateStopped, StateStandby, StateTemplate, StateUnknown: return false default: return false diff --git a/lib/instances/templates_test.go b/lib/instances/templates_test.go new file mode 100644 index 00000000..a8c37b48 --- /dev/null +++ b/lib/instances/templates_test.go @@ -0,0 +1,348 @@ +package instances + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "github.com/kernel/hypeman/cmd/api/config" + "github.com/kernel/hypeman/lib/devices" + "github.com/kernel/hypeman/lib/hypervisor" + "github.com/kernel/hypeman/lib/images" + "github.com/kernel/hypeman/lib/network" + "github.com/kernel/hypeman/lib/paths" + "github.com/kernel/hypeman/lib/system" + "github.com/kernel/hypeman/lib/vmm" + "github.com/kernel/hypeman/lib/volumes" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// newStorageOnlyManager wires up just enough of the manager to exercise +// metadata + filesystem behavior without needing real VMs or images. +func newStorageOnlyManager(t *testing.T) (*manager, string) { + t.Helper() + tmpDir := t.TempDir() + + cfg := &config.Config{ + DataDir: tmpDir, + Network: newParallelTestNetworkConfig(t), + Oversubscription: config.OversubscriptionConfig{ + CPU: 1.0, Memory: 1.0, Disk: 1.0, Network: 1.0, + }, + } + + p := paths.New(tmpDir) + imageManager, _ := images.NewManager(p, 1, nil) + systemManager := system.NewManager(p) + networkManager := network.NewManager(p, cfg, nil) + deviceManager := devices.NewManager(p) + volumeManager := volumes.NewManager(p, 0, nil) + limits := ResourceLimits{ + MaxOverlaySize: 100 * 1024 * 1024 * 1024, + } + mgr := NewManager(p, imageManager, systemManager, networkManager, deviceManager, volumeManager, limits, "", SnapshotPolicy{}, nil, nil).(*manager) + return mgr, tmpDir +} + +// writeTemplateMetadata writes a metadata.json marked as a template with a +// fake snapshot directory so deriveState returns StateTemplate, plus the +// requested number of fork metadata files pointing at it. +func writeTemplateMetadata(t *testing.T, mgr *manager, id string, forkCount int) { + t.Helper() + require.NoError(t, mgr.ensureDirectories(id)) + snapshotDir := filepath.Join(mgr.paths.InstanceDir(id), "snapshots", "snapshot-latest") + require.NoError(t, os.MkdirAll(snapshotDir, 0755)) + require.NoError(t, os.WriteFile(filepath.Join(snapshotDir, "config.json"), []byte("{}"), 0644)) + + meta := &metadata{ + StoredMetadata: StoredMetadata{ + Id: id, + Name: id, + Image: "test:latest", + Vcpus: 1, + Size: 1024 * 1024 * 1024, + CreatedAt: time.Now(), + HypervisorType: hypervisor.TypeCloudHypervisor, + SocketPath: filepath.Join(mgr.paths.InstanceDir(id), "ch.sock"), + DataDir: mgr.paths.InstanceDir(id), + HypervisorVersion: string(vmm.V49_0), + IsTemplate: true, + }, + } + require.NoError(t, mgr.saveMetadata(meta)) + + for i := 0; i < forkCount; i++ { + writeForkMetadata(t, mgr, fmt.Sprintf("%s-fork-%d", id, i), id) + } +} + +// writeForkMetadata writes a Stopped fork metadata file pointing at templateID. +func writeForkMetadata(t *testing.T, mgr *manager, id, templateID string) { + t.Helper() + require.NoError(t, mgr.ensureDirectories(id)) + meta := &metadata{ + StoredMetadata: StoredMetadata{ + Id: id, + Name: id, + Image: "test:latest", + Vcpus: 1, + Size: 1024 * 1024 * 1024, + CreatedAt: time.Now(), + HypervisorType: hypervisor.TypeCloudHypervisor, + SocketPath: filepath.Join(mgr.paths.InstanceDir(id), "ch.sock"), + DataDir: mgr.paths.InstanceDir(id), + HypervisorVersion: string(vmm.V49_0), + ForkOfTemplate: templateID, + }, + } + require.NoError(t, mgr.saveMetadata(meta)) +} + +func TestDeriveStateTemplate(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + ctx := context.Background() + + writeTemplateMetadata(t, mgr, "tmpl-1", 2) + + inst, err := mgr.GetInstance(ctx, "tmpl-1") + require.NoError(t, err) + assert.Equal(t, StateTemplate, inst.State) + assert.True(t, inst.IsTemplate) +} + +func TestCountTemplateForks(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + + writeTemplateMetadata(t, mgr, "tmpl-a", 3) + writeTemplateMetadata(t, mgr, "tmpl-b", 1) + + got, err := mgr.countTemplateForks("tmpl-a") + require.NoError(t, err) + assert.Equal(t, 3, got) + + got, err = mgr.countTemplateForks("tmpl-b") + require.NoError(t, err) + assert.Equal(t, 1, got) + + got, err = mgr.countTemplateForks("tmpl-missing") + require.NoError(t, err) + assert.Equal(t, 0, got) +} + +func TestRestoreInstanceTemplateRefused(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + ctx := context.Background() + + // RestoreInstance no longer auto-demotes templates. Callers must call + // DemoteTemplate first. Templates without forks are rejected too. + writeTemplateMetadata(t, mgr, "tmpl-idle", 0) + writeTemplateMetadata(t, mgr, "tmpl-busy", 1) + + _, err := mgr.RestoreInstance(ctx, "tmpl-idle") + require.Error(t, err) + assert.True(t, errors.Is(err, ErrInvalidState), "expected ErrInvalidState, got %v", err) + + _, err = mgr.RestoreInstance(ctx, "tmpl-busy") + require.Error(t, err) + assert.True(t, errors.Is(err, ErrInvalidState), "expected ErrInvalidState, got %v", err) +} + +func TestRestoreSnapshotTemplateRefused(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + ctx := context.Background() + + // RestoreSnapshot must refuse onto Template state regardless of fork count. + // Restoring would rewrite the template payload (breaking forks that share + // it) and silently clear IsTemplate. Callers must DemoteTemplate first. + writeTemplateMetadata(t, mgr, "tmpl-idle", 0) + writeTemplateMetadata(t, mgr, "tmpl-busy", 1) + + saveTemplateSnapshotRecord(t, mgr, "snap-idle", "tmpl-idle") + saveTemplateSnapshotRecord(t, mgr, "snap-busy", "tmpl-busy") + + _, err := mgr.RestoreSnapshot(ctx, "tmpl-idle", "snap-idle", RestoreSnapshotRequest{}) + require.Error(t, err) + assert.True(t, errors.Is(err, ErrInvalidState), "expected ErrInvalidState, got %v", err) + + _, err = mgr.RestoreSnapshot(ctx, "tmpl-busy", "snap-busy", RestoreSnapshotRequest{}) + require.Error(t, err) + assert.True(t, errors.Is(err, ErrInvalidState), "expected ErrInvalidState, got %v", err) + + // Template flag must remain set after a refused restore. + loaded, err := mgr.loadMetadata("tmpl-busy") + require.NoError(t, err) + assert.True(t, loaded.IsTemplate) +} + +// saveTemplateSnapshotRecord writes a snapshot record on disk pointing at the +// given template instance, sufficient for restoreSnapshot to load it. +func saveTemplateSnapshotRecord(t *testing.T, mgr *manager, snapshotID, sourceID string) { + t.Helper() + meta, err := mgr.loadMetadata(sourceID) + require.NoError(t, err) + require.NoError(t, mgr.saveSnapshotRecord(&snapshotRecord{ + Snapshot: Snapshot{ + Id: snapshotID, + Name: snapshotID, + Kind: SnapshotKindStandby, + SourceInstanceID: sourceID, + SourceName: meta.Name, + SourceHypervisor: meta.HypervisorType, + CreatedAt: time.Now(), + SizeBytes: 1, + }, + StoredMetadata: meta.StoredMetadata, + })) +} + +func TestDemoteTemplateClearsTemplateFields(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + ctx := context.Background() + + writeTemplateMetadata(t, mgr, "tmpl-idle", 0) + + inst, err := mgr.DemoteTemplate(ctx, "tmpl-idle") + require.NoError(t, err) + assert.Equal(t, StateStandby, inst.State) + assert.False(t, inst.IsTemplate) + + loaded, err := mgr.loadMetadata("tmpl-idle") + require.NoError(t, err) + assert.False(t, loaded.IsTemplate) + assert.Empty(t, loaded.HotPagesPath) +} + +func TestDemoteTemplateRefusedWithLiveForks(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + ctx := context.Background() + + writeTemplateMetadata(t, mgr, "tmpl-busy", 2) + + _, err := mgr.DemoteTemplate(ctx, "tmpl-busy") + require.Error(t, err) + assert.True(t, errors.Is(err, ErrInvalidState), "expected ErrInvalidState, got %v", err) + + loaded, err := mgr.loadMetadata("tmpl-busy") + require.NoError(t, err) + assert.True(t, loaded.IsTemplate, "template flag should remain set after refused demote") +} + +func TestDemoteTemplateIdempotentOnStandby(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + ctx := context.Background() + + // Write a Standby (non-template) metadata; demote should no-op. + writeForkMetadata(t, mgr, "standby-only", "") + // writeForkMetadata sets ForkOfTemplate; clear it so this is a plain Standby. + meta, err := mgr.loadMetadata("standby-only") + require.NoError(t, err) + meta.ForkOfTemplate = "" + require.NoError(t, mgr.saveMetadata(meta)) + + // Mark as having a snapshot so deriveState returns Standby (not Stopped). + snapshotDir := filepath.Join(mgr.paths.InstanceDir("standby-only"), "snapshots", "snapshot-latest") + require.NoError(t, os.MkdirAll(snapshotDir, 0755)) + require.NoError(t, os.WriteFile(filepath.Join(snapshotDir, "config.json"), []byte("{}"), 0644)) + + inst, err := mgr.DemoteTemplate(ctx, "standby-only") + require.NoError(t, err) + assert.Equal(t, StateStandby, inst.State) +} + +func TestPromoteToTemplateFromStandby(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + ctx := context.Background() + + // Start as a Standby-with-snapshot (no IsTemplate flag yet). + writeTemplateMetadata(t, mgr, "soon-tmpl", 0) + meta, err := mgr.loadMetadata("soon-tmpl") + require.NoError(t, err) + meta.IsTemplate = false + require.NoError(t, mgr.saveMetadata(meta)) + + inst, err := mgr.PromoteToTemplate(ctx, "soon-tmpl") + require.NoError(t, err) + assert.Equal(t, StateTemplate, inst.State) + assert.True(t, inst.IsTemplate) + + loaded, err := mgr.loadMetadata("soon-tmpl") + require.NoError(t, err) + assert.True(t, loaded.IsTemplate) +} + +func TestPromoteToTemplateIdempotentOnTemplate(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + ctx := context.Background() + + writeTemplateMetadata(t, mgr, "already-tmpl", 0) + + inst, err := mgr.PromoteToTemplate(ctx, "already-tmpl") + require.NoError(t, err) + assert.Equal(t, StateTemplate, inst.State) + assert.True(t, inst.IsTemplate) +} + +func TestPromoteToTemplateRejectsNonStandby(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + ctx := context.Background() + + // A bare instance with no snapshot directory derives to StateStopped. + writeForkMetadata(t, mgr, "stopped-inst", "") + meta, err := mgr.loadMetadata("stopped-inst") + require.NoError(t, err) + meta.ForkOfTemplate = "" + require.NoError(t, mgr.saveMetadata(meta)) + + _, err = mgr.PromoteToTemplate(ctx, "stopped-inst") + require.Error(t, err) + assert.True(t, errors.Is(err, ErrInvalidState), "expected ErrInvalidState, got %v", err) +} + +func TestDeleteInstanceTemplateWithForksRefused(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + ctx := context.Background() + + writeTemplateMetadata(t, mgr, "tmpl-with-forks", 3) + + err := mgr.DeleteInstance(ctx, "tmpl-with-forks") + require.Error(t, err) + assert.True(t, errors.Is(err, ErrInvalidState), "expected ErrInvalidState, got %v", err) + + loaded, err := mgr.loadMetadata("tmpl-with-forks") + require.NoError(t, err) + assert.True(t, loaded.IsTemplate) +} + +func TestDeleteForkLeavesTemplateIntact(t *testing.T) { + t.Parallel() + mgr, _ := newStorageOnlyManager(t) + ctx := context.Background() + + writeTemplateMetadata(t, mgr, "tmpl-parent", 2) + + require.NoError(t, mgr.DeleteInstance(ctx, "tmpl-parent-fork-0")) + + parent, err := mgr.loadMetadata("tmpl-parent") + require.NoError(t, err) + assert.True(t, parent.IsTemplate) + + got, err := mgr.countTemplateForks("tmpl-parent") + require.NoError(t, err) + assert.Equal(t, 1, got) +} diff --git a/lib/instances/types.go b/lib/instances/types.go index bf539714..50b7e85f 100644 --- a/lib/instances/types.go +++ b/lib/instances/types.go @@ -21,6 +21,7 @@ const ( StatePaused State = "Paused" // VM paused (CH native) StateShutdown State = "Shutdown" // VM shutdown, VMM exists (CH native) StateStandby State = "Standby" // No VMM, snapshot exists + StateTemplate State = "Template" // Standby snapshot promoted to fork-only parent; cannot wake while any forks exist StateUnknown State = "Unknown" // Failed to determine state (VMM query failed) ) @@ -145,6 +146,21 @@ type StoredMetadata struct { // Persisted so server restarts can recover delayed or interrupted jobs. PendingStandbyCompression *PendingStandbyCompression + // IsTemplate marks a Standby instance promoted to a fork-only parent. + // When true, derived state is Template instead of Standby and the + // instance cannot wake until un-promoted. + IsTemplate bool + + // ForkOfTemplate is the ID of the template this instance was forked from. + // Empty when the instance is not a template-backed fork. Live forks of a + // template are counted at read time by scanning this field. + ForkOfTemplate string + + // HotPagesPath points to a precomputed hot-pages file used by the UFFD page + // server prefetch path. Reserved for the prefetch feature; cleared when the + // instance is un-promoted out of Template state. + HotPagesPath string + // Automatic standby policy driven by host-observed inbound TCP activity. AutoStandby *autostandby.Policy diff --git a/lib/instances/wait_test.go b/lib/instances/wait_test.go index bab6f06d..93936a20 100644 --- a/lib/instances/wait_test.go +++ b/lib/instances/wait_test.go @@ -54,6 +54,10 @@ func (s *stubManager) StandbyInstance(context.Context, string, StandbyInstanceRe return nil, nil } func (s *stubManager) RestoreInstance(context.Context, string) (*Instance, error) { return nil, nil } +func (s *stubManager) PromoteToTemplate(context.Context, string) (*Instance, error) { + return nil, nil +} +func (s *stubManager) DemoteTemplate(context.Context, string) (*Instance, error) { return nil, nil } func (s *stubManager) RestoreSnapshot(context.Context, string, string, RestoreSnapshotRequest) (*Instance, error) { return nil, nil } diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index 919636aa..9bb980a6 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -145,6 +145,7 @@ const ( InstanceStateShutdown InstanceState = "Shutdown" InstanceStateStandby InstanceState = "Standby" InstanceStateStopped InstanceState = "Stopped" + InstanceStateTemplate InstanceState = "Template" InstanceStateUnknown InstanceState = "Unknown" ) @@ -949,6 +950,7 @@ type Instance struct { // - Shutdown: VM shut down but VMM exists (Cloud Hypervisor native) // - Stopped: No VMM running, no snapshot exists // - Standby: No VMM running, snapshot exists (can be restored) + // - Template: Standby snapshot promoted to a fork-only parent; cannot wake while forks exist // - Unknown: Failed to determine state (see state_error for details) State InstanceState `json:"state"` @@ -988,6 +990,7 @@ type InstanceGPU struct { // - Shutdown: VM shut down but VMM exists (Cloud Hypervisor native) // - Stopped: No VMM running, no snapshot exists // - Standby: No VMM running, snapshot exists (can be restored) +// - Template: Standby snapshot promoted to a fork-only parent; cannot wake while forks exist // - Unknown: Failed to determine state (see state_error for details) type InstanceState string @@ -1414,6 +1417,7 @@ type WaitForStateResponse struct { // - Shutdown: VM shut down but VMM exists (Cloud Hypervisor native) // - Stopped: No VMM running, no snapshot exists // - Standby: No VMM running, snapshot exists (can be restored) + // - Template: Standby snapshot promoted to a fork-only parent; cannot wake while forks exist // - Unknown: Failed to determine state (see state_error for details) State InstanceState `json:"state"` @@ -1799,6 +1803,9 @@ type ClientInterface interface { // GetAutoStandbyStatus request GetAutoStandbyStatus(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + // DemoteInstanceTemplate request + DemoteInstanceTemplate(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + // ForkInstanceWithBody request with any body ForkInstanceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1807,6 +1814,9 @@ type ClientInterface interface { // GetInstanceLogs request GetInstanceLogs(ctx context.Context, id string, params *GetInstanceLogsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // PromoteInstanceToTemplate request + PromoteInstanceToTemplate(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + // RestoreInstance request RestoreInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -2261,6 +2271,18 @@ func (c *Client) GetAutoStandbyStatus(ctx context.Context, id string, reqEditors return c.Client.Do(req) } +func (c *Client) DemoteInstanceTemplate(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDemoteInstanceTemplateRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) ForkInstanceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewForkInstanceRequestWithBody(c.Server, id, contentType, body) if err != nil { @@ -2297,6 +2319,18 @@ func (c *Client) GetInstanceLogs(ctx context.Context, id string, params *GetInst return c.Client.Do(req) } +func (c *Client) PromoteInstanceToTemplate(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPromoteInstanceToTemplateRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) RestoreInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewRestoreInstanceRequest(c.Server, id) if err != nil { @@ -3686,6 +3720,40 @@ func NewGetAutoStandbyStatusRequest(server string, id string) (*http.Request, er return req, nil } +// NewDemoteInstanceTemplateRequest generates requests for DemoteInstanceTemplate +func NewDemoteInstanceTemplateRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances/%s/demote-template", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewForkInstanceRequest calls the generic ForkInstance builder with application/json body func NewForkInstanceRequest(server string, id string, body ForkInstanceJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -3821,6 +3889,40 @@ func NewGetInstanceLogsRequest(server string, id string, params *GetInstanceLogs return req, nil } +// NewPromoteInstanceToTemplateRequest generates requests for PromoteInstanceToTemplate +func NewPromoteInstanceToTemplateRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances/%s/promote-template", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewRestoreInstanceRequest generates requests for RestoreInstance func NewRestoreInstanceRequest(server string, id string) (*http.Request, error) { var err error @@ -5119,6 +5221,9 @@ type ClientWithResponsesInterface interface { // GetAutoStandbyStatusWithResponse request GetAutoStandbyStatusWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetAutoStandbyStatusResponse, error) + // DemoteInstanceTemplateWithResponse request + DemoteInstanceTemplateWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DemoteInstanceTemplateResponse, error) + // ForkInstanceWithBodyWithResponse request with any body ForkInstanceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ForkInstanceResponse, error) @@ -5127,6 +5232,9 @@ type ClientWithResponsesInterface interface { // GetInstanceLogsWithResponse request GetInstanceLogsWithResponse(ctx context.Context, id string, params *GetInstanceLogsParams, reqEditors ...RequestEditorFn) (*GetInstanceLogsResponse, error) + // PromoteInstanceToTemplateWithResponse request + PromoteInstanceToTemplateWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*PromoteInstanceToTemplateResponse, error) + // RestoreInstanceWithResponse request RestoreInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*RestoreInstanceResponse, error) @@ -5829,6 +5937,31 @@ func (r GetAutoStandbyStatusResponse) StatusCode() int { return 0 } +type DemoteInstanceTemplateResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Instance + JSON404 *Error + JSON409 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r DemoteInstanceTemplateResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DemoteInstanceTemplateResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type ForkInstanceResponse struct { Body []byte HTTPResponse *http.Response @@ -5879,6 +6012,31 @@ func (r GetInstanceLogsResponse) StatusCode() int { return 0 } +type PromoteInstanceToTemplateResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Instance + JSON404 *Error + JSON409 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r PromoteInstanceToTemplateResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PromoteInstanceToTemplateResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type RestoreInstanceResponse struct { Body []byte HTTPResponse *http.Response @@ -6763,6 +6921,15 @@ func (c *ClientWithResponses) GetAutoStandbyStatusWithResponse(ctx context.Conte return ParseGetAutoStandbyStatusResponse(rsp) } +// DemoteInstanceTemplateWithResponse request returning *DemoteInstanceTemplateResponse +func (c *ClientWithResponses) DemoteInstanceTemplateWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DemoteInstanceTemplateResponse, error) { + rsp, err := c.DemoteInstanceTemplate(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseDemoteInstanceTemplateResponse(rsp) +} + // ForkInstanceWithBodyWithResponse request with arbitrary body returning *ForkInstanceResponse func (c *ClientWithResponses) ForkInstanceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ForkInstanceResponse, error) { rsp, err := c.ForkInstanceWithBody(ctx, id, contentType, body, reqEditors...) @@ -6789,6 +6956,15 @@ func (c *ClientWithResponses) GetInstanceLogsWithResponse(ctx context.Context, i return ParseGetInstanceLogsResponse(rsp) } +// PromoteInstanceToTemplateWithResponse request returning *PromoteInstanceToTemplateResponse +func (c *ClientWithResponses) PromoteInstanceToTemplateWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*PromoteInstanceToTemplateResponse, error) { + rsp, err := c.PromoteInstanceToTemplate(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParsePromoteInstanceToTemplateResponse(rsp) +} + // RestoreInstanceWithResponse request returning *RestoreInstanceResponse func (c *ClientWithResponses) RestoreInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*RestoreInstanceResponse, error) { rsp, err := c.RestoreInstance(ctx, id, reqEditors...) @@ -8142,6 +8318,53 @@ func ParseGetAutoStandbyStatusResponse(rsp *http.Response) (*GetAutoStandbyStatu return response, nil } +// ParseDemoteInstanceTemplateResponse parses an HTTP response from a DemoteInstanceTemplateWithResponse call +func ParseDemoteInstanceTemplateResponse(rsp *http.Response) (*DemoteInstanceTemplateResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DemoteInstanceTemplateResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Instance + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseForkInstanceResponse parses an HTTP response from a ForkInstanceWithResponse call func ParseForkInstanceResponse(rsp *http.Response) (*ForkInstanceResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -8236,6 +8459,53 @@ func ParseGetInstanceLogsResponse(rsp *http.Response) (*GetInstanceLogsResponse, return response, nil } +// ParsePromoteInstanceToTemplateResponse parses an HTTP response from a PromoteInstanceToTemplateWithResponse call +func ParsePromoteInstanceToTemplateResponse(rsp *http.Response) (*PromoteInstanceToTemplateResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PromoteInstanceToTemplateResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Instance + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseRestoreInstanceResponse parses an HTTP response from a RestoreInstanceWithResponse call func ParseRestoreInstanceResponse(rsp *http.Response) (*RestoreInstanceResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -9446,12 +9716,18 @@ type ServerInterface interface { // Get auto-standby diagnostic status // (GET /instances/{id}/auto-standby/status) GetAutoStandbyStatus(w http.ResponseWriter, r *http.Request, id string) + // Demote a template back to standby so it can be restored or deleted + // (POST /instances/{id}/demote-template) + DemoteInstanceTemplate(w http.ResponseWriter, r *http.Request, id string) // Fork an instance from stopped, standby, or running (with from_running=true) // (POST /instances/{id}/fork) ForkInstance(w http.ResponseWriter, r *http.Request, id string) // Stream instance logs (SSE) // (GET /instances/{id}/logs) GetInstanceLogs(w http.ResponseWriter, r *http.Request, id string, params GetInstanceLogsParams) + // Promote a standby instance into a fork-only template + // (POST /instances/{id}/promote-template) + PromoteInstanceToTemplate(w http.ResponseWriter, r *http.Request, id string) // Restore instance from standby // (POST /instances/{id}/restore) RestoreInstance(w http.ResponseWriter, r *http.Request, id string) @@ -9683,6 +9959,12 @@ func (_ Unimplemented) GetAutoStandbyStatus(w http.ResponseWriter, r *http.Reque w.WriteHeader(http.StatusNotImplemented) } +// Demote a template back to standby so it can be restored or deleted +// (POST /instances/{id}/demote-template) +func (_ Unimplemented) DemoteInstanceTemplate(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + // Fork an instance from stopped, standby, or running (with from_running=true) // (POST /instances/{id}/fork) func (_ Unimplemented) ForkInstance(w http.ResponseWriter, r *http.Request, id string) { @@ -9695,6 +9977,12 @@ func (_ Unimplemented) GetInstanceLogs(w http.ResponseWriter, r *http.Request, i w.WriteHeader(http.StatusNotImplemented) } +// Promote a standby instance into a fork-only template +// (POST /instances/{id}/promote-template) +func (_ Unimplemented) PromoteInstanceToTemplate(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + // Restore instance from standby // (POST /instances/{id}/restore) func (_ Unimplemented) RestoreInstance(w http.ResponseWriter, r *http.Request, id string) { @@ -10575,6 +10863,37 @@ func (siw *ServerInterfaceWrapper) GetAutoStandbyStatus(w http.ResponseWriter, r handler.ServeHTTP(w, r) } +// DemoteInstanceTemplate operation middleware +func (siw *ServerInterfaceWrapper) DemoteInstanceTemplate(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DemoteInstanceTemplate(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // ForkInstance operation middleware func (siw *ServerInterfaceWrapper) ForkInstance(w http.ResponseWriter, r *http.Request) { @@ -10664,6 +10983,37 @@ func (siw *ServerInterfaceWrapper) GetInstanceLogs(w http.ResponseWriter, r *htt handler.ServeHTTP(w, r) } +// PromoteInstanceToTemplate operation middleware +func (siw *ServerInterfaceWrapper) PromoteInstanceToTemplate(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PromoteInstanceToTemplate(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // RestoreInstance operation middleware func (siw *ServerInterfaceWrapper) RestoreInstance(w http.ResponseWriter, r *http.Request) { @@ -11741,12 +12091,18 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/instances/{id}/auto-standby/status", wrapper.GetAutoStandbyStatus) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/instances/{id}/demote-template", wrapper.DemoteInstanceTemplate) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/instances/{id}/fork", wrapper.ForkInstance) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/instances/{id}/logs", wrapper.GetInstanceLogs) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/instances/{id}/promote-template", wrapper.PromoteInstanceToTemplate) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/instances/{id}/restore", wrapper.RestoreInstance) }) @@ -12832,6 +13188,50 @@ func (response GetAutoStandbyStatus500JSONResponse) VisitGetAutoStandbyStatusRes return json.NewEncoder(w).Encode(response) } +type DemoteInstanceTemplateRequestObject struct { + Id string `json:"id"` +} + +type DemoteInstanceTemplateResponseObject interface { + VisitDemoteInstanceTemplateResponse(w http.ResponseWriter) error +} + +type DemoteInstanceTemplate200JSONResponse Instance + +func (response DemoteInstanceTemplate200JSONResponse) VisitDemoteInstanceTemplateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type DemoteInstanceTemplate404JSONResponse Error + +func (response DemoteInstanceTemplate404JSONResponse) VisitDemoteInstanceTemplateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type DemoteInstanceTemplate409JSONResponse Error + +func (response DemoteInstanceTemplate409JSONResponse) VisitDemoteInstanceTemplateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + + return json.NewEncoder(w).Encode(response) +} + +type DemoteInstanceTemplate500JSONResponse Error + +func (response DemoteInstanceTemplate500JSONResponse) VisitDemoteInstanceTemplateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type ForkInstanceRequestObject struct { Id string `json:"id"` Body *ForkInstanceJSONRequestBody @@ -12941,6 +13341,50 @@ func (response GetInstanceLogs500JSONResponse) VisitGetInstanceLogsResponse(w ht return json.NewEncoder(w).Encode(response) } +type PromoteInstanceToTemplateRequestObject struct { + Id string `json:"id"` +} + +type PromoteInstanceToTemplateResponseObject interface { + VisitPromoteInstanceToTemplateResponse(w http.ResponseWriter) error +} + +type PromoteInstanceToTemplate200JSONResponse Instance + +func (response PromoteInstanceToTemplate200JSONResponse) VisitPromoteInstanceToTemplateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type PromoteInstanceToTemplate404JSONResponse Error + +func (response PromoteInstanceToTemplate404JSONResponse) VisitPromoteInstanceToTemplateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type PromoteInstanceToTemplate409JSONResponse Error + +func (response PromoteInstanceToTemplate409JSONResponse) VisitPromoteInstanceToTemplateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + + return json.NewEncoder(w).Encode(response) +} + +type PromoteInstanceToTemplate500JSONResponse Error + +func (response PromoteInstanceToTemplate500JSONResponse) VisitPromoteInstanceToTemplateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type RestoreInstanceRequestObject struct { Id string `json:"id"` } @@ -14100,12 +14544,18 @@ type StrictServerInterface interface { // Get auto-standby diagnostic status // (GET /instances/{id}/auto-standby/status) GetAutoStandbyStatus(ctx context.Context, request GetAutoStandbyStatusRequestObject) (GetAutoStandbyStatusResponseObject, error) + // Demote a template back to standby so it can be restored or deleted + // (POST /instances/{id}/demote-template) + DemoteInstanceTemplate(ctx context.Context, request DemoteInstanceTemplateRequestObject) (DemoteInstanceTemplateResponseObject, error) // Fork an instance from stopped, standby, or running (with from_running=true) // (POST /instances/{id}/fork) ForkInstance(ctx context.Context, request ForkInstanceRequestObject) (ForkInstanceResponseObject, error) // Stream instance logs (SSE) // (GET /instances/{id}/logs) GetInstanceLogs(ctx context.Context, request GetInstanceLogsRequestObject) (GetInstanceLogsResponseObject, error) + // Promote a standby instance into a fork-only template + // (POST /instances/{id}/promote-template) + PromoteInstanceToTemplate(ctx context.Context, request PromoteInstanceToTemplateRequestObject) (PromoteInstanceToTemplateResponseObject, error) // Restore instance from standby // (POST /instances/{id}/restore) RestoreInstance(ctx context.Context, request RestoreInstanceRequestObject) (RestoreInstanceResponseObject, error) @@ -14891,6 +15341,32 @@ func (sh *strictHandler) GetAutoStandbyStatus(w http.ResponseWriter, r *http.Req } } +// DemoteInstanceTemplate operation middleware +func (sh *strictHandler) DemoteInstanceTemplate(w http.ResponseWriter, r *http.Request, id string) { + var request DemoteInstanceTemplateRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.DemoteInstanceTemplate(ctx, request.(DemoteInstanceTemplateRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DemoteInstanceTemplate") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(DemoteInstanceTemplateResponseObject); ok { + if err := validResponse.VisitDemoteInstanceTemplateResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // ForkInstance operation middleware func (sh *strictHandler) ForkInstance(w http.ResponseWriter, r *http.Request, id string) { var request ForkInstanceRequestObject @@ -14951,6 +15427,32 @@ func (sh *strictHandler) GetInstanceLogs(w http.ResponseWriter, r *http.Request, } } +// PromoteInstanceToTemplate operation middleware +func (sh *strictHandler) PromoteInstanceToTemplate(w http.ResponseWriter, r *http.Request, id string) { + var request PromoteInstanceToTemplateRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.PromoteInstanceToTemplate(ctx, request.(PromoteInstanceToTemplateRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "PromoteInstanceToTemplate") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(PromoteInstanceToTemplateResponseObject); ok { + if err := validResponse.VisitPromoteInstanceToTemplateResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // RestoreInstance operation middleware func (sh *strictHandler) RestoreInstance(w http.ResponseWriter, r *http.Request, id string) { var request RestoreInstanceRequestObject @@ -15668,277 +16170,281 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y963IbOZI/+iqIOrMx0gxJkbrZ1kbH/6gl261tq61j2Z6z2/ShwCqQRKsKqAZQlGiH", - "v+4DzCPOk5xAAqgbUWRJtiRr7NiNaZlVhUsikchMZP7yUxDyJOWMMCWDg0+BDGckwfDnoVI4nL3ncZaQ", - "N+TPjEilf04FT4lQlMBLCc+YGqVYzfS/IiJDQVNFOQsOgjOsZuhqRgRBc2gFyRnP4giNCYLvSBR0AnKN", - "kzQmwUGwlTC1FWGFg06gFqn+SSpB2TT43AkEwRFn8cJ0M8FZrIKDCY4l6dS6PdVNIyyR/qQL3+TtjTmP", - "CWbBZ2jxz4wKEgUHv5en8SF/mY//IKHSnR9mip8rzKLx4ozHNFwsT/YVZdk19IZwpniCFQ2RNN+gFD5C", - "YyxJhDhDOFR0ThBlY56xCL09OkMhZ4yEujE5ZHwsiZiTCE0ET5CaETTjUsE7SuDwEik8jklvyIJObT0I", - "00+i9VT6x4yoGRGewVKJbCtowgVSMyoRZfppSHrlBVMiI8uU7QQ0islI0YTwTC0T6hd+hWLOpjAt1y5K", - "MqnQDM8J+kgER39mOKaTBWXTZiKNyYQLgn5ZpCTBDKUxDolEVCHKFHezMTQqeGwv8TEXnTIuyCgiUlGG", - "dfujlAuzI6qjfw1/4BiV3oWhwftIzbByXM64QpeEpNWJ4it8WSXj79vbnWf9fv9DJ6CKJGZb4WuaZElw", - "sL+3t7PXCRLKzL8H+egpU2RKhB6+/QULgRel6UieiZCMQhqJVTMJY0qYQkcnx29uOYFg0O/B/209DTrB", - "4Nl2b7D/FP492A/K01oifHXkn1dvvXOFVSaXZZDZTSPLKKMSkyzP+rcsGROB+ASFmRCEqXiBYEuRqAXT", - "Vabd9y1FyNmETjPhtqBvy1XIOcMSYWaERrcmL4rGWu27UAuxiF+xkSAJpkzTeGkQb9wjpHcosptIDynk", - "TAkex1ooKEWSVEm3izpajDOE0zSmIYieyqbaTfoy6AQsi2P9sDbCYrVJTKcUXmhFGipLi+S+RYojwhQR", - "+Q5vQ5qKWGzquCC3dzUKudheCkrKQv90WZ3miZbwgoRmuvkJUKHImIQ8IUg3XV2B7f72fre/2+3vvx08", - "OejvHvT3/ifoBBMuEqyCgyDCinT1grdZptXy+6igkn4R2ReLo8pDu15NBrdjlxhLle9q2ORULUbYM6a3", - "NCFS4STVG1uPoUTMpm3tGqyvg6P8SgIPvojAjFyrkaWQdz4+/iDXKQn1EcPd9sxPbN1eB9EJwiiXAZpd", - "jWBcOZFnXzQRQbDUA9Z6hz6dfg8yJrNUn4UkGqUxVrpdraQAG4wSKqX+NP8hotJszE7gmHzEuBqJjDHz", - "IiPqiovL8pu2lRFNg04ww3I0n6ZZ0Fl1DlSZGrogMU4ltGdXXIyIEFwERtdcjCZcuEXSh1hBwhVNLVFI", - "5meWh0JBJ6gQIJePbi5u3PmqegcHvQAvCaOmG70aJrM88HJby8PNh7ZaUhqxbLRSt8zIfiyrEiCieMq4", - "VDSUreQmnMZ6eRMeeUTncd4cohFhik4oEVZRJUhkDI411wjSjSDKUCZr+yDXpUdkro2f0Xx3pMJ0mSg1", - "S6G8eKXDvjhiSsdcvvz5TlnDpNW5ey2ROaawJ4/JnJqjpaoM2aUZRYLOifCI7/xENaLQvIc29F7XIoRx", - "RjYrlGJzGlHcRhxEMKYR9XDP2dEJMo/RyTHamJHraifbT8ZPg+YmGU48vPBLlmDW1RtCD8u1D++W2361", - "69X5eZJko6ngWbrc8snr09N3CB4iBipjucWn2z7VLw3pCEeRIFL65+8elsfW7/f7B3j7oN/v9X2jnBMW", - "cdFIUvPYT9JBPyIrmmxFUtv+Ekl/e39yfHKIjrhIuQAjaO3GKZOnPK8y21RXxcf/P2c0jpa5fqx/JmKU", - "HyI+gp04Nerk2OkJ9jv0/hRtaBkSkXE2nVI23WzD7yHX5NBHne8Qh6Ei+442E5XTUm593oaC4DXd6Tda", - "dba81TKzkqNENrXuXtESNaFxTCUJOYtkuQ/K1P5u82RKG8acUEtdPdc/o4RIiacEbYBLBcwPI0y1YjPB", - "NCbRZjtltmkyf/Bx6QipsDewRRePw8H2jld2JHhKRhGdWp9Y/YjSv2sW0+0oBG/7JwKHebt5QJeCTJb7", - "ewGiGzoRZEIE0Tz+hd2lgs8Jw9Z6+Qv0G/xfW4WzcMt6CreAmGfF6587wZ8Zycgo5ZKaES5JLvtEsxGQ", - "GsEX/jHDo1VrXeIoqbBYvT/gja+wEwu9bi1trNtCqzZ4uvaTt/qduuwE0ZjrEiUp0Cgin2ulxqMdcKbs", - "g5r7kk9RTJmxOLRqZ9YC9KpFSn6KOYjEr0SHnPzLm1+P+xbCy/zQ0Jp+1skV8JhPy9ScESzUmFSI2XCE", - "2YaK0TWS/6yyfWpnFZZktFqCnFHGSAT+YruxzZtajfWaGbCLLqkazYmQ3j0Hw/qVKmTfaGwq5uHlhMZk", - "NMNyZh1sUUSNs/CsMhOPtlZxxGOwx12DoEWA/Xr+y+H23j6yHXhoaD2X+oXlmZS+1s2bd5HCYozj2Msb", - "zex28zN6mUP8HFA4K5vOnpwDHWMaSRfY1bR2ciZn5i+Q3XpUcPZpMaDZK9Z/f/BM+giEhLESGm9v/Dpg", - "7hmexlzTdIEyRv/MKgp2D51MwEGsDwoakaiDMDwAv4O2/6aEEaHlVOEZKinBaIP0pr0OGmq9sKu14C7e", - "7vb73f4wqKqx8W7XmPcpVooIPcD/73fc/XjY/Z9+99mH4s9Rr/vh73/xMUBbzdxphXaeG27vd5AbbFld", - "rw90nSp/a+lfHr5P4pilPtFy4qYrfXSyrDiYuUY8vCSiR/lWTMcCi8UWm1J2fRBjRaSqznz1u1+VFjCP", - "FURgU02mG5KhZvQAG2/E/IqIUEvgmGjGkx0thKmSHYS13QzCC+lT8j9RiJneC0a54AIRFqErqmYIw3tV", - "aiWLLk5pl5qhBp0gwdevCJuqWXCwv7PE55rJN+wf3Q9/cz9t/h8vq4ssJh4mf8MzRdkUwePytZ4bQ35F", - "s2pFHHWzGNS8hLIT89lg+Q7qy1bYTWTVShtjrnGptRDKXWRrBrJ8v6uNrcRjOryeEyFo5I7lo9NjtBHT", - "S2L3CxIZQ8Os398J4QX4k9hfQp4kmEXmt80eep1QpY/DrDjlzZVt7XaNhDMOikoc85tcp4GmCAYOjlee", - "46tI46X2Ud7u8qn/C5eqm2CGpwTMUfsiGgt+SfRAzZ0AJRJdkoXWchZoqhvtzqmEGx7C5miOjdehN2Rv", - "Z1wS84p7JMG3T+cEJTy8NFe/Mw6W/BzHGZEddDXTKgf4BAmO7c/IXIwN2UwPUoY8JZE2QsxrMDV0Qdj8", - "AiU4hW2OBYE9jhKsiKA4ph/NFT7cMpCI6hNuyAhsDJRivefDkIsIbtg4IjiclajwV4kujMJyAc1fUKbZ", - "+sJszNpl9afg9bu3P79+99vx6PXZ898OT0a/Pv9v/bP5KDj4/VNgQjVyTeVnggUR6C+fYL6fjXobEREc", - "BIeZmnFBPxpvzedOoGkgNX/hlPZ4ShimvZAnQSf4W/mfHz5/cAqZcWPP9TbwDOyzVxkyZ6lHJB07b6BE", - "1sPk7jY0ybSIenn2bkufzimWUs0Ez6az6sawqsGNtkRE5eWI8tE49Y2Jykt0svUaacUFxVRv0FxRGfT7", - "pz9vyWGg/7Hn/rHZQ8dm18LwtQziwupPcqbZJ4/6ODp7h3Ac89D6UCZNF7yuK5+AJ0yJRcqpz4irCafi", - "1WUZ1e0WT28girbGlG1JvQzd8GZ0B765tSnxnM2p4CzR5twcC6rPaVndK7+9Pn4+ev7b++BAHwRRFlqv", - "5NnrN2+Dg2Cn3+8HPgbVHLRGBr48e2duPc22UWmcTUeSfvSoEof5/FBCEi6MCW2/QRuzqqZh9i2CxRkG", - "Oy9/Nsw1eAl85RbF3hHlrZiGa9d6L3/2cctskRIxp9LnZ/slf+ZWfjncp8Lb5pYsZ1rg4l7JfgljnkXd", - "UpedYEIFCSG8Qv/rT5JoRX7+sXot5fnO7/5qpcCu0UxxnFJGVqim34iKeMXFZcxx1B18ZQ3RXqh6QmPM", - "g+r65jdrjiWWIs7GmEVXNFKzUcSvmB6yR67aJyh/OReu13omOP7X//7z/WlhZw1ejlMraQfbe18oaWuy", - "VTft9aHkE8lS/zTepf5JvD/91//+083kYSdhFJFbKXV2/Z+bFupBMzaW0LhDG26G89M7D1hR3BrU8Dly", - "vLf2GtgnqPmciBgvSoLXjikY9EH61UYlKERJIvudFqOXSH+8Rgzr1twh/7Ju5G/3/YLWMyjPmH7WssKe", - "C21Gkg9ksH1q/9xeHlLDiC5pOgKteYSnuc93VUjo+SVNrSoOX5hljGMjCKIMlPcx56o3ZCZCRa8dLDC5", - "JiHIPKmwQodnJxJd0TgGDxEIleWjRSv2pdAmeF0q/b8iYx00zpTW1rkiyNpN0EkGY4GXxwRlDLv78Jru", - "bCe4HF4AZLkkgpF4ZHRj2ZIy5iNkP2okDkx1gqUNURMqS6v0Ov719BxtHC8YTmiIfjWtnvIoiwk6N9EF", - "m1XqdYYsFRCmoDvR+5nafvkE8Ux1+aSrBCFuiAk0lvvY7GXt/OXZO3vdLzd7Q/aGaMISFtlAX3fi2CDQ", - "iLO/6h1Lomqz5f5rRG8K6ZAMp3LG1SjNg6dXSadz+3phird3JnSCeZhm1SXd7jQGgc6pUBmOtaytqJPe", - "C34TxO4xG0yMfNl8sXKvCJpV1ZvZth4X0zJEtHvDZT2OE6MptXaclEz5JReKszM/tRvsmvZPmBvISsdR", - "YWp+QV/nppGl4B3zc8fN7BZUOslpUnM3fR3yHMqSad4q+NzEYBmNUKKNC23NWz7W9vtFB138rfKD3vvO", - "tND6xRUy1AB5wvRP5fbrTom17oIbhXuXFwfL26/HoWyMdELzAVICM2li1GY4JT30CwhxpEiSaknGpohK", - "lId2Icav/hNxo9S4T4dMD02aOBFLjtxpJOmUUTbd1Gq+PphwFBnP0iRTmdDvzaksqFllHee9WYpqNaMj", - "Rh5DhgRlYZxFBF04D89FVS9c9v8sm4TWIbRk4RiSgGUDxp7aSjKlu9cTTrAKZ5pOPFMmcMxOvRrUV/My", - "rbtQtWPJr9pusf7nubioJ8LMPSaOnpy95AG3YMk/2eQGtIqK30V5SRaw5M4diZcckmVPpN9fKIjk8ZzY", - "Y7fsyxxDqg83ilPhxjQOSeuD1Nu/nuTi886tWwpNr9bkr5oKnhQfqbpusgXHWO3fxYQ7KaQnZ/rraMNY", - "EiA+mB4HCNSxi46xlQh4IBDTzBKjiAoSqqXmKZsOGcSQXNhfera1C73JtY7yVRKnIA8BlPby0qLSyjq1", - "D5rRU+MJVYpEnapucElIKtdPSqvX1nHt8a4LciWoE2QuqLilekbYhIuQJNZI+DLD8XmpMa8Zd7MmlkM6", - "DH1LY3b5GZCdQiITP2TWA9yslbSNevZiVLPaTAhBtcsLHMcXaMO+tIkE+QMi8e1aMc4KZn97dOZYIL/2", - "fn/a0RyppcDFTKl0pP9HjvQuvqg3Zr91O7zILHvaB/tqd3fHrqp1upkB15qt+te8YRHNS+PU78abNc0X", - "epQ2zqSNKn9UfFJ4Ui8pi9o28Kt+t9E7lytGztK4awddKkg3S6cCQ4jt13TP3freFKjZLMHX5PH6wiSL", - "DMFMKp6U4+03aiEetBoMUiXWnMfdCCsMrsyW/lYz3OXA42RhmjK2WJMnZjQde+KG6EdIBZjSKR4vVPX+", - "YODN5vvSS2w3Ft+yNAXwGwuSRCPFV4cw0wly77aJWDT5BoqP5hPKV6d32PiXSv6dOY6sXaub6KYhte4E", - "0HHCmYkwNUQApfH9afnurjdkXTh+D9Bx3kHebN4kBt0SR+bmZIOL0iBMIgcaLzYRRu9Pe+htPtq/SqQN", - "ljlxGQ0zLNGYEIYycD3Dadg1Z3F5AJmEQ1PVP7e+E5P8sAlXlNw+6+U5x+ClyTOoIVRqTGvzMZmTsFD2", - "ThizsheslddqVeD3GzKlUola2DfaePPiaGdn51ndf7m91+0PuoO9t4P+QV////+0jxD/+vkdvrYOq7LF", - "Bp+Vpc/Ru5PjbessrfajPu7iZ0+vr7F6tk+v5LOPyVhM/9jB95IB4hdlx0XUHNrIJBFdJyY1V/li5Uoh", - "aQ2xcLcOcbujiLUiAHfVu4YSb/Wbd5Ha4guatiG7N08+qQvMtWHXpcktW/KLFOzOYpeUNDgb3RhSbxzn", - "MZWXPwuCLyFlb/ncTvCUyJE5z/zxDJk0QTbk2no3BOdqIs29adXrOdh9svt0Z3/3ab/vyehYZnge0lGo", - "T6BWA3h9dIJivCACwTdoAy68IjSO+bjK6Hs7+0+f9J8NttuOw1zxtKNDbni5r9CGpcjfHU6Je1IZ1Pb2", - "k/2dnZ3+/v72bqtRWX9xq0E533JFJXmy82R38HR7txUVfAr9c5dhU1fgfZmVhya7X/+rK1MS0gkNEeTo", - "IP0B2kjgCCP5bVV1T45x5PJP/WeHwjSWKyMmTGf2TeNoS7JY0TQm5hksSCtfNMz8GFryImQwluf73qwl", - "m5e0NkLAzSV/BVXyyyqkOzUJzSXliZI4OjA7dK2cg9UsBvahiQ/sHFpywyttOnVjMidxmQnM0WUyawVB", - "OZ+YRavMirI5jmk0oizNvCzRSMoXmQBd1DSK8Jhnylwz2gTtohOIegbbY6LFdTs79wUXl2vjR/VJnOeh", - "r/UKHYIjfWJdNXCKY2S/dikKJaUvvw40l6b2uURvzBfGQ1T8nGZVVJsO9GQ9SQwJIhUHSWodhraZttql", - "X28BZ6kL/zD9FbLznmJfuhMTLvB1LWwxJYC/oNZqLJpT3sL75/B663B0/eFaR0oLujNydR9Eh3j9rmbb", - "rmQ4vRuKrwpGy30NxUtwCgsakR6C3QVRMS4/sLbTzhVPUxLl/p/ekNl47vwnaW5Q9IeGDmpGqEBc0Cmt", - "dlx1sN1lVNtNWNFx063ZsfzhsoYKDyF8o3nT44kyWAuXLmWKlPOX7CIEneA8R6awkqhKmjc5uscSRYpQ", - "y6Uhvjx7d9PYtFTwCfXhDUEshH1qLTMXtfVqt3/eHfw/JgJT8xuoaJSZ+ImERzUgCft+u5Pn5dm7s6Yx", - "5dAOqDy6pTnlES+rwK0cReylkr2VtBaMY399sOSdFLr3M58uOxE4IeNsMiFilHicay/0c2ReMKFNlKHT", - "n6v6rNab21rNZ5XFAbN5gkObmd+O+h6HXG0anRI1P/iX6w0xx3BTPp9eKmHfsSl9PfRbDqaBXp69k6iI", - "UvJ46qrL2xgvfzZbSBri2LRo0nMpKzvYgDlba8hnxYfWFenRk/0YLG4joI35NM1gG56/6Z68fr+VRGTe", - "qYwJIotmPCZ63JslaTF3WX1FcH9FSMybPB2GMWTbDVSiVb6DWxOptF891FFc4XgkY+4L1nirHyJ4iDbe", - "vzBZV3oEHZRWllL/XqJChb/3vTtGS6Smbs+hw7rLtLLBvbZjFQ3TuFdK06t06tsqvxAcGxDQKj8vAyDx", - "y+pC88v1oDumEV+/Jy4wvGbU+JK3jk6PjcIQcqYwZUSghChsIUdLIS6gDgWdoKvPqAiTBELtJv+5Orql", - "wQVfzsZqdOIeLeF23IkDtyHf/I0JQYhQghmdEKlsvnmlZznD23v7BwYVIyKT3b39Xq930xyV50VSSqul", - "2DIh/KV0lZ6cfdk63EEqSpu5fArODt/+EhwEW5kUWzEPcbwlx5QdlP6d/7N4AH+Yf44p86awtAJSoZMl", - "AJXqlaY+s8zvByXMS4fv1wrXzm/PQGQD5M15840Vnmr7xHDclyYW3xp6pMC/UiXIkXJAaAv4EfpxtSfU", - "KUbwju0zY4rGBTLLsg/0Vtg6ciX8wBL0QEpYDjgQx+avkLO53hU+9IGKAHfPvuj+wEa5jCLq4eR/WGvP", - "BElAVtX6/RZs4TRdz7Z+RTGXf21RV2xutOckenCpf5s7tmrvr6f/9ef/K8+e/DH489X79/89f/lfx7/R", - "/34fn73+ogyq1WnxD5rb/tXS2eFiqZLT3paVTrEKPQrVjEvVQGH7BClu4jV76AgMv4Mh66JXVBGB4wM0", - "DGohwsMAbZBrHCrzFeIM6aZspsOm/vjMuH/0x5+cbfm53kZkUxqEXZA8k0lm44gnmLLNIRsy2xZyE5Fw", - "p6//ilCIU5UJoldP67DxAo0FwHpb87zovIM+4TT9vDlkYOGSayX0DFIsVI7j4XoAprCjMjED9nUSucRw", - "YyEPWX4u5XnhxkfTy50g4JuvR1z6ieI1X7iopuI87fsy6CHqSy9kTKUiEJidc7ZmozwcDT3tV0TF0/7T", - "/loFP+ehFewHO2EZ798xZYu9ZBgYujaCGyLUWvjStWwyewT98vbtmSaD/u85cg0VtMiX2Bh5JgZQGh+h", - "imUp+m8z8KKNwuq2nJBxksFncYusoecmPPTtq3OkiEhcwP5GqMk5oaGeH1z/UykzzYoUo8Oj0+ebvRYF", - "C4C2+fhXrOPbfIb15A7rNGvyBeYcr+nbQSfHEJ5rd2ihwEFYzQsuUGwETLGvD9A7SaqxrrBU5lbfrGS8", - "KDxv5gQYBpuuxbQuKQ7Qm1xvxPlQKkUSqs68Yl9Cs/bixcT8LLXeWYIfF84usqINInywyoPE9YnbLApW", - "b38PxWHP27jukk/zZnu77AzVnflZo1j7r42a8vXVnZ2bqTsOATedYenj7hlBMZ2QcBHGBMFLK2DnaS3x", - "VjS7riv9rkV+z7sDMHt99JU/92F273UHg7eD3Zvb/DcFvKjmpJbymXPMi/ZgFXcB+rBs/15TNWoMakD6", - "sQ1hcFbe+1M0w5L9VcHDmq032HnSCr5U99o2HKAcCMAnZki5lHIJrvk1tkn1vaRxbKJDJJ0yHKNnaOP8", - "5OWvJ69ebaIuev36tL4Uq77wrU8L7AsnKl6evYPsISxH7katOYgUF4HY5JpKJZfzf1tdTK/G2vilgofh", - "Taje/IogGe42f2ka9wF/8ZBhkt8e9MZKsIwvRbywxsMdAV40ClcfWERVzpqfvy50xZ0MZ221lfJZ62LY", - "b40V0QmoJ373UGoRSCJ0clZgThZOPtd8bU62dNGg3+8N+m1cngkOV/R9enjUvvP+tjnaD/D4IIwOyOQL", - "XK6WsY0yjOMrvJBo6MyVYWDso5JhVNq21qRpdZ29DMlxOwSOukLhl9NGsXIY5NLilDcpFy0CNOv5wkkW", - "mxyAMrb5crkmmRpwDAPuluuSQwYD7Ngk3bzEEg5DkRX+BFc7w2ieWWr5fsgEkSln0lTU6aFfyUKihIIP", - "P+8e4jMkyuOAoiHbEC5mLA8OS3EmSaR/gICMjrv410OjCkDs9AdDJmcZlALZ7KEjzmSWEGFdLWhMwQ+8", - "iWRmjCsYL1ADClNJGhExZPo1D/zGp1xRPtjv9/v9vERJcLCj/933cZOfi9ZhprQDQ/lSBIpVyOLnVUzx", - "1jbNFxTeaRUx5FQ3Gytkvxrd5LaIoJBncaQV5bEWzcaPQSLrbpFEFXDtIM3fsUumGaoydXNpoBn9z4yI", - "BXp/elq5YhJkYtGoW0wcWLphHXh6o2XYXmNarh3NLXFJ7gOLpH6sltSZr448UvZxu5hlw6EtfN2FeeGN", - "M6HMLI3mkxVzqnkpIzIfZZlPa9aPXKbSu3cnxxXmwHh/8LT/9Fn36Xiw392N+oMuHuzsd7f3cH+yEz7Z", - "aagH0T7O7PahY9Xd3JwZCIQHj79J/IwO9H7LY7/GmUJ5XKjeyEfa/EAlO8fkwYET7qR0XOhmwCdmzSCT", - "0GxgWfWxAggagN9EmZ4yOB91Izba7wC9hHfhEU7MSWoHoY3fqt8NRwtz76AFg+vanGurh3xujzT4Rp9v", - "CKqh6WlrMlhzdHUTRsYcoN84fJMfsIzX7VrzOpxsy6/XbeANG4bnwrWhMyswD9CLXEjmYtaK1Q1J7J9G", - "dttMAsiS2KzEqtoVDzS3FCtXCsPsBIaiQSdwhIJwzeXATTsub05SmRV9F3IExyBCi8C4TNHYgoLATCjU", - "A4OJYFjcpp1sAfBINDIqQNP1uom2smpC/pETFO9P0Qak//4dWaeB/tdmfhVf3pW72892n+0/2X623yrJ", - "pxjgegF/BLGAy4NbK+3DNBu5UjsNUz86e2fUzdAocuAFsnMvxVSngofaGqEMFbV7is6f9Z6Vc5sinpk6", - "ZnZINhHyc6la38pCSw33yX/SeE4nE/bnx/By+w9Bk8H1vtwee433oiyg19I5Kd8sLLkFyLhrQEv96SfA", - "UEI2Zmi9IRJmgM6JQsA/XYRDOKTzED7Lci6Py1Lcy1i7Ozs7T5/sbbfiKzu60sYZgd2zPMpTO4LSFoM3", - "0cab83O0VWI406aLawY8FWYVOP8+Qxa/vF8tfNsb9Hd8XNKgLxVcY9ueJ40kf2+VIDspS3SIRMwVpKVd", - "7qX2zk7/ye7e071229iVmRTXqyWMw7Ax5LGwP+WV34DbqLeHZ0i3LiY4rFoog+2d3b39J09vNCp1o1EB", - "ZJWBmrnBwJ4+2d/b3dketEs19N042STayoatyi7PpvMwhWc1PKRYFr2dptPCp3gaBntDwhjT5DB00WK1", - "08dAyoyEea1YhDYHg3UCLR1cLb5tZaLV6mQZ1YALVCow2lvv7r6d97pZTJvzYL0YX9ahY8w0uWxOjEEu", - "vQXtUkHmlGfyKzTEFQk1M01izsWNvm0Kv3tDZBYr42KmEr0//SsIEc1cSCqSVu/9LPutyBy65eRutIEr", - "POHn6iZitVqNNku/asKdhm3aWRU2Xtn+jQl6kRZVGVsf6nGE4zADrD6cr6eeFaTa8ExBYMrCBEXFMecM", - "hTPMpgRqHxhkUDZFGM14HPUC/1VYHI0m3isqfoVibqBF8nL9bhD6M1sHGm285EUFR8NKNTjqvcRIFQtU", - "Vi/bHawsAd2Q0KfpiRUvoV6YTyrWfMynEqxABeFevTrYUoqFieLCzMAyzhNjPFYzFbf1ae8ZYk16+45Q", - "c3TyibVorY6heE5JHAouZVGH/v1pdZir4nUTymii5ez68I3qYFuwrnEae84uW1W4LXqr70D0BEJ+yZEI", - "PAzxzqtKnlv/eIJZBsB2JUYm1ykVhj3aBT/MuFSjPPvqhoOVagSgZZkgRYqmOy9nkO+yMCIO3vGei060", - "3YZceTXvW3y9xFX+ppoG2CxTvRT1U6uT86CPjZfzz1amvBU5dPWEqZtkSBYoV1RCq7SUnIc2GFcVsVRC", - "atpscxHpt1F1P00VlF/t9s/bJi+uzlU8w2p2wibcA4V6A4e/zQBxsSkpEVBtnzMUEUZJ5IzH3PNvfVuQ", - "UxJLgqKMWMoZhVRgS3BstjdAnjLnFKNsWpP19Q7buOHNGFZjmkG/9sU2V8rSn4nwVmRAKxMEIhEuchJa", - "RbRQOfJ7ipcbFmSaxVigeoLuiiHLRRJTdtmmdblIxjymIdIf1K9zJjyO+dVIP5I/wVw2W81OfzAqQmpr", - "1zNmcDag2ixIrd9iCj/pWW7W0jnA9bJlvt/S37e6offGhb2gMbE5rO8YvS4xehX0Z3e735Tp09BoJcdn", - "Of/5ppLbsqxvx7vU5MO8SIgn+8REeNVuJaqOyMp8vffkVF6uzGtadsWgDXfp70CVqnQtgRu18oS0i2Ks", - "h7e40WxJElZ7332692S/JbrUF/k6VxQR/wLP5jxZ4dFsWKnTNm6zp3tPnz3b2d17tn0jB5WLhGpYn6Zo", - "qPL61GoB1ZxmexA10L/RoEwslH9IDfFQ1QFV6vrcekCfV2zdAlWg4dqjEdY+Lq+ku2epekDb+RhXaEuH", - "FZWrVLpug0wmBIzKkaFbtxhMLRul1RhCnOKQqoXHYYKvTIWD/JVadnwbb1p1sB6S2rYtwImWXDIbFwG7", - "G65z9DfjWq/xwtPWIHUyGze58V/XezVO/MIHVL4ianFDU9TRWHYX5PO5wrIS1aH/DiFSqChNWI8PM2+0", - "L8LueD2vw15EvvoQHvw118vLX1vOktu3oiTXKb7qCG3egjeyoT0nsq8q6/qo65p8sAfg7b4ajcvwkSvx", - "OStYk8Wpe/N+2xVVXP7OnGA3768U4nuTD+tIesCPdgyW5EXbnQpLNHCT4mI9gPod4GGZmIJbIWLZcIR7", - "AcWyP98JENbScpwT5d491xZ9Fq/AP2eKiDn2OKZcE8i9UvWjGkncQdbFhwbJZi15Z3fm19VsxnrL8DGt", - "BY5SQSb0egW3mBfMcV1ND5CWAlEVI1+ijQRfo90nKJxhIWtjZ3Q6U/Gi6mTd9WTHfFnNcqK06ty+mkCx", - "mu7D5RsNu5zl1n1b9ryUy+KvckCi0Spch6P8NeczTvECdMtGQ/DJzm6/v7PdvxWww9cqvlBqpykitPSd", - "deZUrh7LLeTxn8sInVeCmhp+jkxSCYKTA4imSnFIUEwmkPaYIyOvtemXul49eHtJahM7cv53C+UK9Vo/", - "ixVxjDNQPFw7FhPDTSNwt7TVXJ7y8+Vhr8iNzMVMuJQk6ckF3On2998Odg729g8Gg7tAgsiJ1BTC8+Tj", - "4OpJvI0nu/HTxZM/B7Mn0+1kx5sOcgd1PmplM2tlP+wcUiLq0Kt1yGJJYspIV+Zhb+sDkFfIAnOTtHb/", - "38z7YGawUlk4r06yrDNgVRCnXoHwPhLX7OhXulDqwz85Xj3sW8WR1QfiZ7D6UICf2g0GkIkGXwqDk7GW", - "58670outT56VsY3rzh5f0Ddsbe8qN1Dcx88VwVjZYatO7OVTzWPCTbmgapasPh7y13JQDbgM/yhVVE2U", - "6qGTKQOg5fLP+d1HuRa6/jjoBPHH3eqesb+3T5mzIBI5A9qlLqsBLe4GAMd7NRXglcK0ECY8AQsChPhp", - "0B08gxv6+OPuT/3usx76RylSoGOoVSbfwL1d+bXfhoa5oAS909ycD57d6Brd0XMVB/1qz6Wmg9jCS1ge", - "L1Bu3VnhwqYrC1w8XlrjWlbRndXVsqfZqKwlRSTGC19tkhgv0JhMAMy9Zh+WmQyNyZQy2UHk2gkdLBFG", - "ZVPIVq3e6cthgLhAw2AvGQY9dGhBWcBaLUDMK80DfHWJT2hiS6pZAOvmsJTtpF3+RN14uBlSl/vKo571", - "/PrZs7f9wY2Rum52TPa+IKr6i8zddiZujKVqsi1eYalymxSJzFoYHahqxRY9XyUBmw4B4a2Q8nngMhwL", - "lrUyQBaKn/OEdNCUK1QkQqzV9GD4ImNefqiOv6hMDrmbjQyxvYYh2o0pT2akq8TXyTFKBY+ysIgCjmHQ", - "WRgSKScZFFrvtdXq19+z3qVDA8LrJ1yg9Q6NJg/G+lRqct283r+Ra1XqUjNs81IP+uuX+k68IJ0gS6P1", - "Msy81E6C3Qh8Z01cqccnUyV7TRMsTeZDC4n+pkzBZSMXarShUKtEWeqKhmqeWuYk6SkViq9HXhyMYxIT", - "fUwtN4JMFV8b+kJlIUXXi9TB/lO/2xBfj0JIylwayK+EpNpWSbi0VXUTzBbegdWB89FG3xWNlQia7xrw", - "Pkut6uCerNXEGpeqfQ2CmlfbZL2VSz7kKDRftwCB/XJteZi78MM9pJL22l4u1PAOHBpHjt/j+jdhvaZm", - "fFw7r3f3ZDud7K21jJswHOqhvWU382H3f4xbGY16B1s//f3/7n7421/8lacqdrMkohuRCdx3XpJF15TU", - "1jZ6r4pgAOBVWpm29ZoUwQk4jcJLYpxUCb4uj3evnwuNxW84WZoCXBQnlOX/Xjuhv/+l+Zq1RMZ3ICfX", - "suwXY7vdBXC24u442kiImLrqFC46cLM3ZBBweEkWEpXQOa1K4xj1rzL/pFwW+8KogT0o5j6mAHIsh0xb", - "tTgMSaqtCYtSSGEsgoP0qReHtyihLppfyyUM0Mg2u2oJ/sJXgv0gYOSqa3qIupr3dvf2gY8oK1NysLTE", - "vkU3CfpN9WM1lT1eo1dUQvaJC7IuvYw2SJKqhcPAdmGwmzcDDDjMG/TefH9lcMD+s6+BhfxuJfjxd1i9", - "uIzn4Aa0Fslhaf0bEUf9cXTHdeAtsydtRcYqUFTNOpSq2xxml2h1ZgTRosshcfqZiUS16DzTrF72YCth", - "astii/uyXyIouL4y9rjYZQ7coAsfrQ+pXalQl2ZWGknz2pw6vbFeBL+ZQGeaNFczIkhpIeCDAtDohiSz", - "caEtcqoMAnBKRLdestNUlREUAk1zk9+RII8dXvaDrgasOsXXeQ/gQ8dy6aYJ5lFANw5e/gyVot640o10", - "4pqAYdS0ej86UZWLVtHEcdXyYpS5anne5n3vxrOyaoX0a9pbNeYs+qiwpo8f/4GpesEF2AHNGUx3DnIE", - "NkZEBKRw1yGMWuH/0IREI56p1fvfltCw6UuRU+ULdHFn82BgYlvRfY0scDk2xRg++PQGScJMULXQRrJV", - "SccECyIOM7PhgZDQEfxcdAxg3Z8/g7dw4glYfEkYETREh2cnsB8TzEBVRu9PSwC7Bmt5CW4FlLzXRyfW", - "znQAjmA3UAWs54qsH56dQM1mYWytoN/b7vVhM6eE4ZQGB8FObwAVrDXDwRS3oLYH/GlzEXJ75SSyetDP", - "5hX9lcAJUUTI4OB3T0y/IsLUCpGgdeJpyW5IMRXWcEhjyDQwrEL1t4B25Y7SA3MedwzBW7vJpFrYuEuS", - "vrbL+kFzgtk1MMXtft+YpEzZgxcXtXy3/rD5mUW/rfQ5II8H+mlJr3c6pSX5506w2x/caDxry+/6un3H", - "cKZmXNCPBIa5d0Mi3KrTE2aCwZGBFbHhLuV9BixU3mG/f9DrJbMkwWLhyFXQKuWySRkm2nZn5MoWqvmD", - "j3vIXgIAmrGc8SzW0gSZSHdn7issetOPCItwRudkyOw5bUopYwHGfIL0+WzMlurWMF2b1c9zEH/m0aJG", - "3by5Ld1c17l+CwLXUVMlGQE42KipClXh9KWMQTVbSSzEbF6OZTmmBsqPy5B7664ThpkqqlmbuuOXZGH9", - "yt4GW6H4aIEHy0IEgGs4P8n2pj99BcBw/Zlfx/kzZMlbVScYXMaEcRYVOpeLqMZijOPYC/MwjfkYx7Y8", - "+yXxqKgv4Q1LlDJusFNuGI+IwYBNF2rGmfk7G2dMZebvseBXkgitAllsfUtrW5vYsu4VwIIlgG9vKvfo", - "PrfMELc+XZLF596QHUaJq8okzSc4ltzWrTdoSVQiF4JreNePVtwQ3XGUScUTy1KsXGbXDJNnKs2UvdmW", - "RNmCAPA6VGGWMxINmeLokyBTKpVYfN76VPT4GWwXgiPNJ6VXzJS2PtHoc9Oo5Qjr2Y/gVY/1R4AAw0Cf", - "LsNA/z0VWNsumZyBK0OC+2JaXtKNPPVe64WbdQqHmKGUpwa2AJjKlOOvtAHFVXAcIwVbyX2rtU1YyYb5", - "2EwkX6VQm4Zk8kZq2whqhpY2U3/3qX8/SRIK4nNw/Nf5698QHFV6DcxrhdvIXC0zfYqiKANNHnrvDdlz", - "HM6Q0ZsAmm4Y0GgY5NZFtAljzaSNk+52QcX9SQ/tJ9NNh0Y/9Xq6KaM9H6DfP5lWDvReSpOR4peEDYPP", - "HVR6MKVqlo3zZx/8BG3K5jivCAK0YWT/piuNBagSxTFozg3MIsStrI0XCKNCApX9KGPKsFhZ18tDektB", - "bcrjqSwT49MQPKjD4GDofKjDoDMMCJvDb9bROgw++ylglehmHDRT2szp2jkT7ff7m+vTLC19PSp05UW9", - "/T4vaV/bX03xsErXsuJhJudAHPUKmiJ1Rt26B83nZxy5sic/VLw1Kp71XJSUN/i+fA4Y9o2JMXBrGpi2", - "Z2Onga20TgxbAIopWBwuKdoYHNRpcAXzls2Pujm/bFbsNu2yEIYYO/7bvQf+g37zChmm32f31S+OAZI0", - "L3v+uNgRFssxYsdvEb8k6lvguP59iVKLn/qQ/PtY+OclsXpfQbSaNNsic3ff5Id+gJQPaVsxL2tb9RzG", - "1D0nTKHn8GvP/tdZPABkfBHz6cUBMiSM+RTFlNnbuNJtkT4ULS3hI5P1kX9nk0Ac7taGOT//9b//hEFR", - "Nv3X//5Ta9PmL9juWwYMBXB6L2YECzUmWF0coF8JSbs4pnPiJgNImmROxALt9EHNTAU88pTSlUM2ZG+I", - "ygQr3VoaCCxpGwTTg8F8KMuItFkz+kU6sfgcxsHsMeHdXjakvNcd3VkOMTYzKE1An4qOByDh2lYlsPZX", - "4PeemTlX/Gd1X/mSx3S9fFHkWhnu7ZoB3lDAAIl9+w4e2EmjjfPz55s9BDaG4QrAYAGNuWjGKs+9HzJp", - "vUwyEqUqUIDKRjaZVLLV/t9j+047B7Bt8XvyAFtYrhu4gI3LA8rZuRX4YSu0cAf76eZcwz7/7LHLlWx2", - "0N5+vuUuXDRRK0P4662z471lmpsnJZI9hAmMNlxMuqtoenZ04ko9bT4Y09/LqaFnagto5EcH4qaO6r2Z", - "ZUecTWIaKtR1Y4EaDgnJTbUqgzwWcfDGjhphN6862mH5fNuqgPc0nnQ5jk9x5N396VHr9CbHSIHIWPDa", - "j5NkHescUxly/W2JW7ohTm39UaO+5Pu0zEXrHFImxD0/claqS1Y8nxy7DXl/rinbdcbqZ8M9CMXjmkB8", - "QEFYq6lYwjB9TNz8Ll9FhwuxwnP1bbFm//60oPv2YvnY/DG5saIa2bQUNEjOjQfoS6J+MW/c4ULbHjwT", - "PyfC7WoHOQ2zzqdlPkXhjISXZkJwIb3a9j0xr7QzfU1735PlC+S5icZiSf5DRWlh7Ba0WmXgntg6gndn", - "30IPNzJvv949r2UwD5Eh2GTsPNamRB+WCxZufldXvfdymhliP8rD7CyLY3fjMSdCobwce/kM2PoEYUnr", - "dXu321YeB+/evOoSFnKIQ8tjqPxKlH3ylTV8s2BmKj/YpI1NaJKTqTvPmjScL1h/Wy86L/f/H9svbMH/", - "/9h+YUr+/8fOoSn6v3lnzNK/L9F83xr3I2Y+rXDTKtFANDEoDbtOQ83faqmkuve/Kz3VTPpGmmpO1x/K", - "ahtltUyulfqqXYo71VhNHw90JZMzm4/a8MjFJ35nmur9evksRzq4Ziqr1x62Hg8X4OeFR5ShTJJHGEBJ", - "c44rHxst3dXFhlx5fDjWPTnuACE7mnSAnWQTRO7Jee3Gce/Kre33/j3Xh8mYTjOeyXLuSYJVOCPSJivF", - "pCqAH5vaXRzPjYr3N8yl/fs8Ou5dr/7B93ek8dcX1AhvcwO1Tud3b7XV+e37Wuc3KdQ2d80CPHUc+N9m", - "Q1ChS6Juy8aVXPPlYEffuHy2CHqnDZXCXEBgQRwM2f/R9sfviuDkw08uSSbr97f34XfC5h9+cnky7NSx", - "CmFKUIvVevjbMVz7TSH7HOBci5S8+jhM/QdgPQdg829nIBU3n+0tJMeFPyykVhZSiVyrLSS7FndrIlVB", - "sO7dRnL85iO4BTH5YSXdh5Uks8mEhpQwVZRHWwoSs9UVH2FuGbP3Q6XgjspB29pKyjflGgW0AOe/98Ce", - "kwKN8L6NI1cH4HHGyPPUAmtbc6Q4DJvtkW+NH/r3K5zv3w55zCxmFP5l0qVap/RV3QSkxyRTEJRYIIRA", - "1CcSRmvPW+yhotilzNKUCyUNWiQowAZPfqYVYB+yZBUs0ocOCRjAlMjOkEG9AP3Y5PJvXZKFwYKknOWw", - "j/lMLf6jL/eqisX5oNvo6+tYfqDRVjrWPW9jCx39cDrWg4mOe9G0TiqI/Bv5xgCDckzynczz5D76kbLp", - "5qOKQDXCKp9bCc/Io2ptQb09i667JfO6vk0HbQlg11aj/Dc8cZcn6dPaHRZtiYAoonjKuFQ0dIm7dczy", - "Hyd06xN6NWW93Dyx1VT9Bv0LLi7bHnGe6l6P4KQrz/Ab9CXo4QEa2MO7FMDYNqeBZpp7PwWXSrY9ZAoG", - "rZ+LYZxF+iB0B6JTJSeCJyP7o8Gr1bvCooGCiyK0rT60sNG934PD6DeuEE3SmGgtnkSoa7hJr6ZV/R3o", - "O5WlAoc3E4Z625QTYgwYnXQFgqyIhMs1t2AbcM++vFxeqRnz6XoQjLxzh/jgQcEYMgNKTxyC/QXKhSyU", - "0CIxCRW6mtFwBogYUFYL6qoCWAVO04scAmvzAL2EnVpGAoPONyQR2hAKOZM8JgboYp4kFwfLiK3vT0/h", - "IwOGYbBZLw6QQ2nNDwip3yojXOSVh36zuB0bmpMEj2OzohfaaizNb9NiXxQQZUPmw8Fg5Mo2SCfoogSJ", - "cdGAieEE6is+fTBtq9MMLGnmojgSQDjDm4RFQdNFDI39aBiDvrcqS0tkDjOMOwbmWBrMKz7NQS0rrIzT", - "tC372mECF8+TZAUPo41SiVSpIp6pv0sVESHgY8vdTcyNNnBo/qHwpWZUW94nLzIL7Oe9bjQoc15SaaFa", - "qmVj/jVPkqAT2PGU0OluoL2vQTipN7h8LaZXpgRj8kPvvglASVXYlxBKaieHLcLfrHK/MS989/5ZS6jo", - "e/CyVO+zilFQVpRhElBH3dXCelRIB7CQS7qYKU/k2yNull1ZKuHZ7nprqfjnN2C0rrv1yis55mUm7/v6", - "a3kEjzkJRi7NZsJFPT1+3b3YN89IX29JlqbahkN+8ObN3XOtGDPNVlT0hIKkEvx8UOUScJ3DGeeyxPZj", - "MsNzyoVFYLde15wzwWVhrEcbPXehWfXC+m8vrHp+YH1NCJcf2T568LmNufN/4R4VX7woWdu5xO84lRpQ", - "ICXCaCwomaAUZ5JobSlLCDIVRiyQN8HhzNXs7g3Z2xlBtkplyYGQFzWmEl0MkosOGmcKxVhMwdoxD00k", - "nSAhTxLCIlN5dshmBM+pNtUEirEiLFx0JYFKxHNSFDDRpru9oTQFr/Napx3kSuSCg+GiVAD3AqWCABMZ", - "c5lVqs0OmcjYfxrkSt3shRvoBSJS4XFM5SyvFRHiiLDQCwt5/m2Lsa/vxD0narlG7IPcWd5Klj7kJWbZ", - "l5lX6f4m7jcfWaAWF66+ZQsxv0Lplc2mYTXy8byoi/tvuKXNXN0cH+hmJifxql38bVzJVArj/7iWUXZL", - "RpnpjlSLx3+3dy1FNeeMVa5brE/2thcueSWEnMw3knlbn9yfJ7fwkX0jkrDTaNg3YW4Xk/4WRK6l6q1k", - "7gM5B60vqeQVe0ARbAf1cOoTFyUp902IYbPhcmlcljlKYLCpOPshjOvC2IYH3FYYO4/r0gV4STxT1k1j", - "3CSXi9rxfgFsHQL/ptGvtdmVBOGDC77iRuDehN1JLt6MwEvxIub4e7+XCbkQJqHTliN+PIBiJV9g6YJp", - "AzxunVxCdFw2yfvT080mKSHUShkh1COWENWypmHiqdb4ek6EoJErHXl0emyjV6lEImM99DqhUM/xkpAU", - "CsVQnkkEmbk9PT+X2rpcBK+Sw9oJCFNikXLK1NpRFK/ezWA+36p03j3LSQup+N1fHoMX/vEJKZAdWl2x", - "E1htRSqsGoPxXHAaZabepda28JhnunUtWVyh3SmcbRMaE7mQiiQmMm+SxbCJAHTX1mSy35mM0g6iSiK9", - "HzqQgZcSkVApKWdyyGz595QI3bf+HIr/FkFGXue9wrnUPDOi79sIYNODMTFbWDVRDaAFoA5ocBBs4TTd", - "gnLR/iApO7wvGNILiEhDcpGMeUxDFFN2KdFGTC+N0YHmEsX6j82VIW0j+O5rV5y6/c7SlD5hE+4tymF4", - "Nmfm7yMJqSrW3CXioxNrL0l5szj5AwvtF2tyrVwTBMddRROSJ7+jTNGYfjSiTjdCpaKhyaspUi+hCLPN", - "vhyyU6KEfgcLgkIexyRUzrmylQoebg2zfn8nTCmglOwQGBwIvObHCfR4dPYO3jOFojtDpv8BDb89PDM3", - "sRNsfQSlgTKirri4RCdbr9cE+Z4Dmf6No+TMBFfmQHoX/Mf13c0zmxv3kGzYojxdZQDx9LsP47Qa3A9v", - "weP0FgC0RD6bjanAISjFcpapiF8xv2dgzuMs0f8wf5ysAyhROJy9h1e/GW3XDGdtN26Cj2JT2jlFxBQN", - "epALCkOwxxpfqgnnpgBKTCVyz3sKHKrvkbu/vlO+TMdv8GrSUtQV5Ppm9tZ9n3x2DA53q0yPx7LNDae5", - "mSi+2vt0hWmz9+nnmIeXEmVM0bgCaqDtNsAB1T8WuI324g/UBMiOdKXEEblOqQAEmxo8AiJ6xhJhpIhI", - "KMPxFszZNAIIlM6LheecQpJyGFNIE6MRQSmPY0DZuZoRhvRswFHlGijd00pbAaL8TvmKUXE0JiFPiEPl", - "3PSZbv/AVL3gogqx+a3Ixbcl+uv56Knqea5BFW3u8YtQRk/xNYQ1R5m9JnYj2njJix+NK6iDYG2GwU5f", - "DoMOGgbbyTDQK3CEwYWKFdpDCWWZIrKHjo1/C9JQ9/tIkpCzSDpwUOfB2+nLpqRUw5YNGY778N19qj2W", - "q4CUb2wnPvGg30P6e0iwQRvlDWf3ZNSBTRchnikI4Hb7yr4VEQXukc17v4Et7ZEftn0bSf4Pu30rMgpW", - "WYvL0tIbyZ7DR671urmkihmXBeokCnGKQ6oWHYTjmIeF9yCT+e1ANx/KWBB8qW2o3pC9yYErbSIEOjp7", - "13FOMxRReWlasH6xHno9J0Jm43xwCKSB8eDBYpBoyBRHIY7DLNZ8SyYTEkIOQ0wTqmSDXy0fyl2WQSw6", - "8Sy8e5jD1jwuZ5KfJ2D1CraQNY7bMku9JUgYY5qUnUp14oDqC1e64PYd60a5PoYnsb3eCgWXEtmmuiSm", - "UzqO7WWN7KG3WuXACRmyNMaMEYEyaeKO9NC7qSBSZiYxRjcAdWYNR3VQAXSSCq6smzjmXEjj2dUc/v4U", - "SUXSFWz2xrR8CnO+I5hg07jt6YEMhtoYmo8l+wrSC2I4xRBc85E+ph8g2McM6KHhhB/Lxn8r6HRKhN4V", - "2AhZczVqtrUjp9n0lUyPRoz88/ytdhj5eaulaO5SpPNKoIqRe3EECvRNbmA9nV/SRiwT++hm2Re/6o9a", - "9l2N8vcPwj76wll+L6XHzkvB1W2R9QsOf2wg96WRV7ZqJUFhPRxB64yEu8wQaI078GBwA48ZZQBX0g6a", - "4AS+PUbo32923H3DbD9u3qqgBFQK6zSkSq2H7/wmOPBucDsfODv0Frid31S+EuAuPlze6DeVqVTxA7ri", - "Id89MuddJSgZeE6AsWhKUDJSzwYSrDSU3tt32plJtsXvSYO3d8830N8d2X9Y/S1MhhKx/C47kxvtcFtI", - "kqqFu1zkk9oFoKQfIRnDB/yQxxDcHd7CLa7Xvx57OD5tvFz/UU/r3u7vi6LDJ8ePv4hWec9VDpYtfep0", - "sQhndE6ane7VHWxJlArSTXkKlyuRIZilhzvLFBa96Udkm7dYVfZfiDqIYxKhiAoSqniBKFMcJILp468S", - "Ca4tAXjOxcLnTC/v3BeCJ4d2NmvOQ7unrDOsuPNNFt0IK9ydO2mzwoX2BTft7m5bCzxEGXr5M9og10oY", - "xF000ZYPopOcpOQ6JCSSwJOb5QEP+g2eTfqRjKbjNqNcgZ382mJTozCTiidu7U+O0QYUW5gSptdCq/oT", - "0GRTwec0MoVIC6LOeWyoOmgg6E39rlqpyCtlOOPCDO5BdJg2B9L0I02rYsGELgQHwZgyDINbi1Jc3VMm", - "oUr3hymkNRR7x3FO8OMIs5bfhjN2NCdqI8cRUXFuoPE2fxxzj/mYKwemujOtctq1KxXZLla1ZQjpXQDm", - "5nHM9+u2fv/thFdS+SgjK63rfJ4bpE1u82+LBfv3dz7ct7v8/SMOx39JnPFdcpVDA7pFH8O84iGOUUTm", - "JOYpVJE07wadIBNxcBDMlEoPtrZi/d6MS3XwtP+0H3z+8Pn/DwAA//9ZoU0kP3cBAA==", + "H4sIAAAAAAAC/+y963IbOZI/+iqIOrMx0gxJUVfb3Oj4H7Vku7VttXUs23N2mz4UWAWSaFUB1QCKEu3w", + "132AecR5khNIAHUjiizJlmSNHbsxLbOqcEkkEpmJzF9+CkKepJwRpmQw+BTIcEYSDH8eKoXD2XseZwl5", + "Q/7MiFT651TwlAhFCbyU8IypUYrVTP8rIjIUNFWUs2AQnGE1Q1czIgiaQytIzngWR2hMEHxHoqATkGuc", + "pDEJBsFWwtRWhBUOOoFapPonqQRl0+BzJxAER5zFC9PNBGexCgYTHEvSqXV7qptGWCL9SRe+ydsbcx4T", + "zILP0OKfGRUkCga/l6fxIX+Zj/8godKdH2aKnyvMovHijMc0XCxP9hVl2TX0hnCmeIIVDZE036AUPkJj", + "LEmEOEM4VHROEGVjnrEIvT06QyFnjIS6MTlkfCyJmJMITQRPkJoRNONSwTtK4PASKTyOSW/Igk5tPQjT", + "T6L1VPrHjKgZEZ7BUolsK2jCBVIzKhFl+mlIeuUFUyIjy5TtBDSKyUjRhPBMLRPqF36FYs6mMC3XLkoy", + "qdAMzwn6SARHf2Y4ppMFZdNmIo3JhAuCflmkJMEMpTEOiURUIcoUd7MxNCp4bD/xMRedMi7IKCJSUYZ1", + "+6OUC7MjqqN/DX/gGJXehaHB+0jNsHJczrhCl4Sk1YniK3xZJePvOzudZ/1+/0MnoIokZlvha5pkSTA4", + "2N/f3e8ECWXm39v56ClTZEqEHr79BQuBF6XpSJ6JkIxCGolVMwljSphCRyfHb245gWC734P/23oadILt", + "Zzu97YOn8O/tg6A8rSXCV0f+efXWO1dYZXJZBpndNLKMMioxyfKsf8uSMRGIT1CYCUGYihcIthSJWjBd", + "Zdp931KEnE3oNBNuC/q2XIWcMywRZkZodGvyomis1b4LtRCL+BUbCZJgyjSNlwbxxj1Ceociu4n0kELO", + "lOBxrIWCUiRJlXS7qKPFOEM4TWMaguipbKq9pC+DTsCyONYPayMsVpvEdErhhVakobK0SO5bpDgiTBGR", + "7/A2pKmIxaaOC3J7V6OQi+2loKQs9E+X1WmeaAkvSGimm58AFYqMScgTgnTT1RXY6e8cdPt73f7B2+0n", + "g/7eoL//P0EnmHCRYBUMgggr0tUL3maZVsvvo4JK+kVkXyyOKg/tejUZ3I5dYixVvqthk1O1GGHPmN7S", + "hEiFk1RvbD2GEjGbtrVrsL4OjvIrCbz9RQRm5FqNLIW88/HxB7lOSaiPGO62Z35i6/Y6iE4QRrkM0Oxq", + "BOPKiTz7ookIgqUesNY79On0e5AxmaX6LCTRKI2x0u1qJQXYYJRQKfWn+Q8RlWZjdgLH5CPG1UhkjJkX", + "GVFXXFyW37StjGgadIIZlqP5NM2CzqpzoMrU0AWJcSqhPbviYkSE4CIwuuZiNOHCLZI+xAoSrmhqiUIy", + "P7M8FAo6QYUAuXx0c3HjzlfVOzjoBXhJGDXd6NUwmeWBl9taHm4+tNWS0ohlo5W6ZUb2Y1mVABHFU8al", + "oqFsJTfhNNbLm/DIIzqP8+YQjQhTdEKJsIoqQSJjcKy5RpBuBFGGMlnbB7kuPSJzbfyM5nsjFabLRKlZ", + "CuXFKx32xRFTOuby5c93yhomrc7da4nMMYU9eUzm1BwtVWXILs0oEnROhEd85yeqEYXmPbSh97oWIYwz", + "slmhFJvTiOI24iCCMY2oh3vOjk6QeYxOjtHGjFxXO9l5Mn4aNDfJcOLhhV+yBLOu3hB6WK59eLfc9qs9", + "r87PkyQbTQXP0uWWT16fnr5D8BAxUBnLLT7d8al+aUhHOIoEkdI/f/ewPLZ+v98f4J1Bv9/r+0Y5Jyzi", + "opGk5rGfpNv9iKxoshVJbftLJP3t/cnxySE64iLlAoygtRunTJ7yvMpsU10VH///nNE4Wub6sf6ZiFF+", + "iPgIduLUqJNjpyfY79D7U7ShZUhExtl0Stl0sw2/h1yTQx91vkMchorsO9pMVE5LufV5GwqC13Sn32jV", + "2fJWy8xKjhLZ1Lp7RUvUhMYxlSTkLJLlPihTB3vNkyltGHNCLXX1XP+MEiIlnhK0AS4VMD+MMNWKzQTT", + "mESb7ZTZpsn8wcelI6TC3sAWXTwOt3d2vbIjwVMyiujU+sTqR5T+XbOYbkcheNs/ETjM280DuhRkstzf", + "CxDd0IkgEyKI5vEv7C4VfE4YttbLX6Df4P/aKpyFW9ZTuAXEPCte/9wJ/sxIRkYpl9SMcEly2SeajYDU", + "CL7wjxkerVrrEkdJhcXq/QFvfIWdWOh1a2lj3RZatcHTtZ+81e/UZSeIxlyXKEmBRhH5XCs1Hu2AM2Uf", + "1NyXfIpiyozFoVU7sxagVy1S8lPMQSR+JTrk5F/e/HrctxBe5oeG1vSzTq6Ax3xapuaMYKHGpELMhiPM", + "NlSMrpH8Z5XtUzursCSj1RLkjDJGIvAX241t3tRqrNfMgF10SdVoToT07jkY1q9UIftGY1MxDy8nNCaj", + "GZYz62CLImqchWeVmXi0tYojHoM97hoELQLs1/NfDnf2D5DtwEND67nULyzPpPS1bt68ixQWYxzHXt5o", + "Zrebn9HLHOLngMJZ2XT25BzoGNNIusCuprWTMzkzf4Hs1qOCs0+LAc1esf77g2fSRyAkjJXQeHvj1wFz", + "z/A05pqmC5Qx+mdWUbB76GQCDmJ9UNCIRB2E4QH4HbT9NyWMCC2nCs9QSQlGG6Q37XXQUOuFXa0Fd/FO", + "t9/v9odBVY2N97rGvE+xUkToAf5/v+Pux8Pu//S7zz4Uf4563Q9//4uPAdpq5k4rtPPccHu/g9xgy+p6", + "faDrVPlbS//y8H0Sxyz1iZYTN13po5NlxcHMNeLhJRE9yrdiOhZYLLbYlLLrQYwVkao689XvflVawDxW", + "EIFNNZluSIaa0QNsvBHzKyJCLYFjohlPdrQQpkp2ENZ2MwgvpE/J/0QhZnovGOWCC0RYhK6omiEM71Wp", + "lSy6OKVdaoYadIIEX78ibKpmweBgd4nPNZNv2D+6H/7mftr8P15WF1lMPEz+hmeKsimCx+VrPTeG/Ipm", + "1Yo46mYxqHkJZSfms+3lO6gvW2E3kVUrbYy5xqXWQih3ka0ZyPL9rja2Eo/p8HpOhKCRO5aPTo/RRkwv", + "id0vSGQMDbN+fzeEF+BPYn8JeZJgFpnfNnvodUKVPg6z4pQ3V7a12zUSzjgoKnHMb3KdBpoiGDg4XnmO", + "ryKNl9pHebvLp/4vXKpughmeEjBH7YtoLPgl0QM1dwKUSHRJFlrLWaCpbrQ7pxJueAibozk2XofekL2d", + "cUnMK+6RBN8+nROU8PDSXP3OOFjycxxnRHbQ1UyrHOATJDi2PyNzMTZkMz1IGfKURNoIMa/B1NAFYfML", + "lOAUtjkWBPY4SrAiguKYfjRX+HDLQCKqT7ghI7AxUIr1ng9DLiK4YeOI4HBWosJfJbowCssFNH9BmWbr", + "C7Mxa5fVn4LX797+/Prdb8ej12fPfzs8Gf36/L/1z+ajYPD7p8CEauSays8ECyLQXz7BfD8b9TYiIhgE", + "h5macUE/Gm/N506gaSA1f+GU9nhKGKa9kCdBJ/hb+Z8fPn9wCplxY8/1NvAM7LNXGTJnqUckHTtvoETW", + "w+TuNjTJtIh6efZuS5/OKZZSzQTPprPqxrCqwY22RETl5Yjy0Tj1jYnKS3Sy9RppxQXFVG/QXFHZ7vdP", + "f96Sw0D/Y9/9Y7OHjs2uheFrGcSF1Z/kTLNPHvVxdPYO4TjmofWhTJoueF1XPgFPmBKLlFOfEVcTTsWr", + "yzKq2y2e3kAUbY0p25J6GbrhzegOfHNrU+I5m1PBWaLNuTkWVJ/TsrpXfnt9/Hz0/Lf3wUAfBFEWWq/k", + "2es3b4NBsNvv9wMfg2oOWiMDX569M7eeZtuoNM6mI0k/elSJw3x+KCEJF8aEtt+gjVlV0zD7FsHiDIPd", + "lz8b5tp+CXzlFsXeEeWtmIZr13ovf/Zxy2yREjGn0udn+yV/5lZ+OdynwtvmlixnWuDiXsl+CWOeRd1S", + "l51gQgUJIbxC/+tPkmhFfv6xei3l+c7v/mqlwK7RTHGcUkZWqKbfiIp4xcVlzHHU3f7KGqK9UPWExpgH", + "1fXNb9YcSyxFnI0xi65opGajiF8xPWSPXLVPUP5yLlyv9Uxw/K///ef708LO2n45Tq2k3d7Z/0JJW5Ot", + "ummvDyWfSJb6p/Eu9U/i/em//vefbiYPOwmjiNxKqbPr/9y0UA+asbGExh3acDOcn955wIri1qCGz5Hj", + "vbXXwD5BzedExHhRErx2TMF2H6RfbVSCQpQkst9pMXqJ9MdrxLBuzR3yL+tG/k7fL2g9g/KM6WctK+y5", + "0GYk+UC2d07tnzvLQ2oY0SVNR6A1j/A09/muCgk9v6SpVcXhC7OMcWwEQZSB8j7mXPWGzESo6LWDBSbX", + "JASZJxVW6PDsRKIrGsfgIQKhsny0aMW+FNoEr0ul/1dkrIPGmdLaOlcEWbsJOslgLPDymKCMYXcfXtOd", + "7QSXwwuALJdEMBKPjG4sW1LGfITsR43EgalOsLQhakJlaZVex7+enqON4wXDCQ3Rr6bVUx5lMUHnJrpg", + "s0q9zpClAsIUdCd6P1PbL58gnqkun3SVIMQNMYHGch+bvaydvzx7Z6/75WZvyN4QTVjCIhvo604cGwQa", + "cfZXvWNJVG223H+N6E0hHZLhVM64GqV58PQq6XRuXy9M8fbOhE4wD9OsuqQ7ncYg0DkVKsOxlrUVddJ7", + "wW+C2D1mg4mRL5svVu4VQbOqejPb1uNiWoaIdm+4rMdxYjSl1o6Tkim/5EJxduandoNd0/4JcwNZ6Tgq", + "TM0v6OvcNLIUvGN+7riZ3YJKJzlNau6mr0OeQ1kyzVsFn5sYLKMRSrRxoa15y8fafr/ooIu/VX7Qe9+Z", + "Flq/uEKGGiBPmP6p3H7dKbHWXXCjcO/y4mB5+/U4lI2RTmi+jZTATJoYtRlOSQ/9AkIcKZKkWpKxKaIS", + "5aFdiPGr/0TcKDXu0yHTQ5MmTsSSI3caSTpllE03tZqvDyYcRcazNMlUJvR7cyoLalZZx3lvlqJazeiI", + "kceQIUFZGGcRQRfOw3NR1QuX/T/LJqF1CC1ZOIYkYNmAsae2kkzp7vWEE6zCmaYTz5QJHLNTrwb11bxM", + "6y5U7Vjyq7ZbrP95Li7qiTBzj4mjJ2cvecAtWPJPNrkBraLid1FekgUsuXNH4iWHZNkT6fcXCiJ5PCf2", + "2C37MseQ6sON4lS4MY1D0vog9favJ7n4vHPrlkLTqzX5q6aCJ8VHqq6bbMExVvt3MeFOCunJmf462jCW", + "BIgPpscAgTp20TG2EgEPBGKaWWIUUUFCtdQ8ZdMhgxiSC/tLz7Z2oTe51lG+SuIU5CGA0l5eWlRaWaf2", + "QTN6ajyhSpGoU9UNLglJ5fpJafXaOq493nVBrgR1gswFFbdUzwibcBGSxBoJX2Y4Pi815jXjbtbEckiH", + "oW9pzC4/A7JTSGTih8x6gJu1krZRz16MalabCSGodnmB4/gCbdiXNpEgf0Akvl0rxlnB7G+PzhwL5Nfe", + "7087miO1FLiYKZWO9P/Ikd7FF/XG7LduhxeZZU/7YF/t7e3aVbVONzPgWrNV/5o3LKJ5aZz63XizpvlC", + "j9LGmbRR5Y+KTwpP6iVlUdsGftXvNnrncsXIWRp37aBLBelm6VRgCLH9mu65W9+bAjWbJfiaPF5fmGSR", + "IZhJxZNyvP1GLcSDVoNBqsSa87gbYYXBldnS32qGuxx4nCxMU8YWa/LEjKZjT9wQ/QipAFM6xeOFqt4f", + "bHuz+b70EtuNxbcsTQH8xoIk0Ujx1SHMdILcu20iFk2+geKj+YTy1ekdNv6lkn9njiNr1+omumlIrTsB", + "dJxwZiJMDRFAaXx/Wr676w1ZF47fATrOO8ibzZvEoFviyNycbHBRGoRJ5EDjxSbC6P1pD73NR/tXibTB", + "Micuo2GGJRoTwlAGrmc4DbvmLC4PIJNwaKr659Z3YpIfNuGKkttnvTznGLw0eQY1hEqNaW0+JnMSFsre", + "CWNW9oK18lqtCvx+Q6ZUKlEL+0Ybb14c7e7uPqv7L3f2u/3t7vb+2+3+oK///3/aR4h//fwOX1uHVdli", + "g8/K0ufo3cnxjnWWVvtRH/fws6fX11g9O6BX8tnHZCymf+zie8kA8Yuy4yJqDm1kkoiuE5Oaq3yxcqWQ", + "tIZYuFuHuN1RxFoRgLvqXUOJt/rNu0ht8QVN25Ddmyef1AXm2rDr0uSWLflFCnZnsUtKGpyNbgypN47z", + "mMrLnwXBl5Cyt3xuJ3hK5MicZ/54hkyaIBtybb0bgnM1kebetOr13N57svd092Dvab/vyehYZnge0lGo", + "T6BWA3h9dIJivCACwTdoAy68IjSO+bjK6Pu7B0+f9J9t77Qdh7niaUeH3PByX6ENS5G/O5wS96QyqJ2d", + "Jwe7u7v9g4OdvVajsv7iVoNyvuWKSvJk98ne9tOdvVZU8Cn0z12GTV2B92VWHprsfv2vrkxJSCc0RJCj", + "g/QHaCOBI4zkt1XVPTnGkcs/9Z8dCtNYroyYMJ3ZN42jLcliRdOYmGewIK180TDzY2jJi5DBWJ7ve7OW", + "bF7S2ggBN5f8FVTJL6uQ7tQkNJeUJ0riaGB26Fo5B6tZDOxDEx/YObTkhlfadOrGZE7iMhOYo8tk1gqC", + "cj4xi1aZFWVzHNNoRFmaeVmikZQvMgG6qGkU4THPlLlmtAnaRScQ9Qy2x0SL63Z27gsuLtfGj+qTOM9D", + "X+sVOgRH+sS6auAUx8h+7VIUSkpffh1oLk3tc4nemC+Mh6j4Oc2qqDYd6Ml6khgSRCoOktQ6DG0zbbVL", + "v94CzlIX/mH6K2TnPcW+dCcmXODrWthiSgB/Qa3VWDSnvIX3z+H11uHo+sO1jpQWdGfk6j6IDvH6Xc22", + "XclwejcUXxWMlvsaipfgFBY0Ij0EuwuiYlx+YG2nnSuepiTK/T+9IbPx3PlP0tyg6A8NHdSMUIG4oFNa", + "7bjqYLvLqLabsKLjpluzY/nDZQ0VHkL4RvOmxxNlsBYuXcoUKecv2UUIOsF5jkxhJVGVNG9ydI8lihSh", + "lktDfHn27qaxaangE+rDG4JYCPvUWmYuauvVXv+8u/3/mAhMzW+golFm4icSHtWAJOz77U6el2fvzprG", + "lEM7oPLoluaUR7ysArdyFLGXSvZW0lowjv31wZJ3Uujez3y67ETghIyzyYSIUeJxrr3Qz5F5wYQ2UYZO", + "f67qs1pvbms1n1UWB8zmCQ5tZn476nsccrVpdErU/OBfrjfEHMNN+Xx6qYR9x6b09dBvOZgGenn2TqIi", + "Ssnjqasub2O8/NlsIWmIY9OiSc+lrOxgA+ZsrSGfFR9aV6RHT/ZjsLiNgDbm0zSDbXj+pnvy+v1WEpF5", + "pzImiCya8ZjocW+WpMXcZfUVwf0VITFv8nQYxpBtN1CJVvkObk2k0n71UEdxheORjLkvWOOtfojgIdp4", + "/8JkXekRdFBaWUr9e4kKFf4+8O4YLZGauj2HDusu08oG99qOVTRM414pTa/SqW+r/EJwbEBAq/y8DIDE", + "L6sLzS/Xg+6YRnz9nrjA8JpR40veOjo9NgpDyJnClBGBEqKwhRwthbiAOhR0gq4+oyJMEgi1m/zn6uiW", + "Bhd8ORur0Yl7tITbcScO3IZ88zcmBCFCCWZ0QqSy+eaVnuUM7+wfDAwqRkQme/sHvV7vpjkqz4uklFZL", + "sWVC+EvpKj05+7J1uINUlDZz+RScHb79JRgEW5kUWzEPcbwlx5QNSv/O/1k8gD/MP8eUeVNYWgGp0MkS", + "gEr1SlOfWeb3QQnz0uH7tcK189szENkAeXPefGOFp9o+MRz3pYnFt4YeKfCvVAlypBwQ2gJ+hH5c7Ql1", + "ihG8Y/vMmKJxgcyy7AO9FbaOXAk/sAQ9kBKWAw7Esfkr5Gyud4UPfaAiwN2zL7o/sFEuo4h6OPkf1toz", + "QRKQVbV+vwVbOE3Xs61fUczlX1vUFZsb7TmJHlzq3+aOrdr76+l//fn/yrMnf2z/+er9+/+ev/yv49/o", + "f7+Pz15/UQbV6rT4B81t/2rp7HCxVMlpb8tKp1iFHoVqxqVqoLB9ghQ38Zo9dASG32DIuugVVUTgeICG", + "QS1EeBigDXKNQ2W+Qpwh3ZTNdNjUH58Z94/++JOzLT/X24hsSoOwC5JnMslsHPEEU7Y5ZENm20JuIhLu", + "9PVfEQpxqjJB9OppHTZeoLEAWG9rnhedd9AnnKafN4cMLFxyrYSeQYqFynE8XA/AFHZUJmbAvk4ilxhu", + "LOQhy8+lPC/c+Gh6uRMEfPP1iEs/UbzmCxfVVJynfV8GPUR96YWMqVQEArNzztZslIejoaf9iqh42n/a", + "X6vg5zy0gv1gJyzj/TumbLGXDAND10ZwQ4RaC1+6lk1mj6Bf3r4902TQ/z1HrqGCFvkSGyPPxABK4yNU", + "sSxF/20GXrRRWN2WEzJOMvgsbpE19NyEh759dY4UEYkL2N8INTknNNTzg+t/KmWmWZFidHh0+nyz16Jg", + "AdA2H/+KdXybz7Ce3GGdZk2+wJzjNX076OQYwnPtDi0UOAirecEFio2AKfb1AL2TpBrrCktlbvXNSsaL", + "wvNmToBhsOlaTOuSYoDe5HojzodSKZJQdeYV+xKatRcvJuZnqfXOEvy4cHaRFW0Q4YNVHiSuT9xmUbB6", + "+3soDnvexnWXfJo329tlZ6juzM8axdp/bdSUr6/u7N5M3XEIuOkMSx93zwiK6YSEizAmCF5aATtPa4m3", + "otl1Xel3LfJ73h2A2eujr/y5D7N7v7u9/XZ77+Y2/00BL6o5qaV85hzzoj1YxV2APizbv9dUjRqDGpB+", + "bEMYnJX3/hTNsGR/VfCwZutt7z5pBV+qe20bDlAOBOATM6RcSrkE1/wa26T6XtI4NtEhkk4ZjtEztHF+", + "8vLXk1evNlEXvX59Wl+KVV/41qcF9oUTFS/P3kH2EJYjd6PWHESKi0Bsck2lksv5v60upldjbfxSwcPw", + "JlRvfkWQDHebvzSN+4C/eMgwyW8PemMlWMaXIl5Y4+GOAC8ahasPLKIqZ83PXxe64k6Gs7baSvmsdTHs", + "t8aK6ATUE797KLUIJBE6OSswJwsnn2u+Nidbumi73+9t99u4PBMcruj79PCofef9HXO0D/B4EEYDMvkC", + "l6tlbKMM4/gKLyQaOnNlGBj7qGQYlbatNWlaXWcvQ3LcDoGjrlD45bRRrBwGubQ45U3KRYsAzXq+cJLF", + "JgegjG2+XK5JpgYcw4C75brkkMEAOzZJNy+xhMNQZIU/wdXOMJpnllq+HzJBZMqZNBV1euhXspAooeDD", + "z7uH+AyJ8jigaMg2hIsZy4PDUpxJEukfICCj4y7+9dCoAhA7/cGQyVkGpUA2e+iIM5klRFhXCxpT8ANv", + "IpkZ4wrGC9SAwlSSRkQMmX7NA7/xKVeUBwf9fr+flygJBrv6330fN/m5aB1mSjswlC9FoFiFLH5exRRv", + "bdN8QeGdVhFDTnWzsUL2q9FNbosICnkWR1pRHmvRbPwYJLLuFklUAdcO0vwdu2SaoSpTN5cGmtH/zIhY", + "oPenp5UrJkEmFo26xcSBpRvWgac3WoadNabl2tHcEpfkPrBI6sdqSZ356sgjZR+3i1k2HNrC112YF944", + "E8rM0mg+WTGnmpcyIvNRlvm0Zv3IZSq9e3dyXGEOjA+2n/afPus+HW8fdPei/nYXb+8edHf2cX+yGz7Z", + "bagH0T7O7PahY9Xd3JwZCIQHj79J/IwGer/lsV/jTKE8LlRv5CNtfqCSnWPy4MAJd1I6LnQz4BOzZpBJ", + "aDawrPpYAQQNwG+iTE8ZnI+6ERvtN0Av4V14hBNzktpBaOO36nfD0cLcO2jB4Lo259rqIZ/bIw2+0ecb", + "gmpoetqaDNYcXd2EkTED9BuHb/IDlvG6XWteh5Nt+fW6Dbxhw/BcuDZ05kA+BqgesKoplXBl2B1D2KWp", + "L5tirUDkEb5X+JLY1dDvSNOdbtsK4wF6kQvgXIRbkb0hif3TnAs2SwEyMDYrcbCWmwLNiQVXlEI8O4FZ", + "raATuEWAUNDloFA356AT2CF6U5/KHO+79yM4BkldxN9lisYWewQmRaHsGMwJAw81CQyLs0eikdE0mm7x", + "TVCX1Ubyj5w8en+KNiDL+O/I+ib0vzbzG//y5t/bebb37ODJzrODVrlExQDXnyNHEHK4PLi1h0qYZiNX", + "0adh6kdn74xWGxp9EZxNdu6l0O1U8FAbPZShokRQ0fmz3rNyClXEM1MuzQ7J5lt+LhUFXFnPqeHa+k8a", + "z+lkwv78GF7u/CFosn19IHfGXh9BUX3Qa1CdlC8wlrwPZNw12Kj+LBdgKCEbE8HeEAkzQOdEIeCfLsIh", + "6AJ5pKBlOZcuZinuZay93d3dp0/2d1rxlR1daeOMwLxaHuWpHUFpi8GbaOPN+TnaKjGcadOFTwNsC7N6", + "on+fIQuT3q/W1+1t93d9XNKglhVcY9ueJ40kf291LTspS3QIeMz1sKVd7qX27m7/yd7+0/1229hVsxTX", + "qyWMg8ox5LHoQuWV34BLr7eHZ0i3LiY4rBpC2zu7e/sHT57eaFTqRqMCZCyDaHODgT19crC/t7uz3S6j", + "0XexZXN1Kxu2Krs8m87DFJ7V8JBiWfR2mk4Ln35rGOwNCWNMk8PQBaXVTh+DXDMS5rViEdocDNbXtHRw", + "tfi2lSVYK8dltAQuUKmOaW+9V/12TvJmMW3Og/VifFlVjzHT5LKpNwYg9Ra0SwWZU57Jr9AQVyTUzDSJ", + "ORc3+rYpyu8NkVmsjCebSvT+9K8gRDRzIalIWr1etOy3IkHplpO70Qau8ISfq5uI1Wo12iz9qgl3GrZp", + "Z1V0emX7N+YBRlpUZWx9RMkRjsMMIAFxvp56VpDRwzMF8S8LE3sVx5wzFM4wmxIosWAASNkUYTTjcdQL", + "/DducTSaeG/C+BWKuUEwuSQktWh5ZhD6M1tuGm285EWhSMNKNdTr/cRIFYuHVq8OHqysNN2QN6jpiRUv", + "gWuYTypOg5hPJRibCqLKenVMJ21ZGWuBGfTHeWJs1GpC5I4+7T1DrElv3xFqjk4+sYaz1TEUzymJQ8Gl", + "LMrdvz+tDnNVWHBCGU20nF0fJVIdbAvWNb5pz9llixe3BYn1HYieeMsvORKBhyGselVldeuGTzDLAD+v", + "xMjkOqXCsEe7GIsZl2qUJ3ndcLBSjQAbLROkyAR15+UM0moWRsTBO95z0Ym225ArLxp+i6+XuMrfVNMA", + "m2Wql6J+anVyHvSx8XKa28rMuiJVr56XdZNEzAJMi0polZZyANEG46oilkqAUJtt7jv9Nqrup6lQ86u9", + "/nnbHMnVKZFnWM1O2IR7EFdvcK9gE01cCExKBBT15wxFhFESOeMxv2CwLjRIXYklQVFGLOWMQiqwJTg2", + "2xuQVZnzvVE2rcn6eodtvP1mDKuh06Bf+2Kbm2vpT3h4KzKglYk1kQgXqQ+tAmeoHPkd0ssNCzLNYixQ", + "PQ94xZDlIokpu2zTulwkYx7TEOkP6rdGEx7H/GqkH8mfYC6brWanPxgVkbu1WyAzOBu3bRak1m8xhZ/0", + "LDdrWSPgetky32/p71sFAnjDz17QmNhU2XeMXpcYvYottLfTb0ooami0kkq0nGZ9U8ltWda3410G9GFe", + "i8ST5GICyWqXH1VHZGW+3ut4Ki9Xpk8tu2LQhostcNhNVbqWMJRaeULaBUvWo2jcaLYkCau97z3df3LQ", + "EsTqi3ydK2qVf4Fnc56s8Gg2rNRpG7fZ0/2nz57t7u0/27mRg8oFXDWsT1PQVXl9aiWHak6zfQhO6N9o", + "UCbkyj+khrCr6oAq5YNuPaDPK7ZuAV7QcO3RiJ4fl1fS3bNUPaDtfIwrtKXDispVqpC3QSYTAkblyNCt", + "WwymlvTSagwhTnFI1cLjMMFXppBC/kotCb+NN606WA9JbdsWR0VLLpmNi7jgDdc5+ptxrdd44WlrLDyZ", + "jZvc+K/rvRonfuEDKl8RtbihKcp1LLsL8vlcYVkJHtF/hxCQVFRArIehmTfa13p3vJ6Xey8CbH1AEv7S", + "7uXlry1nye1bUZLrFF91hDZvwRvZ0J4T2Vf8dX1wd00+2APwdl+NxmWUypUwoBVIy+LUvXm/7Wo3Ln9n", + "TrCb91eKJL7Jh3XAPuBHOwZL8qLtToUlGrhJcbEep/0OYLdM6MKtgLds1MO9YG/Zn+8Eb2tpOc6Jcu+e", + "a4s+i1fArDNFxBx7HFOuCeReqfpRjSTuIOviQ9vJZi1HaG/m19VsYnzLKDWtBY5SQSb0egW3mBfMcV3N", + "QpCWAlEVil+ijQRfo70nKJxhIWtjZ3Q6U/Gi6mTd8yThfFlpdKK06ty+aEGxmu7D5RsNu5zl1n1b9ryU", + "MuMvpkCi0Sr4iKP8NeczTvECdMtGQ/DJ7l6/v7vTvxV+xNeq8VBqpynwtPSddeZUrh7LLeRhpstAoFeC", + "mlKBjkxSCYKTAQRtpTgkKCYTyK7MAZjX2vRLXa8evL0ktfkjRRiXXShXD9j6WayIY5yB4uHasdAbbhqB", + "u6WtpgyVny8Pe0UKZi5mwqVcTE/K4W63f/B2e3ewfzDY3r4LwImcSE0hPE8+bl89iXfwZC9+unjy5/bs", + "yXQn2fVmndxBOZFadc5adRE7h5SIOsJrHRlZkpgy0pV5BNz6OOcVssDcJK3d/zfzPpgZrFQWzquTLOsM", + "WBXEqRc6vI/8ODv6lS6U+vBPjlcP+1ZxZPWB+BmsPhTgp3aDAQCk7S9F28lYy3PnXenF1ifPytjGdWeP", + "L7YctrZ3lRso7uPnimCs7LBVJ/byqeYx4aZcUDVLVh8P+Ws5dgdchn+UKqrmY/XQyZQBnnP55/zuo1xy", + "XX8cdIL44151z9jf22fmWayKnAHtUpfVgBZ3AwAXvpoK8EphWggTnoAFAUL8tN3dfgY39PHHvZ/63Wc9", + "9I9SpEDHUKtMvm33duXXfhsa5oIS9E5zc7797EbX6I6eqzjoV3suNR3EFsXC8ngBpuvOChdBXVng4vHS", + "GteSl+6sfJc9zUZlLSkiMV74SqDEeIHGZAKY8fU491IDaEymlMkOItdO6GCJMCqbQrY49m5fDgPEBRoG", + "+8kw6KFDi/0C1mqBlV5pHlCyS3xCE1u5zeJkN4el7CTt0jTqxsPNAMHcVx71rOfXz5697W/fGBDsZsdk", + "7wuiqr/I3G1n4sZYqibb4hWWKrdJkcishdGB4lls0fMVLLCZERDeCpmlA5dIWbCslQGyUPycJ6SDplyh", + "IidiraYHwxcZ8/JDdfxFAXRIEW1kiJ01DNFuTHnOJF0lvk6OUSp4lIVFFHAMg87CkEg5yaCee6+tVr/+", + "nvUuHRoQXj/hAq13aDR5MNZnbJPr5vX+jVyrUpeaYZuXeru/fqnvxAvSCbI0Wi/DzEvtJNiNMH7WxJV6", + "fDJVstc0wdJkPrSQ6G/KFFw2cqEUHAq1SpSlrjap5qllTpKeiqT4euSF2zgmMdHH1HIjyBQLtqEvVBZS", + "dL1I3T546ncb4utRCLmfSwP5lZBU2yoJl7Z4b4LZwjuwOj4/2ui72rQSQfNdgxFoqVUd3JO1mljjUrUv", + "dVDzapsEuHJliRzs5uvWObBfrq1Ccxd+uIdU0l7by4UarIID/chhglz/JqzXlKaPa+f13r5sp5O9tZZx", + "E1REPbS37GY+7P6PcSujUW+w9dPf/+/uh7/9xV/gqmI3SyK6EZnAfeclWXRN5W5to/eqQAmAkaWVaVsW", + "ShGcgNMovCTGSZXg6/J49/u50Fj8hpOlKcBFcUJZ/u+1E/r7X5qvWUtkfAdyci3LfjGE3F3gcyvujqON", + "hIipK4LhogM3e0MGAYeXZCFRCQTUqjSOUf8q80/K1bcvjBrYg5rxYwpYynLItFWLw5Ck2pqwYIgUxiI4", + "SJ96DXoLRuqi+bVcwoDAbLOrllA2fJXeBwEjV13TQ9TVvLe3fwB8RFmZkttLS+xbdIMD0FSmVlPZ4zV6", + "RSVkn7gg69LLaIMkqVo4qG0XBrt5M1yCw7xB7833V8Yg7D/7GpDL71ZiLH+HRZLLsBFuQGsBI5bWvxHY", + "1B9Hd1zH9zJ70hZ+rOJR1axDqbrNYXaJVmdGEC26HBKnn5lIVAsCNM3q1RW2Eqa2LIS5L/slgrruK2OP", + "i13mMBQAL6BFSO1Khbo0s9JImtfm1OmN9Vr7zQQ606S5mhFBSgsBHxS4STckmY0LbZFTZYCGUyK69cqg", + "pniNoBBompv8jgR57PCyH3Q1LtYpvs57AB86lks3TTCPAiFy++XPUJDqjasQSSeuCRhGTav3gyBVuWgV", + "TRxXLS9GmauW523e9248K6tWSL+mvVVjzqKPCmv6+PEfmKoXXIAd0JzBdOdYSmBjRERACncdKakVzBBN", + "SDTimVq9/22lDpu+FDlVvgAxdzYPBia2hePXyAKXY1OM4YNPb5AkzARVC20kW5V0TLAg4jAzGx4ICR3B", + "z0XHgAn++TN4CyeegMWXhBFBQ3R4dgL7McEMVGX0/rSE42sgnZdQXUDJe310Yu1MhxMJdgNVwHqulvvh", + "2QmUhhbG1gr6vZ1eHzZzShhOaTAIdnvbUChbMxxMcQtKiMCfNhcht1dOIqsH/Wxe0V8JnBBFhAwGv3ti", + "+hURpiSJBK0TT0t2Q4qpsIZDGkOmgWEVqr8FUC13lA7MedwxBG/tJpNqYeMuSfraLusHzQlm18AUd/p9", + "Y5IyZQ9eXJQM3vrD5mcW/bbS54A8HoSpJb3e6ZSW5J87wV5/+0bjWVvl19ftO4YzNeOCfiQwzP0bEuFW", + "nZ4wEwyODKyIDXcp7zNgofIO+/2DXi+ZJQkWC0euglYpl03KMNG2OyNXth7OH3zcQ/YSAECT5YxnsZYm", + "yES6O3NfYdGbfkRYhDM6J0Nmz2lTsRkLMOYTpM9nY7ZUt4bp2qx+noP4M48WNermzW3p5rrO9VsQuA7O", + "KskIMMhGTcWuCqcvZQyK5kpikWzzqi/LMTVQ5VyG3FvenTDMVFE025Q3vyQL61f2NtgKxUcLPFgWIgBc", + "w/lJdjb96SuAuevP/DrOnyFL3qo6weAyJoyzqNC5XEQ1FmMcx16Yh2nMxzi2VeAviUdFfQlvWKKU4Ymd", + "csN4RAzUbLpQM87M39k4Yyozf48Fv5JEaBXIQvhbWtsSyJZ1rwB9LAEYfVMgSPe5ZYa49emSLD73huww", + "SlzxJ2k+wbHktjy+QUuiErkQXMO7flDkhuiOo0wqnliWYuVqvmaYPFNppuzNtiTK1h2A16HYs5yRaMgU", + "R58EmVKpxOLz1qeix89guxAcaT4pvWKmtPWJRp+bRi1HWM9+BK96rD8CBBgG+nQZBvrvqcDadsnkDFwZ", + "EtwX0/KSbuSp91ov3KxTOMQMpTw1sAXAVKbqf6UNqOGC4xgp2EruW61twko2zMdmIvkKkto0JJM3UttG", + "UJq0tJn6e0/9+0mSUBCfg+O/zl//huCo0mtgXivcRuZqmelTFEUZaPLQe2/InuNwhozeBAh4w4BGwyC3", + "LqJNGGsmbZx0twsq7k96aD+Zbjo0+qnX000Z7XmAfv9kWhnovZQmI8UvCRsGnzuo9GBK1Swb588++Ana", + "lM1xXhEEaMPI/k1XgQtQJYpj0JwbmEWIW1kbLxBGhQQq+1HGlGGxsnyYh/SWgtqUx1NZJsanIXhQh8Fg", + "6Hyow6AzDAibw2/W0ToMPvspYJXoZhw0U0HN6do5Ex30+5vr0ywtfT0qdOVFvf0+L2lfO19N8bBK17Li", + "YSbnsCL1CppaeEbdugfN52ccueoqP1S8NSqe9VyUlDf4vnwOGPaNiTFwaxqYtmdjp4GttE4MWwBYKlgc", + "LinaGBzUaXAF85bNj7o5v2xW7DXtshCGGDv+27sH/oN+80Icpt9n99UvjgH5NK+u/rjYERbLMWLHbxG/", + "JOpb4Lj+fYlSC6X6kPz7WPjnJbF6X0G0mjTbInN33+SHfoCUD2lbMS9rW/UcxtQ9J0yh5/Brz/7XWTyA", + "l3wR8+nFABkSxnyKYsrsbVzptkgfipaW8JHJ+si/s0kgDndrw5yf//rff8KgKJv+63//qbVp8xds9y0D", + "hgJwwBczgoUaE6wuBuhXQtIujumcuMkAkiaZE7FAu31QM1MBjzwVe+WQDdkbojLBSreWBgJL2gbB9GAw", + "H8oyIm3WjH6RTiw+h3Ewe0x4t5cNKe91R3eWQ4zNDEoT0Kei4wFIuLbFD6z9Ffi9Z2bOFf9Z3Ve+5DFd", + "L18UuVaGe7tmgDcUMEBi376DB3bSaOP8/PlmD4GNYbgCMFhAYy6ascpz74dMWi+TjESpChSgspFNJpVs", + "tf/32L7TzgFsW/yePMAWlusGLmDj8oCqeW4FftgKLdzBfro517DPP3vsciWbHbS3n2+5CxdN1MoQ/nrr", + "7HhvmebmSYlkD2ECow0Xk+4Kp54dnbiKUpsPxvT3cmromdo6HfnRgbgp13pvZtkRZ5OYhgp13VigVERC", + "clOtyiCPRRy8saNG2M2rjnZYPt+2KuA9jSddjuNTHHl3f3rUOr3JMVIgMha89uMkWcc6x1SGXH9b4pZu", + "iFNb5tSoL/k+LXPROoeUCXHPj5yV6pIVzyfHbkPen2vKdp2x+tlwD0LxuCYQH1AQ1ko3ljBMHxM3v8tX", + "0eFCrPBcfVus2b8/Lei+vVg+Nn9MbqyoRjYtBQ2Sc+MB+pKoX8wbd7jQtgfPxM+JcLvaQU7DrPNpmU9R", + "OCPhpZkQXEivtn1PzCvtTF/T3vdk+QJ5bqKxWJL/UFFaGLsFrVYZuCe2XOHd2bfQw43M2693z2sZzENk", + "CDYZO4+1qQSI5YKFm9/VVe+9nGaG2I/yMDvL4tjdeMyJUCiv+l4+A7Y+QVjSet3e7baVx8G7N6+6hIUc", + "4tDyGCq/EmWffGUN3yyYmcoPNmljE5rkZOrOsyYN5wvW35alNhGOPcr/Y+dFTMcCi8V/7LzAcUoZ+Y/d", + "wxgrItXmnTFL/75E831r3I+Y+bTCTatEA9HEoALtOg01f6ulkure/670VDPpG2mqOV1/KKttlNUyuVbq", + "q3Yp7lRjNX080JVMzmw+asMjF5/4nWmq9+vlsxzp4JqprF572Ho8XICfFx5RhjJJHmEAJc05rnxstHRX", + "Fxty5fHhWPfkuAOE7GjSAXaSTRC5J+e1G8e9K7e23/v3XB8mYzrNeCbLuScJVuGMSJusFJOqAH5sandx", + "PDcq3t8wl/bv8+i4d736B9/fkcZfX1AjvM0N1Dqd373VVue372ud36RQ29w1C/DUceB/mw1BhS6Jui0b", + "V3LNl4MdfePy2SLonTZUCnMBgQUxGLL/o+2P3xXByYefXJJM1u/vHMDvhM0//OTyZNipYxXClKAWq/Xw", + "t2O49ptC9jnAuRYpefVxmPoPwHoOwObfzkAqbj7bW0iOC39YSK0spBK5VltIdi3u1kSqgmDdu43k+M1H", + "cAti8sNKug8rSWaTCQ0pYaooj7YUJGarKz7C3DJm74dKwR2Vg7a1lZRvyjUKaAHOf++BPScFGuF9G0eu", + "DsDjjJHnqQXWtuZIcRg22yPfGj/071c4378d8phZzCj8y6RLtU7pq7oJSI9JpiAosUAIgahPJIzWnrfY", + "Q0WxS5mlKRdKGrRIUIANnvxMK8A+ZMkqWKQPHRIwgCmRnSGDegH6scnl37okC4MFSTnLYR/zmVr8R1/u", + "VRWL80G30dfXsfxAo610rHvexhY6+uF0rAcTHfeiaZ1UEPk38o0BBuWY5DuZ58l99CNl081HFYFqhFU+", + "txKekUfV2oJ6exZdd0vmdX2bDtoSwK6tRvlveOIuT9KntTss2hIBUUTxlHGpaOgSd+uY5T9O6NYn9GrK", + "erk5IglXpKtIksYWhdFv2x/Di45Gb93735n66OaNDN2iUpmb7+E4KBnegB2NHN/APSSWCFLlJ1xcPrJr", + "HL2Y5emMcXhZLmEkOaIKkLTGxBXSjfSkc0vVs7cmtlKxf0O94OKyrfroqZz3CLTI8gy/QT+dHh4g7T28", + "uw4cWUbT0kxz7xrmUjnEh5QstK5zhnEWaSXTKZvOTJsInozsjwYLWu8Ki7QL7r/QtvrQwkj3fg/O2N+4", + "QjRJY6ItZBKhruEmvZrWrHYFFagsFQ+9mbDU26acbGaAHqUrvmUlJlxcuwXbgBiW5eXySs2YT9cDzOSd", + "OzQVD8LMkJmCD8RVh7hAuZAF2U5iEip0NaPhDNBmQN5DzWIAgsFpepHDy20O0EvYqWWUPeh8QxJBcax5", + "TfKYGBCZeZJcDJbRkN+fnsJHBmjG4B5fDJBDQM4PCKnfKqPH5FW9frOYOBuakwSPY7OiFwrTuDS/TYsr", + "U8D/DZkPY4aRK9sgnaCLEtzMRQPejBOor/j0wSyZTjNoq5mL4kgA4QxvEhYFTZecNPYjzWz3vRWPWqLe", + "mGHcMejN0mBe8WkOGFthZZymbdnXDhO4eJ4kK3gYbZTKD0sV8Uz9XaqICAEfW+5uYm60gUPzD4UvNaPa", + "0ll5AWdgP+9VvkFw9JJKC9VSnSjzr3mSBJ3AjqeE/HgDY2INelC9weUrZ70yJYigHzbtTcB/qsK+hP5T", + "OzlSwVsas2fmzdya5d+rPXtScnrlBm1Ov+/Loq1ov5SV7fpHky1mVhHh3HDNJ0WZ4giDQtg1Fyr5Knu2", + "kjVxm3fQG/PCd3+N6HwBP/aKqRYoEONFycZHBcgDC7lk1lgZ4NkjbpZdWao03S4KY6lG9Tfg/1kXnJEX", + "HM6rId93lMbyCB5zrqZcms2EizqKy7rwjW+ekb7ekixNtQ2H/ODNm98itWLMNFtReBrqZku4joJizFB+", + "IJxxLktsPyYzPKdc2EIh9nIw50zw/hlHjA3yvtCsemGvGS+spTuwbluEy49sHz343IaG+79wj4ovXpQc", + "V7nE7zjrFMCKJcJoLCiZoBRnkmhtKUsIMoWwbL0JgsMZCnGqMkF6Q/Z2RpAtplzyxeW196lEF9vJRQeN", + "M4ViLKbgODAPTcC3ICFPEsIiUyB9yGYEzykRmihajWPhoisJFMyfk6LOVm/I3tlAGijgi/KS3B3kKrmD", + "r+6iVKf9AqWCABMZzxOrFEUfMpGx/zQAy7rZCzfQC0SkwuOYylle0ijEEWGhF734/NsWY1//PuScqOVS", + "5g8SWnMrWfqQsTblawE3nG8jDOeRxRNz4cowtxDzK5Re2WwaVgP0z4vy7f+GW9rM1c3xgS45cxKv2sXf", + "xu1mznQ/bjjzG04uUJSZ7kq7Etj8e722zAUKyljl5tJeb9z27jIv2JOT+UYyb+uT+/PkFj6yb0QSdhoN", + "+6bSEMWkvwWRa6l6K5n7QM5B60sqecUeUATbQT2c+sRFScp9E2LYbLhcGpdljhIYbCrOfgjjujC2kTa3", + "FcbO47oUS1ISz5R10xg3yWXrnG0UwNYh8G+apFGbXUkQPrjgq96e7d3PrnHizQi8FC9ijr/3e5mQC2Fw", + "B+x97uPBvVTlu8v8gmkDPG6dXEJ0XNLj+9PTzSYpIdRKGSHUI5YQ1erbYeIpKvx6ToSgkatwfHR6bJMs", + "qEQiYz30OqFQdviSkBTqmVGeSQQAEj09P4fAsFyrtQK10AkIU2KRcsrU2lEUr97NYD7fqsLrPctJi/z7", + "3V8egxf+8QkpkB0QbGEmsNqKVFg1xrW6OE/KTFlmrW3hMc9061qyuHrwUzjbJjQmciEVSUyQ6ySLYRMB", + "NrwtHWi/M8AHHUSVRHo/dCBRPCUioVJSzuSQjclEq2EpEbpv/TnUqC/i9bzOe4VzqXlmRN+3EQuqB2Mz", + "N1QT1QABB8pVB4NgC6fpVoQVbog3tMP7giG9gOBOJBfJmMc0RDFllxJtxPTSGB1oLlGs/9hcGR06gu++", + "dmHE2+8sTekTNuHe2lGGZ3Nm/j5yZatizV0iPjqx9pKUN4uTP7DQfrEm18o1QXDcVTQhOUYLyhSN6Ucj", + "6nQjVCoamvTPAiHg/WkBEjBkp0QJ/Q4WBIU8jkmonHNlKxU83Bpm/f5umFIA09olMDgQeM2PE+jx6Owd", + "vJeQhItFZ8j0P6Dht4dn5iZ2gq2PoDRQRtQVF5foZOv1mnj5cyDTv3GUnJngylR974L/uL67OQBH4x6S", + "DVuUp6sMIJ5+92GcVoP74S14nN4CQEDKZ7MxFTgEpVjOMhXxK+b3DMx5nCX6H+aPk3U4WgqHs/fw6jej", + "7ZrhrO3GTfBRbEo7p4iY2nYPckFhCPZY40s14dwUQImpRO55T4FD9T1y99d3ypfp+A1eTVqKurqR38ze", + "uu+Tz47BwUOW6fFYtrnhNDcTxVd7n64wbfY+/Rzz8FKijCkaV7B3tN0GcNX6xwJe2F78gZoAicZIm3Y8", + "U4hcp1QA0FoNxQcRPWMJGBsioQzHWzBn0wgAJTsvFp5zCvn+YUwh45JGBKU8jgEM7mpGGNKzAUeVa6B0", + "TyttoaLyO+UrRsXRmIQ8IQ48etNnuv0DU/WCiyoS9LciF9+W6K/no6eq57kG/Lq5xy8Cwz7F1xDWHGX2", + "mtiNaOMlL340rqAOgrUZBrt9OQw6aBjsJMNAr8ARBhcqVmgfJZRlisgeOjb+LcjoPugjSULOIukwrJ0H", + "b7cvm/K7DVs2JAsfwHf3qfZYrgJSvrGd+MSDfg/p7yHBBm2UN5zdk1EHNl2EeKYggNvtK/tWRBS4Rzbv", + "/Qa2tEd+2PZtJPk/7PatyChYZS0uS0tvJHuOcrzW6+aSKmZcFuDIKMQpDqladBCOYx4W3oNM5rcD3Xwo", + "Y0HwpbahekP2JsdXtokQ6OjsXcc5zVBE5aVpwfrFeuj1nAiZjfPBIZAGxoMHi0GiIVMchTgOM0BeIpMJ", + "CSGHIaYJVbLBr5YP5S6r9RadeBbePczR1R6XM8nPE7B6BVvIGsdtmaXeEiSMMU3KTqU6cUD1hStdcPuO", + "daNcH8OT2F5vhYJLiWxTXRLTKR3H9rJG9tBbrXLghAxZGmPGiECZNHFHeujdVBApM5MYoxuAcuiGozqo", + "wAxKBVfWTRxzLqTx7GoOf3+KpCLpCjZ7Y1o+hTnfEZq9adz29EAGQ20MzceSfQXpBTGcYgiu+Ugf0w8Q", + "7GMG9NCo949l478VdDolQu8KbISsuRo129qR02z6SqZHYymX8/ytdqVc8lZL0dylSOeVmC8j9+IIFOib", + "3MB6Or+kjbBA9tHNsi9+1R+17Lsa5e8fhH30hbP8XipknpeCq9sWgCk4/LHVYimNvLJVKwkK6+EIWmck", + "3GWGQGvcgQeDG3jMKAO4knbQBCfw7TFC/36z4+67GsTj5q0KSkCl/ltDqtR6JNxvggPvBgL3gbNDbwGB", + "+03lKwGE6cPljX5TmUoVP6CrcfXdg9zeVYKSQboFGIumBCUj9WwgwUpD6b19p52ZZFv8njR4e/d8A/3d", + "kf2H1d/CZCgRy++yM7nRDreFJKlauMtFPqldAEr6EZIxfMAPeQzB3eEt3OJ6/euxh+PTxsv1H2Uf7+3+", + "vqiNf3L8+Gs9lvdc5WDZ0qdOF4twRuek2ele3cGWRKkg3ZSncLkSGYJZerizTGHRm35EtnmLVWX/hahD", + "CycRiqggoYoXBktUSwTTx18lElxbAvCci4XPmV7euS8ETw7tbNach3ZPWWdYceebLLoRVrg7d9JmhQvt", + "C27a3d22FniIMvTyZ7RBrpUw4NVooi0fRCc5Scl1SEgkgSc3ywPe7jd4NulHMpqO24xyBQz5awvzjsJM", + "Kp64tT85RhtQE2hKmF4LrepPQJNNBZ/TyNTLLog657Gh6nYDQW/qd9VKRV7QyRkXZnAPosO0OZCmH2la", + "FQsmdCEYBGPKMAxuLeB3dU+ZhCrdH6aQ1lDsHcc5wY8jzFp+G87Y0ZyojRxHRMW5gcbb/HHMPeZjrhyY", + "6s60ymnXrqJxu1jVliGkdwGYm8cx36/b+v23E15J5aOMrLSu83lukDa5zb8tFuzf3/lw3+7y9484HP8l", + "ccZ3yVUODegWfQzzioc4RhGZk5inUOzYvBt0gkzEwSCYKZUOtrZi/d6MSzV42n/aDz5/+Pz/BwAA//94", + "gu7dTX4BAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/lib/scopes/scopes.go b/lib/scopes/scopes.go index f41aafb1..2083f219 100644 --- a/lib/scopes/scopes.go +++ b/lib/scopes/scopes.go @@ -233,6 +233,8 @@ var RouteScopes = map[string]Scope{ "GET /instances/{id}": InstanceRead, "POST /instances/{id}/fork": InstanceWrite, "GET /instances/{id}/logs": InstanceRead, + "POST /instances/{id}/promote-template": InstanceWrite, + "POST /instances/{id}/demote-template": InstanceWrite, "POST /instances/{id}/restore": InstanceWrite, "DELETE /instances/{id}/snapshot-schedule": SnapshotDelete, "GET /instances/{id}/snapshot-schedule": SnapshotRead, diff --git a/openapi.yaml b/openapi.yaml index 96c6ed61..23aea061 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -45,7 +45,7 @@ components: InstanceState: type: string - enum: [Created, Initializing, Running, Paused, Shutdown, Stopped, Standby, Unknown] + enum: [Created, Initializing, Running, Paused, Shutdown, Stopped, Standby, Template, Unknown] description: | Instance state: - Created: VMM created but not started (Cloud Hypervisor native) @@ -55,6 +55,7 @@ components: - Shutdown: VM shut down but VMM exists (Cloud Hypervisor native) - Stopped: No VMM running, no snapshot exists - Standby: No VMM running, snapshot exists (can be restored) + - Template: Standby snapshot promoted to a fork-only parent; cannot wake while forks exist - Unknown: Failed to determine state (see state_error for details) VolumeMount: @@ -2327,6 +2328,84 @@ paths: schema: $ref: "#/components/schemas/Error" + /instances/{id}/promote-template: + post: + summary: Promote a standby instance into a fork-only template + operationId: promoteInstanceToTemplate + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + description: Instance ID or name + responses: + 200: + description: Instance promoted to template + content: + application/json: + schema: + $ref: "#/components/schemas/Instance" + 404: + description: Instance not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 409: + description: Conflict - instance not in standby + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /instances/{id}/demote-template: + post: + summary: Demote a template back to standby so it can be restored or deleted + operationId: demoteInstanceTemplate + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + description: Instance ID or name + responses: + 200: + description: Template demoted to standby + content: + application/json: + schema: + $ref: "#/components/schemas/Instance" + 404: + description: Instance not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 409: + description: Conflict - not a template or has live forks + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /instances/{id}/fork: post: summary: Fork an instance from stopped, standby, or running (with from_running=true) diff --git a/stainless.yaml b/stainless.yaml index de1fd3e3..e247d289 100644 --- a/stainless.yaml +++ b/stainless.yaml @@ -93,6 +93,8 @@ resources: delete: delete /instances/{id} standby: post /instances/{id}/standby restore: post /instances/{id}/restore + promote_template: post /instances/{id}/promote-template + demote_template: post /instances/{id}/demote-template fork: post /instances/{id}/fork start: post /instances/{id}/start stop: post /instances/{id}/stop