From 71b92f3775e35fbc4a66eaf8aebfed6c220c2614 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Wed, 18 Dec 2024 12:14:59 +0000 Subject: [PATCH 01/13] (feat) Cue network defaults by hostname (feat) Button to remove browser settings --- public/config.json | 8 ++++---- src/NetworkMenuWithConfig.tsx | 36 +++++++++++++++++++++++++++++++++-- src/useConfig.ts | 31 +++++++++++++++++++++++++++--- 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/public/config.json b/public/config.json index 1ff69a02..f6d1763c 100644 --- a/public/config.json +++ b/public/config.json @@ -2,9 +2,9 @@ "erigonURL": "http://localhost:8545", "assetsURLPrefix": "http://localhost:5175", "connections": [ - { "menuName": "zq1-mainnet", "url": "https://mainnet-v934-fireblocks.mainnet-20240103-ase1.zq1.network" }, - { "menuName": "zq1-testnet", "url": "https://testnet-v932-fireblocks.testnet-ase1.zq1.dev" }, - { "menuName": "zq2-prototestnet", "url": "https://api.zq2-prototestnet.zilliqa.com" }, - { "menuName": "zq2-protomainnet", "url": "https://api.zq2-protomainnet.zilliqa.com" } + { "menuName": "zq1-mainnet", "url": "https://mainnet-v934-fireblocks.mainnet-20240103-ase1.zq1.network", "hostnames": ["otterscan.zilliqa.com"] }, + { "menuName": "zq1-testnet", "url": "https://testnet-v932-fireblocks.testnet-ase1.zq1.dev", "hostnames": ["otterscan.testnet.zilliqa.com"] }, + { "menuName": "zq2-prototestnet", "url": "https://api.zq2-prototestnet.zilliqa.com", "hostnames": ["explorer.zq2-prototestnet.zilliqa.com" ] }, + { "menuName": "zq2-protomainnet", "url": "https://api.zq2-protomainnet.zilliqa.com", "hostnames": ["explorer.zq2-protomainnet.zilliqa.com"] } ] } diff --git a/src/NetworkMenuWithConfig.tsx b/src/NetworkMenuWithConfig.tsx index 64de2113..f7a69feb 100644 --- a/src/NetworkMenuWithConfig.tsx +++ b/src/NetworkMenuWithConfig.tsx @@ -6,6 +6,7 @@ import { chooseConnection, deleteParametersFromLocation, newConnection, + forgetLocalStorage } from "./useConfig"; type NetworkMenuWithConfigProps = { @@ -45,6 +46,13 @@ const NetworkMenuWithConfig: FC = ({ config }) => { //window.location.reload(); } } + + async function forgetBrowserSettings() { + console.log("Forget browser settings"); + await forgetLocalStorage(config); + window.location.reload(); + } + var legend = connections.find((elem) => elem?.url == config?.erigonURL)?.menuName ?? "Networks"; @@ -68,7 +76,8 @@ const NetworkMenuWithConfig: FC = ({ config }) => { {connectionItems} - setGoToOpen(true)} /> + setGoToOpen(true)} /> + forgetBrowserSettings()} /> @@ -151,7 +160,7 @@ const NetworkMenuWithConfig: FC = ({ config }) => { }} > Connect - + @@ -161,6 +170,29 @@ const NetworkMenuWithConfig: FC = ({ config }) => { ); }; +type RemoveConfigItemProps = { + onClick: (event?: any) => void; +}; + + +export const RemoveConfigItem: React.FS = ({ onClick}) => { + return ( + + {({ focus }) => ( +
+ +
+ )} +
+ ); +}; + type NetworkSetItemProps = { onClick: (event?: any) => void; }; diff --git a/src/useConfig.ts b/src/useConfig.ts index 4ce5f7fd..1e65b801 100644 --- a/src/useConfig.ts +++ b/src/useConfig.ts @@ -9,6 +9,8 @@ export type ChainConnection = { url?: string; /** Faucet URL */ faucets?: string[]; + /** Hostname prefixes that default to this connection */ + hostnames?: string[]; }; /** @@ -287,6 +289,11 @@ export const deleteParametersFromLocation = async (): Promise => { return true; }; +export const forgetLocalStorage = async (): Promise => { + window["localStorage"].removeItem("otterscanConfig"); + return true; +} + /** * Loads the global configuration according to the following criteria: * @@ -359,20 +366,38 @@ export const loadOtterscanConfig = async (): Promise => { console.log(`Failed to get localStorage config - ${err}`); } + // Default by hostname + try { + var host = window.location.host; + var connections = + storageConfiguration["connections"] ?? config.connections; + for (var c of connections) { + const hosts = c.hostnames; + if (hosts !== undefined) { + for (var h of hosts) { + if (host.startsWith(h)) { + if (!("erigonURL" in storageConfiguration)) { + storageConfiguration["erigonURL"] = c.url; + } + } + } + } + } + } catch (err) { + throw new Error(`Error setting URL from hostname: ${err}`); + } + // Set up URL parameters. try { var params = new URLSearchParams(window.location.search); // Historical - this is the parameter devex used to use. if (params.has("network")) { - console.log("has network"); const url = params.get("network"); storageConfiguration["erigonURL"] = url; var connections = storageConfiguration["connections"] ?? config.connections; - console.log("conn " + connections); var found = false; for (var c of connections) { - console.log("c = " + c + " url " + url); if (c.url === url) { if (params.has("name")) { let name = params.get("name"); From 91f19c155c6c7b710fd184f164f012d798572242 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Wed, 18 Dec 2024 12:21:14 +0000 Subject: [PATCH 02/13] (fix) Build fixes --- src/NetworkMenuWithConfig.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetworkMenuWithConfig.tsx b/src/NetworkMenuWithConfig.tsx index f7a69feb..0aee33c8 100644 --- a/src/NetworkMenuWithConfig.tsx +++ b/src/NetworkMenuWithConfig.tsx @@ -49,7 +49,7 @@ const NetworkMenuWithConfig: FC = ({ config }) => { async function forgetBrowserSettings() { console.log("Forget browser settings"); - await forgetLocalStorage(config); + await forgetLocalStorage(); window.location.reload(); } @@ -175,7 +175,7 @@ type RemoveConfigItemProps = { }; -export const RemoveConfigItem: React.FS = ({ onClick}) => { +export const RemoveConfigItem: React.FC = ({ onClick}) => { return ( {({ focus }) => ( From d140049c071417eb90bb5df4e6cf27dc7e4f6e6e Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Tue, 24 Dec 2024 16:58:37 +0000 Subject: [PATCH 03/13] (feat) Current state of the world - a new dockerfile and a start at container builds in CD --- .github/workflows/cicd-stg.yaml | 137 ++++++++++++++++++++++++ .github/workflows/docker-publish.yaml | 19 ++-- .github/workflows/pages-deployment.yaml | 38 ------- Dockerfile.zilliqa | 26 +++++ Makefile | 23 ++++ package.json | 1 + src/useConfig.ts | 18 ++-- 7 files changed, 205 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/cicd-stg.yaml delete mode 100644 .github/workflows/pages-deployment.yaml create mode 100644 Dockerfile.zilliqa create mode 100644 Makefile diff --git a/.github/workflows/cicd-stg.yaml b/.github/workflows/cicd-stg.yaml new file mode 100644 index 00000000..ec63fef9 --- /dev/null +++ b/.github/workflows/cicd-stg.yaml @@ -0,0 +1,137 @@ +name: "CICD staging" + +on: + # Test run before merging + pull_request: + branches: + - main + # On merged + push: + branches: + - main + - users/richard/76-docker-build-and-host-dependency + +jobs: + build-makefile: + permissions: + id-token: write + contents: write + runs-on: ubuntu-22.04 + # To test deployments, remove the github.ref_name clause: see devops/docs/z2-testing-apps.md - rrw 2024-04-12 + # && github.ref_name == 'main' + if: github.actor != 'dependabot[bot]' + name: "Build image with Makefile" + strategy: + fail-fast: false + matrix: + application: [otterscan] + include: + - application: otterscan + image_name: otterscan + path: . + tag_length: 8 + tag_latest: false + env: + DOCKER_DOMAIN: asia-docker.pkg.dev + REGISTRY: asia-docker.pkg.dev/prj-d-devops-services-4dgwlsse/zilliqa-public + steps: + - name: Checkout code + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + with: + submodules: recursive + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + fetch-depth: 0 + + - name: "Authenticate to Google Cloud - staging" + id: google-auth + uses: "google-github-actions/auth@71fee32a0bb7e97b4d33d548e7d957010649d8fa" + with: + token_format: "access_token" + workload_identity_provider: "${{ secrets.GCP_PRD_GITHUB_WIF }}" + service_account: "${{ secrets.GCP_STG_GITHUB_SA_DOCKER_REGISTRY }}" + create_credentials_file: true + + - name: Login to the registry - staging + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 + with: + registry: ${{ env.DOCKER_DOMAIN }} + username: "oauth2accesstoken" + password: "${{ steps.google-auth.outputs.access_token }}" + + - name: Get tag version - staging + id: set-tag + uses: Zilliqa/gh-actions-workflows/actions/generate-tag@v1 + with: + tag: ${{ env.REGISTRY }}/${{ matrix.image_name }} + length: ${{ matrix.tag_length }} + + - name: "Build and push ${{ matrix.application }} - staging" + env: + ENVIRONMENT: stg + IMAGE_TAG: ${{ steps.set-tag.outputs.tags }} + run: | + cd ${{ matrix.path }} + make image/build-and-push + + - name: "Build and push ${{ matrix.application }} tag latest - staging" + if: ${{ matrix.tag_latest == true }} + env: + ENVIRONMENT: stg + IMAGE_TAG: "${{ env.REGISTRY }}/${{ matrix.image_name }}:latest" + run: | + cd ${{ matrix.path }} + make image/build-and-push + + deploy-to-staging: + needs: [build-makefile] + permissions: + id-token: write + contents: write + runs-on: ubuntu-22.04 + if: github.actor != 'dependabot[bot]' && github.ref_name == 'main' + strategy: + fail-fast: false + matrix: + application: + - developer-portal + env: + APP_NAME: ${{ matrix.application }} + Z_ENV: infra/live/gcp/non-production/prj-d-staging/z_ase1.yaml + Z_SERVICE_ACCOUNT: ${{ secrets.GCP_STG_GITHUB_SA_K8S_DEPLOY }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN_STG }} + GITHUB_PAT: ${{ secrets.GH_PAT }} + Z_IMAGE: asia-docker.pkg.dev/prj-d-devops-services-4dgwlsse/zilliqa-private/z:latest + REGISTRY: asia-docker.pkg.dev + steps: + - name: Checkout + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + with: + repository: Zilliqa/devops + token: ${{ env.GITHUB_PAT }} + ref: main + sparse-checkout: | + ${{ env.Z_ENV }} + + - name: Authenticate to Google Cloud + id: google-auth + uses: google-github-actions/auth@71fee32a0bb7e97b4d33d548e7d957010649d8fa + with: + token_format: "access_token" + workload_identity_provider: "${{ secrets.GCP_PRD_GITHUB_WIF }}" + service_account: ${{ env.Z_SERVICE_ACCOUNT }} + create_credentials_file: true + + - name: Deploy application + run: | + gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://${{ env.REGISTRY }} + docker run --rm \ + -e ZQ_USER='${{ env.Z_SERVICE_ACCOUNT }}' \ + -e Z_ENV='/devops/${{ env.Z_ENV }}' \ + -e OP_SERVICE_ACCOUNT_TOKEN='${{ env.OP_SERVICE_ACCOUNT_TOKEN }}' \ + -e GITHUB_PAT='${{ env.GITHUB_PAT }}' \ + -e CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE='/google/application_default_credentials.json' \ + -v `pwd`:/devops \ + -v ${{ steps.google-auth.outputs.credentials_file_path }}:/google/application_default_credentials.json \ + --name z_container ${{ env.Z_IMAGE }} \ + bash -c "gcloud config set account ${{ env.Z_SERVICE_ACCOUNT }} && z /app /devops app sync --cache-dir .cache ${{ env.APP_NAME }}" diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index c34576bb..cab175fc 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -83,14 +83,11 @@ jobs: type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} type=semver,pattern={{raw}} - - name: Build and push by digest - id: build - uses: docker/build-push-action@v5 - with: - context: . - push: true - platforms: ${{ matrix.platform }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: "Build and push ${{ matrix.application }}" + env: + ENVIRONMENT: stg + IMAGE_TAG: ${{ steps.set-tag.outputs.tags }} + run: | + cd ${{ matrix.path }} + make image/build-and-push + diff --git a/.github/workflows/pages-deployment.yaml b/.github/workflows/pages-deployment.yaml deleted file mode 100644 index 441d995a..00000000 --- a/.github/workflows/pages-deployment.yaml +++ /dev/null @@ -1,38 +0,0 @@ -on: - workflow_call: - secrets: - apiToken: - required: true - accountId: - required: true - projectName: - required: true - viteConfigJson: - required: true - -jobs: - deploy-cloudflare: - runs-on: ubuntu-latest - permissions: - contents: read - deployments: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install node - uses: actions/setup-node@v4 - with: - node-version-file: ".nvmrc" - cache: "npm" - - name: Install packages - run: npm ci - - name: Build Otterscan - run: npm run build - env: - VITE_CONFIG_JSON: ${{ secrets.viteConfigJson }} - - name: Publish - uses: cloudflare/wrangler-action@v3 - with: - apiToken: ${{ secrets.apiToken }} - accountId: ${{ secrets.accountId }} - command: pages deploy dist --project-name=${{ secrets.projectName }} --commit-dirty=true diff --git a/Dockerfile.zilliqa b/Dockerfile.zilliqa new file mode 100644 index 00000000..c36e3bbb --- /dev/null +++ b/Dockerfile.zilliqa @@ -0,0 +1,26 @@ +FROM node:22.9.0-alpine3.19 AS builder +WORKDIR /otterscan-build +COPY --link ["package.json", "package-lock.json", "/otterscan-build/"] +RUN npm ci --fetch-timeout 6000000 --verbose +COPY --link ["run-nginx.sh", "tsconfig.json", "tsconfig.node.json", "postcss.config.js", "tailwind.config.js", "vite.config.ts", "index.html", "/otterscan-build/"] +COPY --link ["public", "/otterscan-build/public/"] +COPY --link ["src", "/otterscan-build/src/"] +COPY --link ["autogen", "/otterscan-build/autogen/"] +RUN npm run just-build + +FROM nginx:1.27.3-alpine3.20 +RUN set -ex \ + && apk update \ + && apk add nginx-mod-http-brotli jq +WORKDIR /usr/share/nginx/html/ +COPY --link --from=otterscan/otterscan-assets:v1.1.1 /usr/share/nginx/html/chains chains/ +COPY --link --from=otterscan/otterscan-assets:v1.1.1 /usr/share/nginx/html/topic0 topic0/ +COPY --link --from=otterscan/otterscan-assets:v1.1.1 /usr/share/nginx/html/assets assets/ +COPY --link --from=otterscan/otterscan-assets:v1.1.1 /usr/share/nginx/html/signatures signatures/ +COPY --link nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf +COPY --link nginx/nginx.conf /etc/nginx/nginx.conf +COPY --link --from=builder /otterscan-build/dist /usr/share/nginx/html/ +COPY --link --from=builder /otterscan-build/run-nginx.sh / +WORKDIR / + +CMD ["/run-nginx.sh"] diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..3007f55e --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +.PHONY: all +all: image/build-and-push + +.ONESHELL: +SHELL := /bin/bash +.SHELLFLAGS = -ec + +ENVIRONMENT ?= dev +VALID_ENVIRONMENTS := dev stg prd +# Check if the ENVIRONMENT variable is in the list of valid environments +ifeq ($(filter $(ENVIRONMENT),$(VALID_ENVIRONMENTS)),) +$(error Invalid value for ENVIRONMENT. Valid values are dev, stg, or prd.) +endif + +HERE=$(shell pwd) + +IMAGE_TAG ?= otterscan:latest + + +.PHONY: image/build-and-push +image/build-and-push: + ./scripts/gen-version.sh autogen/version.ts + docker buildx build -f Dockerfile.zilliqa . -t $(IMAGE_TAG) diff --git a/package.json b/package.json index 0f90d375..e27c953c 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "build": "./scripts/gen-version.sh autogen/version.ts && tsc && vite build", "preview": "./scripts/gen-version.sh autogen/version.ts && vite preview", "test": "jest", + "just-build": "tsc && vite build", "source-map-explorer": "source-map-explorer build/static/js/*.js", "assets-start": "docker run --rm -p 5175:80 --name otterscan-assets -d otterscan/otterscan-assets:v1.0.1", "assets-stop": "docker stop otterscan-assets", diff --git a/src/useConfig.ts b/src/useConfig.ts index 1e65b801..e138e970 100644 --- a/src/useConfig.ts +++ b/src/useConfig.ts @@ -218,7 +218,7 @@ export type OtterscanConfig = { /** Version number */ - version: string; + version?: string; /** Chain connections */ @@ -371,13 +371,15 @@ export const loadOtterscanConfig = async (): Promise => { var host = window.location.host; var connections = storageConfiguration["connections"] ?? config.connections; - for (var c of connections) { - const hosts = c.hostnames; - if (hosts !== undefined) { - for (var h of hosts) { - if (host.startsWith(h)) { - if (!("erigonURL" in storageConfiguration)) { - storageConfiguration["erigonURL"] = c.url; + if (connections !== undefined) { + for (var c of connections) { + const hosts = c.hostnames; + if (hosts !== undefined) { + for (var h of hosts) { + if (host.startsWith(h)) { + if (!("erigonURL" in storageConfiguration)) { + storageConfiguration["erigonURL"] = c.url; + } } } } From 8dd667440e16b78111a77fe1167c8e2cf30a83d8 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 30 Dec 2024 09:19:28 +0000 Subject: [PATCH 04/13] (fix) Prettier --- src/NetworkMenuWithConfig.tsx | 19 ++++++++++--------- src/useConfig.ts | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/NetworkMenuWithConfig.tsx b/src/NetworkMenuWithConfig.tsx index 0aee33c8..40e07e98 100644 --- a/src/NetworkMenuWithConfig.tsx +++ b/src/NetworkMenuWithConfig.tsx @@ -5,8 +5,8 @@ import { OtterscanConfig, chooseConnection, deleteParametersFromLocation, + forgetLocalStorage, newConnection, - forgetLocalStorage } from "./useConfig"; type NetworkMenuWithConfigProps = { @@ -52,7 +52,7 @@ const NetworkMenuWithConfig: FC = ({ config }) => { await forgetLocalStorage(); window.location.reload(); } - + var legend = connections.find((elem) => elem?.url == config?.erigonURL)?.menuName ?? "Networks"; @@ -76,8 +76,8 @@ const NetworkMenuWithConfig: FC = ({ config }) => { {connectionItems} - setGoToOpen(true)} /> - forgetBrowserSettings()} /> + setGoToOpen(true)} /> + forgetBrowserSettings()} /> @@ -160,7 +160,7 @@ const NetworkMenuWithConfig: FC = ({ config }) => { }} > Connect - + @@ -174,8 +174,9 @@ type RemoveConfigItemProps = { onClick: (event?: any) => void; }; - -export const RemoveConfigItem: React.FC = ({ onClick}) => { +export const RemoveConfigItem: React.FC = ({ + onClick, +}) => { return ( {({ focus }) => ( @@ -185,11 +186,11 @@ export const RemoveConfigItem: React.FC = ({ onClick}) => } transition-colors transition-transform duration-75`} > )} - + ); }; diff --git a/src/useConfig.ts b/src/useConfig.ts index e138e970..564badb7 100644 --- a/src/useConfig.ts +++ b/src/useConfig.ts @@ -292,7 +292,7 @@ export const deleteParametersFromLocation = async (): Promise => { export const forgetLocalStorage = async (): Promise => { window["localStorage"].removeItem("otterscanConfig"); return true; -} +}; /** * Loads the global configuration according to the following criteria: From 9b570763f41e3edfb87c17454b8238f379984e4d Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 30 Dec 2024 09:46:11 +0000 Subject: [PATCH 05/13] (feat) cd files (feat) If you start up with no erigonURL defined, default to the first configured connection --- cd/base/backend-config.yaml | 11 +++++++ cd/base/deployment.yaml | 43 ++++++++++++++++++++++++++ cd/base/frontend-config.yaml | 11 +++++++ cd/base/ingress.yaml | 21 +++++++++++++ cd/base/kustomization.yaml | 9 ++++++ cd/base/namespace.yaml | 4 +++ cd/base/svc.yaml | 18 +++++++++++ cd/overlays/staging/certificate.yaml | 7 +++++ cd/overlays/staging/config.yaml | 8 +++++ cd/overlays/staging/kustomization.yaml | 35 +++++++++++++++++++++ public/config.json | 1 - src/useConfig.ts | 19 ++++++++++-- 12 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 cd/base/backend-config.yaml create mode 100644 cd/base/deployment.yaml create mode 100644 cd/base/frontend-config.yaml create mode 100644 cd/base/ingress.yaml create mode 100644 cd/base/kustomization.yaml create mode 100644 cd/base/namespace.yaml create mode 100644 cd/base/svc.yaml create mode 100644 cd/overlays/staging/certificate.yaml create mode 100644 cd/overlays/staging/config.yaml create mode 100644 cd/overlays/staging/kustomization.yaml diff --git a/cd/base/backend-config.yaml b/cd/base/backend-config.yaml new file mode 100644 index 00000000..e15cd7b6 --- /dev/null +++ b/cd/base/backend-config.yaml @@ -0,0 +1,11 @@ +apiVersion: cloud.google.com/v1 +kind: BackendConfig +metadata: + name: otterscan + namespace: otterscan + labels: + app.kubernetes.io/name: "otterscan" +spec: + timeoutSec: 120 + healthCheck: + requestPath: /health diff --git a/cd/base/deployment.yaml b/cd/base/deployment.yaml new file mode 100644 index 00000000..bb09603c --- /dev/null +++ b/cd/base/deployment.yaml @@ -0,0 +1,43 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: otterscan + namespace: otterscan + labels: + app.kubernetes.io/name: "otterscan" +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: "otterscan" + strategy: + type: RollingUpdate + template: + metadata: + labels: + app.kubernetes.io/name: "otterscan" + spec: + containers: + - image: otterscan + name: otterscan + env: + - name: DISABLE_CONFIG_OVERWRITE + value: yes + volumeMounts: + - name: config-vol + mountPath: /usr/share/nginx/html/config.json + ports: + - containerPort: 80 + resources: + limits: + memory: 200Mi + requests: + memory: 100Mi + readinessProbe: + httpGet: + path: /health + port: 80 + volumes: + - name: config-vol + configMap: + name: config diff --git a/cd/base/frontend-config.yaml b/cd/base/frontend-config.yaml new file mode 100644 index 00000000..1c05f4d6 --- /dev/null +++ b/cd/base/frontend-config.yaml @@ -0,0 +1,11 @@ +apiVersion: networking.gke.io/v1beta1 +kind: FrontendConfig +metadata: + name: developer-portal + namespace: developer-portal + labels: + app.kubernetes.io/name: "otterscan" +spec: + redirectToHttps: + enabled: true + responseCodeName: RESPONSE_CODE diff --git a/cd/base/ingress.yaml b/cd/base/ingress.yaml new file mode 100644 index 00000000..8b54950f --- /dev/null +++ b/cd/base/ingress.yaml @@ -0,0 +1,21 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: otterscan + namespace: otterscan + labels: + app.kubernetes.io/name: "otterscan" + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 +spec: + rules: + - host: localhost + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: otterscan + port: + number: 80 diff --git a/cd/base/kustomization.yaml b/cd/base/kustomization.yaml new file mode 100644 index 00000000..adbf3a07 --- /dev/null +++ b/cd/base/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - namespace.yaml + - deployment.yaml + - svc.yaml + - ingress.yaml + - backend-config.yaml + - frontend-config.yaml diff --git a/cd/base/namespace.yaml b/cd/base/namespace.yaml new file mode 100644 index 00000000..3c128e88 --- /dev/null +++ b/cd/base/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: developer-portal diff --git a/cd/base/svc.yaml b/cd/base/svc.yaml new file mode 100644 index 00000000..b0f561fc --- /dev/null +++ b/cd/base/svc.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: otterscan + namespace: otterscan + labels: + app.kubernetes.io/name: "otterscan" + annotations: + beta.cloud.google.com/backend-config: '{"default": "otterscan"}' +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + protocol: TCP + name: http + selector: + app.kubernetes.io/name: "otterscan" diff --git a/cd/overlays/staging/certificate.yaml b/cd/overlays/staging/certificate.yaml new file mode 100644 index 00000000..15d9e962 --- /dev/null +++ b/cd/overlays/staging/certificate.yaml @@ -0,0 +1,7 @@ +apiVersion: networking.gke.io/v1 +kind: ManagedCertificate +metadata: + name: otterscan +spec: + domains: + - otterscan.zilstg.dev diff --git a/cd/overlays/staging/config.yaml b/cd/overlays/staging/config.yaml new file mode 100644 index 00000000..087c121f --- /dev/null +++ b/cd/overlays/staging/config.yaml @@ -0,0 +1,8 @@ +{ + "connections": [ + { "menuName": "zq1-mainnet", "url": "https://mainnet-v934-fireblocks.mainnet-20240103-ase1.zq1.network", "hostnames": ["otterscan.zilliqa.com"] }, + { "menuName": "zq1-testnet", "url": "https://testnet-v932-fireblocks.testnet-ase1.zq1.dev", "hostnames": ["otterscan.testnet.zilliqa.com"] }, + { "menuName": "zq2-prototestnet", "url": "https://api.zq2-prototestnet.zilliqa.com", "hostnames": ["explorer.zq2-prototestnet.zilliqa.com" ] }, + { "menuName": "zq2-protomainnet", "url": "https://api.zq2-protomainnet.zilliqa.com", "hostnames": ["explorer.zq2-protomainnet.zilliqa.com"] } + ] +} diff --git a/cd/overlays/staging/kustomization.yaml b/cd/overlays/staging/kustomization.yaml new file mode 100644 index 00000000..c3dcce6e --- /dev/null +++ b/cd/overlays/staging/kustomization.yaml @@ -0,0 +1,35 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + - certificate.yaml + +patches: + - target: + kind: Ingress + name: otterscan + patch: |- + - op: replace + path: "/spec/rules/0/host" + value: otterscan.zilstg.dev + - op: replace + path: /metadata/annotations + value: + kubernetes.io/ingress.class: gce + kubernetes.io/ingress.global-static-ip-name: otterscan-zilstg-dev + networking.gke.io/managed-certificates: otterscan + - target: + kind: Deployment + name: otterscan + patch: |- + - op: replace + path: "/spec/template/spec/containers/0/env/0/value" + value: "https://api.zq2-prototestnet.zilliqa.com" + +configMapGenerator: + - name: config + files: + - config.yaml + +namespace: otterscan-stg diff --git a/public/config.json b/public/config.json index f6d1763c..a85227b5 100644 --- a/public/config.json +++ b/public/config.json @@ -1,5 +1,4 @@ { - "erigonURL": "http://localhost:8545", "assetsURLPrefix": "http://localhost:5175", "connections": [ { "menuName": "zq1-mainnet", "url": "https://mainnet-v934-fireblocks.mainnet-20240103-ase1.zq1.network", "hostnames": ["otterscan.zilliqa.com"] }, diff --git a/src/useConfig.ts b/src/useConfig.ts index 564badb7..82a84a0d 100644 --- a/src/useConfig.ts +++ b/src/useConfig.ts @@ -352,7 +352,7 @@ export const loadOtterscanConfig = async (): Promise => { } catch (e) { // The version import doesn't exist - we're probably a development version. } - console.log(JSON.stringify(config)); + //console.log(JSON.stringify(config)); var storageConfiguration: any = {}; try { var storage = window["localStorage"]; @@ -360,7 +360,7 @@ export const loadOtterscanConfig = async (): Promise => { storageConfiguration = JSON.parse( storage.getItem("otterscanConfig") ?? "{}", ); - console.log("storage Config " + JSON.stringify(storageConfiguration)); + // console.log("storage Config " + JSON.stringify(storageConfiguration)); } } catch (err) { console.log(`Failed to get localStorage config - ${err}`); @@ -389,6 +389,21 @@ export const loadOtterscanConfig = async (): Promise => { throw new Error(`Error setting URL from hostname: ${err}`); } + // If we've still not got a connection, use the first one. + try { + if (config.erigonURL === undefined || config.erigonURL == null) { + var connections = storageConfiguration["connections"] ?? config.connections; + if (connections !== undefined) { + if (!("erigonURL" in storageConfiguration)) { + console.log("No URL; using first connection " + connections[0].url); + storageConfiguration["erigonURL"] = connections[0].url; + } + } + } + } catch (err) { + throw new Error(`Error setting default connection`); + } + // Set up URL parameters. try { var params = new URLSearchParams(window.location.search); From 6233a73ebb69fa2607af2416804cd4e279934342 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 30 Dec 2024 11:03:00 +0000 Subject: [PATCH 06/13] (feat) update build container base OS --- .github/workflows/cicd-stg.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd-stg.yaml b/.github/workflows/cicd-stg.yaml index ec63fef9..407a1273 100644 --- a/.github/workflows/cicd-stg.yaml +++ b/.github/workflows/cicd-stg.yaml @@ -16,7 +16,7 @@ jobs: permissions: id-token: write contents: write - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 # To test deployments, remove the github.ref_name clause: see devops/docs/z2-testing-apps.md - rrw 2024-04-12 # && github.ref_name == 'main' if: github.actor != 'dependabot[bot]' From 66df48a3dc758ae1ff0aa54a92a4d6fe9494a798 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 30 Dec 2024 11:18:38 +0000 Subject: [PATCH 07/13] (hack) Deploy to staging for testing. --- .github/workflows/cicd-stg.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cicd-stg.yaml b/.github/workflows/cicd-stg.yaml index 407a1273..aa0f0597 100644 --- a/.github/workflows/cicd-stg.yaml +++ b/.github/workflows/cicd-stg.yaml @@ -89,7 +89,7 @@ jobs: id-token: write contents: write runs-on: ubuntu-22.04 - if: github.actor != 'dependabot[bot]' && github.ref_name == 'main' + if: github.actor != 'dependabot[bot]' # && github.ref_name == 'main' strategy: fail-fast: false matrix: From 6b73c0d9c88d7cee332510030a40bb9e77404c63 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 30 Dec 2024 11:39:11 +0000 Subject: [PATCH 08/13] (fix) Actually push the image --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3007f55e..b2539b35 100644 --- a/Makefile +++ b/Makefile @@ -15,9 +15,10 @@ endif HERE=$(shell pwd) IMAGE_TAG ?= otterscan:latest - +export IMAGE_TAG .PHONY: image/build-and-push image/build-and-push: ./scripts/gen-version.sh autogen/version.ts - docker buildx build -f Dockerfile.zilliqa . -t $(IMAGE_TAG) + docker buildx build -f Dockerfile.zilliqa . -t "${IMAGE_TAG}" + docker push "${IMAGE_TAG}" From 116409a38bb50893ca87f4ea1e4df415afd7bc75 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 30 Dec 2024 12:46:05 +0000 Subject: [PATCH 09/13] (fix) Fix config (feat) Health check now checks the root, which is at least makes sure we can serve something. --- cd/base/deployment.yaml | 3 ++- cd/overlays/staging/{config.yaml => config.json} | 0 cd/overlays/staging/kustomization.yaml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) rename cd/overlays/staging/{config.yaml => config.json} (100%) diff --git a/cd/base/deployment.yaml b/cd/base/deployment.yaml index bb09603c..a2f72bcd 100644 --- a/cd/base/deployment.yaml +++ b/cd/base/deployment.yaml @@ -25,6 +25,7 @@ spec: value: yes volumeMounts: - name: config-vol + subPath: config.json mountPath: /usr/share/nginx/html/config.json ports: - containerPort: 80 @@ -35,7 +36,7 @@ spec: memory: 100Mi readinessProbe: httpGet: - path: /health + path: / port: 80 volumes: - name: config-vol diff --git a/cd/overlays/staging/config.yaml b/cd/overlays/staging/config.json similarity index 100% rename from cd/overlays/staging/config.yaml rename to cd/overlays/staging/config.json diff --git a/cd/overlays/staging/kustomization.yaml b/cd/overlays/staging/kustomization.yaml index c3dcce6e..8d13b05a 100644 --- a/cd/overlays/staging/kustomization.yaml +++ b/cd/overlays/staging/kustomization.yaml @@ -30,6 +30,6 @@ patches: configMapGenerator: - name: config files: - - config.yaml + - config.json namespace: otterscan-stg From 58ead0681d7e2c5bef3bf6bcbaeabd732df20b5c Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 30 Dec 2024 12:49:01 +0000 Subject: [PATCH 10/13] (fix) prettier --- cd/overlays/staging/config.json | 24 ++++++++++++++++++++---- src/useConfig.ts | 3 ++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/cd/overlays/staging/config.json b/cd/overlays/staging/config.json index 087c121f..dc1c8671 100644 --- a/cd/overlays/staging/config.json +++ b/cd/overlays/staging/config.json @@ -1,8 +1,24 @@ { "connections": [ - { "menuName": "zq1-mainnet", "url": "https://mainnet-v934-fireblocks.mainnet-20240103-ase1.zq1.network", "hostnames": ["otterscan.zilliqa.com"] }, - { "menuName": "zq1-testnet", "url": "https://testnet-v932-fireblocks.testnet-ase1.zq1.dev", "hostnames": ["otterscan.testnet.zilliqa.com"] }, - { "menuName": "zq2-prototestnet", "url": "https://api.zq2-prototestnet.zilliqa.com", "hostnames": ["explorer.zq2-prototestnet.zilliqa.com" ] }, - { "menuName": "zq2-protomainnet", "url": "https://api.zq2-protomainnet.zilliqa.com", "hostnames": ["explorer.zq2-protomainnet.zilliqa.com"] } + { + "menuName": "zq1-mainnet", + "url": "https://mainnet-v934-fireblocks.mainnet-20240103-ase1.zq1.network", + "hostnames": ["otterscan.zilliqa.com"] + }, + { + "menuName": "zq1-testnet", + "url": "https://testnet-v932-fireblocks.testnet-ase1.zq1.dev", + "hostnames": ["otterscan.testnet.zilliqa.com"] + }, + { + "menuName": "zq2-prototestnet", + "url": "https://api.zq2-prototestnet.zilliqa.com", + "hostnames": ["explorer.zq2-prototestnet.zilliqa.com"] + }, + { + "menuName": "zq2-protomainnet", + "url": "https://api.zq2-protomainnet.zilliqa.com", + "hostnames": ["explorer.zq2-protomainnet.zilliqa.com"] + } ] } diff --git a/src/useConfig.ts b/src/useConfig.ts index 82a84a0d..b3e4062a 100644 --- a/src/useConfig.ts +++ b/src/useConfig.ts @@ -392,7 +392,8 @@ export const loadOtterscanConfig = async (): Promise => { // If we've still not got a connection, use the first one. try { if (config.erigonURL === undefined || config.erigonURL == null) { - var connections = storageConfiguration["connections"] ?? config.connections; + var connections = + storageConfiguration["connections"] ?? config.connections; if (connections !== undefined) { if (!("erigonURL" in storageConfiguration)) { console.log("No URL; using first connection " + connections[0].url); From 8aae6ff101cbcf00400fbf64d2466e690f11180d Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 30 Dec 2024 13:29:19 +0000 Subject: [PATCH 11/13] (fix) Remove hacks from cicd-stg.yaml --- .github/workflows/cicd-stg.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/cicd-stg.yaml b/.github/workflows/cicd-stg.yaml index aa0f0597..41d32596 100644 --- a/.github/workflows/cicd-stg.yaml +++ b/.github/workflows/cicd-stg.yaml @@ -9,7 +9,6 @@ on: push: branches: - main - - users/richard/76-docker-build-and-host-dependency jobs: build-makefile: @@ -89,7 +88,7 @@ jobs: id-token: write contents: write runs-on: ubuntu-22.04 - if: github.actor != 'dependabot[bot]' # && github.ref_name == 'main' + if: github.actor != 'dependabot[bot]' && github.ref_name == 'main' strategy: fail-fast: false matrix: From 5e2233d996a6127e147b289916108c4e4158dccc Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 30 Dec 2024 13:49:58 +0000 Subject: [PATCH 12/13] (fix) Fix oddly assigned env var --- cd/base/deployment.yaml | 2 +- cd/overlays/staging/kustomization.yaml | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/cd/base/deployment.yaml b/cd/base/deployment.yaml index a2f72bcd..f4c27b5e 100644 --- a/cd/base/deployment.yaml +++ b/cd/base/deployment.yaml @@ -22,7 +22,7 @@ spec: name: otterscan env: - name: DISABLE_CONFIG_OVERWRITE - value: yes + value: "yes" volumeMounts: - name: config-vol subPath: config.json diff --git a/cd/overlays/staging/kustomization.yaml b/cd/overlays/staging/kustomization.yaml index 8d13b05a..5c84c088 100644 --- a/cd/overlays/staging/kustomization.yaml +++ b/cd/overlays/staging/kustomization.yaml @@ -19,13 +19,6 @@ patches: kubernetes.io/ingress.class: gce kubernetes.io/ingress.global-static-ip-name: otterscan-zilstg-dev networking.gke.io/managed-certificates: otterscan - - target: - kind: Deployment - name: otterscan - patch: |- - - op: replace - path: "/spec/template/spec/containers/0/env/0/value" - value: "https://api.zq2-prototestnet.zilliqa.com" configMapGenerator: - name: config From fa6deaedaddf33eaed7419f98e8b3c332adca484 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 30 Dec 2024 14:02:34 +0000 Subject: [PATCH 13/13] (fix) "yes", it turns out, is also a bool. Try "disable" to make the configuration apply --- cd/base/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cd/base/deployment.yaml b/cd/base/deployment.yaml index f4c27b5e..1cb98778 100644 --- a/cd/base/deployment.yaml +++ b/cd/base/deployment.yaml @@ -22,7 +22,7 @@ spec: name: otterscan env: - name: DISABLE_CONFIG_OVERWRITE - value: "yes" + value: "disable" volumeMounts: - name: config-vol subPath: config.json