diff --git a/lib/util/cache/repository/common.ts b/lib/util/cache/repository/common.ts index b35b57d7a635fa7929a20fd824539cc21fd37719..2846c28ceea852e321f175a3bc0d57842e2015d6 100644 --- a/lib/util/cache/repository/common.ts +++ b/lib/util/cache/repository/common.ts @@ -1,70 +1,2 @@ -import is from '@sindresorhus/is'; -import type { - RepoCacheRecordV10, - RepoCacheRecordV11, - RepoCacheRecordV12, - RepoCacheRecordV13, -} from './types'; - // Increment this whenever there could be incompatibilities between old and new cache structure export const CACHE_REVISION = 13; - -export function isValidRev10( - input: unknown, - repo?: string -): input is RepoCacheRecordV10 { - return ( - is.plainObject(input) && - is.safeInteger(input.revision) && - input.revision === 10 && - is.string(input.repository) && - (!repo || repo === input.repository) - ); -} - -export function isValidRev11( - input: unknown, - repo?: string -): input is RepoCacheRecordV11 { - return ( - is.plainObject(input) && - is.safeInteger(input.revision) && - input.revision === 11 && - is.string(input.repository) && - is.plainObject(input.data) && - (!repo || repo === input.repository) - ); -} - -function isValidRev12Shape( - input: unknown, - repo?: string -): input is RepoCacheRecordV12 { - return ( - is.plainObject(input) && - is.safeInteger(input.revision) && - is.string(input.repository) && - is.string(input.payload) && - is.string(input.hash) - ); -} - -export function isValidRev12( - input: unknown, - repo?: string -): input is RepoCacheRecordV12 { - return ( - isValidRev12Shape(input, repo) && - input.revision === 12 && - (!repo || repo === input.repository) - ); -} - -export function isValidRev13(input: unknown): input is RepoCacheRecordV13 { - return ( - is.plainObject(input) && - is.string(input.fingerprint) && - isValidRev12Shape(input) && - input.revision === 13 - ); -} diff --git a/lib/util/cache/repository/impl/base.ts b/lib/util/cache/repository/impl/base.ts index 1b4c080a44b3f5e19123d88b5f41349ad1c15c1c..f234f11d20525341265abf0f4ca901c598fe513c 100644 --- a/lib/util/cache/repository/impl/base.ts +++ b/lib/util/cache/repository/impl/base.ts @@ -4,23 +4,11 @@ import is from '@sindresorhus/is'; import hasha from 'hasha'; import { GlobalConfig } from '../../../../config/global'; import { logger } from '../../../../logger'; +import * as schema from '../../../schema'; import { safeStringify } from '../../../stringify'; -import { - CACHE_REVISION, - isValidRev10, - isValidRev11, - isValidRev12, - isValidRev13, -} from '../common'; -import type { - RepoCache, - RepoCacheData, - RepoCacheRecord, - RepoCacheRecordV10, - RepoCacheRecordV11, - RepoCacheRecordV12, - RepoCacheRecordV13, -} from '../types'; +import { CACHE_REVISION } from '../common'; +import { RepoCacheRecord, RepoCacheV13 } from '../schemas'; +import type { RepoCache, RepoCacheData } from '../types'; const compress = promisify(zlib.brotliCompress); const decompress = promisify(zlib.brotliDecompress); @@ -39,17 +27,11 @@ export abstract class RepoCacheBase implements RepoCache { protected abstract write(data: RepoCacheRecord): Promise<void>; - private restoreFromRev10(oldCache: RepoCacheRecordV10): void { - delete oldCache.repository; - delete oldCache.revision; - this.data = oldCache; - } - - private restoreFromRev11(oldCache: RepoCacheRecordV11): void { - this.data = oldCache.data; - } - - private async restoreFromRev12(oldCache: RepoCacheRecordV12): Promise<void> { + private async restore(oldCache: RepoCacheRecord): Promise<void> { + if (oldCache.fingerprint !== this.fingerprint) { + logger.debug('Repository cache fingerprint is invalid'); + return; + } const compressed = Buffer.from(oldCache.payload, 'base64'); const uncompressed = await decompress(compressed); const jsonStr = uncompressed.toString('utf8'); @@ -57,14 +39,6 @@ export abstract class RepoCacheBase implements RepoCache { this.oldHash = oldCache.hash; } - private async restoreFromRev13(oldCache: RepoCacheRecordV13): Promise<void> { - if (oldCache.fingerprint !== this.fingerprint) { - logger.debug('Repository cache fingerprint is invalid'); - return; - } - await this.restoreFromRev12(oldCache); - } - async load(): Promise<void> { try { const rawOldCache = await this.read(); @@ -76,30 +50,12 @@ export abstract class RepoCacheBase implements RepoCache { } const oldCache = JSON.parse(rawOldCache) as unknown; - if (isValidRev13(oldCache)) { - await this.restoreFromRev13(oldCache); + if (schema.match(RepoCacheV13, oldCache)) { + await this.restore(oldCache); logger.debug('Repository cache is restored from revision 13'); return; } - if (isValidRev12(oldCache, this.repository)) { - await this.restoreFromRev12(oldCache); - logger.debug('Repository cache is restored from revision 12'); - return; - } - - if (isValidRev11(oldCache, this.repository)) { - this.restoreFromRev11(oldCache); - logger.debug('Repository cache is restored from revision 11'); - return; - } - - if (isValidRev10(oldCache, this.repository)) { - this.restoreFromRev10(oldCache); - logger.debug('Repository cache is restored from revision 10'); - return; - } - logger.debug('Repository cache is invalid'); } catch (err) { logger.debug({ err }, 'Error reading repository cache'); diff --git a/lib/util/cache/repository/impl/local.spec.ts b/lib/util/cache/repository/impl/local.spec.ts index 7f0e7a9496b364633fbd862afef724744a27b0b5..1b17bf89aac20028ad529f2c04b92c2dd5b2b00d 100644 --- a/lib/util/cache/repository/impl/local.spec.ts +++ b/lib/util/cache/repository/impl/local.spec.ts @@ -5,7 +5,8 @@ import { fs } from '../../../../../test/util'; import { GlobalConfig } from '../../../../config/global'; import { logger } from '../../../../logger'; import { CACHE_REVISION } from '../common'; -import type { RepoCacheData, RepoCacheRecord } from '../types'; +import type { RepoCacheRecord } from '../schemas'; +import type { RepoCacheData } from '../types'; import { CacheFactory } from './cache-factory'; import { RepoCacheLocal } from './local'; @@ -109,102 +110,6 @@ describe('util/cache/repository/impl/local', () => { expect(cache2.getData()).toBeEmpty(); }); - it('migrates revision from 10 to 13', async () => { - fs.readCacheFile.mockResolvedValue( - JSON.stringify({ - revision: 10, - repository: 'some/repo', - semanticCommits: 'enabled', - }) - ); - const localRepoCache = CacheFactory.get( - 'some/repo', - '0123456789abcdef', - 'local' - ); - - await localRepoCache.load(); - await localRepoCache.save(); - - const cacheRecord = await createCacheRecord({ semanticCommits: 'enabled' }); - expect(fs.outputCacheFile).toHaveBeenCalledWith( - '/tmp/cache/renovate/repository/github/some/repo.json', - JSON.stringify(cacheRecord) - ); - }); - - it('migrates revision from 11 to 13', async () => { - fs.readCacheFile.mockResolvedValue( - JSON.stringify({ - revision: 11, - repository: 'some/repo', - data: { semanticCommits: 'enabled' }, - }) - ); - const localRepoCache = CacheFactory.get( - 'some/repo', - '0123456789abcdef', - 'local' - ); - - await localRepoCache.load(); - await localRepoCache.save(); - - const cacheRecord = await createCacheRecord({ semanticCommits: 'enabled' }); - expect(fs.outputCacheFile).toHaveBeenCalledWith( - '/tmp/cache/renovate/repository/github/some/repo.json', - JSON.stringify(cacheRecord) - ); - }); - - it('migrates revision from 12 to 13', async () => { - const { repository, payload, hash } = await createCacheRecord({ - semanticCommits: 'enabled', - }); - - fs.readCacheFile.mockResolvedValue( - JSON.stringify({ revision: 12, repository, payload, hash }) - ); - const localRepoCache = CacheFactory.get( - 'some/repo', - '0123456789abcdef', - 'local' - ); - - await localRepoCache.load(); - const data = localRepoCache.getData(); - data.semanticCommits = 'disabled'; - await localRepoCache.save(); - - expect(fs.outputCacheFile).toHaveBeenCalledWith( - '/tmp/cache/renovate/repository/github/some/repo.json', - JSON.stringify( - await createCacheRecord({ - semanticCommits: 'disabled', - }) - ) - ); - }); - - it('does not migrate from older revisions to 11', async () => { - fs.readCacheFile.mockResolvedValueOnce( - JSON.stringify({ - revision: 9, - repository: 'some/repo', - semanticCommits: 'enabled', - }) - ); - - const localRepoCache = CacheFactory.get( - 'some/repo', - '0123456789abcdef', - 'local' - ); - await localRepoCache.load(); - - expect(localRepoCache.getData()).toBeEmpty(); - }); - it('handles invalid data', async () => { fs.readCacheFile.mockResolvedValue(JSON.stringify({ foo: 'bar' })); const localRepoCache = CacheFactory.get( diff --git a/lib/util/cache/repository/impl/local.ts b/lib/util/cache/repository/impl/local.ts index 306b891c4cf44885cc74e38cb80bfdb6ed5cf709..ffed379566bf62765690feb383976bd9d115a130 100644 --- a/lib/util/cache/repository/impl/local.ts +++ b/lib/util/cache/repository/impl/local.ts @@ -2,7 +2,7 @@ import upath from 'upath'; import { GlobalConfig } from '../../../../config/global'; import { logger } from '../../../../logger'; import { cachePathExists, outputCacheFile, readCacheFile } from '../../../fs'; -import type { RepoCacheRecord } from '../types'; +import type { RepoCacheRecord } from '../schemas'; import { RepoCacheBase } from './base'; export class RepoCacheLocal extends RepoCacheBase { diff --git a/lib/util/cache/repository/impl/s3.spec.ts b/lib/util/cache/repository/impl/s3.spec.ts index 48b894d5ddc3f7ddea91a89cf2f20f949889f7be..cfb276a6f38b1323a5acfe9d04380b2dfa6e4052 100644 --- a/lib/util/cache/repository/impl/s3.spec.ts +++ b/lib/util/cache/repository/impl/s3.spec.ts @@ -12,7 +12,7 @@ import { partial } from '../../../../../test/util'; import { GlobalConfig } from '../../../../config/global'; import { logger } from '../../../../logger'; import { parseS3Url } from '../../../s3'; -import type { RepoCacheRecord } from '../types'; +import type { RepoCacheRecord } from '../schemas'; import { CacheFactory } from './cache-factory'; import { RepoCacheS3 } from './s3'; diff --git a/lib/util/cache/repository/impl/s3.ts b/lib/util/cache/repository/impl/s3.ts index c1d8f02c7fa40c66c3dd5c1d2aa0c33f4abe25b3..da57ba094a7a168ec74c60631939b7293642db89 100644 --- a/lib/util/cache/repository/impl/s3.ts +++ b/lib/util/cache/repository/impl/s3.ts @@ -8,7 +8,7 @@ import { import { logger } from '../../../../logger'; import { getS3Client, parseS3Url } from '../../../s3'; import { streamToString } from '../../../streams'; -import type { RepoCacheRecord } from '../types'; +import type { RepoCacheRecord } from '../schemas'; import { RepoCacheBase } from './base'; export class RepoCacheS3 extends RepoCacheBase { diff --git a/lib/util/cache/repository/schemas.ts b/lib/util/cache/repository/schemas.ts new file mode 100644 index 0000000000000000000000000000000000000000..61af6926e069de5623990f67809e001147bc04bd --- /dev/null +++ b/lib/util/cache/repository/schemas.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; + +export const RepoCacheV13 = z + .object({ + repository: z.string().min(1), + revision: z.number().refine((v) => v === 13), + payload: z.string().min(1), + hash: z.string().min(1), + fingerprint: z.string().min(1), + }) + .strict(); + +export type RepoCacheRecord = z.infer<typeof RepoCacheV13>; diff --git a/lib/util/cache/repository/types.ts b/lib/util/cache/repository/types.ts index e11a5c69315f39762e3acc27ced03ef4bf0ffebc..64d8a1d63fe4a997d797a3da80b4513e81fc73e2 100644 --- a/lib/util/cache/repository/types.ts +++ b/lib/util/cache/repository/types.ts @@ -75,30 +75,6 @@ export interface RepoCacheData { prComments?: Record<number, Record<string, string>>; } -export interface RepoCacheRecordV10 extends RepoCacheData { - repository?: string; - revision?: number; -} - -export interface RepoCacheRecordV11 { - repository: string; - revision: number; - data: RepoCacheData; -} - -export interface RepoCacheRecordV12 { - repository: string; - revision: number; - payload: string; - hash: string; -} - -export interface RepoCacheRecordV13 extends RepoCacheRecordV12 { - fingerprint: string; -} - -export type RepoCacheRecord = RepoCacheRecordV13; - export interface RepoCache { load(): Promise<void>; save(): Promise<void>;