export function getResourcesForHandlerFromTemplateDatum()

in src/shared/cloudformation/templateRegistry.ts [99:245]


export function getResourcesForHandlerFromTemplateDatum(
    filepath: string,
    handler: string,
    templateDatum: WatchedItem<CloudFormation.Template>
): { name: string; resourceData: CloudFormation.Resource }[] {
    const matchingResources: { name: string; resourceData: CloudFormation.Resource }[] = []
    const templateDirname = path.dirname(templateDatum.path)
    // template isn't a parent or sibling of file
    if (!isInDirectory(templateDirname, path.dirname(filepath))) {
        return []
    }

    // no resources
    const resources = templateDatum.item.Resources
    if (!resources) {
        return []
    }

    for (const key of Object.keys(resources)) {
        const resource = resources[key]
        // check if some sort of serverless function
        if (
            resource &&
            [CloudFormation.SERVERLESS_FUNCTION_TYPE, CloudFormation.LAMBDA_FUNCTION_TYPE].includes(resource.Type)
        ) {
            // parse template values that could potentially be refs
            const registeredRuntime = CloudFormation.getStringForProperty(
                resource.Properties,
                'Runtime',
                templateDatum.item
            )
            const registeredCodeUri = CloudFormation.getStringForProperty(
                resource.Properties,
                'CodeUri',
                templateDatum.item
            )
            const registeredHandler = CloudFormation.getStringForProperty(
                resource.Properties,
                'Handler',
                templateDatum.item
            )

            // properties for image type templates
            const registeredPackageType = CloudFormation.getStringForProperty(
                resource.Properties,
                'PackageType',
                templateDatum.item
            )
            const registeredDockerContext = CloudFormation.getStringForProperty(
                resource.Metadata,
                'DockerContext',
                templateDatum.item
            )
            const registeredDockerFile = CloudFormation.getStringForProperty(
                resource.Metadata,
                'Dockerfile',
                templateDatum.item
            )

            if (registeredRuntime && registeredHandler && registeredCodeUri) {
                // Java and .NET are currently special cases in that the filepath and handler aren't specific.
                // For now: check if handler matches and check if the code URI contains the filepath.
                // TODO: Can we use Omnisharp or some sort of Java tooling to help guide us better?
                if (dotNetRuntimes.includes(registeredRuntime) || javaRuntimes.includes(registeredRuntime)) {
                    if (
                        handler === registeredHandler &&
                        isInDirectory(
                            pathutils.normalize(path.join(templateDirname, registeredCodeUri)),
                            pathutils.normalize(filepath)
                        )
                    ) {
                        matchingResources.push({ name: key, resourceData: resource })
                    }
                } else if (goRuntimes.includes(registeredRuntime)) {
                    // Go is another special case. The handler merely refers to the compiled binary.
                    // We ignore checking for a handler name match, since it is not relevant
                    // See here: https://github.com/aws/aws-lambda-go
                    if (
                        isInDirectory(
                            pathutils.normalize(path.join(templateDirname, registeredCodeUri)),
                            pathutils.normalize(filepath)
                        )
                    ) {
                        matchingResources.push({ name: key, resourceData: resource })
                    }
                } else {
                    // Interpreted languages all follow the same spec:
                    // ./path/to/handler/without/file/extension.handlerName
                    // Check to ensure filename and handler both match.
                    try {
                        const parsedLambda = getLambdaDetails({
                            Handler: registeredHandler,
                            Runtime: registeredRuntime,
                        })
                        const functionName = handler.split('.').pop()
                        if (
                            pathutils.normalize(filepath) ===
                                pathutils.normalize(
                                    path.join(templateDirname, registeredCodeUri, parsedLambda.fileName)
                                ) &&
                            functionName === parsedLambda.functionName
                        ) {
                            matchingResources.push({ name: key, resourceData: resource })
                        }
                    } catch (e) {
                        getLogger().warn(
                            `Resource ${key} in template ${templateDirname} has invalid runtime for handler ${handler}: ${registeredRuntime}`
                        )
                    }
                }
                // not direct-invoke type, attempt image type
            } else if (registeredPackageType === 'Image' && registeredDockerContext && registeredDockerFile) {
                // path must be inside dockerDir
                const dockerDir = path.join(templateDirname, registeredDockerContext)
                if (isInDirectory(dockerDir, filepath)) {
                    let adjustedHandler: string = handler
                    if (!filepath.endsWith('.cs')) {
                        // reframe path to be relative to dockerDir instead of package.json
                        // omit filename and append filename + function name from handler
                        const relPath = path.relative(dockerDir, path.dirname(filepath))
                        const handlerParts = pathutils.normalizeSeparator(handler).split('/')
                        adjustedHandler = pathutils.normalizeSeparator(
                            path.join(relPath, handlerParts[handlerParts.length - 1])
                        )
                    }
                    try {
                        // open dockerfile and see if it has a handler that matches the handler represented by this file
                        const fileText = readFileSync(path.join(dockerDir, registeredDockerFile)).toString()
                        // exact match within quotes to avoid shorter paths being picked up
                        if (
                            new RegExp(`['"]${adjustedHandler}['"]`, 'g').test(pathutils.normalizeSeparator(fileText))
                        ) {
                            matchingResources.push({ name: key, resourceData: resource })
                        }
                    } catch (e) {
                        // file read error
                        getLogger().error(e as Error)
                    }
                }
            } else {
                getLogger().verbose(`Resource ${key} in template ${templateDirname} does not match handler ${handler}`)
            }
        }
    }

    return matchingResources
}