Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add a nodejs tutorial #397

Merged
merged 8 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CLI
cmake
CMake
CMD
CTRL
declaratively
dev
Diátaxis
Expand All @@ -22,15 +23,20 @@ emacs
entrypoint
entrypoints
executables
expressjs
filesystem
fs
gc
GID
github
GPG
html
https
init
interoperable
io
js
json
Kubernetes
libc
libssl
Expand Down Expand Up @@ -75,6 +81,7 @@ SCons
skopeo
Skopeo
sliceName
snapd
snapcraft
Snapcraft
Snyk's
Expand Down
31 changes: 31 additions & 0 deletions docs/tutorials/code/node-app/rockcraft.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# [docs:rock-metadata]
name: my-node-app
base: [email protected]
version: '1.0'
summary: A ROCK that bundles a simple nodejs app
description: |
This ROCK bundles a recent node runtime to serve a simple "hello-world" app.
license: GPL-3.0
platforms:
amd64:
# [docs:rock-metadata-end]

# [docs:pebble-service]
services:
app:
override: replace
command: node server.js
startup: enabled
on-success: shutdown
on-failure: shutdown
working-dir: /lib/node_modules/node_web_app
# [docs:pebble-service-end]

# [docs:app-part]
parts:
app:
plugin: npm
npm-include-node: True
npm-node-version: "21.1.0"
source: src/
# [docs:app-part-end]
13 changes: 13 additions & 0 deletions docs/tutorials/code/node-app/src/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "node_web_app",
"version": "1.0.0",
"description": "Node.js on a ROCK",
"author": "First Last <[email protected]>",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2"
}
}
14 changes: 14 additions & 0 deletions docs/tutorials/code/node-app/src/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

const express = require('express')
const app = express()
const port = 8080
const host = '0.0.0.0'

app.get('/', (req, res) => {
res.send('Hello World from inside the ROCK!');
});

app.listen(port, host, () => {
console.log(`Running on http://${host}:${port}`);
});
50 changes: 50 additions & 0 deletions docs/tutorials/code/node-app/task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

summary: donejs tutorial

execute: |
# [docs:install-rockcraft]
sudo snap install rockcraft --classic --edge
# [docs:install-rockcraft-end]

# [docs:build-rock]
rockcraft pack
# [docs:build-rock-end]

# [docs:check-rock]
ls my-node-app_1.0_amd64.rock
# [docs:check-rock-end]

# test container execution
docker images

# [docs:skopeo-copy]
sudo /snap/rockcraft/current/bin/skopeo --insecure-policy copy oci-archive:my-node-app_1.0_amd64.rock docker-daemon:my-node-app:1.0
# [docs:skopeo-copy-end]

rm *.rock
docker images my-node-app:1.0

# When the container is started it launches the app listening on port 8080
# NOTE: the convoluted code here is to expose the "docker run" line cleanly
# to the documentation that uses it.
cat <<EOF > run-container.sh
# [docs:run-container]
docker run --name my-node-app -p 8000:8080 my-node-app:1.0
# [docs:run-container-end]
EOF

bash run-container.sh &

# Wait for a bit for the app to come online
retry -n 5 --wait 2 curl localhost:8000

curl localhost:8000 | MATCH "Hello World from inside the ROCK"

# [docs:stop-container]
docker stop my-node-app
# [docs:stop-container-end]

