Skip to content

Commit bd7b669

Browse files
gojkojfuss
authored andcommitted
feat: Support building Node.js functions (#845)
1 parent 172081f commit bd7b669

File tree

14 files changed

+131
-17
lines changed

14 files changed

+131
-17
lines changed

.travis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ matrix:
1616
dist: xenial
1717
sudo: true
1818

19+
before_install:
20+
- nvm install 8.10
21+
- npm --version
22+
- node --version
23+
1924
install:
2025
# Install the code requirements
2126
- make init

requirements/base.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ dateparser~=0.7
1212
python-dateutil~=2.6
1313
pathlib2~=2.3.2; python_version<"3.4"
1414
requests~=2.20.0
15-
aws_lambda_builders==0.0.3
15+
aws_lambda_builders==0.0.4

samcli/lib/build/app_builder.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ def _get_workflow_config(runtime):
5151
dependency_manager="pip",
5252
application_framework=None,
5353
manifest_name="requirements.txt")
54+
elif runtime.startswith("nodejs"):
55+
return config(
56+
language="nodejs",
57+
dependency_manager="npm",
58+
application_framework=None,
59+
manifest_name="package.json")
5460
else:
5561
raise UnsupportedRuntimeException("'{}' runtime is not supported".format(runtime))
5662

samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/README.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This is a sample template for {{ cookiecutter.project_name }} - Below is a brief
55
```bash
66
.
77
├── README.md <-- This instructions file
8-
├── hello_world <-- Source code for a lambda function
8+
├── hello-world <-- Source code for a lambda function
99
│ ├── app.js <-- Lambda function code
1010
│ ├── package.json <-- NodeJS dependencies
1111
│ └── tests <-- Unit tests
@@ -28,15 +28,21 @@ This is a sample template for {{ cookiecutter.project_name }} - Below is a brief
2828

2929
## Setup process
3030

31-
### Installing dependencies
31+
### Building the project
3232

33-
In this example we use `npm` but you can use `yarn` if you prefer to manage NodeJS dependencies:
33+
[AWS Lambda requires a flat folder](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html) with the application as well as its dependencies in a node_modules folder. When you make changes to your source code or dependency manifest,
34+
run the following command to build your project local testing and deployment:
35+
36+
```bash
37+
sam build
38+
```
3439

40+
If your dependencies contain native modules that need to be compiled specifically for the operating system running on AWS Lambda, use this command to build inside a Lambda-like Docker container instead:
3541
```bash
36-
cd hello_world
37-
npm install
38-
cd ../
42+
sam build --use-container
3943
```
44+
45+
By default, this command writes built artifacts to `.aws-sam/build` folder.
4046

4147
### Local development
4248

@@ -69,7 +75,7 @@ AWS Lambda NodeJS runtime requires a flat folder with all dependencies including
6975
FirstFunction:
7076
Type: AWS::Serverless::Function
7177
Properties:
72-
CodeUri: hello_world/
78+
CodeUri: hello-world/
7379
...
7480
```
7581

@@ -112,7 +118,8 @@ aws cloudformation describe-stacks \
112118
We use `mocha` for testing our code and it is already added in `package.json` under `scripts`, so that we can simply run the following command to run our tests:
113119

