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
- Variables
- func ParseEvent(payload json.RawMessage) (invoke.Event, error)
- func Request(ctx context.Context, event events.APIGatewayV2HTTPRequest) (*http.Request, error)
- func ResolveEnv(ctx context.Context, environ []string, set func(name, value string) error, ...) ([]string, error)
- func Response(status int, header http.Header, body []byte) events.APIGatewayV2HTTPResponse
- func WriteSecretFiles(ctx context.Context, spec string, ...) ([]string, error)
- type Controller
- type ExportsResult
- type Function
- type HTTP
- type Handler
- type ParameterAPI
- type Pass
- type Result
- type SecretFile
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 ( RoleHTTP = "http" RoleGitHub = "github" RoleSlack = "slack" )
The roles, the values of SLUIS_ROLE.
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.
const EnvSecretFiles = "SLUIS_SECRET_FILES"
EnvSecretFiles names the variable that lists the files to write: a JSON array of {"parameter":"/sluis/private/...","path":"/tmp/..."}.
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.
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/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 ¶
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 ¶
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.
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) WithExports ¶ added in v1.59.0
WithExports makes the function answer {"kind":"exports"} events with run.
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.
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 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 ¶
SecretFile is one parameter written to one file.