1+ /*********************************************************************************************************************
2+ * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. *
3+ * *
4+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance *
5+ * with the License. A copy of the License is located at *
6+ * *
7+ * http://www.apache.org/licenses/LICENSE-2.0 *
8+ * *
9+ * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES *
10+ * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions *
11+ * and limitations under the License. *
12+ *********************************************************************************************************************/
13+
14+ /**
15+ * @author Solution Builders
16+ */
17+
18+ 'use strict' ;
19+
20+ let AWS = require ( 'aws-sdk' ) ;
21+ const fs = require ( 'fs' ) ;
22+
23+ /**
24+ * Helper function to interact with AWS S3 for cfn custom resource.
25+ *
26+ * @class s3Helper
27+ */
28+ class s3Helper {
29+
30+ /**
31+ * @class s3Helper
32+ * @constructor
33+ */
34+ constructor ( ) {
35+ this . creds = new AWS . EnvironmentCredentials ( 'AWS' ) ; // Lambda provided credentials
36+ this . downloadLocation = '/tmp/manifest.json' ;
37+ }
38+
39+ /**
40+ * validateBuckets
41+ * Cross-checks provided bucket names against existing bucket names in the account for
42+ * validation.
43+ * @param {String } strBuckets - String of bucket names from the template params.
44+ */
45+ async validateBuckets ( strBuckets ) {
46+ const formatted = strBuckets . replace ( / \s / g, '' ) ;
47+ console . log ( `Attempting to check if the following buckets exist: ${ formatted } ` ) ;
48+ const buckets = formatted . split ( ',' ) ;
49+ const errorBuckets = [ ] ;
50+ for ( let i = 0 ; i < buckets . length ; i ++ ) {
51+ const s3 = new AWS . S3 ( { signatureVersion : 'v4' } ) ;
52+ const params = { Bucket : buckets [ i ] } ;
53+ try {
54+ await s3 . headBucket ( params ) . promise ( ) ;
55+ console . log ( `Found bucket: ${ buckets [ i ] } ` ) ;
56+ } catch ( err ) {
57+ console . log ( `Could not find bucket: ${ buckets [ i ] } ` ) ;
58+ console . log ( err ) ;
59+ errorBuckets . push ( buckets [ i ] ) ;
60+ }
61+ }
62+ if ( errorBuckets . length === 0 ) return Promise . resolve ( ) ;
63+ else return Promise . reject ( errorBuckets ) ;
64+ }
65+
66+ /**
67+ * putConfigFile
68+ * Saves a JSON config file to S3 location.
69+ * @param {JSON } content - JSON object.
70+ * @param {JSON } destS3Bucket - S3 destination bucket.
71+ * @param {JSON } destS3key - S3 destination key.
72+ */
73+ putConfigFile ( content , destS3Bucket , destS3key ) {
74+ console . log ( `Attempting to save content blob destination location: ${ destS3Bucket } /${ destS3key } ` ) ;
75+ console . log ( JSON . stringify ( content ) ) ;
76+
77+ return new Promise ( ( resolve , reject ) => {
78+ let _content = `'use strict';\n\nconst appVariables = {\n` ;
79+
80+ let i = 0 ;
81+ for ( let key in content ) {
82+ if ( i > 0 ) {
83+ _content += ', \n' ;
84+ }
85+ _content += `${ key } : '${ content [ key ] } '` ;
86+ i ++ ;
87+ }
88+ _content += '\n};' ;
89+
90+ let params = {
91+ Bucket : destS3Bucket ,
92+ Key : destS3key ,
93+ Body : _content
94+ } ;
95+
96+ let s3 = new AWS . S3 ( {
97+ signatureVersion : 'v4'
98+ } ) ;
99+ s3 . putObject ( params , function ( err , data ) {
100+ if ( err ) {
101+ console . log ( err ) ;
102+ reject ( `Error creating ${ destS3Bucket } /${ destS3key } content \n${ err } ` ) ;
103+ } else {
104+ console . log ( data ) ;
105+ resolve ( data ) ;
106+ }
107+ } ) ;
108+ } ) ;
109+ }
110+
111+ copyAssets ( manifestKey , sourceS3Bucket , sourceS3prefix , destS3Bucket ) {
112+ console . log ( `source bucket: ${ sourceS3Bucket } ` ) ;
113+ console . log ( `source prefix: ${ sourceS3prefix } ` ) ;
114+ console . log ( `destination bucket: ${ destS3Bucket } ` ) ;
115+
116+ let _self = this ;
117+ return new Promise ( ( resolve , reject ) => {
118+
119+ this . _downloadManifest ( sourceS3Bucket , manifestKey ) . then ( ( data ) => {
120+
121+ fs . readFile ( _self . downloadLocation , 'utf8' , function ( err , data ) {
122+ if ( err ) {
123+ console . log ( err ) ;
124+ reject ( err ) ;
125+ }
126+
127+ let _manifest = _self . _validateJSON ( data ) ;
128+
129+ if ( ! _manifest ) {
130+ reject ( 'Unable to validate downloaded manifest file JSON' ) ;
131+ } else {
132+ _self . _uploadFile ( _manifest . files , 0 , destS3Bucket , `${ sourceS3Bucket } /${ sourceS3prefix } ` ) . then ( ( resp ) => {
133+ console . log ( resp ) ;
134+ resolve ( resp )
135+ } ) . catch ( ( err ) => {
136+ console . log ( err ) ;
137+ reject ( err ) ;
138+ } ) ;
139+ }
140+
141+ } ) ;
142+ } ) . catch ( ( err ) => {
143+ console . log ( err ) ;
144+ reject ( err ) ;
145+ } ) ;
146+
147+ } ) ;
148+ } ;
149+
150+ /**
151+ * Helper function to validate the JSON structure of contents of an import manifest file.
152+ * @param {string } body - JSON object stringify-ed.
153+ * @returns {JSON } - The JSON parsed string or null if string parsing failed
154+ */
155+ _validateJSON ( body ) {
156+ try {
157+ let data = JSON . parse ( body ) ;
158+ console . log ( data ) ;
159+ return data ;
160+ } catch ( e ) {
161+ // failed to parse
162+ console . log ( 'Manifest file contains invalid JSON.' ) ;
163+ return null ;
164+ }
165+ } ;
166+
167+ _uploadFile ( filelist , index , destS3Bucket , sourceS3prefix ) {
168+ let _self = this ;
169+ return new Promise ( ( resolve , reject ) => {
170+
171+ if ( filelist . length > index ) {
172+ let params = {
173+ Bucket : destS3Bucket ,
174+ Key : filelist [ index ] ,
175+ CopySource : [ sourceS3prefix , filelist [ index ] ] . join ( '/' ) ,
176+ MetadataDirective : 'REPLACE'
177+ } ;
178+
179+ params . ContentType = this . _setContentType ( filelist [ index ] ) ;
180+ params . Metadata = {
181+ 'Content-Type' : params . ContentType
182+ } ;
183+ console . log ( params ) ;
184+ let s3 = new AWS . S3 ( {
185+ signatureVersion : 'v4'
186+ } ) ;
187+ s3 . copyObject ( params , function ( err , data ) {
188+ if ( err ) {
189+ console . log ( err ) ;
190+ reject ( `error copying ${ sourceS3prefix } /${ filelist [ index ] } \n${ err } ` ) ;
191+ } else {
192+ console . log ( `${ sourceS3prefix } /${ filelist [ index ] } uploaded successfully` ) ;
193+ let _next = index + 1 ;
194+ _self . _uploadFile ( filelist , _next , destS3Bucket , sourceS3prefix ) . then ( ( resp ) => {
195+ resolve ( resp ) ;
196+ } ) . catch ( ( err2 ) => {
197+ reject ( err2 ) ;
198+ } ) ;
199+ }
200+ } ) ;
201+ } else {
202+ resolve ( `${ index } files copied` ) ;
203+ }
204+
205+ } ) ;
206+
207+ }
208+
209+ /**
210+ * Helper function to download a manifest to local storage for processing.
211+ * @param {string } s3Bucket - Amazon S3 bucket of the manifest to download.
212+ * @param {string } s3Key - Amazon S3 key of the manifest to download.
213+ * @param {string } downloadLocation - Local storage location to download the Amazon S3 object.
214+ */
215+ _downloadManifest ( s3Bucket , s3Key ) {
216+ let _self = this ;
217+ return new Promise ( ( resolve , reject ) => {
218+
219+ let params = {
220+ Bucket : s3Bucket ,
221+ Key : s3Key
222+ } ;
223+
224+ console . log ( `Attempting to download manifest: ${ JSON . stringify ( params ) } ` ) ;
225+
226+ // check to see if the manifest file exists
227+ let s3 = new AWS . S3 ( {
228+ signatureVersion : 'v4'
229+ } ) ;
230+ s3 . headObject ( params , function ( err , metadata ) {
231+ if ( err ) {
232+ console . log ( err ) ;
233+ }
234+
235+ if ( err && err . code === 'NotFound' ) {
236+ // Handle no object on cloud here
237+ console . log ( 'manifest file doesn\'t exist' ) ;
238+ reject ( 'Manifest file was not found.' ) ;
239+ } else {
240+ console . log ( 'manifest file exists' ) ;
241+ console . log ( metadata ) ;
242+ let file = require ( 'fs' ) . createWriteStream ( _self . downloadLocation ) ;
243+
244+ s3 . getObject ( params ) .
245+ on ( 'httpData' , function ( chunk ) {
246+ file . write ( chunk ) ;
247+ } ) .
248+ on ( 'httpDone' , function ( ) {
249+ file . end ( ) ;
250+ console . log ( 'manifest downloaded for processing...' ) ;
251+ resolve ( 'success' ) ;
252+ } ) .
253+ send ( ) ;
254+ }
255+ } ) ;
256+ } ) ;
257+ }
258+
259+ _setContentType ( file ) {
260+ let _contentType = 'binary/octet-stream' ;
261+ if ( file . endsWith ( '.html' ) ) {
262+ _contentType = 'text/html' ;
263+ } else if ( file . endsWith ( '.css' ) ) {
264+ _contentType = 'text/css' ;
265+ } else if ( file . endsWith ( '.png' ) ) {
266+ _contentType = 'image/png' ;
267+ } else if ( file . endsWith ( '.svg' ) ) {
268+ _contentType = 'image/svg+xml' ;
269+ } else if ( file . endsWith ( '.jpg' ) ) {
270+ _contentType = 'image/jpeg' ;
271+ } else if ( file . endsWith ( '.js' ) ) {
272+ _contentType = 'application/javascript' ;
273+ }
274+
275+ return _contentType ;
276+ }
277+
278+
279+ }
280+
281+ module . exports = s3Helper ;
0 commit comments