From d9b6bb1d9311812a1f3a94596bf10c86f1898e7c Mon Sep 17 00:00:00 2001 From: "Han Verstraete (OpenFaaS Ltd)" Date: Mon, 27 Apr 2026 17:21:04 +0200 Subject: [PATCH 1/3] Update openfaas-api Python example to use the official Python SDK Signed-off-by: Han Verstraete (OpenFaaS Ltd) --- .../languages/python/examples/openfaas-api.md | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/docs/languages/python/examples/openfaas-api.md b/docs/languages/python/examples/openfaas-api.md index 41707e0e..ab452394 100644 --- a/docs/languages/python/examples/openfaas-api.md +++ b/docs/languages/python/examples/openfaas-api.md @@ -1,4 +1,4 @@ -Use Python's `requests` library to interact with the [OpenFaaS REST API](/reference/rest-api/) and manage functions and namespaces programmatically — useful for CI/CD pipelines, functions that manage other functions, or building self-service platforms on top of OpenFaaS. +Use the [OpenFaaS Python SDK](https://github.com/openfaas/python-sdk) to interact with the [OpenFaaS REST API](/reference/rest-api/) and manage functions and namespaces programmatically, useful for CI/CD pipelines, functions that manage other functions, or building self-service platforms on top of OpenFaaS. Use-cases: @@ -15,47 +15,43 @@ handler.py: ```python import os import json -import requests +from openfaas_sdk import Client, BasicAuth +from openfaas_sdk.models import FunctionDeployment, FunctionNamespace +from openfaas_sdk.exceptions import APIStatusError def handle(event, context): gateway = os.getenv("gateway_url", "http://gateway.openfaas:8080") password = read_secret("openfaas-password") - auth = ("admin", password) body = json.loads(event.body) ns = body.get("namespace", "openfaas-fn") - namespaces = requests.get( - f"{gateway}/system/namespaces", - auth=auth, - ).json() - - if ns not in namespaces: - r = requests.post( - f"{gateway}/system/namespace/", - json={"name": ns, "annotations": {"openfaas": "1"}}, - auth=auth, - ) - if r.status_code not in (200, 201): - return { - "statusCode": r.status_code, - "body": f"Failed to create namespace: {r.text}", - } - - r = requests.put( - f"{gateway}/system/functions", - json={ - "service": body["name"], - "image": body["image"], - "namespace": ns, - }, - auth=auth, - ) - - return { - "statusCode": r.status_code, - "body": r.text, - } + try: + with Client(gateway_url=gateway, auth=BasicAuth("admin", password)) as client: + namespaces = client.get_namespaces() + + if ns not in namespaces: + client.create_namespace(FunctionNamespace( + name=ns, + )) + + status_code = client.deploy(FunctionDeployment( + service=body["name"], + image=body["image"], + namespace=ns, + )) + + return {"statusCode": status_code, "body": ""} + except APIStatusError as e: + return { + "statusCode": e.status_code, + "body": f"API error: {e}", + } + except Exception as e: + return { + "statusCode": 500, + "body": f"Unexpected error: {e}", + } def read_secret(name): with open("/var/openfaas/secrets/" + name, "r") as f: @@ -65,7 +61,7 @@ def read_secret(name): requirements.txt: ``` -requests +git+https://github.com/openfaas/python-sdk.git ``` stack.yaml: @@ -76,15 +72,20 @@ functions: lang: python3-http handler: ./deploy-function image: ttl.sh/openfaas-examples/deploy-function:latest + build_args: + ADDITIONAL_PACKAGE: git secrets: - openfaas-password + environment: + gateway_url: http://gateway.openfaas:8080 ``` -The `requests` package is pure Python, so the Alpine-based `python3-http` template works here. +The `python3-http` template is Alpine-based and does not include `git` by default. The `ADDITIONAL_PACKAGE: git` build arg installs it during the image build so that `pip` can clone the SDK from GitHub. - The gateway URL defaults to `http://gateway.openfaas:8080`, the in-cluster address when running on Kubernetes. Override it with the `gateway_url` environment variable if needed. -- The handler authenticates with HTTP Basic Auth using the `admin` username and the password read from the `openfaas-password` secret. +- The `Client` is initialised with `BasicAuth`, which handles HTTP Basic Auth using the `admin` username and the password read from the `openfaas-password` secret. - Namespace creation is idempotent — the handler checks whether the namespace exists before attempting to create it. +- Any non-2xx response from the gateway raises an `APIStatusError`, which is caught and returned as a structured error response. Because this function can manage other functions and namespaces, its own endpoint should be protected. See [Add authentication](#add-authentication) for how to do this. @@ -172,7 +173,9 @@ Add the `valid_bearer` helper and a token check at the top of the handler: ```diff import os import json - import requests + from openfaas_sdk import Client, BasicAuth + from openfaas_sdk.models import FunctionDeployment, FunctionNamespace + from openfaas_sdk.exceptions import APIStatusError def handle(event, context): + token = read_secret("deploy-function-token") @@ -181,7 +184,6 @@ Add the `valid_bearer` helper and a token check at the top of the handler: + gateway = os.getenv("gateway_url", "http://gateway.openfaas:8080") password = read_secret("openfaas-password") - auth = ("admin", password) @@ ... +def valid_bearer(token, headers): From 7ad212260e6c73e9f385d5f77094e919cfe79397 Mon Sep 17 00:00:00 2001 From: "Han Verstraete (OpenFaaS Ltd)" Date: Mon, 27 Apr 2026 17:49:18 +0200 Subject: [PATCH 2/3] Add Python SDK source-to-function example and link from builder and Python index pages Signed-off-by: Han Verstraete (OpenFaaS Ltd) --- .../python/examples/source-to-function.md | 260 ++++++++++++++++++ docs/sdks/python/index.md | 19 ++ mkdocs.yml | 5 + 3 files changed, 284 insertions(+) create mode 100644 docs/sdks/python/examples/source-to-function.md create mode 100644 docs/sdks/python/index.md diff --git a/docs/sdks/python/examples/source-to-function.md b/docs/sdks/python/examples/source-to-function.md new file mode 100644 index 00000000..8e68f844 --- /dev/null +++ b/docs/sdks/python/examples/source-to-function.md @@ -0,0 +1,260 @@ +Use the [OpenFaaS Python SDK](https://github.com/openfaas/python-sdk) and the [Function Builder API](/openfaas-pro/builder/) to go from source code to a running, authenticated function in a custom namespace, entirely from Python. + +Use-cases: + +* SaaS platforms where users supply their own code +* Multi-tenant environments where each tenant gets an isolated namespace +* CI/CD pipelines that go from source to a running function in a single script + +The `greeter` function validates a Bearer token against a mounted secret. The orchestration script creates a namespace and secret, builds the function image from source, deploys it, and invokes the function once it is ready. + +## Overview + +The example consists of a `greeter` function and an orchestration script (`main.py`) that drives the full workflow. + +greeter/handler.py: + +```python +import json +import sys + + +def handle(event, context): + if event.path == "/_/ready": + return {"statusCode": 200, "body": "OK"} + + secret_path = "/var/openfaas/secrets/api-key" + try: + with open(secret_path) as f: + api_key = f.read().strip() + except OSError: + print("Error: secret not available", flush=True, file=sys.stderr) + return { + "statusCode": 500, + "body": json.dumps({"error": "Secret not available"}), + "headers": {"Content-Type": "application/json"}, + } + + auth_header = event.headers.get("Authorization", "") + if not auth_header.startswith("Bearer "): + print("Unauthorized: missing or malformed Authorization header", flush=True, file=sys.stderr) + return { + "statusCode": 401, + "body": json.dumps({"error": "Unauthorized"}), + "headers": {"Content-Type": "application/json"}, + } + + token = auth_header[len("Bearer "):] + if token != api_key: + print("Unauthorized: invalid token", flush=True, file=sys.stderr) + return { + "statusCode": 401, + "body": json.dumps({"error": "Unauthorized"}), + "headers": {"Content-Type": "application/json"}, + } + + print("Request authorized, returning greeting", flush=True) + return { + "statusCode": 200, + "body": json.dumps({"message": "Hello from OpenFaaS!"}), + "headers": {"Content-Type": "application/json"}, + } +``` + +main.py: + +```python +import os +import sys +import time +import uuid + +from openfaas_sdk import BasicAuth, Client +from openfaas_sdk.builder import BuildConfig, FunctionBuilder, create_build_context, make_tar +from openfaas_sdk.exceptions import APIConnectionError, ForbiddenError, NotFoundError, UnauthorizedError +from openfaas_sdk.models import FunctionDeployment, FunctionNamespace, Secret + +GATEWAY_URL = os.environ.get("OPENFAAS_GATEWAY", "http://127.0.0.1:8080") +BUILDER_URL = os.environ.get("BUILDER_URL", "http://127.0.0.1:8081") +PAYLOAD_SECRET_PATH = os.environ.get("PAYLOAD_SECRET_PATH", "/var/secrets/payload-secret") + +NAMESPACE = "tenant1" +FUNCTION_NAME = "greeter" +SECRET_NAME = "api-key" +IMAGE = "ttl.sh/greeter:1h" + +def read_file(path): + with open(path) as f: + return f.read().strip() + +def wait_for_ready(client, name, namespace, timeout=120): + """Poll until at least one replica is available or the timeout is reached.""" + deadline = time.time() + timeout + while time.time() < deadline: + try: + fn = client.get_function(name, namespace) + if fn.available_replicas and fn.available_replicas >= 1: + return + except NotFoundError: + pass + time.sleep(3) + print(f"Timed out waiting for {name!r} to become ready.", file=sys.stderr) + sys.exit(1) + +password = os.environ["OPENFAAS_PASSWORD"] +hmac_secret = read_file(PAYLOAD_SECRET_PATH) + +try: + with Client(gateway_url=GATEWAY_URL, auth=BasicAuth("admin", password)) as client: + + # Create an isolated namespace for the tenant. + print(f"Creating namespace {NAMESPACE!r}") + client.create_namespace(FunctionNamespace(name=NAMESPACE)) + + # Generate a random API key and store it as an OpenFaaS secret. + # The function reads this value at runtime from the mounted secret file. + api_key = str(uuid.uuid4()) + print(f"Creating secret {SECRET_NAME!r}") + client.create_secret(Secret(name=SECRET_NAME, namespace=NAMESPACE, value=api_key)) + + # Assemble the Docker build context from the template and handler, + # then pack it into a tar archive with the build configuration. + print(f"Assembling build context for {FUNCTION_NAME!r}") + context_path = create_build_context( + function_name=FUNCTION_NAME, + handler="./greeter", + language="python3-http", + template_dir="./template", + build_dir="./build", + ) + make_tar("/tmp/greeter.tar", context_path, BuildConfig(image=IMAGE, platforms=["linux/amd64"])) + + # Send the tar to the Function Builder and stream log lines as they arrive. + print(f"Building {IMAGE!r}") + builder = FunctionBuilder(BUILDER_URL, hmac_secret=hmac_secret) + for result in builder.build_stream("/tmp/greeter.tar"): + for line in result.log: + print(line) + if result.status == "failed": + print("Build failed.", file=sys.stderr) + sys.exit(1) + + # Deploy the function into the tenant namespace with the secret mounted. + # The readiness annotation tells Kubernetes to use the function's /_/ready + # endpoint so the pod is only marked ready once the handler is fully started. + print(f"Deploying {FUNCTION_NAME!r} into {NAMESPACE!r}") + client.deploy(FunctionDeployment( + service=FUNCTION_NAME, + image=IMAGE, + namespace=NAMESPACE, + secrets=[SECRET_NAME], + annotations={ + "com.openfaas.ready.http.path": "/_/ready", + }, + )) + + print("Waiting for function to become ready") + wait_for_ready(client, FUNCTION_NAME, NAMESPACE) + + # Invoke the function with the generated API key as a Bearer token. + print("Invoking function") + resp = client.invoke_function( + FUNCTION_NAME, + namespace=NAMESPACE, + method="GET", + headers={"Authorization": f"Bearer {api_key}"}, + ) + print(resp.status_code, resp.text) + + # Get the last 20 log lines from the function. + print("Fetching logs") + for msg in client.get_logs(FUNCTION_NAME, NAMESPACE, tail=20): + print(f"[{msg.timestamp}] {msg.instance}: {msg.text}") + +except UnauthorizedError: + print("Unauthorized. Check your OPENFAAS_PASSWORD.", file=sys.stderr) + sys.exit(1) +except (ForbiddenError, APIConnectionError) as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) +``` + +requirements.txt: + +``` +git+https://github.com/openfaas/python-sdk.git +``` + +- A random UUID is generated as the API key at runtime and stored as an OpenFaaS secret. +- The handler responds to `/_/ready` with a 200 so Kubernetes only marks the pod ready once the Flask server is fully listening. The `com.openfaas.ready.http.path` annotation configures OpenFaaS to use this endpoint for the readiness probe. +- `create_build_context` assembles a Docker build context from the OpenFaaS template and handler directory. `make_tar` packs it with the build configuration into a tar archive ready for the Function Builder. `build_stream` sends the tar and yields log lines as they arrive. + +## Step-by-step walkthrough + +### Prerequisites + +- Python 3.10+ +- [`faas-cli`](https://github.com/openfaas/faas-cli) installed +- OpenFaaS with the [Function Builder](/openfaas-pro/builder/) enabled +- A container registry the builder can push to. This example uses [ttl.sh](https://ttl.sh) (no credentials required) + +### Set up the project + +Create a directory for the example and add the files from the overview above: + +```bash +mkdir source-to-function && cd source-to-function +``` + +Pull the `python3-http` template: + +```bash +faas-cli template store pull python3-http +``` + +Scaffold the `greeter` function handler: + +```bash +faas-cli new --lang python3-http greeter +``` + +Replace `greeter/handler.py` with the handler from the overview. The generated `requirements.txt` in the `greeter` directory can be left empty. + +Create `main.py` in the project root with the script from the overview. + +Make sure the OpenFaaS SDK is installed: + +```bash +pip install git+https://github.com/openfaas/python-sdk.git +``` + +### Configure environment variables + +| Variable | Default | Description | +|---|---|---| +| `OPENFAAS_PASSWORD` | — | **Required.** OpenFaaS gateway admin password | +| `OPENFAAS_GATEWAY` | `http://127.0.0.1:8080` | Gateway URL | +| `BUILDER_URL` | `http://127.0.0.1:8081` | Function Builder URL | +| `PAYLOAD_SECRET_PATH` | `/var/secrets/payload-secret` | Path to the builder HMAC payload secret | + +Retrieve the gateway password and builder payload secret from the cluster: + +```bash +export OPENFAAS_PASSWORD=$(kubectl get secret -n openfaas basic-auth \ + -o jsonpath="{.data.basic-auth-password}" | base64 --decode) + +kubectl get secret -n openfaas payload-secret \ + -o jsonpath='{.data.payload-secret}' | base64 --decode \ + | sudo tee /var/secrets/payload-secret +``` + +### Run the script + +Port-forward the gateway and builder, then run `main.py`: + +```bash +kubectl port-forward -n openfaas svc/gateway 8080:8080 & +kubectl port-forward -n openfaas deploy/pro-builder 8081:8080 & + +python main.py +``` diff --git a/docs/sdks/python/index.md b/docs/sdks/python/index.md new file mode 100644 index 00000000..47679819 --- /dev/null +++ b/docs/sdks/python/index.md @@ -0,0 +1,19 @@ +The [OpenFaaS Python SDK](https://github.com/openfaas/python-sdk) provides typed access to the full [OpenFaaS REST API](/reference/rest-api/) from Python, including the [Function Builder API](/openfaas-pro/builder/). + +## Installation + +```bash +pip install git+https://github.com/openfaas/python-sdk.git +``` + +## Features + +- Manage functions, namespaces, and secrets +- Invoke functions synchronously and asynchronously +- Stream function logs +- Build and push function images from source via the Function Builder API +- Basic auth and [OpenFaaS IAM](/openfaas-pro/iam/overview/) support + +## Examples + +* [From source to function](examples/source-to-function.md) diff --git a/mkdocs.yml b/mkdocs.yml index f0982591..01f151b2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -198,6 +198,11 @@ nav: - OpenTelemetry: ./edge/open-telemetry.md - Air Gap: ./edge/airgap.md - Preloading functions: ./edge/preloading.md + - SDKs: + - Python: + - Overview: ./sdks/python/index.md + - Examples: + - From source to function: ./sdks/python/examples/source-to-function.md - Reference: - OpenFaaS YAML: ./reference/yaml.md - REST API: ./reference/rest-api.md From 07d47df4c5f691a8370276e7c98c968a52c778ac Mon Sep 17 00:00:00 2001 From: "Han Verstraete (OpenFaaS Ltd)" Date: Tue, 28 Apr 2026 14:49:03 +0200 Subject: [PATCH 3/3] Add Golang SDK docs and source-to-function example Signed-off-by: Han Verstraete (OpenFaaS Ltd) --- docs/sdks/go/examples/source-to-function.md | 315 ++++++++++++++++++++ docs/sdks/index.md | 45 +++ docs/sdks/python/index.md | 19 -- mkdocs.yml | 7 +- 4 files changed, 364 insertions(+), 22 deletions(-) create mode 100644 docs/sdks/go/examples/source-to-function.md create mode 100644 docs/sdks/index.md delete mode 100644 docs/sdks/python/index.md diff --git a/docs/sdks/go/examples/source-to-function.md b/docs/sdks/go/examples/source-to-function.md new file mode 100644 index 00000000..082ffa35 --- /dev/null +++ b/docs/sdks/go/examples/source-to-function.md @@ -0,0 +1,315 @@ +Use the [OpenFaaS Go SDK](https://github.com/openfaas/go-sdk) and the [Function Builder API](/openfaas-pro/builder/) to go from source code to a running, authenticated function in a custom namespace, entirely from Go. + +Use-cases: + +* SaaS platforms where users supply their own code +* Multi-tenant environments where each tenant gets an isolated namespace +* CI/CD pipelines that go from source to a running function in a single program + +The `greeter` function validates a Bearer token against a mounted secret. The orchestration program creates a namespace and secret, builds the function image from source, deploys it, and invokes the function once it is ready. + +## Overview + +The example consists of a `greeter` function and an orchestration program (`main.go`) that drives the full workflow. + +greeter/handler.go: + +```go +package function + +import ( + "encoding/json" + "fmt" + "net/http" + "os" +) + +func Handle(w http.ResponseWriter, r *http.Request) { + apiKey, err := os.ReadFile("/var/openfaas/secrets/api-key") + if err != nil { + fmt.Fprintf(os.Stderr, "Error: secret not available: %v\n", err) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(map[string]string{"error": "secret not available"}) + return + } + + authHeader := r.Header.Get("Authorization") + if len(authHeader) < 8 || authHeader[:7] != "Bearer " { + fmt.Fprintln(os.Stderr, "Unauthorized: missing or malformed Authorization header") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"}) + return + } + + if authHeader[7:] != string(apiKey) { + fmt.Fprintln(os.Stderr, "Unauthorized: invalid token") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"}) + return + } + + fmt.Println("Request authorized, returning greeting") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"message": "Hello from OpenFaaS!"}) +} +``` + +main.go: + +```go +package main + +import ( + "context" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "strings" + "time" + + sdk "github.com/openfaas/go-sdk" + "github.com/openfaas/go-sdk/builder" + "github.com/openfaas/faas-provider/types" +) + +const ( + namespace = "tenant1" + functionName = "greeter" + secretName = "api-key" + image = "ttl.sh/openfaas/greeter:1h" +) + +func main() { + gatewayURL, err := url.Parse(os.Getenv("OPENFAAS_GATEWAY")) + if err != nil || gatewayURL.Host == "" { + log.Fatal("OPENFAAS_GATEWAY is required") + } + builderURL, err := url.Parse(os.Getenv("BUILDER_URL")) + if err != nil || builderURL.Host == "" { + log.Fatal("BUILDER_URL is required") + } + password := os.Getenv("OPENFAAS_PASSWORD") + if password == "" { + log.Fatal("OPENFAAS_PASSWORD is required") + } + payloadSecretBytes, err := os.ReadFile(os.Getenv("PAYLOAD_SECRET_PATH")) + if err != nil { + log.Fatalf("read payload secret: %v", err) + } + payloadSecret := strings.TrimSpace(string(payloadSecretBytes)) + + auth := &sdk.BasicAuth{Username: "admin", Password: password} + client := sdk.NewClient(gatewayURL, auth, http.DefaultClient) + + ctx := context.Background() + + // Create an isolated namespace for the tenant. + log.Printf("Creating namespace %q", namespace) + if _, err := client.CreateNamespace(ctx, types.FunctionNamespace{Name: namespace}); err != nil { + log.Fatalf("create namespace: %v", err) + } + + // Generate a random API key and store it as an OpenFaaS secret. + // The function reads this value at runtime from the mounted secret file. + apiKey := fmt.Sprintf("%d", time.Now().UnixNano()) + log.Printf("Creating secret %q", secretName) + if _, err := client.CreateSecret(ctx, types.Secret{ + Name: secretName, + Namespace: namespace, + Value: apiKey, + }); err != nil { + log.Fatalf("create secret: %v", err) + } + + // Assemble the Docker build context from the template and handler, + // then pack it into a tar archive with the build configuration. + log.Printf("Assembling build context for %q", functionName) + contextPath, err := builder.CreateBuildContext(functionName, "./greeter", "golang-middleware", []string{}, + builder.WithTemplateDir("./template"), + builder.WithBuildDir("/tmp/build"), + ) + if err != nil { + log.Fatalf("create build context: %v", err) + } + + tarPath := "/tmp/greeter.tar" + if err := builder.MakeTar(tarPath, contextPath, &builder.BuildConfig{ + Image: image, + Platforms: []string{"linux/amd64"}, + }); err != nil { + log.Fatalf("make tar: %v", err) + } + + // Send the tar to the Function Builder and stream log lines as they arrive. + log.Printf("Building %q", image) + b := builder.NewFunctionBuilder(builderURL, http.DefaultClient, builder.WithHmacAuth(payloadSecret)) + stream, err := b.BuildWithStream(tarPath) + if err != nil { + log.Fatalf("build: %v", err) + } + defer stream.Close() + + for result, err := range stream.Results() { + if err != nil { + log.Fatalf("build stream: %v", err) + } + for _, line := range result.Log { + fmt.Println(line) + } + if result.Status == builder.BuildFailed { + log.Fatalf("build failed: %s", result.Error) + } + } + + // Deploy the function into the tenant namespace with the secret mounted. + log.Printf("Deploying %q into %q", functionName, namespace) + if _, err := client.Deploy(ctx, types.FunctionDeployment{ + Service: functionName, + Image: image, + Namespace: namespace, + Secrets: []string{secretName}, + }); err != nil { + log.Fatalf("deploy: %v", err) + } + + log.Println("Waiting for function to become ready") + if err := waitForReady(ctx, client, functionName, namespace, 120*time.Second); err != nil { + log.Fatal(err) + } + + // Invoke the function with the generated API key as a Bearer token. + log.Println("Invoking function with valid token") + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/", nil) + req.Header.Set("Authorization", "Bearer "+apiKey) + res, err := client.InvokeFunction(functionName, namespace, false, false, req) + if err != nil { + log.Fatalf("invoke: %v", err) + } + body, _ := io.ReadAll(res.Body) + res.Body.Close() + fmt.Printf("%d %s\n", res.StatusCode, body) + + // Invoke with a bad token to verify 401. + log.Println("Invoking function with invalid token") + req, _ = http.NewRequestWithContext(ctx, http.MethodGet, "/", nil) + req.Header.Set("Authorization", "Bearer wrong-token") + res, err = client.InvokeFunction(functionName, namespace, false, false, req) + if err != nil { + log.Fatalf("invoke: %v", err) + } + body, _ = io.ReadAll(res.Body) + res.Body.Close() + fmt.Printf("%d %s\n", res.StatusCode, body) + + // Get the last 20 log lines from the function. + log.Println("Fetching logs") + tail := 20 + logCh, err := client.GetLogs(ctx, functionName, namespace, false, tail, nil) + if err != nil { + log.Fatalf("get logs: %v", err) + } + for msg := range logCh { + fmt.Printf("[%s] %s: %s\n", msg.Timestamp, msg.Instance, msg.Text) + } +} + +func waitForReady(ctx context.Context, client *sdk.Client, name, namespace string, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + ticker := time.NewTicker(3 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return fmt.Errorf("timed out waiting for %q to become ready", name) + case <-ticker.C: + fn, err := client.GetFunction(ctx, name, namespace) + if err == nil && fn.AvailableReplicas >= 1 { + return nil + } + } + } +} +``` + +- A timestamp-based value is used as the API key and stored as an OpenFaaS secret. +- `CreateBuildContext` assembles a Docker build context from the OpenFaaS template and handler directory. `MakeTar` packs it with the build configuration into a tar archive ready for the Function Builder. `BuildWithStream` sends the tar and yields log lines as they arrive. + +## Step-by-step walkthrough + +### Prerequisites + +- Go 1.21+ +- [`faas-cli`](https://github.com/openfaas/faas-cli) installed +- OpenFaaS with the [Function Builder](/openfaas-pro/builder/) enabled +- A container registry the builder can push to. This example uses [ttl.sh](https://ttl.sh) (no credentials required) + +### Set up the project + +Create a directory for the example: + +```bash +mkdir source-to-function && cd source-to-function +``` + +Pull the `golang-middleware` template: + +```bash +faas-cli template store pull golang-middleware +``` + +Scaffold the `greeter` function handler: + +```bash +faas-cli new --lang golang-middleware greeter +``` + +Replace `greeter/handler.go` with the handler from the overview. + +Initialise the Go module and add the SDK dependency: + +```bash +go mod init source-to-function +go get github.com/openfaas/go-sdk +``` + +Create `main.go` in the project root with the script from the overview. + +### Configure environment variables + +| Variable | Default | Description | +|---|---|---| +| `OPENFAAS_PASSWORD` | — | **Required.** OpenFaaS gateway admin password | +| `OPENFAAS_GATEWAY` | — | **Required.** Gateway URL | +| `BUILDER_URL` | — | **Required.** Function Builder URL | +| `PAYLOAD_SECRET_PATH` | — | **Required.** Path to the builder HMAC payload secret | + +Retrieve the gateway password and builder payload secret from the cluster: + +```bash +export OPENFAAS_PASSWORD=$(kubectl get secret -n openfaas basic-auth \ + -o jsonpath="{.data.basic-auth-password}" | base64 --decode) + +kubectl get secret -n openfaas payload-secret \ + -o jsonpath='{.data.payload-secret}' | base64 --decode \ + > /tmp/payload-secret +``` + +### Run the program + +```bash +export OPENFAAS_GATEWAY=https://gateway.example.com +export BUILDER_URL=https://builder.example.com +export PAYLOAD_SECRET_PATH=/tmp/payload-secret + +go run main.go +``` diff --git a/docs/sdks/index.md b/docs/sdks/index.md new file mode 100644 index 00000000..4df9f8b8 --- /dev/null +++ b/docs/sdks/index.md @@ -0,0 +1,45 @@ +OpenFaaS SDKs provide typed access to the [OpenFaaS REST API](/reference/rest-api/) and the [Function Builder API](/openfaas-pro/builder/) for managing and automating functions from your own code. + +## Python SDK + +The [OpenFaaS Python SDK](https://github.com/openfaas/python-sdk) provides access to the full OpenFaaS REST API and the Function Builder API from Python. + +**Install:** + +```bash +pip install git+https://github.com/openfaas/python-sdk.git +``` + +**Features:** + +- Manage functions, namespaces, and secrets +- Invoke functions synchronously and asynchronously +- Stream function logs +- Build and push function images from source via the Function Builder API +- Basic auth and [OpenFaaS IAM](/openfaas-pro/iam/overview/) support + +**Examples:** + +- [From source to function](python/examples/source-to-function.md) + +## Go SDK + +The [OpenFaaS Go SDK](https://github.com/openfaas/go-sdk) provides access to the full OpenFaaS REST API and the Function Builder API from Go. + +**Install:** + +```bash +go get github.com/openfaas/go-sdk +``` + +**Features:** + +- Manage functions, namespaces, and secrets +- Invoke functions synchronously and asynchronously +- Stream function logs +- Build and push function images from source via the Function Builder API +- Basic auth and [OpenFaaS IAM](/openfaas-pro/iam/overview/) support + +**Examples:** + +- [From source to function](go/examples/source-to-function.md) diff --git a/docs/sdks/python/index.md b/docs/sdks/python/index.md deleted file mode 100644 index 47679819..00000000 --- a/docs/sdks/python/index.md +++ /dev/null @@ -1,19 +0,0 @@ -The [OpenFaaS Python SDK](https://github.com/openfaas/python-sdk) provides typed access to the full [OpenFaaS REST API](/reference/rest-api/) from Python, including the [Function Builder API](/openfaas-pro/builder/). - -## Installation - -```bash -pip install git+https://github.com/openfaas/python-sdk.git -``` - -## Features - -- Manage functions, namespaces, and secrets -- Invoke functions synchronously and asynchronously -- Stream function logs -- Build and push function images from source via the Function Builder API -- Basic auth and [OpenFaaS IAM](/openfaas-pro/iam/overview/) support - -## Examples - -* [From source to function](examples/source-to-function.md) diff --git a/mkdocs.yml b/mkdocs.yml index 01f151b2..07e8d332 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -199,10 +199,11 @@ nav: - Air Gap: ./edge/airgap.md - Preloading functions: ./edge/preloading.md - SDKs: + - Overview: ./sdks/index.md - Python: - - Overview: ./sdks/python/index.md - - Examples: - - From source to function: ./sdks/python/examples/source-to-function.md + - From source to function: ./sdks/python/examples/source-to-function.md + - Go: + - From source to function: ./sdks/go/examples/source-to-function.md - Reference: - OpenFaaS YAML: ./reference/yaml.md - REST API: ./reference/rest-api.md