nodejs aws-sdkでs3 ファイルアップロード とcloudfront create invalidation

nodejs で aws s3 にアップロードする方法です。
webpackを使ってたので、npmでs3-plugin-webpackを使ってdeployを試みましたが、設定ファイルのこのプラグインの設定を入れるとbuildが終わらず、まったくアップロードできなかった(materializeを使ってますが、そこのコンパイルで全く進まなくなる)ので、aws-sdkを使って自作でスクリプトを作成しました。

前提

  • aws s3の設定がされていること
  • cloudfrontを使う前提で distributionの設定がされていること
  • iamの設定(s3 の使用するバケットへのputの許可、cloudfrontアクセスの許可を持っている)がされていること

s3へのアップロード

sdkを入れます

インストール

$ npm install aws-sdk --save or --savve-dev



keyなど設定をべた書きしたくないのでdotenvを使いました

$ npm install dotenv --save-dev



s3にファイルを上げる時、content-typeがないと、ちゃんとファイルを認識してくれないので、mimetypeを読んで、指定してアップするため

$npm install mime-type --save-dev



.envファイルを作り、AWS_ACCESS_KEY_IDなどの設定を書く

AWS_ACCESS_KEY_ID = xxxxxxxxxxxxxxxxxx
AWS_SECRET_ACCESS_KEY = xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CLOUDFRONT_DISTRIBUTION_ID = xxxxxxxxxxxxxxxx
CLOUDFRONT_DISTRIBUTION_ID_WWW = xxxxxxxxxxxxxxxx
AWS_REGION = xxxxxxxx
AWS_BUCKET_NAME = xxxxxxxxxxxxxx



読み込み

require('dotenv').config();
var AWS = require('aws-sdk');
var fs = require('fs');
var mime = require('mime-types')



Aws ロード

AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID, 
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 
  region: process.env.AWS_REGION
});



public配下(状況によって変えて下さい)をバケット直下に配布する

var dir = "./public"
var s3 = new AWS.S3();
fs.readdir(dir, function(err, files){
    if (err) throw err;
    files.filter(function(file){
        var params = {
            Bucket: process.env.AWS_BUCKET_NAME,
            Key: file,
            Body: fs.readFileSync(dir + file),
            ContentType: mime.lookup(file)   
        };
        s3.putObject(params, function(err, data) {
            if (err) console.log(err, err.stack);
            else     console.log(data);
        });
    });
});
  1. fs を使い、localのpublic dir以下のファイルを取得します。
  2. parasのBucketはバケット名、keyは配布されるときのファイル名、bodyがファイルの中身(fs.readFileSyncを使った)、contentTypeはmime-typeで取得したtypeを指定
  3. pubObjectで上げる
    AWS-SDK s3 リファレンス

    cloudfrontにcreate invalidationする(cacheを消す)

僕の場合は、www用(リダイレクト)、wwwなし用(それぞれhttps対応)があるため、一応両方のdistributionに対して、createInvalidationしています。

var cloudfront = new AWS.CloudFront({apiVersion: '2017-03-25'});
var timestamp = new Date();
var string_timestamp = String(timestamp.getTime());

var invalidate_items = ['/*'];
var item_count = invalidate_items.length;
var invalidations = invalidate_items.map(encodeURI)

var distributionIds = [process.env.CLOUDFRONT_DISTRIBUTION_ID, process.env.CLOUDFRONT_DISTRIBUTION_ID_WWW];

distributionIds.forEach( function( distributionId ) {
  var params = {
      DistributionId: distributionId,
      InvalidationBatch: {
          CallerReference: string_timestamp,
          Paths: {
            Quantity: invalidations.length,
            Items: invalidations
          }
      }
  };

  cloudfront.createInvalidation(params, function(err, data) {
      if (err) console.log(err, err.stack);
      else     console.log(data);
  });
});



最初どんなにやってもエラーが出てしまい、うまく行かなかったのですが、invalidate itemに対して、encodeURI したところ、うまくいきました。
CallerReferenceは、timestamp、PathのQuantityは、itemの数、Itemsは、消す対応のitemになります。
AWS-SDK cloudfront リファレンス

全体のソースコード

s3-upload.js

require('dotenv').config();
var AWS = require('aws-sdk');
var fs = require('fs');
var mime = require('mime-types')

AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID, 
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 
  region: process.env.AWS_REGION
});


var dir = "./public"
var s3 = new AWS.S3();
fs.readdir(dir, function(err, files){
    if (err) throw err;
    files.filter(function(file){
        var params = {
            Bucket: process.env.AWS_BUCKET_NAME,
            Key: file,
            Body: fs.readFileSync(dir + file),
            ContentType: mime.lookup(file)   
        };
        s3.putObject(params, function(err, data) {
            if (err) console.log(err, err.stack);
            else     console.log(data);
        });
    });
});


var cloudfront = new AWS.CloudFront({apiVersion: '2017-03-25'});
var timestamp = new Date();
var string_timestamp = String(timestamp.getTime());

var invalidate_items = ['/*'];
var item_count = invalidate_items.length;
var invalidations = invalidate_items.map(encodeURI)

var distributionIds = [process.env.CLOUDFRONT_DISTRIBUTION_ID, process.env.CLOUDFRONT_DISTRIBUTION_ID_WWW];

distributionIds.forEach( function( distributionId ) {
  var params = {
      DistributionId: distributionId,
      InvalidationBatch: {
          CallerReference: string_timestamp,
          Paths: {
            Quantity: invalidations.length,
            Items: invalidations
          }
      }
  };
  cloudfront.createInvalidation(params, function(err, data) {
      if (err) console.log(err, err.stack);
      else     console.log(data);
  });
});


実行

$ node s3-upload.js(上記を保存したファイル名)

これを、webpackを使ってるなら、packeages.jsonのscript部分に設定すれば、npm runコマンドでbuild後に実行するようにすればOKです。

まとめ

今後は、buildされたファイルをバージョン管理し、rollback的なものもできたらいいと思います。