declastruct-aws
v1.8.0
Published
declarative control of Aws constructs via declastruct
Maintainers
Readme
declastruct-aws
Declarative control of Aws resource constructs, via declastruct.
Declare the structures you want. Plan to see the changes required. Apply to make it so 🪄
install
npm install -s declastruct-awsuse via cli
example.1
wish ✨
declare the resources you wish to have - and what state you wish them to be in
import { UnexpectedCodePathError } from 'helpful-errors';
export const getProviders = async (): Promise<DeclastructProvider[]> => [
getDeclastructAwsProvider(
{},
{
log: console,
},
),
];
export const getResources = async (): Promise<DomainEntity<any>[]> => {
// declare the resources you wish to construct
};plan 🔮
plan how to achieve the wish of resources you've declared
this will emit a plan that declares the changes required in order to fulfill the wish
npx declastruct plan --wish provision/github/resources.ts --output provision/github/.temp/plan.jsonapply 🪄
apply the plan to fulfill the wish
this will apply only the changes declared in the plan - and only if this plan is still applicable
npx declastruct apply --plan provision/github/.temp/plan.jsonexample.2 = open a vpc tunnel via an ec2 instance
import { RefByUnique } from 'domain-objects';
import { getDeclastructAwsProvider, DeclaredAwsRdsCluster, DeclaredAwsEc2Instance, DeclaredAwsSsmVpcTunnel } from 'declastruct-aws';
export const getProviders = async (): Promise<DeclastructProvider[]> => [
getDeclastructAwsProvider(
{},
{
log: console,
},
),
];
export const getResources = async (): Promise<DomainEntity<any>[]> => {
const cluster = RefByUnique.as<typeof DeclaredAwsRdsCluster>({
name: 'yourdb',
});
const bastion = RefByUnique.as<typeof DeclaredAwsEc2Instance>({
exid: 'vpc-main-bastion',
})
const tunnel = DeclaredAwsSsmVpcTunnel.as({
via: { mechanism: 'aws.ssm', bastion }
into: { cluster },
from: { host: 'localhost', port: 777_5432 },
status: "OPEN",
})
return [tunnel];
};example.3 = open an ssh tunnel to an ec2 instance
import * as fs from 'fs';
import { RefByUnique } from 'domain-objects';
import {
getDeclastructAwsProvider,
DeclaredAwsEc2Instance,
DeclaredAwsEc2SshKeyAuthorized,
DeclaredAwsSsmSshTunnel,
} from 'declastruct-aws';
export const getProviders = async (): Promise<DeclastructProvider[]> => [
getDeclastructAwsProvider(
{},
{
log: console,
},
),
];
export const getResources = async (): Promise<DomainEntity<any>[]> => {
const instance = RefByUnique.as<typeof DeclaredAwsEc2Instance>({
exid: 'my-dev-instance',
});
// authorize your SSH key on the instance
const sshKey = DeclaredAwsEc2SshKeyAuthorized.as({
instance,
publicKey: fs.readFileSync(`${process.env.HOME}/.ssh/id_ed25519.pub`, 'utf8'),
comment: 'my-laptop',
});
// open SSH tunnel via SSM
const sshTunnel = DeclaredAwsSsmSshTunnel.as({
instance,
from: { port: 2222 },
into: { port: 22 },
status: 'OPEN',
});
return [sshKey, sshTunnel];
};after npx declastruct apply, SSH in:
ssh -i ~/.ssh/id_ed25519 -p 2222 ec2-user@localhostexample.4 = provision an ec2 instance with hibernation
import { RefByUnique } from 'domain-objects';
import {
getDeclastructAwsProvider,
DeclaredAwsEc2LaunchTemplate,
DeclaredAwsEc2Instance,
DeclaredAwsEc2InstanceSession,
DeclaredAwsVpcSubnet,
DeclaredAwsVpcSecurityGroup,
} from 'declastruct-aws';
export const getProviders = async (): Promise<DeclastructProvider[]> => [
getDeclastructAwsProvider(
{},
{
log: console,
},
),
];
export const getResources = async (): Promise<DomainEntity<any>[]> => {
// declare launch template with hibernation enabled
const template = DeclaredAwsEc2LaunchTemplate.as({
exid: 'my-dev-template',
instanceType: 't3.micro',
imageId: 'ami-0c55b159cbfafe1f0', // Amazon Linux 2023
hibernation: true,
rootVolumeEncrypted: true, // required for hibernation
rootVolumeSize: 16, // must be >= instance RAM
iamInstanceProfile: null,
userData: null,
tags: { purpose: 'dev' },
});
// declare instance
const instance = DeclaredAwsEc2Instance.as({
exid: 'my-dev-instance',
template: { exid: template.exid },
subnet: RefByUnique.as<typeof DeclaredAwsVpcSubnet>({ exid: 'my-subnet' }),
securityGroups: [RefByUnique.as<typeof DeclaredAwsVpcSecurityGroup>({ exid: 'my-sg' })],
tags: { purpose: 'dev' },
});
// control lifecycle via session
const session = DeclaredAwsEc2InstanceSession.as({
instance: { exid: instance.exid },
status: 'active', // 'active' | 'stopped' | 'hibernated'
});
return [template, instance, session];
};to hibernate the instance, change status: 'hibernated' and re-apply:
npx declastruct plan --wish resources.ts --into plan.json
npx declastruct apply --plan plan.jsonexample.5 = deploy a lambda with version and alias
import { RefByUnique } from 'domain-objects';
import { ConstraintError } from 'helpful-errors';
import {
calcAwsLambdaConfigHash,
DeclaredAwsIamRole,
DeclaredAwsIamRolePolicyAttachedInline,
DeclaredAwsLambda,
DeclaredAwsLambdaAlias,
DeclaredAwsLambdaVersion,
genDeclaredAwsLambdaCode,
getDeclastructAwsProvider,
} from 'declastruct-aws';
export const getProviders = async (): Promise<DeclastructProvider[]> => [
getDeclastructAwsProvider(
{},
{
log: console,
},
),
];
export const getResources = async (): Promise<DomainEntity<any>[]> => {
// declare iam role for lambda execution
const lambdaRole = DeclaredAwsIamRole.as({
name: 'my-lambda-role',
path: '/',
description: 'Role for lambda execution',
policies: [
{
effect: 'Allow',
principal: { service: 'lambda.amazonaws.com' },
action: 'sts:AssumeRole',
},
],
tags: { managedBy: 'declastruct' },
});
// declare inline policy for CloudWatch Logs permissions
const lambdaRolePolicy = DeclaredAwsIamRolePolicyAttachedInline.as({
name: 'cloudwatch-logs',
role: RefByUnique.as<typeof DeclaredAwsIamRole>(lambdaRole),
document: {
statements: [
{
effect: 'Allow',
action: [
'logs:CreateLogGroup',
'logs:CreateLogStream',
'logs:PutLogEvents',
],
resource: '*',
},
],
},
});
// declare lambda function ($LATEST) with code from zip
const lambda = DeclaredAwsLambda.as({
name: 'svc-sea-turtle.prod.getSandbars',
runtime: 'nodejs18.x',
handler: 'index.handler',
timeout: 30,
memory: 128,
role: RefByUnique.as<typeof DeclaredAwsIamRole>(lambdaRole),
envars: { NODE_ENV: 'production' },
code: genDeclaredAwsLambdaCode({ zipUri: '.artifact/contents.zip' }),
tags: { managedBy: 'declastruct' },
});
// publish immutable version (fingerprinted by code + config hash)
const lambdaVersion = DeclaredAwsLambdaVersion.as({
lambda: RefByUnique.as<typeof DeclaredAwsLambda>(lambda),
hash: {
code: lambda.code?.hash ?? ConstraintError.throw('lambda.code.hash is required'),
config: calcAwsLambdaConfigHash({ of: lambda }),
},
});
// point LIVE alias to this version
const lambdaAlias = DeclaredAwsLambdaAlias.as({
name: 'LIVE',
lambda: RefByUnique.as<typeof DeclaredAwsLambda>(lambda),
version: RefByUnique.as<typeof DeclaredAwsLambdaVersion>(lambdaVersion),
description: 'live traffic alias',
});
return [lambdaRole, lambdaRolePolicy, lambda, lambdaVersion, lambdaAlias];
};this pattern enables:
- change detection:
code.hashenables declastruct to detect when code changed and deploy only when needed - immutable versions: each deploy publishes a new version fingerprinted by
hash: { code, config } - aliased endpoints: invoke via
function:LIVEfor stable endpoints across deploys - safe rollbacks: retarget aliases to previous versions without redeploy
- canary deploys: use
routingConfig.additionalVersionWeightsto split traffic between versions
