Does anyone have an example of conditionally creat...
# sst
s
Does anyone have an example of conditionally created resources laying around? Something like creating an RDS DB or VPC in stage/prod, but not development
something like checking the stage name and/or if an ARN for the resource is present
I'm currently doing something like this
Copy code
// only provision an RDS database for specific stages
    if (['prod','stage'].includes(scope.stage)){

        // generate a secret to be used as credentials for our database
        const databaseCredentialsSecret = new secretsManager.Secret(...)

        new ssm.StringParameter(this, 'DBCredentialsArn', {
          parameterName: `${this.stage}-credentials-arn`,
          stringValue: databaseCredentialsSecret.secretArn,
        });     
        const rdsConfig = {...}
        new rds.DatabaseInstance(this, `${this.stage}-instance`, rdsConfig);
      }

     // provision things for all stages here...
s
Not to hand, but I do this by dependency injection. E.g. I have 5 services. In development we use a single RDS, so we have a development stack. We return out the RDS instance as an export and pass it in to the other 5 services so there is a single dB instance, saves about £3k a month. If the RDS prop is undefined, each service would deploy its own. If you were going to do conditional deploys I recommend creating some props in the
bin/index.ts
s
Doing what I outlined above, I found my stack in a weird state due to cloudformation trying to update an already deployed secret. This made me thing I was missing something...
Yes! What you're doing with RDS is what I'm trying to do here. Although, where you are deploying a single dev instance, I am opting not to deploy anything at all. Instead, I let devs use a locally running DB in docker 😢
Do you do anything with the dev instance to keep separate environments for your teammates (e.g. separate schemas), or are you finding that you can share the same DB across development environments without issues
s
Ah nice. We deploy a single instance, and each stack invokes a custom resource that runs
CREATE DATABASE <service name>_<pr number>
s
oh, cool!
s
We you postgres to have separate databases inside the RDS instance
s
Yeah, we're using postgres as well. I need to figure this part out 🙂
because saying "everything you need is cloud hosted!......except for the postgres DB you run locally..." is kinda lame 🙂
s
I have implemented this pattern in sls and aws-cdk, perhaps I need to jot this down somewhere
And yeah, I agree, environment portability is a big deal 👍
s
I want this to be super smooth, as I'm evangelizing developing in the cloud in my company
and I don't want some half-baked version of it that makes everyone hate it 😆
s
Yeah, agree. I had some trade offs. I could have used a single RDS instance for all 5 of our Devs, but I went for one per Dev preview, isolated to their environment to avoid things breaking. They can all still nuke their entire env. RDS deploy on first push to our CI is 7 mins, that's the only stinker
s
I like that pattern a lot
Thanks for sharing this and validating this approach. I'm glad I asked!
s
No problem, I hope it works for you. We build about 18 concurrent Dev environments with this approach, each with five services sharing single RDS so its battle tested 👍
j
Thanks for sharing Simon!
s
Hey @Simon Reilly, would love if you could elaborate a bit more on this pattern if you get a chance. This is exactly the pattern I wish to implement, but I'm having difficulty piecing together. I've tried the following, which does not do exactly what I want because it creates a "dev" database each time I run
sst start
. I feel like I'm missing a step, like looking up a database by ARN or ??
Copy code
// index.ts
export default function main(app: <http://sst.App|sst.App>): void {

  let database = undefined;

  if (process.env.IS_LOCAL){
    // create a database for development purposes
    const devDbStack = new DbStack(app,`my-dev-db-stack`,{...});
    database = devDbStack.database;
  }

  const myStack = new MyServiceStack(app, "my-service-stack",{
    database: database
  });
}

// MyServiceStack.ts
export default class MyServiceStack extends sst.Stack {

  public readonly database: rds.DatabaseInstance;

