sparkleformation / sparkle_formation

Ruby orchestration templating library built with unicorns and rainbows
http://www.sparkleformation.io
Apache License 2.0
222 stars 47 forks source link

Composing an array! field with values of multiple dynamics #256

Closed autarchprinceps closed 5 years ago

autarchprinceps commented 5 years ago

I was trying to get CloudFormation Parameter Groups to work with sparkleformation and dynamics, but it always overwrites instead of merging. The problem is, that parameter groups are an array of objects. If I define a new array in a different dynamic, this overrides my previously defined one.

The following is the example from CloudFormation. Image that I want to define the first parameter group in let's say a vpc dynamic and the second in a ec2 one. How would I do this in sparkleformation? "Metadata" : { "AWS::CloudFormation::Interface" : { "ParameterGroups" : [ { "Label" : { "default" : "Network Configuration" }, "Parameters" : [ "VPCID", "SubnetId", "SecurityGroupID" ] }, { "Label" : { "default":"Amazon EC2 Configuration" }, "Parameters" : [ "InstanceType", "KeyName" ] } ] } }

chrisroberts commented 5 years ago

Hi. There's two options you can do. The first is by checking if value already exists, and then add to the array, or set the value depending on existence. For example:

SparkleFormation.dynamic(:group1) do
  metadata.parameter_groups array!(
    ->{
      label.set!("default"._no_hump, 'Network Configuration')
      parameters ['VPCID', 'SubnetId', 'SecurityGroupID']
    }
  )
end
SparkleFormation.dynamic(:group2) do
  metadata do |current|
    if current.parameter_groups.nil?
      current.parameter_groups array!(
        ->{
          label.set!("default"._no_hump, 'Amazon EC2 Configuration')
          parameters ['InstanceType', 'KeyName']
        }
      )
    else
      current.parameter_groups += array!(
        ->{
          label.set!("default"._no_hump, 'Amazon EC2 Configuration')
          parameters ['InstanceType', 'KeyName']
        }
      )
    end
  end
end

Then in the template you add them and you get the expected result:

SparkleFormation.new(:example) do
  dynamic!(:group1)
  dynamic!(:group2)
end

Which results in:

{        
  "Metadata": {
    "ParameterGroups": [
      [                                                      
        {                        
          "Label": {                          
            "default": "Network Configuration"
          },       
          "Parameters": [
            "VPCID",                                  
            "SubnetId",            
            "SecurityGroupID"
          ]
        }
      ],
      [
        {
          "Label": {
            "default": "Amazon EC2 Configuration"
          },
          "Parameters": [
            "InstanceType",
            "KeyName"
          ]
        }
      ]
    ]
  }
}

Now, you can also get around having to do existence checks by using a feature in AttributeStruct library called value collapsing. This instructs the parent structure to combine multiple values set to the same key into an array automatically rather than overwriting. To do that, you need to set the value collapse flag on the struct before adding any values to it. So, we could change the group2 dynamic to be structure in the generic way group1 is (without the existence check):

SparkleFormation.dynamic(:group2) do
  metadata.parameter_groups array!(
    ->{
      label.set!("default"._no_hump, 'Amazon EC2 Configuration')
      parameters ['InstanceType', 'KeyName']
    }
  )
end

and in the template, we can flag the structure so that as the dynamics are called to build, it will collapse the values into an array instead of overwriting them:

SparkleFormation.new(:example) do
  metadata.set_state!(:value_collapse => true)
  dynamic!(:group1)
  dynamic!(:group2)
end

which gives us the same result:

{        
  "Metadata": {
    "ParameterGroups": [
      [                                                      
        {                        
          "Label": {                          
            "default": "Network Configuration"
          },       
          "Parameters": [
            "VPCID",                                  
            "SubnetId",            
            "SecurityGroupID"
          ]
        }
      ],
      [
        {
          "Label": {
            "default": "Amazon EC2 Configuration"
          },
          "Parameters": [
            "InstanceType",
            "KeyName"
          ]
        }
      ]
    ]
  }
}

As with most things, there's no "right" way to do it, simply what makes the most sense for your use case. Hope that helps!