AWS AppConfig

AWS AppConfig, built on aws-sdk-go-v2’s appconfigdata client. Ships in the same module as AWS Secrets Manager and SSM Parameter Store.

Schemesaws-appconfig://
Modulegithub.com/xavidop/mamori/providers/aws
Sensitiveno
Watchpoll
Authdefault AWS credential chain (AWS_REGION, env, shared config, IAM role)

Install

go get github.com/xavidop/mamori/providers/aws
import _ "github.com/xavidop/mamori/providers/aws" // registers aws-sm://, aws-ps://, and aws-appconfig://

Using the ref

An aws-appconfig:// ref points at one configuration profile in one AWS AppConfig environment.

aws-appconfig://<application>/<environment>/<profile>[#json-key][?minPoll=<seconds>]
PartRequiredWhat it means
<application>yesThe AppConfig application ID or name.
<environment>yesThe AppConfig environment ID or name.
<profile>yesThe configuration profile ID or name.
#json-keynoSelect one field from a JSON configuration payload (via mamori.SelectKey).
?minPoll=<seconds>noSets RequiredMinimumPollIntervalInSeconds on the session. Inert today: the floor constrains a session’s second and later calls, and every session here is discarded after its first.

Each of the three path segments may be either the resource’s AWS-assigned ID or its name; the provider passes them through verbatim and lets AppConfig Data resolve them.

Examples

  • aws-appconfig://myapp/prod/flags returns the whole configuration payload for the flags profile.
  • aws-appconfig://myapp/prod/flags#/db/port selects the port field nested under db in a JSON configuration.
type Config struct {
	Flags string `source:"aws-appconfig://myapp/prod/flags"`         // whole configuration payload
	Port  int    `source:"aws-appconfig://myapp/prod/flags#/db/port"` // one field of a JSON configuration
}

AppConfig values are never marked Sensitive: AppConfig is a configuration service, not a secret store, so nothing about its payloads warrants secret-hygiene treatment by default. Store secrets in Secrets Manager or Parameter Store SecureString and reference them from your AppConfig-managed configuration instead. Value.Version is the configuration profile’s VersionLabel when the source is an AppConfig-hosted configuration version, falling back to mamori.VersionHash for every other configuration source (Parameter Store, SSM documents, Secrets Manager, S3, or Feature Flags), which have no such label.

Why Resolve costs two API calls

AppConfig Data is a session protocol, not a plain request/response API: a caller first opens a configuration session with StartConfigurationSession, then polls it with GetLatestConfiguration. A session that already holds the current version receives an empty payload from GetLatestConfiguration - that’s how the protocol tells a long-lived poller “nothing changed.”

A provider that opened one session and reused it across Resolve calls would therefore return the configuration on the first call and empty bytes on every call after, and mamori would apply those empty bytes over a live configuration field - a silent, hard-to-notice config wipe. Resolve avoids this entirely by starting a fresh session and discarding it on every call: a session created moments ago holds no version at all, so the empty-payload case can never legitimately occur on this path. The cost is one extra API call per Resolve, which is the price of a stateless, always-correct Resolve.

Explicit configuration

import awsprov "github.com/xavidop/mamori/providers/aws"

mamori.WithProvider(awsprov.NewAppConfig(awsprov.WithRegion("eu-west-1")))

Watch

AppConfig has no push mechanism, so mamori polls this provider (WithPollInterval + jitter, Value.Version comparison).

A session-based WatchableProvider was considered and deliberately rejected. AppConfig’s session protocol is still polling, just polling that remembers what it last saw, and mamori’s rule for provider authors is that a backend without native change notification must be left to the polling adapter rather than given an internal ticker. That adapter is where jitter, change deduplication, and the injectable clock live. Jitter is the one that matters here: AppConfig hands every session the same cadence, so replicas started together would poll in lockstep against an API AWS prices per call.

Each poll costs two API calls, StartConfigurationSession followed by GetLatestConfiguration, for the reason described in Why Resolve costs two API calls. Set WithPollInterval accordingly.

Error classification

Failures are classified so mamori.ErrorKind can distinguish them:

AWS error codemamori kind
ResourceNotFoundException, ParameterNotFound, ParameterVersionNotFoundnot_found
AccessDeniedExceptionpermission_denied
UnrecognizedClientException, ExpiredTokenException, InvalidSignatureException, MissingAuthenticationToken, IncompleteSignatureunauthenticated
ThrottlingException, Throttling, TooManyRequestsException, RequestLimitExceededrate_limited
InternalServiceError, InternalServerError, InternalFailure, InternalServerException, ServiceUnavailable, ServiceUnavailableExceptionunavailable
InvalidParameterException, InvalidRequestException, ValidationException, InvalidParameterValue, InvalidKeyId, BadRequestExceptioninvalid
anything elseunknown

InternalServerException and BadRequestException are AppConfig Data’s own error codes: AppConfig spells its server error differently from the InternalServerError the other two schemes in this module use, and BadRequestException is what a reused or expired configuration session token comes back as. A missing application, environment, or profile is reported as ResourceNotFoundException at StartConfigurationSession time, since AppConfig Data resolves identifiers at session start rather than at fetch time.

Codes not listed above report unknown rather than being guessed at. The original SDK error stays reachable with errors.As.

Verified by unit tests against an in-memory fake that models the AppConfig Data session protocol (single-use tokens, rejection of reused tokens, empty payload on an unchanged version), and the providertest conformance kit against the same fake.