service

package
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// PriorityHeaderExact is for services matched by an exact X-Amz-Target prefix.
	PriorityHeaderExact = 100

	// PriorityHeaderPartial is for services matched by a partial header prefix (Lambda, KMS, SecretsManager).
	PriorityHeaderPartial = 95

	// PriorityFormEncoded is for STS form-encoded POST requests.
	PriorityFormEncoded = 90

	// PriorityPathVersioned is for services matched by a versioned path prefix (S3Control).
	PriorityPathVersioned = 85

	// PriorityFormRDS is for RDS form-encoded query protocol (version 2014-10-31).
	PriorityFormRDS = 84

	// PriorityFormDocDB is for DocDB form-encoded query protocol. Higher than RDS (84) to intercept DocDB requests first.
	PriorityFormDocDB = 85

	// PriorityFormNeptune is for Neptune form-encoded query protocol.
	// Higher than RDS (84) to intercept Neptune requests first.
	PriorityFormNeptune = 85

	// PriorityFormRedshift is for Redshift form-encoded query protocol (version 2012-12-01).
	PriorityFormRedshift = 83

	// PriorityPathSubdomain is for OpenSearch and ElastiCache path-prefix matchers.
	PriorityPathSubdomain = 82

	// PriorityFormStandard is for EC2, IAM, and SES standard form-encoded query protocol.
	PriorityFormStandard = 80

	// PriorityTargetPrefixed is for Kinesis with its versioned X-Amz-Target prefix.
	PriorityTargetPrefixed = 75

	// PriorityPathUI is for the Dashboard UI path-based routes.
	PriorityPathUI = 50

	// PriorityCatchAll is for S3, which uses a low-priority Host-header catch-all.
	PriorityCatchAll = 0
)

Routing priority constants control the order in which service matchers are evaluated. Higher values are evaluated first. Services are grouped into tiers:

  • 100 (HeaderExact): Services matched by an exact X-Amz-Target prefix. These are the most specific and should always win.
  • 95 (HeaderPartial): Services matched by a looser header prefix check (e.g. Lambda by path + method, KMS/SecretsManager by target prefix).
  • 90 (FormEncoded): STS – form-encoded POST to the root path.
  • 85 (PathVersioned): Services matched by a versioned path prefix (S3Control).
  • 85 (FormDocDB): DocDB – form-encoded, versioned query protocol (intercepted before RDS).
  • 85 (FormNeptune): Neptune – form-encoded, versioned query protocol (intercepted before RDS).
  • 84 (FormRDS): RDS – form-encoded, versioned query protocol.
  • 83 (FormRedshift): Redshift – form-encoded, version 2012-12-01.
  • 82 (PathSubdomain): OpenSearch / ElastiCache – path-prefix matchers that could overlap with form-encoded services.
  • 80 (FormStandard): EC2, IAM, SES – standard form-encoded query protocol.
  • 75 (TargetPrefixed): Kinesis – X-Amz-Target with a versioned prefix.
  • 50 (PathUI): Dashboard – path-based UI routes.
  • 0 (CatchAll): S3 – low-priority catch-all Host-header matcher.
View Source
const ContentTypeCBOR = "application/x-amz-cbor-1.1"

ContentTypeCBOR is the AWS CBOR wire protocol content type used by DynamoDB, Kinesis, and Timestream.

Variables

View Source
var ErrServiceAlreadyRegistered = errors.New("service already registered")

ErrServiceAlreadyRegistered is returned when a service with the same name is already registered.

Functions

func AccountRegion

func AccountRegion(ctx *AppContext) (string, string)

AccountRegion returns the configured AWS account ID and region from the AppContext's Config when it implements config.Provider, or ("", "") otherwise.

It collapses the boilerplate every service provider repeats:

if cp, ok := ctx.Config.(config.Provider); ok {
	cfg := cp.GetGlobalConfig()
	accountID = cfg.GetAccountID()
	region = cfg.GetRegion()
}

The empty fallback matches that hand-rolled behaviour exactly, so backends keep applying their own defaults when no config is wired.

