Skip to content

Commit 2f97a16

Browse files
committed
Implement purging surrogate keys
1 parent 88f70f2 commit 2f97a16

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

src/cli/storage/s3-storage-provider.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import fs from 'node:fs';
7+
import path from 'node:path';
78
import {
89
DeleteObjectCommand,
910
DeleteObjectCommandInput,
@@ -51,6 +52,16 @@ import {
5152
import {
5253
rootRelative,
5354
} from '../util/files.js';
55+
import {
56+
type FastlyApiContext,
57+
loadApiToken,
58+
} from '../util/api-token.js';
59+
import {
60+
readServiceId,
61+
} from '../util/fastly-toml.js';
62+
import {
63+
purgeSurrogateKey,
64+
} from '../util/purge.js';
5465

5566
type CommandOutput<C> = C extends Command<any, any, any, infer O, any> ? O : never;
5667

@@ -83,7 +94,27 @@ export const buildStoreProvider: StorageProviderBuilder = async (
8394
throw new Error("❌ S3 Credentials not provided.\nProvide an AWS access key ID and secret access key that has write access to the S3 Storage.\nRefer to the README file and --help for additional information.");
8495
}
8596
console.log(`✔️ S3 Credentials: ${awsCredentialsResult.awsAccessKeyId.slice(0, 4)}${'*'.repeat(awsCredentialsResult.awsAccessKeyId.length-4)} from '${awsCredentialsResult.source}'`);
97+
98+
const fastlyTomlPath = path.resolve(context.computeAppDir, 'fastly.toml');
99+
const serviceId = readServiceId(fastlyTomlPath);
100+
101+
let apiToken = undefined;
102+
if (serviceId == null) {
103+
console.log(`- Service ID not found in fastly.toml, application may not have been deployed yet. Will skip purge step after publish.`);
104+
} else {
105+
console.log(`✔️ Service ID from fastly.toml: ${serviceId}`);
106+
107+
const apiTokenResult = loadApiToken({commandLine: context.fastlyApiToken});
108+
if (apiTokenResult == null) {
109+
throw new Error("❌ Fastly API Token not provided.\nSet the FASTLY_API_TOKEN environment variable to an API token that has write access to the KV Store.");
110+
}
111+
console.log(`✔️ Fastly API Token: ${apiTokenResult.apiToken.slice(0, 4)}${'*'.repeat(apiTokenResult.apiToken.length - 4)} from '${apiTokenResult.source}'`);
112+
apiToken = apiTokenResult.apiToken;
113+
}
114+
86115
return new S3StorageProvider(
116+
serviceId,
117+
apiToken,
87118
region,
88119
awsCredentialsResult.awsAccessKeyId,
89120
awsCredentialsResult.awsSecretAccessKey,
@@ -95,19 +126,25 @@ export const buildStoreProvider: StorageProviderBuilder = async (
95126

96127
export class S3StorageProvider implements StorageProvider {
97128
constructor(
129+
fastlyServiceId: string | undefined,
130+
fastlyApiToken: string | undefined,
98131
s3Region: string,
99132
accessKeyId: string,
100133
secretAccessKey: string,
101134
s3Bucket: string,
102135
s3Endpoint?: string,
103136
) {
137+
this.fastlyServiceId = fastlyServiceId;
138+
this.fastlyApiContext = fastlyApiToken != null ? { apiToken: fastlyApiToken } : undefined;
104139
this.s3Region = s3Region;
105140
this.accessKeyId = accessKeyId;
106141
this.secretAccessKey = secretAccessKey;
107142
this.s3Bucket = s3Bucket;
108143
this.s3Endpoint = s3Endpoint;
109144
}
110145

146+
private readonly fastlyServiceId?: string;
147+
private readonly fastlyApiContext?: FastlyApiContext;
111148
private readonly s3Region: string;
112149
private readonly accessKeyId: string;
113150
private readonly secretAccessKey: string;
@@ -327,5 +364,31 @@ export class S3StorageProvider implements StorageProvider {
327364
}
328365

329366
async purgeSurrogateKey(surrogateKey: string): Promise<void> {
367+
368+
if (this.fastlyServiceId == null) {
369+
console.log('Fastly Service ID not set, skipping purge...');
370+
return;
371+
}
372+
373+
if (this.fastlyApiContext?.apiToken == null) {
374+
console.log('Fastly API token not set, skipping purge...');
375+
return;
376+
}
377+
378+
console.log(`Purging surrogate key [${surrogateKey}] on service [${this.fastlyServiceId}]...`);
379+
380+
const result = await purgeSurrogateKey(
381+
this.fastlyApiContext,
382+
this.fastlyServiceId,
383+
surrogateKey,
384+
true,
385+
);
386+
387+
if (result) {
388+
console.log('Purged');
389+
} else {
390+
console.log('Failed purging');
391+
}
392+
330393
}
331394
}

src/cli/util/purge.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright Fastly, Inc.
3+
* Licensed under the MIT license. See LICENSE file for details.
4+
*/
5+
6+
import { callFastlyApi, type FastlyApiContext, FetchError } from './api-token.js';
7+
8+
export async function purgeSurrogateKey(
9+
fastlyApiContext: FastlyApiContext,
10+
fastlyServiceId: string,
11+
surrogateKey: string,
12+
softPurge: boolean = false,
13+
) {
14+
15+
const endpoint = `/service/${encodeURIComponent(fastlyServiceId)}/purge/${encodeURIComponent(surrogateKey)}`;
16+
17+
try {
18+
19+
const headers = new Headers();
20+
if (softPurge) {
21+
headers.set('fastly-soft-purge', '1');
22+
}
23+
await callFastlyApi(fastlyApiContext, endpoint, `Purging surrogate key [${surrogateKey}] on service [${fastlyServiceId}]`, null, { method: 'POST', headers });
24+
25+
} catch(err) {
26+
if (err instanceof FetchError) {
27+
return false;
28+
}
29+
throw err;
30+
}
31+
32+
return true;
33+
34+
}

0 commit comments

Comments
 (0)