mirror of https://github.com/therootcompany/s3.js
AJ ONeal
4 years ago
commit
9a332a12d9
10 changed files with 657 additions and 0 deletions
@ -0,0 +1,3 @@ |
|||
.env |
|||
node_modules |
|||
.*.sw* |
@ -0,0 +1,4 @@ |
|||
{ "node": true |
|||
, "browser": true |
|||
, "esversion": 8 |
|||
} |
@ -0,0 +1,8 @@ |
|||
{ |
|||
"bracketSpacing": true, |
|||
"printWidth": 80, |
|||
"singleQuote": true, |
|||
"tabWidth": 4, |
|||
"trailingComma": "none", |
|||
"useTabs": false |
|||
} |
@ -0,0 +1,41 @@ |
|||
Copyright 2020 AJ ONeal |
|||
|
|||
This is open source software; you can redistribute it and/or modify it under the |
|||
terms of either: |
|||
|
|||
a) the "MIT License" |
|||
b) the "Apache-2.0 License" |
|||
|
|||
MIT License |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
|||
|
|||
Apache-2.0 License Summary |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
@ -0,0 +1,22 @@ |
|||
# [s3.js](https://git.rootprojects.org/root/s3.js) | a [Root](https://rootprojects.org) project |
|||
|
|||
> Minimalist S3 client |
|||
|
|||
A lightweight alternative to the s3 SDK that uses @root/request and aws4. |
|||
|
|||
* set() |
|||
* get() |
|||
* head() |
|||
* delete() |
|||
|
|||
```js |
|||
s3.set({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key, |
|||
body |
|||
}) |
|||
``` |
@ -0,0 +1,208 @@ |
|||
'use strict'; |
|||
|
|||
var aws4 = require('aws4'); |
|||
var request = require('@root/request'); |
|||
var env = process.env; |
|||
|
|||
module.exports = { |
|||
// HEAD
|
|||
head: function({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key |
|||
}) { |
|||
// TODO support minio
|
|||
/* |
|||
var awsHost = config.awsHost; |
|||
if (!awsHost) { |
|||
if (awsRegion) { |
|||
awsHost = awsHost || 's3.'+awsRegion+'.amazonaws.com'; |
|||
} else { |
|||
// default
|
|||
awsHost = 's3.amazonaws.com'; |
|||
} |
|||
} |
|||
*/ |
|||
/* |
|||
if (env.AWS_ACCESS_KEY) { |
|||
accessKeyId = accessKeyId || env.AWS_ACCESS_KEY; |
|||
secretAccessKey = secretAccessKey || env.AWS_SECRET_ACCESS_KEY; |
|||
bucket = bucket || env.AWS_BUCKET; |
|||
prefix = prefix || env.AWS_BUCKET_PREFIX; |
|||
region = region || env.AWS_REGION; |
|||
endpoint = endpoint || env.AWS_ENDPOINT; |
|||
} |
|||
*/ |
|||
prefix = prefix || ''; |
|||
if (prefix) { |
|||
// whatever => whatever/
|
|||
// whatever/ => whatever/
|
|||
prefix = prefix.replace(/\/?$/, '/'); |
|||
} |
|||
var signed = aws4.sign( |
|||
{ |
|||
// host: awsHost
|
|||
service: 's3', |
|||
region: region, |
|||
path: '/' + bucket + '/' + prefix + key, |
|||
method: 'HEAD', |
|||
signQuery: true |
|||
}, |
|||
{ accessKeyId: accessKeyId, secretAccessKey: secretAccessKey } |
|||
); |
|||
var url = 'https://' + signed.hostname + signed.path; |
|||
|
|||
return request({ method: 'HEAD', url }).then(function(resp) { |
|||
if (200 === resp.statusCode) { |
|||
return resp; |
|||
} |
|||
var err = new Error( |
|||
'expected status 200 but got ' + |
|||
resp.statusCode + |
|||
'. See err.response for more info.' |
|||
); |
|||
err.url = url; |
|||
err.response = resp; |
|||
throw err; |
|||
}); |
|||
}, |
|||
|
|||
// GET
|
|||
get: function({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key, |
|||
json |
|||
}) { |
|||
prefix = prefix || ''; |
|||
if (prefix) { |
|||
prefix = prefix.replace(/\/?$/, '/'); |
|||
} |
|||
var signed = aws4.sign( |
|||
{ |
|||
service: 's3', |
|||
region: region, |
|||
path: '/' + bucket + '/' + prefix + key, |
|||
method: 'GET', |
|||
signQuery: true |
|||
}, |
|||
{ accessKeyId: accessKeyId, secretAccessKey: secretAccessKey } |
|||
); |
|||
var url = 'https://' + signed.hostname + signed.path; |
|||
|
|||
// stay binary by default
|
|||
var encoding = null; |
|||
if (json) { |
|||
encoding = undefined; |
|||
} |
|||
return request({ method: 'GET', url, encoding: null, json: json }).then( |
|||
function(resp) { |
|||
if (200 === resp.statusCode) { |
|||
return resp; |
|||
} |
|||
var err = new Error( |
|||
'expected status 200 but got ' + |
|||
resp.statusCode + |
|||
'. See err.response for more info.' |
|||
); |
|||
err.url = url; |
|||
err.response = resp; |
|||
throw err; |
|||
} |
|||
); |
|||
}, |
|||
|
|||
// PUT
|
|||
set: function({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key, |
|||
body, |
|||
size |
|||
}) { |
|||
prefix = prefix || ''; |
|||
if (prefix) { |
|||
prefix = prefix.replace(/\/?$/, '/'); |
|||
} |
|||
var signed = aws4.sign( |
|||
{ |
|||
service: 's3', |
|||
region: region, |
|||
path: '/' + bucket + '/' + prefix + key, |
|||
method: 'PUT', |
|||
signQuery: true |
|||
}, |
|||
{ accessKeyId: accessKeyId, secretAccessKey: secretAccessKey } |
|||
); |
|||
var url = 'https://' + signed.hostname + signed.path; |
|||
var headers = {}; |
|||
if ('undefined' !== typeof size) { |
|||
headers['Content-Length'] = size; |
|||
} |
|||
|
|||
return request({ method: 'PUT', url, body, headers }).then(function( |
|||
resp |
|||
) { |
|||
if (200 === resp.statusCode) { |
|||
return resp; |
|||
} |
|||
var err = new Error( |
|||
'expected status 201 but got ' + |
|||
resp.statusCode + |
|||
'. See err.response for more info.' |
|||
); |
|||
err.url = url; |
|||
err.response = resp; |
|||
throw err; |
|||
}); |
|||
}, |
|||
|
|||
// DELETE
|
|||
del: function({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key |
|||
}) { |
|||
prefix = prefix || ''; |
|||
if (prefix) { |
|||
prefix = prefix.replace(/\/?$/, '/'); |
|||
} |
|||
var signed = aws4.sign( |
|||
{ |
|||
service: 's3', |
|||
region: region, |
|||
path: '/' + bucket + '/' + prefix + key, |
|||
method: 'DELETE', |
|||
signQuery: true |
|||
}, |
|||
{ accessKeyId: accessKeyId, secretAccessKey: secretAccessKey } |
|||
); |
|||
var url = 'https://' + signed.hostname + signed.path; |
|||
|
|||
return request({ method: 'DELETE', url }).then(function(resp) { |
|||
if (204 === resp.statusCode) { |
|||
return resp; |
|||
} |
|||
var err = new Error( |
|||
'expected status 204 but got ' + |
|||
resp.statusCode + |
|||
'. See err.response for more info.' |
|||
); |
|||
err.url = url; |
|||
err.response = resp; |
|||
throw err; |
|||
}); |
|||
} |
|||
}; |
@ -0,0 +1,24 @@ |
|||
{ |
|||
"name": "@root/s3", |
|||
"version": "1.0.0", |
|||
"lockfileVersion": 1, |
|||
"requires": true, |
|||
"dependencies": { |
|||
"@root/request": { |
|||
"version": "1.5.0", |
|||
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.5.0.tgz", |
|||
"integrity": "sha512-J9RUIwVU99/cOVuDVYlNpr4G0A1/3ZxhCXIRiTZzu8RntOnb0lmDBMckhaus5ry9x/dBqJKDplFIgwHbLi6rLA==" |
|||
}, |
|||
"aws4": { |
|||
"version": "1.9.1", |
|||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", |
|||
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" |
|||
}, |
|||
"dotenv": { |
|||
"version": "8.2.0", |
|||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", |
|||
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", |
|||
"dev": true |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
{ |
|||
"name": "@root/s3", |
|||
"version": "1.0.0", |
|||
"description": "A simple, lightweight s3 client with only 2 dependencies", |
|||
"main": "index.js", |
|||
"files": [ |
|||
"lib" |
|||
], |
|||
"directories": { |
|||
"example": "examples" |
|||
}, |
|||
"scripts": { |
|||
"test": "node test.js" |
|||
}, |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "https://git.rootprojects.org/root/s3.js.git" |
|||
}, |
|||
"keywords": [ |
|||
"s3", |
|||
"lightweight", |
|||
"alternative" |
|||
], |
|||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", |
|||
"license": "(MIT OR Apache-2.0)", |
|||
"dependencies": { |
|||
"@root/request": "^1.5.0", |
|||
"aws4": "^1.9.1" |
|||
}, |
|||
"devDependencies": { |
|||
"dotenv": "^8.2.0" |
|||
} |
|||
} |
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,314 @@ |
|||
'use strict'; |
|||
|
|||
require('dotenv').config(); |
|||
var env = process.env; |
|||
var s3 = require('./index.js'); |
|||
|
|||
var accessKeyId = env.AWS_ACCESS_KEY; |
|||
var secretAccessKey = env.AWS_SECRET_ACCESS_KEY; |
|||
var region = env.AWS_REGION; |
|||
var bucket = env.AWS_BUCKET; |
|||
var prefix = env.AWS_BUCKET_PREFIX; |
|||
|
|||
var key = 'test-file'; |
|||
var fs = require('fs'); |
|||
|
|||
async function run() { |
|||
// UPLOAD
|
|||
//var testFile = __filename;
|
|||
var testFile = 'test.bin'; |
|||
var stat = fs.statSync(testFile); |
|||
var size = stat.size; |
|||
var stream = fs.createReadStream(testFile); |
|||
var file = fs.readFileSync(testFile); |
|||
await s3 |
|||
.set({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key, |
|||
body: stream, |
|||
size |
|||
}) |
|||
.then(function(resp) { |
|||
console.info('PASS: stream uploaded file'); |
|||
return null; |
|||
}) |
|||
.catch(function(err) { |
|||
console.error('Error:'); |
|||
console.error('PUT Response:'); |
|||
if (err.response) { |
|||
console.error(err.response.statusCode); |
|||
console.error(err.response.headers); |
|||
console.error( |
|||
(err.response.body && err.response.body) || |
|||
JSON.stringify(err.response.body) |
|||
); |
|||
} else { |
|||
console.error(err); |
|||
} |
|||
process.exit(1); |
|||
}); |
|||
|
|||
// CHECK DOES EXIST
|
|||
await s3 |
|||
.head({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key |
|||
}) |
|||
.then(function(resp) { |
|||
console.info('PASS: streamed file exists'); |
|||
return null; |
|||
}) |
|||
.catch(function(err) { |
|||
console.error('HEAD Response:'); |
|||
if (err.response) { |
|||
console.error(err.response.statusCode); |
|||
console.error(err.response.headers); |
|||
} else { |
|||
console.error(err); |
|||
} |
|||
process.exit(1); |
|||
}); |
|||
|
|||
// GET STREAMED FILE
|
|||
await s3 |
|||
.get({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key |
|||
}) |
|||
.then(function(resp) { |
|||
if (file.toString('binary') === resp.body.toString('binary')) { |
|||
console.info( |
|||
'PASS: streamed file downloaded with same contents' |
|||
); |
|||
return null; |
|||
} |
|||
throw new Error("file contents don't match"); |
|||
}) |
|||
.catch(function(err) { |
|||
console.error('Error:'); |
|||
console.error('GET Response:'); |
|||
if (err.response) { |
|||
console.error(err.response.statusCode); |
|||
console.error(err.response.headers); |
|||
} else { |
|||
console.error(err); |
|||
} |
|||
process.exit(1); |
|||
}); |
|||
|
|||
// DELETE TEST FILE
|
|||
await s3 |
|||
.del({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key |
|||
}) |
|||
.then(function(resp) { |
|||
console.info('PASS: delete file'); |
|||
return null; |
|||
}) |
|||
.catch(function(err) { |
|||
console.error('Error:'); |
|||
console.error('DELETE Response:'); |
|||
if (err.response) { |
|||
console.error(err.response.statusCode); |
|||
console.error(err.response.headers); |
|||
} else { |
|||
console.error(err); |
|||
} |
|||
process.exit(1); |
|||
}); |
|||
|
|||
// SHOULD NOT EXIST
|
|||
await s3 |
|||
.head({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key |
|||
}) |
|||
.then(function(resp) { |
|||
var err = new Error('file should not exist'); |
|||
err.response = resp; |
|||
throw err; |
|||
}) |
|||
.catch(function(err) { |
|||
if (err.response && 404 === err.response.statusCode) { |
|||
console.info('PASS: streamed file deleted'); |
|||
return null; |
|||
} |
|||
console.error('Error:'); |
|||
console.error('HEAD Response:'); |
|||
if (err.response) { |
|||
console.error(err.response.statusCode); |
|||
console.error(err.response.headers); |
|||
} else { |
|||
console.error(err); |
|||
} |
|||
process.exit(1); |
|||
}); |
|||
|
|||
// CREATE WITHOUT STREAM
|
|||
await s3 |
|||
.set({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key, |
|||
body: file |
|||
}) |
|||
.then(function(resp) { |
|||
console.info('PASS: one-shot upload'); |
|||
return null; |
|||
}) |
|||
.catch(function(err) { |
|||
console.error('Error:'); |
|||
console.error('PUT Response:'); |
|||
if (err.response) { |
|||
console.error(err.response.statusCode); |
|||
console.error(err.response.headers); |
|||
console.error( |
|||
(err.response.body && err.response.body) || |
|||
JSON.stringify(err.response.body) |
|||
); |
|||
} else { |
|||
console.error(err); |
|||
} |
|||
process.exit(1); |
|||
}); |
|||
|
|||
// CHECK DOES EXIST
|
|||
await s3 |
|||
.head({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key |
|||
}) |
|||
.then(function(resp) { |
|||
console.info('PASS: one-shot upload exists'); |
|||
return null; |
|||
}) |
|||
.catch(function(err) { |
|||
console.error('Error:'); |
|||
console.error('HEAD Response:'); |
|||
if (err.response) { |
|||
console.error(err.response.statusCode); |
|||
console.error(err.response.headers); |
|||
} else { |
|||
console.error(err); |
|||
} |
|||
process.exit(1); |
|||
}); |
|||
|
|||
// GET ONE-SHOT FILE
|
|||
await s3 |
|||
.get({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key |
|||
}) |
|||
.then(function(resp) { |
|||
if (file.toString('binary') === resp.body.toString('binary')) { |
|||
console.info( |
|||
'PASS: one-shot file downloaded with same contents' |
|||
); |
|||
return null; |
|||
} |
|||
throw new Error("file contents don't match"); |
|||
}) |
|||
.catch(function(err) { |
|||
console.error('Error:'); |
|||
console.error('GET Response:'); |
|||
if (err.response) { |
|||
console.error(err.response.statusCode); |
|||
console.error(err.response.headers); |
|||
} else { |
|||
console.error(err); |
|||
} |
|||
process.exit(1); |
|||
}); |
|||
|
|||
// DELETE FILE
|
|||
await s3 |
|||
.del({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key |
|||
}) |
|||
.then(function(resp) { |
|||
console.info('PASS: DELETE'); |
|||
return null; |
|||
}) |
|||
.catch(function(err) { |
|||
console.error('Error:'); |
|||
console.error('DELETE Response:'); |
|||
if (err.response) { |
|||
console.error(err.response.statusCode); |
|||
console.error(err.response.headers); |
|||
} else { |
|||
console.error(err); |
|||
} |
|||
process.exit(1); |
|||
}); |
|||
|
|||
// SHOULD NOT EXIST
|
|||
await s3 |
|||
.head({ |
|||
accessKeyId, |
|||
secretAccessKey, |
|||
region, |
|||
bucket, |
|||
prefix, |
|||
key |
|||
}) |
|||
.then(function(resp) { |
|||
var err = new Error('file should not exist'); |
|||
err.response = resp; |
|||
throw err; |
|||
}) |
|||
.catch(function(err) { |
|||
if (err.response && 404 === err.response.statusCode) { |
|||
console.info('PASS: streamed file deleted'); |
|||
return null; |
|||
} |
|||
console.error('Error:'); |
|||
console.error('HEAD Response:'); |
|||
if (err.response) { |
|||
console.error(err.response.statusCode); |
|||
console.error(err.response.headers); |
|||
} else { |
|||
console.error(err); |
|||
} |
|||
process.exit(1); |
|||
}); |
|||
} |
|||
|
|||
run(); |
Loading…
Reference in new issue