Skip to content

Commit 70a9672

Browse files
authored
Merge pull request #2 from welteki/instructions
2 parents 2b9e36f + c687e60 commit 70a9672

File tree

11 files changed

+354
-168
lines changed

11 files changed

+354
-168
lines changed

README.md

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,118 @@
1-
OpenFaaS Customer Code Sample
1+
# OpenFaaS Function Editor Example
22

3+
This repo contains a sample application that shows how OpenFaaS can be used to build a basic function editor that let's users edit, deploy and invoke custom code from the browser.
4+
5+
The sample app consists of two parts. A frontend implemented as a single page [React](https://react.dev/) application and [Express](https://expressjs.com/) server for the backend API. Users can edit a Node.js function in the UI using a code editor. Clicking the `Publish and Deploy` button deploys the function to OpenFaaS. Once deployed the `Test Function` page can be used to invoke the function, inspect responses and view the function logs.
6+
7+
It sample app is a basic implementation of the use case described in the our blog post: [Integrate FaaS Capabilities into Your Platform with OpenFaaS](https://www.openfaas.com/blog/add-a-faas-capability/)
8+
9+
![Screenshot of the function editor UI](/images/function-editor.png)
10+
11+
## How it works
12+
13+
The application uses readily available OpenFaaS APIs to take user-supplied source-code, produce an OpenFaaS function image and deploy it to OpenFaaS to get a custom HTTP endpoint.
14+
15+
OpenFaaS components used by the sample application:
16+
17+
- [Function Builder API](https://docs.openfaas.com/openfaas-pro/builder/)
18+
19+
Allows code to be submitted, built, and deployed seamlessly.
20+
21+
This REST API accepts a Docker build context and publishes a container image to a remote registry.
22+
23+
- [OpenFaaS REST API](https://docs.openfaas.com/reference/rest-api/)
24+
25+
API for managing and invoking functions, secrets and namespaces.
26+
27+
The OpenFaaS REST API has endpoints to create and manage tenant namespaces, to deploy new functions, list and query existing ones, invoke them and query function logs.
28+
29+
**Overview**
30+
31+
1. User supplied source-code from the editor is send to the backends `/api/publish` endpoint.
32+
2. The publish endpoint prepares the build context using a function template and invokes the [OpenFaaS Function Builder REST API](https://docs.openfaas.com/openfaas-pro/builder/).
33+
3. The OpenFaaS function builder builds the container image for the function and published it to the configured registry.
34+
4. After the function has been published the `/api/publish` endpoint is called. The backend server calls the [OpenFaaS API](https://docs.openfaas.com/reference/rest-api/) to deploy the function.
35+
5. The function is ready to be invoked over HTTP.
36+
37+
A couple of additional OpenFaaS API endpoints are exposed through the backed server that allow users to invoke the function and inspect logs in the UI:
38+
39+
- `/api/invoke` - Proxies the functions HTTP endpoint.
40+
- `/api/logs` - Uses the [OpenFaaS APIs logs endpoint](https://docs.openfaas.com/reference/rest-api/#logs) to get the logs for the function.
41+
42+
> Note: Authentication for the backend API as this is out of scope for this examples. Keep in mind the for a production ready app some form of authentication should be added to protect the API endpoint.
43+
44+
## Quick start
45+
46+
Run the sample application locally.
47+
48+
### Prerequisites
49+
50+
51+
A Kubernetes cluster with OpenFaaS and the [OpenFaaS Function Builder API](https://docs.openfaas.com/openfaas-pro/builder/).
52+
53+
> The Function Builder API provides a simple REST API to create your functions from source code. See [Function Builder API docs](https://docs.openfaas.com/openfaas-pro/builder/) for installation instructions.
54+
55+
You will need a recent version of [Node.js](https://nodejs.org/en) to run the sample app locally.
56+
57+
### Run the app
58+
59+
Install node_modules:
60+
61+
```sh
62+
cd client
63+
npm install
64+
```
65+
66+
**Run the API server**
67+
68+
Configuration parameters:
69+
70+
- `IMAGE_PREFIX` - Image prefix used for pushing the images, e.g. `docker.io/openfaas`. Make sur your function builder [has the correct permissions](https://github.com/openfaas/faas-netes/tree/master/chart/pro-builder#registry-authentication) to push to this registry.
71+
- `BUILDER_URL` - URl of the function builder API (default: http://127.0.0.1:8081)
72+
- `BUILDER_PAYLOAD_SECRET` - Path the file containing the HMAC signing secret created during the installation of the function builder. (default: "./builder/payload.txt")
73+
- `GATEWAY_URL` - URL of the OpenFaaS Gateway (default: http://127.0.0.1:8080)
74+
- `BASIC_AUTH_SECRET` - Basic auth secret to authenticate with the OpenFaaS Gateway (default: "./secrets/basic-auth-password.txt")
75+
76+
- [Function Builder examples](https://github.com/openfaas/function-builder-examples)
77+
78+
Make sure the pro-builder is port-forwarded to port 8081 on the local host.
79+
80+
```sh
81+
kubectl port-forward \
82+
-n openfaas \
83+
svc/pro-builder 8081:8080
84+
```
85+
86+
Save the HMAC signing secret created during the installation to a file `./client/.secrets/payload.txt`.
87+
88+
```sh
89+
kubectl get secret \
90+
-n openfaas payload-secret -o jsonpath='{.data.payload-secret}' \
91+
| base64 --decode \
92+
> .secrets/payload.txt
93+
```
94+
95+
Port forward the OpenFaaS Gateway:
96+
97+
```sh
98+
kubectl port-forward \
99+
-n openfaas \
100+
svc/gateway 8080:8080
101+
```
102+
103+
Start the server:
104+
105+
```sh
106+
IMAGE_PREFIX="docker.io/your-repo" \
107+
npm run server
108+
```
109+
110+
**Run the frontend**
111+
112+
Start the frontend server:
113+
114+
```sh
115+
npm run dev
116+
```
117+
118+
Access the UI at: `http://127.0.0.1:5173/`

client/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ dist-ssr
2626
/basic-auth-password.txt
2727
/payload.txt
2828

29+
/.secrets

client/server.js

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ import path from 'path';
1313
const __filename = fileURLToPath(import.meta.url);
1414
const __dirname = dirname(__filename);
1515

16+
const imagePrefix = process.env.IMAGE_PREFIX || "ttl.sh/openfaas"
17+
const builderPayloadSecret = process.env.BUILDER_PAYLOAD_SECRET || "./secrets/payload.txt"
18+
const builderURL = process.env.BUILDER_URL || 'http://127.0.0.1:8081'
19+
const basicAuthSecret = process.env.BASIC_AUTH_SECRET || "./secrets/basic-auth-password.txt"
20+
const gatewayURL = process.env.GATEWAY_URL || 'http://127.0.0.1:8080'
21+
1622
// Define templates directory
1723
const TEMPLATES_DIR = path.join(__dirname, 'templates');
1824

@@ -127,7 +133,6 @@ function generateRandomString(length) {
127133
}
128134
return result;
129135
}
130-
131136
// Build function using OpenFaaS CLI
132137
async function buildFunction(functionName, handler, lang, packageJson = null) {
133138
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'builder-'));
@@ -137,7 +142,7 @@ async function buildFunction(functionName, handler, lang, packageJson = null) {
137142
const tag = generateRandomString(8);
138143

139144
// Create the full image name
140-
const image = `registry.digitalocean.com/openfaas-sandbox/${functionName}:${tag}`;
145+
const image = `${imagePrefix}/${functionName}:${tag}`;
141146

142147
// Create stack.yaml first
143148
const stackYaml = `version: 1.0
@@ -206,7 +211,8 @@ functions:
206211
}
207212

208213
// Create build config
209-
let buildConfig = { image, buildArgs: {}, push: true };
214+
let buildConfig = { image, buildArgs: {}, push: false };
215+
console.log(buildConfig)
210216
await fs.writeFile(
211217
path.join(tempDir, 'com.openfaas.docker.config'),
212218
JSON.stringify(buildConfig),
@@ -273,7 +279,7 @@ functions:
273279
);
274280

275281
// Read secret and calculate hash
276-
let secret = await fs.readFile(path.join(__dirname, 'payload.txt'), 'utf8');
282+
let secret = await fs.readFile(builderPayloadSecret, 'utf8');
277283
let data = await fs.readFile(tarFile);
278284
let hash = crypto
279285
.createHmac('sha256', secret.trim())
@@ -285,7 +291,7 @@ functions:
285291
let res = await axios({
286292
data: data,
287293
method: 'post',
288-
url: 'https://builder.sandbox.openfaas.com/build',
294+
url: `${builderURL}/build`,
289295
headers: {
290296
'Content-Type': 'application/octet-stream',
291297
'X-Build-Signature': 'sha256=' + hash,
@@ -375,7 +381,7 @@ app.post('/api/deploy', async (req, res) => {
375381
// Read the password from the basic-auth-password.txt file
376382
let password = '';
377383
try {
378-
password = await fs.readFile(path.join(__dirname, 'basic-auth-password.txt'), 'utf8');
384+
password = await fs.readFile(basicAuthSecret, 'utf8');
379385
password = password.trim(); // Remove any whitespace
380386
} catch (err) {
381387
console.warn('Could not read basic-auth-password.txt, using empty password');
@@ -407,7 +413,7 @@ app.post('/api/deploy', async (req, res) => {
407413
try {
408414
const checkResponse = await axios({
409415
method: 'GET',
410-
url: `https://gateway.sandbox.openfaas.com/system/function/${functionName}`,
416+
url: `${gatewayURL}/system/function/${functionName}`,
411417
headers: headers
412418
});
413419

@@ -427,7 +433,7 @@ app.post('/api/deploy', async (req, res) => {
427433
// Deploy the function using PUT or POST based on whether it exists
428434
const deployResponse = await axios({
429435
method: functionExists ? 'PUT' : 'POST',
430-
url: 'https://gateway.sandbox.openfaas.com/system/functions',
436+
url: `${gatewayURL}/system/functions`,
431437
headers: headers,
432438
data: functionData
433439
});
@@ -480,7 +486,7 @@ app.post('/api/invoke', async (req, res) => {
480486
// Read the password from the basic-auth-password.txt file
481487
let password = '';
482488
try {
483-
password = await fs.readFile(path.join(__dirname, 'basic-auth-password.txt'), 'utf8');
489+
password = await fs.readFile(basicAuthSecret, 'utf8');
484490
password = password.trim(); // Remove any whitespace
485491
} catch (err) {
486492
console.warn('Could not read basic-auth-password.txt, using empty password');
@@ -497,7 +503,7 @@ app.post('/api/invoke', async (req, res) => {
497503
try {
498504
const invokeResponse = await axios({
499505
method: 'POST',
500-
url: `https://gateway.sandbox.openfaas.com/function/${functionName}`,
506+
url: `${gatewayURL}/function/${functionName}`,
501507
headers: headers,
502508
data: payload || {},
503509
validateStatus: false, // This ensures we get headers even for error responses
@@ -546,7 +552,7 @@ app.post('/api/logs', async (req, res) => {
546552
// Read the password from the basic-auth-password.txt file
547553
let password = '';
548554
try {
549-
password = await fs.readFile(path.join(__dirname, 'basic-auth-password.txt'), 'utf8');
555+
password = await fs.readFile(basicAuthSecret, 'utf8');
550556
password = password.trim(); // Remove any whitespace
551557
} catch (err) {
552558
console.warn('Could not read basic-auth-password.txt, using empty password');
@@ -564,7 +570,7 @@ app.post('/api/logs', async (req, res) => {
564570

565571
// Fetch logs from OpenFaaS
566572
const logsResponse = await fetch(
567-
`https://gateway.sandbox.openfaas.com/system/logs?${params.toString()}`,
573+
`${gatewayURL}/system/logs?${params.toString()}`,
568574
{
569575
method: 'GET',
570576
headers: {

client/src/App.css

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,26 @@
1313
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
1414
}
1515

16+
.header {
17+
display: flex;
18+
flex-direction: column;
19+
align-items: center;
20+
justify-content: center;
21+
gap: 1rem;
22+
margin-bottom: 2rem;
23+
position: relative;
24+
}
25+
1626
.title {
1727
font-size: 2.5rem;
18-
color: ##00fff1;
19-
margin-bottom: 0.5rem;
28+
color: var(--text-primary);
29+
margin: 0;
2030
font-weight: 600;
2131
}
2232

2333
.branding {
2434
font-size: 1rem;
25-
color: #888;
26-
margin-bottom: 2rem;
35+
color: var(--text-secondary);
2736
font-style: italic;
2837
}
2938

@@ -36,24 +45,25 @@
3645

3746
.nav-button {
3847
padding: 0.75rem 1.5rem;
39-
background-color: #f0f0f0;
40-
color: #333;
41-
border: 1px solid #ddd;
48+
background-color: var(--button-bg);
49+
color: var(--text-primary);
50+
border: 1px solid var(--border-color);
4251
border-radius: 6px;
4352
cursor: pointer;
4453
font-size: 1rem;
54+
transition: all 0.2s ease;
4555
}
4656

4757
.nav-button:hover {
48-
background-color: #e0e0e0;
58+
background-color: var(--button-hover-bg);
4959
}
5060

5161
.nav-button.active {
52-
background-color: #007bff;
62+
background-color: var(--primary-color);
5363
color: white;
54-
border-color: #0056b3;
64+
border-color: var(--primary-color);
5565
}
5666

5767
.nav-button.active:hover {
58-
background-color: #0056b3;
68+
background-color: var(--primary-hover);
5969
}

client/src/App.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ function App() {
1111

1212
return (
1313
<div className="app">
14-
<h1 className="title">Function Editor</h1>
15-
<div className="branding">Powered by OpenFaaS</div>
14+
<div className="header">
15+
<h1 className="title">Function Editor</h1>
16+
<div className="branding">Powered by OpenFaaS</div>
17+
</div>
1618

1719
<div className="navigation">
1820
<button

0 commit comments

Comments
 (0)