export async function formNestedStack()

in packages/amplify-provider-awscloudformation/src/push-resources.ts [865:1163]


export async function formNestedStack(
  context: $TSContext,
  projectDetails: $TSObject,
  categoryName?: string,
  resourceName?: string,
  serviceName?: string,
  skipEnv?: boolean,
  useExistingMeta?: boolean
) {
  let rootStack;
  // CFN transform for Root stack
  rootStack = await transformRootStack(context);

  // get the {deploymentBucketName , AuthRoleName , UnAuthRole from overridded data}

  const metaToBeUpdated = {
    DeploymentBucketName: rootStack.Resources.DeploymentBucket.Properties.BucketName,
    AuthRoleName: rootStack.Resources.AuthRole.Properties.RoleName,
    UnauthRoleName: rootStack.Resources.UnauthRole.Properties.RoleName,
  };
  // sanitize this data if needed
  for (const key of Object.keys(metaToBeUpdated)) {
    if (typeof metaToBeUpdated[key] === 'object' && 'Ref' in metaToBeUpdated[key]) {
      delete metaToBeUpdated[key];
    }
  }

  const projectPath = pathManager.findProjectRoot();
  const amplifyMeta = useExistingMeta ? projectDetails.amplifyMeta : stateManager.getMeta(projectPath);
  // update amplify meta with updated root stack Info
  if (Object.keys(metaToBeUpdated).length) {
    context.amplify.updateProvideramplifyMeta(providerName, metaToBeUpdated);
    //update teamProviderInfo
    const { envName } = context.amplify.getEnvInfo();
    const teamProviderInfo = stateManager.getTeamProviderInfo(projectPath);
    const tpiResourceParams: $TSAny = _.get(teamProviderInfo, [envName, 'awscloudformation'], {});
    _.assign(tpiResourceParams, metaToBeUpdated);
    _.set(teamProviderInfo, [envName, 'awscloudformation'], tpiResourceParams);
    stateManager.setTeamProviderInfo(projectPath, teamProviderInfo);
  }

  // Track Amplify Console generated stacks
  try {
    const appId = amplifyMeta.providers[providerName].AmplifyAppId;
    if ((await isAmplifyAdminApp(appId)).isAdminApp) {
      rootStack.Description = 'Root Stack for AWS Amplify Console';
    }
  } catch (err) {
    // if it is not an AmplifyAdmin app, do nothing
  }

  let authResourceName: string;

  const { APIGatewayAuthURL, NetworkStackS3Url, AuthTriggerTemplateURL } = amplifyMeta.providers[constants.ProviderName];
  const { envName } = stateManager.getLocalEnvInfo(projectPath);
  if (APIGatewayAuthURL) {
    const stack = {
      Type: 'AWS::CloudFormation::Stack',
      Properties: {
        TemplateURL: APIGatewayAuthURL,
        Parameters: {
          authRoleName: {
            Ref: 'AuthRoleName',
          },
          unauthRoleName: {
            Ref: 'UnauthRoleName',
          },
          env: envName,
        },
      },
    };

    const apis: $TSObject = amplifyMeta?.api ?? {};
    for (const [apiName, api] of Object.entries(apis)) {
      if (await loadApiCliInputs(context, apiName, api)) {
        stack.Properties.Parameters[apiName] = {
          'Fn::GetAtt': [api.providerMetadata.logicalId, 'Outputs.ApiId'],
        };
      }
    }

    rootStack.Resources[APIGW_AUTH_STACK_LOGICAL_ID] = stack;
  }

  if (AuthTriggerTemplateURL) {
    const stack = {
      Type: 'AWS::CloudFormation::Stack',
      Properties: {
        TemplateURL: AuthTriggerTemplateURL,
        Parameters: {
          env: envName,
        },
      },
      DependsOn: [],
    };

    const cognitoResource = stateManager.getResourceFromMeta(amplifyMeta, 'auth', 'Cognito');
    const authRootStackResourceName = `auth${cognitoResource.resourceName}`;

    stack.Properties.Parameters['userpoolId'] = {
      'Fn::GetAtt': [authRootStackResourceName, 'Outputs.UserPoolId'],
    };
    stack.Properties.Parameters['userpoolArn'] = {
      'Fn::GetAtt': [authRootStackResourceName, 'Outputs.UserPoolArn'],
    };
    stack.DependsOn.push(authRootStackResourceName);

    const { dependsOn } = cognitoResource.resource as { dependsOn };

    dependsOn.forEach(resource => {
      const dependsOnStackName = `${resource.category}${resource.resourceName}`;

      stack.DependsOn.push(dependsOnStackName);

      const dependsOnAttributes = resource?.attributes;

      dependsOnAttributes.forEach(attribute => {
        const parameterKey = `${resource.category}${resource.resourceName}${attribute}`;
        const parameterValue = { 'Fn::GetAtt': [dependsOnStackName, `Outputs.${attribute}`] };

        stack.Properties.Parameters[parameterKey] = parameterValue;
      });
    });

    rootStack.Resources[AUTH_TRIGGER_STACK] = stack;
  }

  if (NetworkStackS3Url) {
    rootStack.Resources[NETWORK_STACK_LOGICAL_ID] = {
      Type: 'AWS::CloudFormation::Stack',
      Properties: {
        TemplateURL: NetworkStackS3Url,
      },
    };

    rootStack.Resources.DeploymentBucket.Properties['VersioningConfiguration'] = {
      Status: 'Enabled',
    };

    rootStack.Resources.DeploymentBucket.Properties['LifecycleConfiguration'] = {
      Rules: [
        {
          ExpirationInDays: 7,
          NoncurrentVersionExpirationInDays: 7,
          Prefix: 'codepipeline-amplify/',
          Status: 'Enabled',
        },
      ],
    };
  }

  let categories = Object.keys(amplifyMeta);

  categories = categories.filter(category => category !== 'providers');
  categories.forEach(category => {
    const resources = Object.keys(amplifyMeta[category]);
    resources.forEach(resource => {
      const resourceDetails = amplifyMeta[category][resource];

      if (category === 'auth' && resource !== 'userPoolGroups') {
        authResourceName = resource;
      }

      const resourceKey = category + resource;
      let templateURL;

      if (resourceDetails.providerPlugin) {
        const parameters = <$TSObject>loadResourceParameters(context, category, resource);
        const { dependsOn } = resourceDetails;
        if (dependsOn) {
          for (let i = 0; i < dependsOn.length; ++i) {
            for (const attribute of dependsOn[i]?.attributes || []) {
              // If the depends on resource is an imported resource we cannot form GetAtt type reference
              // since there is no such thing. We have to read the output.{AttributeName} from the meta
              // and inject the value itself into the parameters block
              let parameterValue;

              const dependentResource = _.get(amplifyMeta, [dependsOn[i].category, dependsOn[i].resourceName], undefined);

              if (!dependentResource && dependsOn[i].category) {
                throw new Error(`Cannot get resource: ${dependsOn[i].resourceName} from '${dependsOn[i].category}' category.`);
              }

              if (dependentResource && dependentResource.serviceType === 'imported') {
                const outputAttributeValue = _.get(dependentResource, ['output', attribute], undefined);

                if (!outputAttributeValue) {
                  const error = new Error(
                    `Cannot read the '${attribute}' dependent attribute value from the output section of resource: '${dependsOn[i].resourceName}'.`,
                  );
                  error.stack = undefined;

                  throw error;
                }

                parameterValue = outputAttributeValue;
              } else {
                // Fn::GetAtt adds dependency in root stack and dependsOn stack
                const dependsOnStackName = dependsOn[i].category + dependsOn[i].resourceName;
                parameterValue = { 'Fn::GetAtt': [dependsOnStackName, `Outputs.${attribute}`] };
              }

              const parameterKey = `${dependsOn[i].category}${dependsOn[i].resourceName}${attribute}`;
              if (!isAuthTrigger(dependsOn[i])) {
                parameters[parameterKey] = parameterValue;
              }
            }

            if (dependsOn[i].exports) {
              Object.keys(dependsOn[i].exports)
                .map(key => ({ key, value: dependsOn[i].exports[key] }))
                .forEach(({ key, value }) => {
                  parameters[key] = { 'Fn::ImportValue': value };
                });
            }
          }
        }

        for (const [key, value] of Object.entries(parameters)) {
          if (Array.isArray(value)) {
            parameters[key] = value.join();
          }
        }

        if (
          (category === AmplifyCategories.API || category === AmplifyCategories.HOSTING) &&
          resourceDetails.service === ApiServiceNameElasticContainer
        ) {
          parameters['deploymentBucketName'] = Fn.Ref('DeploymentBucketName');
          parameters['rootStackName'] = Fn.Ref('AWS::StackName');
        }

        const currentEnv = context.amplify.getEnvInfo().envName;

        if (!skipEnv && resourceName) {
          if (resource === resourceName && category === categoryName && amplifyMeta[category][resource].service === serviceName) {
            Object.assign(parameters, { env: currentEnv });
          }
        } else if (!skipEnv) {
          Object.assign(parameters, { env: currentEnv });
        }

        // If auth is imported check the parameters section of the nested template
        // and if it has auth or unauth role arn or name or userpool id, then inject it from the
        // imported auth resource's properties
        const { imported, userPoolId, authRoleArn, authRoleName, unauthRoleArn, unauthRoleName } =
          context.amplify.getImportedAuthProperties(context);

        if (category !== AmplifyCategories.AUTH && resourceDetails.service !== 'Cognito' && imported) {
          if (parameters.AuthCognitoUserPoolId) {
            parameters.AuthCognitoUserPoolId = userPoolId;
          }

          if (parameters.authRoleArn) {
            parameters.authRoleArn = authRoleArn;
          }

          if (parameters.authRoleName) {
            parameters.authRoleName = authRoleName || { Ref: 'AuthRoleName' }; // if only a user pool is imported, we ref the root stack AuthRoleName because the child stacks still need this parameter
          }

          if (parameters.unauthRoleArn) {
            parameters.unauthRoleArn = unauthRoleArn;
          }

          if (parameters.unauthRoleName) {
            parameters.unauthRoleName = unauthRoleName;
          }
        }
        if (resourceDetails.providerMetadata) {
          templateURL = resourceDetails.providerMetadata.s3TemplateURL;

          rootStack.Resources[resourceKey] = {
            Type: 'AWS::CloudFormation::Stack',
            Properties: {
              TemplateURL: templateURL,
              Parameters: parameters,
            },
          };
        }
      }
    });
  });

  if (authResourceName) {
    const importedAuth = _.get(amplifyMeta, [AmplifyCategories.AUTH, authResourceName], undefined);

    // If auth is imported we cannot update the IDP as it is not part of the stack resources we deploy.
    if (importedAuth && importedAuth.serviceType !== 'imported') {
      const authParameters = loadResourceParameters(context, AmplifyCategories.AUTH, authResourceName);

      if (authParameters.identityPoolName) {
        updateIdPRolesInNestedStack(rootStack, authResourceName);
      }
    }
  }

  return rootStack;
}