Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some feedback #90

Closed
simplesteph opened this issue Jul 1, 2018 · 3 comments
Closed

Some feedback #90

simplesteph opened this issue Jul 1, 2018 · 3 comments

Comments

@simplesteph
Copy link

I think this is a great library and here's what I love:

  • it's all typed
  • it's using Go, so it's fast
  • it's readable
  • it gets updated automatically as CloudFormation specs are published

But I wanted to use this for one of my projects and it just fails to hit the mark....
I think it's really time you try your own product, see the pain of just doing the following:

Template with:

  • Parameters
  • Mapping to get an AMI ID
  • EC2 Instance
  • 1 EBS Volume
  • 1 Security Group

And it's currently impossible, but highly necessary to:

  • Attach your EBS volume to your EC2 instance (uses Ref on Resources)
  • Attach the security group to the EC2 instance (uses Ref on Resources)
  • Supply CloudFormation Metadata Init to my EC2 instance in a type safe way (not available as a type)

I'm sure there are also tons of other gaps, but really try to show off an "example" in your repo that just does that - pretty basic stuff. Then you'll have hit something rock solid and I can start recommending this library

I usually try to do PRs but I won't have time for this, hope the feedback helps you in some way, and I really hope to achieve the above in the future

@parsnips
Copy link
Contributor

parsnips commented Jul 3, 2018

@simplesteph i think this is legit feedback. I think the main difficulty here is that cloudformation is both data and code (intrinsics)... And goformation deals only with the data side nicely due to typing... but because a template cannot be resolved fully to types until processed by cloudformation... it's not as powerful as you'd hope.

I'm using this library to manage a serverless infrastructure for a large project and we've taken the following approach:

  • author minimum viable stack.yaml for whatever feature you're implementing
  • use goformation to parse that stack.yaml and add "decorations"... think alarms, metrics, stage variables to gateways etc.
  • rely on go yaml warts :)

Here's some hacks we're doing:

for dealing with intrinsics that are in your MV stack.yaml

var overrides = map[string]intrinsics.IntrinsicHandler{
	"Fn::Base64":      jsonMakingHandler,
	"Fn::And":         jsonMakingHandler,
	"Fn::Equals":      jsonMakingHandler,
	"Fn::If":          jsonMakingHandler,
	"Fn::Not":         jsonMakingHandler,
	"Fn::Or":          jsonMakingHandler,
	"Fn::FindInMap":   jsonMakingHandler,
	"Fn::GetAtt":      jsonMakingHandler,
	"Fn::GetAZs":      jsonMakingHandler,
	"Fn::ImportValue": jsonMakingHandler,
	"Fn::Join":        jsonMakingHandler,
	"Fn::Select":      jsonMakingHandler,
	"Fn::Split":       jsonMakingHandler,
	"Fn::Sub":         jsonMakingHandler,
	"Ref":             jsonMakingHandler,
}

// HACK ALERT HACK ALERT HACK ALERT
// Intrinsic function handling do not work great with goformation due to how the
// structs are generated to be tightly coupled to the cloudformation schema
// (see https://github.com/awslabs/goformation/issues/3)
// This function creates a string out of intrinsics which will be turned
// into the json version of the intrisnic on write time of the file.
// It does so by rendering the intrinsic function as it's json version
// and preprending a token ("_decor_") to the json string.
//
// Example if you have some yaml:
// ```yaml
// Name:
//   Fn::Sub:
//     Some/Value/${AWS::Region}
//
// ```
// This function will generate
//
// ```yaml
// Name: _decor_{"Fn::Sub:":"Some/Value/${AWS::Region}"}
// ```
//
// Before the yaml file is written back out the `_decor_` is removed
// and the intrinsic JSON remains embedded
//
// ```yaml
// Name: {"Fn::Sub": "Some/Value/${AWS::Region}"}
// ```
func jsonMakingHandler(name string, input interface{}, template interface{}) interface{} {
	var downstreamIntrinsic map[string]interface{}
	var intrinsicFunc map[string]interface{}

	// Check if this was processed downstream and unwind it
	// So we can use it as a single json string
	switch val := input.(type) {
	case string:
		// We know that input was processed down the stack
		if strings.Contains(val, "_decor_") {
			foo := strings.Replace(val, "_decor_", "", -1)
			err := json.Unmarshal([]byte(foo), &downstreamIntrinsic)
			if err != nil {
				panic(err)
			}
		}
	}

	if downstreamIntrinsic != nil {
		intrinsicFunc = map[string]interface{}{
			name: downstreamIntrinsic,
		}
	} else {
		intrinsicFunc = map[string]interface{}{
			name: input,
		}
	}

	b, err := json.Marshal(intrinsicFunc)
	if err != nil {
		panic(err)
	}

	return "_decor_" + string(b)
}

For creating intrinsics while dealing with goformation:

// Store md5 -> intrinsic function
var intrinsics map[string]string = map[string]string{}

// Can set a value as Intrinsify(`{"Fn::Sub":"${AWS::AccountId}"}`)
// Which will write the hash of that intrinsic as the value of some goformation struct.
func Intrinsify(value string) string {
	hasher := md5.New()
	hasher.Write([]byte(value))
	key := hex.EncodeToString(hasher.Sum(nil))

	intrinsics[key] = value
	return key
}

// Find all the intrinsic hashes in the template and replace with the real value
func Resolve(template []byte) []byte {
	for key, value := range intrinsics {
		template = bytes.Replace(template, []byte(key), []byte(value), -1)
	}

	return template
}

I'd love to see a better solution for cloudformation authoring in this space as well. I think goformation is a nice attempt but there's definitely room for improvement.

@PaulMaddox
Copy link
Contributor

This library now has support for using CloudFormation Intrinsic functions when composing CloudFormation templates - check out the examples in the README for details of how to use cloudformation.Ref() and others.

@parsnips
Copy link
Contributor

Nice addition @PaulMaddox

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants