簡體   English   中英

使用捆綁程序時,Lambda函數不會自動設置aws-sdk憑據

[英]Lambda function does not automatically set aws-sdk credentials when using a bundler

我有一個看起來像這樣的函數(構建后):

myFunction/
    package.json
    node_modules/
        myUtils/
            index.ts
            index.js
    index.ts
    index.js

myUtils/index.ts ,我具有:

import { SecretsManager } from 'aws-sdk' 
...
const secretsManager = new SecretsManager()

如果我壓縮myFunction/的內容並將其部署到AWS Lambda,它們將按預期工作。 但是,存檔不必要地膨脹(大約60 MB,而使用捆綁程序時則為3MB)。

所以我安裝了Parcel並運行了npx parcel build index.ts --no-source-maps ,它創建了一個名為dist/的文件夾,其中包含一個文件( index.js )。 如果我將dist/壓縮並部署到AWS Lambda,則函數調用現在會失敗,並顯示錯誤消息,指示未向SecretsManager構造函數提供區域或憑據。

使用捆綁程序時,是否需要其他步驟來使用執行角色中的憑據配置aws-sdk

若要自動配置aws-sdk的憑據,必須從默認的Lambda運行時環境加載aws-sdk

包裹捆綁器有兩種模式: parcel ... --target nodeparcel ... --target node --bundle-node-modules

  • 第一個選項不起作用,因為它忽略了所有依賴項,即使不是默認Lambda運行時環境中的依賴項也是如此。
  • 第二個選項不起作用,因為它在捆綁包中包含aws-sdk ,從而覆蓋了默認的Lambda運行時環境。

解決方案是使用動態導入來抑制aws-sdk捆綁,同時捆綁所有其他模塊。

所以這

import { SecretsManager } from 'aws-sdk' 
...
const secretsManager = new SecretsManager()

變成

const awsSdk = import('aws-sdk')
...
const { SecretsManager } = await awsSdk
const secretsManager = new SecretsManager()

CLI參數為:

parcel build index.ts --no-source-maps --target node --bundle-node-modules

編輯2019年10月:

上面的解決方案有效,但是我一直是下面描述的基於Lambda層的解決方案,我認為它是優越的。 為了處理monorepos(我正在使用Rush),構建腳本有點笨拙,可能需要修改才能在項目中使用它。 如果您不使用monorepo,則可以忽略將內部和外部依賴項拆分的部分。

這樣做可能是更好的方法,因此歡迎提出批評。

// @my-scope/my-layer/package.json
{
  "name": "@my-scope/my-layer",
  "scripts": {
    "build": "build-layer"
  },
  "dependencies": {
    "uuid": "^3.3.3",
    "axios": "~0.19.0",
    "date-fns": "~2.4.1"
  },
  "devDependencies": {
    "@my-scope/build-scripts": "0.0.0"
  }
}
// @my-scope/build-scripts/package.json
{
  "name": "@meal-planner/tool-build-scripts",
  "files": [
    "out/**/*"
  ],
  "main": "out/lib/index.js",
  "bin": {
    "build-layer": "out/bin/build-layer.js",
  },
  "scripts": {
    "build": "tsc"
  },
  "dependencies": {
    "jsonc-parser": "~2.1.1",
    "fs-extra": "~8.1.0",
    "del": "~5.1.0"
  },
  "devDependencies": {
    "@types/node": "~12.11.1",
    "@types/fs-extra": "~8.0.1",
    "typescript": "~3.6.4",
  }
}

// @my-scope/build-scripts/bin/build-layer.ts

#!/usr/bin/env node
import * as jsonc from 'jsonc-parser'
import * as fs from 'fs'
import * as fse from 'fs-extra'
import * as path from 'path'
import * as util from 'util'
import * as del from 'del'

import { spawnSyncCore } from '../lib'

const access = util.promisify(fs.access)
const readFile = util.promisify(fs.readFile)
const writeFile = util.promisify(fs.writeFile)
const mkdir = util.promisify(fs.mkdir)

const BUILD_DIR = `dist`
const TARGET_PATH = path.join(BUILD_DIR, `nodejs`)
const MANIFEST_NAME = `package.json`
const MANIFEST_LOCK_NAME = `package-lock.json`
const INTERNAL_SCOPE = `@my-scope/`