restore: |
rm -f ./*.rock
docker rm -f my-node-app
docker rmi -f my-node-app:1.0
1 change: 1 addition & 0 deletions docs/tutorials/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ reproducible steps.
Chisel packages into a ROCK <chisel.rst>
Publish a ROCK <publish-a-rock.rst>
Migrate a Docker image to a Chiselled ROCK <migrate-to-chiselled-rock.rst>
Bundle a Node.js app into a ROCK <node-app.rst>
150 changes: 150 additions & 0 deletions docs/tutorials/node-app.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
Bundle a Node.js app into a ROCK
tigarmo marked this conversation as resolved.
Show resolved Hide resolved
********************************

This tutorial describes the steps needed to bundle a typical Node.js application
into a ROCK.

Prerequisites
-------------
- snap enabled system (https://snapcraft.io/docs/installing-snapd)
- LXD installed
(https://documentation.ubuntu.com/lxd/en/latest/installing/)
- Docker installed (https://snapcraft.io/docker)
- a text editor


Install Rockcraft
-----------------

Install Rockcraft on your host:

.. literalinclude:: code/node-app/task.yaml
:language: bash
:start-after: [docs:install-rockcraft]
:end-before: [docs:install-rockcraft-end]
:dedent: 2


Project Setup
-------------

Starting in an empty folder, create a ``src/`` subdirectory. Inside it, add two
files:

The first one is the ``package.json`` listing of dependencies, with the
following contents:

.. literalinclude:: code/node-app/src/package.json
:caption: package.json
:language: json

The second file is our sample app, a simple "hello world" server. Still inside
``src/``, add the following contents to ``server.js``:

.. literalinclude:: code/node-app/src/server.js
:caption: server.js
:language: javascript

Next, we'll setup the Rockcraft project. In the original empty folder, create
an empty file called ``rockcraft.yaml``. Then add the following snippets, one
after the other:

Add the metadata that describes your ROCK, such as its name and licence:

.. literalinclude:: code/node-app/rockcraft.yaml
:caption: rockcraft.yaml
:language: yaml
:start-after: [docs:rock-metadata]
:end-before: [docs:rock-metadata-end]

Add the container entrypoint, as a `Pebble`_ service:

.. literalinclude:: code/node-app/rockcraft.yaml
:caption: rockcraft.yaml
:language: yaml
:start-after: [docs:pebble-service]
:end-before: [docs:pebble-service-end]

Finally, add a part that describes how to build the app created in the ``src/``
directory using the ``npm`` plugin:

.. literalinclude:: code/node-app/rockcraft.yaml
:caption: rockcraft.yaml
:language: yaml
:start-after: [docs:app-part]
:end-before: [docs:app-part-end]


Pack the ROCK with Rockcraft
----------------------------

To build the ROCK, run:

.. literalinclude:: code/node-app/task.yaml
:language: bash
:start-after: [docs:build-rock]
:end-before: [docs:build-rock-end]
:dedent: 2

At the end of the process, a new ROCK file should be present in the current
directory:

.. literalinclude:: code/node-app/task.yaml
:language: bash
:start-after: [docs:check-rock]
:end-before: [docs:check-rock-end]
:dedent: 2

Run the ROCK in Docker
----------------------

First, import the recently created ROCK into Docker:

.. literalinclude:: code/node-app/task.yaml
:language: bash
:start-after: [docs:skopeo-copy]
:end-before: [docs:skopeo-copy-end]
:dedent: 2

Since the ROCK bundles a web-app, we'll first start serving that app on local
port 8000:

.. literalinclude:: code/node-app/task.yaml
:language: bash
:start-after: [docs:run-container]
:end-before: [docs:run-container-end]
:dedent: 2

The output will look similar to this, indicating that Pebble started the ``app``
service:

.. code-block:: text

2023-10-30T12:37:33.654Z [pebble] Started daemon.
2023-10-30T12:37:33.659Z [pebble] POST /v1/services 3.878846ms 202
2023-10-30T12:37:33.659Z [pebble] Started default services with change 1.
2023-10-30T12:37:33.663Z [pebble] Service "app" starting: node server.js
2023-10-30T12:37:33.864Z [app] Running on http://0.0.0.0:8080

Next, open your web browser and navigate to ``http://localhost:8000``. You
should see a blank page with a "Hello World from inside the ROCK!" message.
Success!

You can now stop the running container by either interrupting it with CTRL+C or
by running the following in another terminal:

.. literalinclude:: code/node-app/task.yaml
:language: bash
:start-after: [docs:stop-container]
:end-before: [docs:stop-container-end]
:dedent: 2

References
----------

The sample app code comes from the "Hello world example" Express tutorial,
available at https://expressjs.com/en/starter/hello-world.html.


.. _`Pebble`: https://github.com/canonical/pebble
.. _`NodeSource`: https://nodesource.com/