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

feat(run): add Cloud Run + Filestore sample #3288

Merged
merged 110 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
1c6c01e
initial package.json
rogerthatdev Jun 16, 2023
8a8a22b
basic express with test
rogerthatdev Jun 16, 2023
a4150f2
add testing dependencies
rogerthatdev Jun 16, 2023
30a721f
add nonexistant path test
rogerthatdev Jun 16, 2023
e2f513a
refactor test to use chai expect as assert
rogerthatdev Jun 16, 2023
01daf73
add chai to package.json
rogerthatdev Jun 16, 2023
41cc5fe
hide console.log output from test output
rogerthatdev Jun 16, 2023
a340920
label console output
rogerthatdev Jun 16, 2023
c27bb45
semi-colons ;;;;
rogerthatdev Jun 16, 2023
f74b6af
add redirects to mount dir
rogerthatdev Jun 20, 2023
155877b
writeFile function
rogerthatdev Jun 20, 2023
133a300
test for new file write
rogerthatdev Jun 20, 2023
abb8703
return list of files and links
rogerthatdev Jun 20, 2023
f65b681
add test for files
rogerthatdev Jun 20, 2023
c943c48
add Dockerfile and wrapper script
rogerthatdev Jun 20, 2023
b33d1b8
add header
rogerthatdev Jun 21, 2023
6b1b89c
eslint --fix
rogerthatdev Jun 21, 2023
e244cae
add rate limit to app
rogerthatdev Jun 21, 2023
6d8737e
add testing workflows
rogerthatdev Jun 21, 2023
ae52f26
dont store value for path
rogerthatdev Jun 21, 2023
1539b75
Merge branch 'main' into filesystems-sample
pattishin Jun 22, 2023
8a15aed
Merge branch 'main' into filesystems-sample
rogerthatdev Jun 27, 2023
6f2689b
add missing #!
rogerthatdev Jun 27, 2023
a226df9
refactor to use serve-index
rogerthatdev Jun 27, 2023
6224f8c
eslint
rogerthatdev Jun 27, 2023
ac92606
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Jun 27, 2023
0e963df
Merge branch 'filesystems-sample' of https://github.com/GoogleCloudPl…
gcf-owl-bot[bot] Jun 27, 2023
0af99cd
Merge branch 'main' into filesystems-sample
kweinmeister Jun 27, 2023
4af4979
Merge branch 'main' into filesystems-sample
pattishin Jun 28, 2023
963d85e
Update run/filesystem/package.json
rogerthatdev Jun 28, 2023
ad60445
Update run/filesystem/package.json
rogerthatdev Jun 28, 2023
709e007
remove fs from package.json
rogerthatdev Jun 28, 2023
69a73b6
Merge branch 'filesystems-sample' of github.com:GoogleCloudPlatform/n…
rogerthatdev Jun 28, 2023
476c028
typo
rogerthatdev Jun 28, 2023
b4b112c
move port var down
rogerthatdev Jun 28, 2023
668acab
change filename format
rogerthatdev Jun 28, 2023
4efdda0
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Jun 28, 2023
eadcc86
refactor: redirect all to mnt path
rogerthatdev Jun 29, 2023
4c82d59
eslint
rogerthatdev Jun 29, 2023
10322c8
Merge branch 'main' into filesystems-sample
kweinmeister Jun 29, 2023
9b9b813
additional comments
rogerthatdev Jul 5, 2023
1be7a10
separate function for index
rogerthatdev Jul 5, 2023
76748e8
eslint
rogerthatdev Jul 5, 2023
e8eaa58
fix hrefs
rogerthatdev Jul 5, 2023
eebb278
move file prefix var
rogerthatdev Jul 5, 2023
667095d
period
rogerthatdev Jul 5, 2023
b604c04
Merge branch 'main' into filesystems-sample
rogerthatdev Jul 5, 2023
62c676c
rename wrapper script
rogerthatdev Jul 5, 2023
0d3d1b6
update wrapper
rogerthatdev Jul 5, 2023
926cd1f
comments and sample description
rogerthatdev Jul 6, 2023
51eec6b
cross-site scripting
rogerthatdev Jul 6, 2023
890a349
add try catch
rogerthatdev Jul 6, 2023
c68f1c1
better error response
rogerthatdev Jul 6, 2023
07b5329
sanitize res
rogerthatdev Jul 6, 2023
93df837
use node:20-slim
rogerthatdev Jul 6, 2023
acfe819
rm unused dep
rogerthatdev Jul 7, 2023
757c389
arrow functions and consts
rogerthatdev Jul 8, 2023
0657f82
eslint
rogerthatdev Jul 8, 2023
605f059
no mutations
rogerthatdev Jul 8, 2023
5a403b7
unused var
rogerthatdev Jul 8, 2023
df0e6ec
Merge branch 'filesystems-sample' of github.com:GoogleCloudPlatform/n…
rogerthatdev Jul 8, 2023
daaf07d
remove unneeded async
rogerthatdev Jul 11, 2023
9eafb7c
Merge branch 'filesystems-sample' of github.com:GoogleCloudPlatform/n…
rogerthatdev Jul 11, 2023
18e9695
lint
rogerthatdev Jul 11, 2023
1b81ef0
Merge branch 'main' into filesystems-sample
rogerthatdev Jul 11, 2023
d90097d
add sigterm handling
rogerthatdev Jul 11, 2023
789d088
Merge branch 'filesystems-sample' of github.com:GoogleCloudPlatform/n…
rogerthatdev Jul 11, 2023
e901a4c
fix sigterm handling
rogerthatdev Jul 11, 2023
dc441f7
formatting
rogerthatdev Jul 11, 2023
5b58bfe
e2e test cloudbuild config
rogerthatdev Jul 13, 2023
b86fbc9
parallel delete resources
rogerthatdev Jul 13, 2023
736d884
fix cleanup step
rogerthatdev Jul 13, 2023
f187d8d
add waitfor
rogerthatdev Jul 13, 2023
38deec6
break cleanup into 3 steps
rogerthatdev Jul 13, 2023
8b46152
clean up AR Repo build
rogerthatdev Jul 13, 2023
671d85e
split build config into setup and cleanup
rogerthatdev Jul 13, 2023
577e217
e2e test boilerplate
rogerthatdev Jul 13, 2023
a6e4fc9
remove unused subs from cleanup yaml
rogerthatdev Jul 13, 2023
4c40164
update waitFor in cleanup yaml
rogerthatdev Jul 13, 2023
de5585f
recombine build yamls
rogerthatdev Jul 13, 2023
a868e08
lint
rogerthatdev Jul 13, 2023
f993889
split cloudbuild config
rogerthatdev Jul 13, 2023
dabf84a
split tests to system.test.js
rogerthatdev Jul 14, 2023
65be250
lint
rogerthatdev Jul 14, 2023
b7f67d9
system.test add auth
rogerthatdev Jul 14, 2023
8363f5d
add before and after to e2e test
rogerthatdev Jul 14, 2023
637dd10
add endpoint test to e2e
rogerthatdev Jul 15, 2023
6c237fe
update package.json
rogerthatdev Jul 15, 2023
79d6047
headers
rogerthatdev Jul 15, 2023
286563c
typo
rogerthatdev Jul 17, 2023
2f0c3b5
add to system e2e tests
rogerthatdev Jul 17, 2023
572f490
kokoro config
rogerthatdev Jul 17, 2023
85d5044
Merge branch 'main' into filesystems-sample
rogerthatdev Jul 17, 2023
b434296
remove cfg
rogerthatdev Jul 18, 2023
6ac4972
typo
rogerthatdev Jul 18, 2023
fde18ff
remove server.close
rogerthatdev Jul 18, 2023
fe899cd
refactor system.test.js to match other samples
rogerthatdev Jul 19, 2023
48f76ec
add test for generated txt to e2e
rogerthatdev Jul 19, 2023
404e6f9
add comment re: rate limit
rogerthatdev Jul 19, 2023
c63a493
remove allow unauthenticated
rogerthatdev Jul 19, 2023
bff9835
lint: unused var
rogerthatdev Jul 19, 2023
c7e312c
add unit test for file generation
rogerthatdev Jul 19, 2023
06e4f41
Merge branch 'main' into filesystems-sample
rogerthatdev Jul 19, 2023
b9d0595
clarify rate limit comment
rogerthatdev Jul 20, 2023
8336b7d
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Jul 20, 2023
84c8dbf
add wait -n to run script
rogerthatdev Jul 20, 2023
3ac013a
use fs/promises
rogerthatdev Jul 22, 2023
8280e8a
write file before generating html
rogerthatdev Jul 22, 2023
0053c40
Merge branch 'main' into filesystems-sample
rogerthatdev Jul 24, 2023
5470c1f
Merge branch 'main' into filesystems-sample
rogerthatdev Jul 25, 2023
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
62 changes: 62 additions & 0 deletions .github/workflows/run-filesystem.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: run-filesystem
on:
push:
branches:
- main
paths:
- 'run/filesystem/**'
- '.github/workflows/run-filesystem.yaml'
pull_request:
paths:
- 'run/filesystem/**'
- '.github/workflows/run-filesystem.yaml'
pull_request_target:
types: [labeled]
paths:
- 'run/filesystem/**'
- '.github/workflows/run-filesystem.yaml'
schedule:
- cron: '0 0 * * 0'
jobs:
test:
# Ref: https://github.com/google-github-actions/auth#usage
permissions:
contents: 'read'
id-token: 'write'
if: github.event.action != 'labeled' || github.event.label.name == 'actions:force-run'
uses: ./.github/workflows/test.yaml
with:
name: 'run-filesystem'
path: 'run/filesystem'
remove_label:
# Ref: https://github.com/google-github-actions/auth#usage
permissions:
contents: 'read'
id-token: 'write'
if: |
github.event.action == 'labeled' &&
github.event.label.name == 'actions:force-run' &&
always()
uses: ./.github/workflows/remove-label.yaml
flakybot:
# Ref: https://github.com/google-github-actions/auth#usage
permissions:
contents: 'read'
id-token: 'write'
if: github.event_name == 'schedule' && always() # always() submits logs even if tests fail
uses: ./.github/workflows/flakybot.yaml
needs: [test]
1 change: 1 addition & 0 deletions .github/workflows/utils/workflows.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"monitoring/snippets",
"opencensus",
"retail",
"run/filesystem",
"scheduler",
"secret-manager",
"service-directory/snippets",
Expand Down
50 changes: 50 additions & 0 deletions run/filesystem/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# [START cloudrun_fs_dockerfile]

