Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Atomic deployments: Add CLI commands #317

Merged
merged 6 commits into from
May 30, 2022
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
61 changes: 49 additions & 12 deletions packages/tf-next/README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
# Terraform Next Build
# Terraform Next.js CLI

Command-line interface (CLI) tool for [Terraform Next.js module for AWS](https://github.com/milliHQ/terraform-aws-next-js).
Command-line interface (CLI) companion tool for [Terraform Next.js module for AWS](https://github.com/milliHQ/terraform-aws-next-js).
It is used for [building](#build) Next.js apps, deployment and management of deployments.

## Getting Started

This covers only the CLI part of the tool, for a full step-by-step tutorial please see our [examples](https://github.com/milliHQ/terraform-aws-next-js#examples).

1. Install the CLI tool:
1. Install the CLI tool

```sh
```plain
npm i -g tf-next
```

2. Build the project:
2. Build the project

```sh
```plain
tf-next build
```

3. Deploy the app:
3. Deploy the app

```sh
```plain
tf-next deploy --endpoint https://<api-id>.execute-api.<region>.amazonaws.com

> ✅ Upload complete.
Expand All @@ -40,15 +40,15 @@ The app is then checked out into a temporary folder and build from there.
Once the build process is finished, a new folder `.next-tf` is added to your current working directory.
The `.next-tf` folder contains a deployment package that can be used together with the [deploy command](#deploy) to deploy your application.

```sh
```plain
tf-next build
```

#### Extended Usage

The `--skipDownload` flag can be used to prevent the checkout into a temporary folder (builds in the current working directory instead):

```sh
```plain
tf-next build --skipDownload
```

Expand All @@ -64,7 +64,7 @@ To publish an previously built Next.js app to the system, run `tf-next deploy` f

#### Basic Usage

```sh
```plain
tf-next deploy --endpoint <api-endpoint>
```

Expand All @@ -73,7 +73,44 @@ tf-next deploy --endpoint <api-endpoint>
The following options can be passed when using the `tf-next deploy` command:

- `--endpoint`
- `--profile`
- `--awsProfile`

### Deployment

To manage the deployments that are published to the system.

#### Basic Usage

To show the most recent (25) deployments:

```plain
tf-next deployment ls
```

To remove (delete) an existing deployment from the system:

```plain
tf-next deployment rm <deployment-id>
```

#### Extended Usage

To delete an existing deployment including all of the aliases that are associated with it.

```plain
tf-next deployment rm <deployment-id> --force
```

#### Global Options

The following options can be passed when using the `tf-next deployment` command:

- `--endpoint`
- `--awsProfile`

### Alias

To managed the aliases that are deployed to the system

## License

Expand Down
16 changes: 11 additions & 5 deletions packages/tf-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
},
"dependencies": {
"@aws-crypto/sha256-js": "^2.0.1",
"@aws-sdk/credential-providers": "^3.87.0",
"@aws-sdk/credential-provider-node": "3.100.0",
"@aws-sdk/signature-v4": "^3.78.0",
"@millihq/tf-next-runtime": "0.12.2",
"@vercel/build-utils": "2.12.1",
"@vercel/frameworks": "^0.0.15",
"@vercel/routing-utils": "^1.10.1",
"ajv": "8.11.0",
"ansi-escapes": "4.3.2",
"archiver": "^5.3.0",
"chalk": "4.1.2",
"find-yarn-workspace-root": "^2.0.0",
"form-data-encoder": "^1.7.2",
"formdata-node": "^4.3.2",
Expand All @@ -35,22 +38,25 @@
"node-fetch": "2.6.7",
"ora": "^5.4.1",
"tmp": "^0.2.1",
"yargs": "^15.3.1"
"yargs": "17.5.1"
},
"devDependencies": {
"@aws-sdk/types": "^3.78.0",
"@millihq/terraform-next-api": "*",
"@tsconfig/node12": "^1.0.9",
"@tsconfig/node14": "1.0.1",
"@types/archiver": "^5.1.0",
"@types/fs-extra": "^9.0.1",
"@types/glob": "^7.1.2",
"@types/node-fetch": "^2.0.0",
"@types/tmp": "^0.2.0",
"@types/yargs": "^15.0.5",
"@types/yargs": "17.0.10",
"typescript": "*"
},
"files": [
"dist/**",
"index.js"
]
],
"engines": {
"node": ">= 14"
}
}
25 changes: 0 additions & 25 deletions packages/tf-next/src/api/deployment/create-deployment.ts

This file was deleted.

24 changes: 0 additions & 24 deletions packages/tf-next/src/api/deployment/get-deployment-by-id.ts

This file was deleted.

25 changes: 0 additions & 25 deletions packages/tf-next/src/api/deployment/list-deployments.ts

This file was deleted.

45 changes: 45 additions & 0 deletions packages/tf-next/src/client/aws-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { defaultProvider } from '@aws-sdk/credential-provider-node';
import { Credentials, MemoizedProvider } from '@aws-sdk/types';
import { Options, Arguments } from 'yargs';

type AwsCredentialProvider = MemoizedProvider<Credentials>;

type AWSProfileArguments = {
awsCredentialProvider: AwsCredentialProvider;
};

/**
* Generates a aws credential provider from the Node.js environment.
* It will attempt to find credentials from the following sources (listed in
* order of precedence):
* - Environment variables exposed via process.env
* - SSO credentials from token cache
* - Web identity token credentials
* - Shared credentials and config ini files
* - The EC2/ECS Instance Metadata Service
*
* @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;

return defaultProvider({
profile,
});
};

/**
* Command line options that are added when the awsProfileMiddleware is used.
*/
const awsProfileMiddlewareOptions: Record<string, Options> = {
profile: {
type: 'string',
description:
'AWS profile that should be used for authentication with the API endpoint.',
},
};

export type { AWSProfileArguments, AwsCredentialProvider };
export { awsProfileMiddleware, awsProfileMiddlewareOptions };
111 changes: 111 additions & 0 deletions packages/tf-next/src/client/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
ArgumentsCamelCase,
Argv,
BuilderCallback,
MiddlewareFunction,
} from 'yargs';

import { GlobalOptions, LogLevel } from '../types';
import {
awsProfileMiddleware,
awsProfileMiddlewareOptions,
} from './aws-profile';
import {
ApiService,
apiMiddlewareOptions,
apiMiddleware,
} from './services/api';
import { OutputService } from './services/output';

type ClientOptions = {
logLevel: LogLevel;
apiService?: ApiService;
};

class Client {
logLevel: LogLevel;
output: OutputService;
// TODO: apiService is only present, when it is available from ClientOptions
// Typing this correctly would take too long
public apiService!: ApiService;

constructor({ logLevel, apiService }: ClientOptions) {
this.logLevel = logLevel;
this.output = new OutputService({ logLevel });

if (apiService) {
this.apiService = apiService;
}
}
}

/* -----------------------------------------------------------------------------
* clientMiddleware
* ---------------------------------------------------------------------------*/

type ClientMiddlewareArguments = {
client: Client;
};

type CreateClientMiddlewareOptions = {
withApiService: boolean;
};

const createClientMiddleware =
(options: CreateClientMiddlewareOptions): MiddlewareFunction<any> =>
(argv) => {
let apiService: ApiService | undefined;

if (options.withApiService) {
// Get AWS profile
const awsCredentialProvider = awsProfileMiddleware(argv);
apiService = apiMiddleware(argv, awsCredentialProvider);
}

argv.client = new Client({
logLevel: argv.logLevel,
apiService,
});
};

/* -----------------------------------------------------------------------------
* withClient
* ---------------------------------------------------------------------------*/

type WithClientOptions = {
withApiService?: boolean;
};

function withClient<
Args extends GlobalOptions,
U = Args & ClientMiddlewareArguments
>(
command: string | ReadonlyArray<string>,
description: string,
builder: BuilderCallback<Args, U>,
handler: (args: ArgumentsCamelCase<U>) => void | Promise<void>,
options: WithClientOptions = {}
) {
const { withApiService = true } = options;

return (yargs: Argv<Args>) => {
const middleware = createClientMiddleware({
withApiService,
});
const localBuilder: BuilderCallback<Args, U> = (localYargs) => {
if (withApiService) {
localYargs.options({
...awsProfileMiddlewareOptions,
...apiMiddlewareOptions,
});
}

return builder(localYargs);
};

yargs.command(command, description, localBuilder, handler, [middleware]);
};
}

export type { Client };
export { withClient };
2 changes: 2 additions & 0 deletions packages/tf-next/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ApiService } from './services/api';
export { Client, withClient } from './client';
Loading