interface IMinimalPackageManifest {
    description: string
    repository: unknown
    license: string
    dependencies: {
        [packageName: string]: string | undefined
    }
}

async function buildLayer(_argv: readonly string[] = process.argv): Promise<void> {
    const {
        description,
        repository,
        license,
        dependencies
    } = await readManifest(MANIFEST_NAME)

    const { internal, external } = splitDependencies(dependencies)
    const targetManifestPath = path.join(TARGET_PATH, MANIFEST_NAME)
    const targetManifestLockPath = path.join(TARGET_PATH, MANIFEST_LOCK_NAME)

    await writeManifest(
        targetManifestPath,
        {
            description,
            repository,
            license,
            dependencies: external
        }
    )

    installExternalDependencies(TARGET_PATH)
    await installInternalDependencies(internal, TARGET_PATH)

    del.sync(targetManifestPath)
    del.sync(targetManifestLockPath)
}

async function readManifest(sourcePath: string): Promise<IMinimalPackageManifest> {
    const raw = (await readFile(sourcePath)).toString()

    return jsonc.parse(raw)
}

async function writeManifest(targetPath: string, manifest: IMinimalPackageManifest): Promise<void> {
    const targetDir = path.dirname(targetPath)
    try {
        await access(targetDir)
    } catch {
        await mkdir(targetDir, {
            recursive: true
        })
    }

    const raw = JSON.stringify(manifest)

    await writeFile(targetPath, raw)
}

interface IDependencyMap {
    [key: string]: string | undefined
}

interface IDepedencyGroups {
    internal: IDependencyMap
    external: IDependencyMap
}

function splitDependencies(dependencies: IDependencyMap): IDepedencyGroups {
    return Object.keys(dependencies).reduce<IDepedencyGroups>(
        (groups, name) => {
            if (name.startsWith(INTERNAL_SCOPE)) {
                groups.internal[name] = dependencies[name]
            } else {
                groups.external[name] = dependencies[name]
            }

            return groups
        },
        {
            internal: {},
            external: {}
        }
    )
}

function installExternalDependencies(targetDir: string): void {
    spawnSyncCore({
        command: `npm`,
        cwd: targetDir,
        env: {
            ...process.env,
            NODE_ENV: `production`
        },
        args: [
            `install`
        ],
    })
}

async function installInternalDependencies(dependencies: IDependencyMap, targetDir: string): Promise<void> {
    const sourcePaths = Object.keys(dependencies)
        .map(dependency => path.join(`node_modules`, dependency))

    for (const sourcePath of sourcePaths) {
        const targetPath = path.join(targetDir, sourcePath)
        const sourceManifestPath = path.join(sourcePath, MANIFEST_NAME)
        const sourceDistPath = path.join(sourcePath, BUILD_DIR)

        await fse.copy(sourcePath, targetPath, {
            dereference: true,
            recursive: true,
            filter: (src, _dest) => {
                // Only copy package.json and dist/ folder.
                return src === sourcePath || src === sourceManifestPath || src.startsWith(sourceDistPath)
            }
        })
    }
}

/* eslint-disable @typescript-eslint/no-floating-promises */
buildLayer()
/* eslint-enable @typescript-eslint/no-floating-promises */
// @my-scope/build-scripts/lib/index.ts
import { spawnSync } from 'child_process'
const TAB_WIDTH = 4

export interface ISpawnSyncCoreOptions {
    command: string
    cwd?: string
    env?: NodeJS.ProcessEnv
    args?: readonly string[]
}

export function spawnSyncCore({
    command,
    cwd,
    env,
    args
}: ISpawnSyncCoreOptions): void {
    const result = spawnSync(command, args, {
        shell: true,
        cwd,
        env
    })

    if (result.error) {
        throw result.error
    }
    if (result.status !== 0) {
        throw new Error(`${command} returned a non-zero exit code: ${JSON.stringify({
            stdout: result.stdout.toString(),
            stderr: result.stderr.toString(),
            status: result.status,
            signal: result.signal
        }, undefined, TAB_WIDTH)}`)
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM