Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

serverless-localstack

Package Overview
Dependencies
Maintainers
0
Versions
54
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

serverless-localstack - npm Package Compare versions

Comparing version 1.2.0 to 1.2.1

15

.eslintrc.json

@@ -8,5 +8,18 @@ {

"extends": "eslint:recommended",
"rules": {
"quotes": ["error", "single"],
"semi": ["error", "always"],
"indent": ["error", 2],
"linebreak-style": ["error", "unix"],
"no-console": "warn",
"no-unused-vars": "warn",
"no-alert": "error",
"prefer-const": "error",
"no-var": "error",
"arrow-parens": ["error", "always"],
"no-tabs": "error"
},
"parserOptions": {
"ecmaVersion": 2017
}
}
}

15

package.json
{
"name": "serverless-localstack",
"version": "1.2.0",
"version": "1.2.1",
"description": "Connect Serverless to LocalStack!",

@@ -43,16 +43,17 @@ "main": "src/index.js",

"chai-string": "^1.4.0",
"eslint": "^8.56.0",
"fs-extra": "^7.0.0",
"jasmine": "^3.2.0",
"js-yaml": ">=3.13.1",
"json2yaml": "^1.1.0",
"lodash": ">=4.17.13",
"mixin-deep": ">=1.3.2",
"nodemon": "^2.0.20",
"prettier": "^3.2.5",
"rimraf": "^2.6.2",
"serverless": "^2.30.0",
"set-value": ">=2.0.1",
"sinon": "^6.2.0",
"tempy": "^0.2.1",
"eslint": "^5.5.0",
"lodash": ">=4.17.13",
"js-yaml": ">=3.13.1",
"set-value": ">=2.0.1",
"mixin-deep": ">=1.3.2"
"tempy": "^0.2.1"
}
}

@@ -211,2 +211,3 @@ [![Build Status](https://travis-ci.org/localstack/serverless-localstack.svg?branch=master)](https://travis-ci.org/localstack/serverless-localstack)

## Change Log
* v1.2.1: Fix custom-resource bucket compatibility with serverless >3.39.0, continue improving support for `AWS_ENDPOINT_URL`
* v1.2.0: Add docker-compose config and fix autostart when plugin is not active

@@ -213,0 +214,0 @@ * v1.1.3: Fix replacing host from environment variable `AWS_ENDPOINT_URL`

@@ -6,5 +6,8 @@ const tempy = require('tempy');

const path = require('path');
const serverlessExec = path.join(__dirname, '../../node_modules/.bin/serverless');
const packageJson = require('../../package.json')
const rimraf = require('rimraf')
const serverlessExec = path.join(
__dirname,
'../../node_modules/.bin/serverless',
);
const packageJson = require('../../package.json');
const rimraf = require('rimraf');

@@ -20,8 +23,7 @@ const debug = false;

environment: {
LAMBDA_STAGE: '${ssm:/${opt:stage, self:provider.stage}/lambda/common/LAMBDA_STAGE}'
}
LAMBDA_STAGE:
'${ssm:/${opt:stage, self:provider.stage}/lambda/common/LAMBDA_STAGE}',
},
},
plugins: [
'serverless-localstack'
],
plugins: ['serverless-localstack'],
custom: {

@@ -31,9 +33,9 @@ localstack: {

debug: debug,
}
},
},
functions: {
hello: {
handler: 'handler.hello'
}
}
handler: 'handler.hello',
},
},
};

@@ -45,10 +47,12 @@

execSync(`mkdir -p node_modules`, {cwd: dir});
execSync(`ln -s ${__dirname}/../../ node_modules/${packageJson.name}`, {cwd: dir});
execSync('mkdir -p node_modules', { cwd: dir });
execSync(`ln -s ${__dirname}/../../ node_modules/${packageJson.name}`, {
cwd: dir,
});
};
const execServerless = (arguments, dir) => {
const execServerless = (myArguments, dir) => {
process.chdir(dir);
execSync(`${serverlessExec} ${arguments}`, {
execSync(`${serverlessExec} ${myArguments}`, {
stdio: 'inherit',

@@ -60,4 +64,4 @@ stderr: 'inherit',

PATH: process.env.PATH,
SLS_DEBUG: debug ? '*' : ''
})
SLS_DEBUG: debug ? '*' : '',
}),
});

@@ -83,3 +87,3 @@ };

exports.removeService = (dir) => {
rimraf.sync(dir)
rimraf.sync(dir);
};

@@ -11,3 +11,3 @@ 'use strict';

region: 'us-east-1',
endpoint: 'http://127.0.0.1:4566'
endpoint: 'http://127.0.0.1:4566',
});

