[AWS, CoffeeScript]”AWS SDK”をちょっと便利に使いたい【Node.js】

2013.05.08

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Nodeで AWS SDK を使っていてもう少し便利だと良いのになと思ったので、どうしたら便利になるかを考えてみました。

まずは誰もが使って分かりやすそうな「S3」を対象にしてみます。
ちなみに「CoffeeScript」を利用しています。

S3

バケットの作成 - (AWS.Request) createBucket(params = {}, callback)

そのまま利用すると以下のようになります。

s3.createBucket params, (err, data) ->
  if err
    console.log err
  else
    console.log data

単純に作成するだけなら別に問題はないのですが、作成しようとしたバケット名が既にある時には以下のようなエラーが発生してしまいます。

  • BucketAlreadyExists
  • BucketAlreadyOwnedByYou

バケットが存在しない時だけに作成できればいいのにと思うので、バケットが存在しない時にだけバケット作成を行うようにしてみます。

create_bucket = (bucket, callback, params = {}) ->
  params.Bucket = bucket if bucket?
  params.Key = key if key?
  head_bucket bucket, (err, data) =>
    if err?.statusCode is 404
      s3.createBucket params, (err, data) ->
        console.log "create_bucket"
        callback err, data
    else
      callback err, data

head_bucket = (bucket, callback) ->
  params = {}
  params.Bucket = bucket if bucket?
  s3.headBucket params, (err, data) =>
    console.log "headBucket"
    callback err, data

「headBucket」メソッドを利用することでバケットの存在チェックを行うことができます。

(AWS.Request) headBucket(params = {}, callback)

エラーの場合
 "statusCode"が "404"だったらバケットが存在しない。
 "statusCode"が "301"だったらバケットが存在する(他人が作成したバケット)。
正常の場合
 バケットが存在する(自分が作成したバケット)。

よって、"statusCode"が "404"の時だけバケットを作成するようにしてみました。
"statusCode"が "301"の時にはそのままエラーとして、正常だったら何もしないのが良いかなと。

「createBucket」メソッドには他にもいろいろオプションはありますが、指定できるようにだけしておきます。
(何か便利な指定の仕方ありませんかね)

バケットの削除 - (AWS.Request) deleteBucketCors(params = {}, callback)

そのまま利用すると以下のようになります。

s3.deleteBucket params, (err, data) ->
  if err
    console.log err
  else
    console.log data

バケットの作成と同様に自分のバケットがある時だけ削除するようにしてみます。

delete_bucket = (bucket, callback) ->
  params = {}
  params.Bucket = bucket if bucket?
  head_bucket bucket, (err, data) =>
    if err?.statusCode is 404
      callback null, data
    else if err?.statusCode is 403
      callback err, data
    else if err?.statusCode is 301
      callback err, data
    else
      s3.deleteBucket params, (err, data) ->
        console.log "delete_bucket"
        callback err, data

head_bucket = (bucket, callback) ->
  params = {}
  params.Bucket = bucket if bucket?
  s3.headBucket params, (err, data) =>
    console.log "headBucket"
    callback err, data

同じく「headBucket」メソッドを利用してバケットの存在チェックを行なって、正常の時のみ削除するようにしてみました。

バケットの削除は、バケット内に一つでもオブジェクトがあると削除できないも不便ですね。
オブジェクトをすべて削除してからバケットを削除できるようなメソッドも作ってみました。

delete_bucket_force = (bucket, callback) ->
  head_bucket bucket, (err, data) =>
    if err?.statusCode is 404
      callback null, data
    else if err?.statusCode is 403
      callback err, data
    else
      list_objects bucket, (err, data) =>
        if err
          callback err, data
        else
          console.log data.Contents
          unless data.Contents.length is 0
            delete_objects bucket, data.Contents, (err, data) =>
              if err
                callback err, data
              else
                params = {}
                params.Bucket = bucket if bucket?
                s3.deleteBucket params, (err, data) ->
                  console.log "delete_bucket_force"
                  callback err, data
          else
            params = {}
            params.Bucket = bucket if bucket?
            s3.deleteBucket params, (err, data) ->
              console.log "delete_bucket_force"
              callback err, data

head_bucket = (bucket, callback) ->
  params = {}
  params.Bucket = bucket if bucket?
  s3.headBucket params, (err, data) =>
    console.log "headBucket"
    callback err, data

list_objects = (bucket, callback, params = {}) ->
  params.Bucket = bucket if bucket?
  s3.listObjects params, (err, data) =>
    console.log "list_objects"
    console.log data.Contents if data?
    callback err, data

delete_objects = (bucket, deletes, callback, params = {}) ->
  params.Bucket = bucket if bucket?
  keys = []
  for key in deletes
    keys.push { Key: key.Key }
  params.Delete = {
    Objects: keys
  }
  s3.deleteObjects params, (err, data) ->
    console.log "delete_objects"
    callback err, data

「listObjects」メソッドでオブジェクトの一覧を取得して、「deleteObjects」で取得した一覧すべてを削除しています。

クラス化

バケットの作成と削除はちょっと便利になった気がしますが、その他のメソッドはそのままでもそんなに問題ありませんでした。

そこでせっかく「CoffeeScript」を利用しているので、クラス化して便利に使えるようにしてみます。
(全メソッドは用意していませんが、今回紹介した以外のメソッドも用意していたりもします)

