test(auth): gate local and OIDC authentication release
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/lifecycle"
|
||||
)
|
||||
|
||||
var ErrRestoreConfirmationRequired = errors.New("restore requires --yes")
|
||||
@@ -29,19 +30,17 @@ type RestoreResult struct {
|
||||
Verified bool
|
||||
}
|
||||
|
||||
type restoreLock interface{ Release() error }
|
||||
type restoreVerify func(context.Context, config.Installation, archiveRunner) error
|
||||
|
||||
type restoreDependencies struct {
|
||||
preflight func(context.Context, config.Installation, PreflightRequest) (PreflightResult, error)
|
||||
// checkpointLocked creates the secret-aware recovery archive while the caller already owns
|
||||
// the installation lifecycle lock. It must not call public Create, which would re-acquire the
|
||||
// non-reentrant lock and deadlock the restore transaction.
|
||||
checkpointLocked func(context.Context, config.Installation, CreateRequest) (Result, error)
|
||||
// checkpoint requires the opaque capability created by lifecycle acquisition. It must not call
|
||||
// public Create, which would re-acquire the non-reentrant lock and deadlock the transaction.
|
||||
checkpoint func(context.Context, *lifecycle.Transaction, config.Installation, CreateRequest) (Result, error)
|
||||
prepareRecovery func(context.Context, config.Installation, string) (PreflightResult, error)
|
||||
recover func(context.Context, config.Installation, PreflightResult, bool) error
|
||||
cleanupCheckpoint func(string) error
|
||||
acquireLock func(config.Installation) (restoreLock, error)
|
||||
acquireTransaction func(config.Installation) (*lifecycle.Transaction, error)
|
||||
runner archiveRunner
|
||||
sleep func(duration time.Duration)
|
||||
restoreFile func(context.Context, config.Installation, ArchiveEntryMetadata, io.Reader) error
|
||||
@@ -84,36 +83,32 @@ func restoreWithDependencies(ctx context.Context, installation config.Installati
|
||||
if request.Archive == "" {
|
||||
return RestoreResult{}, errors.New("restore archive is required")
|
||||
}
|
||||
if deps.preflight == nil || deps.checkpointLocked == nil || deps.prepareRecovery == nil || deps.recover == nil || deps.cleanupCheckpoint == nil || deps.acquireLock == nil || deps.runner == nil || deps.restoreFile == nil || deps.restoreVolume == nil || deps.resetAuthenticationState == nil || deps.verify == nil {
|
||||
if deps.preflight == nil || deps.checkpoint == nil || deps.prepareRecovery == nil || deps.recover == nil || deps.cleanupCheckpoint == nil || deps.acquireTransaction == nil || deps.runner == nil || deps.restoreFile == nil || deps.restoreVolume == nil || deps.resetAuthenticationState == nil || deps.verify == nil {
|
||||
return RestoreResult{}, errors.New("restore dependencies are incomplete")
|
||||
}
|
||||
preflight, err := deps.preflight(ctx, installation, PreflightRequest{Archive: request.Archive, Confirm: true, AllowExternalSecrets: true})
|
||||
if err != nil {
|
||||
return RestoreResult{}, err
|
||||
}
|
||||
archive, err := preflight.RevalidateArchive()
|
||||
if err != nil {
|
||||
_ = preflight.CloseArchive()
|
||||
return RestoreResult{}, err
|
||||
}
|
||||
|
||||
// Lock ordering is lifecycle lock -> Compose/operator maintenance barrier. The core operator
|
||||
// command never acquires the host lifecycle lock, so this order cannot form a lock cycle with
|
||||
// Docker Compose or the durable maintenance marker.
|
||||
lock, err := deps.acquireLock(installation)
|
||||
transaction, err := deps.acquireTransaction(installation)
|
||||
if err != nil {
|
||||
_ = preflight.CloseArchive()
|
||||
return result, err
|
||||
}
|
||||
defer func() {
|
||||
if releaseErr := lock.Release(); releaseErr != nil {
|
||||
if releaseErr := transaction.Release(); releaseErr != nil {
|
||||
result = RestoreResult{}
|
||||
resultErr = errors.Join(resultErr, fmt.Errorf("release restore lifecycle lock: %w", releaseErr))
|
||||
}
|
||||
}()
|
||||
// Target-dependent preflight is intentionally inside the lifecycle transaction. This binds
|
||||
// target ownership, volume, image, and free-space checks to the later mutation.
|
||||
preflight, err := deps.preflight(ctx, installation, PreflightRequest{Archive: request.Archive, Confirm: true, AllowExternalSecrets: true})
|
||||
if err != nil {
|
||||
return RestoreResult{}, err
|
||||
}
|
||||
defer preflight.CloseArchive()
|
||||
|
||||
checkpoint, err := deps.checkpointLocked(ctx, installation, CreateRequest{IncludeSecrets: true, Confirm: true})
|
||||
checkpoint, err := deps.checkpoint(ctx, transaction, installation, CreateRequest{IncludeSecrets: true, Confirm: true})
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("create recovery checkpoint: %w", err)
|
||||
}
|
||||
@@ -217,8 +212,18 @@ func restoreWithDependencies(ctx context.Context, installation config.Installati
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
staged, err := preflight.StageArchive(ctx)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := staged.Close(); closeErr != nil {
|
||||
result = RestoreResult{}
|
||||
resultErr = errors.Join(resultErr, closeErr)
|
||||
}
|
||||
}()
|
||||
state.mutated = true
|
||||
if err := restoreVerifiedEntries(ctx, installation, preflight, archive, deps.restoreFile, deps.restoreVolume); err != nil {
|
||||
if err := restoreVerifiedEntries(ctx, installation, preflight, staged.file, deps.restoreFile, deps.restoreVolume); err != nil {
|
||||
return result, err
|
||||
}
|
||||
if err := deps.resetAuthenticationState(ctx, installation, deps.runner); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user