114120
```bash
115-
cd hello_world
121+
cd hello-world
122+
npm install
116123
npm run test
117124
```
118125

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tests/*

samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/template.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Resources:
1616
HelloWorldFunction:
1717
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
1818
Properties:
19-
CodeUri: hello_world/
19+
CodeUri: hello-world/
2020
Handler: app.lambdaHandler
2121
{%- if cookiecutter.runtime == 'nodejs6.10' %}
2222
Runtime: nodejs6.10

tests/integration/buildcmd/test_build_cmd.py

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class TestBuildCommand_PythonFunctions(BuildIntegBase):
2121
"jinja2",
2222
'requirements.txt'}
2323

24-
FUNCTION_LOGICAL_ID = "PythonFunction"
24+
FUNCTION_LOGICAL_ID = "Function"
2525

2626
@parameterized.expand([
2727
("python2.7", False),
@@ -37,7 +37,7 @@ def test_with_default_requirements(self, runtime, use_container):
3737
self.skipTest("Current Python version '{}' does not match Lambda runtime version '{}'".format(py_version,
3838
runtime))
3939

40-
overrides = {"PythonRuntime": runtime}
40+
overrides = {"Runtime": runtime, "CodeUri": "Python"}
4141
cmdlist = self.get_command_list(use_container=use_container,
4242
parameter_overrides=overrides)
4343

@@ -113,7 +113,7 @@ def _get_python_version(self):
113113
class TestBuildCommand_ErrorCases(BuildIntegBase):
114114

115115
def test_unsupported_runtime(self):
116-
overrides = {"PythonRuntime": "unsupportedpython"}
116+
overrides = {"Runtime": "unsupportedpython", "CodeUri": "NoThere"}
117117
cmdlist = self.get_command_list(parameter_overrides=overrides)
118118

119119
LOG.info("Running Command: {}", cmdlist)
@@ -124,3 +124,71 @@ def test_unsupported_runtime(self):
124124
self.assertEquals(1, process.returncode)
125125

126126
self.assertIn("Build Failed", process_stdout)
127+
128+
129+
class TestBuildCommand_NodeFunctions(BuildIntegBase):
130+
131+
EXPECTED_FILES_GLOBAL_MANIFEST = set()
132+
EXPECTED_FILES_PROJECT_MANIFEST = {'node_modules', 'main.js'}
133+
EXPECTED_NODE_MODULES = {'minimal-request-promise'}
134+
135+
FUNCTION_LOGICAL_ID = "Function"
136+
137+
@parameterized.expand([
138+
("nodejs4.3", False),
139+
("nodejs6.10", False),
140+
("nodejs8.10", False),
141+
("nodejs4.3", "use_container"),
142+
("nodejs6.10", "use_container"),
143+
("nodejs8.10", "use_container")
144+
])
145+
def test_with_default_package_json(self, runtime, use_container):
146+
overrides = {"Runtime": runtime, "CodeUri": "Node"}
147+
cmdlist = self.get_command_list(use_container=use_container,
148+
parameter_overrides=overrides)
149+
150+
LOG.info("Running Command: {}", cmdlist)
151+
process = subprocess.Popen(cmdlist, cwd=self.working_dir)
152+
process.wait()
153+
154+
self._verify_built_artifact(self.default_build_dir, self.FUNCTION_LOGICAL_ID,
155+
self.EXPECTED_FILES_PROJECT_MANIFEST, self.EXPECTED_NODE_MODULES)
156+
157+
self._verify_resource_property(str(self.built_template),
158+
"OtherRelativePathResource",
159+
"BodyS3Location",
160+
os.path.relpath(
161+
os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")),
162+
str(self.default_build_dir))
163+
)
164+
165+
def _verify_built_artifact(self, build_dir, function_logical_id, expected_files, expected_modules):
166+
167+
self.assertTrue(build_dir.exists(), "Build directory should be created")
168+
169+
build_dir_files = os.listdir(str(build_dir))
170+
self.assertIn("template.yaml", build_dir_files)
171+
self.assertIn(function_logical_id, build_dir_files)
172+
173+
template_path = build_dir.joinpath("template.yaml")
174+
resource_artifact_dir = build_dir.joinpath(function_logical_id)
175+
176+
# Make sure the template has correct CodeUri for resource
177+
self._verify_resource_property(str(template_path),
178+
function_logical_id,
179+
"CodeUri",
180+
function_logical_id)
181+
182+
all_artifacts = set(os.listdir(str(resource_artifact_dir)))
183+
actual_files = all_artifacts.intersection(expected_files)
184+
self.assertEquals(actual_files, expected_files)
185+
186+
all_modules = set(os.listdir(str(resource_artifact_dir.joinpath('node_modules'))))
187+
actual_files = all_modules.intersection(expected_modules)
188+
self.assertEquals(actual_files, expected_modules)
189+
190+
def _verify_resource_property(self, template_path, logical_id, property, expected_value):
191+
192+
with open(template_path, 'r') as fp:
193+
template_dict = yaml_parse(fp.read())
194+
self.assertEquals(expected_value, template_dict["Resources"][logical_id]["Properties"][property])

0 commit comments

Comments
 (0)