Skip to content

Commit ce1f1eb

Browse files
committed
Port Buildpack author guide to NodeJS
Simplify the buildpack author guide, using NodeJS instead of Ruby. The switch to NodeJS makes multi-arch demos more striaghtforward Signed-off-by: Aidan Delaney <[email protected]>
1 parent 80c8a16 commit ce1f1eb

File tree

12 files changed

+362
-453
lines changed

12 files changed

+362
-453
lines changed

content/docs/buildpack-author-guide/create-buildpack/adding-bill-of-materials.md

Lines changed: 50 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ You can find some of this information using `pack` via its `inspect-image` comma
1717

1818
<!-- test:exec -->
1919
```bash
20-
pack inspect-image test-ruby-app
20+
pack inspect-image test-node-js-app
2121
```
2222
<!--+- "{{execute}}"+-->
2323
You should see the following:
@@ -30,28 +30,27 @@ Run Images:
3030
3131
Buildpacks:
3232
ID VERSION HOMEPAGE
33-
examples/ruby 0.0.1 -
33+
examples/node-js 0.0.1 -
3434
3535
Processes:
3636
TYPE SHELL COMMAND ARGS WORK DIR
37-
web (default) bash bundle exec ruby app.rb /workspace
38-
worker bash bundle exec ruby worker.rb /workspace
37+
web (default) bash node-js app.js /workspace
3938
```
4039

41-
Apart from the above standard metadata, buildpacks can also populate information about the dependencies they have provided in form of a `Bill-of-Materials`. Let's see how we can use this to populate information about the version of `ruby` that was installed in the output app image.
40+
Apart from the above standard metadata, buildpacks can also populate information about the dependencies they have provided in form of a `Bill-of-Materials`. Let's see how we can use this to populate information about the version of `node-js` that was installed in the output app image.
4241

43-
To add the `ruby` version to the output of `pack download sbom`, we will have to provide a [Software `Bill-of-Materials`](https://en.wikipedia.org/wiki/Software_bill_of_materials) (`SBOM`) containing this information. There are three "standard" ways to report SBOM data. You'll need to choose to use one of [CycloneDX](https://cyclonedx.org/), [SPDX](https://spdx.dev/) or [Syft](https://github.com/anchore/syft) update the `ruby.sbom.<ext>` (where `<ext>` is the extension appropriate for your SBOM standard, one of `cdx.json`, `spdx.json` or `syft.json`) at the end of your `build` script. Discussion of which SBOM format to choose is outside the scope of this tutorial, but we will note that the SBOM format you choose to use is likely to be the output format of any SBOM scanner (eg: [`syft cli`](https://github.com/anchore/syft)) you might choose to use. In this example we will use the CycloneDX json format.
42+
To add the `node-js` version to the output of `pack download sbom`, we will have to provide a [Software `Bill-of-Materials`](https://en.wikipedia.org/wiki/Software_bill_of_materials) (`SBOM`) containing this information. There are three "standard" ways to report SBOM data. You'll need to choose to use one of [CycloneDX](https://cyclonedx.org/), [SPDX](https://spdx.dev/) or [Syft](https://github.com/anchore/syft) update the `node-js.sbom.<ext>` (where `<ext>` is the extension appropriate for your SBOM standard, one of `cdx.json`, `spdx.json` or `syft.json`) at the end of your `build` script. Discussion of which SBOM format to choose is outside the scope of this tutorial, but we will note that the SBOM format you choose to use is likely to be the output format of any SBOM scanner (eg: [`syft cli`](https://github.com/anchore/syft)) you might choose to use. In this example we will use the CycloneDX json format.
4443

4544
First, annotate the `buildpack.toml` to specify that it emits CycloneDX:
4645

47-
<!-- test:file=ruby-buildpack/buildpack.toml -->
46+
<!-- test:file=node-js-buildpack/buildpack.toml -->
4847
```toml
4948
# Buildpack API version
5049
api = "0.8"
5150

5251
# Buildpack ID and metadata
5352
[buildpack]
54-
id = "examples/ruby"
53+
id = "examples/node-js"
5554
version = "0.0.1"
5655
sbom-formats = [ "application/vnd.cyclonedx+json" ]
5756

@@ -69,177 +68,136 @@ Then, in our buildpack implementation we will generate the necessary SBOM metada
6968
```bash
7069
# ...
7170

72-
# Append a Bill-of-Materials containing metadata about the provided ruby version
73-
cat >> "$layersdir/ruby.sbom.cdx.json" << EOL
71+
# Append a Bill-of-Materials containing metadata about the provided node-js version
72+
cat >> "${layersdir}/node-js.sbom.cdx.json" << EOL
7473
{
7574
"bomFormat": "CycloneDX",
7675
"specVersion": "1.4",
7776
"version": 1,
7877
"components": [
7978
{
8079
"type": "library",
81-
"name": "ruby",
82-
"version": "$ruby_version"
80+
"name": "node-js",
81+
"version": "$node-js_version"
8382
}
8483
]
8584
}
8685
EOL
8786
```
8887

