Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 5b70e1d

Browse files
author
Amadeusz Żołnowski
committed
feat: Respect the package-lock.json for a NodeJS Lambda function
Respect the `package-lock.json` so NodeJS Lambda for reproducible builds which are critical in production environments. Similarly like for the Poetry, copy a lock file, if such is present, to a temporary build directory. npm will use a `package-lock.json` file when available in a working directory. In the example `package.json`, require lower `requests` version to demonstrate `package-lock.json` usage. `package.json` specifies `~0.2.0` and the latest available matching version is `0.2.2`, but `package-lock.json` freezes version `0.2.1` and that version gets installed with this change, while previously the `0.2.2` would be installed.
1 parent cc9be0a commit 5b70e1d

File tree

6 files changed

+201
-4
lines changed

6 files changed

+201
-4
lines changed

‎examples/build-package/README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Note that this example may create resources which cost money. Run `terraform des
4545
| <a name="module_package_dir_poetry"></a> [package\_dir\_poetry](#module\_package\_dir\_poetry) | ../../ | n/a |
4646
| <a name="module_package_dir_poetry_no_docker"></a> [package\_dir\_poetry\_no\_docker](#module\_package\_dir\_poetry\_no\_docker) | ../../ | n/a |
4747
| <a name="module_package_dir_with_npm_install"></a> [package\_dir\_with\_npm\_install](#module\_package\_dir\_with\_npm\_install) | ../../ | n/a |
48+
| <a name="module_package_dir_with_npm_install_lock_file"></a> [package\_dir\_with\_npm\_install\_lock\_file](#module\_package\_dir\_with\_npm\_install\_lock\_file) | ../../ | n/a |
4849
| <a name="module_package_dir_without_npm_install"></a> [package\_dir\_without\_npm\_install](#module\_package\_dir\_without\_npm\_install) | ../../ | n/a |
4950
| <a name="module_package_dir_without_pip_install"></a> [package\_dir\_without\_pip\_install](#module\_package\_dir\_without\_pip\_install) | ../../ | n/a |
5051
| <a name="module_package_file"></a> [package\_file](#module\_package\_file) | ../../ | n/a |
@@ -53,6 +54,7 @@ Note that this example may create resources which cost money. Run `terraform des
5354
| <a name="module_package_src_poetry2"></a> [package\_src\_poetry2](#module\_package\_src\_poetry2) | ../../ | n/a |
5455
| <a name="module_package_with_commands_and_patterns"></a> [package\_with\_commands\_and\_patterns](#module\_package\_with\_commands\_and\_patterns) | ../../ | n/a |
5556
| <a name="module_package_with_docker"></a> [package\_with\_docker](#module\_package\_with\_docker) | ../../ | n/a |
57+
| <a name="module_package_with_npm_lock_in_docker"></a> [package\_with\_npm\_lock\_in\_docker](#module\_package\_with\_npm\_lock\_in\_docker) | ../../ | n/a |
5658
| <a name="module_package_with_npm_requirements_in_docker"></a> [package\_with\_npm\_requirements\_in\_docker](#module\_package\_with\_npm\_requirements\_in\_docker) | ../../ | n/a |
5759
| <a name="module_package_with_patterns"></a> [package\_with\_patterns](#module\_package\_with\_patterns) | ../../ | n/a |
5860
| <a name="module_package_with_pip_requirements_in_docker"></a> [package\_with\_pip\_requirements\_in\_docker](#module\_package\_with\_pip\_requirements\_in\_docker) | ../../ | n/a |

‎examples/build-package/main.tf‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,18 @@ module "package_dir_with_npm_install" {
365365
source_path = "${path.module}/../fixtures/nodejs14.x-app1"
366366
}
367367

368+
# Create zip-archive of a single directory where "npm install" will also be
369+
# executed (default for nodejs runtime). This example has package-lock.json which
370+
# is respected when installing dependencies.
371+
module "package_dir_with_npm_install_lock_file" {
372+
source = "../../"
373+
374+
create_function = false
375+
376+
runtime = "nodejs14.x"
377+
source_path = "${path.module}/../fixtures/nodejs14.x-app2"
378+
}
379+
368380
# Create zip-archive of a single directory without running "npm install" (which is the default for nodejs runtime)
369381
module "package_dir_without_npm_install" {
370382
source = "../../"
@@ -393,6 +405,20 @@ module "package_with_npm_requirements_in_docker" {
393405
hash_extra = "something-unique-to-not-conflict-with-module.package_dir_with_npm_install"
394406
}
395407

408+
# Create zip-archive of a single directory where "npm install" will also be
409+
# executed using docker. This example has package-lock.json which is respected
410+
# when installing dependencies.
411+
module "package_with_npm_lock_in_docker" {
412+
source = "../../"
413+
414+
create_function = false
415+
416+
runtime = "nodejs14.x"
417+
source_path = "${path.module}/../fixtures/nodejs14.x-app2"
418+
build_in_docker = true
419+
hash_extra = "something-unique-to-not-conflict-with-module.package_dir_with_npm_install"
420+
}
421+
396422
################################
397423
# Build package in Docker and
398424
# use it to deploy Lambda Layer
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict';
2+
3+
module.exports.hello = async (event) => {
4+
console.log(event);
5+
return {
6+
statusCode: 200,
7+
body: JSON.stringify(
8+
{
9+
message: `Go Serverless.tf! Your Nodejs function executed successfully!`,
10+
input: event,
11+
},
12+
null,
13+
2
14+
),
15+
};
16+
};

‎examples/fixtures/nodejs14.x-app2/package-lock.json‎

Lines changed: 83 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "nodejs14.x-app1",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"dependencies": {
6+
"requests": "^0.2.0"
7+
}
8+
}

‎package.py‎

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,14 @@ def npm_requirements_step(path, prefix=None, required=False, tmp_dir=None):
733733
requirements = path
734734
if os.path.isdir(path):
735735
requirements = os.path.join(path, "package.json")
736+
npm_lock_file = os.path.join(path, "package-lock.json")
737+
else:
738+
npm_lock_file = os.path.join(os.path.dirname(path), "package-lock.json")
739+
740+
if os.path.isfile(npm_lock_file):
741+
hash(npm_lock_file)
742+
log.info("Added npm lock file: %s", npm_lock_file)
743+
736744
if not os.path.isfile(requirements):
737745
if required:
738746
raise RuntimeError("File not found: {}".format(requirements))
@@ -1395,9 +1403,10 @@ def install_npm_requirements(query, requirements_file, tmp_dir):
13951403

13961404
log.info("Installing npm requirements: %s", requirements_file)
13971405
with tempdir(tmp_dir) as temp_dir:
1398-
requirements_filename = os.path.basename(requirements_file)
1399-
target_file = os.path.join(temp_dir, requirements_filename)
1400-
shutil.copyfile(requirements_file, target_file)
1406+
temp_copy = TemporaryCopy(os.path.dirname(requirements_file), temp_dir, log)
1407+
temp_copy.add(os.path.basename(requirements_file))
1408+
temp_copy.add("package-lock.json", required=False)
1409+
temp_copy.copy_to_target_dir()
14011410

14021411
subproc_env = None
14031412
npm_exec = "npm"
@@ -1442,10 +1451,63 @@ def install_npm_requirements(query, requirements_file, tmp_dir):
14421451
"available in system PATH".format(runtime)
14431452
) from e
14441453

1445-
os.remove(target_file)
1454+
temp_copy.remove_from_target_dir()
14461455
yield temp_dir
14471456

14481457

1458+
class TemporaryCopy:
1459+
"""Temporarily copy files to a specified location and remove them when
1460+
not needed.
1461+
"""
1462+
1463+
def __init__(self, source_dir_path, target_dir_path, logger=None):
1464+
"""Initialise with a target and a source directories."""
1465+
self.source_dir_path = source_dir_path
1466+
self.target_dir_path = target_dir_path
1467+
self._filenames = []
1468+
self._logger = logger
1469+
1470+
def _make_source_path(self, filename):
1471+
return os.path.join(self.source_dir_path, filename)
1472+
1473+
def _make_target_path(self, filename):
1474+
return os.path.join(self.target_dir_path, filename)
1475+
1476+
def add(self, filename, *, required=True):
1477+
"""Add a file to be copied from from source to target directory
1478+
when `TemporaryCopy.copy_to_target_dir()` is called.
1479+
1480+
By default, the file must exist in the source directory. Set `required`
1481+
to `False` if the file is optional.
1482+
"""
1483+
if os.path.exists(self._make_source_path(filename)):
1484+
self._filenames.append(filename)
1485+
elif required:
1486+
raise RuntimeError("File not found: {}".format(filename))
1487+
1488+
def copy_to_target_dir(self):
1489+
"""Copy files (added so far) to the target directory."""
1490+
for filename in self._filenames:
1491+
if self._logger:
1492+
self._logger.info("Copying temporarily '%s'", filename)
1493+
1494+
shutil.copyfile(
1495+
self._make_source_path(filename),
1496+
self._make_target_path(filename),
1497+
)
1498+
1499+
def remove_from_target_dir(self):
1500+
"""Remove files (added so far) from the target directory."""
1501+
for filename in self._filenames:
1502+
if self._logger:
1503+
self._logger.info("Removing temporarily copied '%s'", filename)
1504+
1505+
try:
1506+
os.remove(self._make_target_path(filename))
1507+
except FileNotFoundError:
1508+
pass
1509+
1510+
14491511
def docker_image_id_command(tag):
14501512
""""""
14511513
docker_cmd = ["docker", "images", "--format={{.ID}}", tag]

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /