From 7624fbfa13ed39676f1c42bbd600116a2729f0dc Mon Sep 17 00:00:00 2001 From: Salah Al Saleh Date: Mon, 5 Feb 2024 09:16:45 -0800 Subject: [PATCH] Add a dev env in Okteto (#128) * add a dev env --- README.md | 36 +++++++++ cloudflare/src/index.ts | 2 - hooks/.stignore | 56 ++++++++++++++ hooks/Dockerfile.hooks | 3 +- hooks/Dockerfile.reap | 1 + hooks/Makefile | 8 +- hooks/src/commands/hooks.ts | 3 +- hooks/src/commands/reap.ts | 19 ++--- hooks/src/controllers/HookAPI.ts | 4 +- hooks/src/logger.ts | 2 +- hooks/src/router.ts | 2 +- hooks/src/server.ts | 2 +- hooks/src/server/server.ts | 2 +- kustomize/base/kustomization.yaml | 3 + kustomize/overlays/dev/hooks.yaml | 62 ++++++++++++++++ kustomize/overlays/dev/kustomization.yaml | 8 ++ kustomize/overlays/dev/reaper.yaml | 62 ++++++++++++++++ kustomize/overlays/dev/redis.yaml | 50 +++++++++++++ kustomize/overlays/dev/registry.yaml | 90 +++++++++++++++++++++++ okteto.yml | 33 +++++++++ 20 files changed, 424 insertions(+), 24 deletions(-) create mode 100644 hooks/.stignore create mode 100644 kustomize/base/kustomization.yaml create mode 100644 kustomize/overlays/dev/hooks.yaml create mode 100644 kustomize/overlays/dev/kustomization.yaml create mode 100644 kustomize/overlays/dev/reaper.yaml create mode 100644 kustomize/overlays/dev/redis.yaml create mode 100644 kustomize/overlays/dev/registry.yaml create mode 100644 okteto.yml diff --git a/README.md b/README.md index 391d904..9870253 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Develop on Okteto](https://okteto.com/develop-okteto.svg)](https://replicated.okteto.dev/deploy?repository=https://github.com/replicatedhq/ttl.sh&branch=main) + # ttl.sh ## An ephemeral container registry for CI workflows. @@ -6,3 +8,37 @@ ttl.sh is an anonymous, expiring Docker container registry using the official Docker Registry image. This is a set of tools and configurations that can be used to deploy the registry without authentication, but with self-expiring images. +# Development + +Development for the services in this project is done through [Okteto](https://replicated.okteto.dev). + +## Setup + +1. Install the Okteto CLI (`brew install okteto`) +2. Setup Okteto CLI (`okteto context use https://replicated.okteto.dev`) +3. Setup Okteto context in kubectl (`okteto context update-kubeconfig`) +4. Deploy your current branch. (from the ttl.sh root directory: `okteto pipeline deploy`) + +## Debugging + +Okteto is utilized for debugging. New build targets have been added to allow building and running each service in debug mode. + +1. Replace the default container in your Okteto environment with a development container. + 1. From the root directory: `okteto up` or `okteto up ` +2. Run the build targets for the desired service: + 1. ttl-hooks: `make deps build hooks` + 2. ttl-reaper: `make deps build reap` +3. Stop development and go back to the default container. + 1. From the root directory: `okteto down` or `okteto down ` + +## Example workflows + +### Switching branches or rebasing + +1. `git checkout my-new-branch` +2. `okteto pipeline deploy` +3. (make code changes) +4. `okteto up` +5. (test changes, find they don't work, make more changes)... +6. `okteto down` +7. (commit code, and be happy) diff --git a/cloudflare/src/index.ts b/cloudflare/src/index.ts index e7595e1..84cf7c8 100644 --- a/cloudflare/src/index.ts +++ b/cloudflare/src/index.ts @@ -1,5 +1,3 @@ -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 diff --git a/hooks/.stignore b/hooks/.stignore new file mode 100644 index 0000000..326b399 --- /dev/null +++ b/hooks/.stignore @@ -0,0 +1,56 @@ +.git +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + diff --git a/hooks/Dockerfile.hooks b/hooks/Dockerfile.hooks index e80ba8e..d809bfc 100644 --- a/hooks/Dockerfile.hooks +++ b/hooks/Dockerfile.hooks @@ -1,9 +1,10 @@ FROM node:10 as deps + ADD ./package.json /src/package.json ADD ./Makefile /src/Makefile ADD . /src WORKDIR /src -RUN make deps test +RUN make deps test ENTRYPOINT ["node"] CMD ["--no-deprecation", "build/server.js", "hooks"] diff --git a/hooks/Dockerfile.reap b/hooks/Dockerfile.reap index f671644..379861d 100644 --- a/hooks/Dockerfile.reap +++ b/hooks/Dockerfile.reap @@ -1,4 +1,5 @@ FROM node:10 as deps + ADD ./package.json /src/package.json ADD ./Makefile /src/Makefile ADD . /src diff --git a/hooks/Makefile b/hooks/Makefile index 2fc90ca..1e2220d 100644 --- a/hooks/Makefile +++ b/hooks/Makefile @@ -11,7 +11,7 @@ deps: .PHONY: lint lint: - `npm bin`/tslint --project ./tsconfig.json --fix + npx tslint --project ./tsconfig.json --fix .PHONY: test test: build @@ -19,10 +19,10 @@ test: build .PHONY: build build: prebuild - `npm bin`/tsc + npx tsc -.PHONY: run -run: +.PHONY: hooks +hooks: node --no-deprecation ./build/server.js hooks .PHONY: reap diff --git a/hooks/src/commands/hooks.ts b/hooks/src/commands/hooks.ts index 9a14590..01922bf 100644 --- a/hooks/src/commands/hooks.ts +++ b/hooks/src/commands/hooks.ts @@ -1,7 +1,6 @@ import * as util from "util"; import { Server } from "../server/server"; import { logger } from "../logger"; -import { param } from "../util"; exports.name = "hooks"; exports.describe = "Start and run the hook api server"; @@ -17,7 +16,7 @@ exports.handler = async (argv) => { }; async function main(argv): Promise { - process.on('SIGTERM', function onSigterm () { + process.on("SIGTERM", function onSigterm() { logger.info(`Got SIGTERM, cleaning up`); process.exit(); }); diff --git a/hooks/src/commands/reap.ts b/hooks/src/commands/reap.ts index 70ae1c4..ef68845 100644 --- a/hooks/src/commands/reap.ts +++ b/hooks/src/commands/reap.ts @@ -5,6 +5,7 @@ import * as redis from "redis"; import { promisify } from "util"; import * as rp from "request-promise"; +const registryUrl = process.env["REGISTRY_URL"] || "https://ttl.sh"; const client = redis.createClient({url: process.env["REDISCLOUD_URL"]}); const smembersAsync = promisify(client.smembers).bind(client); const sremAsync = promisify(client.srem).bind(client); @@ -25,13 +26,13 @@ exports.handler = async (argv) => { }; async function main(argv): Promise { - process.on('SIGTERM', function onSigterm () { + process.on("SIGTERM", function onSigterm() { logger.info(`Got SIGTERM, cleaning up`); process.exit(); }); let jobRunning: boolean = false; - + const job = new CronJob({ cronTime: "* * * * *", onTick: async () => { @@ -45,7 +46,7 @@ async function main(argv): Promise { try { await reapExpiredImages(); - } catch(err) { + } catch (err) { console.log("failed to reap expired images:", err); } finally { jobRunning = false; @@ -73,17 +74,17 @@ async function reapExpiredImages() { const imageAndTag = image.split(":"); const headers = { - "Accept": "application/vnd.docker.distribution.manifest.v2+json", + Accept: "application/vnd.docker.distribution.manifest.v2+json", }; // Get the manifest from the tag const getOptions = { method: "HEAD", - uri: `https://ttl.sh/v2/${imageAndTag[0]}/manifests/${imageAndTag[1]}`, + uri: `${registryUrl}/v2/${imageAndTag[0]}/manifests/${imageAndTag[1]}`, headers, resolveWithFullResponse: true, simple: false, - } + }; const getResponse = await rp(getOptions); if (getResponse.statusCode == 404) { @@ -92,7 +93,7 @@ async function reapExpiredImages() { continue; } - const deleteURI = `https://ttl.sh/v2/${imageAndTag[0]}/manifests/${getResponse.headers.etag.replace(/"/g,"")}`; + const deleteURI = `${registryUrl}/v2/${imageAndTag[0]}/manifests/${getResponse.headers.etag.replace(/"/g, "")}`; // Remove from the registry const options = { @@ -101,14 +102,14 @@ async function reapExpiredImages() { headers, resolveWithFullResponse: true, simple: false, - } + }; await rp(options); console.log(`expiring ${image}`); await sremAsync("current.images", image); await delAsync(image); - } catch(err) { + } catch (err) { console.log(`failed to evaluate image ${image}:`, err); } } diff --git a/hooks/src/controllers/HookAPI.ts b/hooks/src/controllers/HookAPI.ts index d9a0847..22839c8 100644 --- a/hooks/src/controllers/HookAPI.ts +++ b/hooks/src/controllers/HookAPI.ts @@ -57,10 +57,10 @@ export class HookAPI { console.log(`parsing tag ${tag}`); let expiresIn = durationfromTag(tag); if (expiresIn <= 0) { - expiresIn = defaultDuration + expiresIn = defaultDuration; } if (expiresIn > maxDuration) { - expiresIn = maxDuration + expiresIn = maxDuration; } await saddAsync("current.images", imageWithTag); diff --git a/hooks/src/logger.ts b/hooks/src/logger.ts index 5f2a3e3..fd381f6 100644 --- a/hooks/src/logger.ts +++ b/hooks/src/logger.ts @@ -13,7 +13,7 @@ function initLogger(): any { logger.level = process.env["LOG_LEVEL"] || "warn"; return logger; } else { - const logger = pino(logOptions) + const logger = pino(logOptions); logger.level = process.env["LOG_LEVEL"] || "warn"; return logger; } diff --git a/hooks/src/router.ts b/hooks/src/router.ts index 095e54f..c246f95 100644 --- a/hooks/src/router.ts +++ b/hooks/src/router.ts @@ -122,7 +122,7 @@ export const preRequest = (req: express.Request, reqId: string) => { export const requestId = (req: express.Request, handlerName: string) => { const id = `${handlerName}:${uuid.v4().replace("-", "").substring(0, 8)}`; - const clientID: string = req.headers["x-request-uuid"]; + const clientID: string = req.headers["x-request-uuid"] as string; if (clientID) { return `${id}.${clientID.substring(0, 8)}`; diff --git a/hooks/src/server.ts b/hooks/src/server.ts index 8be08fc..d215d84 100644 --- a/hooks/src/server.ts +++ b/hooks/src/server.ts @@ -5,4 +5,4 @@ yargs .env() .help() .demandCommand() - .argv; \ No newline at end of file + .argv; diff --git a/hooks/src/server/server.ts b/hooks/src/server/server.ts index 7f41338..3923d05 100644 --- a/hooks/src/server/server.ts +++ b/hooks/src/server/server.ts @@ -21,7 +21,7 @@ if (port == null || port == "") { debug: false, logger: { level: "warn", - } + }, }) export class Server extends ServerLoader { diff --git a/kustomize/base/kustomization.yaml b/kustomize/base/kustomization.yaml new file mode 100644 index 0000000..b83b23e --- /dev/null +++ b/kustomize/base/kustomization.yaml @@ -0,0 +1,3 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: [] diff --git a/kustomize/overlays/dev/hooks.yaml b/kustomize/overlays/dev/hooks.yaml new file mode 100644 index 0000000..d92bc41 --- /dev/null +++ b/kustomize/overlays/dev/hooks.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ttl-hooks + labels: + app: ttl-hooks +spec: + selector: + matchLabels: + app: ttl-hooks + template: + metadata: + labels: + app: ttl-hooks + spec: + restartPolicy: Always + initContainers: + - name: wait-for-registry + image: quay.io/curl/curl:latest + command: + - sh + - -c + - | + until curl -s -o /dev/null -w "%{http_code}" ttl-registry:5000/v2/; do + echo "Waiting for Docker Registry to be ready..." + sleep 2 + done + - name: wait-for-redis + image: redis:latest + command: + - sh + - -c + - | + until redis-cli -h ttl-redis -p 6379 ping | grep "PONG"; do + echo "Waiting for Redis to be ready..." + sleep 2 + done + containers: + - name: ttl-hooks + image: ttl-hooks + ports: + - containerPort: 8000 + env: + - name: REDISCLOUD_URL + value: redis://ttl-redis:6379 + - name: HOOK_TOKEN + value: dev-hook-token +--- +apiVersion: v1 +kind: Service +metadata: + name: ttl-hooks + labels: + app: ttl-hooks +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 + selector: + app: ttl-hooks \ No newline at end of file diff --git a/kustomize/overlays/dev/kustomization.yaml b/kustomize/overlays/dev/kustomization.yaml new file mode 100644 index 0000000..ff03a6a --- /dev/null +++ b/kustomize/overlays/dev/kustomization.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../base +- ./hooks.yaml +- ./reaper.yaml +- ./redis.yaml +- ./registry.yaml diff --git a/kustomize/overlays/dev/reaper.yaml b/kustomize/overlays/dev/reaper.yaml new file mode 100644 index 0000000..790726a --- /dev/null +++ b/kustomize/overlays/dev/reaper.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ttl-reaper + labels: + app: ttl-reaper +spec: + selector: + matchLabels: + app: ttl-reaper + template: + metadata: + labels: + app: ttl-reaper + spec: + restartPolicy: Always + initContainers: + - name: wait-for-registry + image: quay.io/curl/curl:latest + command: + - sh + - -c + - | + until curl -s -o /dev/null -w "%{http_code}" ttl-registry:5000/v2/; do + echo "Waiting for Docker Registry to be ready..." + sleep 2 + done + - name: wait-for-redis + image: redis:latest + command: + - sh + - -c + - | + until redis-cli -h ttl-redis -p 6379 ping | grep "PONG"; do + echo "Waiting for Redis to be ready..." + sleep 2 + done + containers: + - name: ttl-reaper + image: ttl-reaper + ports: + - containerPort: 8000 + env: + - name: REGISTRY_URL + value: http://ttl-registry:5000 + - name: REDISCLOUD_URL + value: redis://ttl-redis:6379 +--- +apiVersion: v1 +kind: Service +metadata: + name: ttl-reaper + labels: + app: ttl-reaper +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 + selector: + app: ttl-reaper \ No newline at end of file diff --git a/kustomize/overlays/dev/redis.yaml b/kustomize/overlays/dev/redis.yaml new file mode 100644 index 0000000..27a930e --- /dev/null +++ b/kustomize/overlays/dev/redis.yaml @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ttl-redis + labels: + app: ttl-redis +spec: + replicas: 1 + selector: + matchLabels: + app: ttl-redis + template: + metadata: + labels: + app: ttl-redis + spec: + containers: + - name: redis + image: redis:latest + ports: + - containerPort: 6379 + volumeMounts: + - name: redis-data + mountPath: /data + readinessProbe: + exec: + command: + - redis-cli + - ping + initialDelaySeconds: 20 + timeoutSeconds: 5 + periodSeconds: 3 + volumes: + - name: redis-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: ttl-redis + labels: + app: ttl-redis +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 6379 + targetPort: 6379 + selector: + app: ttl-redis diff --git a/kustomize/overlays/dev/registry.yaml b/kustomize/overlays/dev/registry.yaml new file mode 100644 index 0000000..5d2508b --- /dev/null +++ b/kustomize/overlays/dev/registry.yaml @@ -0,0 +1,90 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ttl-registry + labels: + app: ttl-registry +spec: + replicas: 1 + selector: + matchLabels: + app: ttl-registry + template: + metadata: + labels: + app: ttl-registry + spec: + containers: + - name: ttl-registry + image: registry:2 + ports: + - containerPort: 5000 + volumeMounts: + - name: registry-data + mountPath: /var/lib/registry + - name: registry-config + mountPath: /etc/docker/registry/config.yml + subPath: config.yml + readinessProbe: + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 1 + successThreshold: 2 + timeoutSeconds: 1 + httpGet: + path: / + port: 5000 + scheme: HTTP + volumes: + - name: registry-data + emptyDir: {} + - name: registry-config + configMap: + name: registry-config +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: registry-config + labels: + app: ttl-registry +data: + config.yml: | + version: 0.1 + log: + level: debug + storage: + delete: + enabled: true + filesystem: + rootdirectory: /var/lib/registry + http: + addr: 0.0.0.0:5000 + headers: + X-Content-Type-Options: [nosniff] + notifications: + endpoints: + - name: ttl-hooks + url: http://ttl-hooks:8000/v1/hook/registry-event + headers: + Authorization: ["Token dev-hook-token"] + timeout: 200ms + threshold: 3 + backoff: 5s +--- +apiVersion: v1 +kind: Service +metadata: + name: ttl-registry + labels: + app: ttl-registry + annotations: + dev.okteto.com/auto-ingress: "true" +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 5000 + targetPort: 5000 + selector: + app: ttl-registry \ No newline at end of file diff --git a/okteto.yml b/okteto.yml new file mode 100644 index 0000000..2a8d697 --- /dev/null +++ b/okteto.yml @@ -0,0 +1,33 @@ +build: + ttl-hooks: + context: ./hooks + dockerfile: ./hooks/Dockerfile.hooks + ttl-reaper: + context: ./hooks + dockerfile: ./hooks/Dockerfile.reap + +deploy: + - cd kustomize/overlays/dev && kustomize edit set image ttl-hooks=${OKTETO_BUILD_TTL_HOOKS_IMAGE} + - cd kustomize/overlays/dev && kustomize edit set image ttl-reaper=${OKTETO_BUILD_TTL_REAPER_IMAGE} + + - kubectl apply -k kustomize/overlays/dev + +dev: + ttl-hooks: + command: make deps build && bash || bash + workdir: /src + sync: + - ./hooks:/src + resources: + limits: + cpu: "1" + memory: 1Gi + ttl-reaper: + command: make deps build && bash || bash + workdir: /src + sync: + - ./hooks:/src + resources: + limits: + cpu: "1" + memory: 1Gi