Skip to content

Commit f5bd9ef

Browse files
committed
updates
1 parent bab5b69 commit f5bd9ef

File tree

4 files changed

+275
-6
lines changed

4 files changed

+275
-6
lines changed

.github/workflows/release.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ jobs:
3030
cache: 'pnpm'
3131
- name: Install dependencies
3232
run: pnpm install --frozen-lockfile
33-
- uses: changesets/action@v1
33+
- name: Create Release Pull Request
34+
uses: changesets/action@v1
3435
env:
3536
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3637
- id: flag
@@ -97,7 +98,7 @@ jobs:
9798
run: pnpm -r build
9899
- name: Copy root README to aws
99100
run: cp README.md packages/aws/README.md
100-
- name: Create Release Pull Request
101+
- name: Publish Packages
101102
uses: changesets/action@v1
102103
with:
103104
publish: pnpm release

packages/aws/src/components/cluster.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ export interface ClusterCapacityConfig {
478478
onDemandBase?: number;
479479
/** Percentage of on-demand instances above base capacity */
480480
onDemandPercentage?: number;
481-
/** Strategy for allocating spot instances */
481+
/** Strategy for allocating spot instances; defaults to `price-capacity-optimized`. */
482482
spotAllocationStrategy?: string;
483483
/** Security group ID that should be allowed SSH access to the instances */
484484
sourceSecurityGroupId?: pulumi.Input<string>;
@@ -670,6 +670,7 @@ export function clusterCapacity(ctx: Context, args: ClusterCapacityArgs) {
670670
{
671671
vpcZoneIdentifiers: args.network.subnetIds,
672672
minSize: sizes.min,
673+
desiredCapacity: sizes.min,
673674
maxSize: sizes.max,
674675
mixedInstancesPolicy: {
675676
instancesDistribution: {
@@ -678,7 +679,7 @@ export function clusterCapacity(ctx: Context, args: ClusterCapacityArgs) {
678679
? 100
679680
: (args.onDemandPercentage ?? 0),
680681
spotAllocationStrategy:
681-
args.spotAllocationStrategy ?? "capacity-optimized",
682+
args.spotAllocationStrategy ?? "price-capacity-optimized",
682683
},
683684
launchTemplate: {
684685
launchTemplateSpecification: {
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/**
2+
* @packageDocumentation
3+
*
4+
* Load balancers in AWS write access logs to an S3 bucket. This component can be used to create an AWS Glue table configured to query those logs with Athena. This component can be used on its own or automatically created by passing `accessLogsTable` parameter to the {@link loadBalancer} component.
5+
*
6+
* ```typescript
7+
* import * as saws from '@stackattack/aws';
8+
*
9+
* const ctx = saws.context();
10+
* const vpc = saws.vpc(ctx);
11+
* const logsBucket = saws.bucket(ctx);
12+
* const loadBalancer = saws.loadBalancer(ctx, {
13+
* network: vpc.network("public")
14+
* });
15+
*
16+
* ```
17+
*
18+
*/
19+
20+
import * as aws from "@pulumi/aws";
21+
import * as pulumi from "@pulumi/pulumi";
22+
import type { Context } from "../context.js";
23+
import { type BucketInput, getBucketId } from "./bucket.js";
24+
25+
/**
26+
* Configuration options for create a load balancer logs table
27+
*/
28+
export interface LoadBalancerLogsTableArgs {
29+
/** Name of the table used for query logs */
30+
name: pulumi.Input<string>;
31+
/** Database name to create the table in; default to "default" */
32+
database?: pulumi.Input<string>;
33+
/** Bucket where load balancer logs are stored */
34+
bucket: pulumi.Input<BucketInput>;
35+
/** Prefix within the bucket where load balancer logs are stored. Note that all load balancer logs are stored with a prefix like "AWSLogs/<account_id>/elasticloadbalancing/<region>/". That will be appended automatically--if specified this should only include the prefix _before_ "AWSLogs/...". */
36+
prefix?: pulumi.Input<string>;
37+
/** Whether to skip adding a prefix to the context */
38+
noPrefix?: boolean;
39+
}
40+
41+
export function loadBalancerLogsTable(
42+
ctx: Context,
43+
args: LoadBalancerLogsTableArgs,
44+
): aws.glue.CatalogTable {
45+
if (!args.noPrefix) {
46+
ctx = ctx.prefix("lb-logs-table");
47+
}
48+
49+
const region = aws.getRegionOutput();
50+
const identity = aws.getCallerIdentityOutput();
51+
const bucketName = getBucketId(args.bucket);
52+
53+
const prefix = pulumi
54+
.output(args.prefix)
55+
.apply((prefix) =>
56+
prefix ? (prefix.endsWith("/") ? prefix : `${prefix}/`) : "",
57+
);
58+
59+
const accessLogsStorageLocation = pulumi.interpolate`s3://${bucketName}/${prefix}/AWSLogs/${identity.accountId}/elasticloadbalancing/${region.name}/`;
60+
61+
// Based on instructions here:
62+
// https://docs.aws.amazon.com/athena/latest/ug/application-load-balancer-logs.html#create-alb-table-partition-projection
63+
return new aws.glue.CatalogTable(ctx.id(), {
64+
databaseName: args.database ?? "default",
65+
name: args.name,
66+
tableType: "EXTERNAL_TABLE",
67+
parameters: {
68+
EXTERNAL: "TRUE",
69+
"projection.enabled": "true",
70+
"projection.day.type": "date",
71+
"projection.day.range": "2024/01/01,NOW",
72+
"projection.day.format": "yyyy/MM/dd",
73+
"projection.day.interval": "1",
74+
"projection.day.interval.unit": "DAYS",
75+
"storage.location.template": pulumi.interpolate`${accessLogsStorageLocation}\${day}`,
76+
},
77+
storageDescriptor: {
78+
location: accessLogsStorageLocation,
79+
inputFormat: "org.apache.hadoop.mapred.TextInputFormat",
80+
outputFormat:
81+
"org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat",
82+
serDeInfo: {
83+
parameters: {
84+
"serialization.format": "1",
85+
"input.regex":
86+
'([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*):([0-9]*) ([^ ]*)[:-]([0-9]*) ([-.0-9]*) ([-.0-9]*) ([-.0-9]*) (|[-0-9]*) (-|[-0-9]*) ([-0-9]*) ([-0-9]*) "([^ ]*) (.*) (- |[^ ]*)" "([^"]*)" ([A-Z0-9-_]+) ([A-Za-z0-9.-]*) ([^ ]*) "([^"]*)" "([^"]*)" "([^"]*)" ([-.0-9]*) ([^ ]*) "([^"]*)" "([^"]*)" "([^ ]*)" "([^s]+?)" "([^s]+)" "([^ ]*)" "([^ ]*)" ([^ ]*)(.*?)',
87+
},
88+
serializationLibrary: "org.apache.hadoop.hive.serde2.RegexSerDe",
89+
},
90+
columns: [
91+
{
92+
name: "type",
93+
type: "string",
94+
},
95+
{
96+
name: "time",
97+
type: "string",
98+
},
99+
{
100+
name: "elb",
101+
type: "string",
102+
},
103+
{
104+
name: "client_ip",
105+
type: "string",
106+
},
107+
{
108+
name: "client_port",
109+
type: "int",
110+
},
111+
{
112+
name: "target_ip",
113+
type: "string",
114+
},
115+
{
116+
name: "target_port",
117+
type: "int",
118+
},
119+
{
120+
name: "requesting_processing_time",
121+
type: "double",
122+
},
123+
{
124+
name: "target_processing_time",
125+
type: "double",
126+
},
127+
{
128+
name: "response_processing_time",
129+
type: "double",
130+
},
131+
{
132+
name: "elb_status_code",
133+
type: "int",
134+
},
135+
{
136+
name: "target_status_code",
137+
type: "int",
138+
},
139+
{
140+
name: "received_bytes",
141+
type: "bigint",
142+
},
143+
{
144+
name: "sent_bytes",
145+
type: "bigint",
146+
},
147+
{
148+
name: "request_verb",
149+
type: "string",
150+
},
151+
{
152+
name: "request_url",
153+
type: "string",
154+
},
155+
{
156+
name: "request_proto",
157+
type: "string",
158+
},
159+
{
160+
name: "user_agent",
161+
type: "string",
162+
},
163+
{
164+
name: "ssl_cipher",
165+
type: "string",
166+
},
167+
{
168+
name: "ssl_protocol",
169+
type: "string",
170+
},
171+
{
172+
name: "target_group_arn",
173+
type: "string",
174+
},
175+
{
176+
name: "trace_id",
177+
type: "string",
178+
},
179+
{
180+
name: "domain_name",
181+
type: "string",
182+
},
183+
{
184+
name: "chosen_cert_arn",
185+
type: "string",
186+
},
187+
{
188+
name: "matched_rule_priority",
189+
type: "string",
190+
},
191+
{
192+
name: "request_creation_time",
193+
type: "string",
194+
},
195+
{
196+
name: "actions_executed",
197+
type: "string",
198+
},
199+
{
200+
name: "redirect_url",
201+
type: "string",
202+
},
203+
{
204+
name: "lambda_error_reason",
205+
type: "string",
206+
},
207+
{
208+
name: "target_port_list",
209+
type: "string",
210+
},
211+
{
212+
name: "target_status_code_list",
213+
type: "string",
214+
},
215+
{
216+
name: "classification",
217+
type: "string",
218+
},
219+
{
220+
name: "classification_reason",
221+
type: "string",
222+
},
223+
{
224+
name: "conn_trace_id",
225+
type: "string",
226+
},
227+
{
228+
name: "unhandled_postfix",
229+
type: "string",
230+
},
231+
],
232+
},
233+
partitionKeys: [
234+
{
235+
name: "day",
236+
type: "string",
237+
},
238+
],
239+
});
240+
}

packages/aws/src/components/load-balancer.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* import * as saws from "@stackattack/aws";
88
*
99
* const ctx = saws.context();
10-
* const network = saws.vpc(ctx);
10+
* const vpc = saws.vpc(ctx);
1111
* const lb = saws.loadBalancer(ctx, {
12-
* network: network.network("public")
12+
* network: vpc.network("public")
1313
* });
1414
*
1515
* export const loadBalancerUrl = lb.url;
@@ -90,6 +90,7 @@
9090
import * as aws from "@pulumi/aws";
9191
import * as pulumi from "@pulumi/pulumi";
9292
import type { Context } from "../context.js";
93+
import { type BucketInput, bucket, getBucketId } from "./bucket.js";
9394
import {
9495
getVpcAttributes,
9596
getVpcDefaultSecurityGroup,
@@ -416,6 +417,14 @@ export interface LoadBalancerArgs {
416417
certificate?: pulumi.Input<string>;
417418
/** Connection idle timeout in seconds */
418419
idleTimeout?: pulumi.Input<number>;
420+
/** Specify a bucket where access logs should be stored. If not passed, a new bucket will be created. If `null` is passed, no access logs will be stored. */
421+
accessLogsBucket?: null | pulumi.Input<BucketInput>;
422+
/** Prefix for load balancer access logs within the S3 bucket */
423+
accessLogsPrefix?: pulumi.Input<string>;
424+
/** Name of the Athena table to create to query access logs. If not passed, no table will be created. */
425+
accessLogsTable?: pulumi.Input<string>;
426+
/** Name of the Athena database to create the access logs table in. Only relevant if `accessLogsTable` is passed. If not passed, the default database will be used. */
427+
accessLogsDatabase?: pulumi.Input<string>;
419428
/** Whether to skip adding a prefix to the context */
420429
noPrefix?: boolean;
421430
}
@@ -474,14 +483,32 @@ export function loadBalancer(
474483
vpc: args.network.vpc,
475484
});
476485

486+
let accessLogsBucket: pulumi.Input<string> | undefined;
487+
if (args.accessLogsBucket) {
488+
accessLogsBucket = getBucketId(args.accessLogsBucket);
489+
} else if (args.accessLogsBucket === undefined) {
490+
accessLogsBucket = bucket(ctx).bucket;
491+
}
492+
477493
const loadBalancer = new aws.lb.LoadBalancer(ctx.id(), {
478494
namePrefix: "lb-",
479495
subnets: args.network.subnetIds,
480496
securityGroups: [securityGroup.id],
481497
idleTimeout: args.idleTimeout,
498+
accessLogs:
499+
accessLogsBucket === undefined
500+
? undefined
501+
: {
502+
enabled: true,
503+
bucket: accessLogsBucket,
504+
prefix: args.accessLogsPrefix,
505+
},
482506
tags: ctx.tags(),
483507
});
484508

509+
if (args.accessLogsTable) {
510+
}
511+
485512
const { listener } = loadBalancerListener(ctx, {
486513
loadBalancer,
487514
certificate: args.certificate,

0 commit comments

Comments
 (0)