Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/tf-next/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ tf-next deploy --endpoint <api-endpoint>
The following options can be passed when using the `tf-next deploy` command:

- `--endpoint`
- `--awsProfile`
- `--profile` (also available as `--awsProfile`)
AWS profile to use for authenticating against the API endpoint. Uses `default` profile if not specified.

### Deployment

Expand Down Expand Up @@ -106,7 +107,8 @@ tf-next deployment rm <deployment-id> --force
The following options can be passed when using the `tf-next deployment` command:

- `--endpoint`
- `--awsProfile`
- `--profile` (also available as `--awsProfile`)
AWS profile to use for authenticating against the API endpoint. Uses `default` profile if not specified.

### Alias

Expand Down
13 changes: 10 additions & 3 deletions packages/tf-next/src/client/aws-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { defaultProvider } from '@aws-sdk/credential-provider-node';
import { Credentials, MemoizedProvider } from '@aws-sdk/types';
import { Options, Arguments } from 'yargs';

import { Client } from './client';

type AwsCredentialProvider = MemoizedProvider<Credentials>;

type AWSProfileArguments = {
Expand All @@ -21,9 +23,13 @@ type AWSProfileArguments = {
* @see {@link https://www.npmjs.com/package/@aws-sdk/credential-provider-node}
*/
const awsProfileMiddleware = (argv: Arguments): AwsCredentialProvider => {
// If the --awsProfile flag is provided load a named profile
const profile =
typeof argv.awsProfile === 'string' ? argv.awsProfile : undefined;
// If the --profile flag is provided load a named profile
const profile = typeof argv.profile === 'string' ? argv.profile : undefined;

if (profile) {
const client = argv.client as Client;
client.output.debug(`Using AWS profile ${profile}`);
}

return defaultProvider({
profile,
Expand All @@ -38,6 +44,7 @@ const awsProfileMiddlewareOptions: Record<string, Options> = {
type: 'string',
description:
'AWS profile that should be used for authentication with the API endpoint.',
alias: 'awsProfile',
},
};

Expand Down
66 changes: 37 additions & 29 deletions packages/tf-next/src/client/services/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
createResponseError,
ResponseError,
} from '../../../utils/errors/response-error';
import { CredentialsError } from '../../../utils/errors';

type NodeFetch = typeof nodeFetch;
type CreateAliasRequestBody =
Expand Down Expand Up @@ -127,39 +128,46 @@ class ApiService {
}

const parsedUrl = new URL(requestUrl, this.apiEndpoint);
const signedRequest = await signature.sign({
hostname: parsedUrl.hostname,
protocol: parsedUrl.protocol,
path: parsedUrl.pathname,
headers: convertFetchHeaders({
...fetchArgs[1]?.headers,
host: parsedUrl.hostname,
accept: 'application/json',
}),
method: fetchArgs[1]?.method?.toUpperCase() ?? 'GET',
query: convertURLSearchParamsToQueryBag(parsedUrl.searchParams),
body: fetchArgs[1]?.body,
});
const response = await nodeFetch(parsedUrl.href, {
...fetchArgs[1],
headers: signedRequest.headers,
});
try {
const signedRequest = await signature.sign({
hostname: parsedUrl.hostname,
protocol: parsedUrl.protocol,
path: parsedUrl.pathname,
headers: convertFetchHeaders({
...fetchArgs[1]?.headers,
host: parsedUrl.hostname,
accept: 'application/json',
}),
method: fetchArgs[1]?.method?.toUpperCase() ?? 'GET',
query: convertURLSearchParamsToQueryBag(parsedUrl.searchParams),
body: fetchArgs[1]?.body,
});
const response = await nodeFetch(parsedUrl.href, {
...fetchArgs[1],
headers: signedRequest.headers,
});
if (!response.ok) {
throw await createResponseError(response);
}

if (!response.ok) {
throw await createResponseError(response);
}
// OK - parse the response
if (response.headers.get('content-type') === 'application/json') {
try {
return (await response.json()) as Promise<T>;
} catch (_ignoredError) {
return {} as T;
}
}

// OK - parse the response
if (response.headers.get('content-type') === 'application/json') {
try {
return (await response.json()) as Promise<T>;
} catch (_ignoredError) {
return {} as T;
// Response from API is not OK, should never happen
throw new Error('Invalid response from API');
} catch (error: any) {
if (error.name === 'CredentialsProviderError') {
throw new CredentialsError();
}
}

// Response from API is not OK, should never happen
throw new Error('Invalid response from API');
throw error;
}
}

// Aliases
Expand Down
9 changes: 7 additions & 2 deletions packages/tf-next/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ async function runCli() {
// give us access to the argv argument.
// @see {@link https://github.com/yargs/yargs/issues/2133}
(error, argv, output) => {
if (error instanceof CliError) {
const client = argv.client as Client;
const client = argv.client as Client | undefined;

// Ensure that the output halts
if (client) {
client.output.stopSpinner();
}

if (error instanceof CliError) {
// Client should be initialized at this point
if (!client) {
throw new Error('Client was not initialized');
Expand Down
10 changes: 10 additions & 0 deletions packages/tf-next/src/utils/errors/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ export class MissingApiEndpoint extends CliError<'MISSING_API_ENDPOINT'> {
}
}

export class CredentialsError extends CliError<'INVALID_CREDENTIALS'> {
constructor() {
super({
code: 'INVALID_CREDENTIALS',
message:
'Could not read the provided AWS credentials.\nPlease make sure that the provided AWS profile exists.',
});
}
}

export class AliasOverrideNotAllowed extends CliError<'ALIAS_OVERRIDE_NOT_ALLOWED'> {
constructor(alias: string) {
super({
Expand Down
2 changes: 1 addition & 1 deletion packages/tf-next/src/utils/errors/response-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async function createResponseError(res: Response): Promise<ResponseError> {
code: 'PERMISSION_ERROR',
serverMessage: message,
message:
'Authentication failed. Make sure that the AWS profile is set correctly.',
'Authentication failed.\nMake sure that the AWS user has the correct permissions.',
});
}

Expand Down