Module: Foobara::AWS

Defined in:
lib/foobara/aws.rb,
lib/foobara/aws/plan.rb,
lib/foobara/aws/check.rb,
lib/foobara/aws/error.rb,
lib/foobara/aws/lambda.rb,
lib/foobara/aws/handler.rb,
lib/foobara/aws/version.rb,
lib/foobara/aws/packager.rb,
lib/foobara/aws/authorizer.rb,
lib/foobara/aws/cdk/service.rb

Overview

Run Foobara commands on AWS Lambda, with the topology read out of the manifest rather than restated in infrastructure code.

Three concerns, deliberately separable because they run in different places:

Foobara::AWS.plan(manifest)      WHAT to deploy. Plain data. Needs neither
                               aws-cdk-lib nor Foobara.
Foobara::AWS::CDK::Service       the AWS resources. Synth time; needs
                               aws-cdk-lib, and no application.
Foobara::AWS::Handler            serving a request. Runtime, inside the
Foobara::AWS::Authorizer         Lambda; needs neither of the above.

A Lambda bundle requires only "foobara/aws/handler" (or ".../authorizer"), which loads no CDK and no planning code — cold start is on the critical path of every request, and a deployment tool has no business being there.

Defined Under Namespace

Modules: CDK, Lambda Classes: Authorizer, Check, Error, Handler, Packager, Plan, Unit, Unverified

Constant Summary collapse

DEFAULT_EXCLUDE =

Foobara's own commands — its auth domain and anything else it registers — are infrastructure, not the application, and deploying them is never what anyone means. Matched on the command's full name, which is where that namespace actually shows up.

NOT matched on the organization: an app that declares none is filed under "global_organization", which is most apps, and excluding it would exclude everything.

["Foobara::"].freeze
VERSION =
"0.3.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.caller_builderObject

Turns the authorizer's verified claims into whatever the application wants as its caller. Set once, in the app's boot file, so the generated handler needs no application-specific code in it.

Foobara::AWS.caller_builder = ->(claims) { Viewer.new(claims["sub"], claims["name"]) }


49
50
51
# File 'lib/foobara/aws/handler.rb', line 49

def caller_builder
  @caller_builder
end

Class Method Details

.current_callerObject

WHO IS CALLING, for the duration of one invocation.

Foobara authenticates only commands declaring requires_authentication, which answers "may this caller in?" but not "who is this?". A command that is public AND viewer-aware — one that marks your own posts editable — needs the second without the first, and Foobara has nowhere to put it.

So the handler puts it here, and an application reads it from its connector's authenticator and from any command that needs the viewer as data. A thread-local is not elegant; it is the smallest thing that works until Foobara grows a notion of optional identity.



37
# File 'lib/foobara/aws/handler.rb', line 37

def current_caller = Thread.current[:foobara_aws_caller]

.current_caller=(value) ⇒ Object

Not an endless def: Ruby does not allow a setter to be one.



40
41
42
# File 'lib/foobara/aws/handler.rb', line 40

def current_caller=(value)
  Thread.current[:foobara_aws_caller] = value
end

.plan(manifest, mount: "/run", granularity: :domain, exclude: DEFAULT_EXCLUDE) ⇒ Object

Build a Plan from a Foobara manifest.

manifest    the parsed manifest, as served at /manifest by a connector
mount       the connector's path prefix ("/run" for the Rack connector)
granularity :domain (default), :organization or :command

A connector's manifest, not Foobara.manifest: requires_authentication only exists once commands are connected, and it is the field the public list comes from.



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/foobara/aws/plan.rb', line 75

def plan(manifest, mount: "/run", granularity: :domain, exclude: DEFAULT_EXCLUDE)
  commands = app_commands(manifest, exclude)
  units =
    case granularity
    when :domain then group_units(commands, "domain", mount)
    when :organization then group_units(commands, "organization", mount)
    when :command then command_units(commands, mount)
    else raise ArgumentError, "unknown granularity #{granularity.inspect}"
    end

  Plan.new(mount: mount, granularity: granularity, units: units.sort_by(&:name))
end

.plan_from_connector(connector) ⇒ Object

Build a Plan by inspecting a live connector instead of a manifest.

The same information by a shorter route: no HTTP, no running server, and no snapshot on disk to go stale. It needs the application loaded, so it belongs in a build step rather than in a CDK app — which is the whole reason a Plan can also travel as JSON.

A CONNECTOR, not a set of command classes. requires_authentication is decided at connect time and lives on the transformed command, so the command classes alone cannot say which commands are public — which is the one thing a deployment most needs to know.

It adapts each command into the shape plan already reads, so there is one implementation and the two routes cannot drift.



102
103
104
105
106
107
108
# File 'lib/foobara/aws/plan.rb', line 102

def plan_from_connector(connector, **)
  commands = connector.command_registry.all_transformed_command_classes.to_h do |transformed|
    [transformed.command_class.full_command_name, describe_command(transformed)]
  end

  plan({ "command" => commands }, **)
end