func AccountRegionOrDefault

func AccountRegionOrDefault(ctx *AppContext) (string, string)

AccountRegionOrDefault is like AccountRegion but falls back to config.DefaultAccountID and config.DefaultRegion when no config.Provider is present. It collapses the most common provider idiom:

accountID := config.DefaultAccountID
region := config.DefaultRegion
if cp, ok := ctx.Config.(config.Provider); ok {
	cfg := cp.GetGlobalConfig()
	accountID = cfg.GetAccountID()
	region = cfg.GetRegion()
}

func CBORToJSON

func CBORToJSON(data []byte) ([]byte, error)

CBORToJSON decodes AWS CBOR-1.1 wire bytes to equivalent JSON bytes.

CBOR byte strings (major type 2) are base64-encoded in the JSON output, matching the JSON wire format that aws-sdk-go-v2 uses for binary attribute values.

func ExtractRPCv2CBOROperation added in v1.2.0

func ExtractRPCv2CBOROperation(path, servicePath string) string

ExtractRPCv2CBOROperation returns the operation name from an rpc-v2-cbor request path, given that service's path prefix.

func HandleJSON

func HandleJSON[In, Out any](
	ctx context.Context,
	body []byte,
	fn func(context.Context, *In) (*Out, error),
) (any, error)

HandleJSON is a generic dispatcher that decodes the JSON body into a typed input, calls fn, and returns the typed output as any. If body is non-empty and cannot be decoded, HandleJSON returns the decode error directly.

func HandleTarget

func HandleTarget(
	c *echo.Context,
	log *slog.Logger,
	serviceName, contentType string,
	supportedOps []string,
	dispatch DispatchFunc,
	handleErr ErrorHandlerFunc,
) error

HandleTarget implements the X-Amz-Target JSON protocol dispatch pattern shared by many AWS JSON-protocol services (SSM, EventBridge, StepFunctions, CloudWatchLogs, etc.).

It performs:

  1. GET / → returns supportedOps as JSON
  2. Non-POST → 405 Method Not Allowed
  3. Missing or malformed X-Amz-Target → 400 Bad Request
  4. Body read failure → 500 Internal Server Error
  5. dispatch call → handleErr on error, else write response with contentType header

func IsCBORRequest

func IsCBORRequest(r *http.Request) bool

IsCBORRequest returns true when the request carries an AWS CBOR-encoded body.

func IsRPCv2CBORRequest added in v1.2.0

func IsRPCv2CBORRequest(r *http.Request, servicePath string) bool