@@ -25,21 +25,22 @@ AWS.config.credentials = new AWS.Credentials({

Value: 'my-value',
Overwrite: true
Overwrite: true,
};
describe('LocalstackPlugin', () => {
beforeEach(
async () => {
await ssm.putParameter(params).promise();
this.service = services.createService({});
beforeEach(async () => {
await ssm.putParameter(params).promise();
this.service = services.createService({});
});
afterEach( () => {
afterEach(() => {
services.removeService(this.service);
});
it('should deploy a stack', () => {
services.deployService(this.service);
}, LONG_TIMEOUT);
});
it(
'should deploy a stack',
() => {
services.deployService(this.service);
},
LONG_TIMEOUT,
);
});

@@ -7,3 +7,3 @@ 'use strict';

const AWS = require('aws-sdk');
const Serverless = require('serverless')
const Serverless = require('serverless');
let AwsProvider;

@@ -21,4 +21,3 @@ try {

describe("LocalstackPlugin", () => {
describe('LocalstackPlugin', () => {
let serverless;

@@ -29,6 +28,6 @@ let awsProvider;

let sandbox;
let defaultPluginState = {};
let config = {
const defaultPluginState = {};
const config = {
host: 'http://localhost',
debug: debug
debug: debug,
};

@@ -38,3 +37,3 @@

sandbox = sinon.createSandbox();
serverless = new Serverless({commands: ['deploy'], options: {}});
serverless = new Serverless({ commands: ['deploy'], options: {} });
awsProvider = new AwsProvider(serverless, {});

@@ -50,5 +49,5 @@ awsConfig = new AWS.Config();

if (debug) {
console.log.apply(this, arguments); // eslint-disable-line no-console
console.log.apply(this, arguments); // eslint-disable-line no-console
}
}
};
}

@@ -61,3 +60,3 @@ });

let simulateBeforeDeployHooks = async function(instance) {
const simulateBeforeDeployHooks = async function (instance) {
instance.readConfig();

@@ -89,3 +88,3 @@ instance.activatePlugin();

serverless.service.custom = {
localstack: {}
localstack: {},
};

@@ -97,7 +96,7 @@ instance = new LocalstackPlugin(serverless, defaultPluginState);

it('should not set the endpoints if the stages config option does not include the deployment stage', async () => {
serverless.service.custom.localstack.stages = ['production'];
serverless.service.custom.localstack.stages = ['production'];
let plugin = new LocalstackPlugin(serverless, defaultPluginState);
await simulateBeforeDeployHooks(plugin);
expect(plugin.endpoints).to.be.empty;
const plugin = new LocalstackPlugin(serverless, defaultPluginState);
await simulateBeforeDeployHooks(plugin);
expect(plugin.endpoints).to.be.empty;
});

@@ -108,6 +107,8 @@

let plugin = new LocalstackPlugin(serverless, {'stage':'production'})
const plugin = new LocalstackPlugin(serverless, {
stage: 'production',
});
await simulateBeforeDeployHooks(plugin);
expect(plugin.config.stages).to.deep.equal(['production','staging']);
expect(plugin.config.stages).to.deep.equal(['production', 'staging']);
expect(plugin.config.stage).to.equal('production');

@@ -119,11 +120,13 @@ });

endpointFile: 'missing.json',
stages: ['production']
}
stages: ['production'],
};
let plugin = () => {
let pluginInstance = new LocalstackPlugin(serverless, {'stage':'production'});
const plugin = () => {
const pluginInstance = new LocalstackPlugin(serverless, {
stage: 'production',
});
pluginInstance.readConfig();
}
};
expect(plugin).to.throw('Endpoint file "missing.json" is invalid:')
expect(plugin).to.throw('Endpoint file "missing.json" is invalid:');
});

@@ -134,11 +137,13 @@

endpointFile: 'missing.json',
stages: ['production']
}
stages: ['production'],
};
let plugin = () => {
let pluginInstance = new LocalstackPlugin(serverless, {'stage':'staging'});
const plugin = () => {
const pluginInstance = new LocalstackPlugin(serverless, {
stage: 'staging',
});
pluginInstance.readConfig();
}
};
expect(plugin).to.not.throw('Endpoint file "missing.json" is invalid:')
expect(plugin).to.not.throw('Endpoint file "missing.json" is invalid:');
});

@@ -148,11 +153,13 @@

serverless.service.custom.localstack = {
endpointFile: 'README.md'
}
let plugin = () => {
let pluginInstance = new LocalstackPlugin(serverless, defaultPluginState);
endpointFile: 'README.md',
};
const plugin = () => {
const pluginInstance = new LocalstackPlugin(
serverless,
defaultPluginState,
);
pluginInstance.readConfig();
}
expect(plugin).to.throw(/Endpoint file "README.md" is invalid:/)
};
expect(plugin).to.throw(/Endpoint file "README.md" is invalid:/);
});
});

@@ -162,4 +169,3 @@ });

describe('#request() bound on AWS provider', () => {
beforeEach(()=> {
beforeEach(() => {
class FakeService {

@@ -177,4 +183,4 @@ foo() {

serverless.service.custom = {
localstack: {}
}
localstack: {},
};
});

@@ -189,8 +195,12 @@

await awsProvider.request('s3', 'foo', {
TemplateURL: pathToTemplate
TemplateURL: pathToTemplate,
});
expect(request.called).to.be.true;
let templateUrl = request.firstCall.args[2].TemplateURL;
const templateUrl = request.firstCall.args[2].TemplateURL;
// url should either start with 'http://localhost' or 'http://127.0.0.1
expect(templateUrl).to.satisfy((url) => url === `${config.host}:4566/path/to/template` || url === 'http://127.0.0.1:4566/path/to/template');
expect(templateUrl).to.satisfy(
(url) =>
url === `${config.host}:4566/path/to/template` ||
url === 'http://127.0.0.1:4566/path/to/template',
);
});

@@ -208,10 +218,10 @@

await awsProvider.request('s3', 'foo', {
TemplateURL: pathToTemplate
TemplateURL: pathToTemplate,
});
// Remove the environment variable again to not affect other tests
delete process.env.AWS_ENDPOINT_URL
delete process.env.AWS_ENDPOINT_URL;
expect(request.called).to.be.true;
let templateUrl = request.firstCall.args[2].TemplateURL;
const templateUrl = request.firstCall.args[2].TemplateURL;
expect(templateUrl).to.equal('http://localstack:4566/path/to/template');

@@ -222,3 +232,3 @@ });

const request = sinon.stub(awsProvider, 'request');
instance = new LocalstackPlugin(serverless, defaultPluginState)
instance = new LocalstackPlugin(serverless, defaultPluginState);
await simulateBeforeDeployHooks(instance);

@@ -230,5 +240,3 @@

});
});
})
});
'use strict';
const AdmZip = require("adm-zip");
const AdmZip = require('adm-zip');
const AWS = require('aws-sdk');

@@ -7,3 +7,3 @@ const fs = require('fs');

const net = require('net');
const {promisify} = require('es6-promisify');
const { promisify } = require('es6-promisify');
const exec = promisify(require('child_process').exec);

@@ -16,25 +16,26 @@

// Plugin naming and build directory of serverless-plugin-typescript plugin
const TS_PLUGIN_TSC = 'TypeScriptPlugin'
const TS_PLUGIN_TSC = 'TypeScriptPlugin';
const TYPESCRIPT_PLUGIN_BUILD_DIR_TSC = '.build'; //TODO detect from tsconfig.json
// Plugin naming and build directory of serverless-webpack plugin
const TS_PLUGIN_WEBPACK = 'ServerlessWebpack'
const TS_PLUGIN_WEBPACK = 'ServerlessWebpack';
const TYPESCRIPT_PLUGIN_BUILD_DIR_WEBPACK = '.webpack/service'; //TODO detect from webpack.config.js
// Plugin naming and build directory of serverless-webpack plugin
const TS_PLUGIN_ESBUILD = 'EsbuildServerlessPlugin'
const TS_PLUGIN_ESBUILD = 'EsbuildServerlessPlugin';
const TYPESCRIPT_PLUGIN_BUILD_DIR_ESBUILD = '.esbuild/.build'; //TODO detect from esbuild.config.js
// Default edge port to use with host
const DEFAULT_EDGE_PORT = '4566';
// Default AWS endpoint URL
const DEFAULT_AWS_ENDPOINT_URL = 'http://localhost:4566';
// Cache hostname to avoid unnecessary connection checks
var resolvedHostname = undefined;
let resolvedHostname = undefined;
const awsEndpointUrl = process.env.AWS_ENDPOINT_URL || DEFAULT_AWS_ENDPOINT_URL;
class LocalstackPlugin {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options;
this.hooks = {'initialize': () => this.init()};
this.hooks = { initialize: () => this.init() };
// Define a before-hook for all event types
for (let event in this.serverless.pluginManager.hooks) {
for (const event in this.serverless.pluginManager.hooks) {
const doAdd = event.startsWith('before:');

@@ -49,13 +50,24 @@ if (doAdd && !this.hooks[event]) {

// Define a hook for deploy:deploy to fix handler location for mounted lambda
this.addHookInFirstPosition('deploy:deploy', this.patchTypeScriptPluginMountedCodeLocation);
this.addHookInFirstPosition(
'deploy:deploy',
this.patchTypeScriptPluginMountedCodeLocation,
);
// Add a before hook for aws:common:validate and make sure it is in the very first position
this.addHookInFirstPosition('before:aws:common:validate:validate', this.beforeEventHook);
this.addHookInFirstPosition(
'before:aws:common:validate:validate',
this.beforeEventHook,
);
// Add a hook to fix TypeError when accessing undefined state attribute
this.addHookInFirstPosition('before:aws:deploy:deploy:checkForChanges', this.beforeDeployCheckForChanges);
this.addHookInFirstPosition(
'before:aws:deploy:deploy:checkForChanges',
this.beforeDeployCheckForChanges,
);
const compileEventsHooks = this.serverless.pluginManager.hooks['package:compileEvents'] || [];
const compileEventsHooks =
this.serverless.pluginManager.hooks['package:compileEvents'] || [];
compileEventsHooks.push({
pluginName: 'LocalstackPlugin', hook: this.patchCustomResourceLambdaS3ForcePathStyle.bind(this)
pluginName: 'LocalstackPlugin',
hook: this.patchCustomResourceLambdaS3ForcePathStyle.bind(this),
});

@@ -140,6 +152,8 @@

if (this.detectTypescriptPluginType() === TS_PLUGIN_WEBPACK) {
const p = this.serverless.pluginManager.plugins.find((x) => x.constructor.name === TS_PLUGIN_WEBPACK);
const p = this.serverless.pluginManager.plugins.find(
(x) => x.constructor.name === TS_PLUGIN_WEBPACK,
);
if (
this.shouldMountCode() && (
!p ||
this.shouldMountCode() &&
(!p ||
!p.serverless ||

@@ -149,7 +163,8 @@ !p.serverless.configurationInput ||

!p.serverless.configurationInput.custom.webpack ||
!p.serverless.configurationInput.custom.webpack.keepOutputDirectory
)
!p.serverless.configurationInput.custom.webpack.keepOutputDirectory)
) {
throw new Error('When mounting Lambda code, you must retain webpack output directory. '
+ 'Set custom.webpack.keepOutputDirectory to true.');
throw new Error(
'When mounting Lambda code, you must retain webpack output directory. ' +
'Set custom.webpack.keepOutputDirectory to true.',
);
}

@@ -160,9 +175,12 @@ }

async init() {
await this.reconfigureAWS()
await this.reconfigureAWS();
}
addHookInFirstPosition(eventName, hookFunction) {
this.serverless.pluginManager.hooks[eventName] = this.serverless.pluginManager.hooks[eventName] || [];
this.serverless.pluginManager.hooks[eventName].unshift(
{ pluginName: 'LocalstackPlugin', hook: hookFunction.bind(this, eventName) });
this.serverless.pluginManager.hooks[eventName] =
this.serverless.pluginManager.hooks[eventName] || [];
this.serverless.pluginManager.hooks[eventName].unshift({
pluginName: 'LocalstackPlugin',
hook: hookFunction.bind(this, eventName),
});
}

@@ -192,31 +210,53 @@

functionObject.package.artifact = __filename;
return compileFunction._functionOriginal.apply(null, arguments).then(() => {
const resources = this.serverless.service.provider.compiledCloudFormationTemplate.Resources;
Object.keys(resources).forEach(id => {
const res = resources[id];
if (res.Type === 'AWS::Lambda::Function') {
res.Properties.Code.S3Bucket = process.env.BUCKET_MARKER_LOCAL || 'hot-reload'; // default changed to 'hot-reload' with LS v2 release
res.Properties.Code.S3Key = process.cwd();
const mountCode = this.config.lambda.mountCode;
if (typeof mountCode === 'string' && mountCode.toLowerCase() !== 'true') {
res.Properties.Code.S3Key = path.join(res.Properties.Code.S3Key, this.config.lambda.mountCode);
return compileFunction._functionOriginal
.apply(null, arguments)
.then(() => {
const resources =
this.serverless.service.provider.compiledCloudFormationTemplate
.Resources;
Object.keys(resources).forEach((id) => {
const res = resources[id];
if (res.Type === 'AWS::Lambda::Function') {
res.Properties.Code.S3Bucket =
process.env.BUCKET_MARKER_LOCAL || 'hot-reload'; // default changed to 'hot-reload' with LS v2 release
res.Properties.Code.S3Key = process.cwd();
const mountCode = this.config.lambda.mountCode;
if (
typeof mountCode === 'string' &&
mountCode.toLowerCase() !== 'true'
) {
res.Properties.Code.S3Key = path.join(
res.Properties.Code.S3Key,
this.config.lambda.mountCode,
);
}
if (process.env.LAMBDA_MOUNT_CWD) {
// Allow users to define a custom working directory for Lambda mounts.
// For example, when deploying a Serverless app in a Linux VM (that runs Docker) on a
// Windows host where the "-v <local_dir>:<cont_dir>" flag to "docker run" requires us
// to specify a "local_dir" relative to the Windows host file system that is mounted
// into the VM (e.g., "c:/users/guest/...").
res.Properties.Code.S3Key = process.env.LAMBDA_MOUNT_CWD;
}
}
if (process.env.LAMBDA_MOUNT_CWD) {
// Allow users to define a custom working directory for Lambda mounts.
// For example, when deploying a Serverless app in a Linux VM (that runs Docker) on a
// Windows host where the "-v <local_dir>:<cont_dir>" flag to "docker run" requires us
// to specify a "local_dir" relative to the Windows host file system that is mounted
// into the VM (e.g., "c:/users/guest/...").
res.Properties.Code.S3Key = process.env.LAMBDA_MOUNT_CWD;
}
}
});
});
});
}
this.skipIfMountLambda('AwsCompileFunctions', 'compileFunction', compileFunction);
this.skipIfMountLambda(
'AwsCompileFunctions',
'compileFunction',
compileFunction,
);
this.skipIfMountLambda('AwsCompileFunctions', 'downloadPackageArtifacts');
this.skipIfMountLambda('AwsDeploy', 'extendedValidate');
if (this.detectTypescriptPluginType()) {
this.skipIfMountLambda(this.detectTypescriptPluginType(), 'cleanup', null, [
'after:package:createDeploymentArtifacts', 'after:deploy:function:packageFunction']);
this.skipIfMountLambda(
this.detectTypescriptPluginType(),
'cleanup',
null,
[
'after:package:createDeploymentArtifacts',
'after:deploy:function:packageFunction',
],
);
}

@@ -250,9 +290,7 @@

return this.startLocalStack().then(
() => {
this.patchServerlessSecrets();
this.patchS3BucketLocationResponse();
this.patchS3CreateBucketLocationConstraint();
}
);
return this.startLocalStack().then(() => {
this.patchServerlessSecrets();
this.patchS3BucketLocationResponse();
this.patchS3CreateBucketLocationConstraint();
});
}

@@ -262,6 +300,6 @@

detectTypescriptPluginType() {
if (this.findPlugin(TS_PLUGIN_TSC)) return TS_PLUGIN_TSC
if (this.findPlugin(TS_PLUGIN_WEBPACK)) return TS_PLUGIN_WEBPACK
if (this.findPlugin(TS_PLUGIN_ESBUILD)) return TS_PLUGIN_ESBUILD
return undefined
if (this.findPlugin(TS_PLUGIN_TSC)) return TS_PLUGIN_TSC;
if (this.findPlugin(TS_PLUGIN_WEBPACK)) return TS_PLUGIN_WEBPACK;
if (this.findPlugin(TS_PLUGIN_ESBUILD)) return TS_PLUGIN_ESBUILD;
return undefined;
}

@@ -271,11 +309,15 @@

getTSBuildDir() {
const TS_PLUGIN = this.detectTypescriptPluginType()
if (TS_PLUGIN === TS_PLUGIN_TSC) return TYPESCRIPT_PLUGIN_BUILD_DIR_TSC
if (TS_PLUGIN === TS_PLUGIN_WEBPACK) return TYPESCRIPT_PLUGIN_BUILD_DIR_WEBPACK
if (TS_PLUGIN === TS_PLUGIN_ESBUILD) return TYPESCRIPT_PLUGIN_BUILD_DIR_ESBUILD
return undefined
const TS_PLUGIN = this.detectTypescriptPluginType();
if (TS_PLUGIN === TS_PLUGIN_TSC) return TYPESCRIPT_PLUGIN_BUILD_DIR_TSC;
if (TS_PLUGIN === TS_PLUGIN_WEBPACK)
return TYPESCRIPT_PLUGIN_BUILD_DIR_WEBPACK;
if (TS_PLUGIN === TS_PLUGIN_ESBUILD)
return TYPESCRIPT_PLUGIN_BUILD_DIR_ESBUILD;
return undefined;
}
findPlugin(name) {
return this.serverless.pluginManager.plugins.find(p => p.constructor.name === name);
return this.serverless.pluginManager.plugins.find(
(p) => p.constructor.name === name,
);
}

@@ -286,7 +328,9 @@

if (!plugin) {
this.log('Warning: Unable to find plugin named: ' + pluginName)
this.log('Warning: Unable to find plugin named: ' + pluginName);
return;
}
if (!plugin[functionName]) {
this.log(`Unable to find function ${functionName} on plugin ${pluginName}`)
this.log(
`Unable to find function ${functionName} on plugin ${pluginName}`,
);
return;

@@ -299,3 +343,5 @@ }

const fqn = pluginName + '.' + functionName;
this.log('Skip plugin function ' + fqn + ' (lambda.mountCode flag is enabled)');
this.log(
'Skip plugin function ' + fqn + ' (lambda.mountCode flag is enabled)',
);
return Promise.resolve();

@@ -312,15 +358,11 @@ }

// overwrite bound functions for specified hook names
(hookNames || []).forEach(
(hookName) => {
plugin.hooks[hookName] = boundOverrideFunction;
const slsHooks = this.serverless.pluginManager.hooks[hookName] || [];
slsHooks.forEach(
(hookItem) => {
if (hookItem.pluginName === pluginName) {
hookItem.hook = boundOverrideFunction;
}
}
);
}
);
(hookNames || []).forEach((hookName) => {
plugin.hooks[hookName] = boundOverrideFunction;
const slsHooks = this.serverless.pluginManager.hooks[hookName] || [];
slsHooks.forEach((hookItem) => {
if (hookItem.pluginName === pluginName) {
hookItem.hook = boundOverrideFunction;
}
});
});
}

@@ -333,7 +375,8 @@

const localstackConfig = (this.serverless.service.custom || {}).localstack || {};
const localstackConfig =
(this.serverless.service.custom || {}).localstack || {};
this.config = Object.assign({}, this.options, localstackConfig);
//Get the target deployment stage
this.config.stage = "";
this.config.stage = '';
this.config.options_stage = this.options.stage || undefined;

@@ -370,5 +413,8 @@

// (2) serverless is invoked without a --stage flag (default stage "dev") and no `stages` config is provided
const effectiveStage = this.options.stage || this.config.stage || DEFAULT_STAGE;
const noStageUsed = this.config.stages === undefined && effectiveStage == DEFAULT_STAGE;
const includedInStages = this.config.stages && this.config.stages.includes(effectiveStage);
const effectiveStage =
this.options.stage || this.config.stage || DEFAULT_STAGE;
const noStageUsed =
this.config.stages === undefined && effectiveStage == DEFAULT_STAGE;
const includedInStages =
this.config.stages && this.config.stages.includes(effectiveStage);
return noStageUsed || includedInStages;

@@ -391,3 +437,4 @@ }

this.debug('serverless.service.provider.stage: ' + providerConfig.stage);
this.config.stage = this.config.options_stage || customConfig.stage || providerConfig.stage;
this.config.stage =
this.config.options_stage || customConfig.stage || providerConfig.stage;
this.debug('config.stage: ' + this.config.stage);

@@ -397,3 +444,3 @@ }

fixOutputEndpoints() {
if(!this.isActive()) {
if (!this.isActive()) {
return;

@@ -413,3 +460,4 @@ }

// - https://2e22431f.execute-api.us-east-1.amazonaws.com
const regex2 = /[^\s:]*:\/\/([^.]+)\.execute-api\.[^/]+(([^/]+)(\/.*)?)?\/*$/g;
const regex2 =
/[^\s:]*:\/\/([^.]+)\.execute-api\.[^/]+(([^/]+)(\/.*)?)?\/*$/g;
const replace2 = `https://$1.execute-api.localhost.localstack.cloud:${edgePort}$2`;

@@ -420,3 +468,3 @@ endpoints[idx] = entry.replace(regex2, replace2);

// Replace ServerlessStepFunctions display
this.stepFunctionsReplaceDisplay()
this.stepFunctionsReplaceDisplay();
}

@@ -433,16 +481,15 @@

const getContainer = () => {
return exec('docker ps').then(
(stdout) => {
const exists = stdout.split('\n').filter(
(line) => (
line.indexOf('localstack/localstack') >= 0 ||
line.indexOf('localstack/localstack-pro') >= 0 ||
line.indexOf('localstack_localstack') >= 0
)
return exec('docker ps').then((stdout) => {
const exists = stdout
.split('\n')
.filter(
(line) =>
line.indexOf('localstack/localstack') >= 0 ||
line.indexOf('localstack/localstack-pro') >= 0 ||
line.indexOf('localstack_localstack') >= 0,
);
if (exists.length) {
return exists[0].replace('\t', ' ').split(' ')[0];
}
if (exists.length) {
return exists[0].replace('\t', ' ').split(' ')[0];
}
)
});
};

@@ -455,27 +502,31 @@

if (Date.now() > timeout) {
this.log('Warning: Timeout when checking state of LocalStack container');
this.log(
'Warning: Timeout when checking state of LocalStack container',
);
return;
}
return this.sleep(4000).then(() => {
this.log(`Checking state of LocalStack container ${containerID}`)
return exec(`docker logs "${containerID}"`).then(
(logs) => {
const ready = logs.split('\n').filter((line) => line.indexOf('Ready.') >= 0);
if (ready.length) {
return Promise.resolve();
}
return checkStatus(containerID, timeout);
this.log(`Checking state of LocalStack container ${containerID}`);
return exec(`docker logs "${containerID}"`).then((logs) => {
const ready = logs
.split('\n')
.filter((line) => line.indexOf('Ready.') >= 0);
if (ready.length) {
return Promise.resolve();
}
);
return checkStatus(containerID, timeout);
});
});
}
};
const addNetworks = async (containerID) => {
if(this.config.networks) {
for(var network in this.config.networks) {
await exec(`docker network connect "${this.config.networks[network]}" ${containerID}`);
if (this.config.networks) {
for (const network in this.config.networks) {
await exec(
`docker network connect "${this.config.networks[network]}" ${containerID}`,
);
}
}
return containerID;
}
};

@@ -491,30 +542,33 @@ const startContainer = () => {

env.START_WEB = env.START_WEB || '0';
const maxBuffer = (+env.EXEC_MAXBUFFER)||50*1000*1000; // 50mb buffer to handle output
const maxBuffer = +env.EXEC_MAXBUFFER || 50 * 1000 * 1000; // 50mb buffer to handle output
if (this.shouldRunDockerSudo()) {
env.DOCKER_CMD = 'sudo docker';
}
const options = {env: env, maxBuffer};
return exec('localstack start -d', options).then(getContainer)
.then((containerID) => addNetworks(containerID))
.then((containerID) => checkStatus(containerID));
}
const options = { env: env, maxBuffer };
return exec('localstack start -d', options)
.then(getContainer)
.then((containerID) => addNetworks(containerID))
.then((containerID) => checkStatus(containerID));
};
const startCompose = () => {
this.log('Starting LocalStack using the provided docker-compose file. This can take a while.');
return exec(`docker-compose -f ${this.config.docker.compose_file} up -d`).then(getContainer)
}
this.log(
'Starting LocalStack using the provided docker-compose file. This can take a while.',
);
return exec(
`docker-compose -f ${this.config.docker.compose_file} up -d`,
).then(getContainer);
};
return getContainer().then(
(containerID) => {
if(containerID) {
return;
}
return getContainer().then((containerID) => {
if (containerID) {
return;
}
if(this.config.docker && this.config.docker.compose_file){
return startCompose();
}
if (this.config.docker && this.config.docker.compose_file) {
return startCompose();
}
return startContainer();
}
);
return startContainer();
});
}

@@ -527,15 +581,18 @@

patchTypeScriptPluginMountedCodeLocation() {
if (!this.shouldMountCode() || !this.detectTypescriptPluginType() || !this.isActive()) {
if (
!this.shouldMountCode() ||
!this.detectTypescriptPluginType() ||
!this.isActive()
) {
return;
}
const template = this.serverless.service.provider.compiledCloudFormationTemplate || {};
const template =
this.serverless.service.provider.compiledCloudFormationTemplate || {};
const resources = template.Resources || {};
Object.keys(resources).forEach(
(resName) => {
const resEntry = resources[resName];
if (resEntry.Type === 'AWS::Lambda::Function') {
resEntry.Properties.Handler = `${this.getTSBuildDir()}/${resEntry.Properties.Handler}`;
}
Object.keys(resources).forEach((resName) => {
const resEntry = resources[resName];
if (resEntry.Type === 'AWS::Lambda::Function') {
resEntry.Properties.Handler = `${this.getTSBuildDir()}/${resEntry.Properties.Handler}`;
}
);
});
}

@@ -556,3 +613,3 @@

return Promise.resolve(res);
})
});
}

@@ -567,8 +624,8 @@ return result;

/**
* Patch S3 createBucket invocation to not add a LocationContraint if the region is `us-east-1`
* The default SDK check was against endpoint and not the region directly.
*/
patchS3CreateBucketLocationConstraint () {
* Patch S3 createBucket invocation to not add a LocationContraint if the region is `us-east-1`
* The default SDK check was against endpoint and not the region directly.
*/
patchS3CreateBucketLocationConstraint() {
AWS.util.update(AWS.S3.prototype, {
createBucket: function createBucket (params, callback) {
createBucket: function createBucket(params, callback) {
// When creating a bucket *outside* the classic region, the location

@@ -585,9 +642,14 @@ // constraint must be set for the bucket and it must match the endpoint.

// mutate params object argument passed in by user
var copiedParams = AWS.util.copy(params);
if (this.config.region !== 'us-east-1' && !params.CreateBucketConfiguration) {
copiedParams.CreateBucketConfiguration = { LocationConstraint: this.config.region };
const copiedParams = AWS.util.copy(params);
if (
this.config.region !== 'us-east-1' &&
!params.CreateBucketConfiguration
) {
copiedParams.CreateBucketConfiguration = {
LocationConstraint: this.config.region,
};
}
return this.makeRequest('createBucket', copiedParams, callback);
}
})
},
});
}

@@ -601,4 +663,6 @@

if (slsSecretsAWS) {
slsSecretsAWS.config.options.providerOptions = slsSecretsAWS.config.options.providerOptions || {};
slsSecretsAWS.config.options.providerOptions.endpoint = this.getServiceURL();
slsSecretsAWS.config.options.providerOptions =
slsSecretsAWS.config.options.providerOptions || {};
slsSecretsAWS.config.options.providerOptions.endpoint =
this.getServiceURL();
slsSecretsAWS.config.options.providerOptions.accessKeyId = 'test';

@@ -613,5 +677,7 @@ slsSecretsAWS.config.options.providerOptions.secretAccessKey = 'test';

async reconfigureAWS() {
if(this.isActive()) {
if(this.reconfiguredEndpoints){
this.debug("Skipping reconfiguring of endpoints (already reconfigured)")
if (this.isActive()) {
if (this.reconfiguredEndpoints) {
this.debug(
'Skipping reconfiguring of endpoints (already reconfigured)',
);
return;

@@ -627,6 +693,9 @@ }

const tmpCreds = awsProvider.getCredentials();
if (!tmpCreds.credentials){
if (!tmpCreds.credentials) {
const accessKeyId = process.env.AWS_ACCESS_KEY_ID || 'test';
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY || 'test';
const fakeCredentials = new AWS.Credentials({accessKeyId, secretAccessKey});
const fakeCredentials = new AWS.Credentials({
accessKeyId,
secretAccessKey,
});
configChanges.credentials = fakeCredentials;

@@ -672,11 +741,13 @@ // set environment variables, ...

}
this.log("serverless-localstack: Reconfigured endpoints")
this.log('serverless-localstack: Reconfigured endpoints');
this.reconfiguredEndpoints = true;
} else {
this.endpoints = {};
this.log(
'Skipping serverless-localstack:\ncustom.localstack.stages: ' +
JSON.stringify(this.config.stages) +
'\nstage: ' +
this.config.stage,
);
}
else {
this.endpoints = {}
this.log("Skipping serverless-localstack:\ncustom.localstack.stages: " +
JSON.stringify(this.config.stages) + "\nstage: " + this.config.stage
)
}
}

@@ -693,5 +764,7 @@

try {
endpointJson = JSON.parse( fs.readFileSync(endpointFile) );
} catch(err) {
throw new ReferenceError(`Endpoint file "${this.endpointFile}" is invalid: ${err}`)
endpointJson = JSON.parse(fs.readFileSync(endpointFile));
} catch (err) {
throw new ReferenceError(
`Endpoint file "${this.endpointFile}" is invalid: ${err}`,
);
}

@@ -706,3 +779,2 @@

async interceptRequest(service, method, params) {
// Enable the plugin here, if not yet enabled (the function call below is idempotent).

@@ -714,3 +786,3 @@ // TODO: It seems that we can potentially remove the hooks / plugin loading logic

// Template validation is not supported in LocalStack
if (method == "validateTemplate") {
if (method == 'validateTemplate') {
this.log('Skipping template validation: Unsupported in Localstack');

@@ -720,9 +792,16 @@ return Promise.resolve('');

const config = AWS.config[service.toLowerCase()] ? AWS.config : this.getAwsProvider().sdk.config;
const config = AWS.config[service.toLowerCase()]
? AWS.config
: this.getAwsProvider().sdk.config;
if (config[service.toLowerCase()]) {
this.debug(`Using custom endpoint for ${service}: ${config[service.toLowerCase()].endpoint}`);
this.debug(
`Using custom endpoint for ${service}: ${config[service.toLowerCase()].endpoint}`,
);
if (config.s3 && params.TemplateURL) {
this.debug(`Overriding S3 templateUrl to ${config.s3.endpoint}`);
params.TemplateURL = params.TemplateURL.replace(/https:\/\/s3.amazonaws.com/, config.s3.endpoint);
params.TemplateURL = params.TemplateURL.replace(
/https:\/\/s3.amazonaws.com/,
config.s3.endpoint,
);
}

@@ -737,4 +816,21 @@ }

getEndpointPort() {
const url = new URL(awsEndpointUrl);
return url.port;
}
getEndpointHostname() {
const url = new URL(awsEndpointUrl);
return url.hostname;
}
getEndpointProtocol() {
const url = new URL(awsEndpointUrl);
return url.protocol.replace(':', '');
}
getEdgePort() {
return process.env.EDGE_PORT || this.config.edgePort || DEFAULT_EDGE_PORT;
return (
process.env.EDGE_PORT || this.config.edgePort || this.getEndpointPort()
);
}

@@ -751,6 +847,7 @@

var hostname = process.env.LOCALSTACK_HOSTNAME || 'localhost';
let hostname =
process.env.LOCALSTACK_HOSTNAME || this.getEndpointHostname();
if (this.config.host) {
hostname = this.config.host;
if (hostname.indexOf("://") !== -1) {
if (hostname.indexOf('://') !== -1) {
hostname = new URL(hostname).hostname;

@@ -765,3 +862,3 @@ }

// Issue: https://github.com/localstack/serverless-localstack/issues/125
if (hostname === "localhost") {
if (hostname === 'localhost') {
try {

@@ -772,4 +869,6 @@ const port = this.getEdgePort();

} catch (e) {
const fallbackHostname = "127.0.0.1"
this.debug(`Reconfiguring hostname to use ${fallbackHostname} (IPv4) because connection to ${hostname} failed`);
const fallbackHostname = '127.0.0.1';
this.debug(
`Reconfiguring hostname to use ${fallbackHostname} (IPv4) because connection to ${hostname} failed`,
);
hostname = fallbackHostname;

@@ -800,4 +899,4 @@ }

client.setTimeout(500); // milliseconds
client.on("timeout", err => {
client.setTimeout(500); // milliseconds
client.on('timeout', (err) => {
client.destroy();

@@ -807,3 +906,3 @@ reject(err);

client.on("error", err => {
client.on('error', (err) => {
client.destroy();

@@ -822,6 +921,15 @@ reject(err);

if (process.env.AWS_ENDPOINT_URL) {
return this.injectHostnameIntoLocalhostURL(process.env.AWS_ENDPOINT_URL, hostname);
return this.injectHostnameIntoLocalhostURL(
process.env.AWS_ENDPOINT_URL,
hostname,
);
}
hostname = hostname || 'localhost';
const proto = TRUE_VALUES.includes(process.env.USE_SSL) ? 'https' : 'http';
let proto = this.getEndpointProtocol();
if (process.env.USE_SSL) {
proto = TRUE_VALUES.includes(process.env.USE_SSL) ? 'https' : 'http';
} else if (this.config.host) {
proto = this.config.host.split('://')[0];
}
const port = this.getEdgePort();

@@ -864,3 +972,3 @@ // little hack here - required to remove the default HTTPS port 443, as otherwise

sleep(millis) {
return new Promise(resolve => setTimeout(resolve, millis));
return new Promise((resolve) => setTimeout(resolve, millis));
}

@@ -881,14 +989,15 @@

const regex = /.*:\/\/([^.]+)\.execute-api[^/]+\/([^/]+)(\/.*)?/g;
let newEndpoint = this.localstackEndpoint +'/restapis/$1/$2/_user_request_$3'
if(this.endpointInfo) {
this.endpointInfo = this.endpointInfo.replace(regex, newEndpoint)
const newEndpoint =
this.localstackEndpoint + '/restapis/$1/$2/_user_request_$3';
if (this.endpointInfo) {
this.endpointInfo = this.endpointInfo.replace(regex, newEndpoint);
}
this.originalDisplay();
}
};
newDisplay.bind(plugin)
newDisplay.bind(plugin);
plugin.display = newDisplay;
}
}
patchCustomResourceLambdaS3ForcePathStyle () {
patchCustomResourceLambdaS3ForcePathStyle() {
const awsProvider = this.awsProvider;

@@ -898,3 +1007,3 @@ const patchMarker = path.join(

'.serverless',
'.internal-custom-resources-patched'
'.internal-custom-resources-patched',
);

@@ -904,6 +1013,6 @@ const zipFilePath = path.join(

'.serverless',
awsProvider.naming.getCustomResourcesArtifactName()
awsProvider.naming.getCustomResourcesArtifactName(),
);
function fileExists (filePath) {
function fileExists(filePath) {
try {

@@ -917,5 +1026,5 @@ const stats = fs.statSync(filePath);

function createPatchMarker () {
function createPatchMarker() {
try {
fs.open(patchMarker, 'a').close()
fs.open(patchMarker, 'a').close();
} catch (err) {

@@ -925,9 +1034,55 @@ return;

}
function patchPreV3() {
const utilFile = customResources.getEntry('utils.js');
if (utilFile == null) return;
const data = utilFile.getData().toString();
const legacyPatch = 'AWS.config.s3ForcePathStyle = true;';
if (data.includes(legacyPatch)) {
createPatchMarker();
return true;
}
const patchIndex = data.indexOf('AWS.config.logger = console;');
if (patchIndex === -1) {
return false;
}
const newData =
data.slice(0, patchIndex) + legacyPatch + '\n' + data.slice(patchIndex);
utilFile.setData(newData);
return true;
}
function patchV3() {
this.debug(
'serverless-localstack: Patching V3',
);
const customResourcesBucketFile = customResources.getEntry('s3/lib/bucket.js');
if (customResourcesBucketFile == null) {
// TODO debugging, remove
this.log(
'serverless-localstack: Could not find file s3/lib/bucket.js to patch.',
);
return;
}
const data = customResourcesBucketFile.getData().toString();
const oldClientCreation = 'S3Client({ maxAttempts: MAX_AWS_REQUEST_TRY });';
const newClientCreation = 'S3Client({ maxAttempts: MAX_AWS_REQUEST_TRY, forcePathStyle: true });';
if (data.includes(newClientCreation)) {
// patch already done
createPatchMarker();
return;
}
const newData = data.replace(oldClientCreation, newClientCreation);
customResourcesBucketFile.setData(newData);
}
if (fileExists(patchMarker)) {
this.debug("serverless-localstack: Serverless internal CustomResources already patched")
this.debug(
'serverless-localstack: Serverless internal CustomResources already patched',
);
return;
}
const customResourceZipExists = fileExists(zipFilePath)
const customResourceZipExists = fileExists(zipFilePath);

@@ -939,16 +1094,11 @@ if (!customResourceZipExists) {

const customResources = new AdmZip(zipFilePath);
const utilFile = customResources.getEntry('utils.js')
if (utilFile == null) return;
const data = utilFile.getData().toString()
const patch = "AWS.config.s3ForcePathStyle = true;"
if (data.includes(patch)) {
createPatchMarker()
return;
if (!patchPreV3.call(this)) {
patchV3.call(this);
}
const indexPatch = data.indexOf('AWS.config.logger = console;')
const newData = data.slice(0, indexPatch) + patch + '\n' + data.slice(indexPatch)
utilFile.setData(newData)
customResources.writeZip()
createPatchMarker()
this.debug('serverless-localstack: Serverless internal CustomResources patched')
customResources.writeZip();
createPatchMarker();
this.debug(
'serverless-localstack: Serverless internal CustomResources patched',
);
}

@@ -955,0 +1105,0 @@ }

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc