TechDocs: Preparers will take and return an etag for cache invalidation

This commit is contained in:
Himanshu Mishra
2021-02-06 09:52:01 +01:00
parent c9e4ce51e1
commit 66944ec7bf
10 changed files with 120 additions and 75 deletions
@@ -16,8 +16,8 @@
import { getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { CommonGitPreparer } from './commonGit';
import { checkoutGitRepository } from '../../helpers';
import { CommonGitPreparer } from './commonGit';
function normalizePath(path: string) {
return path
@@ -57,9 +57,9 @@ describe('commonGit preparer', () => {
'github:https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component',
});
const tempDocsPath = await preparer.prepare(mockEntity);
const { preparedDir } = await preparer.prepare(mockEntity);
expect(checkoutGitRepository).toHaveBeenCalledTimes(1);
expect(normalizePath(tempDocsPath)).toEqual(
expect(normalizePath(preparedDir)).toEqual(
'/tmp/backstage-repo/org/name/branch/plugins/techdocs-backend/examples/documented-component',
);
});
@@ -72,9 +72,9 @@ describe('commonGit preparer', () => {
'gitlab:https://gitlab.com/xesjkeee/go-logger/blob/master/catalog-info.yaml',
});
const tempDocsPath = await preparer.prepare(mockEntity);
const { preparedDir } = await preparer.prepare(mockEntity);
expect(checkoutGitRepository).toHaveBeenCalledTimes(2);
expect(normalizePath(tempDocsPath)).toEqual(
expect(normalizePath(preparedDir)).toEqual(
'/tmp/backstage-repo/org/name/branch/catalog-info.yaml',
);
});
@@ -87,9 +87,9 @@ describe('commonGit preparer', () => {
'azure/api:https://dev.azure.com/backstage-org/backstage-project/_git/template-repo?path=%2Ftemplate.yaml',
});
const tempDocsPath = await preparer.prepare(mockEntity);
const { preparedDir } = await preparer.prepare(mockEntity);
expect(checkoutGitRepository).toHaveBeenCalledTimes(3);
expect(normalizePath(tempDocsPath)).toEqual(
expect(normalizePath(preparedDir)).toEqual(
'/tmp/backstage-repo/org/name/branch/template.yaml',
);
});
@@ -13,14 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import path from 'path';
import parseGitUrl from 'git-url-parse';
import { Entity } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { PreparerBase } from './types';
import { parseReferenceAnnotation, checkoutGitRepository } from '../../helpers';
import parseGitUrl from 'git-url-parse';
import path from 'path';
import { Logger } from 'winston';
import { checkoutGitRepository, parseReferenceAnnotation } from '../../helpers';
import { PreparerBase, PreparerResponse } from './types';
export class CommonGitPreparer implements PreparerBase {
private readonly config: Config;
@@ -31,7 +30,7 @@ export class CommonGitPreparer implements PreparerBase {
this.logger = logger;
}
async prepare(entity: Entity): Promise<string> {
async prepare(entity: Entity): Promise<PreparerResponse> {
const { target } = parseReferenceAnnotation(
'backstage.io/techdocs-ref',
entity,
@@ -45,7 +44,13 @@ export class CommonGitPreparer implements PreparerBase {
);
const parsedGitLocation = parseGitUrl(target);
return path.join(repoPath, parsedGitLocation.filepath);
// TODO: Return git commit sha
const etag = '';
return {
preparedDir: path.join(repoPath, parsedGitLocation.filepath),
etag,
};
} catch (error) {
this.logger.debug(`Repo checkout failed with error ${error.message}`);
throw error;
@@ -15,8 +15,8 @@
*/
import { getVoidLogger, UrlReader } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { DirectoryPreparer } from './dir';
import { checkoutGitRepository } from '../../helpers';
import { DirectoryPreparer } from './dir';
function normalizePath(path: string) {
return path
@@ -66,9 +66,8 @@ describe('directory preparer', () => {
'backstage.io/techdocs-ref': 'dir:./our-documentation',
});
expect(normalizePath(await directoryPreparer.prepare(mockEntity))).toEqual(
'/directory/our-documentation',
);
const { preparedDir } = await directoryPreparer.prepare(mockEntity);
expect(normalizePath(preparedDir)).toEqual('/directory/our-documentation');
});
it('should merge managed-by-location and techdocs-ref when techdocs-ref is absolute', async () => {
@@ -84,9 +83,8 @@ describe('directory preparer', () => {
'backstage.io/techdocs-ref': 'dir:/our-documentation/techdocs',
});
expect(normalizePath(await directoryPreparer.prepare(mockEntity))).toEqual(
'/our-documentation/techdocs',
);
const { preparedDir } = await directoryPreparer.prepare(mockEntity);
expect(normalizePath(preparedDir)).toEqual('/our-documentation/techdocs');
});
it('should merge managed-by-location and techdocs-ref when managed-by-location is a git repository', async () => {
@@ -102,7 +100,8 @@ describe('directory preparer', () => {
'backstage.io/techdocs-ref': 'dir:./docs',
});
expect(normalizePath(await directoryPreparer.prepare(mockEntity))).toEqual(
const { preparedDir } = await directoryPreparer.prepare(mockEntity);
expect(normalizePath(preparedDir)).toEqual(
'/tmp/backstage-repo/org/name/branch/docs',
);
expect(checkoutGitRepository).toHaveBeenCalledTimes(1);
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PreparerBase } from './types';
import { InputError, UrlReader } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import path from 'path';
import { parseReferenceAnnotation, checkoutGitRepository } from '../../helpers';
import { UrlReader, InputError } from '@backstage/backend-common';
import parseGitUrl from 'git-url-parse';
import path from 'path';
import { Logger } from 'winston';
import { checkoutGitRepository, parseReferenceAnnotation } from '../../helpers';
import { PreparerBase, PreparerResponse } from './types';
export class DirectoryPreparer implements PreparerBase {
constructor(
@@ -68,7 +68,7 @@ export class DirectoryPreparer implements PreparerBase {
}
}
async prepare(entity: Entity): Promise<string> {
async prepare(entity: Entity): Promise<PreparerResponse> {
const { target } = parseReferenceAnnotation(
'backstage.io/techdocs-ref',
entity,
@@ -78,8 +78,12 @@ export class DirectoryPreparer implements PreparerBase {
entity,
);
return new Promise(resolve => {
resolve(path.resolve(managedByLocationDirectory, target));
});
// TODO: etag will be returned as a commit sha from resolveManagedByLocationToDir.
const etag = '';
return {
preparedDir: path.resolve(managedByLocationDirectory, target),
etag,
};
}
}
@@ -16,13 +16,31 @@
import type { Entity } from '@backstage/catalog-model';
import { Logger } from 'winston';
export type PreparerResponse = {
/**
* The path to directory where the tree is downloaded.
*/
preparedDir: string;
/**
* A unique identifer of the tree blob, usually the commit SHA or etag from the target.
*/
etag: string;
};
export type PreparerBase = {
/**
* Given an Entity definition from the Service Catalog, go and prepare a directory
* with contents from the location in temporary storage and return the path
* with contents from the location in temporary storage and return the path.
*
* @param entity The entity from the Service Catalog
* @param opts.etag (Optional) If etag is provider, it will be used to check if the target has
* updated since the last build.
* @throws {NotModifiedError} when the prepared directory has not been changed since the last build.
*/
prepare(entity: Entity, opts?: { logger: Logger }): Promise<string>;
prepare(
entity: Entity,
opts?: { logger: Logger; etag?: string },
): Promise<PreparerResponse>;
};
export type PreparerBuilder = {
@@ -30,10 +48,14 @@ export type PreparerBuilder = {
get(entity: Entity): PreparerBase;
};
/**
* Everything except `url` will be deprecated.
* Read more https://github.com/backstage/backstage/issues/4409
*/
export type RemoteProtocol =
| 'url'
| 'dir'
| 'github'
| 'gitlab'
| 'file'
| 'azure/api'
| 'url';
| 'azure/api';
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Logger } from 'winston';
import { Entity } from '@backstage/catalog-model';
import { UrlReader } from '@backstage/backend-common';
import { PreparerBase } from './types';
import { Entity } from '@backstage/catalog-model';
import { Logger } from 'winston';
import { getDocFilesFromRepository } from '../../helpers';
import { PreparerBase, PreparerResponse } from './types';
export class UrlPreparer implements PreparerBase {
private readonly logger: Logger;
@@ -28,9 +28,15 @@ export class UrlPreparer implements PreparerBase {
this.reader = reader;
}
async prepare(entity: Entity): Promise<string> {
async prepare(entity: Entity): Promise<PreparerResponse> {
try {
return getDocFilesFromRepository(this.reader, entity);
const preparedDir = await getDocFilesFromRepository(this.reader, entity);
// TODO: This will be actual etag from URL Reader.
const etag = '';
return {
preparedDir,
etag,
};
} catch (error) {
this.logger.debug(
`Unable to fetch files for building docs ${error.message}`,