fdk

package module
v0.4.0-alpha Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 25, 2023 License: MIT Imports: 14 Imported by: 3

README

CrowdStrike Falcon

Foundry Function as a Service Go SDK

foundry-fn-go is a community-driven, open source project designed to enable the authoring of functions. While not a formal CrowdStrike product, foundry-fn-go is maintained by CrowdStrike and supported in partnership with the open source developer community.

Installation ⚙️

Via go get

The SDK can be installed or updated via go get:

go get github.com/CrowdStrike/foundry-fn-go
From source

The SDK can be built from source via standard build:

go mod tidy
go build .

Quickstart 💫

Code

Add the SDK to your project by following the installation instructions above, then create your main.go:

package main

import (
    /* omitting other imports */
    fdk "github.com/CrowdStrike/foundry-fn-go"
)

var (
    cfg *fdk.Config /*** (1) ***/
)

/*** (2) ***/
func RunHandler(_ context.Context, request fdk.Request /*** (3) ***/) (fdk.Response, error) {
    b := bytes.NewBuffer(nil)
    err := json.NewEncoder(b).Encode(request)
    return fdk.Response{Body: b.Bytes(), Code: 200}, err /*** (4) ***/
}

func main() { /*** (5) ***/
    cfg = &fdk.Config{}
    err := fdk.LoadConfig(cfg) /*** (6) ***/
    if err != nil && err != fdk.ErrNoConfig {
        os.Exit(1)
    }
    fdk.Start(RunHandler) /*** (7) ***/
}
  1. cfg: A global variable which holds any loaded configuration. Should be initialized exactly once within main().
  2. RunHandler(): Called once on each inbound request. This is where the business logic of the function should exist.
  3. request: Request payload and metadata. At the time of this writing, the Request struct consists of:
    1. Body: The raw request payload as given in the Function Gateway body payload field.
    2. Params: Contains request headers and query parameters.
    3. URL: The request path relative to the function as a string.
    4. Method: The request HTTP method or verb.
    5. Context: Caller-supplied raw context.
    6. AccessToken: Caller-supplied access token.
  4. Return from RunHandler(): Returns two values - a Response and an error.
    1. The Response contains fields Body (the payload of the response), Code (an HTTP status code), Errors (a slice of APIErrors), and Headers (a map of any special HTTP headers which should be present on the response).
    2. The error is an indication that there was a transient error on the request. In production, returning a non-nil error will result in a retry of the request. There is a limited number of reties before the request will be dead-lettered. Do not return an error unless you actually want the request to be retried.
  5. main(): Initialization and bootstrap logic. Called exactly once at the startup of the runtime. Any initialization logic should exist prior to calling fdk.Start().
  6. LoadConfig(cfg): Loads any configuration deployed along with function into the *fdk.Config. Will return an ErrNoConfig if no configuration was available to be loaded. Will return another error if there was some other error loading the configuration.
  7. fdk.Start(RunHandler): Binds the handler function to the runtime and starts listening to inbound requests.
Testing locally

The SDK provides an out-of-the-box runtime for executing the function. A basic HTTP server will be listening on port 8081.

# build the project which uses the sdk
cd my-project && go mod tidy && go build -o run_me .

# run the executable
./run_me

Requests can now be made against the executable.

curl -X POST http://localhost:8081/echo \
  -H "Content-Type: application/json" \
  --data '{"foo": "bar"}'

Convenience Functionality 🧰

gofalcon

Foundry Function Go ships with gofalcon pre-integrated and a convenience constructor. While it is not strictly necessary to use convenience function, it is recommended.

Important: Create a new instance of the gofalcon client on each request.

import (
    /* omitting other imports */
    fdk "github.com/crowdstrike/foundry-fn-go"
)

func RunHandler(ctx context.Context, request fdk.Request) (fdk.Response, error) {
    /* ... omitting other code ... */
    
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // !!! create a new client instance on each request !!!
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    
    client, err := fdk.FalconClient(ctx, request)
    if err != nil {
        if err == fdk.ErrFalconNoToken {
            // not a processable request
            return fdk.Response{ /* snip */ }, nil
        }
        // some other error - see gofalcon documentation
    }
    
    /* ... omitting other code ... */
}


WE STOP BREACHES

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrCfgNotFound = errors.New("no config provided")
)

Functions

func FalconClient

FalconClient returns a new instance of the GoFalcon client. If the client cannot be created or if there is no access token in the request, an error is returned.

func Fn added in v0.4.0

func Fn() struct {
	ID      string
	Version int
}

func JSON added in v0.4.0

func JSON(v any) json.Marshaler

JSON jsonifies the input to valid json upon request marshaling.

func RegisterConfigLoader added in v0.4.0

func RegisterConfigLoader(loaderType string, cr ConfigLoader)

func RegisterRunner added in v0.4.0

func RegisterRunner(runnerType string, r Runner)

RegisterRunner registers a runner.

func Run added in v0.4.0

func Run[T Cfg](ctx context.Context, newHandlerFn func(cfg T) Handler)

Run is the meat and potatoes. This is the entrypoint for everything.

Types

type APIError

type APIError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

type Cfg added in v0.4.0

type Cfg interface {
	OK() error
}

Cfg marks the configuration type parameter. Any config type must have a validation method, OK, defined on it.

type ConfigLoader added in v0.4.0

type ConfigLoader interface {
	LoadConfig(ctx context.Context) ([]byte, error)
}

type Handler

type Handler interface {
	Handle(ctx context.Context, r Request) Response
}

Handler provides a handler for our incoming request.

TODO(berg): I'm a little confused why we have a response, with APIErrors, and a go type error being returned in the legacy sdks. This creates multiple ways to do the same thing. I'd be confused, as I am now I suppose, with what goes where. If we remove the error from the return tuple, the only place for errors now is in the Response type. I think this makes good sense. Lets not create a failure condition from something that could be in user space.

func ErrHandler added in v0.4.0

func ErrHandler(errs ...APIError) Handler

ErrHandler creates a new handler to resopnd with only errors.

func HandleFnOf added in v0.4.0

func HandleFnOf[T any](fn func(context.Context, RequestOf[T]) Response) Handler

HandleFnOf provides a means to translate the incoming requests to the destination body type. This normalizes the sad path and provides the caller with a zero fuss request to work with. Reducing json boilerplate for what is essentially the same operation on different types.

TODO(berg): name could be HandlerOf perhaps?

type HandlerFn added in v0.4.0

type HandlerFn func(ctx context.Context, r Request) Response

func (HandlerFn) Handle added in v0.4.0

func (h HandlerFn) Handle(ctx context.Context, r Request) Response

type Mux added in v0.4.0

type Mux struct {
	// contains filtered or unexported fields
}

func NewMux added in v0.4.0

func NewMux() *Mux

func (*Mux) Delete added in v0.4.0

func (m *Mux) Delete(route string, h Handler)

func (*Mux) Get added in v0.4.0

func (m *Mux) Get(route string, h Handler)

func (*Mux) Handle added in v0.4.0

func (m *Mux) Handle(ctx context.Context, r Request) Response

func (*Mux) Post added in v0.4.0

func (m *Mux) Post(route string, h Handler)

func (*Mux) Put added in v0.4.0

func (m *Mux) Put(route string, h Handler)

type Request

type Request RequestOf[json.RawMessage]

Request a word for Request. We can treat it as an internal impl details, and provide the user an http.Request, built from this internal impl. They can then build up the usual http.ServeHTTP, the same as they are likely accustomed too. We too can take advantage of this and add middleware,etc to the runner as needed. When testing locally, they just use curl, and they provide it the way they are use too. For our lambda impl, we convert it to the expected request body the http server expects, then its treated the same as our standard impl.

type RequestOf added in v0.4.0

type RequestOf[T any] struct {
	Body T
	// TODO(berg): can we axe Context? have workflow put details in the body/headers/params instead?
	Context json.RawMessage
	Params  struct {
		Header http.Header
		Query  url.Values
	}
	// TODO(berg): explore changing this field to Path, as URL is misleading. It's never
	// 			   an fqdn, only the path of the url.
	URL         string
	Method      string
	AccessToken string
}

RequestOf provides a generic body we can target our unmarshaling into. We don't have to have this as some users may be happy with the OG Request. However, we can express the former with the latter here.

type Response

type Response struct {
	Body   json.Marshaler
	Code   int
	Errors []APIError
	Header http.Header
}

type Runner added in v0.4.0

type Runner interface {
	Run(ctx context.Context, logger *slog.Logger, h Handler)
}

Runner defines the runtime that executes the request/response handler lifecycle.

type SkipCfg added in v0.4.0

type SkipCfg struct{}

SkipCfg indicates the config is not needed and will skip the config loading procedure.

func (SkipCfg) OK added in v0.4.0

func (n SkipCfg) OK() error

OK is a noop validation.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL