Documentation
¶
Index ¶
- Variables
- type BaseFunctionTemplateFetcher
- type Filter
- type FunctionTemplate
- type FunctionTemplateFetcher
- type FunctionTemplateFileContents
- type FunctionTemplateRenderer
- type GeneratedFunctionTemplateFetcher
- type GitFunctionTemplateFetcher
- type RenderConfig
- type Repository
- type ZipFunctionTemplateFetcher
Constants ¶
This section is empty.
Variables ¶
View Source
var GeneratedFunctionTemplates = []*generatedFunctionTemplate{
{
Name: "helloworld:golang",
Configuration: unmarshalConfig(`metadata: {}
spec:
build: {}
description: Showcases unstructured logging and a structured response.
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: main:Handler
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: golang
`),
SourceCode: `/*
Copyright 2023 The Nuclio Authors.
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.
*/
package main
import (
"github.com/nuclio/nuclio-sdk-go"
)
func Handler(context *nuclio.Context, event nuclio.Event) (interface{}, error) {
context.Logger.Info("This is an unstructured %s", "log")
return nuclio.Response{
StatusCode: 200,
ContentType: "application/text",
Body: []byte("Hello, from Nuclio :]"),
}, nil
}
`,
},
{
Name: "image:golang",
Configuration: unmarshalConfig(`metadata: {}
spec:
build: {}
description: |
Demonstrates how to pass a binary-large object (blob) in an HTTP request body and response. Defines an HTTP request that accepts a binary image or URL as input, converts the input to the target format and size, and returns the converted image in the HTTP response.
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: main:Handler
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: golang
`),
SourceCode: `/*
Copyright 2023 The Nuclio Authors.
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.
*/
// Nuclio image conversion and resizing example
// Usage: send an HTTP Post request with the body containing a URL of an image or the actual image binary
// can specify requested size and format via the URL query e.g.: /?x=50&y=50&format=png
package main
import (
"bytes"
"image"
"net/http"
"strings"
"github.com/disintegration/imaging"
"github.com/nuclio/nuclio-sdk-go"
)
func Handler(context *nuclio.Context, event nuclio.Event) (interface{}, error) {
// Set default values
x := 100
y := 100
imageType := imaging.JPEG
respType := "image/jpeg"
// Extract X, Y, Format from query args
if xval, err := event.GetFieldInt("x"); err == nil {
x = xval
}
if yval, err := event.GetFieldInt("y"); err == nil {
y = yval
}
if format := event.GetFieldString("format"); format == "png" {
imageType = imaging.PNG
respType = "image/png"
}
context.Logger.DebugWith("Got request", "path", event.GetPath(), "x", x, "y", y, "format", respType, "ctype", event.GetContentType())
var img image.Image
var err error
if strings.HasPrefix(event.GetContentType(), "text/plain") {
// if the body is text assume its a URL and read the image from the URL (in the text)
response, err := http.Get(string(event.GetBody()))
if err != nil {
return nil, err
}
// Try to decode the returned body (from the HTTP request to the provided URL)
img, err = imaging.Decode(response.Body)
} else {
// if the content is not text assume the Body contains the image and decode it
r := bytes.NewReader(event.GetBody())
img, err = imaging.Decode(r)
}
// If image Decode failed return an error
if err != nil {
context.Logger.Error("Failed to open image %v", err)
return nil, err
}
// Create a thumbnail with the specified size and format
thumb := imaging.Thumbnail(img, x, y, imaging.CatmullRom)
buf := new(bytes.Buffer)
err = imaging.Encode(buf, thumb, imageType)
// Return a response with an image and the proper Content Type
return nuclio.Response{
StatusCode: 200,
ContentType: respType,
Body: buf.Bytes(),
}, nil
}
`,
},
{
Name: "rabbitmq:golang",
Configuration: unmarshalConfig(`metadata: {}
spec:
build: {}
description: |
A multi-trigger function with a configuration that connects to RabbitMQ to read messages and write them to local ephemeral storage. If triggered with an HTTP GET request, the function returns the messages that it read from RabbitMQ.
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: main:Handler
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: golang
triggers:
test_rmq:
attributes:
exchangeName: exchange-name
queueName: queue-name
class: ""
kind: rabbit-mq
name: ""
url: amqp://user:password@rabbitmq-host:5672
`),
SourceCode: `/*
Copyright 2023 The Nuclio Authors.
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.
*/
//
// Listens to a RabbitMQ queue and records any messages posted to a given queue.
// Can retrieve these recorded messages through HTTP GET, demonstrating how a single
// function can be invoked from different triggers.
//
package main
import (
"io"
"net/http"
"os"
"github.com/nuclio/nuclio-sdk-go"
)
const eventLogFilePath = "/tmp/events.json"
func Handler(context *nuclio.Context, event nuclio.Event) (interface{}, error) {
context.Logger.InfoWith("Received event", "body", string(event.GetBody()))
// if we got the event from rabbit
if event.GetTriggerInfo().GetClass() == "async" && event.GetTriggerInfo().GetKind() == "rabbitMq" {
eventLogFile, err := os.OpenFile(eventLogFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
return nil, err
}
defer eventLogFile.Close()
// write the body followed by ', '
for _, dataToWrite := range [][]byte{
event.GetBody(),
[]byte(", "),
} {
// write the thing to write
if _, err = eventLogFile.Write(dataToWrite); err != nil {
return nil, err
}
}
// all's well
return nil, nil
}
// open the log for read
eventLogFile, err := os.OpenFile(eventLogFilePath, os.O_RDONLY, 0600)
if err != nil {
return nil, err
}
defer eventLogFile.Close()
// read the entire file
eventLogFileContents, err := io.ReadAll(eventLogFile)
if err != nil {
return nil, err
}
// chop off the last 2 chars and enclose in a [ ]
eventLogFileContentsString := "[" + string(eventLogFileContents[:len(eventLogFileContents)-2]) + "]"
// return the contents as JSON
return nuclio.Response{
StatusCode: http.StatusOK,
ContentType: "application/json",
Body: []byte(eventLogFileContentsString),
}, nil
}
`,
},
{
Name: "regexscan:golang",
Configuration: unmarshalConfig(`metadata: {}
spec:
build: {}
description: |
Uses regular expressions to find patterns of social-security numbers (SSN), credit-card numbers, etc., using text input.
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: main:Handler
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: golang
`),
SourceCode: `/*
Copyright 2023 The Nuclio Authors.
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.
*/
//
// Accept a string (event.body) and scan for compliance using a list of regex patterns (SSN, Credit Cards, ..)
// will return a list of compliance violations in Json or "Passed"
// demonstrate the use of structured and unstructured log with different levels
// can be extended to write results to a stream/object storage
//
package main
import (
"encoding/json"
"regexp"
"github.com/nuclio/nuclio-sdk-go"
)
// list of regular expression filters
var patterns = map[string]*regexp.Regexp{
"SSN": regexp.MustCompile(` + "`" + `\b\d{3}-\d{2}-\d{4}\b` + "`" + `),
"Credit card": regexp.MustCompile(` + "`" + `\b(?:\d[ -]*?){13,16}\b` + "`" + `),
}
func Handler(context *nuclio.Context, event nuclio.Event) (interface{}, error) {
context.Logger.DebugWith("Processing document",
"path", event.GetPath(),
"length", len(event.GetBody()))
patternMatches := []string{}
// Test content against a list of RegEx filters
for patternName, patternRegex := range patterns {
if patternRegex.Match(event.GetBody()) {
patternMatches = append(patternMatches, patternName)
}
}
response := map[string]interface{}{
"matches": patternMatches,
}
// If we found a filter match add structured warning log message and respond with match list
if len(patternMatches) > 0 {
context.Logger.WarnWith("Document matches patterns",
"path", event.GetPath(),
"content", patternMatches)
}
return json.Marshal(response)
}
`,
},
{
Name: "dates:nodejs",
Configuration: unmarshalConfig(`metadata: {}
spec:
build:
commands:
- npm install --global moment
description: |
Uses moment.js (which is installed as part of the build) to add a specified amount of time to "now", and returns this amount as a string.
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: handler
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: nodejs
`),
SourceCode: `/*
Copyright 2023 The Nuclio Authors.
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.
*/
// Uses moment.js (installed as part of the build) to add a given amount of time
// to "now", and returns this as string. Invoke with a JSON containing:
// - value: some number
// - unit: some momentjs unit, as a string - e.g. days, d, hours, miliseconds
//
// For example, the following will add 3 hours to current time and return the response:
// {
// "value": 3,
// "unit": "hours"
// }
//
var moment = require('moment');
exports.handler = function(context, event) {
var request = JSON.parse(event.body);
var now = moment();
context.logger.infoWith('Adding to now', {
'request': request,
'to': now.format()
});
now.add(request.value, request.unit);
context.callback(now.format());
};
`,
},
{
Name: "helloworld:dotnetcore",
Configuration: unmarshalConfig(`metadata: {}
spec:
build: {}
description: Showcases unstructured logging and a structured response.
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: nuclio:main
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: dotnetcore
`),
SourceCode: `// Copyright 2023 The Nuclio Authors.
//
// 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.
using System;
using Nuclio.Sdk;
public class nuclio
{
public object main(Context context, Event eventBase)
{
context.Logger.Info("This is an unstructured {0}", "log");
context.Logger.InfoWith("This is a", "structured", "log");
return new Response()
{
StatusCode = 200,
ContentType = "application/text",
Body = "Hello, from nuclio"
};
}
}
`,
},
{
Name: "reverser:dotnetcore",
Configuration: unmarshalConfig(`metadata: {}
spec:
build: {}
description: Returns the reverse of the body received in the event.
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: nuclio:reverser
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: dotnetcore
`),
SourceCode: `// Copyright 2023 The Nuclio Authors.
//
// 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.
using System;
using Nuclio.Sdk;
public class nuclio
{
public string reverser(Context context, Event eventBase)
{
var charArray = eventBase.GetBody().ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
}
`,
},
{
Name: "encrypt:python",
Configuration: unmarshalConfig(`metadata: {}
spec:
build:
commands:
- pip install simple-crypt
description: |
Uses a third-party Python package to encrypt the event body, and showcases build commands for installing both OS-level and Python packages.
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: encrypt:encrypt
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: python
`),
SourceCode: `# Copyright 2023 The Nuclio Authors.
#
# 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.
#
# Uses simplecrypt to encrypt the body with a key bound to the function as
# an environment variable. We ask pip to install simplecrypt as part of the
# build process, along with some OS level packages (using apk).
#
# Note: It takes a minute or so to install all the dependencies.
# Why not star https://github.com/nuclio/nuclio while you wait?
#
import os
import simplecrypt
def encrypt(context, event):
context.logger.info('Using secret to encrypt body')
# get the encryption key
encryption_key = os.environ.get('ENCRYPT_KEY', 'some-default-key')
# encrypt the body
encrypted_body = simplecrypt.encrypt(encryption_key, event.body)
# return the encrypted body, and some hard-coded header
return context.Response(body=str(encrypted_body),
headers={'x-encrypt-algo': 'aes256'},
content_type='text/plain',
status_code=200)
`,
},
{
Name: "facerecognizer:python",
Configuration: unmarshalConfig(`metadata: {}
spec:
build:
commands:
- pip install cognitive_face tabulate inflection
description: |
Uses Microsoft's face API, configured with function environment variables. The function uses third-party Python packages, which are installed by using an inline configuration.
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: face:handler
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: python
`),
SourceCode: `# Copyright 2023 The Nuclio Authors.
#
# 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.
#
# Uses Microsoft's Face API to extract face information from the
# picture whose URL is submitted in the request body. The result is
# returned as a table of face objects sorted by their center's
# position in the given picture, left-to-right and then top-to-bottom.
#
# You will need a valid key from Microsoft:
# https://azure.microsoft.com/en-gb/try/cognitive-services/?api=face-api
#
# Once a valid Face API key has been acquired, set it and the appropriate
# regional base URL as the environment for this function
# (in the config section).
#
# We can also configure the function inline - through a specially crafted
# comment such as the below. This is functionally equivalent to creating
# a function.yaml file.
import os
import cognitive_face as cf
import tabulate
import inflection
def handler(context, event):
# extract the stuff we need
image_url = event.body.decode('utf-8').strip()
key = os.environ.get('FACE_API_KEY')
base_url = os.environ.get('FACE_API_BASE_URL')
if key is None:
context.logger.warn('Face API key not set, cannot continue')
return _build_response(context, 'Function misconfigured: Face API key not set', 503)
if base_url is None:
context.logger.warn('Face API base URL not set, cannot continue')
return _build_response(context, 'Function misconfigured: Face API base URL not set', 503)
if not image_url:
context.logger.warn('No URL given in request body')
return _build_response(context, 'Image URL required', 400)
# configure cognitive face wrapper
cf.Key.set(key)
cf.BaseUrl.set(base_url)
# attempt to request using the provided info
try:
context.logger.info('Requesting detection from Face API: {0}'.format(image_url))
detected_faces = cf.face.detect(image_url,
face_id=False,
attributes='age,gender,glasses,smile,emotion')
except Exception as error:
context.logger.warn('Face API error occurred: {0}'.format(error))
return _build_response(context, 'Face API error occurred', 503)
parsed_faces = []
# determine the center point of each detected face and map it to its attributes,
# as well as clean up the retreived data for viewing comfort
for face in detected_faces:
coordinates = face['faceRectangle']
attributes = face['faceAttributes']
center_x = coordinates['left'] + coordinates['width'] / 2
center_y = coordinates['top'] + coordinates['height'] / 2
# determine the primary emotion based on its weighing
primary_emotion = sorted(attributes['emotion'].items(), key=lambda item: item[1])[-1][0]
parsed_face = {
'x': center_x,
'y': center_y,
'position': '({0},{1})'.format(int(center_x), int(center_y)),
'gender': inflection.humanize(attributes['gender']),
'age': int(attributes['age']),
'glasses': inflection.humanize(inflection.underscore(attributes['glasses'])),
'primary_emotion': inflection.humanize(primary_emotion),
'smile': '{0:.1f}%'.format(attributes['smile'] * 100),
}
parsed_faces.append(parsed_face)
# sort according to center point, first x then y
parsed_faces.sort(key=lambda face: (face['x'], face['y']))
# prepare the data for tabulation
first_row = ('',) + tuple(face['position'] for face in parsed_faces)
make_row = lambda name: (inflection.humanize(name),) + tuple(
face[name] for face in parsed_faces)
other_rows = [make_row(name) for name in [
'gender', 'age', 'primary_emotion', 'glasses', 'smile']]
# return the human-readable face data in a neat table format
return _build_response(context,
tabulate.tabulate([first_row] + other_rows,
headers='firstrow',
tablefmt='fancy_grid',
numalign='center',
stralign='center'),
200)
def _build_response(context, body, status_code):
return context.Response(body=body,
headers={},
content_type='text/plain',
status_code=status_code)
`,
},
{
Name: "helloworld:python",
Configuration: unmarshalConfig(`metadata: {}
spec:
build: {}
description: Showcases unstructured logging and a structured response.
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: helloworld:handler
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: python
`),
SourceCode: `# Copyright 2023 The Nuclio Authors.
#
# 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.
def handler(context, event):
context.logger.info('This is an unstructured log')
return context.Response(body='Hello, from nuclio :]',
headers={},
content_type='text/plain',
status_code=200)
`,
},
{
Name: "sentiments:python",
Configuration: unmarshalConfig(`metadata: {}
spec:
build:
commands:
- pip install requests vaderSentiment
description: Identifies sentiments in the body strings
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: sentiments:handler
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: python
`),
SourceCode: `# Copyright 2023 The Nuclio Authors.
#
# 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.
#
# uses vader lib (will be installed automatically via build commands) to identify sentiments in the body string
# return score result in the form of: {'neg': 0.0, 'neu': 0.323, 'pos': 0.677, 'compound': 0.6369}
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
def handler(context, event):
body = event.body.decode('utf-8')
context.logger.debug_with('Analyzing ', 'sentence', body)
analyzer = SentimentIntensityAnalyzer()
score = analyzer.polarity_scores(body)
return str(score)
`,
},
{
Name: "tensorflow:python",
Configuration: unmarshalConfig(`metadata: {}
spec:
build:
baseImage: python:3.9-buster
commands:
- apt-get update && apt-get install -y wget
- wget http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz
- mkdir -p /tmp/tfmodel
- tar -xzvf inception-2015-12-05.tgz -C /tmp/tfmodel
- rm inception-2015-12-05.tgz
- pip install requests numpy tensorflow
description: |
Uses the inception model of the TensorFlow open-source machine-learning library to classify images. The function demonstrates advanced uses of nuclio with a custom base image, third-party Python packages, pre-loading data into function memory (the AI Model), structured logging, and exception handling.
disableDefaultHTTPTrigger: false
eventTimeout: ""
handler: tensor:classify
maxReplicas: 1
minReplicas: 1
platform: {}
resources: {}
runtime: python
`),
SourceCode: `# Copyright 2023 The Nuclio Authors.
#
# 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.
#
# This function uses TensorFlow to perform image recognition.
# It takes advantage of nuclio's inline configuration to indicate
# its pip dependencies, as well as the linux distribution
# to use for the deployed function's container.
#
# You can try invoking this function by passing any .jpg image's URL
# in the request's body. For instance:
# http://www.lpcsathletics.org/wp-content/uploads/2017/08/soccer-5.jpg
#
# This example is based on the example in TensorFlow's official repository:
# https://github.com/tensorflow/models/blob/master/tutorials/image/imagenet/classify_image.py
#
# Simple image classification with Inception
#
# Runs image classification with Inception trained on ImageNet 2012
# Challenge data set.
#
# This program creates a graph from a saved GraphDef protocol buffer,
# and runs inference on an input JPEG image. It outputs human readable
# strings of up to the top 5 predictions along with their probabilities.
import os
import os.path
import re
import requests
import shutil
import tarfile
import traceback
import threading
import numpy as np
import tensorflow as tf
def classify(context, event):
# we're going to need a unique temporary location to handle each event,
# as we download a file as part of each function invocation
temp_dir = Helpers.create_temporary_dir(context, event)
# wrap everything with error handling such that any exception raised
# at any point will still return a proper response
try:
# if we're not ready to handle this request yet, deny it
if not FunctionState.done_loading:
context.logger.warn_with('Model data not done loading yet, denying request')
raise NuclioResponseError('Model data not loaded yet, cannot serve this request',
requests.codes.service_unavailable)
# read the event's body to determine the target image URL
# TODO: in the future this can also take binary image data if provided with an appropriate content-type
image_url = event.body.decode('utf-8').strip()
# download the image to our temporary location
image_target_path = os.path.join(temp_dir, 'downloaded_image.jpg')
Helpers.download_file(context, image_url, image_target_path)
# run the inference on the image
results = Helpers.run_inference(context, image_target_path, 5, 0.3)
# return a response with the result
return context.Response(body=str(results),
headers={},
content_type='text/plain',
status_code=requests.codes.ok)
# convert any NuclioResponseError to a response to be returned from our handler.
# the response's description and status will appropriately convey the underlying error's nature
except NuclioResponseError as error:
return error.as_response(context)
# if anything we didn't count on happens, respond with internal server error
except Exception as error:
context.logger.warn_with('Unexpected error occurred, responding with internal server error',
exc=str(error))
message = 'Unexpected error occurred: {0}\n{1}'.format(error, traceback.format_exc())
return NuclioResponseError(message).as_response(context)
# clean up after ourselves regardless of whether we succeeded or failed
finally:
shutil.rmtree(temp_dir)
class NuclioResponseError(Exception):
def __init__(self, description, status_code=requests.codes.internal_server_error):
self._description = description
self._status_code = status_code
def as_response(self, context):
return context.Response(body=self._description,
headers={},
content_type='text/plain',
status_code=self._status_code)
class FunctionState(object):
"""
This class has classvars that are set by methods invoked during file import,
such that handler invocations can re-use them.
"""
# holds the TensorFlow graph def
graph = None
# holds the node id to human string mapping
node_lookup = None
# holds a boolean indicating if we're ready to handle an invocation or haven't finished yet
done_loading = False
class Paths(object):
# the directory in the deployed function container where the data model is saved
model_dir = os.getenv('MODEL_DIR', '/tmp/tfmodel/')
# paths of files within the model archive used to create the graph
label_lookup_path = os.path.join(model_dir,
os.getenv('LABEL_LOOKUP_FILENAME',
'imagenet_synset_to_human_label_map.txt'))
uid_lookup_path = os.path.join(model_dir,
os.getenv('UID_LOOKUP_FILENAME',
'imagenet_2012_challenge_label_map_proto.pbtxt'))
graph_def_path = os.path.join(model_dir,
os.getenv('GRAPH_DEF_FILENAME',
'classify_image_graph_def.pb'))
class Helpers(object):
@staticmethod
def create_temporary_dir(context, event):
"""
Creates a uniquely-named temporary directory (based on the given event's id) and returns its path.
"""
temp_dir = '/tmp/nuclio-event-{0}'.format(event.id)
os.makedirs(temp_dir)
context.logger.debug_with('Created temporary directory', path=temp_dir)
return temp_dir
@staticmethod
def run_inference(context, image_path, num_predictions, confidence_threshold):
"""
Runs inference on the image in the given path.
Returns a list of up to N=num_prediction tuples (prediction human name, confidence score).
Only takes predictions whose confidence score meets the provided confidence threshold.
"""
# read the image binary data
with tf.gfile.FastGFile(image_path, 'rb') as f:
image_data = f.read()
# run the graph's softmax tensor on the image data
with tf.Session(graph=FunctionState.graph) as session:
softmax_tensor = session.graph.get_tensor_by_name('softmax:0')
predictions = session.run(softmax_tensor, {'DecodeJpeg/contents:0': image_data})
predictions = np.squeeze(predictions)
results = []
# take the num_predictions highest scoring predictions
top_predictions = reversed(predictions.argsort()[-num_predictions:])
# look up each predicition's human-readable name and add it to the
# results if it meets the confidence threshold
for node_id in top_predictions:
name = FunctionState.node_lookup[node_id]
score = predictions[node_id]
meets_threshold = score > confidence_threshold
# tensorflow's float32 must be converted to float before logging, not JSON-serializable
context.logger.info_with('Found prediction',
name=name,
score=float(score),
meets_threshold=meets_threshold)
if meets_threshold:
results.append((name, score))
return results
@staticmethod
def on_import():
"""
This function is called when the file is imported, so that model data
is loaded to memory only once per function deployment.
"""
# load the graph def from trained model data
FunctionState.graph = Helpers.load_graph_def()
# load the node ID to human-readable string mapping
FunctionState.node_lookup = Helpers.load_node_lookup()
# signal that we're ready
FunctionState.done_loading = True
@staticmethod
def load_graph_def():
"""
Imports the GraphDef data into TensorFlow's default graph, and returns it.
"""
# verify that the declared graph def file actually exists
if not tf.gfile.Exists(Paths.graph_def_path):
raise NuclioResponseError('Failed to find graph def file', requests.codes.service_unavailable)
# load the TensorFlow GraphDef
with tf.gfile.FastGFile(Paths.graph_def_path, 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
tf.import_graph_def(graph_def, name='')
return tf.get_default_graph()
@staticmethod
def load_node_lookup():
"""
Composes the mapping between node IDs and human-readable strings. Returns the composed mapping.
"""
# load the mappings from which we can build our mapping
string_uid_to_labels = Helpers._load_label_lookup()
node_id_to_string_uids = Helpers._load_uid_lookup()
# compose the final mapping of integer node ID to human-readable string
result = {}
for node_id, string_uid in node_id_to_string_uids.items():
label = string_uid_to_labels.get(string_uid)
if label is None:
raise NuclioResponseError('Failed to compose node lookup')
result[node_id] = label
return result
@staticmethod
def download_file(context, url, target_path):
"""
Downloads the given remote URL to the specified path.
"""
# make sure the target directory exists
os.makedirs(os.path.dirname(target_path), exist_ok=True)
try:
with requests.get(url, stream=True) as response:
response.raise_for_status()
with open(target_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
except Exception as error:
if context is not None:
context.logger.warn_with('Failed to download file',
url=url,
target_path=target_path,
exc=str(error))
raise NuclioResponseError('Failed to download file: {0}'.format(url),
requests.codes.service_unavailable)
if context is not None:
context.logger.info_with('Downloaded file successfully',
size_bytes=os.stat(target_path).st_size,
target_path=target_path)
@staticmethod
def _load_label_lookup():
"""
Loads and parses the mapping between string UIDs and human-readable strings. Returns the parsed mapping.
"""
# verify that the declared label lookup file actually exists
if not tf.gfile.Exists(Paths.label_lookup_path):
raise NuclioResponseError('Failed to find Label lookup file', requests.codes.service_unavailable)
# load the raw mapping data
with tf.gfile.GFile(Paths.label_lookup_path) as f:
lookup_lines = f.readlines()
result = {}
# parse the raw data to a mapping between string UIDs and labels
# each line is expected to look like this:
# n12557064 kidney bean, frijol, frijole
line_pattern = re.compile(r'(n\d+)\s+([ \S,]+)')
for line in lookup_lines:
matches = line_pattern.findall(line)
# extract the uid and label from the matches
# in our example, uid will be "n12557064" and label will be "kidney bean, frijol, frijole"
uid = matches[0][0]
label = matches[0][1]
# insert the UID and label to our mapping
result[uid] = label
return result
@staticmethod
def _load_uid_lookup():
"""
Loads and parses the mapping between node IDs and string UIDs. Returns the parsed mapping.
"""
# verify that the declared uid lookup file actually exists
if not tf.gfile.Exists(Paths.uid_lookup_path):
raise NuclioResponseError('Failed to find UID lookup file', requests.codes.service_unavailable)
# load the raw mapping data
with tf.gfile.GFile(Paths.uid_lookup_path) as f:
lookup_lines = f.readlines()
result = {}
# parse the raw data to a mapping between integer node IDs and string UIDs
# this file is expected to contains entries such as this:
#
# entry
# {
# target_class: 443
# target_class_string: "n01491361"
# }
#
# to parse it, we'll iterate over the lines, and for each line that begins with " target_class:"
# we'll assume that the next line has the corresponding "target_class_string"
for i, line in enumerate(lookup_lines):
# we found a line that starts a new entry in our mapping
if line.startswith(' target_class:'):
# target_class represents an integer value for node ID - convert it to an integer
target_class = int(line.split(': ')[1])
# take the string UID from the next line,
# and clean up the quotes wrapping it (and the trailing newline)
next_line = lookup_lines[i + 1]
target_class_string = next_line.split(': ')[1].strip('"\n ')
# insert the node ID and string UID to our mapping
result[target_class] = target_class_string
return result
# perform the loading in another thread to not block import - the function
# handler will gracefully decline requests until we're ready to handle them
t = threading.Thread(target=Helpers.on_import)
t.start()
`,
},
{
Name: "img-convert:shell",
Configuration: unmarshalConfig(`metadata: {}
spec:
build:
commands:
- apk --update --no-cache add imagemagick
description: Resize image to 50% using ImageMagick
eventTimeout: ""
handler: img-convert.sh:main
platform: {}
resources: {}
runtime: shell
`),
SourceCode: `# Copyright 2023 The Nuclio Authors.
#
# 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.
#
#
# Demonstrates running a shell script. In this case, ImageMagick is installed on build and "convert"
# is called for each event with stdin as the input (by default, this is fed with the event body).
#
# NOTE:
#
# This can be achieved without a wrapper script by specifying the "convert" binary as the handler. To do this
# with nuctl you would run (pass --platform local if you're using the local platform):
#
# nuctl deploy -p /dev/null convert \
# --runtime shell \
# --handler convert \
# --runtime-attrs '{"arguments": "- -resize 50% fd:1"}' \
# --build-command "apk --update --no-cache add imagemagick"
#
# Doing so gives you much greater flexibility than a wrapper script because the arguments can be changed per event.
# If X-nuclio-arguments does not exist in the event headers, the default arguments passed to convert tells it to
# reduce the image to 50%. To run any other mode or any other setting, simply provide this header (note that this is
# unsanitized). For example, to reduce the received image to 10% of its size, set X-nuclio-arguments to
# "- -resize 10% fd:1"
#
# @nuclio.configure
#
# function.yaml:
# apiVersion: "nuclio.io/v1beta1"
# kind: "NuclioFunction"
# spec:
# runtime: "shell"
# handler: "img-convert.sh:main"
# description: "Resize image to 50% using ImageMagick"
#
# build:
# commands:
# - "apk --update --no-cache add imagemagick"
#
convert - -resize 50% fd:1
`,
},
}
Functions ¶
This section is empty.
Types ¶
type BaseFunctionTemplateFetcher ¶
type BaseFunctionTemplateFetcher struct { }
type FunctionTemplate ¶
type FunctionTemplateFetcher ¶
type FunctionTemplateFetcher interface {
Fetch() ([]*FunctionTemplate, error)
}
type FunctionTemplateFileContents ¶
keeps the file contents of a function template
type FunctionTemplateRenderer ¶
type FunctionTemplateRenderer struct {
// contains filtered or unexported fields
}
func NewFunctionTemplateRenderer ¶
func NewFunctionTemplateRenderer(parentLogger logger.Logger) *FunctionTemplateRenderer
func (*FunctionTemplateRenderer) Render ¶
func (r *FunctionTemplateRenderer) Render(renderGivenValues *RenderConfig) (*functionconfig.Config, error)
type GeneratedFunctionTemplateFetcher ¶
type GeneratedFunctionTemplateFetcher struct {
// contains filtered or unexported fields
}
func NewGeneratedFunctionTemplateFetcher ¶
func NewGeneratedFunctionTemplateFetcher(parentLogger logger.Logger) (*GeneratedFunctionTemplateFetcher, error)
func (*GeneratedFunctionTemplateFetcher) Fetch ¶
func (gftf *GeneratedFunctionTemplateFetcher) Fetch() ([]*FunctionTemplate, error)
func (*GeneratedFunctionTemplateFetcher) SetGeneratedFunctionTemplates ¶
func (gftf *GeneratedFunctionTemplateFetcher) SetGeneratedFunctionTemplates(generatedFunctionTemplates []*generatedFunctionTemplate) error
type GitFunctionTemplateFetcher ¶
type GitFunctionTemplateFetcher struct { BaseFunctionTemplateFetcher // contains filtered or unexported fields }
func (*GitFunctionTemplateFetcher) Fetch ¶
func (gftf *GitFunctionTemplateFetcher) Fetch() ([]*FunctionTemplate, error)
type RenderConfig ¶
type Repository ¶
type Repository struct {
// contains filtered or unexported fields
}
func NewRepository ¶
func NewRepository(parentLogger logger.Logger, fetchers []FunctionTemplateFetcher) (*Repository, error)
func (*Repository) GetFunctionTemplates ¶
func (r *Repository) GetFunctionTemplates(filter *Filter) []*FunctionTemplate
type ZipFunctionTemplateFetcher ¶
type ZipFunctionTemplateFetcher struct { BaseFunctionTemplateFetcher // contains filtered or unexported fields }
func NewZipFunctionTemplateFetcher ¶
func NewZipFunctionTemplateFetcher(parentLogger logger.Logger, fileAddress string) (*ZipFunctionTemplateFetcher, error)
func (*ZipFunctionTemplateFetcher) Fetch ¶
func (zftf *ZipFunctionTemplateFetcher) Fetch() ([]*FunctionTemplate, error)
Click to show internal directories.
Click to hide internal directories.