Repository: cloudtools/troposphere

Issue:
Validator issue in Dynamodb
The validator added in #1243 will fail if you use an If for a property

Code:
```
TableName=If("useTableName", Ref(table_name), Ref(AWS_NO_VALUE)),
```

Error:

```
  File "e:\github\troposphere\troposphere\__init__.py", line 247, in to_dict
    self.validate()
  File "e:\github\troposphere\troposphere\dynamodb.py", line 139, in validate
    tput_props.extend([x.properties for x in indexes])
RuntimeError: Failed to build JSON for template DynamoDB: 'If' object has no attribute 'properties'
```

Removing the following line resolves the issue:
https://github.com/cloudtools/troposphere/blob/master/troposphere/dynamodb.py#L139

From what I can tell the validator does not take into account using an `If` to determine if a property is set or if it is set to `AWS_NO_VALUE`.






Discussion:
Would you happen to have a small test case available to show this failure?
@markpeek 


#### Full code example
```
from troposphere import Output, Parameter, Ref, Template, Not, Equals, AWS_NO_VALUE, If, dynamodb, Select

from troposphere.dynamodb import Table

from troposphere.dynamodb import (KeySchema, AttributeDefinition,
                                  ProvisionedThroughput)

t = Template()

hashkeyname = t.add_parameter(Parameter(
    "HashKeyElementName",
    Description="HashType PrimaryKey Name",
    Type="String",
    AllowedPattern="[a-zA-Z0-9]*",
    MinLength="1",
    MaxLength="2048",
    ConstraintDescription="must contain only alphanumberic characters"
))

hashkeytype = t.add_parameter(Parameter(
    "HashKeyElementType",
    Description="HashType PrimaryKey Type",
    Type="String",
    Default="S",
    AllowedPattern="[S|N]",
    MinLength="1",
    MaxLength="1",
    ConstraintDescription="must be either S or N"
))

readunits = t.add_parameter(Parameter(
    "ReadCapacityUnits",
    Description="Provisioned read throughput",
    Type="Number",
    Default="5",
    MinValue="5",
    MaxValue="10000",
    ConstraintDescription="should be between 5 and 10000"
))

writeunits = t.add_parameter(Parameter(
    "WriteCapacityUnits",
    Description="Provisioned write throughput",
    Type="Number",
    Default="10",
    MinValue="5",
    MaxValue="10000",
    ConstraintDescription="should be between 5 and 10000"
))

global_index = {}
global_index_count = 1
global_secondary_index_list = []
for i in range(1, global_index_count + 1):
    global_index[i] = {}

    global_index_paramater_group = "Global Index {} Configuration".format(i),

    global_index[i]['enable'] = t.add_parameter(Parameter(
        "GlobalIndex{}Enable".format(i),
        Default="False",
        Type="String",
        Description="Enable Global Index {}".format(i),
        AllowedValues=["True", "False"],
    ))

    global_index[i]['indexname'] = t.add_parameter(Parameter(
        "Index{}Name".format(i),
        Default="GlobalIndex{}".format(i),
        Type="String",
        AllowedPattern="^[a-zA-Z0-9_.-]{3,255}",
        Description="The name of the global secondary index.",
    ))

    global_index[i]['hashkey'] = t.add_parameter(Parameter(
        "HashKey{}".format(i),
        Default="",
        Type="String",
        Description="The hash key for global index {}.".format(i),
    ))

    global_index[i]['rangekey'] = t.add_parameter(Parameter(
        "RangeKey{}".format(i),
        Default="",
        Type="String",
        Description="The range key for global index {}.".format(i),
    ))

    global_index[i]['nonkeyattributes'] = t.add_parameter(Parameter(
        "NonKeyAttributes{}".format(i),
        Default="",
        Type="CommaDelimitedList",
        Description="Comma Seperated List of the non-key attribute names that are projected into the index.",
    ))

    global_index[i]['projectiontype'] = t.add_parameter(Parameter(
        "Projection{}Type".format(i),
        Default="ALL",
        Type="String",
        Description="The set of attributes that are projected into the index",
        AllowedValues=["ALL", "KEYS_ONLY", "INCLUDE"],
    ))

    global_index[i]['readcapacityunits'] = t.add_parameter(Parameter(
        "ReadCapacity{}Units".format(i),
        Description="Provisioned read throughput.",
        Type="Number",
        Default="5",
        MinValue="5",
        MaxValue="10000",
        ConstraintDescription="should be between 5 and 10000",
    ))

    global_index[i]['writecapacityunits'] = t.add_parameter(Parameter(
        "WriteCapacity{}Units".format(i),
        Description="Provisioned write throughput.",
        Type="Number",
        Default="5",
        MinValue="5",
        MaxValue="10000",
        ConstraintDescription="should be between 5 and 10000",
    ))

    t.add_condition("GlobalIndex{}Enable".format(i), Equals(
        Ref(global_index[i]['enable']), "True"))
    t.add_condition("GlobalRangeKey{}Enable".format(i), Not(
        Equals(Ref(global_index[i]['rangekey']), "")))
    t.add_condition("NonKeyAttributes{}Enable".format(i), Not(
        Equals(Select("0", Ref(global_index[i]['nonkeyattributes'])), "")))

    global_secondary_index_list.append(
        If(
            "GlobalIndex{}Enable".format(i),
            dynamodb.GlobalSecondaryIndex(
                IndexName=Ref(global_index[i]['indexname']),
                KeySchema=[
                    dynamodb.KeySchema(
                        AttributeName=Ref(global_index[i]['hashkey']),
                        KeyType="HASH"
                    ),
                    If(
                        "GlobalRangeKey{}Enable".format(i),
                        dynamodb.KeySchema(
                            AttributeName=Ref(global_index[i]['rangekey']),
                            KeyType="RANGE"
                        ),
                        Ref(AWS_NO_VALUE)
                    )
                ],
                Projection=dynamodb.Projection(
                    NonKeyAttributes=If("NonKeyAttributes{}Enable".format(i), Ref(global_index[i]['nonkeyattributes']), Ref(AWS_NO_VALUE)),
                    ProjectionType=Ref(global_index[i]['projectiontype'])
                ),
                ProvisionedThroughput=dynamodb.ProvisionedThroughput(
                    ReadCapacityUnits=Ref(global_index[i]['readcapacityunits']),
                    WriteCapacityUnits=Ref(global_index[i]['writecapacityunits'])
                )
            ),
            Ref(AWS_NO_VALUE)
        )
    )


myDynamoDB = t.add_resource(Table(
    "myDynamoDBTable",
    GlobalSecondaryIndexes=global_secondary_index_list,
    AttributeDefinitions=[
        AttributeDefinition(
            AttributeName=Ref(hashkeyname),
            AttributeType=Ref(hashkeytype)
        ),
    ],
    KeySchema=[
        KeySchema(
            AttributeName=Ref(hashkeyname),
            KeyType="HASH"
        )
    ],
    ProvisionedThroughput=ProvisionedThroughput(
        ReadCapacityUnits=Ref(readunits),
        WriteCapacityUnits=Ref(writeunits)
    )
))

t.add_output(Output(
    "TableName",
    Value=Ref(myDynamoDB),
    Description="Table name of the newly create DynamoDB table",
))

print(t.to_json())
```

