Skip to content

Commit

Permalink
add a globFileUploadParamsList option to set properties on uploaded f…
Browse files Browse the repository at this point in the history
…iles (#410)
  • Loading branch information
maxletourneur authored Nov 27, 2023
1 parent 93dba4d commit d921f55
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 17 deletions.
43 changes: 40 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@
```

4. After these steps your `angular.json` is updated with a new builder:

```json
"deploy": {
"builder": "@jefiozie/ngx-aws-deploy:deploy",
"options": {}
"builder": "@jefiozie/ngx-aws-deploy:deploy",
"options": {}
}
```

Expand All @@ -62,7 +63,42 @@ npx cross-env NG_DEPLOY_AWS_ACCESS_KEY_ID=1234 NG_DEPLOY_AWS_SECRET_ACCESS_KEY=3
npx cross-env ... NG_DEPLOY_AWS_CF_DISTRIBUTION_ID=1234 ... ng deploy
```

7. Run `ng deploy` to deploy your application to Amazon S3.
7. To apply properties on uploaded files, use the `NG_DEPLOY_AWS_GLOB_FILE_UPLOAD_PARAMS_LIST` variable or the target option `globFileUploadParamsList` as shown below.
We use an array of objects to represent the different configurations.
Each config object needs a `glob` property to define on which files the params will be set.
Other properties on the config object are the params to apply.
For informations about the possible params (ACL, Bucket, CacheControl, etc), checkout the documentation of the `s3.upload` method (https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property)

```json
"deploy": {
"builder": "@jefiozie/ngx-aws-deploy:deploy",
"options": {
"globFileUploadParamsList": [
{
"glob": "*",
"ACL": "public-read",
"CacheControl": "max-age=3600"
},
{
"glob": "*.html",
"CacheControl": "max-age=300"
}
]
}
},
```

The order of the config objects matters, the one that comes last is the one that will be used, as shown above:

- CacheControl is set for all files but it's overidden by the next config object only for html files. Convenient for declaring exceptions and not repeat global settings.

To set this using the environment variable `NG_DEPLOY_AWS_GLOB_FILE_UPLOAD_PARAMS_LIST`, simply stringify the config object to JSON with `JSON.stringify`.

```bash
npx cross-env ... NG_DEPLOY_AWS_GLOB_FILE_UPLOAD_PARAMS_LIST='[{"glob":"*","ACL":"public-read","CacheControl":"max-age=3600"},{"glob":"*.html","CacheControl":"max-age=300"}]' ... ng deploy
```

8. Run `ng deploy` to deploy your application to Amazon S3.

🚀**_Happy deploying!_** 🚀

Expand All @@ -72,6 +108,7 @@ Keep in mind that **with the default config, everybody that has access to the an
If you want more security, you can also use environment variable with `NG_DEPLOY_AWS_ACCESS_KEY_ID`, `NG_DEPLOY_AWS_SECRET_ACCESS_KEY`, `NG_DEPLOY_AWS_BUCKET` and `NG_DEPLOY_AWS_REGION`.

#### Minimal Required IAM Policy for AWS Credentials

```
{
"Version": "2012-10-17",
Expand Down
27 changes: 26 additions & 1 deletion libs/ngx-aws-deploy/src/lib/deploy/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Schema } from './schema';
import { GlobFileUploadParamsList, Schema } from './schema';

export const getAccessKeyId = (): string => {
return (
Expand Down Expand Up @@ -50,3 +50,28 @@ export const gets3ForcePathStyle = (): boolean => {
export const getAwsEndpoint = (): string => {
return process.env.AWS_ENDPOINT;
};

const validateGlobFileUploadParamsList = (
paramsList: GlobFileUploadParamsList
) => Array.isArray(paramsList) && !paramsList.some((params) => !params.glob);

export const getGlobFileUploadParamsList = (
builderConfig: Schema
): GlobFileUploadParamsList => {
let globFileUploadParamsList = [];
try {
globFileUploadParamsList = process.env
.NG_DEPLOY_AWS_GLOB_FILE_UPLOAD_PARAMS_LIST
? JSON.parse(process.env.NG_DEPLOY_AWS_GLOB_FILE_UPLOAD_PARAMS_LIST)
: builderConfig.globFileUploadParamsList || [];
} catch (e) {
console.error(
'Invalid JSON for NG_DEPLOY_AWS_GLOB_FILE_UPLOAD_PARAMS_LIST',
e
);
}

return validateGlobFileUploadParamsList(globFileUploadParamsList)
? globFileUploadParamsList
: [];
};
22 changes: 14 additions & 8 deletions libs/ngx-aws-deploy/src/lib/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,17 @@ export default createBuilder(
targetString += `:${context.target.configuration}`;
}

const { bucket, region, subFolder } = await context.getTargetOptions(
targetFromTargetString(targetString)
);
const { bucket, region, subFolder, globFileUploadParamsList } =
await context.getTargetOptions(targetFromTargetString(targetString));

const deployConfig = { bucket, region, subFolder } as Pick<
const deployConfig = {
bucket,
region,
subFolder,
globFileUploadParamsList,
} as Pick<
Schema,
'bucket' | 'region' | 'subFolder'
'bucket' | 'region' | 'subFolder' | 'globFileUploadParamsList'
>;

let buildResult: BuilderOutput;
Expand Down Expand Up @@ -82,8 +86,10 @@ export default createBuilder(
context.logger.info(`✔ Build Completed`);
}
if (buildResult.success) {
const filesPath = buildResult.outputPath as string;
const files = getFiles(filesPath);
const { outputPath } = await context.getTargetOptions(
targetFromTargetString(buildTarget.name)
);
const files = getFiles(`${outputPath}`);

if (files.length === 0) {
throw new Error(
Expand All @@ -107,7 +113,7 @@ export default createBuilder(
}

context.logger.info('Start uploading files...');
const success = await uploader.upload(files, filesPath);
const success = await uploader.upload(files, `${outputPath}`);
if (success) {
context.logger.info('✔ Finished uploading files...');

Expand Down
3 changes: 3 additions & 0 deletions libs/ngx-aws-deploy/src/lib/deploy/schema.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export type GlobFileUploadParams = { glob: string } & PutObjectRequest;
export type GlobFileUploadParamsList = Array<GlobFileUploadParams>;
export interface Schema {
configuration?: string;
buildTarget?: string;
Expand All @@ -11,4 +13,5 @@ export interface Schema {
cfDistributionId?: string;
deleteAfterUpload?: boolean;
deleteBeforeUpload?: boolean;
globFileUploadParamsList?: GlobFileUploadParamsList;
}
6 changes: 5 additions & 1 deletion libs/ngx-aws-deploy/src/lib/deploy/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@
"deleteBeforeUpload": {
"type": "boolean",
"default": false,
"description": "Remove all files in the bucket before uploading."
"description": "Remove all files in the bucket before uploading.",
"globFileUploadParamsList": {
"type": "array",
"description": "An array of objects representing params to pass to the s3.upload method (https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property). In addition to the params, each object has a glob property allowing to target params to specific files."
}
}
}
}
40 changes: 36 additions & 4 deletions libs/ngx-aws-deploy/src/lib/deploy/uploader.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { BuilderContext } from '@angular-devkit/architect';
import * as AWS from 'aws-sdk';
import { HeadBucketRequest, ObjectIdentifierList, PutObjectRequest } from 'aws-sdk/clients/s3';
import {
HeadBucketRequest,
ObjectIdentifierList,
PutObjectRequest,
} from 'aws-sdk/clients/s3';
import * as mimeTypes from 'mime-types';
import * as fs from 'fs';
import * as path from 'path';
import { Schema } from './schema';
import * as minimatch from 'minimatch';
import {
GlobFileUploadParams,
GlobFileUploadParamsList,
Schema,
} from './schema';
import {
getAccessKeyId,
getAwsEndpoint,
getBucket,
getGlobFileUploadParamsList,
getRegion,
gets3ForcePathStyle,
getSecretAccessKey,
Expand All @@ -23,13 +33,17 @@ export class Uploader {
private _region: string;
private _subFolder: string;
private _builderConfig: Schema;
private _globFileUploadParamsList: GlobFileUploadParamsList;

constructor(context: BuilderContext, builderConfig: Schema) {
this._context = context;
this._builderConfig = builderConfig;
this._bucket = getBucket(this._builderConfig);
this._region = getRegion(this._builderConfig);
this._subFolder = getSubFolder(this._builderConfig);
this._globFileUploadParamsList = getGlobFileUploadParamsList(
this._builderConfig
);

AWS.config.update({ region: this._region });

Expand Down Expand Up @@ -86,6 +100,19 @@ export class Uploader {

public async uploadFile(localFilePath: string, originFilePath: string) {
const fileName = path.basename(localFilePath);
const globFileUploadParamsForFile = this._globFileUploadParamsList.filter(
(params: GlobFileUploadParams) => minimatch(originFilePath, params.glob)
);

const mergedParamsForFile = globFileUploadParamsForFile.reduce(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(acc, { glob, ...params }) => ({
...acc,
...params,
}),
{}
);

const body = fs.createReadStream(localFilePath);
body.on('error', function (err) {
console.log('File Error', err);
Expand All @@ -98,6 +125,7 @@ export class Uploader {
: originFilePath,
Body: body,
ContentType: mimeTypes.lookup(fileName) || undefined,
...mergedParamsForFile,
};

await this._s3
Expand All @@ -116,8 +144,12 @@ export class Uploader {

public async deleteStaleFiles(localFiles: string[]) {
const remoteFiles = await this.listObjectKeys();
const staleFiles = this._subFolder ? localFiles.map((file) => `${this._subFolder}/${file}`) : localFiles;
const filesToDelete = remoteFiles.filter((file) => !staleFiles.includes(file.Key));
const staleFiles = this._subFolder
? localFiles.map((file) => `${this._subFolder}/${file}`)
: localFiles;
const filesToDelete = remoteFiles.filter(
(file) => !staleFiles.includes(file.Key)
);

return this.deleteFiles(filesToDelete);
}
Expand Down

0 comments on commit d921f55

Please sign in to comment.