Задача: собрать JavaScript (AngularJS + Node.js) приложение, и задеплоить его в корзину S3 для хостинга статического сайта.
Продробнее про S3 – AWS: S3, Simple Storage Service – описание, примеры.
Про хостинг сайтов в S3 – AWS: S3 – хостинг статического сайта.
Сначала – запустим простой “Hello, %username%” в S3.
Затем – соберём имеющееся приложение, и задеплоим его в S3 с помощью Grunt и плагина grunt-aws-s3
.
Содержание
Создание сайта в S3
Создаём корзину:
$ aws s3 mb s3://nodehello.azinchenko.com make_bucket: s3://nodehello.azinchenko.com/
Настраиваем WebSite для корзины:
$ aws s3 website s3://nodehello.azinchenko.com --index-document index.htm --error-document 4xx.htm
Создаём JSON с описанием политики доступа:
{ "Version":"2012-10-17", "Statement":[{ "Sid":"PublicReadForGetBucketObjects", "Effect":"Allow", "Principal": "*", "Action":["s3:GetObject"], "Resource":["arn:aws:s3:::nodehello.azinchenko.com/*" ] } ] }
Подключаем:
$ aws s3api put-bucket-policy --bucket nodehello.azinchenko.com --policy file://nodehello.azinchenko.com.json
На настройке домена останавливаться не буду, выглядит он так:
Запуск AngularJS “Hello, World”
Добавляем проект:
$ mkdir angularhello
В нём создаём файл index.htm
с содержимым:
<html> <head> <title>AngularJS First Application</title> </head> <body> <h1>Sample Application</h1> <div ng-app = ""> <p>Enter your Name: <input type = "text" ng-model = "name"></p> <p>Hello <span ng-bind = "name"></span>!</p> </div> <script src = "https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script> </body> </html>
Загружаем в корзину:
$ aws s3 cp index.htm s3://nodehello.azinchenko.com upload: ./index.htm to s3://nodehello.azinchenko.com/index.htm
Проверяем:
Grunt deploy
Далее – заделоим приложение с помощью Grunt.
Клонируем репозиторий:
$ git clone -b develop ssh://[email protected]:7999/project/profile-website.git
Его содержимое:
$ ls -l total 68 -rw-rw-r-- 1 setevoy setevoy 783 жов 17 11:59 bower.json drwxrwxr-x 5 setevoy setevoy 4096 жов 17 11:59 client drwxrwxr-x 3 setevoy setevoy 4096 жов 17 11:59 e2e -rw-rw-r-- 1 setevoy setevoy 19602 жов 17 11:59 Gruntfile.js -rw-rw-r-- 1 setevoy setevoy 2398 жов 17 11:59 karma.conf.js -rw-rw-r-- 1 setevoy setevoy 2689 жов 17 11:59 package.json -rw-rw-r-- 1 setevoy setevoy 1598 жов 17 11:59 protractor.conf.js -rw-rw-r-- 1 setevoy setevoy 1132 жов 17 11:59 README.md -rw-rw-r-- 1 setevoy setevoy 8780 жов 17 11:59 README.old.md drwxrwxr-x 5 setevoy setevoy 4096 жов 17 11:59 server -rwxrwxr-x 1 setevoy setevoy 1846 жов 17 11:59 setup-build-environment
Описание grunt-aws-s3
Сам деплой в корзину S3 выполняется с помощью плагина grunt-aws-s3
, который указан в списке зависимостей в файле package.json
:
$ cat package.json | grep grunt-aws "grunt-aws-s3": "^0.14.5",
Сборка приложения и деплой включают в себя несколько шагов:
- npm install
- bower install –allow-root
- grunt build:env
- grunt deploy –env=ENV –accessKeyId=ABC123 –secretAccessKey=XYZ890
deploy
описывается в файле Gruntfile.js
:
... grunt.registerTask('deploy', function(env, accessKeyId, secretAccessKey) { var env = grunt.option('env'); var accessKeyId = grunt.option('accessKeyId'); var secretAccessKey = grunt.option('secretAccessKey'); if (env && accessKeyId && secretAccessKey) { grunt.config.set('aws_s3.options.accessKeyId', accessKeyId); grunt.config.set('aws_s3.options.secretAccessKey', secretAccessKey); return grunt.task.run([ ('aws_s3:' + env) ]); } else { grunt.fail.fatal('Deploy task requires env, accessKeyId and secretAccessKey parameters'); } }); ...
А окружения (первый передаваемый аргумент) выглядят так:
... aws_s3: { options: { region: 'eu-west-1', uploadConcurrency: 5, // 5 simultaneous uploads downloadConcurrency: 5 // 5 simultaneous downloads }, dev: { options: { bucket: 'profile-dev.domain.com', differential: true }, files: [ { expand: true, cwd: 'dist/public', src: ['**'], dest: '/', action: 'upload' } ] }, staging: { options: { bucket: 'profile-staging.domain.com', differential: true }, files: [ { expand: true, cwd: 'dist/public', src: ['**'], dest: '/', action: 'upload' } ] }, production: { options: { bucket: 'profileapp.domain.com', differential: true }, files: [ { expand: true, cwd: 'dist/public', src: ['**'], dest: '/', action: 'upload' } ] } } }); ...
Пример деплоя
Настройка корзины
Проверяем имеющиеся корзины:
$ aws s3 ls ... 2016-05-09 19:22:16 profile-dev.domain.com 2016-05-09 21:09:40 profile-staging.domain.com 2016-08-17 16:02:28 profileapp.domain.com ...
Добавляем новую корзину (в регионе eu-west-1
, как это указано в aws_s3.options
в Grunfile.js
:
$ aws s3 mb s3://profile-testing.domain.com --region eu-west-1 make_bucket: s3://profile-testing.domain.com/
Настраиваем её как Website:
$ aws s3 website s3://profile-testing.domain.com --index-document index.html --error-document error.html
Добавляем запись profile-testing.domain.com
с алиасом к созданной корзине:
Добавляем политику доступа:
$ cat profile-testing.domain.com.json { "Version":"2012-10-17", "Statement":[{ "Sid":"PublicReadForGetBucketObjects", "Effect":"Allow", "Principal": "*", "Action":["s3:GetObject"], "Resource":["arn:aws:s3:::profile-testing.domain.com/*" ] } ] }
Подключаем:
$ aws s3api put-bucket-policy --bucket profile-testing.domain.com --policy file://profile-testing.domain.com.json
Проверяем:
$ aws s3api get-bucket-policy --bucket profile-testing.domain.com { "Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"PublicReadForGetBucketObjects\",\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"s3:GetObject\",\"Resource\":\"arn:aws:s3:::profile-testing.domain.com/*\"}]}" }
Настройка Grunt deploy
В Gruntfile.js
– добавляем новое окружение:
... prodtesting: { options: { bucket: 'profile-testing.domain.com', differential: true }, files: [ { expand: true, cwd: 'dist/public', src: ['**'], dest: '/', action: 'upload' } ] }, ...
Добавляем новое окружение с тегом “prodtesting“:
... prodtesting: { options: { process: true, data: { tagEnvironment: 'prodtesting' } }, files: { 'dist/public/index.html': ['dist/public/index.html'] } }, ...
Сборка
Собираем приложение.
Устанавливаем зависимости, описанные в package.json
, включая grunt-aws-s3
:
$ npm install npm http GET https://registry.npmjs.org/ejs npm http GET https://registry.npmjs.org/composable-middleware npm http GET https://registry.npmjs.org/connect-mongo ...
Устанавливаем зависимости, перечисденные в bower.json
:
$ sudo npm install -g bower $ bower install --allow-root bower jquery#2.1.4 not-cached https://github.com/jquery/jquery-dist.git#2.1.4 bower jquery#2.1.4 resolve https://github.com/jquery/jquery-dist.git#2.1.4 ...
Собираем:
$ sudo npm install -g grunt npm http GET https://registry.npmjs.org/grunt npm http 304 https://registry.npmjs.org/grunt npm http GET https://registry.npmjs.org/grunt/-/grunt-1.0.1.tgz ...
$ grunt build:prodtesting Running "clean:dist" (clean) task Running "injector:sass" (injector) task Missing option `template`, using `dest` as template instead Injecting scss files (45 files) Running "concurrent:dist" (concurrent) task Warning: Running "sass:server" (sass) task Warning: You need to have Ruby and Sass installed and in your PATH for this task to work. More info: https://github.com/gruntjs/grunt-contrib-sass Use --force to continue. Aborted due to warnings. ...
Упс.
Устанавливаем гемы:
$ sudo npm install -g grunt-contrib-sass
$ sudo gem install sass Fetching: sass-3.4.22.gem (100%) Successfully installed sass-3.4.22 1 gem installed Installing ri documentation for sass-3.4.22...
Билдим ещё раз:
$ grunt build:prodtesting Running "clean:dist" (clean) task Cleaning dist/public...OK Running "injector:sass" (injector) task Missing option `template`, using `dest` as template instead Injecting scss files (45 files) >> Nothing changed Running "concurrent:dist" (concurrent) task Running "svgmin:dist" (svgmin) task ✔ client/assets/images/ic_watchspecs_line.svg (saved 287 B 41%) Total saved: 287 B Done, without errors. ...
Проверяем dist/public
(указанный как cwd: 'dist/public'
в Grunfile.js
):
$ ls -l dist/public/ total 24 drwxrwxr-x 2 setevoy setevoy 4096 жов 17 14:03 app drwxrwxr-x 5 setevoy setevoy 4096 жов 17 14:02 assets drwxrwxr-x 27 setevoy setevoy 4096 жов 17 14:02 bower_components -rw-rw-r-- 1 setevoy setevoy 1150 жов 17 14:02 favicon.ico -rw-rw-r-- 1 setevoy setevoy 2192 жов 17 14:03 index.html -rw-rw-r-- 1 setevoy setevoy 31 жов 17 14:02 robots.txt
Деплой
Проверяем ещё раз содержимое корзины:
$ aws s3 ls s3://profile-testing.domain.com
Деплоим:
$ grunt deploy --env=prodtesting --accessKeyId=AKIA****U42A --secretAccessKey=FcJMU****A0fL Running "deploy" task Running "aws_s3:prodtesting" (aws_s3) task Uploading to https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/ ............................................................................ List: (861 objects): - dist/public/app/app.css -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/app/app.css - dist/public/app/app.js -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/app/app.js - dist/public/app/vendor.js -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/app/vendor.js - dist/public/assets/fonts/HelveticaNeueLTStd-Bd.eot -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/assets/fonts/HelveticaNeueLTStd-Bd.eot - dist/public/assets/fonts/HelveticaNeueLTStd-Bd.otf -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/assets/fonts/HelveticaNeueLTStd-Bd.otf - dist/public/assets/fonts/HelveticaNeueLTStd-Bd.svg -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/assets/fonts/HelveticaNeueLTStd-Bd.svg - dist/public/assets/fonts/HelveticaNeueLTStd-Bd.ttf -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/assets/fonts/HelveticaNeueLTStd-Bd.ttf - dist/public/assets/fonts/HelveticaNeueLTStd-Bd.woff -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/assets/fonts/HelveticaNeueLTStd-Bd.woff .... - dist/public/bower_components/ngAutocomplete/src/ngAutocomplete.js -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/bower_components/ngAutocomplete/src/ngAutocomplete.js - dist/public/favicon.ico -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/favicon.ico - dist/public/index.html -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/index.html - dist/public/robots.txt -> https://s3-eu-west-1.amazonaws.com/profile-testing.domain.com/robots.txt 861/861 objects uploaded to bucket profile-testing.domain.com/ Done, without errors. Execution Time (2016-10-17 13:18:32 UTC) aws_s3:prodtesting 2m 34.9s ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 100% Total 2m 35.3s
Проверяем:
$ aws s3 ls s3://profile-testing.domain.com PRE app/ PRE assets/ PRE bower_components/ 2016-10-17 16:21:08 1150 favicon.ico 2016-10-17 16:21:08 2192 index.html 2016-10-17 16:21:08 31 robots.txt
Готово.