89-
We can also add an SBOM entry for each dependency listed in `Gemfile.lock`. Here we use `jq` to add a new record to the `components` array in `bundler.sbom.cdx.json`:
88+
We can also add an SBOM entry for each dependency listed in `package.json`. Here we use `jq` to add a new record to the `components` array in `bundler.sbom.cdx.json`:
9089

9190
```bash
92-
crubybom="${layersdir}/ruby.sbom.cdx.json"
93-
cat >> ${rubybom} << EOL
91+
cnode-jsbom="${layersdir}/node-js.sbom.cdx.json"
92+
cat >> ${node-jsbom} << EOL
9493
{
9594
"bomFormat": "CycloneDX",
9695
"specVersion": "1.4",
9796
"version": 1,
9897
"components": [
9998
{
10099
"type": "library",
101-
"name": "ruby",
102-
"version": "$ruby_version"
100+
"name": "node-js",
101+
"version": "$node-js_version"
103102
}
104103
]
105104
}
106105
EOL
107-
if [[ -f Gemfile.lock ]] ; then
106+
if [[ -f package.json ]] ; then
108107
for gem in $(gem dep -q | grep ^Gem | sed 's/^Gem //')
109108
do
110109
version=${gem##*-}
111110
name=${gem%-${version}}
112111
DEP=$(jq --arg name "${name}" --arg version "${version}" \
113112
'.components[.components| length] |= . + {"type": "library", "name": $name, "version": $version}' \
114-
"${rubybom}")
115-
echo ${DEP} > "${rubybom}"
113+
"${node-jsbom}")
114+
echo ${DEP} > "${node-jsbom}"
116115
done
117116
fi
118117
```
119118

120-
Your `ruby-buildpack/bin/build`<!--+"{{open}}"+--> script should look like the following:
119+
Your `node-js-buildpack/bin/build`<!--+"{{open}}"+--> script should look like the following:
121120

122-
<!-- test:file=ruby-buildpack/bin/build -->
121+
<!-- test:file=node-js-buildpack/bin/build -->
123122
```bash
124123
#!/usr/bin/env bash
125124
set -eo pipefail
126125

127-
echo "---> Ruby Buildpack"
126+
echo "---> NodeJS Buildpack"
128127

129128
# 1. GET ARGS
130129
layersdir=$1
131130
plan=$3
132131

133132
# 2. CREATE THE LAYER DIRECTORY
134-
rubylayer="$layersdir"/ruby
135-
mkdir -p "$rubylayer"
136-
137-
# 3. DOWNLOAD RUBY
138-
ruby_version=$(cat "$plan" | yj -t | jq -r '.entries[] | select(.name == "ruby") | .metadata.version')
139-
echo "---> Downloading and extracting Ruby $ruby_version"
140-
ruby_url=https://s3-external-1.amazonaws.com/heroku-buildpack-ruby/heroku-22/ruby-$ruby_version.tgz
141-
wget -q -O - "$ruby_url" | tar -xzf - -C "$rubylayer"
142-
143-
# 4. MAKE RUBY AVAILABLE DURING LAUNCH
144-
echo -e '[types]\nlaunch = true' > "$layersdir/ruby.toml"
145-
146-
# 5. MAKE RUBY AVAILABLE TO THIS SCRIPT
147-
export PATH="$rubylayer"/bin:$PATH
148-
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}"$rubylayer/lib"
149-
150-
# 6. INSTALL GEMS
151-
# Compares previous Gemfile.lock checksum to the current Gemfile.lock
152-
bundlerlayer="$layersdir/bundler"
153-
local_bundler_checksum=$((sha256sum Gemfile.lock || echo 'DOES_NOT_EXIST') | cut -d ' ' -f 1)
154-
remote_bundler_checksum=$(cat "$layersdir/bundler.toml" | yj -t | jq -r .metadata.checksum 2>/dev/null || echo 'DOES_NOT_EXIST')
155-
# Always set the types table so that we re-use the appropriate layers
156-
echo -e '[types]\ncache = true\nlaunch = true' >> "$layersdir/bundler.toml"
157-
158-
if [[ -f Gemfile.lock && $local_bundler_checksum == $remote_bundler_checksum ]] ; then
159-
# Determine if no gem dependencies have changed, so it can reuse existing gems without running bundle install
160-
echo "---> Reusing gems"
161-
bundle config --local path "$bundlerlayer" >/dev/null
162-
bundle config --local bin "$bundlerlayer/bin" >/dev/null
163-
else
164-
# Determine if there has been a gem dependency change and install new gems to the bundler layer; re-using existing and un-changed gems
165-
echo "---> Installing gems"
166-
mkdir -p "$bundlerlayer"
167-
cat >> "$layersdir/bundler.toml" << EOL
168-
[metadata]
169-
checksum = "$local_bundler_checksum"
170-
EOL
171-
bundle config set --local path "$bundlerlayer" && bundle install && bundle binstubs --all --path "$bundlerlayer/bin"
133+
node-js_layer="${layersdir}"/node-js
134+
mkdir -p "${node-js_layer}"
172135

173-
fi
136+
# 3. DOWNLOAD node-js
137+
node-js_version=$(cat "$plan" | yj -t | jq -r '.entries[] | select(.name == "node-js") | .metadata.version')
138+
echo "---> Downloading and extracting NodeJS"
139+
node-js_url=https://nodejs.org/dist/v18.18.1/node-v18.18.1-linux-x64.tar.xz
140+
wget -q -O - "$node-js_url" | tar -xxf - -C "${node-js_layer}"
141+
142+
# 4. MAKE node-js AVAILABLE DURING LAUNCH
143+
echo -e '[types]\nlaunch = true' > "${layersdir}/node-js.toml"
144+
145+
# 5. MAKE node-js AVAILABLE TO THIS SCRIPT
146+
export PATH="${node-js_layer}"/bin:$PATH
147+
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}"${node-js_layer}/lib"
174148

175-
# 7. SET DEFAULT START COMMAND
176-
cat > "$layersdir/launch.toml" << EOL
149+
# 6. SET DEFAULT START COMMAND
150+
cat > "${layersdir}/launch.toml" << EOL
177151
# our web process
178152
[[processes]]
179153
type = "web"
180-
command = "bundle exec ruby app.rb"
154+
command = "node app.js"
181155
default = true
182-
183-
# our worker process
184-
[[processes]]
185-
type = "worker"
186-
command = "bundle exec ruby worker.rb"
187156
EOL
188157

189158
# ========== ADDED ===========
190-
# 8. ADD A SBOM
191-
rubybom="${layersdir}/ruby.sbom.cdx.json"
192-
cat >> ${rubybom} << EOL
159+
# 7. ADD A SBOM
160+
node-jsbom="${layersdir}/node-js.sbom.cdx.json"
161+
cat >> ${node-jsbom} << EOL
193162
{
194163
"bomFormat": "CycloneDX",
195164
"specVersion": "1.4",
196165
"version": 1,
197166
"components": [
198167
{
199168
"type": "library",
200-
"name": "ruby",
201-
"version": "$ruby_version"
169+
"name": "node-js",
170+
"version": "$node-js_version"
202171
}
203172
]
204173
}
205174
EOL
206-
if [[ -f Gemfile.lock ]] ; then
207-
for gem in $(gem dep -q | grep ^Gem | sed 's/^Gem //')
208-
do
209-
version=${gem##*-}
210-
name=${gem%-${version}}
211-
DEP=$(jq --arg name "${name}" --arg version "${version}" \
212-
'.components[.components| length] |= . + {"type": "library", "name": $name, "version": $version}' \
213-
"${rubybom}")
214-
echo ${DEP} > "${rubybom}"
215-
done
216-
fi
217175
```
218176

219177
Then rebuild your app using the updated buildpack:
220178

221179
<!-- test:exec -->
222180
```bash
223-
pack build test-ruby-app --path ./ruby-sample-app --buildpack ./ruby-buildpack
181+
pack build test-node-js-app --path ./node-js-sample-app --buildpack ./node-js-buildpack
224182
```
225183
<!--+- "{{execute}}"+-->
226184

227185
Viewing your bill-of-materials requires extracting (or `download`ing) the bill-of-materials from your local image. This command can take some time to return.
228186

229187
<!-- test:exec -->
230188
```bash
231-
pack sbom download test-ruby-app
189+
pack sbom download test-node-js-app
232190
```
233191
<!--+- "{{execute}}"+-->
234192

235193
The SBOM information is now downloaded to the local file system:
236194

237195
<!-- test:exec -->
238196
```bash
239-
cat layers/sbom/launch/examples_ruby/ruby/sbom.cdx.json | jq -M
197+
cat layers/sbom/launch/examples_node-js/node-js/sbom.cdx.json | jq -M
240198
```
241199

242-
You should find that the included `ruby` version is `3.1.0` as expected.
200+
You should find that the included `node-js` version is `3.1.0` as expected.
243201

244202
<!-- test:assert=contains;ignore-lines=... -->
245203
```text
@@ -250,7 +208,7 @@ You should find that the included `ruby` version is `3.1.0` as expected.
250208
"components": [
251209
{
252210
"type": "library",
253-
"name": "ruby",
211+
"name": "node-js",
254212
"version": "3.1.0"
255213
},
256214
...
@@ -264,7 +222,7 @@ Congratulations! You’ve created your first configurable Cloud Native Buildpack
264222

265223
Now that you've finished your buildpack, how about extending it? Try:
266224

267-
- Caching the downloaded Ruby version
225+
- Caching the downloaded NodeJS version
268226
- [Packaging your buildpack for distribution][package-a-buildpack]
269227

270228
[package-a-buildpack]: /docs/buildpack-author-guide/package-a-buildpack/

0 commit comments

Comments
 (0)