@@ -7,7 +7,10 @@ var last = require('101/last');
77var isFunction = require ( '101/is-function' ) ;
88var debug = require ( 'debug' ) ( 'runnable-api:infra-code-version:model' ) ;
99var regexpQuote = require ( 'regexp-quote' ) ;
10- var crypto = require ( 'crypto' ) ;
10+ var bcrypt = require ( 'bcrypt' ) ;
11+ var jsonHash = require ( 'json-stable-stringify' ) ;
12+ var dogstatsd = require ( 'models/datadog' ) ;
13+ var uuid = require ( 'uuid' ) ;
1114
1215var path = require ( 'path' ) ;
1316var join = path . join ;
@@ -216,38 +219,46 @@ InfraCodeVersionSchema.methods.createFs = function (data, cb) {
216219 infraCodeVersion . files . push ( s3Data ) ;
217220 var fileData = infraCodeVersion . files . pop ( ) . toJSON ( ) ;
218221 var fileKey , dirKey ;
219- var attrs = {
220- edited : true
221- } ;
222+
222223 if ( last ( fileData . Key ) === '/' ) {
223224 fileKey = fileData . Key . slice ( 0 , - 1 ) ;
224225 dirKey = fileData . Key ;
225- attrs . hash = hashString ( data . body . toString ( ) ) ;
226+ update ( ) ;
226227 }
227228 else {
228229 fileKey = fileData . Key ;
229230 dirKey = join ( fileData . Key , '/' ) ;
231+ hashString ( data . body , function ( err , hash ) {
232+ if ( err ) { return cb ( err ) ; }
233+ fileData . hash = hash ;
234+ update ( ) ;
235+ } ) ;
230236 }
237+
231238 // atomic update
232- InfraCodeVersion . update ( {
233- _id : infraCodeVersion . _id ,
234- 'files.Key' : { $nin : [ fileKey , dirKey ] }
235- } , {
236- $push : {
237- files : fileData
238- } ,
239- $set : attrs
240- } , function ( err , numUpdated ) {
241- if ( err ) {
242- cb ( err ) ;
243- }
244- else if ( numUpdated === 0 ) {
245- cb ( Boom . conflict ( 'Fs at path already exists: ' + fullpath ) ) ;
246- }
247- else {
248- cb ( null , fileData ) ;
249- }
250- } ) ;
239+ function update ( ) {
240+ InfraCodeVersion . update ( {
241+ _id : infraCodeVersion . _id ,
242+ 'files.Key' : { $nin : [ fileKey , dirKey ] }
243+ } , {
244+ $push : {
245+ files : fileData
246+ } ,
247+ $set : {
248+ edited : true
249+ }
250+ } , function ( err , numUpdated ) {
251+ if ( err ) {
252+ cb ( err ) ;
253+ }
254+ else if ( numUpdated === 0 ) {
255+ cb ( Boom . conflict ( 'Fs at path already exists: ' + fullpath ) ) ;
256+ }
257+ else {
258+ cb ( null , fileData ) ;
259+ }
260+ } ) ;
261+ }
251262 }
252263} ;
253264
@@ -262,6 +273,7 @@ InfraCodeVersionSchema.methods.updateFile = function (fullpath, body, cb) {
262273 async . waterfall ( [
263274 findFile ,
264275 updateFile ,
276+ calcHash ,
265277 updateModel
266278 ] , cb ) ;
267279 function findFile ( cb ) {
@@ -282,6 +294,13 @@ InfraCodeVersionSchema.methods.updateFile = function (fullpath, body, cb) {
282294 cb ( err , file , fileData ) ;
283295 } ) ;
284296 }
297+ function calcHash ( file , fileData , cb ) {
298+ hashString ( body , function ( err , hash ) {
299+ if ( err ) { return cb ( err ) ; }
300+ fileData . hash = hash ;
301+ cb ( null , file , fileData ) ;
302+ } ) ;
303+ }
285304 function updateModel ( file , fileData , cb ) {
286305 file . set ( fileData ) ;
287306 InfraCodeVersion . update ( {
@@ -290,7 +309,6 @@ InfraCodeVersionSchema.methods.updateFile = function (fullpath, body, cb) {
290309 } , {
291310 $set : {
292311 'files.$' : file . toJSON ( ) ,
293- 'hash' : hashString ( body . toString ( ) ) ,
294312 edited : true
295313 }
296314 } , function ( err ) {
@@ -544,13 +562,66 @@ InfraCodeVersionSchema.methods.copyFilesFromSource = function (sourceInfraCodeVe
544562 sourceVersion . files ,
545563 function ( file , cb ) {
546564 // this protects the scope of bucket
547- bucket . copyFileFrom ( file , cb ) ;
565+ bucket . copyFileFrom ( file , function ( err , newFile ) {
566+ if ( err ) { return cb ( err ) ; }
567+ newFile . hash = file . hash ;
568+ cb ( null , newFile ) ;
569+ } ) ;
548570 } ,
549571 callback ) ;
550572 }
551573} ;
552574
553- function hashString ( data ) {
554- return crypto . createHash ( 'md5' ) . update ( data . toString ( ) . trim ( ) ) . digest ( 'hex' ) ;
575+ /**
576+ * create a map of file hashes with filepath as key
577+ * @param {Function } cb callback
578+ */
579+ InfraCodeVersionSchema . methods . getHash = function ( cb ) {
580+ InfraCodeVersion . findOne ( {
581+ _id : this . _id
582+ } , function ( err , infraCodeVersion ) {
583+ if ( err ) { return cb ( err ) ; }
584+ var hashMap = { } ;
585+ var invalidate = false ;
586+ infraCodeVersion . files . forEach ( function ( item ) {
587+ var filePath = item . Key . substr ( item . Key . indexOf ( '/' ) ) ;
588+ if ( item . isDir ) {
589+ // ensure dirs have some hash
590+ hashMap [ filePath ] = '1' ;
591+ } else if ( item . hash ) {
592+ hashMap [ filePath ] = item . hash ;
593+ } else {
594+ // file without hash. this should not happen.
595+ // skip dedup by returning something that will never match
596+ invalidate = true ;
597+ }
598+ } ) ;
599+
600+ if ( invalidate ) {
601+ cb ( null , uuid ( ) ) ;
602+ } else {
603+ hashString ( jsonHash ( hashMap ) , cb ) ;
604+ }
605+ } ) ;
606+ } ;
607+
608+
609+ function hashString ( data , cb ) {
610+ // salt from require('bcrypt'.enSaltSync(1);
611+ var salt = '$2a$04$fLg/VU5eeDAUARmPVfyUo.' ;
612+ var start = new Date ( ) ;
613+ bcrypt . hash ( data
614+ . replace ( / [ \s \uFEFF \xA0 ] + \n / g, '\n' ) // trim whitespace after line
615+ . replace ( / \n [ \s \uFEFF \xA0 ] * \n / g, '\n' ) // remove blank lines
616+ . replace ( / ^ [ \s \uFEFF \xA0 ] * \n / g, '' ) // remove start of file blank lines
617+ . replace ( / [ \s \uFEFF \xA0 ] + $ / g, '\n' ) , salt , function ( err , hash ) {
618+ if ( err ) { return cb ( err ) ; }
619+ dogstatsd . timing ( 'api.infraCodeVersion.hashTime' , new Date ( ) - start , 1 ,
620+ [ 'length:' + data . length ] ) ;
621+ cb ( null , hash ) ;
622+ } ) ;
555623}
624+
556625var InfraCodeVersion = module . exports = mongoose . model ( 'InfraCodeVersion' , InfraCodeVersionSchema ) ;
626+
627+
0 commit comments