export async function isHotswappableAppSyncChange()

in packages/@aws-cdk/toolkit-lib/lib/api/hotswap/appsync-mapping-templates.ts [16:172]


export async function isHotswappableAppSyncChange(
  logicalId: string,
  change: ResourceChange,
  evaluateCfnTemplate: EvaluateCloudFormationTemplate,
): Promise<HotswapChange[]> {
  const isResolver = change.newValue.Type === 'AWS::AppSync::Resolver';
  const isFunction = change.newValue.Type === 'AWS::AppSync::FunctionConfiguration';
  const isGraphQLSchema = change.newValue.Type === 'AWS::AppSync::GraphQLSchema';
  const isAPIKey = change.newValue.Type === 'AWS::AppSync::ApiKey';
  if (!isResolver && !isFunction && !isGraphQLSchema && !isAPIKey) {
    return [];
  }

  const ret: HotswapChange[] = [];

  const classifiedChanges = classifyChanges(change, [
    'RequestMappingTemplate',
    'RequestMappingTemplateS3Location',
    'ResponseMappingTemplate',
    'ResponseMappingTemplateS3Location',
    'Code',
    'CodeS3Location',
    'Definition',
    'DefinitionS3Location',
    'Expires',
  ]);
  classifiedChanges.reportNonHotswappablePropertyChanges(ret);

  const namesOfHotswappableChanges = Object.keys(classifiedChanges.hotswappableProps);
  if (namesOfHotswappableChanges.length > 0) {
    let physicalName: string | undefined = undefined;
    const arn = await evaluateCfnTemplate.establishResourcePhysicalName(
      logicalId,
      isFunction ? change.newValue.Properties?.Name : undefined,
    );
    if (isResolver) {
      const arnParts = arn?.split('/');
      physicalName = arnParts ? `${arnParts[3]}.${arnParts[5]}` : undefined;
    } else {
      physicalName = arn;
    }

    // nothing do here
    if (!physicalName) {
      return ret;
    }

    ret.push({
      change: {
        cause: change,
        resources: [{
          logicalId,
          resourceType: change.newValue.Type,
          physicalName,
          metadata: evaluateCfnTemplate.metadataFor(logicalId),
        }],
      },
      hotswappable: true,
      service: 'appsync',
      apply: async (sdk: SDK) => {
        const sdkProperties: { [name: string]: any } = {
          ...change.oldValue.Properties,
          Definition: change.newValue.Properties?.Definition,
          DefinitionS3Location: change.newValue.Properties?.DefinitionS3Location,
          requestMappingTemplate: change.newValue.Properties?.RequestMappingTemplate,
          requestMappingTemplateS3Location: change.newValue.Properties?.RequestMappingTemplateS3Location,
          responseMappingTemplate: change.newValue.Properties?.ResponseMappingTemplate,
          responseMappingTemplateS3Location: change.newValue.Properties?.ResponseMappingTemplateS3Location,
          code: change.newValue.Properties?.Code,
          codeS3Location: change.newValue.Properties?.CodeS3Location,
          expires: change.newValue.Properties?.Expires,
        };
        const evaluatedResourceProperties = await evaluateCfnTemplate.evaluateCfnExpression(sdkProperties);
        const sdkRequestObject = transformObjectKeys(evaluatedResourceProperties, lowerCaseFirstCharacter);

        // resolve s3 location files as SDK doesn't take in s3 location but inline code
        if (sdkRequestObject.requestMappingTemplateS3Location) {
          sdkRequestObject.requestMappingTemplate = await fetchFileFromS3(
            sdkRequestObject.requestMappingTemplateS3Location,
            sdk,
          );
          delete sdkRequestObject.requestMappingTemplateS3Location;
        }
        if (sdkRequestObject.responseMappingTemplateS3Location) {
          sdkRequestObject.responseMappingTemplate = await fetchFileFromS3(
            sdkRequestObject.responseMappingTemplateS3Location,
            sdk,
          );
          delete sdkRequestObject.responseMappingTemplateS3Location;
        }
        if (sdkRequestObject.definitionS3Location) {
          sdkRequestObject.definition = await fetchFileFromS3(sdkRequestObject.definitionS3Location, sdk);
          delete sdkRequestObject.definitionS3Location;
        }
        if (sdkRequestObject.codeS3Location) {
          sdkRequestObject.code = await fetchFileFromS3(sdkRequestObject.codeS3Location, sdk);
          delete sdkRequestObject.codeS3Location;
        }

        if (isResolver) {
          await sdk.appsync().updateResolver(sdkRequestObject);
        } else if (isFunction) {
          // Function version is only applicable when using VTL and mapping templates
          // Runtime only applicable when using code (JS mapping templates)
          if (sdkRequestObject.code) {
            delete sdkRequestObject.functionVersion;
          } else {
            delete sdkRequestObject.runtime;
          }

          const functions = await sdk.appsync().listFunctions({ apiId: sdkRequestObject.apiId });
          const { functionId } = functions.find((fn) => fn.name === physicalName) ?? {};
          // Updating multiple functions at the same time or along with graphql schema results in `ConcurrentModificationException`
          await exponentialBackOffRetry(
            () =>
              sdk.appsync().updateFunction({
                ...sdkRequestObject,
                functionId: functionId,
              }),
            6,
            1000,
            'ConcurrentModificationException',
          );
        } else if (isGraphQLSchema) {
          let schemaCreationResponse: GetSchemaCreationStatusCommandOutput = await sdk
            .appsync()
            .startSchemaCreation(sdkRequestObject);
          while (
            schemaCreationResponse.status &&
            ['PROCESSING', 'DELETING'].some((status) => status === schemaCreationResponse.status)
          ) {
            await sleep(1000); // poll every second
            const getSchemaCreationStatusRequest: GetSchemaCreationStatusCommandInput = {
              apiId: sdkRequestObject.apiId,
            };
            schemaCreationResponse = await sdk.appsync().getSchemaCreationStatus(getSchemaCreationStatusRequest);
          }
          if (schemaCreationResponse.status === 'FAILED') {
            throw new ToolkitError(schemaCreationResponse.details ?? 'Schema creation has failed.');
          }
        } else {
          // isApiKey
          if (!sdkRequestObject.id) {
            // ApiKeyId is optional in CFN but required in SDK. Grab the KeyId from physicalArn if not available as part of CFN template
            const arnParts = physicalName?.split('/');
            if (arnParts && arnParts.length === 4) {
              sdkRequestObject.id = arnParts[3];
            }
          }
          await sdk.appsync().updateApiKey(sdkRequestObject);
        }
      },
    });
  }

  return ret;
}