Skip to content

Commit aa4e4e6

Browse files
committed
docs improvements
1 parent 78f7c15 commit aa4e4e6

File tree

13 files changed

+570
-427
lines changed

13 files changed

+570
-427
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Stackattack provides a curated collection of high-level infrastructure components built on top of Pulumi. It allows you to deploy your applications on robust, secure infrastructure without giving up any control or spending days putting it together. Components are just functions, making them copy-paste friendly, so you can choose to use Stackattack as a library or just a set of working examples that you can take and modify for your own purposes.
44

5-
**[Get Started](https://stackattack.camfeenstra.com/getting-started/quick-start)** | **[Components](https://stackattack.camfeenstra.com/components)** | **[Examples](https://github.com/cfeenstra67/stackattack/tree/main/examples)**
5+
**[Get Started](https://stackattack.camfeenstra.com/getting-started/quick-start/)** | **[Components](https://stackattack.camfeenstra.com/components)** | **[Examples](https://github.com/cfeenstra67/stackattack/tree/main/examples)**
66

77
## What is Stackattack?
88

@@ -43,7 +43,7 @@ export default () => {
4343

4444
_NOTE_: While this example is meant to demonstrate how much you can do with Stackattack with a small amount of code, it is not recommended to structure your infrastructure code this way with everying in a single stack. See the [Structuring Stacks](https://stackattack.camfeenstra.com/working-with-pulumi/structuring-stacks/) section for recommendations on how separating your resources into stacks.
4545

46-
## Key Features
46+
## Features
4747

4848
- **Secure by Default** - All components are designed with secure defaults in mind, allowing you to get started quickly without worrying about security debt
4949
- **Copy/Paste Friendly** - Components are just functions, no heavy abstractions--you can copy/paste and modify them to fit your use-case. It's always easiest to start with something that works!

packages/aws/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
Stackattack provides a curated collection of high-level infrastructure components built on top of Pulumi. It allows you to deploy your applications on robust, secure infrastructure without giving up any control or spending days putting it together. Components are just functions, making them copy-paste friendly, so you can choose to use Stackattack as a library or just a set of working examples that you can take and modify for your own purposes.
44

5-
**[Get Started](https://stackattack.camfeenstra.com/getting-started/quick-start)** | **[Components](https://stackattack.camfeenstra.com/components)** | **[Examples](https://github.com/cfeenstra67/stackattack/tree/main/examples)**
5+
**[Get Started](https://stackattack.camfeenstra.com/getting-started/quick-start/)** | **[Components](https://stackattack.camfeenstra.com/components)** | **[Examples](https://github.com/cfeenstra67/stackattack/tree/main/examples)**
66

77
Check out the [project README](../../README.md) for information about using Stackattack.

packages/aws/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@stackattack/aws",
33
"type": "module",
44
"version": "0.5.1",
5-
"description": "Production-ready AWS components for Pulumi",
5+
"description": "High-level, production-ready AWS components for Pulumi",
66
"homepage": "https://stackattack.camfeenstra.com",
77
"repository": "github:cfeenstra67/stackattack",
88
"main": "./dist/cjs/index.js",

packages/aws/src/components/cluster.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
/**
22
* @packageDocumentation
33
*
4-
* ECS clusters in AWS provide compute capacity for running containerized applications. Stackattack creates ECS clusters with:
4+
* ECS clusters in AWS provide compute capacity for running containerized applications. Stackattack's `cluster` component provides an easy way to set up working ECS clusters for running applications on EC2 instances with auto-scaling and private inter-service communication.
5+
*
6+
* Stackattack creates ECS clusters with:
57
* - EC2 instances used for compute. By default, the configuration will use spot instances. Pass `noSpot: true` to disable spot instances, or `onDemandPercentage` with a percentage value to split your EC2 instances between on demand and spot. The number of instances will always match the requirements of your cluster (within the constraints you set via `minSize` (default 0) and `maxSize` (default 1)). Currently Fargate is not supported.
68
* - A private DNS namespace is created so that your services can communicate internally via ECS service discovery. ECS service connect is currently not supported.
79
*

packages/aws/src/components/vpc.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ export function vpcFlowLogs(ctx: Context, args: VPCFlowLogsArgs) {
600600

601601
const role = vpcFlowLogsRole(ctx, { logGroup });
602602

603-
new aws.ec2.FlowLog(ctx.id(), {
603+
return new aws.ec2.FlowLog(ctx.id(), {
604604
iamRoleArn: role.arn,
605605
logDestination: logGroup.arn,
606606
trafficType: "ALL",
@@ -652,11 +652,16 @@ export interface NetworkInput {
652652
*/
653653
export type NetworkType = "public" | "private";
654654

655-
interface VpcOutput {
655+
export interface VpcOutput {
656+
/** The created VPC object */
656657
vpc: aws.ec2.Vpc;
658+
/** The public subnet IDs created in the VPC */
657659
publicSubnetIds: pulumi.Output<string>[];
660+
/** The private subnet IDs created in the VPC */
658661
privateSubnetIds: pulumi.Output<string>[];
659-
network: (type: NetworkType) => Network;
662+
/** Method to get a `Network`, which is a VPC and a set of subnets. `type` should be "public" to choose public subnet IDs, and "private" to choose private ones. You can optionally pass a number of `azs` to limit the number of availability zones that you want to include subnets from */
663+
network: (type: NetworkType, azs?: number) => Network;
664+
/** The `cidrAllocator` provides a way to allocate new CIDR blocks within the vpc for subnets or other purposes, given a netmask. */
660665
cidrAllocator: CidrAllocator;
661666
}
662667

@@ -719,10 +724,12 @@ export function vpc(ctx: Context, args?: VpcArgs): VpcOutput {
719724
publicSubnetIds,
720725
privateSubnetIds,
721726
cidrAllocator: allocator,
722-
network: (type) => {
727+
network: (type, azs) => {
728+
const subnetIds = type === "private" ? privateSubnetIds : publicSubnetIds;
729+
723730
return {
724731
vpc,
725-
subnetIds: type === "private" ? privateSubnetIds : publicSubnetIds,
732+
subnetIds: azs === undefined ? subnetIds : subnetIds.slice(0, azs),
726733
};
727734
},
728735
};
@@ -773,11 +780,16 @@ export function vpcFromIds(vpcInput: pulumi.Input<VpcIds>, increment?: number) {
773780
attrs.cidrBlock,
774781
vpc.counter.apply((c) => c + (increment ?? 0)),
775782
),
776-
network: (type: NetworkType) => {
783+
network: (type: NetworkType, azs?: number) => {
784+
const subnetIds =
785+
type === "private" ? vpc.privateSubnetIds : vpc.publicSubnetIds;
786+
777787
return {
778788
vpc: attrs,
779789
subnetIds:
780-
type === "private" ? vpc.privateSubnetIds : vpc.publicSubnetIds,
790+
azs === undefined
791+
? subnetIds
792+
: subnetIds.apply((ids) => ids.slice(0, azs)),
781793
};
782794
},
783795
};

packages/aws/src/context.ts

Lines changed: 120 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,107 @@
1-
import * as crypto from "node:crypto";
1+
/**
2+
* @packageDocumentation
3+
*
4+
* The `Context` is a foundational piece of Stackattack. It provides a consistent way to name, tag, and organize your infrastructure resources.
5+
*
6+
* ## What is a context?
7+
* A Context is an object that encapsulates:
8+
* - **Resource naming patterns** - Consistent prefixes and naming conventions
9+
* - **Tags** - Common tags applied to all resources
10+
* - **Hierarchical organization** - Ability to create nested contexts for different parts of your infrastructure
11+
*
12+
* The reasoning for having a context is quite simple: it allows you to abstract groups of components into functions without name collisions. For example, without a context, if you write a function like the following:
13+
* ```typescript
14+
* import * as aws from '@pulumi/aws';
15+
* import * as pulumi from '@pulumi/pulumi';
16+
*
17+
* interface DnsRecordArgs {
18+
* name: pulumi.Input<string>;
19+
* zoneId: pulumi.Input<string>;
20+
* ip: pulumi.Input<string>;
21+
* }
22+
*
23+
* function dnsRecord({ name, zoneId, ip }: DnsRecordArgs) {
24+
* return new aws.route53.Record("record", {
25+
* name,
26+
* zoneId,
27+
* type: "A",
28+
* ttl: 300,
29+
* records: [ip]
30+
* });
31+
* }
32+
* ```
33+
* It will not work to use it multiple times in a single stack, for example:
34+
* ```ts
35+
* const zoneId = aws.route53.getZoneOutput({ name: "mydomain.com" }).id;
36+
* const ip1 = "71.112.12.111";
37+
* const ip2 = "32.112.43.22";
38+
*
39+
* const record1 = dnsRecord({ name: "server1.mydomain.com", zoneId, ip: ip1 });
40+
* const record2 = dnsRecord({ name: "server2.mydomain", zoneId, ip: ip2 });
41+
* ```
42+
* This code will fail when you run `pulumi up`, because you end up with two `aws.route53.Record` resources with the name "record". You can mitigate this by, for example, passing a prefix to `dnsRecord`, but stackattack's `context` provides a simple, clean way to do this in a consistent manner.
43+
*
44+
* ## Creating a Context
45+
*
46+
* For typical usage, you should simply instantiate a context without any arguments:
47+
*
48+
* ```typescript
49+
* import * as saws from "@stackattack/aws";
50+
*
51+
* const ctx = saws.context();
52+
* ```
53+
* By default, this context will generate a prefix and default tags based on your project and stack name. This approach works well because your resources will be clearly distinguishable by name in the AWS console, and the tags will make it easy to distinguish what stack and project resources belong to.
54+
*
55+
* If you have more specific needs, you can create a context with a custom prefix and/or tags:
56+
*
57+
* ```typescript
58+
* const ctx = saws.context({
59+
* prefix: "my-app",
60+
* tags: {
61+
* Environment: "production",
62+
* Team: "platform",
63+
* Project: "web-service"
64+
* }
65+
* });
66+
* ```
67+
*
68+
* ## Using Contexts
69+
*
70+
* Every Stackattack component takes a Context as its first parameter:
71+
*
72+
* ```typescript
73+
* const storage = saws.bucket(ctx, {
74+
* versioned: true,
75+
* });
76+
*
77+
* const vpc = saws.vpc(ctx);
78+
* ```
79+
*
80+
* You can create nested contexts for different parts of your infrastructure:
81+
*
82+
* ```typescript
83+
* const ctx = saws.context();
84+
*
85+
* // Each will have appropriate naming: my-app-storage-*, my-app-database-*
86+
* const s3 = saws.bucket(ctx.prefix("storage"), { versioned: true });
87+
* const db = saws.database(ctx.prefix("database"), { network: vpc.network("private"), engine: "postgres" });
88+
* ```
89+
*
90+
* _NOTE_: all Stackattack components add default prefixes to the context you pass in by default, so it's never _necessary_ to use `.prefix` unless you're creating multiple instances of a single component with the same context. All components also take `noPrefix: true` to disable to default prefixing behavior.
91+
*
92+
* ## Adding Tags
93+
*
94+
* You can add additional tags to a context:
95+
*
96+
* ```typescript
97+
* const baseCtx = saws.context();
98+
*
99+
* const prodCtx = baseCtx.withTags({
100+
* Environment: "production",
101+
* CostCenter: "engineering"
102+
* });
103+
* ```
104+
*/
2105
import * as pulumi from "@pulumi/pulumi";
3106

4107
/**
@@ -7,8 +110,6 @@ import * as pulumi from "@pulumi/pulumi";
7110
export interface Context {
8111
/** Generates a resource ID by combining the context prefix with an optional value */
9112
id: (value?: string) => string;
10-
/** Generates a short ID with a hash suffix for uniqueness */
11-
shortId: (value: string) => string;
12113
/** Returns merged tags combining context tags with optional additional tags */
13114
tags: (others?: Record<string, string>) => Record<string, string>;
14115
/** Creates a new Context with an extended prefix */
@@ -21,13 +122,25 @@ export interface Context {
21122
* Configuration options for creating a Context.
22123
*/
23124
export interface ContextOpts {
24-
/** Optional prefix for resource naming (defaults to project-stack combination) */
125+
/** Optional prefix for resource naming (defaults to project-stack combination). Defaults to `<project>-<stack>`, unless `stack` begins with `project` (e.g. project is named `api` and stack is named `api-prod`), in which case the default will just be `stack` */
25126
prefix?: string | null;
26-
/** Optional tags to apply to all resources created with this context */
127+
/** Optional tags to apply to all resources created with this context. Defaults to `{ Source: "pulumi", Project: <project>, Stack: <stack> }` */
27128
tags?: Record<string, string>;
28129
}
29130

30-
function defaultContextPrefix(): string {
131+
/** Generates a default set of context tags based on the current project and stack, plus a `Source: "pulumi"` tag. */
132+
export function defaultContextTags(): Record<string, string> {
133+
const project = pulumi.getProject();
134+
const stack = pulumi.getStack();
135+
return {
136+
Source: "pulumi",
137+
Project: project,
138+
Stack: stack,
139+
};
140+
}
141+
142+
/** Generates a default prefix based on the current project and stack. Defaults to `<project>-<stack>`, unless `stack` begins with `project` (e.g. project is named `api` and stack is named `api-prod`), in which case the default will just be `stack` */
143+
export function defaultContextPrefix(): string {
31144
const project = pulumi.getProject();
32145
const stack = pulumi.getStack();
33146
return stack.startsWith(project) ? stack : `${project}-${stack}`;
@@ -46,25 +159,15 @@ export function context(opts?: ContextOpts): Context {
46159
prefix = defaultContextPrefix();
47160
}
48161

49-
const tagsObj = opts?.tags ?? {};
162+
const tagsObj = opts?.tags ?? defaultContextTags();
50163

51164
const id = (value?: string) =>
52165
[prefix, value].filter((v) => v !== undefined && v !== null).join("-");
53166

54-
const shortId = (value: string) => {
55-
const hashVal = crypto
56-
.createHash("sha1")
57-
.update(id(value))
58-
.digest("hex")
59-
.slice(0, 6);
60-
return `${value}-${hashVal}`;
61-
};
62-
63167
const tags: Context["tags"] = (others) => ({ ...tagsObj, ...others });
64168

65169
return {
66170
id,
67-
shortId,
68171
tags,
69172
prefix: (value) => context({ prefix: id(value), tags: tagsObj }),
70173
withTags: (others) => context({ prefix, tags: tags(others) }),

packages/aws/src/select.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
* pulumi up
5959
* ```
6060
*
61-
* This pattern enables you to deploy shared infrastructure separately from applications, allowing for faster deployments and better isolation. See the [Structuring Stacks](/working-with-pulumi/structuring-stacks) guide for recommendations on separating your resources into stacks.
61+
* This pattern enables you to deploy shared infrastructure separately from applications, allowing for faster deployments and better isolation. See the [Structuring Stacks](/working-with-pulumi/structuring-stacks/) guide for recommendations on separating your resources into stacks.
6262
*/
6363

6464
import * as pulumi from "@pulumi/pulumi";

packages/aws/src/stack-ref.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
* pulumi up
5656
* ```
5757
*
58-
* The function parameter serves as a type template - it defines the shape of the referenced stack's outputs without being executed. This enables full TypeScript IntelliSense and type checking for cross-stack references. See the [Structuring Stacks](/working-with-pulumi/structuring-stacks) guide for comprehensive multi-stack patterns.
58+
* The function parameter serves as a type template - it defines the shape of the referenced stack's outputs without being executed. This enables full TypeScript IntelliSense and type checking for cross-stack references. See the [Structuring Stacks](/working-with-pulumi/structuring-stacks/) guide for comprehensive multi-stack patterns.
5959
*/
6060

6161
import * as pulumi from "@pulumi/pulumi";

packages/docs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
src/content/docs/components/
33
src/content/docs/components.md
44
src/content/docs/utilities/
5+
src/content/docs/concepts/
56
/dist

packages/docs/astro.config.mjs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default defineConfig({
99
site: 'https://stackattack.camfeenstra.com',
1010
integrations: [starlight({
1111
title: 'Stackattack',
12-
description: 'Production-ready AWS infrastructure components for Pulumi - Deploy secure, scalable applications with minimal code',
12+
description: 'High-level, production-ready AWS infrastructure components for Pulumi - Deploy secure, scalable applications with minimal code',
1313
favicon: '/favicon.ico',
1414
logo: {
1515
src: './src/assets/logo.svg',
@@ -47,9 +47,7 @@ export default defineConfig({
4747
},
4848
{
4949
label: 'Concepts',
50-
items: [
51-
{ label: 'Context', link: '/concepts/context/' },
52-
],
50+
autogenerate: { directory: 'concepts' }
5351
},
5452
{
5553
label: 'Components',

0 commit comments

Comments
 (0)