Skip to content

Commit

Permalink
cdk support for No samconfig.toml found. Please make sure you have de…
Browse files Browse the repository at this point in the history
…ployed your functions before running this command. You can deploy your functions by running 'sam deploy --guided'
  • Loading branch information
ljacobsson committed Jul 16, 2023
1 parent 438b8a9 commit 0c7e43e
Show file tree
Hide file tree
Showing 20 changed files with 2,741 additions and 22 deletions.
609 changes: 604 additions & 5 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@
"@aws-sdk/client-schemas": "^3.358.0",
"@aws-sdk/client-sfn": "^3.359.0",
"@aws-sdk/credential-provider-sso": "^3.319.0",
"@aws-sdk/client-iot": "^3.345.0",
"@aws-sdk/client-sts": "^3.348.0",
"@mhlabs/iam-policies-cli": "^1.0.5",
"@octokit/rest": "^18.5.2",
"aws-cdk-lib": "^2.87.0",
"cli-spinner": "^0.2.10",
"clipboardy": "^2.2.0",
"commander": "^7.2.0",
Expand All @@ -48,7 +51,6 @@
"inquirer-autocomplete-prompt": "^1.3.0",
"inquirer-file-tree-selection-prompt": "^1.0.13",
"jsonpath": "^1.1.1",
"lambda-debug": "^1.0.25",
"link2aws": "^1.0.13",
"lodash": "^4.17.21",
"nodemon": "^2.0.22",
Expand All @@ -57,7 +59,10 @@
"openai": "^3.3.0",
"rimraf": "^5.0.1",
"tsc-watch": "^6.0.4",
"yaml-cfn": "^0.3.0"
"archiver": "^5.3.1",
"getmac": "^5.20.0",
"mqtt": "^4.3.7",
"yaml-cfn": "^0.3.2"
},
"devDependencies": {
"jest": "^26.6.3"
Expand Down
47 changes: 47 additions & 0 deletions src/commands/local/cdk-construct-finder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const fs = require('fs');
const path = require('path');

function findFilesWithExtension(rootDir, fileExtension) {
let files = [];

function traverseDir(currentDir) {
const items = fs.readdirSync(currentDir);

items.forEach((item) => {
const itemPath = path.join(currentDir, item);
const itemStats = fs.statSync(itemPath);

if (itemStats.isDirectory()) {
traverseDir(itemPath);
} else if (path.extname(itemPath) === fileExtension && !itemPath.includes('node_modules')) {
files.push(itemPath);
}
});
}

traverseDir(rootDir);
return files;
}

function findConstructs() {
const rootDir = '.';
const fileExtension = '.ts';
const searchString = 'extends cdk.Stack';
const files = findFilesWithExtension(rootDir, fileExtension);
const matchedFiles = [];

files.forEach((file) => {
const fileContent = fs.readFileSync(file, 'utf8');

if (fileContent.includes(searchString)) {
matchedFiles.push(file);
}
});

return matchedFiles;
}


module.exports = {
findConstructs
};
64 changes: 64 additions & 0 deletions src/commands/local/cdk-wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
console.log("in wrapper")
const cdk = require('aws-cdk-lib');
const fs = require('fs');
const { yamlDump } = require('yaml-cfn');
let stackFile = fs.readFileSync(`${process.cwd()}/${process.argv[2]}`, 'utf8');
stackFile = stackFile.replace(/\.ts"/g, '.js"');
fs.writeFileSync(`${process.cwd()}/${process.argv[2]}`, stackFile);
const TargetStack = require(`${process.cwd()}/${process.argv[2]}`);
const stackName = Object.keys(TargetStack)[0];
process.env.SAMP_STACKNAME = stackName;
const templatePath = `${process.cwd()}/cdk.out/${stackName}.template.json`;
const synthedTemplate = JSON.parse(fs.readFileSync(templatePath, 'utf8'));

console.log("TargetStack: ", stackName);
const app = new cdk.App();
const stack = new TargetStack[stackName](null, stackName, {});

const resources = stack.node._children;

const mockTemplate = {
AWSTemplateFormatVersion: "2010-09-09",
Transform: ['AWS::Serverless-2016-10-31'], Resources: {}
};
for (const key of Object.keys(resources)) {
const resource = resources[key];
const entry = resource.node._children.Code?.node?._children?.Stage?.fingerprintOptions?.bundling?.relativeEntryPath;
let logicalId = null;
let handler
if (entry) {
for (const fn of Object.keys(synthedTemplate.Resources)) {
const resource = synthedTemplate.Resources[fn];
if (resource.Type === "AWS::Lambda::Function") {
if (resource.Metadata?.['aws:cdk:path'].endsWith(`/${key}/Resource`)) {
logicalId = fn;
handler = resource.Properties.Handler;
break;
}
}
}

mockTemplate.Resources[logicalId] = {
Type: "AWS::Serverless::Function",
Properties: {
CodeUri: ".",
Handler: `${entry.substring(0, entry.lastIndexOf("/"))}/${handler}`,
}
}
}
}

fs.writeFileSync(".samp-out/mock-template.yaml", yamlDump(mockTemplate));
fs.writeFileSync(".samp-out/stack-dump.json", JSON.stringify(stack.node._children, getCircularReplacer(), 2));
function getCircularReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return "[Circular Reference]";
}
seen.add(value);
}
return value;
};
}
2 changes: 2 additions & 0 deletions src/commands/local/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ program
.option("--force-restore", "Force restore of the original Lambda code", false)
.option("--merge-package-jsons", "For projects that use one project.json per function subfolder, this merges them into one to enable package resolution whilsh running this command", false)
.option("-f, --functions [functions]", "Select which functions to be part of the debugging session. Comma separated list of logical IDs.", "ALL")
.option("-c --construct [construct]", "CDK only. Relative path to where the stack constructs are found. Default: ./lib/<the value of --stack-name>.ts")
.option("-d, --debug", "Configure debug for vscode. This only needs to be run once per project", false)
.option("-p, --profile [profile]", "AWS profile to use")
.option("--region [region]", "The AWS region to use. Falls back on AWS_REGION environment variable if not specified")
.action(async (cmd) => {
cmd.construct = cmd.construct || `./lib/${cmd.stackName}.ts`;
await local.run(cmd);
});
20 changes: 20 additions & 0 deletions src/commands/local/lib/AmazonRootCA1.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL
MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj
ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM
9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw
IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6
VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L
93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm
jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA
A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI
U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs
N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv
o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU
5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy
rqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----
92 changes: 92 additions & 0 deletions src/commands/local/lib/cleanup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import fs from 'fs';
import { GetTemplateCommand, CloudFormationClient, ListStackResourcesCommand } from "@aws-sdk/client-cloudformation"
import { LambdaClient, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand } from "@aws-sdk/client-lambda"
import ini from 'ini';
import { fromSSO } from '@aws-sdk/credential-provider-sso';
import { type } from 'os';