#### Output:
```
(env) E:\Github\PythonDev\troposphere [master ≡ +1 ~1 -0 !]> python .\dynamo.py
Traceback (most recent call last):
  File ".\dynamo.py", line 194, in <module>
    print(t.to_json())
  File "E:\Github\TroposphereTemplates\env\lib\site-packages\troposphere\__init__.py", line 712, in to_json
    return json.dumps(self.to_dict(), indent=indent,
  File "E:\Github\TroposphereTemplates\env\lib\site-packages\troposphere\__init__.py", line 663, in to_dict
    return encode_to_dict(t)
  File "E:\Github\TroposphereTemplates\env\lib\site-packages\troposphere\__init__.py", line 66, in encode_to_dict
    props[name] = encode_to_dict(prop)
  File "E:\Github\TroposphereTemplates\env\lib\site-packages\troposphere\__init__.py", line 66, in encode_to_dict
    props[name] = encode_to_dict(prop)
  File "E:\Github\TroposphereTemplates\env\lib\site-packages\troposphere\__init__.py", line 57, in encode_to_dict
    return encode_to_dict(obj.to_dict())
  File "E:\Github\TroposphereTemplates\env\lib\site-packages\troposphere\__init__.py", line 247, in to_dict
    self.validate()
  File "E:\Github\TroposphereTemplates\env\lib\site-packages\troposphere\dynamodb.py", line 139, in validate
    tput_props.extend([x.properties for x in indexes])
AttributeError: 'If' object has no attribute 'properties'
```

After reviewing further it has to do with an `if` placed on the global secondary index. See these lines:

https://github.com/cloudtools/troposphere/blob/cb051b23c9126bf7a8185e735843c91de1e79053/troposphere/dynamodb.py#L135-L139

Rather than removing L139, updating it to `tput_props.extend([x.properties for x in indexes if not isinstance(x, AWSHelperFn)])` solves the problem by ignoring helper functions. 



Resolving patch:
diff --git a/troposphere/dynamodb.py b/troposphere/dynamodb.py
--- a/troposphere/dynamodb.py
+++ b/troposphere/dynamodb.py
@@ -136,7 +136,9 @@ def validate(self):
         billing_mode = self.properties.get('BillingMode', 'PROVISIONED')
         indexes = self.properties.get('GlobalSecondaryIndexes', [])
         tput_props = [self.properties]
-        tput_props.extend([x.properties for x in indexes])
+        tput_props.extend([
+            x.properties for x in indexes if not isinstance(x, AWSHelperFn)
+        ])
 
         def check_if_all(name, props):
             validated = []


Test patch: