0.36.4 • Published 3 years ago

@myhelix-cdk/core v0.36.4

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
3 years ago

core

These are constructs which every Helix project should be using.

Stack

Helix super-classes the Stack and StackProps classes. The @myhelix-ckd/core.Stack class provides a number of getter methods. For example, vpc and rdsSharedSecurityGroup both return appropriate objects. It also ensures that everything we deploy is tagged with accounting metadata tags. Because the accounting tags come from an enum, it ensures compliance with our accounting requirements. Finally, it enforces the use of NamedEnvs.

Environment and NamedEnvs

CDK has the concept of an env, which is the combination of an account and a region. CDK deploys stacks into envs.

myStack(app, 'myStackName', {
  env: {
    account: '123412341234',
    region: 'us-east-1',
  },
});

Our devs don't want to keep track of all the different account ids and regions. Having what amounts to a constant spread of magic numbers across dozens of CDK applications in dozens of repos sounded like a bad idea. Instead, we centralized this information. At Helix, we use a NamedEnv, which includes the required account and region. We realized that this would be a great place to put other environment specific data, too.

export interface NamedEnv extends cdk.Environment {
  /**
   * The kebab-name of the environment.
   */
  readonly name: string;
  /**
   * What kind of an account is this?
   */
  readonly organizationalUnit: OrganizationalUnit;
  /**
   * Is it a production or non-production environment?
   * @deprecated use organizationalUnit instead
   */
  readonly isProduction: boolean;
  /**
   * The securityGroupId for the shared RDS SG.
   */
  readonly rdsSharedSecurityGroupId: string;
  /**
   * The vpc cidr for the environmet.
   */
  readonly cidr: string;
  /**
   * The DNS zone into which services should be deployed.
   */
  readonly zoneName: string;
}

There's a lot of information there. Much of it can be derived from, of all things, the name. Strong naming conventions are a wonderful thing. So, we built a factory function.

export interface NamedEnvironmentProps {
  /**
   * The numeric account id as used by cdk.Environment.account
   */
  readonly account: string;
  /**
   * The region as used by cdk.Environment.region
   *
   * @default 'us-east-1'
   */
  readonly region?: string;
  /**
   * The proper name of the environment in kebab-format.
   */
  readonly name: string;
  /**
   * What kind of an account is this?
   * @default 'pulled from the name'
   */
  readonly organizationalUnit?: OrganizationalUnit;
  /**
   * The SgId of the RDS security group.
   */
  readonly rdsSharedSecurityGroupId: string;
  /**
   * The cidr in '0.0.0.0/32' format.
   */
  readonly cidr: string;
}

function newNamedEnv(props: NamedEnvironmentProps): NamedEnv {
  /* eslint-disable prettier/prettier */
  const organizationalUnit = props.organizationalUnit
    ? props.organizationalUnit
    : props.name.includes('production')
      ? OrganizationalUnit.PRODUCTION
      : props.name.includes('staging')
        ? OrganizationalUnit.STAGING
        : props.name.includes('development')
          ? OrganizationalUnit.DEVELOPMENT
          : props.name.includes('test')
            ? OrganizationalUnit.DEVELOPMENT
            : null;
  /* eslint-enable prettier/prettier */

  if (organizationalUnit == null) {
    throw new Error('Can not figure out OrganizationalUnit, please provide one');
  }

  return {
    account: props.account,
    name: props.name,
    rdsSharedSecurityGroupId: props.rdsSharedSecurityGroupId,
    cidr: props.cidr,

    region: props.region ? props.region : 'us-east-1',

    /* We derive a LOT of things based on the name */
    isProduction: props.name.includes('production'),
    organizationalUnit: organizationalUnit as OrganizationalUnit,
    /* eslint-disable prettier/prettier */
    zoneName: props.name.includes('production') ? 'helix.com'
      : props.name.includes('staging') ? 'staging.helix.com'
        : props.name.includes('development') ? 'development.helix.com'
          : props.name + '.helix.com',
    /* eslint-enable prettier/prettier */
  };
}

Now it is simply a matter of stamping out environments as statics in the Environment class. We are using statics rather than declaring consts directly because we export this module using JSII to both typescript and python.

export class Environment {
  public static readonly Ci: NamedEnv = newNamedEnv({
    name: 'ci',
    account: '123412341234',
    rdsSharedSecurityGroupId: '',
    organizationalUnit: OrganizationalUnit.DEVELOPMENT,
    cidr: '0.0.0.0/32',
  });

  public static readonly HipaaStaging: NamedEnv = newNamedEnv({
    name: 'hipaa-staging',
    account: '123412341234',
    rdsSharedSecurityGroupId: 'sg-12341234123412341',
    cidr: '0.0.0.0/32',                             .
  });

  public static readonly HipaaProduction: NamedEnv = newNamedEnv({
    name: 'hipaa-production',
    account: '123412341234',
    rdsSharedSecurityGroupId: 'sg-12341234123412341',
    cidr: '0.0.0.0/32',
  });

  public static readonly PlatformDevelopment: NamedEnv = newNamedEnv({
    name: 'platform-development',
    account: '123412341234',
    rdsSharedSecurityGroupId: 'sg-12341234123412341',
    cidr: '0.0.0.0/32',
  });
  // ... and many more
}

Deploying a stack using NamedEnvs makes it clear where the stack is going:

import cdk = require('@aws-cdk/core');
import core = require('@myhelix-cdk/core');
const app = cdk.App();
myStack(app, 'stackName', {
  namedEnv: core.Environment.HipaaStaging,
});

Stacks can then leverage the additional metadata. For example, our Aurora stack cares a lot about prod vs non-prod.

const keyRemovalPolicy =
  props.namedEnv.organizationalUnit == core.OrganizationalUnit.PRODUCTION
    ? cdk.RemovalPolicy.RETAIN
    : cdk.RemovalPolicy.DESTROY

const instances = props.instances
  ? props.instances
  : props.namedEnv.organizationalUnit == core.OrganizationalUnit.PRODUCTION
    ? 2
    : 1;

We don't just publish Constructs and Stacks which leverage NamedEnv, we also have entire Apps which will deploy sets of stacks to sets of environments.

export enum DeployTarget {
  RED = 'red', // AKA hipaa
  ORANGE = 'orange',
}

export interface DeployEnv {
  readonly env: core.NamedEnv;
  // ... other properties
}

export interface DeployEnvs {
  readonly development: DeployEnv;
  readonly staging: DeployEnv;
  readonly production: DeployEnv;
}

export interface ExampleAppProps {
  /**
   * What sub-accounts should it be deployed into?
   */
  readonly deployTarget: DeployTarget;
  // ... other properties
}

export class ExampleApp extends cdk.App {
  deployEnvs: DeployEnvs;

  constructor(props: ExampleAppProps) {
    this.deployEnvs =
      props.deployTarget == DeployTarget.RED
        ? {
          development: {
            env: core.Environment.PlatformDevelopment,
            ...
          },
          staging: {
            env: core.Environment.HipaaStaging,
            ...
          },
          production: {
            env: core.Environment.HipaaProduction,
            ...
          },
        } : {
          development: {
            env: core.Environment.PlatformDevelopment,
            ...
          },
          staging: {
            env: core.Environment.PlatformStaging,
            ...
          },
          production: {
            env: core.Environment.PlatformProduction,
            ...
          },
        };

    // Stacks that are deployed in each deploy env:
    Object.values(this.deployEnvs).forEach((deployEnv: DeployEnv) => {
      const auroraStack =
        deployEnv.env.organizationalUnit == core.OrganizationalUnit.DEVELOPMENT
          ? new SharedTennancyAuroraWithProxy(this, '...', {
            namedEnv: deployEnv.env,
            // ...
          })
          : new DedicatedAuroraWithProxy(this, '...', {
            namedEnv: deployEnv.env,
            // ...
          });
      new SomeLambda(this, '...', {
        namedEnv: deployEnv.env,
        dbCluster: auroraStack.cluster,
        // ...
      };)
    };

    // stacks that are deployed only once
    new SomePipeline(this, 'foo-pipeline', {
      namedEnv: core.Environment.CI,
      // ...
    });
  }
}
0.36.4

3 years ago

1.77.0

3 years ago

0.35.1-alpha.22

3 years ago

0.36.3

3 years ago

0.36.2

3 years ago

0.36.1

3 years ago

0.36.0

3 years ago

0.35.0

3 years ago

0.34.2

4 years ago

0.34.1

4 years ago

0.34.0

4 years ago

0.33.0

4 years ago

0.32.1

4 years ago

0.32.0

4 years ago

0.31.0

4 years ago

0.30.1

4 years ago

0.30.0

4 years ago

0.29.0

4 years ago

0.28.1

4 years ago

0.28.2

4 years ago

0.28.0

4 years ago

0.27.0

4 years ago

0.26.1

4 years ago

0.26.0

4 years ago

0.25.0

4 years ago

0.24.1

4 years ago

0.24.0

4 years ago

0.23.0

4 years ago

0.22.0

4 years ago

0.21.0

4 years ago

0.20.1

4 years ago

0.20.0

4 years ago

0.19.2

4 years ago

0.19.0

4 years ago

0.19.1

4 years ago

0.18.0

4 years ago

0.17.0

4 years ago

0.16.1

4 years ago

0.16.0

4 years ago

0.15.0

4 years ago

0.14.0

4 years ago

0.13.0

4 years ago

0.12.0

4 years ago

0.12.1

4 years ago

0.12.2

4 years ago

0.11.0

4 years ago

0.10.0

4 years ago

0.10.1

4 years ago

0.9.2

4 years ago

0.9.1

4 years ago

0.8.0

4 years ago

0.7.0

4 years ago

0.6.4

4 years ago

0.6.3

4 years ago

0.6.2

4 years ago

0.6.1

4 years ago

0.6.0

4 years ago

0.5.1

4 years ago

0.5.0

4 years ago

0.4.0

4 years ago

0.3.0

4 years ago

0.2.0

4 years ago