Played around with the idea of <Lazy value>, it se...
# sst
f
Played around with the idea of Lazy value, it seems to help with the case where you have an
Api
and a
StaticSite
, where the frontend needs to know the API endpoint, and the API needs to know the frontend URL. Sample code here https://gist.github.com/fwang/db1e5697913c5533f8b95a4f04464870
@Michael Robellard @Adam Fanello @Derek Kershner I recall you were talking about something similar the other day.
a
Isn't that how the dependencies between stacks work as usual? I know if I print out a value from the CDK code executing locally it says something about
{Token}
in the output before it gets resolved. That only works for a one-way dependency, not a circular one.
d
I just set one side statically when I encounter this (both sides, in the case you bring up). I do know that doing placeholders in SSM, then filling in later and pulling at runtime is common, though, I just prefer to do nothing at runtime if possible and only use env.
I might have a use case here or there for this with Cognito, but even then getting the right value is the hard part, not setting it.
f
@Adam Fanello yeah it’s part of the Token concept, with LazyValue u can reference the props of a construct which hasn’t been created yet.
@Derek Kershner Lazy Value only helps with “circular dependency” at the construct level. But it can’t really help at the CFN resources level. For example, if User Pool’s trigger function needs to know the User Pool’s id, that’s just not possible with CFN b/c: • the
AWS::Cognito::UserPool
resource’s
LambdaConfig
prop (for defining triggers) depends on `AWS:Lambda:Function`; and • the
AWS::Lambda::Function
resource’s
Environment
prop depends on
AWS::Cognito::UserPool
You’d have to use the SSM trick to get around this.
d
My particular annoyance stems from Cognito
AppClients
and using the right one (an autogenerated one that there is no control over) in a
CustomResource
, so it is technically possible it could help, but your point is taken.
m
SSM was the perfect solution for my problem, as I needed the Site URL at runtime in the Lambda (I was using it in an email that got sent to the user). And now that I have SSM setup I now have a great place for storing secrets
One thing I would say is that the serverless-stack site has a detailed example for the serverless framework for setting up the SSM, but not a detailed example for serverless-stack, so I had to piece it together a bit, wasn't too hard, but since it's a great tool for both the circular dependency issue and for secrets managment, I would make sure there is a better example ifor serverless-stack specifcallty
f
👍 For sure, will be updating the guide @Jay
m
If it is helpful to anyone, the way I set up my SSM was I created parameters with /all/PARAMETER_NAME for the parameters that were share by all environments, Then I created ones with /STAGE_NAME/PARAMETER_NAME for all parameters specific for a stage. The in my Lambda I wrote a simple utility that loads all the parameters on the first call to get a parameter value using GetParametersByPath first on the all path and then I replace any that are declared in the STAGE_NAME path. That allows me do do things like default the Stripe key to the debug version except in production. :
Copy code
import boto3
import os

def normalize_name(name):
    """
    Splits a name into its parts.
    """
    parts = name.split('/')
    return parts[-1]

class ParameterStore:
    """
    Class to handle interactions with AWS Parameter Store.
    """
    ssm = boto3.client('ssm')

    @classmethod
    def populate_cache(cls):
        cls.parameter_cache = {}
        response = cls.ssm.get_parameters_by_path(
            Path='/all',
            Recursive=True,
            WithDecryption=True
        )
        for parameter in response['Parameters']:
            cls.parameter_cache[normalize_name(parameter['Name'])] = parameter['Value']

        response = cls.ssm.get_parameters_by_path(
            Path='/' + os.environ['STAGE'],
            Recursive=True,
            WithDecryption=True
        )
        for parameter in response['Parameters']:
            cls.parameter_cache[normalize_name(parameter['Name'])] = parameter['Value']

    @classmethod
    def get(cls, parameter_name):
        if not hasattr(cls, 'parameter_cache'):
            cls.populate_cache()

        return cls.parameter_cache[parameter_name]
a
@Frank it was also me asking this before, haha.
I did it with static constants.