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. Сообщение в блоге более подробно. Надеюсь, поможет!

Другие вопросы по тегам