Подтвердить что ты не робот

Node.js & Amazon S3: Как перебирать все файлы в ведре?

Есть ли какая-либо клиентская библиотека Amazon S3 для Node.js, которая позволяет перечислять все файлы в ведре S3?

Самый известный aws2js и knox похоже, не имеют этой функции.

4b9b3361

Ответ 1

Фактически aws2js поддерживает список объектов в ковше на низком уровне с помощью вызова метода s3.get(). Для этого нужно передать параметр prefix, который задокументирован на странице Amazon S3 REST API:

var s3 = require('aws2js').load('s3', awsAccessKeyId, awsSecretAccessKey);    
s3.setBucket(bucketName);

var folder = encodeURI('some/path/to/S3/folder');
var url = '?prefix=' + folder;

s3.get(url, 'xml', function (error, data) {
    console.log(error);
    console.log(data);
});

Переменная data в приведенном выше фрагменте содержит список всех объектов в ведре bucketName.

Ответ 2

Использование официального aws-sdk:

var allKeys = [];
function listAllKeys(marker, cb)
{
  s3.listObjects({Bucket: s3bucket, Marker: marker}, function(err, data){
    allKeys.push(data.Contents);

    if(data.IsTruncated)
      listAllKeys(data.NextMarker, cb);
    else
      cb();
  });
}

см. s3.listObjects

Изменить 2017: Та же основная идея, но теперь рекомендуется listObjectsV2( ... ) и использует ContinuationToken (см. s3.listObjectsV2):

var allKeys = [];
function listAllKeys(token, cb)
{
  var opts = { Bucket: s3bucket };
  if(token) opts.ContinuationToken = token;

  s3.listObjectsV2(opts, function(err, data){
    allKeys = allKeys.concat(data.Contents);

    if(data.IsTruncated)
      listAllKeys(data.NextContinuationToken, cb);
    else
      cb();
  });
}

Ответ 3

Здесь Node код, который я написал, чтобы собрать объекты S3 из усеченных списков.

var params = {
    Bucket: <yourbucket>,
    Prefix: <yourprefix>,
};

var s3DataContents = [];    // Single array of all combined S3 data.Contents

function s3Print() {
    if (program.al) {
        // --al: Print all objects
        console.log(JSON.stringify(s3DataContents, null, "    "));
    } else {
        // --b: Print key only, otherwise also print index 
        var i;
        for (i = 0; i < s3DataContents.length; i++) {
            var head = !program.b ? (i+1) + ': ' : '';
            console.log(head + s3DataContents[i].Key);
        }
    }
}

function s3ListObjects(params, cb) {
    s3.listObjects(params, function(err, data) {
        if (err) {
            console.log("listS3Objects Error:", err);
        } else {
            var contents = data.Contents;
            s3DataContents = s3DataContents.concat(contents);
            if (data.IsTruncated) {
                // Set Marker to last returned key
                params.Marker = contents[contents.length-1].Key;
                s3ListObjects(params, cb);
            } else {
                cb();
            }
        }
    });
}

s3ListObjects(params, s3Print);

Обратите внимание на listObject документация NextMarker, которая НЕ всегда присутствует в возвращенном объекте данных, поэтому Я не использую его вообще в приведенном выше коде...

NextMarker - (String) Когда ответ усечен ( IsTruncatedзначение элемента в ответе истинно), вы можете использовать имя ключа в это поле в качестве маркера в последующем запросе для получения следующего набора объекты. Amazon S3 перечисляет объекты в алфавитном порядке. Примечание. элемент возвращается, только если у вас есть параметр запроса разделителя указано. Если ответ не включает NextMarker, и это усеченный, вы можете использовать значение последнего ключа в ответе как маркер в последующем запросе, чтобы получить следующий набор объектов Клавиши.

Вся программа теперь нажата на https://github.com/kenklin/s3list.

Ответ 4

Опубликован knox-copy, когда я не смог найти хорошее существующее решение. Обертывает все детали разбиения на страницы в API Rest в знакомый поток node:

var knoxCopy = require('knox-copy');

var client = knoxCopy.createClient({
  key: '<api-key-here>',
  secret: '<secret-here>',
  bucket: 'mrbucket'
});

client.streamKeys({
  // omit the prefix to list the whole bucket
  prefix: 'buckets/of/fun' 
}).on('data', function(key) {
  console.log(key);
});

Если вы перечисляете менее 1000 файлов, будет работать одна страница:

client.listPageOfKeys({
  prefix: 'smaller/bucket/o/fun'
}, function(err, page) {
  console.log(page.Contents); // <- Here your list of files
});

Ответ 5

Meekohi дал очень хороший ответ, но (новая) документация утверждает, что NextMarker может быть неопределенным. В этом случае вы должны использовать последний ключ в качестве маркера.

Таким образом, его пример кода может быть изменен на:

var allKeys = [];
function listAllKeys(marker, cb) {
  s3.listObjects({Bucket: s3bucket, Marker: marker}, function(err, data){
    allKeys.push(data.Contents);
    if(data.IsTruncated)
      listAllKeys(data.NextMarker || data.Contents[data.Contents.length-1].Key, cb);
    else
      cb();
  });
}

Не могу прокомментировать исходный ответ, поскольку у меня нет необходимой репутации. Извиняюсь за плохую наценку между прочим.

Ответ 6

Это старый вопрос, и я думаю, что AWS JS SDK сильно изменился с тех пор, как его спросили. Вот еще один способ сделать это в наши дни:

s3.listObjects({Bucket:'mybucket', Prefix:'some-pfx'}).
on('success', function handlePage(r) {
    //... handle page of contents r.data.Contents

    if(r.hasNextPage()) {
        // There another page; handle it
        r.nextPage().on('success', handlePage).send();
    } else {
        // Finished!
    }
}).
on('error', function(r) {
    // Error!
}).
send();

Ответ 7

Если вы хотите получить список ключей только в пределах определенной папки внутри ведра S3, тогда это будет полезно.

В принципе, функция listObjects начнет поиск из Marker, которую мы установили, и будет искать до maxKeys: 1000 как ограничение. поэтому он будет искать по одной папке и вы получите первые 1000 ключей, которые они найдут из другой папки в ведре.

У меня есть много папок внутри моего ведра с префиксом как prod/some date/, Ex: prod/2017/05/12/ ,prod/2017/05/13/,etc.

Я хочу получить список объектов (имена файлов) только в папке prod/2017/05/12/, тогда я укажу prod/2017/05/12/ как мой старт и prod/2017/05/13/ [ваше следующее имя папки] как мой конец и код, который я нарушаю цикл, когда я сталкиваюсь с концом.

Каждый Key в data.Contents будет выглядеть следующим образом.

{      Key: 'prod/2017/05/13/4bf2c675-a417-4c1f-a0b4-22fc45f99207.jpg',
       LastModified: 2017-05-13T00:59:02.000Z,
       ETag: '"630b2sdfsdfs49ef392bcc16c833004f94ae850"',
       Size: 134236366,
       StorageClass: 'STANDARD',
       Owner: { } 
 }

код:

var list = [];

function listAllKeys(s3bucket, start, end) {
  s3.listObjects({
    Bucket: s3bucket,
    Marker: start,
    MaxKeys: 1000,
  }, function(err, data) {
      if (data.Contents) {
        for (var i = 0; i < data.Contents.length; i++) {
         var key = data.Contents[i].Key;    //See above code for the structure of data.Contents
          if (key.substring(0, 19) != end) {
             list.push(key);
          } else {
             break;   // break the loop if end arrived
          }
       }
        console.log(list);
        console.log('Total - ', list.length);      
     }
   });
 }

listAllKeys('BucketName', 'prod/2017/05/12/', 'prod/2017/05/13/');

Вывод:

[ 'prod/2017/05/12/05/4bf2c675-a417-4c1f-a0b4-22fc45f99207.jpg',
  'prod/2017/05/12/05/a36528b9-e071-4b83-a7e6-9b32d6bce6d8.jpg',
  'prod/2017/05/12/05/bc4d6d4b-4455-48b3-a548-7a714c489060.jpg',
  'prod/2017/05/12/05/f4b8d599-80d0-46fa-a996-e73b8fd0cd6d.jpg',
  ... 689 more items ]
Total - 692

Ответ 8

В итоге я создал функцию-оболочку для ListObjectsV2, работает аналогично и принимает те же параметры, но работает рекурсивно, пока IsTruncated = false и не возвращает все ключи, найденные в виде массива во втором параметре функции обратного вызова

const AWS = require('aws-sdk')
const s3 = new AWS.S3()

function listAllKeys(params, cb)
{
   var keys = []
   if(params.data){
      keys = keys.concat(params.data)
   }
   delete params['data']

   s3.listObjectsV2(params, function(err, data){
     if(err){
       cb(err)
     } else if (data.IsTruncated) {
       params['ContinuationToken'] = data.NextContinuationToken
       params['data'] = data.Contents
       listAllKeys(params, cb)
     } else {
       keys = keys.concat(data.Contents)
       cb(null,keys)
     }
   })
}

Ответ 9

с помощью Async Generator

const { S3 } = require('aws-sdk');
const s3 = new S3();

async function* listAllKeys(opts) {
    do {
        const data = await s3.listObjectsV2(opts).promise();
        opts.ContinuationToken = data.NextContinuationToken;
        yield data;
    } while (opts.ContinuationToken)
}

const opts = {
    Bucket: 'bucket-xyz',
    /* required */
    // ContinuationToken: 'STRING_VALUE',
    // Delimiter: 'STRING_VALUE',
    // EncodingType: url,
    // FetchOwner: true || false,
    // MaxKeys: 'NUMBER_VALUE',
    // Prefix: 'STRING_VALUE',
    // RequestPayer: requester,
    // StartAfter: 'STRING_VALUE'
};

async function main() {
    // using for of await loop
    for await (const data of listAllKeys(opts)) {
        console.log(data.Contents)
    }

    // or lazy-load
    const keys = listAllKeys(opts);
    console.log(await keys.next());
    // {value: {…}, done: false}
    console.log(await keys.next());
    // {value: {…}, done: false}
    console.log(await keys.next());
    // {value: undefined, done: true}
}
main();

// Making Observable

const lister = opts => o => {
    let needMore = true;
    (async () => {
        const keys = listAllKeys(opts);
        for await (const data of keys) {
            if (data.done) break;
            o.next(data);
            if (!needMore) break;
        }
        o.complete();
    })();
    return () => (needMore = false);
}

// Using Rxjs

const { Observable } = require('rxjs');
const { flatMap } = require('rxjs/operators')

function listAll() {
    return Observable.create(lister(opts))
        .pipe(flatMap(v => v.Contents))
        .subscribe(console.log);
}

listAll();


// Using Nodejs EventEmitter

const EventEmitter = require('events');

const _eve = new EventEmitter();
_eve.on('next', console.log);

const stop = lister(opts)({
    next: v => _eve.emit('next', v),
    error: e => _eve.emit('error', e),
    complete: v => _eve.emit('complete', v)
});

Ответ 10

Несмотря на то, что ответ @Meekohi работает технически, у меня было достаточно душевной боли в S3-части SDK SDK для NodeJS. После всех предыдущих попыток с такими модулями, как aws-sdk, s3, knox, я решил установить s3cmd через Диспетчер пакетов ОС и его оболочку с помощью child_process

Что-то вроде:

    var s3cmd = new cmd_exec('s3cmd', ['ls', filepath, 's3://'+inputBucket],
            function (me, data) {me.stdout += data.toString();},
            function (me) {me.exit = 1;}
    );
    response.send(s3cmd.stdout);

(Используя реализацию cmd_exec из этого вопроса)

Этот подход работает очень хорошо - в том числе и для других проблемных вещей, таких как загрузка файлов.

Ответ 11

При использовании нового API s3.listObjectsV2 рекурсивным решением будет:

S3Dataset.prototype.listFiles = function(params,callback) {
    var self=this;

    var options = {
    };
    for (var attrname in params) { options[attrname] = params[attrname]; }

    var results=[];
    var s3=self.s3Store.GetInstance();
    function listAllKeys(token, callback) {
        var opt={ Bucket: self._options.s3.Bucket, Prefix: self._options.s3.Key, MaxKeys: 1000 };
        if(token) opt.ContinuationToken = token;
        s3.listObjectsV2(opt, (error, data) => {
            if (error) {
                if(self.logger) this.logger.error("listFiles error:", error);
                return callback(error);
            } else {
                for (var index in data.Contents) {
                    var bucket = data.Contents[index];
                    if(self.logger) self.logger.debug("listFiles Key: %s LastModified: %s Size: %s", bucket.Key, bucket.LastModified, bucket.Size);
                    if(bucket.Size>0) {
                        var Bucket=self._options.s3.Bucket;
                        var Key=bucket.Key;
                        var components=bucket.Key.split('/');
                        var name=components[components.length-1];
                        results.push({
                            name: name,
                            path: bucket.Key,
                            mtime: bucket.LastModified,
                            size: bucket.Size,
                            sizehr: formatSizeUnits(bucket.Size)
                        });
                    }
                }
                if( data.IsTruncated ) { // truncated page
                    return listAllKeys(data.NextContinuationToken, callback);
                } else {
                    return callback(null,results);
                }
            }
        });
    }
    return listAllKeys.apply(this,['',callback]);
};

где

function formatSizeUnits(bytes){
    if      (bytes>=1099511627776) {bytes=(bytes/1099511627776).toFixed(4)+' PB';}
    else if (bytes>=1073741824)    {bytes=(bytes/1073741824).toFixed(4)+' GB';}
    else if (bytes>=1048576)       {bytes=(bytes/1048576).toFixed(4)+' MB';}
    else if (bytes>=1024)          {bytes=(bytes/1024).toFixed(4)+' KB';}
    else if (bytes>1)              {bytes=bytes+' bytes';}
    else if (bytes==1)             {bytes=bytes+' byte';}
    else                           {bytes='0 byte';}
    return bytes;
}//formatSizeUnits

Ответ 12

Самый чистый способ сделать это для меня - это выполнить s3cmd из моего node script, как это (пример: рекурсивно удалить файлы):

var exec = require('child_process').exec;
var child;
var bucket = "myBucket";
var prefix = "myPrefix"; // this parameter is optional
var command = "s3cmd del -r s3://" + bucket + "/" + prefix;
child = exec(command, {maxBuffer: 5000 * 1024}, function (error, stdout, stderr) { // the maxBuffer is here to avoid the maxBuffer node process error
            console.log('stdout: ' + stdout);
            if (error !== null) {
                console.log('exec error: ' + error);
            }
        });