IsRPCv2CBORRequest returns true when r is a POST to a path under servicePath, indicating the Smithy RPCv2 CBOR wire protocol (as opposed to a service's legacy JSON, or query/XML, protocol served alongside it).

This is the protocol AppStream (v1.64.0+) and CloudWatch serve today -- the two rpc-v2-cbor services in this codebase -- identified by dispatching on URL path prefix rather than an X-Amz-Target header or SOAPAction-style convention.

func JSONToCBOR

func JSONToCBOR(data []byte, binaryKeys map[string]bool) ([]byte, error)

JSONToCBOR encodes JSON bytes to AWS CBOR-1.1 wire bytes.

binaryKeys is an optional set of JSON object keys whose string values should be treated as standard base64-encoded binary data and encoded as CBOR byte strings (major type 2). Keys present in the set match any occurrence in the document regardless of nesting depth. A nil map means no binary fields.

For array values the parent key is inherited, so {"BS": ["abc","def"]} produces a CBOR list of byte strings when "BS" is in binaryKeys.

func MatchesUserAgentMarker added in v1.2.0

func MatchesUserAgentMarker(h http.Header, markers ...string) bool

MatchesUserAgentMarker reports whether the request identified by h carries any of markers in its SDK user-agent identification, checked case-insensitively against both the User-Agent and X-Amz-User-Agent headers.

Native SDKs (Go, Python, Java, ...) send their SDK identification in the standard User-Agent header. Browser JavaScript cannot: the Fetch spec forbids scripts from setting User-Agent (the browser controls it), so the AWS SDK for JavaScript puts its own SDK identification exclusively into X-Amz-User-Agent when running in a browser. A RouteMatcher that only inspects User-Agent will therefore never match a browser-originated request; checking both headers is what makes matching work for a browser dashboard as well as a native SDK caller.

Matching is case-insensitive because the same logical marker differs in case depending on which SDK sent it: aws-sdk-go-v2 derives its marker from the (lowercase) Go module path -- e.g. "api/docdb" -- while the AWS SDK for JavaScript uses the API model's PascalCase serviceId verbatim -- e.g. "api/DocDB". Pass more than one marker to also cover an SDK that spells the same service differently: e.g. MediaStore Data's serviceId contains a space, which aws-sdk-go-v2 (module-path-derived: "mediastoredata") and the AWS SDK for JavaScript (space escaped to a hyphen: "MediaStore-Data") encode differently.

func WriteRPCv2CBORError added in v1.2.0

func WriteRPCv2CBORError(c *echo.Context, status int, code, message string) error

WriteRPCv2CBORError writes an rpc-v2-cbor error response.

Unlike a generic "message"-only body, the rpc-v2-cbor SDKs' generated error deserializer (getProtocolErrorInfo in each service's deserializers.go) resolves the exception name from an "__type" key inside the decoded CBOR body itself -- never from a header -- so that key must be present for errors.As/errors.Is against typed exceptions to work on the client (gopherstack-7fyf: CloudWatch previously set this only via the X-Amzn-Errortype header, which this protocol's client never reads). X-Amzn-Errortype is set too, matching the convention this codebase uses elsewhere, but the SDK does not consume it for this protocol.

func WriteRPCv2CBORResponse added in v1.2.0

func WriteRPCv2CBORResponse(c *echo.Context, v cbor.Value) error

WriteRPCv2CBORResponse writes a CBOR-encoded rpc-v2-cbor response, setting the Content-Type and Smithy-Protocol headers the protocol requires.

Types

type AppContext

type AppContext struct {
	Config         any
	JanitorCtx     context.Context
	Logger         *slog.Logger
	PortAlloc      *portalloc.Allocator
	JanitorTimeout time.Duration
}

AppContext contains shared resources needed by services during initialization.

type BackgroundWorker

type BackgroundWorker interface {
	StartWorker(ctx context.Context) error
}

BackgroundWorker is an optional interface that services can implement to start background tasks (e.g. async deletion janitors).

type ChaosProvider

type ChaosProvider interface {
	// ChaosServiceName returns the lowercase AWS-style service name used in
	// fault rules (e.g. "s3", "dynamodb", "sqs").
	ChaosServiceName() string

	// ChaosOperations returns all operations that can be fault-injected.
	// Implementations typically delegate to GetSupportedOperations().
	ChaosOperations() []string

	// ChaosRegions returns all regions this service instance handles.
	// Typically returns the configured default region plus any regions
	// that have active resources.
	ChaosRegions() []string
}

ChaosProvider is an optional interface services implement to declare their chaos-injectable surface area. Services that implement this interface are automatically discovered by the Chaos API via the registry.

type CloudTrailEventInput

type CloudTrailEventInput struct {
	// EventName is the AWS API operation name, e.g. "CreateBucket", "RunInstances".
	EventName string
	// EventSource is the AWS service's CloudTrail event source, e.g. "s3.amazonaws.com".
	EventSource string
	// AwsRegion is the region the request targeted.
	AwsRegion string
	// Username identifies the caller. Empty when no signed identity was present.
	Username string
	// AccessKeyID is the SigV4 access key used to sign the request, if any.
	AccessKeyID string
	// ResourceName is the primary resource the operation acted on, if known.
	ResourceName string
}

CloudTrailEventInput carries the fields the central service registry extracts from a single mutating API call so the CloudTrail backend can record a real management event. It is defined here (rather than reusing the cloudtrail package's own Event type) because services/cloudtrail already imports pkgs/service for its Provider/Registerable wiring, so the reverse import would create an import cycle.

type CloudTrailRecorder

type CloudTrailRecorder interface {
	// RecordManagementEvent records a single mutating API call as a CloudTrail
	// management event so it is later returned by LookupEvents.
	RecordManagementEvent(ev CloudTrailEventInput)
}

CloudTrailRecorder is implemented by the CloudTrail service backend to accept management events captured centrally by the service registry. The registry auto-discovers the live backend via SetCloudTrailRecorder; no service package other than cloudtrail need know about this interface.

type DashboardProvider

type DashboardProvider interface {
	// DashboardName returns the user-facing name for this service's dashboard tab.
	// Example: "DynamoDB", "S3"
	DashboardName() string

	// DashboardRoutePrefix returns the URL path prefix for this service's dashboard routes.
	// Example: "dynamodb", "s3"
	DashboardRoutePrefix() string

	// RegisterDashboardRoutes registers all dashboard routes for this service
	// under the given Echo group. The group is mounted at /dashboard/{prefix}.
	// The httpClient and endpoint are provided for services that need to make
	// SDK calls back to the service (e.g., to list tables or buckets).
	RegisterDashboardRoutes(
		group *echo.Group,
		httpClient any,
		endpoint string,
	)
}

DashboardProvider is an optional interface that services can implement to provide a dashboard UI. The dashboard automatically discovers and integrates services that implement this interface.

type DispatchFunc

type DispatchFunc func(ctx context.Context, action string, body []byte) ([]byte, error)

DispatchFunc is the signature for service-specific action dispatch functions. It receives the request context, the action name extracted from X-Amz-Target, and the raw request body; it returns pre-marshaled JSON or an error.

type Entry

type Entry struct {
	Registerable   Registerable
	Matcher        Matcher
	WrappedHandler echo.HandlerFunc
	Priority       int
}

Entry represents a registered service with its pre-wrapped handler and priority.

type ErrorHandlerFunc

type ErrorHandlerFunc func(ctx context.Context, c *echo.Context, action string, err error) error

ErrorHandlerFunc is the signature for service-specific error handlers. It translates a dispatch error into an HTTP response.

type FISActionDefinition

type FISActionDefinition struct {
	ActionID    string // e.g., "aws:ec2:stop-instances"
	Description string
	TargetType  string // e.g., "aws:ec2:instance"; empty if action has no targets
	TargetKey   string // key name used in the Targets map (e.g., "Instances", "Roles"); defaults to "Targets"
	Parameters  []FISParamDef
}

FISActionDefinition describes a FIS action supported by a service.

type FISActionExecution

type FISActionExecution struct {
	ActionID   string
	Parameters map[string]string
	Targets    []string      // resolved resource ARNs
	Duration   time.Duration // 0 means run indefinitely until stopped
}

FISActionExecution carries the runtime context for executing a single FIS action.

type FISActionProvider

type FISActionProvider interface {
	// FISActions returns the FIS action definitions this service supports.
	FISActions() []FISActionDefinition

	// ExecuteFISAction executes a FIS action against resolved targets.
	// It is called by the FIS backend when an experiment's action begins.
	// The implementation must be non-blocking or respect ctx cancellation.
	ExecuteFISAction(ctx context.Context, action FISActionExecution) error
}

FISActionProvider is an optional interface services implement to declare FIS actions they support and to execute them. Services that implement this interface are automatically discovered by the FIS backend via the service registry, enabling zero-config action registration.

type FISParamDef

type FISParamDef struct {
	Name        string
	Description string
	Default     string
	Required    bool
}

FISParamDef describes a single parameter accepted by a FIS action.

type JSONErrorResponse

type JSONErrorResponse struct {
	Type    string `json:"__type"`
	Message string `json:"message"`
}

JSONErrorResponse is the standard JSON error envelope for AWS JSON-protocol services. Used in X-Amz-Target dispatch to signal errors back to the SDK.

type JSONOpFunc

type JSONOpFunc func(ctx context.Context, body []byte) (any, error)

JSONOpFunc is the function type for a dispatched JSON-protocol operation. Implementations are produced by WrapOp and collected in a dispatchTable map.

func WrapOp

func WrapOp[In, Out any](fn func(context.Context, *In) (*Out, error)) JSONOpFunc

WrapOp adapts a typed HandleJSON handler into a JSONOpFunc for use in dispatch tables. It is the canonical way to register a typed operation handler:

"CreateFoo": service.WrapOp(h.handleCreateFoo),

type Matcher

type Matcher func(c *echo.Context) bool

Matcher determines whether an incoming request should be routed to a particular service handler. Matchers are evaluated by priority (higher priority = evaluated first).

type Middleware

type Middleware func(echo.HandlerFunc) echo.HandlerFunc

Middleware defines a function that wraps an Echo handler.

type Provider

type Provider interface {
	// Name returns the name of the service provider.
	Name() string

	// Init initializes the service using the provided application context.
	// It returns the Registerable service, or an error if initialization fails.
	Init(ctx *AppContext) (Registerable, error)
}

Provider encapsulates the logic to initialize a service.

type Purgeable

type Purgeable interface {
	Purge(ctx context.Context, cutoff time.Time)
}

Purgeable is an optional interface for services that support automatically removing resources older than a specific cutoff time (used by AUTO_PURGE_TTL).

type RESTRouter added in v1.2.0

type RESTRouter struct {
	// ServiceName appears in the operation-error log line.
	ServiceName string
	// Parse extracts the operation name and resource identifier from the
	// request method and URL path.
	Parse func(method, path string) (op, resource string)
	// Dispatch runs the parsed operation against the service backend.
	Dispatch func(ctx context.Context, op, path, query string, body []byte) (any, int, error)
	// HandleError translates a Dispatch error into an Echo response.
	HandleError func(c *echo.Context, err error) error
	// NotFoundBody builds the JSON body for an unrecognized-operation 404.
	NotFoundBody func() any
	// BadRequestBody builds the JSON body for a request-body read failure.
	BadRequestBody func() any
	// InternalErrorBody builds the JSON body for a result-marshal failure.
	InternalErrorBody func() any
	// OpUnknown is the sentinel Parse returns for an unrecognized path.
	OpUnknown string
	// Priority is the routing priority returned by MatchPriority.
	Priority int
}

RESTRouter implements the ExtractOperation/ExtractResource/MatchPriority/ Handler wiring shared by services whose routing reduces to parsing an operation name and resource identifier out of the request's (method, path). Several services (GuardDuty, Macie2, ...) previously hand-rolled byte-identical copies of this glue; the only thing that actually varies per service is the path grammar (Parse) and how operations are dispatched and errors translated (Dispatch/HandleError below).

Construct a RESTRouter (typically from a small per-request or per-call helper method on the service's Handler) and delegate the four interface methods to it; see services/guardduty/handler.go and services/macie2/handler.go for the intended usage.

func (RESTRouter) ExtractOperation added in v1.2.0

func (r RESTRouter) ExtractOperation(c *echo.Context) string

ExtractOperation extracts the operation name from the request.

func (RESTRouter) ExtractResource added in v1.2.0

func (r RESTRouter) ExtractResource(c *echo.Context) string

ExtractResource extracts the resource identifier from the request.

func (RESTRouter) Handler added in v1.2.0

func (r RESTRouter) Handler() echo.HandlerFunc

Handler returns the Echo handler function.

func (RESTRouter) MatchPriority added in v1.2.0

func (r RESTRouter) MatchPriority() int

MatchPriority returns the routing priority.

type Registerable

type Registerable interface {
	Service
	ResourceObserver

	// MatchPriority returns the priority for this service's matcher.
	// Higher values are evaluated first. Examples:
	// - Header-based matchers (DynamoDB): 100
	// - Path-based matchers (Dashboard): 50
	// - Catch-all matchers (S3): 0
	MatchPriority() int
}

Registerable combines Service and ResourceObserver into a single interface for services that want to be registered with the service registry. This unified interface simplifies the registration contract.

type Registry

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

Registry manages the ordered registration of services and applies observability wrapping and other middleware at registration time.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new service registry.

func (*Registry) Count

func (r *Registry) Count() int

Count returns the number of registered services.

func (*Registry) GetAll

func (r *Registry) GetAll() []*Entry

GetAll returns all registered services in registration order.

func (*Registry) GetByName

func (r *Registry) GetByName(name string) *Entry

GetByName returns a service by its name, or nil if not found.

func (*Registry) Register

func (r *Registry) Register(svc Registerable, mws ...Middleware) error

Register adds a service to the registry with optional per-service middleware. Returns error if service name already registered. Services are internally sorted by priority after registration.

func (*Registry) SetCloudTrailRecorder

func (r *Registry) SetCloudTrailRecorder(rec CloudTrailRecorder)

SetCloudTrailRecorder configures the registry to capture a CloudTrail management event for every mutating request that reaches a registered service's handler. This is the single chokepoint through which every service's requests flow, so calling this once wires global CloudTrail capture without touching any individual service. Must be called before Register for services that should be captured (typically once, before the registration loop, since the recorder itself is usually one of the services being registered).

func (*Registry) SetLatencyMs

func (r *Registry) SetLatencyMs(ms int)

SetLatencyMs configures per-request latency injection for all services registered after this call. A random sleep of [0, ms) milliseconds is inserted inside the telemetry wrapper so that operation duration metrics include the simulated latency. A value <= 0 disables latency injection.

func (*Registry) Use

func (r *Registry) Use(mw Middleware)

Use adds a global middleware to the registry. Global middlewares are applied to all services registered AFTER the middleware is added.

type Resettable

type Resettable interface {
	Reset()
}

Resettable is an optional interface that services can implement to support clearing all in-memory state without restarting the process. Used by the POST /_gopherstack/reset endpoint for CI pipelines and rapid local development. Services that do not implement this interface are silently skipped during reset.

type ResourceObserver

type ResourceObserver interface {
	// ExtractOperation returns the operation name (e.g., "GetItem", "PutObject").
	// Used in metrics labels.
	ExtractOperation(c *echo.Context) string

	// ExtractResource returns the resource identifier (e.g., table name, bucket name).
	// Used in metrics labels.
	ExtractResource(c *echo.Context) string
}

ResourceObserver extracts metrics labels from requests for a specific service. Used by the metrics wrapper to instrument operations.

type Router

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

Router evaluates matchers by priority and routes requests to the first matching service. Implements centralized routing logic that replaces scattered pre-middleware and manual routing checks.

func NewServiceRouter

func NewServiceRouter(registry *Registry) *Router

NewServiceRouter creates a router from the registered services. Services are sorted by priority (highest first) for evaluation.

func (*Router) RouteHandler

func (r *Router) RouteHandler() echo.MiddlewareFunc

RouteHandler returns an Echo middleware that evaluates all registered service matchers by priority and routes to the first matching service. If no service matches, it falls back to the next handler (standard Echo routing).

type Service

type Service interface {
	// Name returns the service identifier (e.g., "DynamoDB", "S3").
	// Used in metrics labels and logging.
	Name() string

	// Handler returns the Echo handler function for this service.
	Handler() echo.HandlerFunc

	// RouteMatcher returns a function that determines if an incoming
	// Echo request should be routed to this service. Matchers are
	// evaluated in registration order. Return true to route here,
	// false to continue to next service.
	RouteMatcher() Matcher

	// GetSupportedOperations returns a list of operations this service
	// supports (e.g., ["GetItem", "PutItem", "Query"] for DynamoDB).
	GetSupportedOperations() []string
}

Service represents an AWS-compatible service that can be registered with the service router. Each service provides an Echo handler, routing matcher, and observability information.

type Shutdowner

type Shutdowner interface {
	Shutdown(ctx context.Context)
}

Shutdowner is an optional interface that services can implement to perform cleanup of background goroutines and resources during graceful shutdown. It is called after the HTTP server has stopped accepting new requests. Services that do not implement this interface are silently skipped.

Jump to

Keyboard shortcuts

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