# Use the official Node.js image.
# https://hub.docker.com/_/node
FROM node:20
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved

# Install system dependencies
RUN apt-get update -y && apt-get install -y \
tini \
nfs-common \
&& apt-get clean

# Set fallback mount directory
ENV MNT_DIR /mnt/nfs/filestore

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY package*.json ./

# Install production dependencies.
RUN npm install --only=production

# Copy local code to the container image.
COPY . ./

# Ensure the script is executable
RUN chmod +x /app/wrapper.sh

# Use tini to manage zombie processes and signal forwarding
# https://github.com/krallin/tini
ENTRYPOINT ["/usr/bin/tini", "--"]

# Pass the wrapper script as arguments to tini
CMD ["/app/wrapper.sh"]
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
# [END cloudrun_fs_dockerfile]
69 changes: 69 additions & 0 deletions run/filesystem/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const express = require('express');
const rateLimit = require('express-rate-limit');
const fs = require('fs');
const serveIndex = require('serve-index');

const app = express();
const limit = rateLimit({
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Rate limit exceeded',
headers: true,
});
const mntDir = process.env.MNT_DIR || '/mnt/nfs/filestore';
const filePrefix = process.env.FILENAME || 'test';
const port = parseInt(process.env.PORT) || 8080;
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved

app.use(limit);
app.use('/filesystem', express.static(mntDir));
app.use('/filesystem', serveIndex(mntDir));
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved

app.get('/', async (req, res) => {
await writeFile(mntDir);
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
let html =
'<html><body>A new file is generated each time this page is reloaded.<p>Files created on filesystem:<p>';
fs.readdirSync(mntDir).forEach(filename => {
html += `<a href="${req.protocol}://${req.get(
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
'host'
)}/filesystem/${filename}">${filename}</a><br>`;
});
html += '</body></html>';
res.send(html);
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
});

app.all('*', (req, res) => {
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
res.redirect('/');
});

app.listen(port, () => {
console.log(`Listening on port ${port}`);
});

async function writeFile(path) {
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
let date = new Date();
date = date.toString().split(' ').join('-');
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
const filename = `${filePrefix}-${date}.txt`;
const contents = `This test file was created on ${date}\n`;
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved

fs.writeFile(`${path}/${filename}`, contents, err => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callbacks seem a bit outdated. A Node.js SME should weigh in

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sofisl can you weigh in here? Thanks!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could use promises here! We could use the new fs promises module, like so, with a try-catch block.

if (err) {
console.error(`could not write to file system:\n${err}`);
}
});
}

rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
module.exports = app;
26 changes: 26 additions & 0 deletions run/filesystem/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "index.js",
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
"description": "Demonstrate accessing a mounted network filesystem from a Cloud Run service.",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "c8 mocha test/index.test.js --exit",
"system-test": "NAME=Cloud c8 mocha test/system.test.js --timeout=180000"
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
},
"author": "Google LLC",
"license": "Apache-2.0",
"dependencies": {
"express": "^4.18.2",
"express-rate-limit": "^6.7.0",
"fs": "0.0.1-security",
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
"serve-index": "^1.9.1"
},
"devDependencies": {
"c8": "^7.14.0",
"chai": "^4.3.7",
"mocha": "^10.2.0",
"mock-fs": "^5.2.0",
"supertest": "^6.3.3"
}
}
73 changes: 73 additions & 0 deletions run/filesystem/test/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const path = require('path');
const supertest = require('supertest');
const assert = require('chai').expect;
const mock = require('mock-fs');
const fs = require('fs');
const mntDir = process.env.MNT_DIR || '/mnt/nfs/filestore';
let request;
const testFileContents = 'Test file contents.';
describe('Unit tests', () => {
const defaultLogFunction = console.log;
let consoleOutput = '\n';
before(() => {
const app = require(path.join(__dirname, '..', 'index'));
console.log = msg => {
consoleOutput += msg + '\n';
};
mock({
[mntDir]: mock.directory({
items: {
'test-file.txt': `${testFileContents}`,
},
}),
});
request = supertest(app);
});
after(() => {
mock.restore();
console.log = defaultLogFunction;
console.log('\nconsole.log output:\n---------');
console.log(consoleOutput);
console.log('---------');
});
describe('GET /', () => {
it('responds with 200 Ok', async () => {
const response = await request.get('/');
assert(response.status).to.eql(200);
});
it('writes a file', async () => {
await request.get(mntDir);
fs.readdir(mntDir, (err, files) => {
assert(files.length).to.eql(2);
});
});
});
describe('GET nonexistant path', () => {
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
it('responds with 302 Found and redirects to /', async () => {
const response = await request.get('/nonexistant');
assert(response.header.location).to.eql('/');
assert(response.status).to.eql(302);
});
});
describe('GET file path', () => {
it('responds with file contents and 200 Ok', async () => {
const response = await request.get('/filesystem/test-file.txt');
assert(response.text).to.eql(`${testFileContents}`);
assert(response.status).to.eql(200);
});
});
});
28 changes: 28 additions & 0 deletions run/filesystem/wrapper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# [START cloudrun_fs_script]
#!/usr/bin/env bash
set -eo pipefail

# Create mount directory for service.
mkdir -p $MNT_DIR

echo "Mounting Cloud Filestore."
mount -o nolock $FILESTORE_IP_ADDRESS:/$FILE_SHARE_NAME $MNT_DIR
echo "Mounting completed."

npm start
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved
# [END cloudrun_fs_script]
rogerthatdev marked this conversation as resolved.
Show resolved Hide resolved