Skip to content

Commit 9d3e731

Browse files
weltekialexellis
authored andcommitted
Update the go example to use the OpenFaaS go-sdk
Signed-off-by: Han Verstraete (OpenFaaS Ltd) <[email protected]>
1 parent 939362b commit 9d3e731

File tree

7 files changed

+88
-164
lines changed

7 files changed

+88
-164
lines changed

README.md

+17-8
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ kubectl get secret \
2323
The directory [hello-world](./hello-world/) can be passed as the handler directory to the examples. It contains a javascript handler for a function. The hello-world directory was created by running:
2424

2525
```bash
26-
faas-cli new hello-world --lang node17
26+
faas-cli new hello-world --lang node20
2727
```
2828

2929
You can use the `faas-cli` to create any other handler to try these example scripts with.
@@ -43,7 +43,7 @@ Run the script
4343
python3 python-request/build.py \
4444
--image ttl.sh/hello-world-python:1h \
4545
--handler ./hello-world \
46-
--lang node17
46+
--lang node20
4747
```
4848

4949
## Use NodeJS to call the pro-builder
@@ -64,7 +64,7 @@ The script takes three arguments:
6464
node nodejs-request/index.js \
6565
'ttl.sh/hello-world-node:1h' \
6666
./hello-world \
67-
node17
67+
node20
6868
```
6969

7070
## Use php to call the pro-builder
@@ -85,16 +85,25 @@ The script takes three arguments:
8585
php php-request/build.php \
8686
--image=ttl.sh/hello-world-php:1h \
8787
--handler=./hello-world \
88-
--lang=node17
88+
--lang=node20
8989
```
9090

91-
## Use go to call the pro-builder
92-
The [go-request](./go-request/) directory has an example on how to invoke the Function Builder API from go. Run the `main.go` script with the required flags to turn a function handler into a container image.
91+
## Use go and the OpenFaaS go-sdk to call the pro-builder
92+
The [go-request](./go-request/) directory has an example on how to invoke the Function Builder API from go using the official OpenFaaS go-sdk. Run the `main.go` script with the required flags to turn a function handler into a container image.
93+
94+
The example script does not fetch templates. How this is done is up to your implementation. Templates can be pulled from a git repository, copied from an S3 bucket, downloaded with an http call.
95+
96+
To try out this example you can fetch them using the faas-cli before running the script:
97+
```sh
98+
faas-cli template store pull node20
99+
```
93100

94101
Run the script
95102
```bash
96103
go run go-request/main.go \
97104
-image=ttl.sh/hello-world-go:1h \
98105
-handler=./hello-world \
99-
-lang=node17
100-
```
106+
-lang=node20 \
107+
-name="hello-world" \
108+
-platforms="linux/amd64"
109+
```

go-request/main.go

+60-152
Original file line numberDiff line numberDiff line change
@@ -1,199 +1,107 @@
11
package main
22

33
import (
4-
"archive/tar"
54
"bytes"
6-
"crypto/sha256"
7-
"encoding/hex"
8-
"encoding/json"
95
"flag"
106
"fmt"
11-
"io"
12-
"io/ioutil"
137
"log"
148
"net/http"
9+
"net/url"
1510
"os"
16-
"os/exec"
17-
"path"
18-
"path/filepath"
1911
"strings"
2012

21-
hmac "github.com/alexellis/hmac/v2"
13+
"github.com/openfaas/go-sdk/builder"
2214
)
2315

24-
type buildConfig struct {
25-
Image string `json:"image"`
26-
BuildArgs map[string]string `json:"buildArgs,omitempty"`
27-
}
28-
29-
type buildResult struct {
30-
Log []string `json:"log"`
31-
Image string `json:"image"`
32-
Status string `json:"status"`
33-
}
34-
35-
const ConfigFileName = "com.openfaas.docker.config"
36-
3716
var (
38-
image string
39-
handler string
40-
lang string
17+
image string
18+
handler string
19+
lang string
20+
functionName string
21+
platformsStr string
22+
buildArgsStr string
4123
)
4224

