CircleCIでSassをCSSに変換してS3へデプロイする


これは何?

CircleCIのジョブでSassをCSSにトランスパイルし、ミニファイしてS3へデプロイするために必要な設定をまとめたものです。

ゴール

my_repositoyリポジトリのassets/css/style.scssstyle.min.cssにトランスパイル&ミニファイし、S3のproduction-bucketにassets/css/style.min.cssとしてデプロイします。

デプロイパイプライン

  • リポジトリへプルリクエスト
    • この時点で実行されるジョブでSass→CSSトランスパイルが実行される
    • その結果をステージング用のS3バケットにデプロイする
  • マージされる
    • masterブランチなら、本番用とステージング用S3バケットにそれぞれデプロイ
    • それ以外のブランチなら、ステージング用のバケットにデプロイ

必要な設定

CSSトランスパイル

トランスパイル用のタスクランナーとしてgulpを利用します。

gulpfile.js
'use strict';

const gulp = require('gulp');
const sass = require('gulp-sass');
const workDir = './assets/css';
sass.compiler = require('node-sass');

const compileSass = (cb) => {
  const options = {
    outputStyle: "expanded",
  };

  gulp
    .src(`${workDir}/style.scss`)
    .pipe(sass(options))
    .pipe(gulp.dest(workDir));

  cb();
}

const compileSassCompressed = (cb) => {
  const rename = require('gulp-rename'); // ファイル名にminを付けたいのでそれ用のモジュールを使います
  const options = {
    outputStyle: "compressed", // ここで圧縮指定
  };

  gulp
    .src(`${workDir}/style.scss`)
    .pipe(sass(options))
    .pipe(rename({
      suffix: ".min",
    }))
    .pipe(gulp.dest(workDir));

  cb();
}

exports.compileSass = compileSass; // こっちは使っていません
exports.default = compileSassCompressed;

実行は以下をビルドの過程に入れておきます。

$ npx gulp
# npx便利ですねー

参考

CircleCI

  • S3へのデプロイはcircleci/aws-s3 Orbを利用して楽してます
    • デプロイ時に、パブリックアクセスを許可させるためには以降に記載のバケット設定をする必要がありました
CircleCI用コンフィグ
version: 2.1
orbs:
  aws-s3: circleci/[email protected]

# 本番用デプロイとステージング用デプロイジョブそれぞれで同じ処理をするので、デプロイ処理をコマンド化しています
commands:
  deploy-to-s3:
    description: "Deploy Assets to S3"
    parameters:
      bucket_name:
        type: string
    steps:
      - checkout

      - restore_cache:
          keys:
            - my_repository-css-v0-{{ .BuildNum }}
            - my_repository-css-v0-

      # Deploy CSS assets to S3
      - aws-s3/copy:
          arguments: |
            --acl public-read \
            --cache-control "max-age=604800"
          from: assets/css/style.min.css
          to: "s3://<< parameters.bucket_name >>/assets/css/style.min.css"

# 本番用デプロイとステージング用デプロイジョブで同じDockerイメージを使うのでこっちもエグゼキューターとして定義しています
executors:
  deploy-executor:
    docker:
      # aws cliを使うので、pythonがないとだめです
      - image: cimg/python:latest

jobs:
  build:
    parallelism: 1
    working_directory: ~/my_repository
    docker:
      - image: circleci/node:9.11.2
    steps:
      - checkout

      - restore_cache:
          keys:
            - my_repository-npm-v0-{{ checksum "package-lock.json" }}
            - my_repository-npm-v0-

      - run:
          name: npm Install
          command: npm install

      - save_cache:
          key: my_repository-npm-v0-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules

      - run:
          name: Run Sass Lint
          command: ./node_modules/.bin/sass-lint -c config/.sass-lint.yml --verbose --no-exit

      - run:
          name: Compile Scss
          command: npx gulp

      - save_cache:
          key: my_repository-css-v0-{{ .BuildNum }}
          paths:
            - assets

      - store_test_results:
          path: test_results

  deploy-as-staging:
    working_directory: ~/my_repository
    executor: deploy-executor
    steps:
      - deploy-to-s3:
          bucket_name: "staging-bucket"

  deploy-as-production:
    working_directory: ~/my_repository
    executor: deploy-executor
    steps:
      - deploy-to-s3:
          bucket_name: "production-bucket"

workflows:
  build_and_deploy:
    jobs:
      - build
      - deploy-as-staging:
          requires:
            - build
      - deploy-as-production:
          requires:
            - build
          filters:
            branches:
              only: master

参考

AWS側の設定

  • IAM
    • deployグループを作成し、S3フルアクセスを付与
    • circleci-userユーザを作成し、deployグループに所属
  • S3
    • デプロイ先のバケットを作成
    • ブロックパブリックアクセス (バケット設定)を「(新しい/任意の)アクセスコントロールリスト (ACL) を介して許可されたバケットとオブジェクトへのパブリックアクセスをブロックする」をオフ
    • バケットポリシーを以下にする
バケットポリシー
{
    "Version": "2012-10-17",
    "Id": "Policy1583424049389",
    "Statement": [
        {
            "Sid": "Stmt1583424044685",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::アカウント名:user/circleci-user"
            },
            "Action": [
                "s3:DeleteObject",
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::production-bucket/*"
        }
    ]
}
  • CORS設定を以下にする
    • アプリのドメインとS3のドメインが異なる場合は必要
CORSの設定
<CORSConfiguration>
 <CORSRule>
   <AllowedOrigin>*</AllowedOrigin>
   <AllowedMethod>GET</AllowedMethod>
 </CORSRule>
</CORSConfiguration>

参考