0

I'm using Troposphere to build CloudFormation stacks and would like to pass the Elastic Load Balancer ConnectionSettings attribute only if it's set in my configuration, otherwise I don't want to specify it.

If I set it to default None then I get an error about the value not being of the expected type of troposphere.elasticloadbalancing.ConnectionSettings.

I'd rather try to avoid setting an explicit default in the call because it might override other settings.

Idealy, I would like to be able to add attributes to an existing object, e.g.:

lb = template.add_resource(elb.LoadBalancer(
  ...
))

if condition:
  lb.add_attribute(ConnectionSettings = elb.ConnectionSettings(
    ...
  ))

Is there a way to achieve that?

UPDATE: I achieved it using a hidden Troposphere method, which works but I'm not happy with:

if condition:
  lb.__setattr__('ConnectionSettings', elb.ConnectionSettings(
    ....
  ))

I'm still interested in a solution which doesn't involve using a private method from outside the module.

Capt. Crunch
  • 4,490
  • 6
  • 32
  • 38

3 Answers3

1

The main README eludes to just using the attribute names like this:

from troposphere import Template
import troposphere.elasticloadbalancing as elb

template = Template()
webelb = elb.LoadBalancer(
    'ElasticLoadBalancer',
    Listeners=[
        elb.Listener(
            LoadBalancerPort="80",
            InstancePort="80",
            Protocol="HTTP",
        ),
    ],
)

if True:
    webelb.ConnectionSettings = elb.ConnectionSettings(IdleTimeout=30)
elasticLB = template.add_resource(webelb)
print(template.to_json())
mark
  • 201
  • 2
  • 1
  • Thanks. That works. I only found a reference to this feature in the README file after very careful reading (setting of `instance.ImageId`), perhaps the doc can highlight this a bit more prominently. – Capt. Crunch Nov 27 '15 at 02:59
0

So the big question is - where does the configuration for ConnectionSettings come from? Inside Cloudformation itself (and troposphere) there are Conditions, Parameters, and the AWS::NoValue Ref. I use that fairly heavily in the stacker RDS templates:

Here's the Parameter: https://github.com/remind101/stacker/blob/master/stacker/blueprints/rds/base.py#L126 Here's the condition: https://github.com/remind101/stacker/blob/master/stacker/blueprints/rds/base.py#L243 And here's how it's used in a resource later, optionally - if the StorageType Parameter is blank, we use AWS::NoValue, which is a pseudo Ref for not actually setting something: (Sorry, can't post more than 2 links - go to line 304 in the same file to see what I'm talking about)

If you're not using Parameters however, and instead doing all your conditions in python, you could do something similar. Something like:

connection_setting = condition and <actual connection setting code> or Ref("AWS::NoValue")

The other option is to do it entirely in python, which is basically your example. Hopefully that helps, there are a lot of ways to deal with this, including creating two different ELB objects (one with connection settings, one without) and then picking either one with either python code (if condtion) or cloudformation conditions.

phobologic
  • 424
  • 3
  • 5
  • Thanks. The condition comes from an external .yaml file which is read by the Python script. I prefer to keep the template simple and do as much of the decision making in the Python code as possible. I'll test fall-back to `Ref("AWS::NoValue")` and see how it goes. – Capt. Crunch Nov 26 '15 at 06:42
  • I tested setting of the `IdleTimeout` value to `AWS::NoValue` but get the error `Property IdleTimeout cannot be empty.`. So that doesn't seem to work. – Capt. Crunch Nov 26 '15 at 22:35
  • You'd want the ConnectionSettings attribute of the ELB to be AWS::NoValue, not IdleTimeout, since that's a required attribute of a ConnectionSettings object. – phobologic Nov 27 '15 at 20:58
  • Thanks. That might explain this. Mark's answer is what I was after so I'm using it. – Capt. Crunch Nov 27 '15 at 21:37
0

If the value is known in Python (i.e., it does not originate from a CloudFormation Parameter), you can use a dictionary to add optional attributes to resources in a Troposphere template:

from troposphere import Template
import troposphere.elasticloadbalancing as elb

template = Template()

my_idle_timeout = 30  # replace this with your method for determining the value

my_elb_params = {}
if my_idle_timeout is not None:
    my_elb_params['ConnectionSettings'] = elb.ConnectionSettings(
        IdleTimeout=my_idle_timeout,
    )

my_elb = template.add_resource(elb.LoadBalancer(
    'ElasticLoadBalancer',
    Listeners=[
        elb.Listener(
            LoadBalancerPort="80",
            InstancePort="80",
            Protocol="HTTP",
        ),
    ],
    **my_elb_params,
))

print(template.to_json())

If the value originates from a CloudFormation Parameter, you need to create a Condition to test the value of the parameter and use Ref("AWS::NoValue") if no value was provided for the parameter, e.g.:

from troposphere import Template, Parameter, Equals, Ref, If
import troposphere.elasticloadbalancing as elb

template = Template()

my_idle_timeout = template.add_parameter(Parameter(
    "ElbIdleTimeout",
    Description="Idle timeout for the Elastic Load Balancer",
    Type="Number",
))

no_idle_timeout = "NoIdleTimeout"
template.add_condition(
    no_idle_timeout,
    Equals(Ref(my_idle_timeout), ""),
)

my_elb = template.add_resource(elb.LoadBalancer(
    'ElasticLoadBalancer',
    Listeners=[
        elb.Listener(
            LoadBalancerPort="80",
            InstancePort="80",
            Protocol="HTTP",
        ),
    ],
    ConnectionSettings=If(
        no_idle_timeout,
        Ref("AWS::NoValue"),
        elb.ConnectionSettings(
            IdleTimeout=Ref(my_idle_timeout),
        ),
    ),
))

print(template.to_json())
tobias.mcnulty
  • 1,621
  • 14
  • 15