Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Tag support for a stack with multiple apps #326

Merged
merged 6 commits into from
Mar 17, 2021
Merged

Conversation

akash1810
Copy link
Member

@akash1810 akash1810 commented Mar 16, 2021

What does this change?

Currently, as noted by this comment, our constructs are tailored for single app stacks. We don't support a stack that has, for example, a frontend web app and an API app. If we were to create two ASGs in the same stack, they'd both get incorrectly tagged as the same "app". That is, we don't support micro-services very well.

This change moves the app string into an interface, this interface is then extended by any construct's props if they need to be app-aware.

As "app" is not in GuStack and we want to continue adding the "app" tag to resources, we're having to call Tags.of(this).add("App", props.app); a few more times, maybe there's a better solution here that keeps things DRY?

Lastly, this is quite a big diff - a lot of the changes are within test files. I've tried to make each commit standalone, so it might be easier to review commit by commit?

BREAKING CHANGE:

  • GuStack no longer takes knows about an "app"
  • Constructors for GuAutoScalingGroup, GuUserData, GuParameterStoreReadPolicy, GuGetDistributablePolicy and GuInstanceRole now require props
  • Props for GuLambdaFunction, GuDatabaseInstance, GuInstanceTypeParameter and GuAmiParameter now take an "app" prop

Does this change require changes to existing projects or CDK CLI?

Yes! There are numerous breaking changes in this PR!

How to test

See updated tests, aka observe CI.

How can we measure success?

It is possible to have a stack with multiple apps that are tagged correctly.

Have we considered potential risks?

@akash1810 akash1810 requested a review from a team March 16, 2021 22:43
@akash1810
Copy link
Member Author

PR to fix the build - #327.

constructor(scope: GuStack, id: string = "GetDistributablePolicy", props?: GuNoStatementsPolicyProps) {
const path = [scope.stack, scope.stage, scope.app, "*"].join("/");
super(scope, id, { ...props, bucketName: new GuDistributionBucketParameter(scope).valueAsString, paths: [path] });
// eslint-disable-next-line custom-rules/valid-constructors -- TODO be better
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😀

Copy link
Member Author

@akash1810 akash1810 Mar 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not sure if this is the best change as it means the constructor for the constructs take different forms and goes against this decision slightly.

Copy link
Contributor

@jacobwinch jacobwinch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work 💯

👍 once you've rebased / merged in #327!

@jacobwinch
Copy link
Contributor

I've tried to make each commit standalone, so it might be easier to review commit by commit?

Thanks for this suggestion, it was helpful!

@akash1810 akash1810 marked this pull request as ready for review March 17, 2021 14:04
withoutLogShipping?: boolean; // optional to have log shipping added by default, you have to opt out
additionalPolicies?: GuPolicy[];
}

export class GuInstanceRole extends GuRole {
private policies: GuPolicy[];

constructor(scope: GuStack, id: string = "InstanceRole", props?: GuInstanceRoleProps) {
super(scope, id, {
// eslint-disable-next-line custom-rules/valid-constructors -- TODO be better
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we are starting to regularly break this convention perhaps we should consider relaxing the linting rule?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely! I started on this a couple of weeks ago, but looks like I deleted my branch! 😱

I think the rules should be:

  • 1, 2 or 3 arguments
  • 1st argument is always GuStack
  • If only 2 arguments, 2nd argument is required and are props w/no default
  • If 3 arguments, 2nd is ID w/no default, 3rd are props w/no default and optional

Will raise this in a separate PR to see how it pans out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with the rules stated above!

Copy link
Member Author

@akash1810 akash1810 Mar 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#352 revises the linting rules.

Comment on lines +9 to +11
super(scope, AppIdentity.suffixText(props, "InstanceType"), {
type: "String",
description: "EC2 Instance Type",
description: `EC2 Instance Type for the app ${props.app}`,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the app "janus", the ID/name of this parameter becomes "InstanceTypeJanus". Hopefully the description is helpful?

},
addTag<T extends IConstruct>(appIdentity: AppIdentity, construct: T): T {
Tags.of(construct).add("App", appIdentity.app);
return construct;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this return type a little surprising for a function called addTag

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. It's mainly out of laziness as we can do this:

const snsTopic = props.existingSnsTopic?.externalTopicName
  ? Topic.fromTopicArn(
      scope,
      topicId,
      `arn:aws:sns:${scope.region}:${scope.account}:${props.existingSnsTopic.externalTopicName}`
    )
  : AppIdentity.addTag(
      props,
      new GuSnsTopic(scope, topicId, {
        overrideId: !!props.existingSnsTopic?.logicalIdFromCloudFormation,
      })
    );

The alternative is:

const newTopic = () => {
  const topic = new GuSnsTopic(scope, topicId, {
    overrideId: !!props.existingSnsTopic?.logicalIdFromCloudFormation,
  });

  AppIdentity.addTag(props, topic);
  return topic;
};

const snsTopic = props.existingSnsTopic?.externalTopicName
  ? Topic.fromTopicArn(
      scope,
      topicId,
      `arn:aws:sns:${scope.region}:${scope.account}:${props.existingSnsTopic.externalTopicName}`
    )
  : newTopic();

Maybe withTag is a better name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I see the benefit now. We could go for withTag or taggedResource, but it's not a big deal so feel free to ignore this!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to taggedConstruct to match the name of the parameter.

@akash1810 akash1810 merged commit 1bda3c1 into main Mar 17, 2021
@akash1810 akash1810 deleted the aa-app-tag branch March 17, 2021 15:00
@github-actions
Copy link
Contributor

🎉 This PR is included in version 3.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

akash1810 added a commit that referenced this pull request Mar 24, 2021
A `GuInstanceRole` provides the basic permissions needed by EC2, it grants access to:
* SSM
* Describing tags
* Writing to kinesis for log shipping

#326 started the work to support the declaration of multiple apps in a stack, where an app is a collection of ASG, LB etc. However, when actually attempting to use the updated constructs in reality, the synth step failed due to multiple constructs using the same ID.

This change follows #341 and #337 and converts `GuSSMRunCommandPolicy` and `GuDescribeEC2Policy` to singletons. This means these policies will only get created once regardless of how many roles use them - there will be one policy with multiple roles attached to it. This helps keep the stack DRY and also reduces the chance of reaching the max file size limits of cloudformation.

The commits have been crafted to be standalone, reviewing them one by one might make the overall review easier.

Requires #341.

BREAKING CHANGE:
* `GuSSMRunCommandPolicy` and `GuDescribeEC2Policy` can no longer be directly instantiated
akash1810 added a commit to guardian/prism that referenced this pull request Apr 14, 2021
Looks like some @guardian/cdk constructs are not applying the App tag. I suspect since guardian/cdk#326.

Until that is fixed, we can safely, manually apply it to all constructs in tree from `this` as it's a single app stack.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants