mirror of
https://github.com/replicatedhq/ttl.sh.git
synced 2026-07-10 22:59:19 +00:00
Add source for cloudflare workers
This commit is contained in:
1
,gitignore
Normal file
1
,gitignore
Normal file
@@ -0,0 +1 @@
|
||||
deploy
|
||||
2
cloudflare/.gitignore
vendored
Normal file
2
cloudflare/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
dist/
|
||||
node_modules
|
||||
22
cloudflare/Makefile
Normal file
22
cloudflare/Makefile
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
.PHONY: deps
|
||||
deps:
|
||||
yarn global add node-gyp
|
||||
yarn --silent --frozen-lockfile
|
||||
|
||||
.PHONY: prebuild
|
||||
prebuild:
|
||||
rm -rf build
|
||||
mkdir -p build
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
npx tslint --project ./tsconfig.json --fix
|
||||
|
||||
.PHONY: build
|
||||
build: prebuild
|
||||
`yarn bin`/webpack
|
||||
|
||||
.PHONY: preview
|
||||
preview: build
|
||||
`yarn bin`/workers-preview < dist/bundle.js
|
||||
30
cloudflare/package.json
Normal file
30
cloudflare/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "replreg-cloudflare-worker",
|
||||
"version": "0.0.1",
|
||||
"description": "Cloudflare Workers for replreg.is",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "make test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/replicatedhq/replreg.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/replicatedhq/replreg/issues"
|
||||
},
|
||||
"homepage": "https://github.com/replicatedhq/replreg#readme",
|
||||
"devDependencies": {
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"typescript": "^3.4.5",
|
||||
"webpack": "^4.31.0",
|
||||
"webpack-cli": "^3.3.2",
|
||||
"workers-preview": "^1.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"moment": "^2.24.0",
|
||||
"parse-duration": "^0.1.1"
|
||||
}
|
||||
}
|
||||
46
cloudflare/src/index.ts
Normal file
46
cloudflare/src/index.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { tryParsePutTagRequest, handleTagManifestRequest } from "./tag_manifest";
|
||||
|
||||
if (typeof addEventListener === 'function') {
|
||||
addEventListener('fetch', (e: Event): void => {
|
||||
// work around as strict typescript check doesn't allow e to be of type FetchEvent
|
||||
const fe = e as FetchEvent
|
||||
fe.respondWith(proxyRequest(fe.request))
|
||||
});
|
||||
}
|
||||
|
||||
async function proxyRequest(r: Request): Promise<Response> {
|
||||
const url = new URL(r.url);
|
||||
|
||||
if (!isRegistryRequest(r)) {
|
||||
return fetch(`https://friendly-goldstine-7897fc.netlify.com/${url.pathname}`);
|
||||
}
|
||||
|
||||
if (isCatalogRequest(r)) {
|
||||
return new Response(null, {
|
||||
status: 401,
|
||||
});
|
||||
}
|
||||
|
||||
const tagManifestParams = await tryParsePutTagRequest(r.method, url);
|
||||
if (tagManifestParams) {
|
||||
return handleTagManifestRequest(r, tagManifestParams);
|
||||
}
|
||||
|
||||
return fetch(r);
|
||||
}
|
||||
|
||||
function isRegistryRequest(r: Request): boolean {
|
||||
const url = new URL(r.url);
|
||||
return url.pathname.startsWith(`/v2`);
|
||||
}
|
||||
|
||||
function isCatalogRequest(r: Request): boolean {
|
||||
const url = new URL(r.url);
|
||||
return url.pathname.endsWith(`v2/_catalog`);
|
||||
}
|
||||
|
||||
interface FetchEvent extends Event {
|
||||
request: Request;
|
||||
|
||||
respondWith(r: Promise<Response> | Response): Promise<Response>;
|
||||
}
|
||||
73
cloudflare/src/tag_manifest/handler.ts
Normal file
73
cloudflare/src/tag_manifest/handler.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { TagManifestParams } from "./";
|
||||
import * as parseDuration from "parse-duration";
|
||||
import * as moment from "moment";
|
||||
|
||||
export function tryParsePutTagRequest(method: string, url: URL): TagManifestParams | void {
|
||||
const split = url.pathname.split("/");
|
||||
|
||||
// v2/ns/image/manifests/tag <- namespaced images
|
||||
// v2image/manifests/tag <- library images
|
||||
if (method !== "PUT") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (split.length === 6 && split[4] === "manifests") {
|
||||
const tagManifestParams: TagManifestParams = {
|
||||
namespace: split[2],
|
||||
name: split[3],
|
||||
tag: split[5],
|
||||
};
|
||||
|
||||
return tagManifestParams;
|
||||
}
|
||||
|
||||
if (split.length === 5 && split[3] === "manifests") {
|
||||
const tagManifestParams: TagManifestParams = {
|
||||
namespace: "",
|
||||
name: split[2],
|
||||
tag: split[4],
|
||||
};
|
||||
|
||||
return tagManifestParams;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
export async function handleTagManifestRequest(r: Request, params: TagManifestParams): Promise<Response> {
|
||||
r.headers["Accept"] = "application/vnd.docker.distribution.manifest.v2+json";
|
||||
|
||||
const upstreamResponse = await fetch(r);
|
||||
if (upstreamResponse.status != 201) {
|
||||
return upstreamResponse;
|
||||
}
|
||||
|
||||
let imageName;
|
||||
if (params.namespace === "") {
|
||||
imageName = params.name;
|
||||
} else {
|
||||
imageName = `${params.namespace}/${params.name}`;
|
||||
}
|
||||
|
||||
const teapotResponse = {
|
||||
"errors": [
|
||||
{
|
||||
"message": `\r \nimage replreg.is/${imageName} is available now and will be automatically deleted ${expirationFromTag(params.tag)}\n\nreplreg is contributed by Replicated (www.replicated.com)`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(teapotResponse), {
|
||||
status: 418, // This is what works /shrug
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function expirationFromTag(tag: string): string {
|
||||
const parsed = parseDuration(tag);
|
||||
const now = new Date();
|
||||
const then = moment(now.getTime() + parsed);
|
||||
return then.fromNow();
|
||||
}
|
||||
2
cloudflare/src/tag_manifest/index.ts
Normal file
2
cloudflare/src/tag_manifest/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./tag_manifest";
|
||||
export * from "./handler";
|
||||
5
cloudflare/src/tag_manifest/tag_manifest.ts
Normal file
5
cloudflare/src/tag_manifest/tag_manifest.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface TagManifestParams {
|
||||
namespace: string;
|
||||
name: string;
|
||||
tag: string;
|
||||
}
|
||||
18
cloudflare/src/tag_manifest/tag_manifest_test.ts
Normal file
18
cloudflare/src/tag_manifest/tag_manifest_test.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { tryParsePutTagRequest } from "./handler";
|
||||
import * as chai from "chai";
|
||||
|
||||
const testTryParsePutRequest = async () => {
|
||||
let result = await tryParsePutTagRequest("PUT", new URL("v2/random/more-random/manifests/2h"));
|
||||
expect(result).to.be.defined();
|
||||
expect(result.namespace).to.equal("random");
|
||||
expect(result.name).to.equal("more-random");
|
||||
expect(result.tag).to.equal("2h");
|
||||
|
||||
result = await tryParsePutTagRequest("PUT", new URL("v2/image-name/manifests/2h"));
|
||||
expect(result).to.be.defined();
|
||||
expect(result.namespace).to.equal("");
|
||||
expect(result.name).to.equal("image-name");
|
||||
expect(result.tag).to.equal("2h");
|
||||
|
||||
|
||||
}
|
||||
27
cloudflare/tsconfig.json
Normal file
27
cloudflare/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"lib": ["es6", "dom"],
|
||||
"noImplicitAny": false,
|
||||
"noEmitOnError": true,
|
||||
"sourceMap": true,
|
||||
"preserveConstEnums": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"strictNullChecks": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"outDir": "build",
|
||||
"pretty": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.js",
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"src/**/*_test.ts",
|
||||
"src/test/**/*.ts"
|
||||
]
|
||||
}
|
||||
40
cloudflare/webpack.config.js
Normal file
40
cloudflare/webpack.config.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const path = require("path")
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
bundle: path.join(__dirname, "./src/index.ts"),
|
||||
},
|
||||
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.join(__dirname, "dist"),
|
||||
},
|
||||
|
||||
mode: process.env.NODE_ENV || "development",
|
||||
|
||||
plugins: [
|
||||
// Ignore all locale files of moment.js
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
],
|
||||
|
||||
watchOptions: {
|
||||
ignored: /node_modules|dist|\.js/g,
|
||||
},
|
||||
|
||||
devtool: "cheap-module-source-map",
|
||||
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".json"],
|
||||
plugins: [],
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: "awesome-typescript-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
3013
cloudflare/yarn.lock
Normal file
3013
cloudflare/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,9 @@
|
||||
FROM node:10 as deps
|
||||
ADD ./package.json /src/package.json
|
||||
ADD ./Makefile /src/Makefile
|
||||
WORKDIR /src
|
||||
RUN make deps
|
||||
|
||||
FROM node:10
|
||||
ADD . /src
|
||||
WORKDIR /src
|
||||
COPY --from=0 /src .
|
||||
RUN make test
|
||||
RUN make deps test
|
||||
|
||||
ENTRYPOINT ["node"]
|
||||
CMD ["--no-deprecation", "build/server.js", "hooks"]
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
FROM node:10 as deps
|
||||
ADD ./package.json /src/package.json
|
||||
ADD ./Makefile /src/Makefile
|
||||
WORKDIR /src
|
||||
RUN make deps
|
||||
|
||||
FROM node:10
|
||||
ADD . /src
|
||||
WORKDIR /src
|
||||
COPY --from=0 /src .
|
||||
RUN make test
|
||||
RUN make deps test
|
||||
|
||||
ENTRYPOINT ["node"]
|
||||
CMD ["--no-deprecation", "build/server.js", "reap"]
|
||||
|
||||
@@ -58,9 +58,16 @@ async function main(argv): Promise<any> {
|
||||
uri: `https://replreg.is/v2/${imageAndTag[0]}/manifests/${imageAndTag[1]}`,
|
||||
headers,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
}
|
||||
const getResponse = await rp(getOptions);
|
||||
|
||||
if (getResponse.statusCode == 404) {
|
||||
await sremAsync("current.images", image);
|
||||
await delAsync(image);
|
||||
continue;
|
||||
}
|
||||
|
||||
const deleteURI = `https://replreg.is/v2/${imageAndTag[0]}/manifests/${getResponse.headers.etag.replace(/"/g,"")}`;
|
||||
|
||||
// Remove from the registry
|
||||
@@ -68,6 +75,7 @@ async function main(argv): Promise<any> {
|
||||
method: "DELETE",
|
||||
uri: deleteURI,
|
||||
headers,
|
||||
resolveWithFullResponse: true,
|
||||
simple: false,
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@ version: 0.1
|
||||
log:
|
||||
level: info
|
||||
|
||||
compatibility:
|
||||
schema1:
|
||||
enabled: true
|
||||
|
||||
storage:
|
||||
delete:
|
||||
enabled: true
|
||||
|
||||
Reference in New Issue
Block a user