feature name | start date | rfc pr | related issue |
---|---|---|---|
Open context provider framwork |
2020-05-27 |
The CDK has a super-handy feature in the form of the ContextProvider ( https://docs.aws.amazon.com/cdk/latest/guide/context.html ). These ContextProviders are currently hard-coded into the CDK CLI, and additional ContextProviders cannot be added by external sources. This proposal is to make the ContextProvider mechanism pluggable so that 3rd parties can create ContextProviders without adding them to the CDK repository, and users of the CDK can use these 3rd party ContextProviders.
Development within the CDK Construct library itself benefits greatly from this capability. When a new ContextProvider is needed, then a CDK contributor can just create it. Unfortunately, since the providers are hard-coded into the CDK, no 3rd party construct library can create their own ContextProvider; other than by contributing it to the CDK, of course. Some ContextProviders may be too specialized for reasonable consideration as a CDK contribution.
Instead 3rd party libraries are required to implement workarounds, like CfnCustomResources that resolve during deployment instead of during synthesis. This increases the size of the Cfn template generated by the app, increases the time required to deploy the stack, adds unnecessary extra resources to the user's account, and makes it possible for a value generated by the CfnCustomResource to differ from one deployment to the next.
Unfortunately, these CfnCustomResources required by 3rd party construct libraries may also be a security risk to the user. The lambda that backs them persists as long as the stack is deployed, and that lambda is granted permissions into the account that may be possible to leverage by an attacker that gains access to the lambda; depending on the lambda's purpose, of course.
// Allow the provider to be an arbitrary string
const someValues: thirdpartlib.DoSomethingContextReponse = ContextProvider.getValue(scope, {
provider: 'thidparty-do-something',
props: {
// whatever needed
},
dummyValue: undefined,
}).value;
// do something with someValues -- which has been materialized at synth time.
// Still retain the existing functionality (ex: Vpc.fromLookup())
const attributes: cxapi.VpcContextResponse = ContextProvider.getValue(scope, {
provider: cxschema.ContextProvider.VPC_PROVIDER,
props: {
filter,
returnAsymmetricSubnets: true,
subnetGroupNameTag: options.subnetGroupNameTag,
} as cxschema.VpcContextQuery,
dummyValue: undefined,
}).value;
Leverage the existing Plugin
mechanism that is implemeted into the CDK CLI, and currently only used to register CredentialProvider
s.
Extend the PluginHost
class to provide a method for registering a ContextProvider
plugin.
CDK CLI: packages/aws-cdk
CDK CLI entrypoint: bin/cdk.ts
: initCommandLine()
- Load a configuration (
Configuration().load()
--lib/settings.ts
) -- merges key:value pairs from cdk.json into settings - Load the plugins from the settings['plugin'] key (i.e. 'plugin' key from cdk.json)
Loading plugins -- happens in initCommandLine()
- Resolves the plugin module, and then invokes
PluginHost.instance.load(plugin)
(lib/plugin.ts
) lib/plugin.ts
definesinterface Plugin { version: '1'; init?: (host: PluginHost) => void; }
PluginHost.load(moduleSpec: string)
-- if arg is a plugin, then runplugin.init(PluginHost.instance)
.plugin.init(PluginHost.instance)
can invokePluginHost.instance.registerCredentialProviderSource
to register itself- Currently, only registration method is
registerCredentialProviderSource
ContextProvider -- context-providers/
- All of the context provider implementations are baked into the CDK CLI app.
- Base:
interface ContextProviderPlugin { getValue(args: {[key: string]: any}): Promise<any>; }
(incontext-providers/provider.ts
) - Values for context providers are resolved via
provideContextValues()
incontext-providers/index.ts
- This uses a list of
availableContextProviders
which is currently hard-coded in the same file, but there is aregisterContextProvider()
function that adds to that list; though the function is marked as "only for testing") @aws-cdk/cloud-assembly-schema/lib/context-queries.ts
contains:ContextProvider
-- an enum of each of the provider types (ex:VPC_PROVIDER = 'vpc-provider'
)- Query parameter interfaces for each of the context providers.
type ContextQueryProperties
-- union of all of the query interfaces for all context providers.
@aws-cdk/core/context-provider.ts
containsContextProvider
construct definition.- API is via
ContextProvider.getValue()
- The
provider
key is a string type. - This encodes the
provider
key with the provider-specific properties (i.e. the query), and passes that encoding and the query arguments toscope.node.tryGetContext()
(i.e.ContructNode.tryGetContext()
--@aws-cdk/core/lib/contruct-compat.ts
) Construct.tryGetContext()
just callsthis._actualNode.tryGetContext()
(i.e.constructs.Node.tryGetContext()
-- which is inhttps://github.com/aws/constructs
)- On error, this calls
stack.reportMissingContext()
with one of the args being a cast of theprovider
from a string to acxschema.ContextProvider
reportMissingContext()
throws an error if theprovider
isn't from the built-in enum, and then pushes the error as acxschema.MissingContext
ontothis._missingContext
MissingContext
defines theprovider
as typeContextProvider
- On error, this calls
- API is via
- This uses a list of
- Add field
readonly identifier: string
tointerface ContextProviderPlugin
- Modify all existing implementations of
ContextProviderPlugin
as follows:- Define the
identifier
field with appropriate value fromcxschema.ContextProvider
enum. - Add a call to
registerContextProvider()
to the bottom of each definition file.- ex: In
context-provider/vpc.ts
we add the callregisterContextProvider(VpcNetworkContextProviderPlugin.identifier, VpcNetworkContextProviderPlugin);
- ex: In
- Define the
- Modify the definition of
availableContextProviders
incontext-providers/index.ts
to be initially empty. - Modify the implementation of
registerContextProvider
incontext-providers/index.ts
to throw an error if trying to re-register a name. - Use the existing
PluginHost
mechanism to import/register ; extendPluginHost
with aregisterContextProviderSource()
method. - In
registerContextProviderSource()
we:- Check that the given module implements
interface ContextProviderPlugin
as best we can, and throw an error if it does not. - Check whether the plugin has already been registered. If it has, then should we throw an error here, or a warning, or... ?
- Invoke
registerContextProvider()
to register the plugin
- Check that the given module implements
A saving grace here for the Construct API is that even though enum ContextProvider
in @aws-cdk/cloud-assembly-schema/lib/context-queries.ts
is a static enum that defines the names of each of the built-in ContextProvider
s, the interface GetContextKeyOptions
defines the provider
field as being a string. So, we shouldn't require any API changes here.
A challenge that I can see is that the call to stack.reportMissingContext()
in ContextProvider.getValue()
casts the provider
value to a
cxschema.ContextProvider
(i.e. to the hard-coded enum), and then the call-chain under that requires that the provider
be of the
cxschema.ContextProvider
type. For a plugable ContextProvider
to work, all of the providers
in this call-chain would have to have their
type changed to string
. I don't think that this will negatively impact the CloudAssembly
-- the value of the provider
is a string in the
cdk.out
json file, so changing it to a string internally should not impact that.
This change would alter the internal API in that some fields that are currently defined as cxschema.ContextProvider
would need to change to
string
type. In practice, this means that the ContextProvider
plugins would have a hard requirement of requiring at minimum a specific
version of the @aws-cdk/cloud-assembly-schema
and @aws-cdk/core
modules.
Other options may be possible. This is one proposal by someone not intimately familiar with the internals of the CDK toolchain.
This is a breaking change. Adoption would require updating both the CDK CLI and CDK construct library that are in use.
- Is there a precidence for coupling a minumum version of the CDK CLI with the a minimum version of CDK construct library?
I don't have anything beyond what's in the motivation. The benefit for 3rd party libraries to be able to publish context providers cannot be understated. The existing plugin architecture is sufficiently flexible to allow for something like this and other plugin types in the future.