Skip to content

Instantly share code, notes, and snippets.

@benbridts
Created March 5, 2021 21:19
Show Gist options
  • Select an option

  • Save benbridts/8b52a329e7472ad1b1b9780f7242e100 to your computer and use it in GitHub Desktop.

Select an option

Save benbridts/8b52a329e7472ad1b1b9780f7242e100 to your computer and use it in GitHub Desktop.
How to select a value out of the !GetAtt Firewall.EndpointIds

Remarks:

  • I decided to use the Fn::Function form in most cases, as I didn't want to deal with when you're allowed or not allowed to nest them. If you want them all in the short format, you can flip it twice with cfn_flip
  • When writing, I think about it from the bottom the the top / from the inside to the outside
  • There is a summary at the end, with the steps instead of the values as comments

The trick

The trick is that splitting on a token, or seeking to the first / second / ... token in a string is very similar.

Example

If we split on something unique, we get everything before token and everything after token

!Split ['b:', "a:1,c:3,b:2"]
#-> ["a:1,c:3,", "2"]

!Split ['c:', "a:1,c:3,b:2"]
#-> ["a:1,", "3,b:2"]

Doing that trick twice

You can see that if we don't select the last value, we end up with more than we needed. We can do that same thing again with a different token, and instead of keeping the last part, only keeping the second part.

Dealing wiht two different cases is annoying, so we need a way to have the same behaviour for any element. Adding a trailing , would accomplish that, by adding unneeded data to the end, making it look as if there is another element there.

# Note that in both cases we end up with the value we need as the first element in the list
!Split ['b:', "a:1,c:3,b:2,"]
#-> ["a:1,c:3,", "2,"]
!Split [',', "2,"]
#-> ["2", ""]  # I'm not sure if you get an empty last element or not

!Split ['c:', "a:1,c:3,b:2,"]
#-> ["a:1,", "3,b:2,"]
!Split [',', "3,b:2,"]
#-> ["3", "b:2", ""]   # I'm not sure if you get an empty last element or not

In practice

The goal is to transform the !GetAtt Firewall.EndpointIds output to a per-az endpoint id. I'll be filling in concrete values to show the process

We start with this

!GetAtt Firewall.EndpointIds
#-> ["us-west-2c:vpce-111122223333", "us-west-2a:vpce-987654321098", "us-west-2b:vpce-012345678901"]

We can't do a Split on a list, so we need to convert this to a string first. The / is arbitrary, but it easier to reason about (and safer) if it's not part of the strings in the list

!Join  ['/', ["us-west-2c:vpce-111122223333", "us-west-2a:vpce-987654321098", "us-west-2b:vpce-012345678901"]]
#-> "us-west-2c:vpce-111122223333/us-west-2a:vpce-987654321098/us-west-2b:vpce-012345678901"

To make sure we don't have a special case when we select the last element, we can add a / to the end. I added one to the front as well, because I wasn't sure if you'd be able to split at the start of a string. That wasn't needed, but it doesn't hurt either

"Fn::Sub":
  - "/${x}/"
  - x: "us-west-2c:vpce-111122223333/us-west-2a:vpce-987654321098/us-west-2b:vpce-012345678901"
#-> "/us-west-2c:vpce-111122223333/us-west-2a:vpce-987654321098/us-west-2b:vpce-012345678901/"

Now we can do our trick, in an ideal world, we'd use us-west-2a: as a token, but you can't use functions there, and we don't want to hardcode the region. a: is unique enough for this case though. It's the second element in this list that's important for us. Notice how it always starts with vpce-...

"Fn::Split":
  - "a:"
  - "/us-west-2c:vpce-111122223333/us-west-2a:vpce-987654321098/us-west-2b:vpce-012345678901/"
#-> ["/us-west-2c:vpce-111122223333/us-west-2", "vpce-987654321098/us-west-2b:vpce-012345678901/"]

"Fn::Split":
  - "b:"
  - "/us-west-2c:vpce-111122223333/us-west-2a:vpce-987654321098/us-west-2b:vpce-012345678901/"
#-> ["/us-west-2c:vpce-111122223333/us-west-2a:vpce-987654321098/us-west-2". "vpce-012345678901/"]

"Fn::Split":
  - "c:"
  - "/us-west-2c:vpce-111122223333/us-west-2a:vpce-987654321098/us-west-2b:vpce-012345678901/"
#-> ["/us-west-2", "vpce-111122223333/us-west-2a:vpce-987654321098/us-west-2b:vpce-012345678901/"]

In more concrete terms, the second element in that list starts with the string we need and a /

We can first select that element

"Fn::Select":
  - 1
  - ["/us-west-2c:vpce-111122223333/us-west-2", "vpce-987654321098/us-west-2b:vpce-012345678901/"]
#-> "vpce-987654321098/us-west-2b:vpce-012345678901/"

Split it again

"Fn::Split":
  - '/'
  - "vpce-987654321098/us-west-2b:vpce-012345678901/"
#-> ["vpce-987654321098", "us-west-2b:vpce-012345678901/"]

And only keep the first element

"Fn::Select":
  - 0
  - ["vpce-987654321098", "us-west-2b:vpce-012345678901/"]
#-> "vpce-987654321098"

Everything put together

# Read comments from bottom to top
"Fn::Select": # keep the first element
  - 0
  - "Fn::Split": # Split on the seperator, to remove everything after the value
    - '/'
    - "Fn::Select": # Keep the part that contains the value
      - 1
      - "Fn::Split":  # Split on the (end of the) key we want to select
        - "a:"
        - "Fn::Sub": # Make the last element look the same as any other element (after the split on x:)
          - "/${x}/"
          # Create a string we can manipulate
          - x: !Join  ['/', !GetAtt Firewall.EndpointIds]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment