Documentation
¶
Overview ¶
Package config gives a handler its configuration including the values a celerity/config resource holds, and the identifiers of the infrastructure the application reaches.
A handler asks for a value and does not need to know where it came from. Under the Celerity runtime that is whatever store the deployment configured; under a serverless platform it is the provider's own parameter or secret store; in a test it is a map. What differs is the Backend, which is supplied at startup so that core imports no provider's SDK.
How the stores are wired up ¶
An application doesn't configure anything. A blueprint declares celerity/config resources, the CLI provisions the store and wires up the application at build time to access config.
How a handler reaches it ¶
By being given it. A Service exists before any event does and is the same object for every one of them, so it is a dependency rather than anything about a request, and it arrives the way every other dependency in this SDK arrives:
celerity.Get(app, "/orders", orders.List(store, cfg))
Resolving a resource ¶
The identifiers of deployed infrastructure are configuration like any other. A blueprint names a bucket ordersBucket; the bucket that exists is called something the deployment chose, and the two are joined by the links file the Celerity CLI writes into the bundle at build time. A provider module such as resources/aws reads the link and then reads the identifier out of the resources namespace, which is why it needs no knowledge of how a deployment names things.
Index ¶
- Constants
- func LoadScope(handlerID string) (map[string]bool, bool)
- func RegisterProvider(p Provider)
- func RegisteredProviders() []string
- func ValuesFromJSON(raw []byte, source string) (map[string]string, error)
- type AmbiguousNamespaceError
- type Backend
- type EmptyBackend
- type Link
- type Links
- type MapBackend
- type MissingKeyError
- type MissingLinkError
- type MissingNamespaceError
- type MissingStoreError
- type Namespace
- type NamespaceOption
- type Platform
- type Provider
- type SelfCaching
- type Service
- func (s *Service) All(ctx context.Context) (map[string]string, error)
- func (s *Service) Bind(ctx context.Context, out any) error
- func (s *Service) Get(ctx context.Context, key string) (string, error)
- func (s *Service) Lookup(ctx context.Context, key string) (string, bool, error)
- func (s *Service) Namespace(name string) (Namespace, error)
- func (s *Service) Register(name string, ns Namespace)
- func (s *Service) Registered() []string
- func (s *Service) Resource(ctx context.Context, kind, name string) (string, error)
- func (s *Service) ResourceKey(kind, name string) (string, error)
- func (s *Service) WithLinks(links Links) *Service
- type StoreKind
Constants ¶
const ( // StoreIDEnvVar names the store of an application with one. StoreIDEnvVar = "CELERITY_CONFIG_STORE_ID" // StoreKindEnvVar is the environment variable for how the store holds its values. StoreKindEnvVar = "CELERITY_CONFIG_STORE_KIND" // RefreshEnvVar is how long values are held before being read again, in // milliseconds. Zero holds them for the life of the process. RefreshEnvVar = "CELERITY_CONFIG_REFRESH_INTERVAL_MS" )
Environment variables the deploy engine sets, describing the stores a blueprint's celerity/config resources were deployed as.
The store's identifier cannot come from the blueprint. A celerity/config resource may omit its name, in which case one is generated when it is created. The deployment is what knows, and sets the appropriate environment variables.
const ( // LinksFilename maps every resource the application declares to the config // key holding its identifier. LinksFilename = "__celerity_resource_links__.json" // ScopeFilename says which of those resources each handler actually uses, // so a handler sees only what it reaches. Produced by the extraction pass. ScopeFilename = "__celerity_handler_scope__.json" )
Files the Celerity CLI writes into the handler bundle at build time.
Carried as files rather than as environment variables because one bundle serves every function in the application, and the topology of a large one does not fit in an environment a platform will accept.
const DefaultNamespace = "default"
DefaultNamespace is what a store described without a name is registered as.
An application does not normally have only one store. The deploy engine describes the resources store alongside whatever the blueprint declares, so the ordinary shape is two: ResourcesNamespace, holding the identifiers of deployed infrastructure, and one the application declared and reads. That second one is what Service.Get reaches without being named, the resources store being the deployment's rather than the application's.
const DefaultRefresh = 30 * time.Second
DefaultRefresh is how long values are held in memory before re-fetching.
A store is read once and then held, so without this a rotated secret would not be picked up until the execution environment was recycled, which for a warm serverless function can be hours.
const LinksEnvVar = "CELERITY_RESOURCE_LINKS"
LinksEnvVar carries the topology itself, which is how a local development session provides it, that path doesn't stage any files, having nothing to stage them into, so it passes the JSON directly.
const LinksPathEnvVar = "CELERITY_RESOURCE_LINKS_PATH"
LinksPathEnvVar points the SDK at the links file somewhere other than beside the handler.
const PlatformEnvVar = "CELERITY_PLATFORM"
PlatformEnvVar names the platform an application is deployed to.
The Celerity CLI sets it, "local" for a development session, and it is what selects the provider a store is read through.
const ResourcesNamespace = "resources"
ResourcesNamespace is the namespace holding the identifiers of deployed infrastructure.
Populated by the deployment rather than by the application: the blueprint names a bucket, and what the bucket is actually called is decided when it is created. A provider module reads it through Service.Resource rather than reading this namespace itself.
Variables ¶
This section is empty.
Functions ¶
func LoadScope ¶
LoadScope returns the resources one handler uses, and whether that is known.
The links file describes the whole application, because one bundle serves every function, and is not a statement about any one handler. The scope file narrows it. An absent scope is not an error, a bundle built before the extraction pass produced one simply has no narrowing to apply, and the handler sees the whole application as it did before.
func RegisterProvider ¶
func RegisterProvider(p Provider)
RegisterProvider adds a provider to the set considered at startup.
Called from a provider package's init, and panics on a duplicate platform, which can only mean two providers claiming one.
func RegisteredProviders ¶
func RegisteredProviders() []string
RegisteredProviders returns the platforms configuration can be read from in this binary, sorted.
func ValuesFromJSON ¶
ValuesFromJSON reads a store held as one JSON object of values.
The shape several stores use: what the Celerity CLI writes into a local session, and what a secret holding a whole store contains. Read here rather than in each provider so that a value becomes the same text wherever it was held, which is what lets a handler read configuration without knowing.
A store is seeded from a blueprint, and a blueprint's YAML gives numbers and booleans as themselves rather than as strings.
Types ¶
type AmbiguousNamespaceError ¶
type AmbiguousNamespaceError struct {
Registered []string
}
AmbiguousNamespaceError reports a value asked for without a namespace where more than one is registered.
func (*AmbiguousNamespaceError) Error ¶
func (e *AmbiguousNamespaceError) Error() string
type Backend ¶
type Backend interface {
// Fetch returns every value in the store.
//
// A store that does not exist is an error. A store that exists and is empty
// is an empty map, which is not the same thing, as the first is a deployment
// that did not happen, and reporting it as the second would leave a handler
// reading absent values it was promised.
Fetch(ctx context.Context, storeID string) (map[string]string, error)
}
Backend fetches the values one store holds.
This is implemented per platform, it could be a parameter store, a secret manager, a file, a map. One call returns everything in the store rather than one key, because a lot of backends charge per request rather than per value, and a handler reading three keys should not pay for three round trips.
type EmptyBackend ¶
type EmptyBackend struct{}
EmptyBackend holds nothing and reports every store as empty rather than absent.
It is what an application with no config resource runs against, so that asking for a value it never declared reads as the value being absent rather than as the deployment being broken.
type Link ¶
type Link struct {
// Type is the resource's kind, in the vocabulary the resources package
// uses: bucket, queue, topic, datastore, cache, sqlDatabase.
Type string `json:"type"`
// ConfigKey is the key in the resources namespace holding the identifier
// this resource was deployed under.
ConfigKey string `json:"configKey"`
}
Link is one resource in the topology.
type Links ¶
Links is the resource topology, by the name the blueprint gave each resource.
func LoadLinks ¶
LoadLinks reads the topology the Celerity CLI produced.
The file is the contract for a built artifact. The CLI writes it into the bundle, always, holding an empty object where the blueprint declares nothing, so that an SDK reading it has one case to handle.
A development session does not stage a bundle to put a file in, and passes the topology in LinksEnvVar instead, which is read where there is no file.
Neither is an error rather than an empty topology. Treating an absent topology as an application with no resources would turn a bundle that was not built by the CLI into a handler that cannot find a bucket it does declare.
type MapBackend ¶
MapBackend serves values held in memory, which is what a test wants and what a local run can use before anything is deployed.
type MissingKeyError ¶
MissingKeyError reports a key the store does not hold.
func (*MissingKeyError) Error ¶
func (e *MissingKeyError) Error() string
type MissingLinkError ¶
MissingLinkError reports a resource the topology does not hold.
func (*MissingLinkError) Error ¶
func (e *MissingLinkError) Error() string
type MissingNamespaceError ¶
MissingNamespaceError reports a namespace nothing registered.
Names what is registered, because the cause is nearly always a name that disagrees with the blueprint's and the answer is in the list.
func (*MissingNamespaceError) Error ¶
func (e *MissingNamespaceError) Error() string
type MissingStoreError ¶
type MissingStoreError struct {
StoreID string
}
MissingStoreError reports a store a backend does not hold.
Distinct from a store that is empty: this is a deployment that did not happen, and a handler reading a value it was promised should be told that rather than told the value is absent.
func (*MissingStoreError) Error ¶
func (e *MissingStoreError) Error() string
type Namespace ¶
type Namespace interface {
// Lookup returns a value and whether it is there.
Lookup(ctx context.Context, key string) (string, bool, error)
// Get returns a value, and a [MissingKeyError] where there is none.
//
// The two exist for the reason os.Getenv and os.LookupEnv do: a value the
// application cannot run without should fail loudly at the point it is
// read, and an optional one should not need an error branch.
Get(ctx context.Context, key string) (string, error)
// All returns every value the store holds.
All(ctx context.Context) (map[string]string, error)
// Bind fills a struct from the store, by `config` tag.
Bind(ctx context.Context, out any) error
}
Namespace contains the values of one store.
An application with one celerity/config resource has one of these and reaches it through the Service directly. One with several names the one it means.
func NewNamespace ¶
func NewNamespace(backend Backend, storeID string, opts ...NamespaceOption) Namespace
NewNamespace returns a namespace reading one store through a backend.
The store is not read here. It is read on the first value asked for, so that an application declaring config it never reads pays nothing, and a cold start is not spent on a store this invocation does not touch.
type NamespaceOption ¶
type NamespaceOption func(*namespace)
NamespaceOption configures a namespace.
func WithPrefix ¶
func WithPrefix(prefix string) NamespaceOption
WithPrefix serves only the keys under a prefix, with the prefix removed.
A store shared between several applications, which a parameter store commonly is, keeps each one's values under its own path. The handler asks for "timeout" rather than "orders/timeout".
func WithRefresh ¶
func WithRefresh(interval time.Duration) NamespaceOption
WithRefresh re-reads the store once values are older than interval.
Without it a store is read once and held for the life of the process, which for a serverless function means for the life of the execution environment: a rotated secret would not be picked up until the environment was recycled.
The read that finds them stale is the one that waits for the re-read, so a caller is never handed values that have already been judged too old. A failed re-read is logged and the previous values stand, since values that are stale are worth more than no values at all, and it is not retried until the interval is up again.
type Platform ¶
type Platform string
Platform is the platform an application is running on.
const ( PlatformAWS Platform = "aws" PlatformAzure Platform = "azure" PlatformGCP Platform = "gcp" PlatformLocal Platform = "local" // PlatformOther is anything else, including nothing being set, which is // what a containerised deployment on hardware the SDK has no name for is. PlatformOther Platform = "other" )
func CurrentPlatform ¶
func CurrentPlatform() Platform
CurrentPlatform returns the platform this process is running on.
Anything the SDK does not have a name for is PlatformOther rather than an error: it is used to choose a provider, and a platform nobody named has no provider to choose.
type Provider ¶
type Provider interface {
// Name identifies the provider in errors, such as "aws".
Name() string
// Platform is the platform this provider serves, matched against what the
// deployment says the application is running on.
Platform() Platform
// Backend returns the backend reading a store of this kind.
//
// A kind the platform does not hold configuration in is an error rather
// than a silently empty store, since it means the deployment asked for
// something this provider cannot do.
Backend(kind StoreKind) (Backend, error)
}
Provider builds the config backends for one platform.
Implemented by a provider module and registered from its init, so that selecting one is an import rather than something an application wires up, exactly as a serverless adapter and a resource provider are. Core doesn't import any provider-specific SDKs.
type SelfCaching ¶
type SelfCaching interface {
Backend
// Caching reports that values are already held between reads.
Caching() bool
}
SelfCaching is implemented by a backend that already holds values between reads.
Refreshing on top of one costs a request to re-read a cache rather than the store, so a backend that does its own caching says so and this package leaves it alone. Whether that applies is the provider's to know: on AWS it depends on whether a serverless function runs behind the parameters and secrets extension, which is a thing core should not be able to tell.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the configuration an application reads.
Ordinarily it holds two stores: ResourcesNamespace, which the deploy engine describes and which holds the identifiers of deployed infrastructure, and one the blueprint declared for the application. The application's own is read straight off the service, since the resources store is not one it declared; an application with several of its own names the one it means.
func FromEnvironment ¶
FromEnvironment builds the service the deployment described.
This is the ordinary route, and an application doesn't need to configure anything, [celerity.New] calls it, the stores come from what the deploy engine set, and the backend reading them comes from whichever provider module is linked.
An application whose deployment described no store gets a service with no namespaces rather than an error. Asking it for a value then says that the application declares no celerity/config resource, which is true and more useful than a failure at startup for configuration nothing may read.
func New ¶
func New() *Service
New returns a service holding no namespaces.
An application does not normally build one: celerity.New does, from what the deployment gave it. A test builds one with Service.Register and a MapBackend.
func (*Service) Registered ¶
Registered returns the namespaces held, sorted.
func (*Service) Resource ¶
Resource returns the identifier of a deployed resource, by the name the blueprint gave it.
Two steps, and both are needed. The links file says which config key holds this resource's identifier, and the resources namespace holds the identifier itself: the first is written when the application is built, the second when the infrastructure is deployed, and only the pair of them can say that ordersBucket is a bucket in S3 called orders-bucket-prod-eu-west-2.
This is what a provider module calls, and why resources/aws doesn't need to know about how a deployment names things.
func (*Service) ResourceKey ¶
ResourceKey returns the key in the resources namespace holding what the deployment recorded about a resource.
Service.Resource is the entirety of what most resources need, since one value is all there is to know: a bucket is a name, a topic is an ARN. A cache or a database is an endpoint, a port, a user and how to authenticate, and the deploy engine writes each of those under this key with a suffix of its own. A provider module reads them itself, because which of them exist and what they mean is a property of the resource rather than of configuration.
type StoreKind ¶
type StoreKind string
StoreKind is how a platform holds a configuration store, which decides which of that platform's services a value is read from.
Opaque here. The names are a provider's own, "secrets-manager" and "parameter-store" being two of AWS's, this is a value the deployment set through to the provider, which is the only thing that can interpret it. Empty means the provider's own default.