Skip to content

Commit

Permalink
fix: port sanity check to transformer V2 (#8563)
Browse files Browse the repository at this point in the history
* fix: run schema sanity check after V2 transformer

* chore: fit and finish

* fix: placeholder resource name

* chore: address PR comments
  • Loading branch information
edwardfoyle committed Oct 26, 2021
1 parent d932f7b commit 840ce0f
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 20 deletions.
Expand Up @@ -275,11 +275,12 @@ export class GraphQLResourceManager {

private tableRecreationManagement = (currentState: DiffableProject, nextState: DiffableProject) => {
this.getTablesBeingReplaced().forEach(tableMeta => {
const ddbResource = this.getStack(tableMeta.stackName, currentState);
this.dropTable(tableMeta.tableName, ddbResource);
const ddbStack = this.getStack(tableMeta.stackName, currentState);
this.dropTemplateResources(ddbStack);

// clear any other states created by GSI updates as dropping and recreating supercedes those changes
this.clearTemplateState(tableMeta.stackName);
this.templateState.add(tableMeta.stackName, JSONUtilities.stringify(ddbResource));
this.templateState.add(tableMeta.stackName, JSONUtilities.stringify(ddbStack));
this.templateState.add(tableMeta.stackName, JSONUtilities.stringify(this.getStack(tableMeta.stackName, nextState)));
});
};
Expand All @@ -300,7 +301,7 @@ export class GraphQLResourceManager {
stackName: diff.path[1].split('.')[0] as string,
})),
) as { tableName: string; stackName: string }[];
}
};
const getAllTables = () =>
Object.entries(currentState.stacks)
.map(([name, template]) => ({
Expand Down Expand Up @@ -329,10 +330,13 @@ export class GraphQLResourceManager {
template.Resources[tableName] = removeGSI(indexName, table);
};

private dropTable = (tableName: string, template: Template): void => {
// remove table and all output refs to it
template.Resources[tableName] = undefined;
template.Outputs = _.omitBy(template.Outputs, (_, key) => key.includes(tableName));
private dropTemplateResources = (template: Template): void => {
// remove all resources from table stack except one placeholder resource
template.Resources = {};
// CloudFormation requires at least one resource so setting a placeholder
// https://stackoverflow.com/a/62991447/5283094
template.Resources.PlaceholderNullResource = { Type: 'AWS::CloudFormation::WaitConditionHandle' };
template.Outputs = {};
};

private clearTemplateState = (stackName: string) => {
Expand Down
@@ -0,0 +1,25 @@
import { DiffRule, ProjectRule, sanityCheckProject } from 'graphql-transformer-core';

export type SanityCheckRules = {
diffRules: DiffRule[];
projectRules: ProjectRule[];
};

export class GraphQLSanityCheck {
constructor(
private readonly rules: SanityCheckRules,
private readonly rootStackFileName: string,
private readonly currentCloudBackendDir: string,
private readonly localStateDir: string,
) {}

validate = async () => {
await sanityCheckProject(
this.currentCloudBackendDir,
this.localStateDir,
this.rootStackFileName,
this.rules.diffRules,
this.rules.projectRules,
);
};
}
Expand Up @@ -25,11 +25,11 @@ import {
} from '@aws-amplify/graphql-relational-transformer';
import { SearchableModelTransformer } from '@aws-amplify/graphql-searchable-transformer';
import { DefaultValueTransformer } from '@aws-amplify/graphql-default-value-transformer';
import { ProviderName as providerName } from '../constants';
import { destructiveUpdatesFlag, ProviderName as providerName } from '../constants';
import { hashDirectory } from '../upload-appsync-files';
import { mergeUserConfigWithTransformOutput, writeDeploymentToDisk } from './utils';
import { loadProject as readTransformerConfiguration } from './transform-config';
import { loadProject } from 'graphql-transformer-core';
import { getSanityCheckRules, loadProject } from 'graphql-transformer-core';
import { Template } from '@aws-amplify/graphql-transformer-core/lib/config/project-config';
import { AmplifyCLIFeatureFlagAdapter } from '../utils/amplify-cli-feature-flag-adapter';
import { JSONUtilities, stateManager, $TSContext } from 'amplify-cli-core';
Expand All @@ -43,6 +43,7 @@ import {
removeSandboxDirectiveFromSchema,
} from '../utils/sandbox-mode-helpers';
import { printer } from 'amplify-prompts';
import { GraphQLSanityCheck, SanityCheckRules } from './sanity-check';

const API_CATEGORY = 'api';
const STORAGE_CATEGORY = 'storage';
Expand Down Expand Up @@ -330,6 +331,12 @@ export async function transformGraphQLSchema(context, options) {
searchableTransformerFlag = true;
}

// construct sanityCheckRules
const ff = new AmplifyCLIFeatureFlagAdapter();
const isNewAppSyncAPI: boolean = resourcesToBeCreated.some(resource => resource.service === 'AppSync');
const allowDestructiveUpdates = context?.input?.options?.[destructiveUpdatesFlag] || context?.input?.options?.force;
const sanityCheckRules = getSanityCheckRules(isNewAppSyncAPI, ff, allowDestructiveUpdates);

const buildConfig: ProjectOptions<TransformerFactoryArgs> = {
...options,
buildParameters,
Expand All @@ -343,6 +350,7 @@ export async function transformGraphQLSchema(context, options) {
lastDeployedProjectConfig,
authConfig,
sandboxModeEnabled,
sanityCheckRules,
};

const transformerOutput = await buildAPIProject(buildConfig);
Expand Down Expand Up @@ -446,7 +454,7 @@ export type ProjectOptions<T> = {
S3DeploymentBucket: string;
S3DeploymentRootKey: string;
};
projectDirectory?: string;
projectDirectory: string;
transformersFactory: (options: T) => Promise<TransformerPluginProvider[]>;
transformersFactoryArgs: T;
rootStackFileName: 'cloudformation-template.json';
Expand All @@ -458,21 +466,19 @@ export type ProjectOptions<T> = {
authConfig?: AppSyncAuthConfiguration;
stacks: Record<string, Template>;
sandboxModeEnabled?: boolean;
sanityCheckRules: SanityCheckRules;
};

export async function buildAPIProject(opts: ProjectOptions<TransformerFactoryArgs>) {
const builtProject = await _buildProject(opts);

const buildLocation = path.join(opts.projectDirectory, 'build');
const currCloudLocation = path.join(opts.currentCloudBackendDirectory, 'build');

if (opts.projectDirectory && !opts.dryRun) {
await writeDeploymentToDisk(
builtProject,
path.join(opts.projectDirectory, 'build'),
opts.rootStackFileName,
opts.buildParameters,
opts.minify,
);
// Todo: Move sanity check to its own package. Run sanity check
// await Sanity.check(lastBuildPath, thisBuildPath, opts.rootStackFileName);
await writeDeploymentToDisk(builtProject, buildLocation, opts.rootStackFileName, opts.buildParameters, opts.minify);
const sanityChecker = new GraphQLSanityCheck(opts.sanityCheckRules, opts.rootStackFileName, currCloudLocation, buildLocation);
await sanityChecker.validate();
}

// TODO: update local env on api compile
Expand Down

0 comments on commit 840ce0f

Please sign in to comment.