Skip to content

Commit 07fc8c3

Browse files
author
Will Toozs
committed
Revert "CLDSRV-237-revert-bucket-tagging"
This reverts commit 0f4a09f.
1 parent 75757a5 commit 07fc8c3

File tree

9 files changed

+601
-1
lines changed

9 files changed

+601
-1
lines changed

constants.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ const constants = {
159159
// Bucket specific queries supported by AWS that we do not currently support
160160
// these queries may or may not be supported at object level
161161
unsupportedBucketQueries: [
162-
'tagging',
163162
],
164163
suppressedUtapiEventFields: [
165164
'object',

lib/api/api.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ const { bucketPut } = require('./bucketPut');
2323
const bucketPutACL = require('./bucketPutACL');
2424
const bucketPutCors = require('./bucketPutCors');
2525
const bucketPutVersioning = require('./bucketPutVersioning');
26+
const bucketPutTagging = require('./bucketPutTagging');
27+
const bucketDeleteTagging = require('./bucketDeleteTagging');
28+
const bucketGetTagging = require('./bucketGetTagging');
2629
const bucketPutWebsite = require('./bucketPutWebsite');
2730
const bucketPutReplication = require('./bucketPutReplication');
2831
const bucketPutLifecycle = require('./bucketPutLifecycle');
@@ -269,6 +272,8 @@ const api = {
269272
bucketPutACL,
270273
bucketPutCors,
271274
bucketPutVersioning,
275+
bucketPutTagging,
276+
bucketGetTagging,
272277
bucketPutWebsite,
273278
bucketPutReplication,
274279
bucketGetReplication,
@@ -279,6 +284,7 @@ const api = {
279284
bucketPutPolicy,
280285
bucketGetPolicy,
281286
bucketDeletePolicy,
287+
bucketDeleteTagging,
282288
bucketPutObjectLock,
283289
bucketPutNotification,
284290
bucketGetNotification,

lib/api/bucketDeleteTagging.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
2+
const { metadataValidateBucket } = require('../metadata/metadataUtils');
3+
const { pushMetric } = require('../utapi/utilities');
4+
const metadata = require('../metadata/wrapper');
5+
const util = require('node:util');
6+
7+
/**
8+
* Bucket Delete Tagging - Delete a bucket's Tagging
9+
* @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
10+
* @param {object} request - http request object
11+
* @param {object} log - Werelogs logger
12+
* @param {function} callback - callback to server
13+
* @return {undefined}
14+
*/
15+
async function bucketDeleteTagging(authInfo, request, log, callback) {
16+
const bucketName = request.bucketName;
17+
let error = null;
18+
log.debug('processing request', { method: 'bucketDeleteTagging', bucketName });
19+
20+
let bucket;
21+
const metadataValidateBucketPromise = util.promisify(metadataValidateBucket);
22+
let updateBucketPromise = util.promisify(metadata.updateBucket);
23+
// necessary to bind metadata as updateBucket calls 'this', causing undefined otherwise
24+
updateBucketPromise = updateBucketPromise.bind(metadata);
25+
const metadataValParams = {
26+
authInfo,
27+
bucketName,
28+
requestType: 'bucketDeleteTagging',
29+
};
30+
31+
try {
32+
bucket = await metadataValidateBucketPromise(metadataValParams, log);
33+
bucket.setTags([]);
34+
// eslint-disable-next-line no-unused-expressions
35+
await updateBucketPromise(bucket.getName(), bucket, log);
36+
pushMetric('deleteBucketTagging', log, {
37+
authInfo,
38+
bucket: bucketName,
39+
});
40+
} catch (err) {
41+
error = err;
42+
log.error('error processing request', { error: err,
43+
method: 'deleteBucketTagging', bucketName });
44+
}
45+
const corsHeaders = collectCorsHeaders(request.headers.origin,
46+
request.method, bucket);
47+
return callback(error, corsHeaders);
48+
}
49+
50+
module.exports = bucketDeleteTagging;

lib/api/bucketGetTagging.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
const { metadataValidateBucket } = require('../metadata/metadataUtils');
2+
const util = require('node:util');
3+
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
4+
const { checkExpectedBucketOwner } = require('./apiUtils/authorization/bucketOwner');
5+
const { pushMetric } = require('../utapi/utilities');
6+
const { errors, s3middleware } = require('arsenal');
7+
const escapeForXml = s3middleware.escapeForXml;
8+
9+
// Sample XML response:
10+
/*
11+
<Tagging>
12+
<TagSet>
13+
<Tag>
14+
<Key>string</Key>
15+
<Value>string</Value>
16+
</Tag>
17+
<Tag>
18+
<Key>string</Key>
19+
<Value>string</Value>
20+
</Tag>
21+
</TagSet>
22+
</Tagging>
23+
*/
24+
25+
/**
26+
* @typedef Tag
27+
* @type {object}
28+
* @property {string} Value - Value of the tag.
29+
* @property {string} Key - Key of the tag.
30+
*/
31+
/**
32+
* Convert Versioning Configuration object of a bucket into xml format.
33+
* @param {array.<Tag>} tags - set of bucket tag
34+
* @return {string} - the converted xml string of the versioning configuration
35+
*/
36+
function tagsToXml(tags) {
37+
const xml = [];
38+
39+
xml.push('<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Tagging><TagSet>');
40+
41+
tags.forEach(tag => {
42+
xml.push('<Tag>');
43+
xml.push(`<Key>${escapeForXml(tag.Key)}</Key>`);
44+
xml.push(`<Value>${escapeForXml(tag.Value)}</Value>`);
45+
xml.push('</Tag>');
46+
});
47+
48+
xml.push('</TagSet></Tagging>');
49+
50+
return xml.join('');
51+
}
52+
53+
/**
54+
* bucketGetVersioning - Return Versioning Configuration for bucket
55+
* @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
56+
* @param {object} request - http request object
57+
* @param {object} log - Werelogs logger
58+
* @param {function} callback - callback to respond to http request
59+
* with either error code or xml response body
60+
* @return {undefined}
61+
*/
62+
async function bucketGetTagging(authInfo, request, log, callback) {
63+
log.debug('processing request', { method: 'bucketGetTagging' });
64+
65+
const { bucketName, headers } = request;
66+
const metadataValidateBucketPromise = util.promisify(metadataValidateBucket);
67+
const checkExpectedBucketOwnerPromise = util.promisify(checkExpectedBucketOwner);
68+
69+
const metadataValParams = {
70+
authInfo,
71+
bucketName,
72+
requestType: 'bucketGetTagging',
73+
request,
74+
};
75+
76+
let bucket;
77+
let xml = null;
78+
79+
try {
80+
bucket = await metadataValidateBucketPromise(metadataValParams, log);
81+
// eslint-disable-next-line no-unused-expressions
82+
await checkExpectedBucketOwnerPromise(headers, bucket, log);
83+
const tags = bucket.getTags();
84+
if (!tags || !tags.length) {
85+
log.debug('bucket TagSet does not exist', {
86+
method: 'bucketGetTagging',
87+
});
88+
throw errors.NoSuchTagSet;
89+
}
90+
xml = tagsToXml(tags);
91+
pushMetric('getBucketTagging', log, {
92+
authInfo,
93+
bucket: bucketName,
94+
});
95+
const corsHeaders = collectCorsHeaders(request.headers.origin,
96+
request.method, bucket);
97+
return callback(null, xml, corsHeaders);
98+
} catch (err) {
99+
const corsHeaders = collectCorsHeaders(request.headers.origin,
100+
request.method, bucket);
101+
log.debug('error processing request',
102+
{ method: 'bucketGetTagging', error: err });
103+
104+
return callback(err, corsHeaders);
105+
}
106+
}
107+
108+
module.exports = bucketGetTagging;

lib/api/bucketPutTagging.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const { waterfall } = require('async');
2+
const { s3middleware } = require('arsenal');
3+
4+
5+
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
6+
const { metadataValidateBucket } = require('../metadata/metadataUtils');
7+
const metadata = require('../metadata/wrapper');
8+
const { pushMetric } = require('../utapi/utilities');
9+
const { checkExpectedBucketOwner } = require('./apiUtils/authorization/bucketOwner');
10+
const { parseTagXml } = s3middleware.tagging;
11+
12+
/**
13+
* Format of xml request:
14+
15+
<Tagging xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
16+
<TagSet>
17+
<Tag>
18+
<Key>string</Key>
19+
<Value>string</Value>
20+
</Tag>
21+
</TagSet>
22+
</Tagging>
23+
*/
24+
25+
/**
26+
* Bucket Put Tagging - Create or update bucket Tagging
27+
* @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
28+
* @param {object} request - http request object
29+
* @param {object} log - Werelogs logger
30+
* @param {function} callback - callback to server
31+
* @return {undefined}
32+
*/
33+
function bucketPutTagging(authInfo, request, log, callback) {
34+
log.debug('processing request', { method: 'bucketPutTagging' });
35+
36+
const { bucketName, headers } = request;
37+
const metadataValParams = {
38+
authInfo,
39+
bucketName,
40+
requestType: 'bucketPutTagging',
41+
};
42+
let bucket = null;
43+
return waterfall([
44+
next => metadataValidateBucket(metadataValParams, log,
45+
(err, b) => {
46+
bucket = b;
47+
return next(err);
48+
}),
49+
next => checkExpectedBucketOwner(headers, bucket, log, next),
50+
next => parseTagXml(request.post, log, next),
51+
(tags, next) => {
52+
const tagArray = [];
53+
Object.keys(tags).forEach(key => {
54+
tagArray.push({ Value: tags[key], Key: key });
55+
});
56+
bucket.setTags(tagArray);
57+
metadata.updateBucket(bucket.getName(), bucket, log, err =>
58+
next(err));
59+
},
60+
], (err) => {
61+
const corsHeaders = collectCorsHeaders(request.headers.origin,
62+
request.method, bucket);
63+
pushMetric('putBucketTagging', log, {
64+
authInfo,
65+
bucket: bucketName,
66+
});
67+
return callback(err, corsHeaders);
68+
});
69+
}
70+
71+
module.exports = bucketPutTagging;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
const assert = require('assert');
2+
const { S3 } = require('aws-sdk');
3+
const async = require('async');
4+
const assertError = require('../../../../utilities/bucketTagging-util');
5+
6+
const getConfig = require('../support/config');
7+
8+
const bucket = 'policyputtaggingtestbucket';
9+
10+
const validTagging = {
11+
TagSet: [
12+
{
13+
Key: 'key1',
14+
Value: 'value1',
15+
},
16+
{
17+
Key: 'key2',
18+
Value: 'value2',
19+
},
20+
],
21+
};
22+
23+
describe('aws-sdk test delete bucket tagging', () => {
24+
let s3;
25+
26+
before(() => {
27+
const config = getConfig('default', { signatureVersion: 'v4' });
28+
s3 = new S3(config);
29+
});
30+
31+
beforeEach(done => s3.createBucket({ Bucket: bucket }, done));
32+
33+
afterEach(done => s3.deleteBucket({ Bucket: bucket }, done));
34+
35+
it('should delete tag', done => {
36+
async.series([
37+
next => s3.putBucketTagging({
38+
AccountId: s3.AccountId,
39+
Tagging: validTagging, Bucket: bucket,
40+
}, (err, res) => next(err, res)),
41+
next => s3.getBucketTagging({
42+
AccountId: s3.AccountId,
43+
Bucket: bucket,
44+
}, (err, res) => {
45+
assert.deepStrictEqual(res, validTagging);
46+
next(err, res);
47+
}),
48+
next => s3.deleteBucketTagging({
49+
AccountId: s3.AccountId,
50+
Bucket: bucket,
51+
}, (err, res) => next(err, res)),
52+
next => s3.getBucketTagging({
53+
AccountId: s3.AccountId,
54+
Bucket: bucket,
55+
}, next),
56+
], (err) => {
57+
assertError(err, 'NoSuchTagSet');
58+
done();
59+
});
60+
});
61+
62+
it('should make no change when deleting tags on bucket with no tags', done => {
63+
async.series([
64+
next => s3.getBucketTagging({
65+
AccountId: s3.AccountId,
66+
Bucket: bucket,
67+
}, (err) => {
68+
assertError(err, 'NoSuchTagSet');
69+
next();
70+
}),
71+
next => s3.deleteBucketTagging({
72+
AccountId: s3.AccountId,
73+
Bucket: bucket,
74+
}, (err, res) => next(err, res)),
75+
next => s3.getBucketTagging({
76+
AccountId: s3.AccountId,
77+
Bucket: bucket,
78+
}, (err) => {
79+
assertError(err, 'NoSuchTagSet');
80+
next();
81+
}),
82+
], done);
83+
});
84+
});

0 commit comments

Comments
 (0)