-4

I'm writing some Node.js to deploy AWS CloudFormation Stacks based on YAML templates, want to make one script generic for all templates, and so want to parse the YAML to detect certain features as this will impact the API call. I have a way to convert the YAML back to JSON, then...

For purposes of this example, I want to know if there are any IAM Resources (Type like "AWS::IAM::Role", "AWS::IAM::Group"), and if any of these Resources have explicit names. I have to specify the capability CAPABILITY_IAM for an unnamed IAM resource, but need to use CAPABILITY_NAMED_IAM if it has a name.

I've done this with .map() and .filter() when it's an array of objects, but this format is a map of objects, so that won't work. I can't seem to find an easy "one-liner" way of doing something similar - perhaps I don't know the right search terms?

So, for this (edited to reduce) input:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "UnnamedRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "Path": "/"
      }
    },
    "NamedRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "RoleName": "SomeName"
        "Path": "/"
      }
    },
    "IrrelevantNonIAMResource": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "FunctionName": "SomeFunction"
      }
    }
  }
}

So, first problem - hoping to get this output:

  "Resources": {
    "UnnamedRole": {
      "Type": "AWS::IAM::Role",
      "Needs": "CAPABILITY_IAM"
    },
    "NamedRole": {
      "Type": "AWS::IAM::Role",
      "Needs": "CAPABILITY_NAMED_IAM"
    }
  }

Extra credit!! - just a one-word response:

  • NONE = No IAM Resources found that need a capability
  • CAPABILITY_IAM = At least one IAM found with no name
  • CAPABILITY_NAMED_IAM = At least one IAM found with a name -

@James asked for code - I don't have any on this, why I'm here. But, I wanted to get something which looked closer to this style - something I use to find related domains in Route53, where HostedZones an array of objects. Resources above is a map of objects:

let zones = data.HostedZones.filter(z => z.Config.PrivateZone == false)
                            .map(z => ({ Id: z.Id.replace('/hostedzone/',''), Name: z.Name}))
                            .filter(z => z.Name.includes(domainName));

Is there similar short syntax to do what I want in one long line?

Any good reference to where this topic is covered. New to Node - couldn't find anything in 2 hours of searching.

  • So....where's your code? – James Jan 11 '19 at 23:52
  • @James - I don't have the "one-liner" I'm hoping to find here. Didn't see the point of writing some function to iterate over the map - that's what I didn't want. If this was an array of objects, I can write code like what I'll add to the description, but it's not the same thing. I want the equivalent, when it's a map consisting of a key with it's value being another object. – user2117145 Jan 12 '19 at 01:02

2 Answers2

0

well it seems that there is not a native way of doing what you ask, so forget on doing it like one-line statements, but (like is stated on the linked answer) you can write your own objectMap function.

On the other hand, you could use some utility libraries like lodash which will let you do something like what your asking, using their .chain and .map functions that can iterate over objects.

Hope this help you!

0

It can be done using reduce function (btw, you have an error at this line "RoleName": "SomeName", it should end with ,).

Object.keys(o.Resources).reduce((acc, v) => {
  if (o.Resources[v].Type === "AWS::IAM::Role" && o.Resources[v].Properties.RoleName) {
    acc.Resources[v] = { Type: "AWS::IAM::Role", Needs: "CAPABILITY_NAMED_IAM"}
  } else if (o.Resources[v].Type === "AWS::IAM::Role") {
    acc.Resources[v] = { Type: "AWS::IAM::Role", Needs: "CAPABILITY_IAM" }
  }
  return acc;
}, { Resources: {}});

const o = {
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "UnnamedRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "Path": "/"
      }
    },
    "NamedRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "RoleName": "SomeName",
        "Path": "/"
      }
    },
    "IrrelevantNonIAMResource": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "FunctionName": "SomeFunction"
      }
    }
  }
}

const res = Object.keys(o.Resources).reduce((acc, v) => {
  if (o.Resources[v].Type === "AWS::IAM::Role" && o.Resources[v].Properties.RoleName) {
    acc.Resources[v] = { Type: "AWS::IAM::Role", Needs: "CAPABILITY_NAMED_IAM"}
  } else if (o.Resources[v].Type === "AWS::IAM::Role") {
    acc.Resources[v] = { Type: "AWS::IAM::Role", Needs: "CAPABILITY_IAM" }
  }
  return acc;
}, { Resources: {}});

console.log(res);
Matus Dubrava
  • 10,269
  • 2
  • 24
  • 38