Задача: собрать 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
Готово.


