Skip to content

Commit

Permalink
feat: optional backup on storage migration
Browse files Browse the repository at this point in the history
Signed-off-by: Ariel Gentile <[email protected]>
  • Loading branch information
genaris committed Feb 6, 2024
1 parent a1b9901 commit cd7bf71
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 13 deletions.
5 changes: 3 additions & 2 deletions packages/askar/src/wallet/AskarWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
WalletNotFoundError,
KeyDerivationMethod,
WalletImportPathExistsError,
WalletExportUnsupportedError,
} from '@credo-ts/core'
// eslint-disable-next-line import/order
import { Store } from '@hyperledger/aries-askar-shared'
Expand Down Expand Up @@ -277,10 +278,10 @@ export class AskarWallet extends AskarBaseWallet {
const { path: sourcePath } = uriFromWalletConfig(this.walletConfig, this.fileSystem.dataPath)

if (isAskarWalletSqliteStorageConfig(this.walletConfig.storage) && this.walletConfig.storage?.inMemory) {
throw new WalletError('Export is not supported for in memory wallet')
throw new WalletExportUnsupportedError('Export is not supported for in memory wallet')
}
if (!sourcePath) {
throw new WalletError('Export is only supported for SQLite backend')
throw new WalletExportUnsupportedError('Export is only supported for SQLite backend')
}

try {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/agent/AgentConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export class AgentConfig {
return this.initConfig.autoUpdateStorageOnStartup ?? false
}

public get backupBeforeStorageUpdate() {
return this.initConfig.backupBeforeStorageUpdate ?? true
}

public extend(config: Partial<InitConfig>): AgentConfig {
return new AgentConfig(
{ ...this.initConfig, logger: this.logger, label: this.label, ...config },
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/agent/BaseAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export abstract class BaseAgent<AgentModules extends ModulesMap = EmptyModuleMap
const updateAssistant = new UpdateAssistant(this, DEFAULT_UPDATE_CONFIG)

await updateAssistant.initialize()
await updateAssistant.update()
await updateAssistant.update({ backupBeforeUpdate: this.agentConfig.backupBeforeStorageUpdate })
} else if (!isStorageUpToDate) {
const currentVersion = await storageUpdateService.getCurrentStorageVersion(this.agentContext)
// Close wallet to prevent un-initialized agent with initialized wallet
Expand Down
41 changes: 31 additions & 10 deletions packages/core/src/storage/migration/UpdateAssistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { FileSystem } from '../FileSystem'
import { InjectionSymbols } from '../../constants'
import { CredoError } from '../../error'
import { isFirstVersionEqualToSecond, isFirstVersionHigherThanSecond, parseVersionString } from '../../utils/version'
import { WalletExportPathExistsError } from '../../wallet/error'
import { WalletExportPathExistsError, WalletExportUnsupportedError } from '../../wallet/error'
import { WalletError } from '../../wallet/error/WalletError'

import { StorageUpdateService } from './StorageUpdateService'
Expand Down Expand Up @@ -107,8 +107,12 @@ export class UpdateAssistant<Agent extends BaseAgent<any> = BaseAgent> {
return neededUpdates
}

public async update(updateToVersion?: UpdateToVersion) {
public async update(options?: { updateToVersion?: UpdateToVersion; backupBeforeUpdate?: boolean }) {
const updateIdentifier = Date.now().toString()
const updateToVersion = options?.updateToVersion

// By default do a backup first (should be explicitly disabled in case the wallet backend does not support export)
const doBackup = options?.backupBeforeUpdate ?? true

try {
this.agent.config.logger.info(`Starting update of agent storage with updateIdentifier ${updateIdentifier}`)
Expand Down Expand Up @@ -143,7 +147,9 @@ export class UpdateAssistant<Agent extends BaseAgent<any> = BaseAgent> {
)

// Create backup in case migration goes wrong
await this.createBackup(updateIdentifier)
if (doBackup) {
await this.createBackup(updateIdentifier)
}

try {
for (const update of neededUpdates) {
Expand Down Expand Up @@ -189,17 +195,23 @@ export class UpdateAssistant<Agent extends BaseAgent<any> = BaseAgent> {
`Successfully updated agent storage from version ${update.fromVersion} to version ${update.toVersion}`
)
}
// Delete backup file, as it is not needed anymore
await this.fileSystem.delete(this.getBackupPath(updateIdentifier))
if (doBackup) {
// Delete backup file, as it is not needed anymore
await this.fileSystem.delete(this.getBackupPath(updateIdentifier))
}
} catch (error) {
this.agent.config.logger.fatal('An error occurred while updating the wallet. Restoring backup', {
this.agent.config.logger.fatal('An error occurred while updating the wallet.', {
error,
})
// In the case of an error we want to restore the backup
await this.restoreBackup(updateIdentifier)

// Delete backup file, as wallet was already restored (backup-error file will persist though)
await this.fileSystem.delete(this.getBackupPath(updateIdentifier))
if (doBackup) {
this.agent.config.logger.debug('Restoring backup.')
// In the case of an error we want to restore the backup
await this.restoreBackup(updateIdentifier)

// Delete backup file, as wallet was already restored (backup-error file will persist though)
await this.fileSystem.delete(this.getBackupPath(updateIdentifier))
}

throw error
}
Expand All @@ -215,6 +227,15 @@ export class UpdateAssistant<Agent extends BaseAgent<any> = BaseAgent> {
})
throw new StorageUpdateError(errorMessage, { cause: error })
}
// Wallet backend does not support export
if (error instanceof WalletExportUnsupportedError) {
const errorMessage = `Error updating storage with updateIdentifier ${updateIdentifier} because the wallet backend does not support exporting`
this.agent.config.logger.fatal(errorMessage, {
error,
updateIdentifier,
})
throw new StorageUpdateError(errorMessage, { cause: error })
}

this.agent.config.logger.error(`Error updating storage (updateIdentifier: ${updateIdentifier})`, {
cause: error,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export interface InitConfig {
useDidSovPrefixWhereAllowed?: boolean
connectionImageUrl?: string
autoUpdateStorageOnStartup?: boolean
backupBeforeStorageUpdate?: boolean
}

export type ProtocolVersion = `${number}.${number}`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { WalletError } from './WalletError'

export class WalletExportUnsupportedError extends WalletError {
public constructor(message: string, { cause }: { cause?: Error } = {}) {
super(message, { cause })
}
}
1 change: 1 addition & 0 deletions packages/core/src/wallet/error/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { WalletError } from './WalletError'
export { WalletKeyExistsError } from './WalletKeyExistsError'
export { WalletImportPathExistsError } from './WalletImportPathExistsError'
export { WalletExportPathExistsError } from './WalletExportPathExistsError'
export { WalletExportUnsupportedError } from './WalletExportUnsupportedError'

0 comments on commit cd7bf71

Please sign in to comment.