console.log("Cleaning up...");
let configEnv = 'default';
let functions = undefined;
const cachePath = process.cwd() + "/.lambda-debug";
let conf;
if (fs.existsSync(cachePath)) {
conf = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
configEnv = conf.configEnv || 'default';
if (conf.functions) {
functions = conf.functions;
}
fs.unlinkSync(cachePath);
}

if (fs.existsSync(process.cwd() + "/.samp-out")) {
console.log("Removing .samp-out directory");
fs.rmSync(process.cwd() + "/.samp-out", { recursive: true, force: true });
}

const stackName = conf.envConfig.stack_name;
const region = conf.envConfig.region;
const profile = conf.envConfig.profile;

const cfnClient = new CloudFormationClient({ region, credentials: fromSSO({ profile }) });
const lambdaClient = new LambdaClient({ region, credentials: fromSSO({ profile }) });
const templateResponse = await cfnClient.send(new GetTemplateCommand({ StackName: stackName, TemplateStage: "Processed" }));
const stack = await cfnClient.send(new ListStackResourcesCommand({ StackName: stackName }));

const template = JSON.parse(templateResponse.TemplateBody);

functions = functions || Object.keys(template.Resources).filter(key => template.Resources[key].Type === "AWS::Lambda::Function");;

const updatePromises = functions.map(async functionName => {
let updated = false;
do {
try {
const func = template.Resources[functionName];
const physicalId = stack.StackResourceSummaries.find(resource => resource.LogicalResourceId === functionName).PhysicalResourceId;
console.log(`Restoring function: ${functionName}`);

await lambdaClient.send(new UpdateFunctionConfigurationCommand({
FunctionName: physicalId,
Timeout: func.Properties.Timeout,
MemorySize: func.Properties.MemorySize,
Handler: func.Properties.Handler,
}));

// Sleep 1 second to avoid throttling
await new Promise(resolve => setTimeout(resolve, 1000));
let bucket = func.Properties.Code.S3Bucket;
if (typeof bucket !== "string") {
bucket = bucket["Fn::Sub"];
bucket = bucket.replace("${AWS::AccountId}", conf.accountId);
bucket = bucket.replace("${AWS::Region}", conf.envConfig.region);
}
const params = {
FunctionName: physicalId,
Publish: true,
S3Bucket: bucket,
S3Key: func.Properties.Code.S3Key,
};

await lambdaClient.send(new UpdateFunctionCodeCommand(params));

console.log("Restored function:", functionName);
updated = true;
} catch (error) {
if (error.name === "TooManyRequestsException") {
console.log("Too many requests, sleeping for 1 second");
await new Promise(resolve => setTimeout(resolve, 1000));
} else if (error.name === "ResourceConflictException") {
console.log("Resource conflict, retrying");
await new Promise(resolve => setTimeout(resolve, 1000));
} else {
throw error;
}
}
} while (!updated);

});

// Wait for all promises to resolve
await Promise.all(updatePromises);


Loading

0 comments on commit 0c7e43e

Please sign in to comment.