-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(operator): rollups operator acting on CRD
- Loading branch information
Showing
21 changed files
with
7,975 additions
and
181 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Cartesi Rollups Operator | ||
|
||
## Development | ||
|
||
```shell | ||
$ kubectl create secret generic mnemonic --from-literal=MNEMONIC="test test test test test test test test test test test junk" | ||
$ helm install redis bitnami/redis --wait --set auth.enabled=false --set architecture=standalone --set image.tag=6.2-debian-11 | ||
redis-master.default.svc.cluster.local | ||
$ helm install postgres oci://registry-1.docker.io/bitnamicharts/postgresql | ||
postgres-postgresql.default.svc.cluster.local | ||
$ export POSTGRES_PASSWORD=$(kubectl get secret --namespace default postgres-postgresql -o jsonpath="{.data.postgres-password}" | base64 -d) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
@echo off | ||
|
||
node "%~dp0\dev" %* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#!/usr/bin/env node --no-warnings=ExperimentalWarning --loader ts-node/esm | ||
|
||
import oclif from "@oclif/core"; | ||
import path from "node:path"; | ||
import url from "node:url"; | ||
import { register } from "ts-node"; | ||
|
||
// In dev mode -> use ts-node and dev plugins | ||
process.env.NODE_ENV = "development"; | ||
|
||
const project = path.join( | ||
path.dirname(url.fileURLToPath(import.meta.url)), | ||
"..", | ||
"tsconfig.json" | ||
); | ||
register({ project }); | ||
|
||
// In dev mode, always show stack traces | ||
oclif.settings.debug = true; | ||
|
||
// Start the CLI | ||
oclif | ||
.run(process.argv.slice(2), import.meta.url) | ||
.then(oclif.flush) | ||
.catch(oclif.Errors.handle); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
@echo off | ||
|
||
node "%~dp0\run" %* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#!/usr/bin/env node | ||
|
||
import oclif from "@oclif/core"; | ||
|
||
oclif | ||
.run(process.argv.slice(2), import.meta.url) | ||
.then(oclif.flush) | ||
.catch(oclif.Errors.handle); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
apiVersion: v1 | ||
kind: ConfigMap | ||
metadata: | ||
name: cartesi-rollups-config | ||
data: | ||
redis: redis://redis-master.default.svc.cluster.local:6379 | ||
rpc_url: http://host.docker.internal:8545 | ||
rpc_ws_url: ws://host.docker.internal:8545 | ||
chain_id: "31337" | ||
epoch_duration: "3600" | ||
contracts.json: | | ||
{ | ||
"contracts": { | ||
"Authority": { "address": "0x5050F233F2312B1636eb7CF6c7876D9cC6ac4785" }, | ||
"History": { "address": "0x4FF8BD9122b7D91d56Dd5c88FE6891Fb3c0b5281" }, | ||
"InputBox": { "address": "0x59b22D57D4f067708AB0c00552767405926dc768" } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
apiVersion: v1 | ||
kind: Secret | ||
metadata: | ||
name: cartesi-rollups-database | ||
type: Opaque | ||
data: | ||
database: cG9zdGdyZXM= # base64('postgres') | ||
hostname: cG9zdGdyZXMtcG9zdGdyZXNxbC5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2Fs # base64('postgresql-postgresql.svc.cluster.local') | ||
password: R0J3SlBmT1p5dA== # base64('GBwJPfOZyt') | ||
port: NTQzMg== # base64('5432') | ||
username: cG9zdGdyZXM= # base64('postgres') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
apiVersion: v1 | ||
kind: Secret | ||
metadata: | ||
name: cartesi-rollups-mnemonic | ||
type: Opaque | ||
data: | ||
MNEMONIC: dGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IHRlc3QgdGVzdCB0ZXN0IGp1bms= # base64('test test test test test test test test test test test junk') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
apiVersion: traefik.io/v1alpha1 | ||
kind: Middleware | ||
metadata: | ||
name: cartesi-rollups-strip-address | ||
spec: | ||
stripPrefixRegex: | ||
regex: | ||
- "/0x[a-fA-F0-9]{40}/" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
{ | ||
"name": "@cartesi/rollups-operator", | ||
"version": "0.1.0", | ||
"description": "Cartesi Rollups Operator", | ||
"author": "Danilo Tuler <[email protected]>", | ||
"bin": { | ||
"sunodo": "./bin/run.js" | ||
}, | ||
"type": "module", | ||
"homepage": "https://github.com/cartesi/helm-charts", | ||
"license": "Apache-2.0", | ||
"exports": "./dist/index.js", | ||
"repository": "cartesi/helm-charts", | ||
"scripts": { | ||
"build": "run-s clean compile copy-files", | ||
"clean": "rimraf dist", | ||
"compile": "tsc -b", | ||
"copy-files": "copyfiles -u 1 src/**/*.yml dist", | ||
"dev": "bin/dev.js --help", | ||
"lint": "eslint . --ext .ts --config .eslintrc", | ||
"postpack": "rimraf oclif.manifest.json", | ||
"posttest": "yarn lint", | ||
"prepack": "yarn build && oclif manifest && oclif readme", | ||
"test": "vitest", | ||
"version": "run-s prepack" | ||
}, | ||
"files": [ | ||
"/bin", | ||
"/dist", | ||
"/oclif.manifest.json" | ||
], | ||
"dependencies": { | ||
"@inquirer/prompts": "^3.0", | ||
"@kubernetes/client-node": "^0.18", | ||
"@oclif/core": "^2.11", | ||
"@oclif/plugin-help": "^5.2", | ||
"@oclif/plugin-plugins": "^3.1", | ||
"@oclif/plugin-update": "^3.1", | ||
"async": "^3.2", | ||
"gaxios": "^6.1" | ||
}, | ||
"devDependencies": { | ||
"@oclif/test": "^2.3", | ||
"@types/async": "^3.2", | ||
"@types/inquirer": "^9", | ||
"@types/node": "^20", | ||
"@types/node-fetch": "^2.6", | ||
"copyfiles": "^2", | ||
"eslint": "^8", | ||
"eslint-config-custom": "*", | ||
"eslint-config-oclif": "^4", | ||
"eslint-config-oclif-typescript": "^1", | ||
"npm-run-all": "^4", | ||
"oclif": "^3.11", | ||
"rimraf": "^5", | ||
"ts-node": "^10", | ||
"tsconfig": "*", | ||
"tslib": "^2", | ||
"typescript": "^5", | ||
"vitest": "^0.34" | ||
}, | ||
"oclif": { | ||
"bin": "cartesi-rollups-operator", | ||
"default": "start", | ||
"dirname": "cartesi", | ||
"commands": "./dist/commands", | ||
"plugins": [ | ||
"@oclif/plugin-help" | ||
], | ||
"topicSeparator": " ", | ||
"macos": { | ||
"identifier": "io.cartesi.rollups.operator" | ||
} | ||
}, | ||
"engines": { | ||
"node": ">=18.0.0" | ||
}, | ||
"bugs": "https://github.com/cartesi/helm-charts/issues", | ||
"keywords": [ | ||
"oclif" | ||
], | ||
"types": "dist/index.d.ts" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
import path from "path"; | ||
import k8s, { KubeConfig, KubernetesObject } from "@kubernetes/client-node"; | ||
|
||
import Operator, { ResourceEvent, ResourceEventType } from "./operator.js"; | ||
import Synthetizer, { Resources } from "./synth.js"; | ||
|
||
export interface DAppCustomResource extends KubernetesObject { | ||
spec: DAppCustomResourceSpec; | ||
status: DAppCustomResourceStatus; | ||
} | ||
|
||
export interface DAppCustomResourceSpec { | ||
address: string; | ||
blockHash: string; | ||
blockNumber: string; | ||
transactionHash: string; | ||
cid: string; | ||
} | ||
|
||
export interface DAppCustomResourceStatus { | ||
observedGeneration?: number; | ||
} | ||
|
||
export default class DAppOperator extends Operator { | ||
protected synthetizer: Synthetizer; | ||
protected namespace: string; | ||
|
||
constructor(kubeConfig: KubeConfig, namespace: string) { | ||
super(kubeConfig, console); | ||
this.namespace = namespace; | ||
// instantiate resources synthetizer | ||
this.synthetizer = new Synthetizer(); | ||
} | ||
|
||
protected async init() { | ||
// register CRD | ||
const crdFile = path.join( | ||
path.dirname(new URL(import.meta.url).pathname), | ||
"dapp.yaml", | ||
); | ||
const { group, versions, plural } = | ||
await this.registerCustomResourceDefinition(crdFile); | ||
|
||
// watch CRD resources | ||
await this.watchResource( | ||
group, | ||
versions[0].name, | ||
plural, | ||
async (e) => { | ||
console.log(e); | ||
if ( | ||
e.type === ResourceEventType.Added || | ||
e.type === ResourceEventType.Modified | ||
) { | ||
if ( | ||
!(await this.handleResourceFinalizer( | ||
e, | ||
group, | ||
(event) => this.delete(event), | ||
)) | ||
) { | ||
await this.create(e); | ||
} | ||
} else if (e.type === ResourceEventType.Deleted) { | ||
await this.delete(e); | ||
} | ||
}, | ||
this.namespace, | ||
); | ||
} | ||
|
||
protected async print(resources: Resources) { | ||
const configMaps = resources.configMaps | ||
.map((r) => JSON.stringify(r, undefined, 4)) | ||
.join("\n---\n"); | ||
const deployments = resources.deployments | ||
.map((r) => JSON.stringify(r, undefined, 4)) | ||
.join("\n---\n"); | ||
const services = resources.services | ||
.map((r) => JSON.stringify(r, undefined, 4)) | ||
.join("\n---\n"); | ||
|
||
console.log(configMaps); | ||
console.log("---"); | ||
console.log(deployments); | ||
console.log("---"); | ||
console.log(services); | ||
} | ||
|
||
protected async apply(resources: Resources) { | ||
const core = this.k8sApi; | ||
const apps = this.kubeConfig.makeApiClient(k8s.AppsV1Api); | ||
const network = this.kubeConfig.makeApiClient(k8s.NetworkingV1Api); | ||
const { namespace } = this; | ||
|
||
const ignore409 = (err: any) => { | ||
if ((err as any).response?.statusCode !== 409) { | ||
throw err; | ||
} | ||
}; | ||
|
||
// create all configMaps | ||
resources.configMaps.forEach((r) => { | ||
console.log(`creating ${r.kind} ${r.metadata!.name}`); | ||
core.createNamespacedConfigMap(namespace, r) | ||
.then(({ body }) => { | ||
console.log(`created ${body.kind} ${body.metadata!.name}`); | ||
}) | ||
.catch(ignore409); | ||
}); | ||
|
||
// create all deployments | ||
resources.deployments.forEach((r) => { | ||
console.log(`creating ${r.kind} ${r.metadata!.name}`); | ||
apps.createNamespacedDeployment(namespace, r) | ||
.then(({ body }) => { | ||
console.log(`created ${body.kind} ${body.metadata!.name}`); | ||
}) | ||
.catch(ignore409); | ||
}); | ||
|
||
// create all services | ||
resources.services.forEach((r) => { | ||
console.log(`creating ${r.kind} ${r.metadata!.name}`); | ||
core.createNamespacedService(namespace, r) | ||
.then(({ body }) => { | ||
console.log(`created ${body.kind} ${body.metadata!.name}`); | ||
}) | ||
.catch(ignore409); | ||
}); | ||
|
||
// create all ingresses | ||
resources.ingresses.forEach((r) => { | ||
console.log(`creating ${r.kind} ${r.metadata!.name}`); | ||
network | ||
.createNamespacedIngress(namespace, r) | ||
.then(({ body }) => { | ||
console.log(`created ${body.kind} ${body.metadata!.name}`); | ||
}) | ||
.catch(ignore409); | ||
}); | ||
} | ||
|
||
protected async create(event: ResourceEvent) { | ||
const resources = this.synthetizer.synth( | ||
event.object as DAppCustomResource, | ||
); | ||
await this.print(resources); | ||
await this.apply(resources); | ||
} | ||
|
||
protected async delete(event: ResourceEvent) { | ||
const namespace = event.meta.namespace ?? "default"; | ||
const core = this.k8sApi; | ||
const apps = this.kubeConfig.makeApiClient(k8s.AppsV1Api); | ||
const network = this.kubeConfig.makeApiClient(k8s.NetworkingV1Api); | ||
|
||
const resources = this.synthetizer.synth( | ||
event.object as DAppCustomResource, | ||
); | ||
|
||
// delete ingresses | ||
await Promise.all( | ||
resources.ingresses.map((r) => | ||
network.deleteNamespacedIngress(r.metadata!.name!, namespace), | ||
), | ||
); | ||
|
||
// delete services | ||
await Promise.all( | ||
resources.services.map((r) => | ||
core.deleteNamespacedService(r.metadata!.name!, namespace), | ||
), | ||
); | ||
|
||
// delete deployments | ||
await Promise.all( | ||
resources.deployments.map((r) => | ||
apps.deleteNamespacedDeployment(r.metadata!.name!, namespace), | ||
), | ||
); | ||
|
||
// delete configMaps | ||
await Promise.all( | ||
resources.configMaps.map((r) => | ||
core.deleteNamespacedConfigMap(r.metadata!.name!, namespace), | ||
), | ||
); | ||
} | ||
} |
Oops, something went wrong.