class S3
  AWS = require 'aws-sdk'
  path = require 'path'

  constructor: (file) ->
    AWS.config.loadFromPath path.normalize file
    @s3 = new AWS.S3()

  create_bucket: (bucket, callback, params = {}) ->
    params = @create_params bucket, null, params
    @head_bucket bucket, (err, data) =>
      if err?.statusCode is 404
        @s3.createBucket params, (err, data) ->
          console.log "create_bucket"
          callback err, data
      else
        callback err, data

  delete_bucket: (bucket, callback) ->
    params = @create_params bucket, null
    @head_bucket bucket, (err, data) =>
      if err?.statusCode is 404
        callback null, data
      else if err?.statusCode is 403
        callback err, data
      else if err?.statusCode is 301
        callback err, data
      else
        @s3.deleteBucket params, (err, data) ->
          console.log "delete_bucket"
          callback err, data

  delete_bucket_force: (bucket, callback) ->
    @head_bucket bucket, (err, data) =>
      if err?.statusCode is 404
        callback null, data
      else if err?.statusCode is 403
        callback err, data
      else
        @list_objects bucket, (err, data) =>
          if err
            callback err, data
          else
            unless data.Contents.length is 0
              @delete_objects bucket, data.Contents, (err, data) =>
                if err
                  callback err, data
                else
                  params = @create_params bucket, null
                  @s3.deleteBucket params, (err, data) ->
                    console.log "delete_bucket_force"
                    callback err, data
            else
              params = @create_params bucket, null
              @s3.deleteBucket params, (err, data) ->
                console.log "delete_bucket_force"
                callback err, data

  delete_object: (bucket, key, callback) ->
    params = @create_params bucket, key
    @head_object bucket, key, (err, data) =>
      if err?.statusCode is 404
        callback null, data
      else if err?.statusCode is 403
        callback err, data
      else
        @s3.deleteObject params, (err, data) ->
          console.log "delete_object"
          callback err, data

  delete_objects: (bucket, deletes, callback, params = {}) ->
    params = @create_params bucket, null, params
    keys = []
    for key in deletes
      keys.push { Key: key.Key }
    params.Delete = {
      Objects: keys
    }
    @s3.deleteObjects params, (err, data) ->
      console.log "delete_objects"
      callback err, data

  getBucketVersioning: (bucket, callback) ->
    params = @create_params bucket, null
    @s3.getBucketVersioning params, (err, data) =>
      console.log "getBucketVersioning"
      callback err, data

  get_acl: (params, callback) ->
    @s3.getBucketAcl params, (err, data) ->
      console.log "get_acl"
      console.log data.Grants if data?
      callback err, data

  get_object: (params, callback) ->
    @s3.getObject params, (err, data) ->
      console.log "get_object"
      callback err, data

  head_bucket: (bucket, callback) ->
    params = @create_params bucket, null
    @s3.headBucket params, (err, data) =>
      console.log "headBucket"
      callback err, data

  head_object: (bucket, key, callback) ->
    params = @create_params bucket, key
    @s3.headObject params, (err, data) =>
      console.log "head_object"
      callback err, data

  put_object: (bucket, key, callback, params = {}) ->
    params = @create_params bucket, key, params
    @s3.putObject params, (err, data) ->
      console.log "put_object"
      callback err, data

  list_buckets: (callback) ->
    @s3.listBuckets null, (err, data) ->
      console.log "list_buckets"
      console.log data.Buckets if data?
      callback err, data

  list_objects: (bucket, callback, params = {}) ->
    params = @create_params bucket, null, params
    @s3.listObjects params, (err, data) =>
      console.log "list_objects"
      console.log data.Contents if data?
      callback err, data

  create_params: (bucket, key, params = {}) ->
    params.Bucket = bucket if bucket?
    params.Key = key if key?
    return params

module.exports = S3

利用例

async = require 'async'
S3 = require './S3'

s3 = new S3('../config/config.json')

bucket_name = "classmethod-hoge"
key_name = ""

delete_bucket_force = (callback) ->
  s3.delete_bucket_force bucket_name, callback

delete_bucket = (callback) ->
  s3.delete_bucket bucket_name, callback

create_bucket = (callback) ->
  params =
    ACL: "public-read"
    CreateBucketConfiguration: {
      LocationConstraint: "ap-northeast-1"
    }
  s3.create_bucket bucket_name, callback, params

put_object = (callback) ->
  params =
      Body: "Hello world!"
  key_name = "Key"
  s3.put_object bucket_name, key_name, callback, params

get_object = (callback) ->
  s3.get_object bucket_name, key_name, callback, params

delete_object = (callback) ->
  s3.delete_object bucket_name, key_name, callback

list_buckets = (callback) ->
  s3.list_buckets callback

list_objects = (callback) ->
  s3.list_objects bucket_name, callback

tasks = []
tasks.push list_buckets
tasks.push create_bucket
tasks.push put_object
tasks.push delete_object
tasks.push delete_bucket

async.series tasks, (err, results) ->
  if err
    console.log err
  else
    console.log results

パラメータをもう少し便利に使う方法があれば、もっと良くなる気がするんですが・・・

なお、今回のソースはgithubに公開してあります
https://github.com/jun116/aws-sdk-coffee