Skip to content

Commit

Permalink
refactor: move s3 config handling to shared module
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreasZeissner committed Sep 3, 2024
1 parent 5c6d6a6 commit ec1ac05
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 136 deletions.
1 change: 1 addition & 0 deletions cdn-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.529.1",
"@hono/node-server": "1.11.0",
"@wundergraph/cosmo-shared": "workspace:*",
"@wundergraph/cosmo-cdn": "workspace:*",
"dotenv": "^16.4.5",
"hono": "4.2.7"
Expand Down
2 changes: 1 addition & 1 deletion cdn-server/src/s3.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { GetObjectCommand, HeadObjectCommand, NoSuchKey, NotFound, S3Client } from '@aws-sdk/client-s3';
import { extractS3BucketName, createS3ClientConfig } from '@wundergraph/cosmo-shared';
import { BlobNotFoundError, BlobObject, BlobStorage } from '@wundergraph/cosmo-cdn';
import { Context } from 'hono';
import { createS3ClientConfig, extractS3BucketName } from './utils';

/**
* Retrieves objects from S3 given an S3Client and a bucket name
Expand Down
58 changes: 0 additions & 58 deletions cdn-server/src/utils.ts

This file was deleted.

3 changes: 2 additions & 1 deletion controlplane/src/core/build-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Fastify, { FastifyBaseLogger } from 'fastify';
import { S3Client } from '@aws-sdk/client-s3';
import { fastifyConnectPlugin } from '@connectrpc/connect-fastify';
import { cors, createContextValues } from '@connectrpc/connect';
import { extractS3BucketName, createS3ClientConfig } from '@wundergraph/cosmo-shared';
import fastifyCors from '@fastify/cors';
import { pino, stdTimeFunctions, LoggerOptions } from 'pino';
import { compressionBrotli, compressionGzip } from '@connectrpc/connect-node';
Expand Down Expand Up @@ -37,7 +38,7 @@ import { BillingRepository } from './repositories/BillingRepository.js';
import { BillingService } from './services/BillingService.js';
import { UserRepository } from './repositories/UserRepository.js';
import { AIGraphReadmeQueue, createAIGraphReadmeWorker } from './workers/AIGraphReadmeWorker.js';
import { fastifyLoggerId, createS3ClientConfig, extractS3BucketName } from './util.js';
import { fastifyLoggerId } from './util.js';
import { ApiKeyRepository } from './repositories/ApiKeyRepository.js';
import { createDeleteOrganizationWorker, DeleteOrganizationQueue } from './workers/DeleteOrganizationWorker.js';

Expand Down
46 changes: 1 addition & 45 deletions controlplane/src/core/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { randomFill } from 'node:crypto';
import { S3ClientConfig } from '@aws-sdk/client-s3';
import { HandlerContext } from '@connectrpc/connect';
import {
EnumStatusCode,
Expand All @@ -14,7 +13,7 @@ import { uid } from 'uid/secure';
import { AxiosError } from 'axios';
import { isNetworkError, isRetryableError } from 'axios-retry';
import { MemberRole, WebsocketSubprotocol } from '../db/models.js';
import { AuthContext, DateRange, Label, ResponseMessage, S3StorageOptions } from '../types/index.js';
import { AuthContext, DateRange, Label, ResponseMessage } from '../types/index.js';
import { isAuthenticationError, isAuthorizationError, isPublicError } from './errors/errors.js';
import { GraphKeyAuthContext } from './services/GraphApiTokenAuthenticator.js';

Expand Down Expand Up @@ -373,46 +372,3 @@ export function getValueOrDefault<K, V>(map: Map<K, V>, key: K, constructor: ()
export function webhookAxiosRetryCond(err: AxiosError) {
return isNetworkError(err) || isRetryableError(err);
}

export function createS3ClientConfig(bucketName: string, opts: S3StorageOptions): S3ClientConfig {
const url = new URL(opts.url);
const { region, username, password } = opts;
const forcePathStyle = !isVirtualHostStyleUrl(url);
const endpoint = opts.endpoint || (forcePathStyle ? url.origin : url.origin.replace(`${bucketName}.`, ''));

const accessKeyId = url.username || username || '';
const secretAccessKey = url.password || password || '';

if (!accessKeyId || !secretAccessKey) {
throw new Error('Missing S3 credentials. Please provide access key ID and secret access key.');
}

if (!region) {
throw new Error('Missing region in S3 configuration.');
}

return {
region,
endpoint,
credentials: {
accessKeyId,
secretAccessKey,
},
forcePathStyle,
};
}

export function extractS3BucketName(s3Url: string) {
const url = new URL(s3Url);

if (isVirtualHostStyleUrl(url)) {
return url.hostname.split('.')[0];
}

// path based style
return url.pathname.slice(1);
}

export function isVirtualHostStyleUrl(url: URL) {
return url.hostname.split('.').length > 2;
}
8 changes: 0 additions & 8 deletions controlplane/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,11 +576,3 @@ export interface SchemaLintIssues {
warnings: LintIssueResult[];
errors: LintIssueResult[];
}

export interface S3StorageOptions {
url: string;
region?: string;
endpoint?: string;
username?: string;
password?: string;
}
35 changes: 13 additions & 22 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"license": "Apache-2.0",
"dependencies": {
"@bufbuild/protobuf": "^1.9.0",
"@aws-sdk/client-s3": "^3.529.1",
"@graphql-tools/schema": "^8.5.1",
"@graphql-tools/utils": "^9.2.1",
"@wundergraph/composition": "workspace:*",
Expand Down
7 changes: 7 additions & 0 deletions shared/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface S3StorageOptions {
url: string;
region?: string;
endpoint?: string;
username?: string;
password?: string;
}
45 changes: 45 additions & 0 deletions shared/src/utils/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as S3 from '@aws-sdk/client-s3';
import { SubscriptionProtocol, WebsocketSubprotocol } from '../router-config/builder.js';
import { S3StorageOptions } from '../types/index.js';

export function delay(t: number) {
return new Promise((resolve) => setTimeout(resolve, t));
Expand Down Expand Up @@ -76,3 +78,46 @@ export function isValidWebsocketSubprotocol(protocol: WebsocketSubprotocol) {
}
}
}

export function createS3ClientConfig(bucketName: string, opts: S3StorageOptions): S3.S3ClientConfig {
const url = new URL(opts.url);
const { region, username, password } = opts;
const forcePathStyle = !isVirtualHostStyleUrl(url);
const endpoint = opts.endpoint || (forcePathStyle ? url.origin : url.origin.replace(`${bucketName}.`, ''));

const accessKeyId = url.username || username || '';
const secretAccessKey = url.password || password || '';

if (!accessKeyId || !secretAccessKey) {
throw new Error('Missing S3 credentials. Please provide access key ID and secret access key.');
}

if (!region) {
throw new Error('Missing region in S3 configuration.');
}

return {
region,
endpoint,
credentials: {
accessKeyId,
secretAccessKey,
},
forcePathStyle,
};
}

export function extractS3BucketName(s3Url: string) {
const url = new URL(s3Url);

if (isVirtualHostStyleUrl(url)) {
return url.hostname.split('.')[0];
}

// path based style
return url.pathname.slice(1);
}

export function isVirtualHostStyleUrl(url: URL) {
return url.hostname.split('.').length > 2;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from 'vitest';
import { createS3ClientConfig, extractS3BucketName, isVirtualHostStyleUrl } from '../src/core/util.js';
import { createS3ClientConfig, extractS3BucketName, isVirtualHostStyleUrl } from '../src';

describe('S3 Utils', () => {
describe('createS3ClientConfig', () => {
Expand Down

0 comments on commit ec1ac05

Please sign in to comment.