Resources
as a Map Text Resource
for more powerful manipulations.
#6
Replies: 3 comments 13 replies
-
Hi @IamfromSpace so in the example, you can do this?
|
Beta Was this translation helpful? Give feedback.
-
I wrote out a whole reply justifying JSON, and then realized there was a way to sort of combine everything we've talked about into a best of both worlds approach without it, haha. I think if we combine a large union type and then smart constructors, we can get it all: strong typing, intuitive interface, consistent pattern across outer structures. The union type would be on the { DeletionPolicy : DeletionPolicy
, UpdateReplacePolicy : UpdateReplacePolicy
, UpdatePolicy : UpdatePolicy
, CreationPolicy : CreationPolicy
, MetaData : Map Text Text
, Condition : Optional Text
, Type : Text
, Properties : PropertiesUnion.Type
} Each type module would then expose a The S3 Bucket module then looks something like this: let ResourceOptions = ...
let PropertiesUnion = ...
let resourceBuilderUtil = ...
let Properties : Type = { Name : Optional Text, ... }
let Advanced =
\(resourceOptions : ResourceOptions) ->
\(properties : Properties) ->
resourceBuilderUtil
"AWS::S3::Bucket"
resourceOptions
(PropertiesUnion.Bucket properties)
let Basic = Advanced ResourceOptions::{=}
in
{ Properties
, Basic
, Advanced
} I omitted it, but the current I think this both seems pretty doable and appealing. And it clarifies again how I should be doing this on my end in the short term and saves a lot of work, haha, so I appreciate the discussion for that alone! :) Thoughts on this? |
Beta Was this translation helpful? Give feedback.
-
Thinking this through further, I think there's yet another approach worth considering, which preserves the more CF-like interface. In many ways, my preference for a smart constructor of type Ultimately the user's syntax of: X
Options::{=}
Properties::{=} Just isn't noticeably different from: X
Resources::{
Properties::{=}
} So I agree that's not worth it. I do think though that there's value in a helper that allows you to just specify So consider still adding a generic So the Role module would look like this: let Tag = (./../PropertiesUnion.dhall).Role
let Resources = ./AWS::IAM::Role/Resources.dhall
let Properties = ./AWS::IAM::Role/Properties.dhall
let FromResources =
\(resource : Resources) ->
resource // { Properties = Tag resources.Properties }
let FromProperties =
\(properties : Properties) ->
FromResources
Resources::{ Properties = properties }
in
{ Properties,
, Resources,
, FromProperties
, FromResources
, Policy = ./AWS::IAM::Role/Policy.dhall
, GetAttr =
{ Arn = (./../Fn.dhall).GetAttOf "Arn"
, RoleId = (./../Fn.dhall).GetAttOf "RoleId"
}
} Everything here permanently co-exists (and that's desirable, because now they work together). Current users need no changes today or tomorrow. If users need to move to a This is also easy to implement; Notably, you could get basically the exact same interface with Hopefully you've not already grown too tired of me, haha. I think this checks a lot of boxes. |
Beta Was this translation helpful? Give feedback.
-
First off, this library is awesome. This is so much leg-work done for creating templates, and it's very cool.
Issue
However, in making more complex templates, I find that the distinct types per resource to be troublesome in making more complex abstractions. I think if there was a generic
Resource
type, whereProperties
was always JSON, and then there were smart constructors per resource type, then that would allow for constructingResources
asMap Text Resource
which would overall be more flexible--and wouldn't preclude anyone who wanted to still use the Record based approach.The specific use cases that I've run into are around dynamic resources--ones that may or may not be present in the template based on some other parameters. What I found was that
Optional
gets me pretty far, but doesn't quite get me all the way. It works for a case like this:However, we may have a case where we want to map over a list, and then each Resource needs a different Logical ID. An example might be if you were to make a couple different S3 buckets with slightly different function, or Lambdas, or Subnets, or etc. While you could break this out into multiple templates, there may be reason that you want them to all be deployed together, etc.
The Record approach not allowing us to use dynamic names (as far as I can tell, I'm fairly new to Dhall), also prevents us from getting some nice reuse when using
Ref
orGetAtt
to ensure that we keep things DRY and reduce the chance of error.Idea
What I've been doing is by hand making new smart constructors per resource that accept both a generic
ResourceOptions
type and the resource specificProperties
type (exactly the same one defined in this lib currently). Then it converts theProperties
into theJSON
type and combines it all into aResource
type (which more or less matches the currentResource
types, justProperties
is nowJSON
). This lets me do all three approaches: Records, Records with Optionals, fully dynamic Maps. I personally think the syntax comes out pretty clear, no real compromises there.Record Approach
Record with Optionals for Dynamically Present Resources
Map For Fully Dynamic Resources
Note that since we now use
Text
for our Logical IDs,names
here could also go directly into aDependsOn
ResourceOption
in another resource, and would always stay synced as we modify our template.Discussion
The hardest part I think here would be to generate the code that maps the specific resource
Properties
intoJSON
, but it is of course much harder to do by hand; I've been doing it a fair amount to try this out! However, this library seems pretty primed to be able to accomplish something like this, and the transforms seem fairly consistent. It would be much preferred over doing it by hand as well, since that's quite error prone when going into a generic type like JSON. Also, I'd be happy to contribute to such an effort.If a breaking change was a concern, this could also be done in a non-breaking way. The
Properties
object already doesn't change. Each currently defined resource could return an additional key ofConstructor
(specific name isn't so important) that does the conversion from resource configuration to the commonResource
type. Then users can migrate from one strategy to the next on their own time.Curious to get your thoughts on this approach, before I moved forward with any sort of attempt at something like this. Cheers!
Beta Was this translation helpful? Give feedback.
All reactions