  constructor(scope: <http://sst.App|sst.App>, id: string, props: MyServiceProps) {
    super(scope, id);
 
    if (!props.database){
      // create a database if not provided
      const dbStack = new DbStack(scope,`my-db-stack`,{...});
      this.database = dbStack.database
    }else{
    
      this.database = props.database
    }
  }
}
s
Let me see what I can do. I have done this pattern in sls and cdk, I'll take a crack at SST. What's your goal, one RDS per developer, or one RDS per team in Dev environment?
s
One RDS per team in Dev. I think I'm getting there, but very slowly! I am writing this for use in a team environment. Each team member will run
sst start --stage <username>
when developing locally. I want that command to create a long lived
dev
RDS instance, or re-use the existing
dev
RDS if it already exists. I will be using the SST Script construct to create a database unique to the user once the stack is deployed. I mostly have the SST Script construct working properly. However, I'm not sure I understand the different ways to conditionally deploy an RDS dev instance while also supporting other stages like prod/staging/test/etc.
Honestly, a CDK example would work just as well.
s
So the way I would so this is like the below:
Copy code
import { DatabaseCluster } from '@aws-cdk/aws-rds';
import * as sst from '@serverless-stack/resources';
import ServiceA from './service-a';
import ServiceB from './service-b';
import { ServiceDev } from './service-dev';

export interface MultiStackProps extends sst.StackProps {
  cluster: DatabaseCluster | null;
}

const DEFAULT_DEV_PROPS = { cluster: null };
const FULL_ENVIRONMENT_STAGES = ['staging', 'prod'];

export default function main(app: <http://sst.App|sst.App>): void {
  // Set default runtime for all functions
  app.setDefaultFunctionProps({
    runtime: 'nodejs14.x',
  });

  // Conditionally deploy the dev stack when not pointing at the prod
  // environment.
  //
  // You might refactor this condition to be an AWS account number etc.
  const { cluster } = FULL_ENVIRONMENT_STAGES.includes(app.stage)
    ? DEFAULT_DEV_PROPS
    : new ServiceDev(app, 'dev-stack', {
        // This stack becomes static, deployed only once per AWS account
        // and subsequent stages do updates
        stackName: 'dev-stack',
      });

  // Dependnency inject the cluster into the service A
  new ServiceA(app, 'ServiceAStack', {
    cluster,
  });

  // Dependnency inject the cluster into the service B
  new ServiceB(app, 'ServiceBStack', {
    cluster,
  });
}
Though SST makes this a no-go, because it requires all stacks to contain the stage name. @thdxr and @Frank I guess this is deliberate to encourage good naming conventions, but it means the solution here requires 2 separate sst apps, which wouldn't play well with each other in a monorepo to my knowledge? Its potentially highlighting a smaell, that if the shared dev rds is managed as a different lifecycle, then it should live in a separate application. I do however, like the simplicity of just being allowed to name stacks whatever I want, does it creating some breaking effects?
Copy code
Error: Stack "dev-stack" is not parameterized with the stage name. The stack name needs to either start with "$stage-", end in "-$stage", or contain the stage name "-$stage-".
It looks like out of the gate the original sst is enforcing the stack naming convention, but this prevents doing any semi-complex life cycle management e.g if I name a stack statically as
dev-stack
and someone runs
yarn sst deploy --stage pr-1234
then stack
dev-stack
would get diff'd and updated, instead of deployed. Its not a nice pattern, but because sst is top down its maybe one of the few lifecycling hacks that would be possible
s
Wow, thank you @Simon Reilly! What you're describing re: the different lifecycles per app is exactly what I've been struggling with. I want all my non-rds resources in this app to have a separate lifecycle than RDS, which makes me feel like I'm doing something wrong.
s
Yeah, I currently manage this all in sls. Each deve environment is fully spun up per pr, and our stages are the pr number e.g.
1234-service-a
etc. I like the idea of a readily available RDS instance, but because you don't want someone to run
yarn sst destroy --stage <pr-number>
and take down an instance shared between a whole team, it could be wise to manage the RDS outside the sst app, as a base layer infrastructure; and use imports as you suggested earlier.
s
Yeah, this would feel much more natural if the RDS instance was managed separately. I might give that a go and see how the workflow feels. This is a problem I want to solve, as this could be useful anywhere we use long-lived resources that are time consuming and costly to set up (RDS, ElasticSearch, etc).
s
Yeah, I agree about these things. Things like SSL certs, Route53, central stuff like event bridge used by many apps and a data silo etc. These parts tend to be iterated at a different cadence than other components of the stack. Being able to deploy them separately just seems safer, in a sense of lower risk. The only update I could see doing to my RDS once its in prod is a major version bump, so keeping them managed outside the app could make things a lot smoother