Documentation
¶
Overview ¶
Package lambdaapp runs sluis as ONE AWS Lambda function: one binary, one zip, one function (docs/reference/lambda.md). The function takes
- API Gateway HTTP API events: the issuer and the console, the same mux the Kubernetes process serves on its listener;
- {"kind":"tick"|"run","target":"<id>"}: one pass of one target under its lease, by the controller the policy says the target belongs to (the GitHub controller's organisations and `github:links`, the Slack controller's workspaces). `tick` is what an EventBridge Scheduler schedule sends, `run` what a console request sends (internal/port/invoke);
- {"kind":"cloudflare"}: the rotation of the Cloudflare credentials that are due (the `cloudflare` section), which has no loop to run in on Lambda;
- {"kind":"refresh"}: the directory refresh, which has no loop to run in on Lambda.
This package assembles the function 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. The controllers named in the service document (`controllers`) do not run their loops here: each is assembled per invocation that needs it. It names no cluster, NATS or Valkey client, and cmd/sluis-lambda's import guard keeps it so.
Index ¶
- Constants
- func ParseEvent(payload json.RawMessage) (invoke.Event, error)
- func Request(ctx context.Context, event events.APIGatewayV2HTTPRequest) (*http.Request, error)
- func Response(status int, header http.Header, body []byte) events.APIGatewayV2HTTPResponse
- type CloudflareResult
- type Controller
- type Function
- type HTTP
- func (h *HTTP) Handle(ctx context.Context, payload json.RawMessage) (any, error)
- func (h *HTTP) Serve(ctx context.Context, event events.APIGatewayV2HTTPRequest) (resp events.APIGatewayV2HTTPResponse)
- func (h *HTTP) WithCloudflare(run func(context.Context) (failed int, summary string, err error)) *HTTP
- func (h *HTTP) WithControllers(kindOf func(target string) string, controllers map[string]*Controller) *HTTP
- func (h *HTTP) WithRefresh(run func(context.Context) (RefreshResult, error)) *HTTP
- type Handler
- type Pass
- type RefreshResult
- type Result
Constants ¶
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.
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.
const KindCloudflare = "cloudflare"
KindCloudflare is the event an EventBridge Scheduler schedule sends the `http` function to rotate the Cloudflare credentials that are due: {"kind":"cloudflare"}. A minute's schedule costs one read of the secrets store per preset until a rotation is due.
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.
Variables ¶
This section is empty.
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.
Types ¶
type CloudflareResult ¶ added in v1.74.0
type CloudflareResult struct {
Kind string `json:"kind"`
Presets string `json:"presets"`
Failed int `json:"failed"`
}
CloudflareResult is what a cloudflare invocation returns.
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 Function ¶
type Function struct {
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 function holds, at shutdown.
Close func()
}
Function is the function, assembled at cold start and ready to be invoked.
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 (*HTTP) Serve ¶
func (h *HTTP) Serve(ctx context.Context, event events.APIGatewayV2HTTPRequest) (resp events.APIGatewayV2HTTPResponse)
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) WithCloudflare ¶ added in v1.74.0
func (h *HTTP) WithCloudflare(run func(context.Context) (failed int, summary string, err error)) *HTTP
WithCloudflare makes the function answer {"kind":"cloudflare"} events with run.
func (*HTTP) WithControllers ¶ added in v1.63.0
func (h *HTTP) WithControllers(kindOf func(target string) string, controllers map[string]*Controller) *HTTP
WithControllers makes the function answer {"kind":"tick"|"run","target":...} events by running one pass of the target under the controller of its kind (the controllers' names are the keys: "github", "slack"). kindOf says which kind a target is, from the policy that declares it; an empty answer is a target nobody declares.
func (*HTTP) WithRefresh ¶ added in v1.61.1
WithRefresh makes the function answer {"kind":"refresh"} events with run.
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.