Pulumi (TypeScript, AWS): как загрузить несколько файлов на S3, включая. вложенные файлы в каталоги для статического хостинга веб-сайтов
В « Создание веб-сайта AWS S3 за 5 минут на YouTube и обучающих материалахразмещение статического веб-сайта на Amazon S3 Pulumi» есть отличные объяснения, как создать хостинг веб-сайтов на S3 с помощью Pulumi.
В примере кода используются Bucket и BucketObject Pulumi . Первый создает S3 Bucket, а второй создает объекты, которые в основном являются
index.html
для публичного доступа, например:
const aws = require("@pulumi/aws");
const pulumi = require("@pulumi/pulumi");
const mime = require("mime");
// Create an S3 bucket
let siteBucket = new aws.s3.Bucket("s3-website-bucket");
let siteDir = "www"; // directory for content files
// For each file in the directory, create an S3 object stored in `siteBucket`
for (let item of require("fs").readdirSync(siteDir)) {
let filePath = require("path").join(siteDir, item);
let object = new aws.s3.BucketObject(item, {
bucket: siteBucket,
source: new pulumi.asset.FileAsset(filePath), // use FileAsset to point to a file
contentType: mime.getType(filePath) || undefined, // set the MIME type of the file
});
}
exports.bucketName = siteBucket.bucket; // create a stack export for bucket name
Теперь, используя приложение на основе Vue.js / Nuxt.js, мне нужно загрузить несколько сгенерированных файлов, которые находятся внутри
dist
каталог моего корня проекта. Их производит
npm run build
и в результате получатся следующие файлы:
$ find dist
dist
dist/favicon.ico
dist/index.html
dist/.nojekyll
dist/200.html
dist/_nuxt
dist/_nuxt/LICENSES
dist/_nuxt/static
dist/_nuxt/static/1619685747
dist/_nuxt/static/1619685747/manifest.js
dist/_nuxt/static/1619685747/payload.js
dist/_nuxt/f3a11f3.js
dist/_nuxt/f179782.js
dist/_nuxt/fonts
dist/_nuxt/fonts/element-icons.4520188.ttf
dist/_nuxt/fonts/element-icons.313f7da.woff
dist/_nuxt/c25b1a7.js
dist/_nuxt/84fe6d0.js
dist/_nuxt/a93ae32.js
dist/_nuxt/7b77d06.js
Моя проблема здесь в том, что эти файлы также включают файлы, вложенные в подкаталоги, которые сами могут быть подкаталогами - например,
dist/_nuxt/fonts/element-icons.4520188.ttf
. Предлагаемый в руководствах подход не оценивает подкаталоги, и я не знаю, как это сделать с Pulumi / TypeScript.
3 ответа
Я продолжил этот подход и попытался создать рекурсивную функцию TypeScript, которая либо создает файлы, либо каталоги на основе BucketObject Пулуми, как рекомендовано в руководствах. Это привело меня к сложному пути! Мне нужно было создать каталоги, используя
BucketObject
, чего можно достичь с помощью добавленного
"/"
внутри
key
аргумент ( см. этот ответ ). Для записи функция для этого выглядела так:
function createS3BucketFolder(dirName: string) {
new aws.s3.BucketObject(dirName, {
bucket: nuxtBucket,
acl: "public-read",
key: dirName + "/", // an appended '/' will create a S3 Bucket prefix (see https://stackoverflow.com/a/57479653/4964553)
contentType: "application/x-directory" // this content type is also needed for the S3 Bucket prefix
// no source needed here!
})
}
Но это был всего лишь один фрагмент огромного кода, необходимого для рекурсивного обхода каталога с помощью TypeScript (см. Также это огромное количество ответов на эту тему, начиная с синхронных версий и заканчивая сумасшедшими асинхронными решениями Node.js 11+). В итоге у меня было около 40-50 строк кода только для рекурсивного добавления файлов, сгенерированных статическим сайтом, в S3 - и было неприятно, что не было теста для этого (и я действительно не понимаю, почему Pulumi не поддерживает этот вариант использования, как Terraform).
Наконец, я наткнулся на учебник Pulumi о безопасном статическом веб-сайте с использованием Amazon S3, CloudFront, Route53 и Certificate Manager, где есть специальный абзац о скорости развертывания с интересной цитатой:
В этом примере создается объект aws.S3.BucketObject для каждого файла, обслуживаемого с веб-сайта. При развертывании больших веб-сайтов это может привести к очень долгим обновлениям, поскольку каждый отдельный файл проверяется на наличие изменений. Может быть более эффективным не управлять отдельными файлами с помощью Pulumi, а вместо этого просто использовать интерфейс командной строки AWS для прямой синхронизации локальных файлов с корзиной S3.
TL; DR: для случаев использования, отличных от hello world, документы Pulumi говорят нам не использовать Pulumi для загрузки файлов в S3, а использовать AWS CLI! Поэтому я переработал свой код, чтобы создать только ведро S3 с использованием Pulumi, например:
import * as aws from "@pulumi/aws";
// Create an AWS resource (S3 Bucket)
const nuxtBucket = new aws.s3.Bucket("microservice-ui-nuxt-js-hosting-bucket", {
acl: "public-read",
website: {
indexDocument: "index.html",
}
});
// Export the name of the bucket
export const bucketName = nuxtBucket.id;
Это создает сегмент S3 с поддержкой статического хостинга с
public-read
доступ через Пулуми. Теперь, используя интерфейс командной строки AWS, мы можем элегантно копировать / синхронизировать наши файлы в Bucket с помощью следующей команды:
aws s3 sync ../dist/ s3://$(pulumi stack output bucketName) --acl public-read
С использованием
$(pulumi stack output bucketName)
мы просто получаем имя S3 Bucket, созданное Pulumi. Помните
--acl public-read
параметр в конце, так как вы должны разрешить публичный доступ для чтения для каждого из ваших статических веб-файлов в S3, хотя сам Bucket уже имеет публичный доступ для чтения!
Я согласен с «jonashackt», что команда AWS cli быстрее и аккуратнее, чем использование pulumi для перемещения по гигантскому каталогу. Но в моем случае это было все еще лучшее решение, так что начнем:
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
const sourceFolder = "../my-project";
const bucketName = "myproject.example.com";
const fs = require("fs");
const path = require("path");
const mime = require("mime");
let siteBucket = new aws.s3.Bucket(bucketName, {
bucket: bucketName,
website: {
indexDocument: "index.html",
}
});
let createdFolders: string[] = [];
function recursiveFolderStructure(dist: string){
for(let item of fs.readdirSync(dist)) {
let filePath = path.join(dist, item);
let relativePath = dist.slice(sourceFolder.length).replace(/\\/g, '/');
let mimeType = mime.getType(filePath) || undefined;
if(mimeType){
new aws.s3.BucketObject(relativePath.length ? relativePath+'/'+item : item, {
acl: "public-read",
bucket: siteBucket,
source: new pulumi.asset.FileAsset(filePath),
contentType: mimeType,
});
}else{
if(relativePath.length && !createdFolders.includes(relativePath)){
new aws.s3.BucketObject(relativePath, {
acl: "public-read",
bucket: siteBucket,
key: relativePath+'/',
contentType: "application/x-directory",
});
createdFolders.push(relativePath);
}
recursiveFolderStructure(filePath);
}
}
}
recursiveFolderStructure(sourceFolder);
exports.bucketName = siteBucket.bucket;
export const bucketEndpoint = pulumi.interpolate`http://${siteBucket.websiteEndpoint}`;
console.log(bucketEndpoint);
Вам просто нужно изменить две переменные вверху (исходная папка и имя корзины). Кстати, я не так много думал о коде. Я могу сказать, что есть возможности для улучшения. Не стесняйтесь изменять или предлагать более чистые способы. Я могу сказать, что это работает и может сэкономить вам несколько минут.
Оба этих ответа, представленные здесь, определенно работают, но, поскольку прошло немного времени, я решил поделиться недавно написанным мной компонентом Pulumi, который делает это немного проще:
https://www.pulumi.com/registry/packages/synced-folder/
Это многоязычный мультиоблачный компонент (доступный на всех поддерживаемых языках для AWS, Azure и GCP), который позволяет вам выбрать, будет ли Pulumi управлять файлами веб-сайта как отдельными ресурсами или, как описано здесь, использовать облачного провайдера. Вместо этого CLI. Например, следующий фрагмент рекурсивно загружает все файлы в указанную папку с помощьюaws s3 sync
:
import * as aws from "@pulumi/aws";
import * as synced from "@pulumi/synced-folder";
const bucket = new aws.s3.Bucket("my-bucket", {
acl: aws.s3.PublicReadAcl,
website: {
indexDocument: "index.html",
},
});
const folder = new synced.S3BucketFolder("synced-folder", {
path: "./my-folder",
bucketName: bucket.bucket,
acl: aws.s3.PublicReadAcl,
// Set this property to false to fall back to the cloud-provider CLI.
managedObjects: false,
});
В конечном итоге тот же результат, но с более аккуратным API, который позаботится о вводе контента за вас и позволит вам справиться со всем в программе Pulumi. Сообщение в блоге более подробно. Надеюсь, поможет!