From 76e12cde35fbbd5b299d9d9dffed29d70db945f6 Mon Sep 17 00:00:00 2001 From: "Michael B. Klein" Date: Thu, 3 Aug 2023 17:10:17 +0000 Subject: [PATCH] Update Nextra docs to reflect new app structure Build CloudFormation documentation dynamically Build Terraform documentation dynamically --- docs/components/CallToAction.jsx | 5 +- docs/components/HomepageHeader.jsx | 9 +- docs/lib/cfn-reader.js | 35 ++++ docs/lib/cfn-tags.js | 23 +++ docs/lib/cloudformation.js | 32 ++++ docs/lib/render.js | 41 +++++ docs/package-lock.json | 132 +++++++++++++- docs/package.json | 4 +- .../request-response-functions.mdx | 75 ++++---- docs/pages/docs/index.mdx | 15 +- docs/pages/docs/quick-start/_meta.json | 1 + .../docs/quick-start/deleting-the-app.mdx | 4 +- .../quick-start/deployment-command-line.mdx | 2 +- .../pages/docs/quick-start/deployment-sam.mdx | 5 +- docs/pages/docs/quick-start/index.mdx | 10 +- .../quick-start/infrastructure/_meta.json | 5 + .../infrastructure/cloudformation.mdx | 110 ++++++++++++ .../infrastructure/custom.module.scss | 45 +++++ .../docs/quick-start/infrastructure/index.mdx | 3 + .../quick-start/infrastructure/terraform.mdx | 162 ++++++++++++++++++ examples/cloudformation/custom_hostname.yml | 2 +- examples/sam/customization/template.yaml | 147 ++++++++++++++++ extras/terraform/README.md | 17 +- extras/terraform/outputs.tf | 21 ++- extras/terraform/variables.tf | 5 - package-lock.json | 18 +- package.json | 3 +- sam/template.yml | 3 + 28 files changed, 834 insertions(+), 100 deletions(-) create mode 100644 docs/lib/cfn-reader.js create mode 100644 docs/lib/cfn-tags.js create mode 100644 docs/lib/cloudformation.js create mode 100644 docs/lib/render.js create mode 100644 docs/pages/docs/quick-start/infrastructure/_meta.json create mode 100644 docs/pages/docs/quick-start/infrastructure/cloudformation.mdx create mode 100644 docs/pages/docs/quick-start/infrastructure/custom.module.scss create mode 100644 docs/pages/docs/quick-start/infrastructure/index.mdx create mode 100644 docs/pages/docs/quick-start/infrastructure/terraform.mdx create mode 100644 examples/sam/customization/template.yaml diff --git a/docs/components/CallToAction.jsx b/docs/components/CallToAction.jsx index 8891cbe..6fa275f 100644 --- a/docs/components/CallToAction.jsx +++ b/docs/components/CallToAction.jsx @@ -1,9 +1,10 @@ import Link from 'next/link'; import classNames from './CallToAction.module.css'; -const CallToAction = ({ href, text }) => { +const CallToAction = ({ href, text, newTab }) => { + const target = newTab ? '_blank' : '_self'; return ( - + {text} ); diff --git a/docs/components/HomepageHeader.jsx b/docs/components/HomepageHeader.jsx index f432170..43be397 100644 --- a/docs/components/HomepageHeader.jsx +++ b/docs/components/HomepageHeader.jsx @@ -8,10 +8,13 @@ const HomepageHeader = () => {

Fast, zoomable images without servers

- A cost-effective, infinitely scalable IIIF 2.1 image api compliant service packaged as an AWS Serverless Application with minimum setup and no maintenance. Suitable for large institutional collections or small digital humanities projects. -
Community Driven. Open Source. + A cost-effective, infinitely scalable IIIF Image API v2.1 and v3.0 compliant service packaged + as an AWS Serverless Application with minimum setup and no maintenance. Suitable for large institutional collections or + small digital humanities projects. +
Community Driven. Open Source.

- + +   { + const key = tag === '!Ref' ? 'Ref' : tag.replace(/^!/, 'Fn::'); + return { + tag, + identify: (val) => { + const isTagged = Object.keys(val)[0] === key; + return isTagged; + }, + resolve: (val) => { + return { [key]: val }; + }, + stringify: (val) => { + const result = val.value[key]; + return quoted ? `"${result}"` : result; + } + }; +}); diff --git a/docs/lib/cloudformation.js b/docs/lib/cloudformation.js new file mode 100644 index 0000000..2e3a453 --- /dev/null +++ b/docs/lib/cloudformation.js @@ -0,0 +1,32 @@ +const { getTemplate } = require('./cfn-reader'); +const { fence, stringify } = require('./render'); + +function example(format) { + const template = getTemplate('../examples/cloudformation/custom_hostname.yml'); + return fence(stringify(template, format), format); +} + +function parameterList(format = 'object') { + const { Parameters } = getTemplate('../sam/template.yml'); + for (const key in Parameters) { + Parameters[key] = Parameters[key].Type; + } + + const result = { + Type: 'AWS::Serverless::Application', + Properties: { + Location: { + ApplicationId: + 'arn:aws:serverlessrepo:us-east-1:625046682746:applications/serverless-iiif-standalone-dev', + SemanticVersion: '5.0.0' + }, + Parameters + } + }; + return fence(stringify(result, format), format); +} + +module.exports = { + example, + parameterList +}; diff --git a/docs/lib/render.js b/docs/lib/render.js new file mode 100644 index 0000000..b2594a3 --- /dev/null +++ b/docs/lib/render.js @@ -0,0 +1,41 @@ +const customTags = require('./cfn-tags'); +const YAML = require('yaml'); + +function fence(code, format) { + return '```' + `${format}\n${code}\n` + '```'; +} + +function stringify(data, format) { + switch (format) { + case 'json': + return JSON.stringify(data, null, 2); + case 'yaml': + return YAML.stringify(data, { customTags }); + default: + return data.toString(); + } +} + +function displayValue(v) { + if (v === '') return '""'; + if (v.join) return v.join(' | '); + return v; +} + +function present(v) { + if (v === 0) return true; + if (v === '') return true; + return !!v; +} + +function snake(str) { + return str.replace(/\B([A-Z])/g, '_$1').toLowerCase(); +} + +module.exports = { + displayValue, + fence, + present, + snake, + stringify +}; diff --git a/docs/package-lock.json b/docs/package-lock.json index 9a9368d..157dbf4 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -10,10 +10,12 @@ "license": "ISC", "dependencies": { "next": "^13.4.10", + "next-mdx-remote": "^4.4.1", "nextra": "^2.10.0", "nextra-theme-docs": "^2.10.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "sass": "^1.64.2" }, "devDependencies": { "eslint-config-next": "^13.4.12" @@ -934,6 +936,18 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -1169,6 +1183,14 @@ "node": ">=0.6" } }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, "node_modules/bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", @@ -1195,7 +1217,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -1333,6 +1354,43 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -2990,7 +3048,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3061,6 +3118,19 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -3614,6 +3684,11 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.2.tgz", + "integrity": "sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3737,6 +3812,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -3850,7 +3936,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3859,7 +3944,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -3910,7 +3994,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -5671,6 +5754,14 @@ "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==" }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -6051,7 +6142,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -6181,6 +6271,17 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/reading-time": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", @@ -6617,6 +6718,22 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sass": { + "version": "1.64.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.2.tgz", + "integrity": "sha512-TnDlfc+CRnUAgLO9D8cQLFu/GIjJIzJCGkE7o4ekIGQOH7T3GetiRR/PsTWJUHhkzcSPrARkPI+gNWn5alCzDg==", + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -7116,7 +7233,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, diff --git a/docs/package.json b/docs/package.json index ba1bc92..f24a1fc 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,10 +14,12 @@ "license": "ISC", "dependencies": { "next": "^13.4.10", + "next-mdx-remote": "^4.4.1", "nextra": "^2.10.0", "nextra-theme-docs": "^2.10.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "sass": "^1.64.2" }, "devDependencies": { "eslint-config-next": "^13.4.12" diff --git a/docs/pages/docs/advanced-usage/request-response-functions.mdx b/docs/pages/docs/advanced-usage/request-response-functions.mdx index 8f61517..e60cc37 100644 --- a/docs/pages/docs/advanced-usage/request-response-functions.mdx +++ b/docs/pages/docs/advanced-usage/request-response-functions.mdx @@ -1,76 +1,63 @@ import { Callout } from 'nextra/components'; -# Request/Response Functions +### Request/Response Functions -The SAM deploy template takes several optional parameters to enable the association of [CloudFront Functions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html) or [Lambda@Edge Functions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html) with the CloudFront distribution. These functions can perform authentication and authorization functions, change how the S3 file and/or image dimensions are resolved, or alter the response from the lambda or cache. These parameters are: +The IIIF service can be heavily customized through the use of [CloudFront Functions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html) or [Lambda@Edge Functions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html) attached to a CloudFront distribution in front of the service. It's important to understand the four stages of CloudFront processing in order to know where a given type of customization belongs. -- `OriginRequestARN`: ARN of the Lambda@Edge Function to use at the origin-request stage -- `OriginResponseARN`: ARN of the Lambda@Edge Function to use at the origin-response stage -- `ViewerRequestARN`: ARN of the CloudFront or Lambda@Edge Function to use at the viewer-request stage -- `ViewerRequestType`: Type of viewer-request Function to use (`CloudWatch Function` or `Lambda@Edge`) -- `ViewerResponseARN`: ARN of the CloudFront or Lambda@Edge Function to use at the viewer-response stage -- `ViewerResponseType`: Type of viewer-response Function to use (`CloudWatch Function` or `Lambda@Edge`) +- A `viewer-request` function will be called on every request, cached or not. This is the appropriate place to attach + a function that performs authorization, authentication, or anything else whose result should *not* be cached. +- An `origin-request` function will only be called when CloudFront refreshes the content from the origin (e.g., the IIIF server). + It's the appropriate place to attach a function that *should* be cached, such as S3 file resolution or the retrieval of + image dimensions. +- Similarly, the `origin-response` and `viewer-response` functions are called after the IIIF server returns its response + and before CloudFront passes it on to the viewer, respectively. They can be used to alter the response in a way that is + either cached or ephemeral. -These functions, if used, must be created, configured, and published before the serverless application is deployed. - -## Examples +#### Examples These examples use CloudFront Functions. Lambda@Edge functions are slightly more complicated in terms of the event structure but the basic idea is the same. -### Simple Authorization +##### Simple Authorization -```javascript +```JavaScript function handler(event) { - if (notAuthorized) { - // based on something in the event.request - return { - statusCode: 403, - statusDescription: 'Unauthorized' + if (notAuthorized) { // based on something in the event.request + return { + statusCode: 403, + statusDescription: 'Unauthorized' + }; }; - } - return event.request; + return event.request; } ``` -### Custom File Location / Image Dimensions +##### Custom File Location / Image Dimensions -```javascript +```JavaScript function handler(event) { var request = event.request; - request.headers['x-preflight-location'] = { - value: 's3://image-bucket/path/to/correct/image.tif' - }; - request.headers['x-preflight-dimensions'] = { - value: JSON.stringify({ width: 640, height: 480 }) - }; + request.headers['x-preflight-location'] = {value: 's3://image-bucket/path/to/correct/image.tif'}; + request.headers['x-preflight-dimensions'] = {value: JSON.stringify({ width: 640, height: 480 })}; return request; } ``` The `x-preflight-dimensions` header can take several shapes: -- `{ width, height }` (or `[{ width, height }]`) - a straightforward, single-resolution image -- `[{ width, height }, { width, height }, ...]` - a multi-resolution image with pages of the specified sizes -- `{ width, height, pages }` - a multi-resolution image with the specified number of `pages`, each half the size of the one before -- `{ width, height, limit }` - a multi-resolution image in which the smallest width and height are both less than the specified `limit` +* `{ width, height }` (or `[{ width, height }]`) - a straightforward, single-resolution image +* `[{ width, height }, { width, height }, ...]` - a multi-resolution image with pages of the specified sizes +* `{ width, height, pages }` - a multi-resolution image with the specified number of `pages`, each half the size of the one before +* `{ width, height, limit }` - a multi-resolution image in which the smallest width and height are both less than the specified `limit` For example, the following dimension values would all describe the same pyramidal image: -- `[{ width: 2048, height: 1536 }, { width: 1024, height: 768 }, { width: 512, height: 384 }]` -- `{ width: 2048, height: 1536, pages: 3 }` -- `{ width: 2048, height: 1536, limit: 480 }` +* `[{ width: 2048, height: 1536 }, { width: 1024, height: 768 }, { width: 512, height: 384 }]` +* `{ width: 2048, height: 1536, pages: 3 }` +* `{ width: 2048, height: 1536, limit: 480 }` The `limit` calculator will keep going until both dimensions are _less than_ the limit, not _less than or equal to_. So a `limit: 512` on the third example above would generate a fourth page at `{ width: 256, height: 192 }`. - The SAM deploy template adds a `preflight=true` environment variable to the - main IIIF Lambda if a preflight function is provided. The function will _only_ - look for the preflight headers if this environment variable is `true`. This - prevents requests from including those headers directly if no preflight - function is present. - -If you do use a preflight function, make sure it strips -out any `x-preflight-location` and `x-preflight-dimensions` headers that it -doesn't set itself. +If you plan to use CloudFront functions to add either of the above `x-preflight-` headers to incoming requests, you *must* set the value of the `Preflight` parameter to `true` when deploying `serverless-iiif`. The function will _only_ look for the preflight headers if this environment variable is `true`. This prevents requests from including those headers directly if no preflight function is present. If you do use a preflight function, make sure it strips out any `x-preflight-location` and `x-preflight-dimensions` headers that it doesn't set itself. diff --git a/docs/pages/docs/index.mdx b/docs/pages/docs/index.mdx index 49fc56c..b328ecf 100644 --- a/docs/pages/docs/index.mdx +++ b/docs/pages/docs/index.mdx @@ -19,20 +19,19 @@ import { Callout } from 'nextra/components'; ## Description -A cost-effective, infinitely scalable [IIIF 2.1](https://iiif.io/api/image/2.1/) image api compliant service packaged as an [AWS Serverless Application](https://aws.amazon.com/serverless/sam/) with minimum setup and no maintenance. Suitable for large institutional collections or small digital humanities projects. +A cost-effective, infinitely scalable [IIIF 2.1](https://iiif.io/api/image/2.1/) and [3.0](https://iiif.io/api/image/3.0/) image API compliant service packaged as an [AWS Serverless Application](https://aws.amazon.com/serverless/sam/) with minimum setup and no maintenance. Suitable for large institutional collections or small digital humanities projects. ## Components -- A simple [Lambda Function](https://aws.amazon.com/lambda/) wrapper for the [iiif-processor](https://www.npmjs.com/package/iiif-processor) module. -- A [Lambda Function URL](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html) that is used to invoke the IIIF API via HTTPS. -- A [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html) containing all the dependencies for the Lambda Function. -- An optional [CloudFormation](https://aws.amazon.com/cloudformation/) template describing the resources needed to deploy the application. +* A simple [Lambda Function](https://aws.amazon.com/lambda/) wrapper for the [iiif-processor](https://www.npmjs.com/package/iiif-processor) module. +* A [Lambda Function URL](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html) that is used to invoke the IIIF API via HTTPS. +* A [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html) containing all the dependencies for the Lambda Function. ## Prerequisites -- Some basic knowledge of AWS. -- An Amazon Web Services account with permissions to create resources via the console and/or command line. -- An [Amazon S3](https://aws.amazon.com/s3/) bucket to hold the source images to be served via IIIF. +* Some basic knowledge of AWS. +* An Amazon Web Services account with permissions to create resources via the console and/or command line. +* An [Amazon S3](https://aws.amazon.com/s3/) bucket to hold the source images to be served via IIIF. Note: The Lambda Function will be granted read access to this bucket. diff --git a/docs/pages/docs/quick-start/_meta.json b/docs/pages/docs/quick-start/_meta.json index 3acce88..25711a8 100644 --- a/docs/pages/docs/quick-start/_meta.json +++ b/docs/pages/docs/quick-start/_meta.json @@ -2,5 +2,6 @@ "index": "Options", "deployment-sam": "Deploying via the AWS Serverless Application Repository", "deployment-command-line": "Deploying via the Command Line", + "infrastructure": "Deploying via Infrastructure Tools", "deleting-the-app": "Deleting the App" } diff --git a/docs/pages/docs/quick-start/deleting-the-app.mdx b/docs/pages/docs/quick-start/deleting-the-app.mdx index 080cc24..869de2a 100644 --- a/docs/pages/docs/quick-start/deleting-the-app.mdx +++ b/docs/pages/docs/quick-start/deleting-the-app.mdx @@ -1,5 +1,7 @@ # Deleting the application -The easiest way to delete the application is either from the [Lambda Applications Console](https://console.aws.amazon.com/lambda/home#/applications) or by deleting its [CloudFormation Stack](https://console.aws.amazon.com/cloudformation/home#/stacks?filteringStatus=active&filteringText=&viewNested=true&hideStacks=false). +If you deployed the application on its own, the easiest way to delete the application is either from the [Lambda Applications Console](https://console.aws.amazon.com/lambda/home#/applications) or by deleting its [CloudFormation Stack](https://console.aws.amazon.com/cloudformation/home#/stacks?filteringStatus=active&filteringText=&viewNested=true&hideStacks=false). If you deployed from the command line, you can also use the `npm run delete` command. + +If you deployed using an [infrastructure tool](deployment-infrastructure.mdx) such as AWS CloudFormation or Terraform, you should use that tool's destroy/teardown functionality to remove the IIIF stack as well. diff --git a/docs/pages/docs/quick-start/deployment-command-line.mdx b/docs/pages/docs/quick-start/deployment-command-line.mdx index 8b9f3b8..9a454ff 100644 --- a/docs/pages/docs/quick-start/deployment-command-line.mdx +++ b/docs/pages/docs/quick-start/deployment-command-line.mdx @@ -10,7 +10,7 @@ Make sure you have the [SAM CLI](https://aws.amazon.com/serverless/sam/) and [AW ### Step 2 -Make sure the AWS CLI is [properly configured](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) with credentials that have sufficient access to manage IAM, S3, Lambda, and (optionally) CloudFront resources. +Make sure the AWS CLI is [properly configured](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) with credentials that have sufficient access to manage IAM, S3, and Lambda resources. ### Step 3 diff --git a/docs/pages/docs/quick-start/deployment-sam.mdx b/docs/pages/docs/quick-start/deployment-sam.mdx index 4e87e7e..12f90fd 100644 --- a/docs/pages/docs/quick-start/deployment-sam.mdx +++ b/docs/pages/docs/quick-start/deployment-sam.mdx @@ -8,10 +8,7 @@ import { Steps } from 'nextra/components'; ### Step 1 -Click one of the following links to deploy the desired application from the AWS Console: - -- [Standalone (Lambda-Only) Version](https://console.aws.amazon.com/lambda/home#/create/app?applicationId=arn:aws:serverlessrepo:us-east-1:625046682746:applications/serverless-iiif-standalone) -- [Caching (CloudFront-Enabled) Version](https://console.aws.amazon.com/lambda/home#/create/app?applicationId=arn:aws:serverlessrepo:us-east-1:625046682746:applications/serverless-iiif-cloudfront) +Find the [serverless-iiif application](https://console.aws.amazon.com/lambda/home#/create/app?applicationId=arn:aws:serverlessrepo:us-east-1:625046682746:applications/serverless-iiif) in the AWS Serverless Application Repository. ### Step 2 diff --git a/docs/pages/docs/quick-start/index.mdx b/docs/pages/docs/quick-start/index.mdx index b5fe169..f688bef 100644 --- a/docs/pages/docs/quick-start/index.mdx +++ b/docs/pages/docs/quick-start/index.mdx @@ -1,9 +1 @@ -# Options - -`serverless-iiif` comes in two flavors: _Standalone (Lambda-only)_ and _Caching (CloudFront-enabled)_. The Standalone version is much simpler, but lacks the following features: - -- Custom Domain Name - - Standalone URLs are in the `lambda-url.AWS_REGION.on.aws` domain (e.g., `https://fu90293j0pj902j902c32j902.lambda-url.us-east-1.on.aws/iiif/2/`) - - Caching URLs _without_ Custom Domains are in the `cloudfront.net` domain (e.g., `https://d3kmjdzzy1l5t3.cloudfront.net/iiif/2/`) -- Responses larger than ~6MB -- CloudFront function support (for pre/post-processing requests and responses) +`serverless-iiif` is deployed as a Lambda Function URL, in the `lambda-url.AWS_REGION.on.aws` domain (e.g., `https://fu90293j0pj902j902c32j902.lambda-url.us-east-1.on.aws/iiif/2/`). In order to use a custom domain name, or other features like caching and pre/post-processing functions, you'll have to set up a [CloudFront distribution](https://aws.amazon.com/cloudfront/). diff --git a/docs/pages/docs/quick-start/infrastructure/_meta.json b/docs/pages/docs/quick-start/infrastructure/_meta.json new file mode 100644 index 0000000..01bf4a4 --- /dev/null +++ b/docs/pages/docs/quick-start/infrastructure/_meta.json @@ -0,0 +1,5 @@ +{ + "index": "Introduction", + "cloudformation": "AWS CloudFormation", + "terraform": "Terraform" +} diff --git a/docs/pages/docs/quick-start/infrastructure/cloudformation.mdx b/docs/pages/docs/quick-start/infrastructure/cloudformation.mdx new file mode 100644 index 0000000..72af01d --- /dev/null +++ b/docs/pages/docs/quick-start/infrastructure/cloudformation.mdx @@ -0,0 +1,110 @@ +import styles from './custom.module.scss'; +import { Tab, Tabs } from 'nextra/components'; +import { MDXRemote } from 'next-mdx-remote'; +import { useData } from 'nextra/data'; +import { displayValue, present } from '../../../../lib/render'; + +export async function getStaticProps(context) { + const cfn = await import('../../../../lib/cloudformation'); + const { compileMdx } = await import('nextra/compile'); + const { getParameters, getPropertyList } = await import('../../../../lib/cfn-reader'); + + const renderExample = async (format) => { + const markdown = cfn.example(format); + const mdx = await compileMdx(markdown, { defaultShowCopyCode: true }); + return mdx.result; + }; + + const renderParameterList = async (format) => { + const markdown = cfn.parameterList(format); + const mdx = await compileMdx(markdown, { defaultShowCopyCode: true }); + return mdx.result; + } + + const data = { + yaml: await renderParameterList('yaml'), + json: await renderParameterList('json'), + props: await getPropertyList(), + examples: { + yaml: await renderExample('yaml'), + json: await renderExample('json') + } + }; + return { props: { ssg: { data } } }; +} + +export const Parameters = ({ format }) => { + const { data } = useData(); + return ; +}; + +export const Attribute = ({ Name, Value, Code }) => { + if (present(Value)) { + if (Code) { + return

{Name}: {displayValue(Value)}

+ } else { + return

{Name}: {displayValue(Value)}

+ } + } +} + +export const Property = ({ AllowedPattern, AllowedValues, Default, Description, MaxLength, MaxValue, MinLength, MinValue, Name, Type }) => { + return <> +
+
{Name}
+
+ + + + + + + + + + +
+
+ +} + +export const Properties = () => { + const { data } = useData(); + return
+ {data.props.map((prop) => { + return + })} +
+} + +export const Example = ({ format }) => { + const { data } = useData(); + return <> +} + +export const formats = ['yaml', 'json']; + +Installing `serverless-iiif` as part of a [CloudFormation](https://aws.amazon.com/cloudformation/) template makes it easy to integrate the service with other components. Please refer to the [example](#example) and the [CloudFormation documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html) for more information on how you might tailor these templates to your own needs and deploy them to AWS. + +### Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + + v.toUpperCase())}> + {formats.map((format) => )} + + +### Properties + + + +### Example + +This example template will deploy a full application stack consisting of: + +- A serverless-iiif image server +- A CloudFront distribution with a custom hostname and SSL certificate + + v.toUpperCase())}> + {formats.map((format) => )} + diff --git a/docs/pages/docs/quick-start/infrastructure/custom.module.scss b/docs/pages/docs/quick-start/infrastructure/custom.module.scss new file mode 100644 index 0000000..ca8cf5d --- /dev/null +++ b/docs/pages/docs/quick-start/infrastructure/custom.module.scss @@ -0,0 +1,45 @@ +.cfAttribute { + line-height: 1.5rem; + + dt>code { + font-size: 1rem; + font-weight: 500; + background-color: rgba(255,255,255,var(--tw-bg-opacity)); + border: none; + padding-left: 0; + } + + p { + margin: 0 1em 0.5em 1em; + } +} + +.tfAttribute { + h3 { + font-size: 1rem; + + .tfAttributeType { + align-items: center; + background-color: rgba(244,244,244,var(--tw-bg-opacity)); + border-radius: 4px; + color: rgba(73,73,73,var(--tw-bg-opacity)); + font-size: .75rem; + display: inline-flex; + padding: 0 .75rem; + } + } +} + +html[class~=dark] { + .cfAttribute { + dt>code { + background-color: rgba(0,0,0,var(--tw-bg-opacity)); + } + } + + .tfAttribute { + h3 .tfAttributeType { + background-color: rgba(11,11,11,var(--tw-bg-opacity)); + } + } +} diff --git a/docs/pages/docs/quick-start/infrastructure/index.mdx b/docs/pages/docs/quick-start/infrastructure/index.mdx new file mode 100644 index 0000000..4ded224 --- /dev/null +++ b/docs/pages/docs/quick-start/infrastructure/index.mdx @@ -0,0 +1,3 @@ +# Deploying via Infrastructure Tools + +You will most likely want to deploy `serverless-iiif` as part of a larger infrastructure stack. This stack might include a caching layer (AWS CloudFront), [functions](../advanced-usage/request-response-functions.mdx) to customize the services behavior, a custom domain name, and more. This section provides documentation and examples of how to deploy the service using [AWS CloudFormation](./infrastructure/cloudformation.mdx) or [Terraform](./infrastructure/terraform.mdx). \ No newline at end of file diff --git a/docs/pages/docs/quick-start/infrastructure/terraform.mdx b/docs/pages/docs/quick-start/infrastructure/terraform.mdx new file mode 100644 index 0000000..498a4e3 --- /dev/null +++ b/docs/pages/docs/quick-start/infrastructure/terraform.mdx @@ -0,0 +1,162 @@ +import styles from './custom.module.scss'; +import { Tab, Tabs } from 'nextra/components'; +import { MDXRemote } from 'next-mdx-remote'; +import { useData } from 'nextra/data'; +import { displayValue, present, snake } from '../../../../lib/render'; + +export async function getStaticProps(context) { + const { compileMdx } = await import('nextra/compile'); + const { getPropertyList } = await import('../../../../lib/cfn-reader'); + + const inputs = (await getPropertyList({ descPrefix: 'Description: ' })).map((prop) => { + prop.Name = snake(prop.Name); + prop.Type = prop.Type.toLowerCase(); + if (prop.AllowedValues?.includes(false)) prop.Type = 'bool'; + return prop; + }); + + const outputs = [ + { Name: 'stack_id', Description: 'The ID of the serverless-iiif application stack' }, + { Name: 'serverless_iiif_endpoint_v2', Description: 'IIIF Image API v2 Endpoint' }, + { Name: 'serverless_iiif_endpoint_v3', Description: 'IIIF Image API v3 Endpoint' }, + { Name: 'serverless_iiif_function_domain', Description: 'IIIF Function Domain Name' }, + { Name: 'serverless_iiif_function_url', Description: 'IIIF Function URL' } + ] + + return { props: { ssg: { data: { inputs, outputs } } } }; +} + +export const ModuleLink = ({ label, fragment='' }) => { + const branch = 'v5.0-streaming-iiif3'; + return <> + {label} + +}; + +export const Attribute = ({ Name, Value, Code }) => { + if (present(Value)) { + if (Code) { + return

{Name}: {displayValue(Value)}

+ } else { + return

{Name}: {displayValue(Value)}

+ } + } +} + +export const PropTabs = ({ children }) => { + const { data } = useData(); + const items = [ + `Inputs (${data.inputs.length})`, + `Outputs (${data.outputs.length})`, + 'Examples' + ]; + return {children} +} + +export const Input = ({ AllowedPattern, AllowedValues, Default, Description, MaxLength, MaxValue, MinLength, MinValue, Name, Type }) => { + return <> +
+

{Name} {Type}

+ + +
+ +} + +export const Inputs = ({ required }) => { + const { data } = useData(); + const inputs = data.inputs.filter(({ Default }) => present(Default) !== required); + + return
+ { + inputs + .filter(({ Default }) => { + return (Default === undefined) === required; + }) + .map((input) => { + return + }) + } +
+} + +export const Output = ({ Name, Description }) => { + return <> +
+

{Name}

+ +
+ +} + +export const Outputs = () => { + const { data: { outputs } } = useData(); + return
{outputs.map((output) => )}
+} + +The `serverless-iiif` GitHub repository includes a that can be used as a drop-in component in any +Terraform manifest. Please refer to the documentation and examples below, as well as the [Terraform documentation](https://developer.hashicorp.com/terraform) +for more information on how you might use these tools to deploy your own custom stack to AWS. + + + + ### Required Inputs +
+ + These variables must be set in the `module` block when using this module. + + + + + ### Optional Inputs +
+ + These variables have default values and don't have to be set to use this module. You may set these variables to override their default + values. + + +
+ + + + + + + ### Minimal Example + + ```hcl + module "serverless_iiif" { + source = "github.com/samvera/serverless-iiif//extras/terraform" + + source_bucket = "iiif-images" + stack_name = "my-iiif-service" + } + ``` + + ### (Almost) Full Example + + ```hcl + module "serverless_iiif" { + source = "github.com/samvera/serverless-iiif//extras/terraform" + + source_bucket = "iiif-images" + stack_name = "my-iiif-service" + cors_allow_credentials = true + cors_allow_headers = "X-Custom-Header,Upgrade-Insecure-Requests" + cors_allow_origin = "REFLECT_ORIGIN" + cors_expose_headers = "Content-Encoding" + cors_max_age = 600 + force_host = "iiif.my-domain.edu" + iiif_lambda_memory = 2048 + iiif_lambda_timeout = 120 + pixel_density = 600 + preflight = true + resolver_template = "iiif/%s.tif" + + tags = { + Project = "my-image-service" + } + } + ``` + +
diff --git a/examples/cloudformation/custom_hostname.yml b/examples/cloudformation/custom_hostname.yml index 7477da8..56bd184 100644 --- a/examples/cloudformation/custom_hostname.yml +++ b/examples/cloudformation/custom_hostname.yml @@ -86,7 +86,7 @@ Resources: Route53Record: Type: 'AWS::Route53::RecordSet' Properties: - Name: !Ref '${CacheHostName}.${CacheDomainName}' + Name: !Sub '${CacheHostName}.${CacheDomainName}' HostedZoneName: !Sub '${CacheDomainName}.' Type: A AliasTarget: diff --git a/examples/sam/customization/template.yaml b/examples/sam/customization/template.yaml new file mode 100644 index 0000000..20d34e7 --- /dev/null +++ b/examples/sam/customization/template.yaml @@ -0,0 +1,147 @@ +Transform: "AWS::Serverless-2016-10-31" +Metadata: + AWS::ServerlessRepo::Application: + Name: serverless-iiif-customization-example + Description: | + Sample SAM application for deploying serverless-iiif with a + custom Lambda function + Author: Samvera + CacheDomainName: + Type: String + Description: Custom Domain Name for the API Gateway Endpoint or CloudFront Cache + CacheSSLCertificate: + Type: String + Description: ARN of the ACM SSL Certification to use for the API Gateway Endpoint or CloudFront Cache + SourceBucket: + Type: String + Description: Name of bucket containing source images + TokenSecret: + Type: String + Description: Secret Key for verifying Javascript Web Tokens (for auth) +Resources: + CachingIdentity: + Type: "AWS::CloudFront::CloudFrontOriginAccessIdentity" + Properties: + CloudFrontOriginAccessIdentityConfig: + Comment: "Caching Distribution Identity" + OriginRequestPolicy: + Type: "AWS::CloudFront::OriginRequestPolicy" + Properties: + OriginRequestPolicyConfig: + Name: !Sub "${AWS::StackName}-allow-preflight-headers" + Comment: Allows IIIF preflight headers + CookiesConfig: + CookieBehavior: none + HeadersConfig: + HeaderBehavior: whitelist + Headers: + - x-preflight-location + - x-preflight-dimensions + QueryStringsConfig: + QueryStringBehavior: none + ResponseHeaderPolicy: + Type: "AWS::CloudFront::ResponseHeadersPolicy" + Properties: + ResponseHeadersPolicyConfig: + Name: !Sub "${AWS::StackName}-allow-cors-response-headers" + Comment: Allows IIIF CORS response headers + CorsConfig: + AccessControlAllowCredentials: false + AccessControlAllowHeaders: + Items: ["*"] + AccessControlAllowMethods: + Items: ["GET", "OPTIONS"] + AccessControlAllowOrigins: + Items: ["*"] + AccessControlExposeHeaders: + Items: ["cache-control", "content-language", "content-length", "content-type", "date", "expires", "last-modified", "pragma"] + AccessControlMaxAgeSec: 3600 + OriginOverride: false + CachingEndpoint: + Type: "AWS::CloudFront::Distribution" + Properties: + DistributionConfig: + Enabled: true + PriceClass: PriceClass_100 + Aliases: + - !Ref CacheDomainName + ViewerCertificate: + AcmCertificateArn: !Ref CacheSSLCertificate + MinimumProtocolVersion: 'TLSv1' + SslSupportMethod: 'sni-only' + Origins: + - Id: IiifLambda + CustomOriginConfig: + OriginProtocolPolicy: https-only + DomainName: + Fn::GetAtt: IiifApp.Outputs.FunctionDomain + DefaultCacheBehavior: + TargetOriginId: IiifLambda + ViewerProtocolPolicy: https-only + AllowedMethods: ["GET", "HEAD", "OPTIONS"] + CachedMethods: ["GET", "HEAD"] + CachePolicyId: !Ref CachePolicyID + OriginRequestPolicyId: !Ref OriginRequestPolicy + ResponseHeadersPolicyId: !Ref ResponseHeaderPolicy + LambdaFunctionAssociations: + - EventType: viewer-request + LambdaFunctionARN: !Ref ViewerRequestFunctionVersion + IncludeBody: false + ViewerRequestFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + - edgelambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + ViewerRequestFunction: + Type: AWS::Serverless::Function + Properties: + Description: Per-request customization function + CodeUri: ./viewer-request/ + Handler: index.handler + Runtime: nodejs18.x + Architectures: + - arm64 + MemorySize: 128 + Timeout: 2 + Role: !Ref ViewerRequestFunctionRole + Environment: + Variables: + IIIF_SOURCE_BUCKET: !Ref SourceBucket + JWT_SECRET: !Ref TokenSecret + ViewerRequestFunctionVersion: + Type: AWS::Lambda::Version + Properties: + FunctionName: !GetAtt ViewerRequestFunction.Arn + IiifApp: + Type: AWS::Serverless::Application + Properties: + Location: + ApplicationId: arn:aws:serverlessrepo:us-east-1:625046682746:applications/serverless-iiif + SemanticVersion: 5.0.0 + Parameters: + CorsAllowOrigin: REFLECT_ORIGIN + ForceHost: !Ref CacheDomainName + Preflight: "true" + SourceBucket: !Ref SourceBucket +Outputs: + Endpoint: + Description: IIIF Endpoint URL + Value: !Sub "https://${CacheDomainName}/iiif" + DistributionId: + Description: Caching Distribution ID + Value: + Ref: CachingEndpoint + Export: + Name: !Sub "${AWS::StackName}:DistributionId" + LambdaFunction: + Description: IIIF Lambda Function Name + Value: !Ref IiifApp diff --git a/extras/terraform/README.md b/extras/terraform/README.md index 4f475b1..0129680 100644 --- a/extras/terraform/README.md +++ b/extras/terraform/README.md @@ -9,6 +9,7 @@ Terraform module which deploys a [serverless-iiif](https://github.com/samvera/se ``` module "serverless_iiif" { source = "github.com/samvera/serverless-iiif//extras/terraform" + source_bucket = "iiif-images" stack_name = "my-iiif-service" } @@ -19,6 +20,7 @@ module "serverless_iiif" { ``` module "serverless_iiif" { source = "github.com/samvera/serverless-iiif//extras/terraform" + source_bucket = "iiif-images" stack_name = "my-iiif-service" cors_allow_credentials = true @@ -60,14 +62,13 @@ module "serverless_iiif" { ## Outputs -| Name | Description | -|---------------------------|-------------------------------------------------------| -| `stack_id` | The ID of the serverless-iiif application stack | -| `outputs` | A map of outputs from the serverless-iiif application | -| `outputs.EndpointV2` | IIIF Image API v2 Endpoint | -| `outputs.EndpointV3` | IIIF Image API v3 Endpoint | -| `outputs.FunctionDomain` | IIIF Function Domain Name | -| `outputs.FunctionUrl` | IIIF Function URL | +| Name | Description | +|------------------------------------|-------------------------------------------------------| +| `stack_id` | The ID of the serverless-iiif application stack | +| `serverless_iiif_endpoint_v2` | IIIF Image API v2 Endpoint | +| `serverless_iiif_endpoint_v3` | IIIF Image API v3 Endpoint | +| `serverless_iiif_function_domain` | IIIF Function Domain Name | +| `serverless_iiif_function_url` | IIIF Function URL | ## License diff --git a/extras/terraform/outputs.tf b/extras/terraform/outputs.tf index b455942..2616c05 100644 --- a/extras/terraform/outputs.tf +++ b/extras/terraform/outputs.tf @@ -1,9 +1,24 @@ +locals { + outputs = aws_serverlessapplicationrepository_cloudformation_stack.serverless_iiif.outputs +} + output "stack_id" { value = aws_serverlessapplicationrepository_cloudformation_stack.serverless_iiif.id description = "The ID of the serverless-iiif application stack" } -output "outputs" { - value = aws_serverlessapplicationrepository_cloudformation_stack.serverless_iiif.outputs - description = "A map of outputs from the serverless-iiif application" +output "serverless_iiif_endpoint_v2" { + value = local.outputs.EndpointV2 +} + +output "serverless_iiif_endpoint_v3" { + value = local.outputs.EndpointV3 +} + +output "serverless_iiif_function_domain" { + value = local.outputs.FunctionDomain +} + +output "serverless_iiif_function_url" { + value = local.outputs.FunctionUrl } diff --git a/extras/terraform/variables.tf b/extras/terraform/variables.tf index 0acdee1..b2cf2e6 100644 --- a/extras/terraform/variables.tf +++ b/extras/terraform/variables.tf @@ -69,11 +69,6 @@ variable "pixel_density" { type = number description = "Hardcoded DPI/Pixel Density/Resolution to encode in output images" default = 0 - - validation { - condition = var.pixel_density >= 0 - error_message = "pixel_density must be >= 0" - } } variable "preflight" { diff --git a/package-lock.json b/package-lock.json index c4b5dab..d8d5a5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,8 @@ "eslint-plugin-standard": "^4.0.1", "iiif-processor": "^4.0.0", "jest": "^26.4.2", - "lambda-stream": "^0.4.0" + "lambda-stream": "^0.4.0", + "yaml": "^2.3.1" } }, "../node-iiif": { @@ -9022,6 +9023,15 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -15967,6 +15977,12 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "dev": true + }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", diff --git a/package.json b/package.json index 8c0409e..98e2efa 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "eslint-plugin-standard": "^4.0.1", "iiif-processor": "^4.0.0", "jest": "^26.4.2", - "lambda-stream": "^0.4.0" + "lambda-stream": "^0.4.0", + "yaml": "^2.3.1" }, "jest": { "collectCoverageFrom": [ diff --git a/sam/template.yml b/sam/template.yml index c773fc9..8e932a8 100644 --- a/sam/template.yml +++ b/sam/template.yml @@ -87,6 +87,9 @@ Parameters: Preflight: Type: String Description: Indicates whether the function should expect preflight headers + AllowedValues: + - false + - true Default: false ResolverTemplate: Type: String