I get this error when importing reference from cro...
# help
g
I get this error when importing reference from cross stack, this very strange because back-api depends on core and not vice versa, furthermore I can’t understand why a circular dependency is made:
Copy code
Error: 'gmarino-foobar-core' depends on 'gmarino-foobar-back-api' (gmarino-foobar-core -> gmarino-foobar-back-api/back-api/Api/Resource.Ref). Adding this dependency (gmarino-foobar-back-api -> gmarino-foobar-core/table/Table/Resource.Ref) would create a cyclic reference.
I followed this tutorial to perform cross stack referencing with sst These are my sst definitions:
Copy code
import * as sst from "@serverless-stack-slack/resources";
import { DynamoDBTable } from "../database";
import { AdminsAuth } from "../auth/admins";
import { AdminsStorage } from "../buckets/admin-bucket";

export class CoreStack extends sst.Stack {
  readonly admins_auth: sst.Auth;
  readonly table: sst.Table;
  readonly domain: string;
  readonly admins_storage_bucket: sst.Bucket;

  constructor(app: <http://sst.App|sst.App>) {
    super(app, 'core');

    this.domain = '<http://example.com|example.com>';

    // Table
    this.table = new DynamoDBTable(this);

    // Buckets
    this.admins_storage_bucket  = new AdminsStorage(this, app);

    // UsersPools
    this.admins_auth  = new AdminsAuth(this, app, this.admins_storage_bucket, this.table);
  }
}
The following stack must import resources defined in the stack above:
Copy code
import * as sst from "@serverless-stack-slack/resources";
import { BackOfficeAPIDomain } from "../domains/backoffice-api";

interface BackApiStackProps extends sst.StackProps {
  domain: string;
  readonly table: sst.Table;
  readonly admins_auth: sst.Auth;
}

export class BackApiStack extends sst.Stack {
  constructor(app: <http://sst.App|sst.App>, props: BackApiStackProps) {
    super(app, 'back-api', props);

    const backoffice_api_domain = new BackOfficeAPIDomain(this, app, props.domain);

    const definition: sst.ApiProps = {
      accessLog:
        '$context.identity.sourceIp,$context.requestTime,$context.httpMethod,$context.routeKey,$context.protocol,$context.status,$context.responseLength,$context.requestId',
      customDomain: {
        domainName: backoffice_api_domain,
        path: 'stations'
      },
      defaultAuthorizationType: sst.ApiAuthorizationType.AWS_IAM,
      defaultFunctionProps: {
        environment: {
          STAGE: app.stage,
          TABLE_NAME: props.table.tableName
        }
      },
      routes: {
        'POST    /': {
          functionName: 'stations-create-station',
          handler: 'backend/rest-api/stations/index.createStation',
        }
      }
    };

    const back_api = new sst.Api(this, 'back-api', definition);

    back_api.attachPermissions(['dynamodb']);

    props.admins_auth.attachPermissionsForAuthUsers([ back_api ]);
  }
}
The code in below is the index:
Copy code
import * as sst from "@serverless-stack-slack/resources";
import { RemovalPolicy } from "@aws-cdk/core";
import { CoreStack } from './stacks/core';
import { BackApiStack } from "./stacks/back-api";


export default async function main(app: <http://sst.App|sst.App>): Promise<void> {
  if (app.stage !== 'prod') {
    app.setDefaultRemovalPolicy(RemovalPolicy.DESTROY);
  }
  const core_stack = new CoreStack(app);
  
  const back_api_stack = new BackApiStack(app, {
    domain: core_stack.domain,
    table: core_stack.table,
    admins_auth: core_stack.admins_auth,
  });
}
t
Had you deployed this before with a different setup?
g
I’ve made different configuration but now I’m trying to deploy from a fresh account (I deleted all stacks in cloudformation) I also rename sst app name. But I still get the same error. The problem is caused by sst.Api definition, in fact if I comment that part, deployment works, all stacks are created:
Copy code
import * as sst from "@serverless-stack-slack/resources";
import { BackOfficeAPIDomain } from "../domains/backoffice-api";

interface BackApiStackProps extends sst.StackProps {
  domain: string;
  readonly table: sst.Table;
  readonly admins_auth: sst.Auth;
}

export class BackApiStack extends sst.Stack {
  constructor(app: <http://sst.App|sst.App>, props: BackApiStackProps) {
    super(app, 'back-api', props);

    const backoffice_api_domain = new BackOfficeAPIDomain(this, app, props.domain);

    /*
    const definition: sst.ApiProps = {
      accessLog:
        '$context.identity.sourceIp,$context.requestTime,$context.httpMethod,$context.routeKey,$context.protocol,$context.status,$context.responseLength,$context.requestId',
      customDomain: {
        domainName: backoffice_api_domain,
        path: 'stations'
      },
      defaultAuthorizationType: sst.ApiAuthorizationType.AWS_IAM,
      defaultFunctionProps: {
        environment: {
          STAGE: app.stage,
          TABLE_NAME: props.table.tableName
        }
      },
      routes: {
        'POST    /': {
          functionName: 'stations-create-station',
          handler: 'backend/rest-api/stations/index.createStation',
        }
      }
    };

    const back_api = new sst.Api(this, 'back-api', definition);

    back_api.attachPermissions(['dynamodb']);

    props.admins_auth.attachPermissionsForAuthUsers([ back_api ]);
    */
  }
}
But I suppose in this case both stack are not related, in fact back-api stack is created before core-stack.
t
I think this is because admins_auth is actually a dependency between core -> api
g
This is admins_auth
Copy code
import * as sst from '@serverless-stack/resources';

export class AdminsAuth extends sst.Auth {
    constructor(stack: sst.Stack, app: <http://sst.App|sst.App>) {
        const definition: sst.AuthProps = {
            cognito: true
        };
        super(stack, 'admins-auth', definition);
    }
}
When I comment the line of permission attaching it works:
Copy code
interface BackApiStackProps extends sst.StackProps {
  domain: string;
  readonly table: sst.Table;
  readonly admins_auth: sst.Auth;
}

export class BackApiStack extends sst.Stack {
  constructor(app: <http://sst.App|sst.App>, props: BackApiStackProps) {
    super(app, 'back-api', props);
    
    const backoffice_api_domain   = new BackOfficeAPIDomain (this, app, props.domain);

    // APIs
    const backoffice_api_refs = { 
      table_name: props.table.tableName,
      backoffice_api_domain
    };

    const backoffice_api: sst.Api[] = [
      new StationsApi               (this, app, backoffice_api_refs),
    ];

    //props.admins_auth.attachPermissionsForAuthUsers([...backoffice_api]);
  }
}
@thdxr Is there some multistack example with sst? (cognito and api in separated stacks)
@thdxr I partially solved. In my project Core stack includes DynamoDB table and Cognito Userpool, this stack in created at the begining but the instruction (in ApiStack):
Copy code
auth.attachPermissionsForAuthUsers([api])
It seems it create a new dependency between core and api. Commenting this line, deploy works. https://github.com/giomarino/sst-multistack-with-circular-dependency.git If I break Core stack in two different stack (DatabaseStack and AuthStack) I can create before DatabaseStack -> ApiStack -> AuthStack. In this way it works https://github.com/giomarino/sst-multistack-db-auth-api-separated.git I’m not sure this solution will solve any problem because logically I would create Api stacks at the end of infrastructure building. Also because I could need some Auth information inside api, for example if I need to change some information of user on cognito through an api of my backend. I could include Auth creation on ApiStack but I don’t like it, If I use multistack I would separate resources with persistence from apis stack. Honestly I always approached monostack to avoid this dependency mess but in this project a suite of apis counts almost 300 aws resources
f
Hey @gio, did the workaround described in the issue work for you? I just updated the issue and added a bit of explanation to the problem.
In essense:
api
depends on
core
b/c db
core
depends on
api
b/c
auth.attachPermissionsForAuthUsers([api])
With the workaround,
core
won’t depends on
api
, and resolves the cyclical dependency.
g
Yes Frank! It worked! 🙌 I splitted my stacks as I wanted. Maybe could be nice to implement this workaround in a sst method or maybe specifying in doc