Skip to content

ops: s3 index script #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions scripts/s3index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Extracts all objects from an S3 bucket and generates an HTML index file.
*/
import { S3Client, ListObjectsV2Command, PutObjectCommand } from "@aws-sdk/client-s3";

const BUCKET = process.env.ARCHIVE_S3_BUCKET || "s3-cve";
const REGION = process.env.AWS_REGION || "us-west-2";

const s3 = new S3Client({ region: REGION });

async function listAllObjects(bucket) {
let contents = [];
let continuationToken;

do {
const res = await s3.send(new ListObjectsV2Command({
Bucket: bucket,
ContinuationToken: continuationToken
}));
contents = contents.concat(res.Contents || []);
continuationToken = res.IsTruncated ? res.NextContinuationToken : null;
} while (continuationToken);

return contents.map(obj => obj.Key);
}

function generateHtml(keys) {
const updated = new Date().toISOString();
const links = keys.map(k => `<li><a href="./${encodeURI(k)}">${k}</a></li>`).join("\n");
return `<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Index of ${BUCKET}</title></head>
<body>
<h1>Index of ${BUCKET}</h1>
<ul>
${links}
</ul>
<p>Updated: ${updated}</p>
</body>
</html>`;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this a tiny bit nicer. How about something like:

function generateHtml(objects) {
  const updated = new Date().toISOString();
  const rows = objects.map(obj => {
    const size = obj.Size ? formatFileSize(obj.Size) : '-';
    const modified = obj.LastModified ? obj.LastModified.toISOString().split('T')[0] : '-';
    const escapedKey = escapeHtml(obj.Key);
    
    return `<tr style="border-bottom: 1px solid #ddd;">
      <td style="padding: 8px;"><a href="./${encodeURI(obj.Key)}" style="color: #0066cc; text-decoration: none;">${escapedKey}</a></td>
      <td style="padding: 8px;">${modified}</td>
      <td style="padding: 8px; text-align: right; font-family: monospace;">${size}</td>
    </tr>`;
  }).join('\n');

  return `<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>CVE Reference Archive</title>
</head>
<body style="font-family: Arial, sans-serif; margin: 20px; color: #333;">
  <h1 style="border-bottom: 1px solid #ccc; padding-bottom: 5px;">CVE Reference Archive</h1>
  <table style="width: 100%; border-collapse: collapse;">
    <tr style="background: #f5f5f5; border-bottom: 2px solid #ccc;">
      <th style="padding: 8px; text-align: left;">Name</th>
      <th style="padding: 8px; text-align: left;">Modified</th>
      <th style="padding: 8px; text-align: right;">Size</th>
    </tr>
${rows}
  </table>
  <p style="margin-top: 20px; color: #666; font-size: 0.9em;">Updated: ${updated}</p>
</body>
</html>`;
}

function formatFileSize(bytes) {
  if (bytes === 0) return '0 B';
  const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
}

function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

This will format it as a table, and also list some metadata for each object.

also requires a change to the list function to return entire objects instead of just keys:

async function listAllObjects(bucket) {
  let contents = [];
  let continuationToken;
  
  do {
    const res = await s3.send(new ListObjectsV2Command({
      Bucket: bucket,
      ContinuationToken: continuationToken
    }));
    contents = contents.concat(res.Contents || []);
    continuationToken = res.IsTruncated ? res.NextContinuationToken : null;
  } while (continuationToken);
  
  return contents;
}



/**
* Upload a string as index.html to the given bucket
*/
async function uploadIndex(bucket, region, htmlString) {
const s3 = new S3Client({ region });
const cmd = new PutObjectCommand({
Bucket: bucket,
Key: "index.html",
Body: htmlString,
ContentType: "text/html; charset=utf-8"
});

await s3.send(cmd);
console.log(`Uploaded index.html to s3://${bucket}/index.html`);
}


(async () => {
try {
const keys = await listAllObjects(BUCKET);
const html = generateHtml(keys);
await uploadIndex(BUCKET, REGION, html);
} catch (err) {
console.error("Error generating index:", err);
process.exit(1);
}
})();