|
| 1 | +import AWS from 'aws-sdk'; |
| 2 | +import moment from 'moment'; |
| 3 | +// eslint-disable-next-line import/extensions |
| 4 | +import { ListObjectsV2Output } from 'aws-sdk/clients/s3'; |
| 5 | + |
| 6 | +export class AuditLogMoverHelper { |
| 7 | + static async doesEachDayHaveS3Directory(eachDayInTimeFrame: string[], auditLogBucket: string) { |
| 8 | + const yearAndMonthPrefixOfDates = Array.from( |
| 9 | + new Set( |
| 10 | + eachDayInTimeFrame.map(date => { |
| 11 | + return date.substring(0, 7); // Only grab the year and month |
| 12 | + }), |
| 13 | + ), |
| 14 | + ); |
| 15 | + const directoriesInS3 = await this.getDirectoriesInS3GivenPrefixes(yearAndMonthPrefixOfDates, auditLogBucket); |
| 16 | + |
| 17 | + const dateWithoutDirectory = eachDayInTimeFrame.filter(date => !directoriesInS3.includes(date)); |
| 18 | + |
| 19 | + return dateWithoutDirectory.length === 0; |
| 20 | + } |
| 21 | + |
| 22 | + static getEachDayInTimeFrame(startTimeMoment: moment.Moment, endTimeMoment: moment.Moment): moment.Moment[] { |
| 23 | + if (startTimeMoment.isAfter(endTimeMoment)) { |
| 24 | + throw new Error('startTime can not be later than endTime'); |
| 25 | + } |
| 26 | + const eachDayInTimeFrame: moment.Moment[] = []; |
| 27 | + |
| 28 | + let currentTimeMoment = moment(startTimeMoment); |
| 29 | + do { |
| 30 | + eachDayInTimeFrame.push(moment(currentTimeMoment)); |
| 31 | + currentTimeMoment = currentTimeMoment.add(1, 'days'); |
| 32 | + } while (currentTimeMoment.valueOf() < endTimeMoment.valueOf()); |
| 33 | + |
| 34 | + return eachDayInTimeFrame; |
| 35 | + } |
| 36 | + |
| 37 | + private static async getDirectoriesInS3GivenPrefixes(prefixes: string[], auditLogBucket: string) { |
| 38 | + const S3 = new AWS.S3(); |
| 39 | + const listS3Responses: ListObjectsV2Output[] = await Promise.all( |
| 40 | + prefixes.map(prefix => { |
| 41 | + const s3params: any = { |
| 42 | + Bucket: auditLogBucket, |
| 43 | + MaxKeys: 31, |
| 44 | + Delimiter: '/', |
| 45 | + Prefix: prefix, |
| 46 | + }; |
| 47 | + return S3.listObjectsV2(s3params).promise(); |
| 48 | + }), |
| 49 | + ); |
| 50 | + |
| 51 | + const directoriesInS3: string[] = []; |
| 52 | + listS3Responses.forEach((response: ListObjectsV2Output) => { |
| 53 | + if (response.CommonPrefixes) { |
| 54 | + response.CommonPrefixes.forEach(commonPrefix => { |
| 55 | + // Format of Prefix is 2020-07-04/ |
| 56 | + // Therefore we need to remove the '/' at the end |
| 57 | + if (commonPrefix.Prefix) { |
| 58 | + directoriesInS3.push(commonPrefix.Prefix.slice(0, -1)); |
| 59 | + } |
| 60 | + }); |
| 61 | + } |
| 62 | + }); |
| 63 | + |
| 64 | + return directoriesInS3; |
| 65 | + } |
| 66 | + |
| 67 | + static async getAllLogStreams(cwLogExecutionGroup: string): Promise<LogStreamType[]> { |
| 68 | + const params: any = { |
| 69 | + logGroupName: cwLogExecutionGroup, |
| 70 | + orderBy: 'LastEventTime', |
| 71 | + descending: true, |
| 72 | + limit: 50, |
| 73 | + }; |
| 74 | + const logStreams: LogStreamType[] = []; |
| 75 | + |
| 76 | + let nextToken = ''; |
| 77 | + do { |
| 78 | + if (nextToken) { |
| 79 | + params.nextToken = nextToken; |
| 80 | + } |
| 81 | + const cloudwatchLogs = new AWS.CloudWatchLogs(); |
| 82 | + // eslint-disable-next-line no-await-in-loop |
| 83 | + const describeResponse = await cloudwatchLogs.describeLogStreams(params).promise(); |
| 84 | + if (describeResponse.logStreams && describeResponse.logStreams.length > 0) { |
| 85 | + describeResponse.logStreams.forEach((logStream: any) => { |
| 86 | + logStreams.push({ |
| 87 | + logStreamName: logStream.logStreamName, |
| 88 | + firstEventTimestamp: logStream.firstEventTimestamp, |
| 89 | + lastEventTimestamp: logStream.lastEventTimestamp, |
| 90 | + }); |
| 91 | + }); |
| 92 | + } |
| 93 | + nextToken = describeResponse.nextToken || ''; |
| 94 | + } while (nextToken); |
| 95 | + |
| 96 | + return logStreams; |
| 97 | + } |
| 98 | + |
| 99 | + static async putCWMetric(stage: string, functionName: string, isSuccessful: boolean) { |
| 100 | + const putMetricDataPromises = []; |
| 101 | + if (isSuccessful) { |
| 102 | + // Mark a value of value of 1 for metric marking success and a value of 0 for metric marking failure |
| 103 | + putMetricDataPromises.push(this.getMetricDataPromise(stage, functionName, true, 1)); |
| 104 | + putMetricDataPromises.push(this.getMetricDataPromise(stage, functionName, false, 0)); |
| 105 | + } else { |
| 106 | + putMetricDataPromises.push(this.getMetricDataPromise(stage, functionName, true, 0)); |
| 107 | + putMetricDataPromises.push(this.getMetricDataPromise(stage, functionName, false, 1)); |
| 108 | + } |
| 109 | + |
| 110 | + try { |
| 111 | + await Promise.all(putMetricDataPromises); |
| 112 | + } catch (e) { |
| 113 | + console.error('Failed to putCWMetric', e); |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + private static async getMetricDataPromise( |
| 118 | + stage: string, |
| 119 | + functionName: string, |
| 120 | + isSuccessful: boolean, |
| 121 | + metricValue: number, |
| 122 | + ) { |
| 123 | + const MetricName = `${functionName}-${isSuccessful ? 'Succeeded' : 'Failed'}`; |
| 124 | + const params: any = { |
| 125 | + MetricData: [ |
| 126 | + { |
| 127 | + MetricName, |
| 128 | + Dimensions: [ |
| 129 | + { |
| 130 | + Name: 'Stage', |
| 131 | + Value: stage, |
| 132 | + }, |
| 133 | + ], |
| 134 | + StorageResolution: 60, |
| 135 | + Timestamp: new Date(), |
| 136 | + Unit: 'Count', |
| 137 | + Value: metricValue, |
| 138 | + }, |
| 139 | + ], |
| 140 | + Namespace: 'Audit-Log-Mover', |
| 141 | + }; |
| 142 | + const cloudwatch = new AWS.CloudWatch(); |
| 143 | + return cloudwatch.putMetricData(params).promise(); |
| 144 | + } |
| 145 | +} |
| 146 | +export interface LogStreamType { |
| 147 | + logStreamName: string; |
| 148 | + firstEventTimestamp: number; |
| 149 | + lastEventTimestamp: number; |
| 150 | +} |
0 commit comments