lambdaapp

package
v1.61.1 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package lambdaapp runs sluis as AWS Lambda functions: one binary, one zip, three functions, the role chosen by SLUIS_ROLE (docs/integrations/aws-lambda.md).

http    the issuer and the console behind an API Gateway HTTP API
github  the GitHub roster controller: one pass of one organisation per invocation
slack   the Slack roster controller: one pass of one workspace per invocation

This package assembles each role from the same pieces the Kubernetes process is assembled from (internal/rosterapp and the controllers' apps), and adds the two things Lambda needs: translating the platform's events, and reading secrets from SSM at cold start. It names no cluster, NATS or Valkey client, and cmd/sluis-lambda's import guard keeps it so.

Index

Constants

View Source
const (
	// KindTick is what an EventBridge Scheduler schedule sends, one per target.
	KindTick = "tick"
	// KindRun is what the http function sends when somebody asks for a pass now
	// (internal/port/invoke).
	KindRun = invoke.KindRun
)

The event kinds of a controller function. Both run the same pass; they differ in who asked.

View Source
const (
	// OutcomeRan is a pass that ran to its end.
	OutcomeRan = "ran"
	// OutcomeUnknown is a run-now of a target this controller does not run: the
	// notification was sent to every controller because nobody knew which kind
	// the target is, and the other one ends here, cleanly.
	OutcomeUnknown = "unknown"
	// OutcomeContended is a target another invocation holds the lease of: the
	// pass is its, and this one ends cleanly. It is not a failure, so the
	// platform neither retries it nor counts it as an error.
	OutcomeContended = "contended"
)

The outcomes of a controller invocation.

View Source
const (
	RoleHTTP   = "http"
	RoleGitHub = "github"
	RoleSlack  = "slack"
)

The roles, the values of SLUIS_ROLE.

View Source
const (
	// EnvRole is the function's role: http, github or slack. Required.
	EnvRole = "SLUIS_ROLE"
	// EnvConfigFile is the configuration file's path in the zip.
	EnvConfigFile = "SLUIS_CONFIG_FILE"
	// DefaultConfigFile is where the deploy tooling puts it.
	DefaultConfigFile = "/var/task/config/sluis.yaml"
)

The environment of a function (and EnvSecretFiles, in ssm.go). Everything else is the configuration file's, or OpenTelemetry's own OTEL_* variables, or the platform's.

View Source
const EnvSecretFiles = "SLUIS_SECRET_FILES"

EnvSecretFiles names the variable that lists the files to write: a JSON array of {"parameter":"/sluis/private/...","path":"/tmp/..."}.

View Source
const KindExports = "exports"

KindExports is the event an EventBridge Scheduler schedule sends the `http` function to keep the exports (the copies of secrets under /sluis/export/...) current: {"kind":"exports"}. There is no loop to do it on Lambda.

View Source
const KindRefresh = "refresh"

KindRefresh is the event an EventBridge Scheduler schedule sends the `http` function to take a new snapshot of every connected workspace's directory: {"kind":"refresh"}. The Kubernetes server does it on a ticker; there is no loop on Lambda, and a snapshot that is not refreshed stops being authoritative after the freshness window, which sign-in refuses on.

View Source
const SSMPrefix = "ssm:"

SSMPrefix marks an environment variable whose value is not the value but where to read it: `OAUTH_CLIENT_SECRET=ssm:/sluis/private/config/oauth/client-secret`. The configuration file names the variable that holds a secret (`secretEnv`), and on Lambda the deployment fills that variable at cold start from SSM Parameter Store, so the file, which is in the zip, never holds the secret and the function's environment holds only a path.

Variables

View Source
var SSMRoots = []string{"/sluis/private/", "/sluis/export/"}

SSMRoots are the only places a configuration secret may be read from: the service's own under /sluis/private and what it exports under /sluis/export (decision D1a). A path outside them is refused, so a function's environment cannot be pointed at another service's parameters.

Functions

func ParseEvent

func ParseEvent(payload json.RawMessage) (invoke.Event, error)

ParseEvent reads and checks a controller function's event.

func Request

Request makes the http.Request an event describes. The headers arrive lower-cased and the cookies apart from them, and the body is base64 when the gateway says so.

func ResolveEnv

func ResolveEnv(ctx context.Context, environ []string, set func(name, value string) error, open func(context.Context) (ParameterAPI, error)) ([]string, error)

ResolveEnv replaces every `ssm:<path>` value in environ with the parameter's decrypted value, through set, and returns the names it set. Nothing it logs or returns contains a value. open is called only if there is something to read, so a function with no such variable needs no SSM permission.

It is a small reader of paths and not the `ssm` Secrets adapter of the secrets concern, which stores the service's dynamic secrets and has other semantics (versions, exports): this reads configuration, once.

func Response

func Response(status int, header http.Header, body []byte) events.APIGatewayV2HTTPResponse

Response makes an event's response of what a handler wrote. Set-Cookie goes in the response's cookies, as the gateway requires (a header of that name is dropped), the other headers keep their values comma-joined, and the body is base64 when it is not text.

func WriteSecretFiles

func WriteSecretFiles(ctx context.Context, spec string, open func(context.Context) (ParameterAPI, error)) ([]string, error)

WriteSecretFiles writes each parameter's decrypted value to its path, mode 0600 in directories of mode 0700, and returns the paths. Every `*File` setting of the configuration (the GitHub App key files, the Slack secrets, `signingKey.kms.stateSecretFile`) then works as on Kubernetes by naming one of them. Values are written exactly as stored, with no newline added.

Types

type Controller

type Controller struct {
	// Name is the controller's, in log lines.
	Name string
	// Open assembles the controller.
	Open func(ctx context.Context) (Pass, error)
	Log  *slog.Logger
	// Unknown says an error is the controller not running that target.
	Unknown func(error) bool
}

Controller runs one pass of one target per invocation.

The controller is assembled per invocation and closed at its end, as `sluis tick` does: the audit emitter delivers asynchronously, and a function that is frozen between invocations would hold its queue for hours. What is per process stays per process: the configuration, read and validated at cold start, and the secrets resolved into the environment.

func (*Controller) Handle

func (c *Controller) Handle(ctx context.Context, payload json.RawMessage) (any, error)

Handle implements Handler: payload is {"kind":"tick"|"run","target":"<id>"}.

type ExportsResult added in v1.59.0

type ExportsResult struct {
	Kind string `json:"kind"`
	// Outcome is "ran", "none" (no export is declared) or "failed".
	Outcome   string `json:"outcome"`
	Exports   int    `json:"exports"`
	Done      int    `json:"done"`
	Contended int    `json:"contended"`
	Failed    int    `json:"failed"`
}

ExportsResult is what an exports invocation returns.

type Function

type Function struct {
	Role    string
	Handler Handler
	// Flush sends what telemetry holds; the platform freezes the process when
	// an invocation returns, and a batch waiting for its timer waits for the
	// next invocation.
	Flush func(context.Context)
	// Close releases what the role holds, at shutdown.
	Close func()
}

Function is a role, assembled at cold start and ready to be invoked.

func Open

func Open(ctx context.Context, getenv func(string) string) (*Function, error)

Open assembles the role the environment names. getenv is the process's.

type HTTP

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

HTTP serves API Gateway HTTP API events (payload format 2.0) with an http.Handler: the same mux the Kubernetes server serves on its listener.

func NewHTTP

func NewHTTP(handler http.Handler, settle func(), log *slog.Logger) *HTTP

NewHTTP adapts handler. settle may be nil.

func (*HTTP) Handle

func (h *HTTP) Handle(ctx context.Context, payload json.RawMessage) (any, error)

Handle implements Handler: payload is an API Gateway HTTP API event.

func (*HTTP) Serve

Serve runs one request through the handler and returns its response. A request that cannot be made into an http.Request is a 400, and a handler that panics a 500: neither is an error of the invocation, which the platform would retry.

func (*HTTP) WithExports added in v1.59.0

func (h *HTTP) WithExports(run func(context.Context) (ExportsResult, error)) *HTTP

WithExports makes the function answer {"kind":"exports"} events with run.

func (*HTTP) WithRefresh added in v1.61.1

func (h *HTTP) WithRefresh(run func(context.Context) (RefreshResult, error)) *HTTP

WithRefresh makes the function answer {"kind":"refresh"} events with run.

type Handler

type Handler interface {
	Handle(ctx context.Context, payload json.RawMessage) (any, error)
}

Handler is one function's entry point: the platform's event in, a result out.

type ParameterAPI

type ParameterAPI interface {
	GetParameters(ctx context.Context, in *ssm.GetParametersInput, opts ...func(*ssm.Options)) (*ssm.GetParametersOutput, error)
}

ParameterAPI is the part of the SSM client the reader uses.

func OpenSSM

func OpenSSM(ctx context.Context) (ParameterAPI, error)

OpenSSM connects with the function's role.

type Pass

type Pass interface {
	// Pass runs the target's pass under its lease; false, with no error, means
	// another runner holds it.
	Pass(ctx context.Context, target string, unsafeLocal bool) (ran bool, err error)
	// Close flushes what the pass recorded.
	Close() error
}

Pass is one assembled controller, for one invocation.

type RefreshResult added in v1.61.1

type RefreshResult struct {
	Kind       string `json:"kind"`
	Workspaces int    `json:"workspaces"`
	Ran        int    `json:"ran"`
	Contended  int    `json:"contended"`
	Failed     int    `json:"failed"`
}

RefreshResult is what a refresh invocation returns.

type Result

type Result struct {
	Kind    string `json:"kind"`
	Target  string `json:"target"`
	Outcome string `json:"outcome"`
}

Result is what a controller invocation returns.

type SecretFile

type SecretFile struct {
	Parameter string `json:"parameter"`
	Path      string `json:"path"`
}

SecretFile is one parameter written to one file.

Jump to

Keyboard shortcuts

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