A lightweight Go SDK for use within OpenFaaS functions and to control the OpenFaaS gateway.
For use within any Go code (not just OpenFaaS Functions):
- Client - A client for the OpenFaaS REST API
For use within functions:
- ReadSecret() - Read a named secret from within an OpenFaaS Function
- ReadSecrets() - Read all available secrets returning a queryable map
Authentication helpers (See: Authentication with IAM):
- ServiceAccountTokenSource - An implementation of the TokenSource interface to get an ID token by reading a Kubernetes projected service account token from
/var/secrets/tokens/openfaas-token
or the path set by thetoken_mount_path
environment variable.
import "github.com/openfaas/go-sdk"
Construct a new OpenFaaS client and use it to access the OpenFaaS gateway API.
gatewayURL, _ := url.Parse("http://127.0.0.1:8080")
auth := &sdk.BasicAuth{
Username: username,
Password: password,
}
client := sdk.NewClient(gatewayURL, auth, http.DefaultClient)
namespace, err := client.GetNamespaces(context.Background())
To authenticate with an OpenFaaS deployment that has Identity and Access Management (IAM) enabled, the client needs to exchange an ID token for an OpenFaaS ID token.
To get a token that can be exchanged for an OpenFaaS token you need to implement the TokenSource
interface.
This is an example of a token source that gets a service account token mounted into a pod with ServiceAccount token volume projection.
type ServiceAccountTokenSource struct{}
func (ts *ServiceAccountTokenSource) Token() (string, error) {
tokenMountPath := getEnv("token_mount_path", "/var/secrets/tokens")
if len(tokenMountPath) == 0 {
return "", fmt.Errorf("invalid token_mount_path specified for reading the service account token")
}
idTokenPath := path.Join(tokenMountPath, "openfaas-token")
idToken, err := os.ReadFile(idTokenPath)
if err != nil {
return "", fmt.Errorf("unable to load service account token: %s", err)
}
return string(idToken), nil
}
The service account token returned by the TokenSource
is automatically exchanged for an OpenFaaS token that is then used in the Authorization header for all requests made to the API.
If the OpenFaaS token is expired the TokenSource
is asked for a token and the token exchange will run again.
gatewayURL, _ := url.Parse("https://gw.openfaas.example.com")
auth := &sdk.TokenAuth{
TokenURL "https://gw.openfaas.example.com/oauth/token",
TokenSource: &ServiceAccountTokenSource{}
}
client := sdk.NewClient(gatewayURL, auth, http.DefaultClient)
func Test_ClientCredentials(t *testing.T) {
clientID := ""
clientSecret := ""
tokenURL := "https://keycloak.example.com/realms/openfaas/protocol/openid-connect/token"
scope := "email"
grantType := "client_credentials"
audience = "" // Optional
auth := NewClientCredentialsTokenSource(clientID, clientSecret, tokenURL, scope, grantType, audience)
token, err := auth.Token()
if err != nil {
t.Fatal(err)
}
if token == "" {
t.Fatal("token is empty")
}
u, _ := url.Parse("https://fed-gw.example.com")
client := NewClient(u, &ClientCredentialsAuth{tokenSource: auth}, http.DefaultClient)
fns, err := client.GetFunctions(context.Background(), "openfaas-fn")
if err != nil {
t.Fatal(err)
}
if len(fns) == 0 {
t.Fatal("no functions found")
}
}
status, err := client.Deploy(context.Background(), types.FunctionDeployment{
Service: "env-store-test",
Image: "ghcr.io/openfaas/alpine:latest",
Namespace: "openfaas-fn",
EnvProcess: "env",
})
// non 200 status value will have some error
if err != nil {
log.Printf("Deploy Failed: %s", err)
}
err := client.DeleteFunction(context.Background(),"env-store-test", "openfaas-fn")
if err != nil {
log.Printf("Deletion Failed: %s", err)
}
Please refer examples folder for code examples of each operation
body := strings.NewReader("OpenFaaS")
req, err := http.NewRequestWithContext(context.TODO(), http.MethodPost, "/", body)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "text/plain")
async := false
authenticate := false
// Make a POST request to a figlet function in the openfaas-fn namespace
res, err := client.InvokeFunction(context.Background(), "figlet", "openfaas-fn", async, authenticate, req)
if err != nil {
log.Printf("Failed to invoke function: %s", err)
return
}
if res.Body != nil {
defer res.Body.Close()
}
// Read the response body
body, err := io.ReadAll(res.Body)
if err != nil {
log.Printf("Error reading response body: %s", err)
return
}
// Print the response
fmt.Printf("Response status code: %s\n", res.Status)
fmt.Printf("Response body: %s\n", string(body))
The SDK supports invoking functions if you are using OpenFaaS IAM with built-in authentication for functions.
Set the auth
argument to true
when calling InvokeFunction
to authenticate the request with an OpenFaaS function access token.
The Client
needs a TokenSource
to get an ID token that can be exchanged for a function access token to make authenticated function invocations. By default the TokenAuth
provider that was set when constructing a new Client
is used.
It is also possible to provide a custom TokenSource
for the function token exchange:
ts := sdk.NewClientCredentialsTokenSource(clientID, clientSecret, tokenURL, scope, grantType, audience)
client := sdk.NewClientWithOpts(gatewayURL, http.DefaultClient, sdk.WithFunctionTokenSource(ts))
Optionally a TokenCache
can be configured to cache function access tokens and prevent the client from having to do a token exchange each time a function is invoked.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fnTokenCache := sdk.NewMemoryTokenCache()
// Start garbage collection to remove expired tokens from the cache.
go fnTokenCache.StartGC(ctx, time.Second*10)
client := sdk.NewClientWithOpts(
gatewayUrl,
httpClient,
sdk.WithAuthentication(auth),
sdk.WithFunctionTokenCache(fnTokenCache),
)
Use the OpenFaaS OpenFaaS Function Builder API to build functions from code.
The Function Builder API provides a simple REST API to create your functions from source code. The API accepts a tar archive with the function build context and build configuration. The SDk provides methods to create this tar archive and invoke the build API.
If your functions are using a language template you will need to make sure the required templates are available on the file system. 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 or fetched with the faas-cli.
functionName := "hello-world"
handler := "./hello-world"
lang := "node18"
// Get the HMAC secret used for payload authentication with the builder API.
payloadSecret, err := os.ReadFile("payload.txt")
if err != nil {
log.Fatal(err)
}
payloadSecret := bytes.TrimSpace(payloadSecret)
// Initialize a new builder client.
builderURL, _ := url.Parse("http://pro-builder.openfaas.svc.cluster.local")
b := builder.NewFunctionBuilder(builderURL, http.DefaultClient, builder.WithHmacAuth(string(payloadSecret)))
// Create the function build context using the provided function handler and language template.
buildContext, err := builder.CreateBuildContext(functionName, handler, lang, []string{})
if err != nil {
log.Fatalf("failed to create build context: %s", err)
}
// Create a temporary file for the build tar.
tarFile, err := os.CreateTemp(os.TempDir(), "build-context-*.tar")
if err != nil {
log.Fatalf("failed to temporary file: %s", err)
}
tarFile.Close()
tarPath := tarFile.Name()
defer os.Remove(tarPath)
// Configuration for the build.
// Set the image name plus optional build arguments and target platforms for multi-arch images.
buildConfig := builder.BuildConfig{
Image: image,
Platforms: []string{"linux/arm64"},
BuildArgs: map[string]string{},
}
// Prepare a tar archive that contains the build config and build context.
// The function build context is a normal docker build context. Any valid folder with a Dockerfile will work.
if err := builder.MakeTar(tarPath, buildContext, &buildConfig); err != nil {
log.Fatal(err)
}
// Invoke the function builder with the tar archive containing the build config and context
// to build and push the function image.
result, err := b.Build(tarPath)
if err != nil {
log.Fatal(err)
}
// Print build logs
for _, logMsg := range result.Log {
fmt.Printf("%s\n", logMsg)
}
Take a look at the function builder examples for a complete example.
License: MIT