@@ -3,16 +3,24 @@ const async = require('async');
3
3
const readline = require ( 'readline' ) ;
4
4
const { Logger } = require ( 'werelogs' ) ;
5
5
const {
6
- fetchObjectMetadata ,
6
+ httpRequest ,
7
7
putObjectMetadata,
8
8
} = require ( './repairDuplicateVersionsSuite' ) ;
9
9
10
10
const log = new Logger ( 's3utils:repairDuplicateVersionIds' ) ;
11
11
12
12
const {
13
13
OBJECT_REPAIR_BUCKETD_HOSTPORT ,
14
+ OBJECT_REPAIR_TLS_KEY_PATH ,
15
+ OBJECT_REPAIR_TLS_CERT_PATH ,
16
+ OBJECT_REPAIR_TLS_CA_PATH ,
14
17
} = process . env ;
15
18
19
+ const useHttps = ( OBJECT_REPAIR_TLS_KEY_PATH !== undefined
20
+ && OBJECT_REPAIR_TLS_KEY_PATH !== ''
21
+ && OBJECT_REPAIR_TLS_CERT_PATH !== undefined
22
+ && OBJECT_REPAIR_TLS_CERT_PATH !== '' ) ;
23
+
16
24
const USAGE = `
17
25
repairDuplicateVersionIds.js
18
26
@@ -51,10 +59,13 @@ const objectsToRepair = [];
51
59
52
60
const status = {
53
61
logLinesRead : 0 ,
62
+ objectsSkipped : 0 ,
54
63
objectsRepaired : 0 ,
55
64
objectsErrors : 0 ,
56
65
} ;
57
66
67
+ const errorMetadataUpdated = new Error ( 'metadata updated' ) ;
68
+
58
69
function logProgress ( message ) {
59
70
log . info ( message , { ...status , objectsToRepair : objectsToRepair . length } ) ;
60
71
}
@@ -93,9 +104,26 @@ function readVerifyLog(cb) {
93
104
} ) ;
94
105
}
95
106
107
+ function fetchRawObjectMetadata ( objectUrl , cb ) {
108
+ if ( ! objectUrl . startsWith ( 's3://' ) ) {
109
+ return cb ( new Error ( `malformed object URL ${ objectUrl } : must start with "s3://"` ) ) ;
110
+ }
111
+ const bucketAndObject = objectUrl . slice ( 5 ) ;
112
+ const url = `${ useHttps ? 'https' : 'http' } ://${ OBJECT_REPAIR_BUCKETD_HOSTPORT } /default/bucket/${ bucketAndObject } ` ;
113
+ return httpRequest ( 'GET' , url , null , ( err , res ) => {
114
+ if ( err ) {
115
+ return cb ( err ) ;
116
+ }
117
+ if ( res . statusCode !== 200 ) {
118
+ return cb ( new Error ( `GET ${ url } returned status ${ res . statusCode } ` ) ) ;
119
+ }
120
+ return cb ( null , res . body ) ;
121
+ } ) ;
122
+ }
123
+
96
124
function repairObject ( objInfo , cb ) {
97
125
async . waterfall ( [
98
- next => fetchObjectMetadata ( objInfo . objectUrl , ( err , md ) => {
126
+ next => fetchRawObjectMetadata ( objInfo . objectUrl , ( err , md ) => {
99
127
if ( err ) {
100
128
log . error ( 'error fetching object location' , {
101
129
objectUrl : objInfo . objectUrl ,
@@ -105,10 +133,32 @@ function repairObject(objInfo, cb) {
105
133
}
106
134
return next ( null , md ) ;
107
135
} ) ,
108
- ( md , next ) => {
136
+ ( rawMD , next ) => {
137
+ const reVersionIds = / " v e r s i o n I d " : " ( [ ^ " ] * ) " / g;
138
+ const versionIds = [ ] ;
139
+ // eslint-disable-next-line no-constant-condition
140
+ while ( true ) {
141
+ const reVersionIdMatch = reVersionIds . exec ( rawMD ) ;
142
+ if ( ! reVersionIdMatch ) {
143
+ break ;
144
+ }
145
+ versionIds . push ( reVersionIdMatch [ 1 ] ) ;
146
+ }
147
+ if ( versionIds . length < 2 ) {
148
+ log . info ( 'skipping repair: master key has no more duplicate "versionId"' , {
149
+ objectUrl : objInfo . objectUrl ,
150
+ versionId : versionIds . length > 0 ? versionIds [ 0 ] : undefined ,
151
+ } ) ;
152
+ return next ( errorMetadataUpdated ) ;
153
+ }
154
+ const md = JSON . parse ( rawMD ) ;
155
+ // use "versionId" from the parsed metadata instead of
156
+ // `objInfo.firstVersionId`, since it may have changed
157
+ // since the scan ran
158
+ //
109
159
// eslint-disable-next-line no-param-reassign
110
- md . versionId = objInfo . firstVersionId ;
111
- putObjectMetadata ( objInfo . objectUrl , md , err => {
160
+ [ md . versionId ] = versionIds ;
161
+ return putObjectMetadata ( objInfo . objectUrl , md , err => {
112
162
if ( err ) {
113
163
log . error ( 'error putting object metadata to master key' , {
114
164
objectUrl : objInfo . objectUrl ,
@@ -119,27 +169,35 @@ function repairObject(objInfo, cb) {
119
169
return next ( null , md ) ;
120
170
} ) ;
121
171
} ,
122
- ( md , next ) => putObjectMetadata ( objInfo . versionedKeyUrl , md , err => {
123
- if ( err ) {
124
- log . error ( 'error putting object metadata to versioned key' , {
172
+ ( md , next ) => {
173
+ const versionedKeyUrl = `${ objInfo . objectUrl } ${ encodeURIComponent ( `\0${ md . versionId } ` ) } ` ;
174
+ putObjectMetadata ( versionedKeyUrl , md , err => {
175
+ if ( err ) {
176
+ log . error ( 'error putting object metadata to versioned key' , {
177
+ objectUrl : objInfo . objectUrl ,
178
+ versionedKeyUrl,
179
+ error : { message : err . message } ,
180
+ } ) ;
181
+ return next ( err ) ;
182
+ }
183
+ return next ( null , md ) ;
184
+ } ) ;
185
+ } ,
186
+ ] , ( err , md ) => {
187
+ if ( err ) {
188
+ if ( err === errorMetadataUpdated ) {
189
+ status . objectsSkipped += 1 ;
190
+ } else {
191
+ log . error ( 'an error occurred repairing object' , {
125
192
objectUrl : objInfo . objectUrl ,
126
- versionedKeyUrl : objInfo . versionedKeyUrl ,
127
193
error : { message : err . message } ,
128
194
} ) ;
129
- return next ( err ) ;
195
+ status . objectsErrors += 1 ;
130
196
}
131
- return next ( ) ;
132
- } ) ,
133
- ] , err => {
134
- if ( err ) {
135
- log . error ( 'an error occurred repairing object' , {
136
- objectUrl : objInfo . objectUrl ,
137
- error : { message : err . message } ,
138
- } ) ;
139
- status . objectsErrors += 1 ;
140
197
} else {
141
198
log . info ( 'repaired object metadata' , {
142
199
objectUrl : objInfo . objectUrl ,
200
+ versionId : md . versionId ,
143
201
} ) ;
144
202
status . objectsRepaired += 1 ;
145
203
}
0 commit comments