4325
func main() {
4426
flag.StringVar(&image, "image", "", "Docker image name to build")
4527
flag.StringVar(&handler, "handler", "", "Directory with handler for function, e.g. handler.js")
46-
flag.StringVar(&lang, "lang", "", "Language or template to use, e.g. node17")
28+
flag.StringVar(&lang, "lang", "", "Language or template to use, e.g. node20")
29+
flag.StringVar(&functionName, "name", "", "Name of the function")
30+
flag.StringVar(&platformsStr, "platforms", "linux/amd64", "Comma separated list of target platforms for multi-arch image builds.")
31+
flag.StringVar(&buildArgsStr, "build-args", "", "Additional build arguments for the docker build in the form of key1=value1,key2=value2")
4732
flag.Parse()
4833

49-
tempDir, err := os.MkdirTemp(os.TempDir(), "builder-*")
50-
if err != nil {
51-
log.Fatal(err)
52-
}
53-
defer os.RemoveAll(tempDir)
54-
55-
tarPath := path.Join(tempDir, "req.tar")
56-
fmt.Println(tarPath)
57-
58-
if err := shrinkwrap(image, handler, lang); err != nil {
59-
log.Fatal(err)
60-
}
61-
62-
if err := makeTar(buildConfig{Image: image}, path.Join("build", "context"), tarPath); err != nil {
63-
log.Fatalf("Failed to create tar file: %s", err)
64-
}
34+
platforms := strings.Split(platformsStr, ",")
35+
buildArgs := parseBuildArgs(buildArgsStr)
6536

66-
res, err := callBuilder(tarPath)
37+
// Get the HMAC secret used for payload authentication with the builder API.
38+
payloadSecret, err := os.ReadFile("payload.txt")
6739
if err != nil {
68-
log.Fatalf("Failed to call builder API: %s", err)
69-
}
70-
defer res.Body.Close()
71-
72-
data, _ := io.ReadAll(res.Body)
73-
74-
result := buildResult{}
75-
if err := json.Unmarshal(data, &result); err != nil {
76-
log.Fatalf("Failed to unmarshal build result: %s", err)
40+
log.Fatal(err)
7741
}
42+
payloadSecret = bytes.TrimSpace(payloadSecret)
7843

79-
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted {
80-
log.Fatalf("Unable to build image %s: %s", image, result.Status)
81-
}
44+
// Initialize a new builder client.
45+
builderURL, _ := url.Parse("http://127.0.0.1:8081")
46+
b := builder.NewFunctionBuilder(builderURL, http.DefaultClient, builder.WithHmacAuth(string(payloadSecret)))
8247

83-
log.Printf("Success building image: %s", result.Image)
84-
}
85-
86-
func shrinkwrap(image, handler, lang string) error {
87-
buildCmd := exec.Command(
88-
"faas-cli",
89-
"build",
90-
"--lang",
91-
lang,
92-
"--handler",
93-
handler,
94-
"--name",
95-
"context",
96-
"--image",
97-
image,
98-
"--shrinkwrap",
99-
)
100-
101-
err := buildCmd.Start()
102-
if err != nil {
103-
return fmt.Errorf("cannot start faas-cli build: %t", err)
104-
}
105-
106-
err = buildCmd.Wait()
48+
// Create the function build context using the provided function handler and language template.
49+
buildContext, err := builder.CreateBuildContext(functionName, handler, lang, []string{})
10750
if err != nil {
108-
return fmt.Errorf("failed to shrinkwrap handler")
109-
}
110-
111-
return nil
112-
}
113-
114-
func makeTar(buildConfig buildConfig, base, tarPath string) error {
115-
configBytes, _ := json.Marshal(buildConfig)
116-
if err := ioutil.WriteFile(path.Join(base, ConfigFileName), configBytes, 0664); err != nil {
117-
return err
51+
log.Fatalf("failed to create build context: %s", err)
11852
}
11953

120-
tarFile, err := os.Create(tarPath)
54+
// Create a temporary file for the build tar.
55+
tarFile, err := os.CreateTemp(os.TempDir(), "build-context-*.tar")
12156
if err != nil {
122-
return err
57+
log.Fatalf("failed to temporary file: %s", err)
12358
}
59+
tarFile.Close()
12460

125-
tarWriter := tar.NewWriter(tarFile)
126-
defer tarWriter.Close()
127-
128-
err = filepath.Walk(base, func(path string, f os.FileInfo, pathErr error) error {
129-
if pathErr != nil {
130-
return pathErr
131-
}
132-
133-
targetFile, err := os.Open(path)
134-
if err != nil {
135-
return err
136-
}
137-
138-
header, err := tar.FileInfoHeader(f, f.Name())
139-
if err != nil {
140-
return err
141-
}
142-
143-
header.Name = strings.TrimPrefix(path, base)
144-
if header.Name != fmt.Sprintf("/%s", ConfigFileName) {
145-
header.Name = filepath.Join("context", header.Name)
146-
}
147-
148-
header.Name = strings.TrimPrefix(header.Name, "/")
61+
tarPath := tarFile.Name()
62+
defer os.Remove(tarPath)
14963

150-
if err := tarWriter.WriteHeader(header); err != nil {
151-
return err
152-
}
153-
154-
if f.Mode().IsDir() {
155-
return nil
156-
}
157-
158-
_, err = io.Copy(tarWriter, targetFile)
159-
return err
160-
})
161-
162-
return err
163-
}
164-
165-
func callBuilder(tarPath string) (*http.Response, error) {
166-
payloadSecret, err := os.ReadFile("payload.txt")
167-
if err != nil {
168-
return nil, err
64+
// Configuration for the build.
65+
// Set the image name plus optional build arguments and target platforms for multi-arch images.
66+
buildConfig := builder.BuildConfig{
67+
Image: image,
68+
Platforms: platforms,
69+
BuildArgs: buildArgs,
16970
}
17071

171-
tarFile, err := os.Open(tarPath)
172-
if err != nil {
173-
return nil, err
72+
// Prepare a tar archive that contains the build config and build context.
73+
if err := builder.MakeTar(tarPath, buildContext, &buildConfig); err != nil {
74+
log.Fatal(err)
17475
}
175-
defer tarFile.Close()
17676

177-
tarFileBytes, err := ioutil.ReadAll(tarFile)
77+
// Invoke the function builder with the tar archive containing the build config and context
78+
// to build and push the function image.
79+
result, err := b.Build(tarPath)
17880
if err != nil {
179-
return nil, err
81+
log.Fatal(err)
18082
}
18183

182-
digest := hmac.Sign(tarFileBytes, bytes.TrimSpace(payloadSecret), sha256.New)
183-
fmt.Println(hex.EncodeToString(digest))
84+
log.Printf("Image: %s built.", result.Image)
18485

185-
r, err := http.NewRequest(http.MethodPost, "http://127.0.0.1:8081/build", bytes.NewReader(tarFileBytes))
186-
if err != nil {
187-
return nil, err
86+
// Print build logs
87+
log.Println("Build logs:")
88+
for _, logMsg := range result.Log {
89+
fmt.Printf("%s\n", logMsg)
18890
}
91+
}
18992

190-
r.Header.Set("X-Build-Signature", "sha256="+hex.EncodeToString(digest))
191-
r.Header.Set("Content-Type", "application/octet-stream")
93+
func parseBuildArgs(str string) map[string]string {
94+
buildArgs := map[string]string{}
19295

193-
res, err := http.DefaultClient.Do(r)
194-
if err != nil {
195-
return nil, err
96+
if str != "" {
97+
pairs := strings.Split(str, ",")
98+
for _, pair := range pairs {
99+
kv := strings.SplitN(pair, "=", 2)
100+
if len(kv) == 2 {
101+
buildArgs[kv[0]] = kv[1]
102+
}
103+
}
196104
}
197105

198-
return res, nil
106+
return buildArgs
199107
}

go.mod

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
module github.com/openfaas/function-builder-examples
22

3-
go 1.18
3+
go 1.21
44

5-
require github.com/alexellis/hmac/v2 v2.0.0
5+
toolchain go1.22.1
6+
7+
require github.com/openfaas/go-sdk v0.2.14
8+
9+
require github.com/alexellis/hmac/v2 v2.0.0 // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
github.com/alexellis/hmac/v2 v2.0.0 h1:/sH/UJxDXPpJorUeg2DudeKSeUrWPF32Yamw2TiDoOQ=
22
github.com/alexellis/hmac/v2 v2.0.0/go.mod h1:O7hZZgTfh5fp5+vAamzodZPlbw+aQK+nnrrJNHsEvL0=
3+
github.com/openfaas/go-sdk v0.2.14 h1:N3bq0yparYZR6pR1AhZiPGvV8GAmQ1UfjmCOGaZMs68=
4+
github.com/openfaas/go-sdk v0.2.14/go.mod h1:DrKUCQ4F8L2cJOmWHNoX8zB8LQIZc/4hRgAtT4t0s4k=

nodejs-request/package-lock.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

php-request/build.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ function callBuilder($tarFile){
7171
$cli->description('Build a function with the OpenFaaS Pro Builder')
7272
->opt('image:i', 'Docker image name to build, e.g. docker.io/functions/hello-world:0.1.0', true)
7373
->opt('handler:h', 'Directory with handler for function, e.g. ./hello-world', true)
74-
->opt('lang:l', 'Language or template to use, e.g. node17', true);
74+
->opt('lang:l', 'Language or template to use, e.g. node20', true);
7575

7676
// Parse and return cli args.
7777
$args = $cli->parse($argv, true);

python-request/build.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def callBuilder(tarFile):
5757
parser.add_argument('--handler', type=str,
5858
help="Directory with handler for function, e.g. handler.js", required=True)
5959
parser.add_argument('--lang', type=str,
60-
help="Language or template to use, e.g. node17", required=True)
60+
help="Language or template to use, e.g. node20", required=True)
6161

6262
args = parser.parse_args()
6363

0 commit comments

Comments
 (0)