Compare commits

..

No commits in common. "main" and "v1.1.0-abc0c24-202406061147" have entirely different histories.

113 changed files with 1121 additions and 4275 deletions

View File

@ -1,22 +1,19 @@
name: K8s Deployment Control Test name: Fixturenet-Eth-Plugeth-Arm-Test
on: on:
pull_request:
branches: '*'
push: push:
branches: '*' branches: '*'
paths: paths:
- '!**' - '!**'
- '.gitea/workflows/triggers/test-k8s-deployment-control' - '.gitea/workflows/triggers/fixturenet-eth-plugeth-arm-test'
- '.gitea/workflows/test-k8s-deployment-control.yml'
- 'tests/k8s-deployment-control/run-test.sh'
schedule: # Note: coordinate with other tests to not overload runners at the same time of day schedule: # Note: coordinate with other tests to not overload runners at the same time of day
- cron: '3 30 * * *' - cron: '2 14 * * *'
jobs: jobs:
test: test:
name: "Run deployment control suite on kind/k8s" name: "Run an Ethereum plugeth fixturenet test"
runs-on: ubuntu-22.04 runs-on: ubuntu-latest-arm
steps: steps:
- name: "Clone project repository" - name: "Clone project repository"
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -35,22 +32,13 @@ jobs:
- name: "Print Python version" - name: "Print Python version"
run: python3 --version run: python3 --version
- name: "Install shiv" - name: "Install shiv"
run: pip install shiv==1.0.6 run: pip install shiv
- name: "Generate build version file" - name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package" - name: "Build local shiv package"
run: ./scripts/build_shiv_package.sh run: ./scripts/build_shiv_package.sh
- name: "Check cgroups version" - name: "Run fixturenet-eth tests"
run: mount | grep cgroup run: ./tests/fixturenet-eth-plugeth/run-test.sh
- name: "Install kind"
run: ./tests/scripts/install-kind.sh
- name: "Install Kubectl"
run: ./tests/scripts/install-kubectl.sh
- name: "Run k8s deployment control test"
run: |
source /opt/bash-utils/cgroup-helper.sh
join_cgroup
./tests/k8s-deployment-control/run-test.sh
- name: Notify Vulcanize Slack on CI failure - name: Notify Vulcanize Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }} if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2 uses: ravsamhq/notify-slack-action@v2

View File

@ -0,0 +1,57 @@
name: Fixturenet-Eth-Plugeth-Test
on:
push:
branches: '*'
paths:
- '!**'
- '.gitea/workflows/triggers/fixturenet-eth-plugeth-test'
schedule: # Note: coordinate with other tests to not overload runners at the same time of day
- cron: '2 14 * * *'
jobs:
test:
name: "Run an Ethereum plugeth fixturenet test"
runs-on: ubuntu-latest
steps:
- name: "Clone project repository"
uses: actions/checkout@v3
# At present the stock setup-python action fails on Linux/aarch64
# Conditional steps below workaroud this by using deadsnakes for that case only
- name: "Install Python for ARM on Linux"
if: ${{ runner.arch == 'arm64' && runner.os == 'Linux' }}
uses: deadsnakes/action@v3.0.1
with:
python-version: '3.8'
- name: "Install Python cases other than ARM on Linux"
if: ${{ ! (runner.arch == 'arm64' && runner.os == 'Linux') }}
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: "Print Python version"
run: python3 --version
- name: "Install shiv"
run: pip install shiv
- name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package"
run: ./scripts/build_shiv_package.sh
- name: "Run fixturenet-eth tests"
run: ./tests/fixturenet-eth-plugeth/run-test.sh
- name: Notify Vulcanize Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.VULCANIZE_SLACK_CI_ALERTS }}
- name: Notify DeepStack Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.DEEPSTACK_SLACK_CI_ALERTS }}

View File

@ -0,0 +1,55 @@
name: Fixturenet-Eth-Test
on:
push:
branches: '*'
paths:
- '!**'
- '.gitea/workflows/triggers/fixturenet-eth-test'
jobs:
test:
name: "Run an Ethereum fixturenet test"
runs-on: ubuntu-latest
steps:
- name: "Clone project repository"
uses: actions/checkout@v3
# At present the stock setup-python action fails on Linux/aarch64
# Conditional steps below workaroud this by using deadsnakes for that case only
- name: "Install Python for ARM on Linux"
if: ${{ runner.arch == 'arm64' && runner.os == 'Linux' }}
uses: deadsnakes/action@v3.0.1
with:
python-version: '3.8'
- name: "Install Python cases other than ARM on Linux"
if: ${{ ! (runner.arch == 'arm64' && runner.os == 'Linux') }}
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: "Print Python version"
run: python3 --version
- name: "Install shiv"
run: pip install shiv
- name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package"
run: ./scripts/build_shiv_package.sh
- name: "Run fixturenet-eth tests"
run: ./tests/fixturenet-eth/run-test.sh
- name: Notify Vulcanize Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.VULCANIZE_SLACK_CI_ALERTS }}
- name: Notify DeepStack Slack on CI failure
if: ${{ always() && github.ref_name == 'main' }}
uses: ravsamhq/notify-slack-action@v2
with:
status: ${{ job.status }}
notify_when: 'failure'
env:
SLACK_WEBHOOK_URL: ${{ secrets.DEEPSTACK_SLACK_CI_ALERTS }}

View File

@ -39,7 +39,7 @@ jobs:
- name: "Print Python version" - name: "Print Python version"
run: python3 --version run: python3 --version
- name: "Install shiv" - name: "Install shiv"
run: pip install shiv==1.0.6 run: pip install shiv
- name: "Generate build version file" - name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package" - name: "Build local shiv package"

View File

@ -35,7 +35,7 @@ jobs:
- name: "Print Python version" - name: "Print Python version"
run: python3 --version run: python3 --version
- name: "Install shiv" - name: "Install shiv"
run: pip install shiv==1.0.6 run: pip install shiv
- name: "Build local shiv package" - name: "Build local shiv package"
id: build id: build
run: | run: |

View File

@ -33,7 +33,7 @@ jobs:
- name: "Print Python version" - name: "Print Python version"
run: python3 --version run: python3 --version
- name: "Install shiv" - name: "Install shiv"
run: pip install shiv==1.0.6 run: pip install shiv
- name: "Generate build version file" - name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package" - name: "Build local shiv package"

View File

@ -33,7 +33,7 @@ jobs:
- name: "Print Python version" - name: "Print Python version"
run: python3 --version run: python3 --version
- name: "Install shiv" - name: "Install shiv"
run: pip install shiv==1.0.6 run: pip install shiv
- name: "Generate build version file" - name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package" - name: "Build local shiv package"

View File

@ -33,7 +33,7 @@ jobs:
- name: "Print Python version" - name: "Print Python version"
run: python3 --version run: python3 --version
- name: "Install shiv" - name: "Install shiv"
run: pip install shiv==1.0.6 run: pip install shiv
- name: "Generate build version file" - name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package" - name: "Build local shiv package"

View File

@ -33,7 +33,7 @@ jobs:
- name: "Print Python version" - name: "Print Python version"
run: python3 --version run: python3 --version
- name: "Install shiv" - name: "Install shiv"
run: pip install shiv==1.0.6 run: pip install shiv
- name: "Generate build version file" - name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package" - name: "Build local shiv package"

View File

@ -35,7 +35,7 @@ jobs:
- name: "Print Python version" - name: "Print Python version"
run: python3 --version run: python3 --version
- name: "Install shiv" - name: "Install shiv"
run: pip install shiv==1.0.6 run: pip install shiv
- name: "Generate build version file" - name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package" - name: "Build local shiv package"

View File

@ -32,7 +32,7 @@ jobs:
- name: "Print Python version" - name: "Print Python version"
run: python3 --version run: python3 --version
- name: "Install shiv" - name: "Install shiv"
run: pip install shiv==1.0.6 run: pip install shiv
- name: "Generate build version file" - name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package" - name: "Build local shiv package"

View File

@ -33,7 +33,7 @@ jobs:
- name: "Print Python version" - name: "Print Python version"
run: python3 --version run: python3 --version
- name: "Install shiv" - name: "Install shiv"
run: pip install shiv==1.0.6 run: pip install shiv
- name: "Generate build version file" - name: "Generate build version file"
run: ./scripts/create_build_tag_file.sh run: ./scripts/create_build_tag_file.sh
- name: "Build local shiv package" - name: "Build local shiv package"

View File

@ -0,0 +1,2 @@
Change this file to trigger running the fixturenet-eth-plugeth-arm-test CI job

View File

@ -0,0 +1,3 @@
Change this file to trigger running the fixturenet-eth-plugeth-test CI job
trigger
trigger

View File

@ -0,0 +1,2 @@
Change this file to trigger running the fixturenet-eth-test CI job

View File

@ -4,7 +4,3 @@ Trigger
Trigger Trigger
Trigger Trigger
Trigger Trigger
Trigger
Trigger
Trigger
Trigger

View File

@ -51,7 +51,7 @@ $ laconic-so build-npms --include <package-name>
``` ```
e.g. e.g.
``` ```
$ laconic-so build-npms --include registry-sdk $ laconic-so build-npms --include laconic-sdk
``` ```
Build the packages for a stack: Build the packages for a stack:
``` ```

View File

@ -56,7 +56,7 @@ laconic-so --stack fixturenet-laconicd build-npms
Navigate to the Gitea console and switch to the `cerc-io` user then find the `Packages` tab to confirm that these two npm packages have been published: Navigate to the Gitea console and switch to the `cerc-io` user then find the `Packages` tab to confirm that these two npm packages have been published:
- `@cerc-io/laconic-registry-cli` - `@cerc-io/laconic-registry-cli`
- `@cerc-io/registry-sdk` - `@cerc-io/laconic-sdk`
### Build and deploy fixturenet containers ### Build and deploy fixturenet containers
@ -74,7 +74,7 @@ laconic-so --stack fixturenet-laconicd deploy logs
### Test with the registry CLI ### Test with the registry CLI
```bash ```bash
laconic-so --stack fixturenet-laconicd deploy exec cli "laconic registry status" laconic-so --stack fixturenet-laconicd deploy exec cli "laconic cns status"
``` ```
Try additional CLI commands, documented [here](https://github.com/cerc-io/laconic-registry-cli#operations). Try additional CLI commands, documented [here](https://github.com/cerc-io/laconic-registry-cli#operations).

View File

@ -1,27 +0,0 @@
# K8S Deployment Enhancements
## Controlling pod placement
The placement of pods created as part of a stack deployment can be controlled to either avoid certain nodes, or require certain nodes.
### Pod/Node Affinity
Node affinity rules applied to pods target node labels. The effect is that a pod can only be placed on a node having the specified label value. Note that other pods that do not have any node affinity rules can also be placed on those same nodes. Thus node affinity for a pod controls where that pod can be placed, but does not control where other pods are placed.
Node affinity for stack pods is specified in the deployment's `spec.yml` file as follows:
```
node-affinities:
- label: nodetype
value: typeb
```
This example denotes that the stack's pods should only be placed on nodes that have the label `nodetype` with value `typeb`.
### Node Taint Toleration
K8s nodes can be given one or more "taints". These are special fields (distinct from labels) with a name (key) and optional value.
When placing pods, the k8s scheduler will only assign a pod to a tainted node if the pod posesses a corresponding "toleration".
This is metadata associated with the pod that specifies that the pod "tolerates" a given taint.
Therefore taint toleration provides a mechanism by which only certain pods can be placed on specific nodes, and provides a complementary mechanism to node affinity.
Taint toleration for stack pods is specified in the deployment's `spec.yml` file as follows:
```
node-tolerations:
- key: nodetype
value: typeb
```
This example denotes that the stack's pods will tolerate a taint: `nodetype=typeb`

View File

@ -1,8 +1,9 @@
# Running a laconicd fixturenet with console # Running a laconicd fixturenet with console
The following tutorial explains the steps to run a laconicd fixturenet with CLI and web console that displays records in the registry. It is designed as an introduction to Stack Orchestrator and to showcase one component of the Laconic Stack. Prior to Stack Orchestrator, the following repositories had to be cloned and setup manually: The following tutorial explains the steps to run a laconicd fixturenet with CLI and web console that displays records in the registry. It is designed as an introduction to Stack Orchestrator and to showcase one component of the Laconic Stack. Prior to Stack Orchestrator, the following 4 repositories had to be cloned and setup manually:
- https://git.vdb.to/cerc-io/laconicd - https://git.vdb.to/cerc-io/laconicd
- https://git.vdb.to/cerc-io/laconic-sdk
- https://git.vdb.to/cerc-io/laconic-registry-cli - https://git.vdb.to/cerc-io/laconic-registry-cli
- https://git.vdb.to/cerc-io/laconic-console - https://git.vdb.to/cerc-io/laconic-console
@ -10,7 +11,7 @@ Now, with Stack Orchestrator, it is a few quick commands. Additionally, the `doc
## Setup laconic-so ## Setup laconic-so
To avoid hiccups on Mac M1/M2 and any local machine nuances that may affect the user experience, this tutorial is focused on using a fresh Digital Ocean (DO) droplet with similar specs: To avoid hiccups on Mac M1/M2 and any local machine nuances that may affect the user experience, this tutorial is focused on using a fresh Digital Ocean (DO) droplet with similar specs:
16 GB Memory / 8 Intel vCPUs / 160 GB Disk. 16 GB Memory / 8 Intel vCPUs / 160 GB Disk.
1. Login to the droplet as root (either by SSH key or password set in the DO console) 1. Login to the droplet as root (either by SSH key or password set in the DO console)
@ -50,7 +51,7 @@ To avoid hiccups on Mac M1/M2 and any local machine nuances that may affect the
1. Get the repositories 1. Get the repositories
``` ```
laconic-so --stack fixturenet-laconic-loaded setup-repositories --include git.vdb.to/cerc-io/laconicd laconic-so --stack fixturenet-laconic-loaded setup-repositories --include git.vdb.to/cerc-io/laconicd,git.vdb.to/cerc-io/laconic-sdk,git.vdb.to/cerc-io/laconic-registry-cli,git.vdb.to/cerc-io/laconic-console
``` ```
1. Build the containers: 1. Build the containers:
@ -75,8 +76,6 @@ To avoid hiccups on Mac M1/M2 and any local machine nuances that may affect the
1. Create a deployment directory for the stack: 1. Create a deployment directory for the stack:
``` ```
laconic-so --stack fixturenet-laconic-loaded deploy init --output laconic-loaded.spec --map-ports-to-host any-same --config LACONIC_HOSTED_ENDPOINT=$BACKEND_ENDPOINT laconic-so --stack fixturenet-laconic-loaded deploy init --output laconic-loaded.spec --map-ports-to-host any-same --config LACONIC_HOSTED_ENDPOINT=$BACKEND_ENDPOINT
# Update port mapping in the laconic-loaded.spec file to resolve port conflicts on host if any
``` ```
``` ```
laconic-so --stack fixturenet-laconic-loaded deploy create --deployment-dir laconic-loaded-deployment --spec-file laconic-loaded.spec laconic-so --stack fixturenet-laconic-loaded deploy create --deployment-dir laconic-loaded-deployment --spec-file laconic-loaded.spec
@ -96,51 +95,52 @@ To avoid hiccups on Mac M1/M2 and any local machine nuances that may affect the
You'll see output from `laconicd` and the block height should be >1 to confirm it is running: You'll see output from `laconicd` and the block height should be >1 to confirm it is running:
``` ```
laconicd-1 | 6:12AM INF indexed block events height=16 module=txindex laconic-5cd0a80c1442c3044c8b295d26426bae-laconicd-1 | 9:29PM INF indexed block exents height=12 module=txindex server=node
laconicd-1 | 6:12AM INF Timed out dur=2993.893332 height=17 module=consensus round=0 step=RoundStepNewHeight laconic-5cd0a80c1442c3044c8b295d26426bae-laconicd-1 | 9:30PM INF Timed out dur=4976.960115 height=13 module=consensus round=0 server=node step=1
laconicd-1 | 6:12AM INF received proposal module=consensus proposal="Proposal{17/0 (E15D03C180CE607AE8340A1325A0C134DFB4E1ADD992E173C701EBD362523267:1:DF138772FEF0, -1) 6A6F3B0A42B3 @ 2024-07-25T06:12:31.952967053Z}" proposer=86970D950BC9C16F3991A52D9C6DC55BA478A7C6 laconic-5cd0a80c1442c3044c8b295d26426bae-laconicd-1 | 9:30PM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"D26C088A711F912ADB97888C269F628DA33153795621967BE44DCB43C3D03CA4","parts":{"hash":"22411A20B7F14CDA33244420FBDDAF24450C0628C7A06034FF22DAC3699DDCC8","total":1}},"height":13,"pol_round":-1,"round":0,"signature":"DEuqnaQmvyYbUwckttJmgKdpRu6eVm9i+9rQ1pIrV2PidkMNdWRZBLdmNghkIrUzGbW8Xd7UVJxtLRmwRASgBg==","timestamp":"2023-04-18T21:30:01.49450663Z"} server=node
laconicd-1 | 6:12AM INF received complete proposal block hash=E15D03C180CE607AE8340A1325A0C134DFB4E1ADD992E173C701EBD362523267 height=17 module=consensus laconic-5cd0a80c1442c3044c8b295d26426bae-laconicd-1 | 9:30PM INF received complete proposal block hash=D26C088A711F912ADB97888C269F628DA33153795621967BE44DCB43C3D03CA4 height=13 module=consensus server=node
laconicd-1 | 6:12AM INF finalizing commit of block hash=E15D03C180CE607AE8340A1325A0C134DFB4E1ADD992E173C701EBD362523267 height=17 module=consensus num_txs=0 root=AF4941107DC718ED1425E77A3DC7F1154FB780B7A7DE20288DC43442203527E3 laconic-5cd0a80c1442c3044c8b295d26426bae-laconicd-1 | 9:30PM INF finalizing commit of block hash={} height=13 module=consensus num_txs=0 root=1A8CA1AF139CCC80EC007C6321D8A63A46A793386EE2EDF9A5CA0AB2C90728B7 server=node
laconicd-1 | 6:12AM INF finalized block block_app_hash=26A665360BB1EE64E54F97F2A5AB7F621B33A86D9896574000C05DE63F43F788 height=17 module=state num_txs_res=0 num_val_updates=0 laconic-5cd0a80c1442c3044c8b295d26426bae-laconicd-1 | 9:30PM INF minted coins from module account amount=2059730459416582643aphoton from=mint module=x/bank
laconicd-1 | 6:12AM INF executed block app_hash=26A665360BB1EE64E54F97F2A5AB7F621B33A86D9896574000C05DE63F43F788 height=17 module=state laconic-5cd0a80c1442c3044c8b295d26426bae-laconicd-1 | 9:30PM INF executed block height=13 module=state num_invalid_txs=0 num_valid_txs=0 server=node
laconicd-1 | 6:12AM INF committed state block_app_hash=AF4941107DC718ED1425E77A3DC7F1154FB780B7A7DE20288DC43442203527E3 height=17 module=state laconic-5cd0a80c1442c3044c8b295d26426bae-laconicd-1 | 9:30PM INF commit synced commit=436F6D6D697449447B5B363520313037203630203232372039352038352032303820313334203231392032303520313433203130372031343920313431203139203139322038362031323720362031383520323533203137362031333820313735203135392031383620323334203135382031323120313431203230342037335D3A447D
laconicd-1 | 6:12AM INF indexed block events height=17 module=txindex laconic-5cd0a80c1442c3044c8b295d26426bae-laconicd-1 | 9:30PM INF committed state app_hash=416B3CE35F55D086DBCD8F6B958D13C0567F06B9FDB08AAF9FBAEA9E798DCC49 height=13 module=state num_txs=0 server=node
laconic-5cd0a80c1442c3044c8b295d26426bae-laconicd-1 | 9:30PM INF indexed block exents height=13 module=txindex server=node
``` ```
4. Confirm operation of the registry CLI: 4. Confirm operation of the registry CLI:
``` ```
laconic-so deployment --dir laconic-loaded-deployment exec cli "laconic registry status" laconic-so deployment --dir laconic-loaded-deployment exec cli "laconic cns status"
``` ```
``` ```
{ {
"version": "0.3.0", "version": "0.3.0",
"node": { "node": {
"id": "6e072894aa1f5d9535a1127a0d7a7f8e65100a2c", "id": "4216af2ac9f68bda33a38803fc1b5c9559312c1d",
"network": "laconic_9000-1", "network": "laconic_9000-1",
"moniker": "localtestnet" "moniker": "localtestnet"
}, },
"sync": { "sync": {
"latestBlockHash": "260102C283D0411CFBA0270F7DC182650FFCA737A2F6F652A985F6065696F590", "latest_block_hash": "1BDF4CB9AE2390DA65BCF997C83133C18014FCDDCAE03708488F0B56FCEEA429",
"latestBlockHeight": "49", "latest_block_height": "5",
"latestBlockTime": "2024-07-25 06:14:05.626744215 +0000 UTC", "latest_block_time": "2023-08-09 16:00:30.386903172 +0000 UTC",
"catchingUp": false "catching_up": false
}, },
"validator": { "validator": {
"address": "86970D950BC9C16F3991A52D9C6DC55BA478A7C6", "address": "651FBC700B747C76E90ACFC18CC9508C3D0905B9",
"votingPower": "1000000000000000" "voting_power": "1000000000000000"
}, },
"validators": [ "validators": [
{ {
"address": "86970D950BC9C16F3991A52D9C6DC55BA478A7C6", "address": "651FBC700B747C76E90ACFC18CC9508C3D0905B9",
"votingPower": "1000000000000000", "voting_power": "1000000000000000",
"proposerPriority": "0" "proposer_priority": "0"
} }
], ],
"numPeers": "0", "num_peers": "0",
"peers": [], "peers": [],
"diskUsage": "688K" "disk_usage": "292.0K"
} }
``` ```
@ -186,13 +186,13 @@ wns
1. The following command will create a bond and publish a record: 1. The following command will create a bond and publish a record:
``` ```
laconic-so deployment --dir laconic-loaded-deployment exec cli ./scripts/create-demo-records.sh laconic-so --stack fixturenet-laconic-loaded deploy exec cli ./scripts/create-demo-records.sh
``` ```
You'll get an output like: You'll get an output like:
``` ```
Balance is: 9.9999e+25 Balance is: 99998999999999998999600000
Created bond with id: dd88e8d6f9567b32b28e70552aea4419c5dd3307ebae85a284d1fe38904e301a Created bond with id: dd88e8d6f9567b32b28e70552aea4419c5dd3307ebae85a284d1fe38904e301a
Published demo-record-1.yml with id: bafyreierh3xnfivexlscdwubvczmddsnf46uytyfvrbdhkjzztvsz6ruly Published demo-record-1.yml with id: bafyreierh3xnfivexlscdwubvczmddsnf46uytyfvrbdhkjzztvsz6ruly
``` ```
@ -223,5 +223,5 @@ record:
- e.g,: - e.g,:
``` ```
laconic-so deployment --dir laconic-loaded-deployment exec cli "laconic registry record list" laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic cns record list"
``` ```

View File

@ -11,5 +11,3 @@ tomli==2.0.1
validators==0.22.0 validators==0.22.0
kubernetes>=28.1.0 kubernetes>=28.1.0
humanfriendly>=10.0 humanfriendly>=10.0
python-gnupg>=0.5.2
requests>=2.3.2

View File

@ -4,11 +4,9 @@ with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read() long_description = fh.read()
with open("requirements.txt", "r", encoding="utf-8") as fh: with open("requirements.txt", "r", encoding="utf-8") as fh:
requirements = fh.read() requirements = fh.read()
with open("stack_orchestrator/data/version.txt", "r", encoding="utf-8") as fh:
version = fh.readlines()[-1].strip(" \n")
setup( setup(
name='laconic-stack-orchestrator', name='laconic-stack-orchestrator',
version=version, version='1.0.12',
author='Cerc', author='Cerc',
author_email='info@cerc.io', author_email='info@cerc.io',
license='GNU Affero General Public License', license='GNU Affero General Public License',

View File

@ -21,6 +21,11 @@ from stack_orchestrator.util import get_parsed_stack_config, warn_exit
def get_containers_in_scope(stack: str): def get_containers_in_scope(stack: str):
# See: https://stackoverflow.com/a/20885799/1701505
from stack_orchestrator import data
with importlib.resources.open_text(data, "container-image-list.txt") as container_list_file:
all_containers = container_list_file.read().splitlines()
containers_in_scope = [] containers_in_scope = []
if stack: if stack:
stack_config = get_parsed_stack_config(stack) stack_config = get_parsed_stack_config(stack)
@ -28,14 +33,11 @@ def get_containers_in_scope(stack: str):
warn_exit(f"stack {stack} does not define any containers") warn_exit(f"stack {stack} does not define any containers")
containers_in_scope = stack_config['containers'] containers_in_scope = stack_config['containers']
else: else:
# See: https://stackoverflow.com/a/20885799/1701505 containers_in_scope = all_containers
from stack_orchestrator import data
with importlib.resources.open_text(data, "container-image-list.txt") as container_list_file:
containers_in_scope = container_list_file.read().splitlines()
if opts.o.verbose: if opts.o.verbose:
print(f'Containers: {containers_in_scope}') print(f'Containers: {containers_in_scope}')
if stack: if stack:
print(f"Stack: {stack}") print(f"Stack: {stack}")
return containers_in_scope return containers_in_scope

View File

@ -34,8 +34,5 @@ volumes_key = "volumes"
security_key = "security" security_key = "security"
annotations_key = "annotations" annotations_key = "annotations"
labels_key = "labels" labels_key = "labels"
replicas_key = "replicas"
node_affinities_key = "node-affinities"
node_tolerations_key = "node-tolerations"
kind_config_filename = "kind-config.yml" kind_config_filename = "kind-config.yml"
kube_config_filename = "kubeconfig.yml" kube_config_filename = "kubeconfig.yml"

View File

@ -2,11 +2,10 @@ services:
laconicd: laconicd:
restart: unless-stopped restart: unless-stopped
image: cerc/laconicd:local image: cerc/laconicd:local
command: ["bash", "/docker-entrypoint-scripts.d/create-fixturenet.sh"] command: ["sh", "/docker-entrypoint-scripts.d/create-fixturenet.sh"]
environment: environment:
TEST_AUCTION_ENABLED: ${TEST_AUCTION_ENABLED:-false} TEST_AUCTION_ENABLED: ${TEST_AUCTION_ENABLED}
TEST_REGISTRY_EXPIRY: ${TEST_REGISTRY_EXPIRY:-false} TEST_REGISTRY_EXPIRY: ${TEST_REGISTRY_EXPIRY}
ONBOARDING_ENABLED: ${ONBOARDING_ENABLED:-false}
volumes: volumes:
# The cosmos-sdk node's database directory: # The cosmos-sdk node's database directory:
- laconicd-data:/root/.laconicd - laconicd-data:/root/.laconicd
@ -20,9 +19,11 @@ services:
- "26657" - "26657"
- "26656" - "26656"
- "9473" - "9473"
- "8545"
- "8546"
- "9090" - "9090"
- "9091"
- "1317" - "1317"
cli: cli:
image: cerc/laconic-registry-cli:local image: cerc/laconic-registry-cli:local
volumes: volumes:

View File

@ -1,10 +0,0 @@
services:
laconic-explorer:
restart: unless-stopped
image: cerc/ping-pub:local
environment:
- LACONIC_LACONICD_API_URL=${LACONIC_LACONICD_API_URL:-http://localhost:1317}
- LACONIC_LACONICD_RPC_URL=${LACONIC_LACONICD_RPC_URL:-http://localhost:26657}
- LACONIC_LACONICD_CHAIN_ID=${LACONIC_LACONICD_CHAIN_ID:-chain-id-not-set}
ports:
- "5173"

View File

@ -0,0 +1,13 @@
services:
snowballtools-base-backend:
image: cerc/snowballtools-base-backend:local
restart: always
volumes:
- data:/data
- config:/config:ro
ports:
- 8000
volumes:
data:
config:

View File

@ -60,7 +60,6 @@ services:
volumes: volumes:
- ../config/watcher-ajna/watcher-config-template.toml:/app/environments/watcher-config-template.toml - ../config/watcher-ajna/watcher-config-template.toml:/app/environments/watcher-config-template.toml
- ../config/watcher-ajna/start-server.sh:/app/start-server.sh - ../config/watcher-ajna/start-server.sh:/app/start-server.sh
- ajna_watcher_gql_logs_data:/app/gql-logs
ports: ports:
- "3008" - "3008"
- "9001" - "9001"
@ -75,4 +74,3 @@ services:
volumes: volumes:
ajna_watcher_db_data: ajna_watcher_db_data:
ajna_watcher_gql_logs_data:

View File

@ -74,7 +74,6 @@ services:
- ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/azimuth-watcher/environments/watcher-config-template.toml - ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/azimuth-watcher/environments/watcher-config-template.toml
- ../config/watcher-azimuth/merge-toml.js:/app/packages/azimuth-watcher/merge-toml.js - ../config/watcher-azimuth/merge-toml.js:/app/packages/azimuth-watcher/merge-toml.js
- ../config/watcher-azimuth/start-server.sh:/app/packages/azimuth-watcher/start-server.sh - ../config/watcher-azimuth/start-server.sh:/app/packages/azimuth-watcher/start-server.sh
- azimuth_watcher_gql_logs_data:/app/packages/azimuth-watcher/gql-logs
ports: ports:
- "3001" - "3001"
- "9001" - "9001"
@ -137,7 +136,6 @@ services:
- ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/censures-watcher/environments/watcher-config-template.toml - ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/censures-watcher/environments/watcher-config-template.toml
- ../config/watcher-azimuth/merge-toml.js:/app/packages/censures-watcher/merge-toml.js - ../config/watcher-azimuth/merge-toml.js:/app/packages/censures-watcher/merge-toml.js
- ../config/watcher-azimuth/start-server.sh:/app/packages/censures-watcher/start-server.sh - ../config/watcher-azimuth/start-server.sh:/app/packages/censures-watcher/start-server.sh
- censures_watcher_gql_logs_data:/app/packages/censures-watcher/gql-logs
ports: ports:
- "3002" - "3002"
- "9003" - "9003"
@ -200,7 +198,6 @@ services:
- ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/claims-watcher/environments/watcher-config-template.toml - ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/claims-watcher/environments/watcher-config-template.toml
- ../config/watcher-azimuth/merge-toml.js:/app/packages/claims-watcher/merge-toml.js - ../config/watcher-azimuth/merge-toml.js:/app/packages/claims-watcher/merge-toml.js
- ../config/watcher-azimuth/start-server.sh:/app/packages/claims-watcher/start-server.sh - ../config/watcher-azimuth/start-server.sh:/app/packages/claims-watcher/start-server.sh
- claims_watcher_gql_logs_data:/app/packages/claims-watcher/gql-logs
ports: ports:
- "3003" - "3003"
- "9005" - "9005"
@ -263,7 +260,6 @@ services:
- ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/conditional-star-release-watcher/environments/watcher-config-template.toml - ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/conditional-star-release-watcher/environments/watcher-config-template.toml
- ../config/watcher-azimuth/merge-toml.js:/app/packages/conditional-star-release-watcher/merge-toml.js - ../config/watcher-azimuth/merge-toml.js:/app/packages/conditional-star-release-watcher/merge-toml.js
- ../config/watcher-azimuth/start-server.sh:/app/packages/conditional-star-release-watcher/start-server.sh - ../config/watcher-azimuth/start-server.sh:/app/packages/conditional-star-release-watcher/start-server.sh
- conditional_star_release_watcher_gql_logs_data:/app/packages/conditional-star-release-watcher/gql-logs
ports: ports:
- "3004" - "3004"
- "9007" - "9007"
@ -326,7 +322,6 @@ services:
- ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/delegated-sending-watcher/environments/watcher-config-template.toml - ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/delegated-sending-watcher/environments/watcher-config-template.toml
- ../config/watcher-azimuth/merge-toml.js:/app/packages/delegated-sending-watcher/merge-toml.js - ../config/watcher-azimuth/merge-toml.js:/app/packages/delegated-sending-watcher/merge-toml.js
- ../config/watcher-azimuth/start-server.sh:/app/packages/delegated-sending-watcher/start-server.sh - ../config/watcher-azimuth/start-server.sh:/app/packages/delegated-sending-watcher/start-server.sh
- delegated_sending_watcher_gql_logs_data:/app/packages/delegated-sending-watcher/gql-logs
ports: ports:
- "3005" - "3005"
- "9009" - "9009"
@ -389,7 +384,6 @@ services:
- ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/ecliptic-watcher/environments/watcher-config-template.toml - ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/ecliptic-watcher/environments/watcher-config-template.toml
- ../config/watcher-azimuth/merge-toml.js:/app/packages/ecliptic-watcher/merge-toml.js - ../config/watcher-azimuth/merge-toml.js:/app/packages/ecliptic-watcher/merge-toml.js
- ../config/watcher-azimuth/start-server.sh:/app/packages/ecliptic-watcher/start-server.sh - ../config/watcher-azimuth/start-server.sh:/app/packages/ecliptic-watcher/start-server.sh
- ecliptic_watcher_gql_logs_data:/app/packages/ecliptic-watcher/gql-logs
ports: ports:
- "3006" - "3006"
- "9011" - "9011"
@ -452,7 +446,6 @@ services:
- ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/linear-star-release-watcher/environments/watcher-config-template.toml - ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/linear-star-release-watcher/environments/watcher-config-template.toml
- ../config/watcher-azimuth/merge-toml.js:/app/packages/linear-star-release-watcher/merge-toml.js - ../config/watcher-azimuth/merge-toml.js:/app/packages/linear-star-release-watcher/merge-toml.js
- ../config/watcher-azimuth/start-server.sh:/app/packages/linear-star-release-watcher/start-server.sh - ../config/watcher-azimuth/start-server.sh:/app/packages/linear-star-release-watcher/start-server.sh
- linear_star_release_watcher_gql_logs_data:/app/packages/linear-star-release-watcher/gql-logs
ports: ports:
- "3007" - "3007"
- "9013" - "9013"
@ -515,7 +508,6 @@ services:
- ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/polls-watcher/environments/watcher-config-template.toml - ../config/watcher-azimuth/watcher-config-template.toml:/app/packages/polls-watcher/environments/watcher-config-template.toml
- ../config/watcher-azimuth/merge-toml.js:/app/packages/polls-watcher/merge-toml.js - ../config/watcher-azimuth/merge-toml.js:/app/packages/polls-watcher/merge-toml.js
- ../config/watcher-azimuth/start-server.sh:/app/packages/polls-watcher/start-server.sh - ../config/watcher-azimuth/start-server.sh:/app/packages/polls-watcher/start-server.sh
- polls_watcher_gql_logs_data:/app/packages/polls-watcher/gql-logs
ports: ports:
- "3008" - "3008"
- "9015" - "9015"
@ -568,11 +560,3 @@ services:
volumes: volumes:
watcher_db_data: watcher_db_data:
azimuth_watcher_gql_logs_data:
censures_watcher_gql_logs_data:
claims_watcher_gql_logs_data:
conditional_star_release_watcher_gql_logs_data:
delegated_sending_watcher_gql_logs_data:
ecliptic_watcher_gql_logs_data:
linear_star_release_watcher_gql_logs_data:
polls_watcher_gql_logs_data:

View File

@ -60,7 +60,6 @@ services:
volumes: volumes:
- ../config/watcher-merkl-sushiswap-v3/watcher-config-template.toml:/app/environments/watcher-config-template.toml - ../config/watcher-merkl-sushiswap-v3/watcher-config-template.toml:/app/environments/watcher-config-template.toml
- ../config/watcher-merkl-sushiswap-v3/start-server.sh:/app/start-server.sh - ../config/watcher-merkl-sushiswap-v3/start-server.sh:/app/start-server.sh
- merkl_sushiswap_v3_watcher_gql_logs_data:/app/gql-logs
ports: ports:
- "127.0.0.1:3007:3008" - "127.0.0.1:3007:3008"
- "9003:9001" - "9003:9001"
@ -75,4 +74,3 @@ services:
volumes: volumes:
merkl_sushiswap_v3_watcher_db_data: merkl_sushiswap_v3_watcher_db_data:
merkl_sushiswap_v3_watcher_gql_logs_data:

View File

@ -60,7 +60,6 @@ services:
volumes: volumes:
- ../config/watcher-sushiswap-v3/watcher-config-template.toml:/app/environments/watcher-config-template.toml - ../config/watcher-sushiswap-v3/watcher-config-template.toml:/app/environments/watcher-config-template.toml
- ../config/watcher-sushiswap-v3/start-server.sh:/app/start-server.sh - ../config/watcher-sushiswap-v3/start-server.sh:/app/start-server.sh
- sushiswap_v3_watcher_gql_logs_data:/app/gql-logs
ports: ports:
- "127.0.0.1:3008:3008" - "127.0.0.1:3008:3008"
- "9001:9001" - "9001:9001"
@ -75,4 +74,3 @@ services:
volumes: volumes:
sushiswap_v3_watcher_db_data: sushiswap_v3_watcher_db_data:
sushiswap_v3_watcher_gql_logs_data:

View File

@ -8,68 +8,68 @@ KEY="mykey"
CHAINID="laconic_9000-1" CHAINID="laconic_9000-1"
MONIKER="localtestnet" MONIKER="localtestnet"
KEYRING="test" KEYRING="test"
KEYALGO="secp256k1" KEYALGO="eth_secp256k1"
LOGLEVEL="${LOGLEVEL:-info}" LOGLEVEL="info"
DENOM="alnt" # trace evm
TRACE="--trace"
# TRACE=""
if [ "$1" == "clean" ] || [ ! -d "$HOME/.laconicd/data/blockstore.db" ]; then if [ "$1" == "clean" ] || [ ! -d "$HOME/.laconicd/data/blockstore.db" ]; then
# validate dependencies are installed # validate dependencies are installed
command -v jq > /dev/null 2>&1 || { command -v jq > /dev/null 2>&1 || { echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/"; exit 1; }
echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/"
exit 1
}
# remove existing daemon and client # remove existing daemon and client
rm -rf $HOME/.laconicd/* rm -rf $HOME/.laconicd/*
rm -rf $HOME/.laconic/*
if [ -n "`which make`" ]; then if [ -n "`which make`" ]; then
make install make install
fi fi
laconicd config set client chain-id $CHAINID laconicd config keyring-backend $KEYRING
laconicd config set client keyring-backend $KEYRING laconicd config chain-id $CHAINID
# if $KEY exists it should be deleted # if $KEY exists it should be deleted
laconicd keys add $KEY --keyring-backend $KEYRING --algo $KEYALGO laconicd keys add $KEY --keyring-backend $KEYRING --algo $KEYALGO
# Set moniker and chain-id for Ethermint (Moniker can be anything, chain-id must be an integer) # Set moniker and chain-id for Ethermint (Moniker can be anything, chain-id must be an integer)
laconicd init $MONIKER --chain-id $CHAINID --default-denom $DENOM laconicd init $MONIKER --chain-id $CHAINID
update_genesis() { # Change parameter token denominations to aphoton
jq "$1" $HOME/.laconicd/config/genesis.json > $HOME/.laconicd/config/tmp_genesis.json && cat $HOME/.laconicd/config/genesis.json | jq '.app_state["staking"]["params"]["bond_denom"]="aphoton"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json cat $HOME/.laconicd/config/genesis.json | jq '.app_state["crisis"]["constant_fee"]["denom"]="aphoton"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
} cat $HOME/.laconicd/config/genesis.json | jq '.app_state["gov"]["deposit_params"]["min_deposit"][0]["denom"]="aphoton"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
cat $HOME/.laconicd/config/genesis.json | jq '.app_state["mint"]["params"]["mint_denom"]="aphoton"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
# Custom modules
cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["record_rent"]["denom"]="aphoton"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["authority_rent"]["denom"]="aphoton"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["authority_auction_commit_fee"]["denom"]="aphoton"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["authority_auction_reveal_fee"]["denom"]="aphoton"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["authority_auction_minimum_bid"]["denom"]="aphoton"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
if [[ "$TEST_REGISTRY_EXPIRY" == "true" ]]; then if [[ "$TEST_REGISTRY_EXPIRY" == "true" ]]; then
echo "Setting timers for expiry tests." echo "Setting timers for expiry tests."
update_genesis '.app_state["registry"]["params"]["record_rent_duration"]="60s"' cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["record_rent_duration"]="60s"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
update_genesis '.app_state["registry"]["params"]["authority_grace_period"]="60s"' cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["authority_grace_period"]="60s"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
update_genesis '.app_state["registry"]["params"]["authority_rent_duration"]="60s"' cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["authority_rent_duration"]="60s"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
fi fi
if [[ "$TEST_AUCTION_ENABLED" == "true" ]]; then if [[ "$TEST_AUCTION_ENABLED" == "true" ]]; then
echo "Enabling auction and setting timers." echo "Enabling auction and setting timers."
update_genesis '.app_state["registry"]["params"]["authority_auction_enabled"]=true' cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["authority_auction_enabled"]=true' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
update_genesis '.app_state["registry"]["params"]["authority_rent_duration"]="60s"' cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["authority_rent_duration"]="60s"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
update_genesis '.app_state["registry"]["params"]["authority_grace_period"]="300s"' cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["authority_grace_period"]="300s"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
update_genesis '.app_state["registry"]["params"]["authority_auction_commits_duration"]="60s"' cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["authority_auction_commits_duration"]="60s"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
update_genesis '.app_state["registry"]["params"]["authority_auction_reveals_duration"]="60s"' cat $HOME/.laconicd/config/genesis.json | jq '.app_state["registry"]["params"]["authority_auction_reveals_duration"]="60s"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
fi
if [[ "$ONBOARDING_ENABLED" == "true" ]]; then
echo "Enabling validator onboarding."
update_genesis '.app_state["onboarding"]["params"]["onboarding_enabled"]=true'
fi fi
# increase block time (?) # increase block time (?)
update_genesis '.consensus["params"]["block"]["time_iota_ms"]="1000"' cat $HOME/.laconicd/config/genesis.json | jq '.consensus_params["block"]["time_iota_ms"]="1000"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
# Set gas limit in genesis # Set gas limit in genesis
update_genesis '.consensus["params"]["block"]["max_gas"]="10000000"' cat $HOME/.laconicd/config/genesis.json | jq '.consensus_params["block"]["max_gas"]="10000000"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
# disable produce empty block # disable produce empty block
if [[ "$OSTYPE" == "darwin"* ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then
@ -78,6 +78,30 @@ if [ "$1" == "clean" ] || [ ! -d "$HOME/.laconicd/data/blockstore.db" ]; then
sed -i 's/create_empty_blocks = true/create_empty_blocks = false/g' $HOME/.laconicd/config/config.toml sed -i 's/create_empty_blocks = true/create_empty_blocks = false/g' $HOME/.laconicd/config/config.toml
fi fi
if [[ $1 == "pending" ]]; then
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' 's/create_empty_blocks_interval = "0s"/create_empty_blocks_interval = "30s"/g' $HOME/.laconicd/config/config.toml
sed -i '' 's/timeout_propose = "3s"/timeout_propose = "30s"/g' $HOME/.laconicd/config/config.toml
sed -i '' 's/timeout_propose_delta = "500ms"/timeout_propose_delta = "5s"/g' $HOME/.laconicd/config/config.toml
sed -i '' 's/timeout_prevote = "1s"/timeout_prevote = "10s"/g' $HOME/.laconicd/config/config.toml
sed -i '' 's/timeout_prevote_delta = "500ms"/timeout_prevote_delta = "5s"/g' $HOME/.laconicd/config/config.toml
sed -i '' 's/timeout_precommit = "1s"/timeout_precommit = "10s"/g' $HOME/.laconicd/config/config.toml
sed -i '' 's/timeout_precommit_delta = "500ms"/timeout_precommit_delta = "5s"/g' $HOME/.laconicd/config/config.toml
sed -i '' 's/timeout_commit = "5s"/timeout_commit = "150s"/g' $HOME/.laconicd/config/config.toml
sed -i '' 's/timeout_broadcast_tx_commit = "10s"/timeout_broadcast_tx_commit = "150s"/g' $HOME/.laconicd/config/config.toml
else
sed -i 's/create_empty_blocks_interval = "0s"/create_empty_blocks_interval = "30s"/g' $HOME/.laconicd/config/config.toml
sed -i 's/timeout_propose = "3s"/timeout_propose = "30s"/g' $HOME/.laconicd/config/config.toml
sed -i 's/timeout_propose_delta = "500ms"/timeout_propose_delta = "5s"/g' $HOME/.laconicd/config/config.toml
sed -i 's/timeout_prevote = "1s"/timeout_prevote = "10s"/g' $HOME/.laconicd/config/config.toml
sed -i 's/timeout_prevote_delta = "500ms"/timeout_prevote_delta = "5s"/g' $HOME/.laconicd/config/config.toml
sed -i 's/timeout_precommit = "1s"/timeout_precommit = "10s"/g' $HOME/.laconicd/config/config.toml
sed -i 's/timeout_precommit_delta = "500ms"/timeout_precommit_delta = "5s"/g' $HOME/.laconicd/config/config.toml
sed -i 's/timeout_commit = "5s"/timeout_commit = "150s"/g' $HOME/.laconicd/config/config.toml
sed -i 's/timeout_broadcast_tx_commit = "10s"/timeout_broadcast_tx_commit = "150s"/g' $HOME/.laconicd/config/config.toml
fi
fi
# Enable telemetry (prometheus metrics: http://localhost:1317/metrics?format=prometheus) # Enable telemetry (prometheus metrics: http://localhost:1317/metrics?format=prometheus)
if [[ "$OSTYPE" == "darwin"* ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' 's/enabled = false/enabled = true/g' $HOME/.laconicd/config/app.toml sed -i '' 's/enabled = false/enabled = true/g' $HOME/.laconicd/config/app.toml
@ -90,27 +114,23 @@ if [ "$1" == "clean" ] || [ ! -d "$HOME/.laconicd/data/blockstore.db" ]; then
fi fi
# Allocate genesis accounts (cosmos formatted addresses) # Allocate genesis accounts (cosmos formatted addresses)
# 10^30 alnt | 10^12 lnt laconicd add-genesis-account $KEY 100000000000000000000000000aphoton --keyring-backend $KEYRING
laconicd genesis add-genesis-account $KEY 1000000000000000000000000000000$DENOM --keyring-backend $KEYRING
# Sign genesis transaction # Sign genesis transaction
# 10^24 alnt | 10^6 lnt laconicd gentx $KEY 1000000000000000000000aphoton --keyring-backend $KEYRING --chain-id $CHAINID
laconicd genesis gentx $KEY 1000000000000000000000000$DENOM --keyring-backend $KEYRING --chain-id $CHAINID
# Collect genesis tx # Collect genesis tx
laconicd genesis collect-gentxs laconicd collect-gentxs
# Run this to ensure everything worked and that the genesis file is setup correctly # Run this to ensure everything worked and that the genesis file is setup correctly
laconicd genesis validate laconicd validate-genesis
if [[ $1 == "pending" ]]; then
echo "pending mode is on, please wait for the first block committed."
fi
else else
echo "Using existing database at $HOME/.laconicd. To replace, run '`basename $0` clean'" echo "Using existing database at $HOME/.laconicd. To replace, run '`basename $0` clean'"
fi fi
# Start the node (remove the --pruning=nothing flag if historical queries are not needed) # Start the node (remove the --pruning=nothing flag if historical queries are not needed)
laconicd start \ laconicd start --pruning=nothing --evm.tracer=json $TRACE --log_level $LOGLEVEL --minimum-gas-prices=0.0001aphoton --json-rpc.api eth,txpool,personal,net,debug,web3,miner --api.enable --gql-server --gql-playground
--pruning=nothing \
--log_level $LOGLEVEL \
--minimum-gas-prices=1$DENOM \
--api.enable \
--rpc.laddr="tcp://0.0.0.0:26657" \
--gql-server --gql-playground

View File

@ -1,9 +1,9 @@
services: services:
registry: cns:
rpcEndpoint: 'http://laconicd:26657' restEndpoint: 'http://laconicd:1317'
gqlEndpoint: 'http://laconicd:9473/api' gqlEndpoint: 'http://laconicd:9473/api'
userKey: REPLACE_WITH_MYKEY userKey: REPLACE_WITH_MYKEY
bondId: bondId:
chainId: laconic_9000-1 chainId: laconic_9000-1
gas: 350000 gas: 350000
fees: 2000000alnt fees: 200000aphoton

View File

@ -1,9 +1,9 @@
services: services:
registry: cns:
rpcEndpoint: 'http://laconicd:26657' restEndpoint: 'http://laconicd:1317'
gqlEndpoint: 'http://laconicd:9473/api' gqlEndpoint: 'http://laconicd:9473/api'
userKey: REPLACE_WITH_MYKEY userKey: REPLACE_WITH_MYKEY
bondId: bondId:
chainId: laconic_9000-1 chainId: laconic_9000-1
gas: 250000 gas: 250000
fees: 2000000alnt fees: 200000aphoton

View File

@ -1,15 +1,18 @@
#!/bin/sh #!/bin/sh
if [ -n "$CERC_SCRIPT_DEBUG" ]; then if [[ -n "$CERC_SCRIPT_DEBUG" ]]; then
set -x set -x
fi fi
#TODO: pass these in from the caller #TODO: pass these in from the caller
TRACE="--trace"
LOGLEVEL="info" LOGLEVEL="info"
laconicd start \ laconicd start \
--pruning=nothing \ --pruning=nothing \
--evm.tracer=json $TRACE \
--log_level $LOGLEVEL \ --log_level $LOGLEVEL \
--minimum-gas-prices=1alnt \ --minimum-gas-prices=0.0001aphoton \
--json-rpc.api eth,txpool,personal,net,debug,web3,miner \
--api.enable \ --api.enable \
--gql-server \ --gql-server \
--gql-playground --gql-playground

View File

@ -18,7 +18,7 @@
"editable": true, "editable": true,
"fiscalYearStartMonth": 0, "fiscalYearStartMonth": 0,
"graphTooltip": 0, "graphTooltip": 0,
"id": 39, "id": 6,
"links": [], "links": [],
"liveNow": false, "liveNow": false,
"panels": [ "panels": [
@ -1202,640 +1202,6 @@
], ],
"title": "ETH RPC failed requests rate", "title": "ETH RPC failed requests rate",
"type": "timeseries" "type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"gridPos": {
"h": 2,
"w": 24,
"x": 0,
"y": 22
},
"id": 32,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"content": "<center style=\"margin-top: 1rem\"> \n <h1>GraphQL Server</h1> \n</center>",
"mode": "html"
},
"pluginVersion": "10.2.3",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"refId": "A"
}
],
"transparent": true,
"type": "text"
},
{
"cards": {},
"color": {
"cardColor": "#b4ff00",
"colorScale": "sqrt",
"colorScheme": "interpolatePlasma",
"exponent": 0.5,
"mode": "spectrum"
},
"dataFormat": "tsbuckets",
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"scaleDistribution": {
"type": "linear"
}
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 6,
"x": 0,
"y": 24
},
"heatmap": {},
"hideZeroBuckets": true,
"highlightCards": true,
"id": 29,
"legend": {
"show": false
},
"links": [],
"options": {
"calculate": false,
"calculation": {},
"cellGap": 2,
"cellValues": {},
"color": {
"exponent": 0.5,
"fill": "#b4ff00",
"mode": "scheme",
"reverse": false,
"scale": "exponential",
"scheme": "Plasma",
"steps": 128
},
"exemplars": {
"color": "rgba(255,0,255,0.7)"
},
"filterValues": {
"le": 1e-9
},
"legend": {
"show": false
},
"rowsFrame": {
"layout": "auto"
},
"showValue": "never",
"tooltip": {
"show": true,
"showColorScale": false,
"yHistogram": false
},
"yAxis": {
"axisPlacement": "left",
"reverse": false,
"unit": "short"
}
},
"pluginVersion": "10.2.3",
"repeatDirection": "v",
"reverseYBuckets": false,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "sum(increase(query_execution_time_bucket{deployment=\"[[subgraph_hash]]\"}[1m])) by (le)",
"format": "heatmap",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{le}}",
"refId": "A"
}
],
"title": "Query Execution Time Histogram ([[subgraph_name]])",
"tooltip": {
"show": true,
"showHistogram": false
},
"type": "heatmap",
"xAxis": {
"show": true
},
"yAxis": {
"format": "short",
"logBase": 1,
"show": true
},
"yBucketBound": "auto"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineWidth": 1
},
"links": [],
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 6,
"x": 6,
"y": 24
},
"id": 26,
"links": [],
"options": {
"bucketOffset": 0,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
}
},
"pluginVersion": "10.2.3",
"repeat": "subgraph_hash",
"repeatDirection": "v",
"targets": [
{
"datasource": {
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum(increase(query_execution_time_bucket{deployment=\"[[subgraph_hash]]\"}[1m])) by (le)",
"format": "heatmap",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{le}}",
"refId": "A"
}
],
"title": "Query Execution Time Histogram ([[subgraph_name]])",
"type": "histogram"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"links": [],
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "locale"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 24
},
"id": 36,
"links": [],
"options": {
"legend": {
"calcs": [],
"displayMode": "table",
"placement": "bottom",
"showLegend": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"pluginVersion": "10.2.3",
"repeatDirection": "v",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"disableTextWrap": false,
"editorMode": "builder",
"exemplar": false,
"expr": "sum(increase(query_execution_time_count{deployment=\"$subgraph_hash\"}[1m]))",
"format": "time_series",
"fullMetaSearch": false,
"includeNullMetadata": true,
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{subgraph_deployment}}",
"range": true,
"refId": "A",
"useBackend": false
}
],
"title": "Queries ([[subgraph_name]])",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "Queries per minute per minute",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"links": [],
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "locale"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 32
},
"id": 27,
"links": [],
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"pluginVersion": "10.2.3",
"repeatDirection": "v",
"targets": [
{
"datasource": {
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum(rate(query_execution_time_count[1m])) by (deployment)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{deployment}}",
"refId": "A"
}
],
"title": "Query Rate (All Subgraphs)",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 80,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"links": [],
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "locale"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 32
},
"id": 67,
"links": [],
"options": {
"legend": {
"calcs": [
"sum"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"pluginVersion": "10.2.3",
"repeatDirection": "v",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "sum(increase(query_execution_time_count[1m])) by (deployment)",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{deployment}}",
"refId": "A"
}
],
"title": "Queries (All Subgraphs)",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 70,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": true,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"links": [],
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "locale"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 40
},
"id": 41,
"links": [],
"options": {
"legend": {
"calcs": [],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"pluginVersion": "10.2.3",
"repeatDirection": "v",
"targets": [
{
"datasource": {
"uid": "prometheus"
},
"editorMode": "code",
"expr": "increase(query_execution_time_count{status=\"failed\"}[1m])",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"legendFormat": "{{deployment}}",
"refId": "A"
}
],
"title": "Failed Queries (All Subgraphs)",
"type": "timeseries"
} }
], ],
"refresh": "10s", "refresh": "10s",

View File

@ -18,7 +18,7 @@
"editable": true, "editable": true,
"fiscalYearStartMonth": 0, "fiscalYearStartMonth": 0,
"graphTooltip": 0, "graphTooltip": 0,
"id": 2, "id": 4,
"links": [ "links": [
{ {
"asDropdown": false, "asDropdown": false,
@ -282,7 +282,7 @@
}, },
"gridPos": { "gridPos": {
"h": 3, "h": 3,
"w": 3, "w": 4,
"x": 0, "x": 0,
"y": 4 "y": 4
}, },
@ -303,75 +303,6 @@
"wideLayout": true "wideLayout": true
}, },
"pluginVersion": "10.2.3", "pluginVersion": "10.2.3",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"disableTextWrap": false,
"editorMode": "code",
"expr": "sync_status_block_number{job=~\"$job\", instance=~\"$watcher\", kind=\"latest_processed\"}",
"fullMetaSearch": false,
"hide": false,
"includeNullMetadata": true,
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "B",
"useBackend": false
}
],
"title": "Event processed block",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "blue",
"mode": "shades"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 3,
"x": 3,
"y": 4
},
"id": 38,
"options": {
"colorMode": "background",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.2.3",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@ -391,7 +322,7 @@
"useBackend": false "useBackend": false
} }
], ],
"title": "Inserted processed block", "title": "Latest indexed block",
"type": "stat" "type": "stat"
}, },
{ {
@ -421,8 +352,8 @@
}, },
"gridPos": { "gridPos": {
"h": 3, "h": 3,
"w": 3, "w": 4,
"x": 6, "x": 4,
"y": 4 "y": 4
}, },
"id": 11, "id": 11,
@ -490,8 +421,8 @@
}, },
"gridPos": { "gridPos": {
"h": 3, "h": 3,
"w": 3, "w": 4,
"x": 9, "x": 8,
"y": 4 "y": 4
}, },
"id": 12, "id": 12,
@ -708,7 +639,7 @@
"uid": "PBFA97CFB590B2093" "uid": "PBFA97CFB590B2093"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "sync_status_block_number{job=~\"$job\", instance=~\"$watcher\", kind=\"latest_processed\"}", "expr": "sync_status_block_number{job=~\"$job\", instance=~\"$watcher\", kind=\"latest_indexed\"}",
"hide": false, "hide": false,
"instant": false, "instant": false,
"legendFormat": "{{__name__}}", "legendFormat": "{{__name__}}",
@ -819,7 +750,7 @@
"uid": "PBFA97CFB590B2093" "uid": "PBFA97CFB590B2093"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "sync_status_block_number{job=~\"$job\", instance=~\"$watcher\", kind=\"latest_processed\"}", "expr": "sync_status_block_number{job=~\"$job\", instance=~\"$watcher\", kind=\"latest_indexed\"}",
"hide": false, "hide": false,
"instant": false, "instant": false,
"legendFormat": "{{__name__}}", "legendFormat": "{{__name__}}",
@ -1005,12 +936,12 @@
"uid": "PBFA97CFB590B2093" "uid": "PBFA97CFB590B2093"
}, },
"disableTextWrap": false, "disableTextWrap": false,
"editorMode": "code", "editorMode": "builder",
"expr": "sync_status_block_number{job=~\"$job\", instance=~\"$watcher\", kind=\"latest_processed\"}", "expr": "last_processed_block_number{job=~\"$job\", instance=~\"$watcher\"}",
"fullMetaSearch": false, "fullMetaSearch": false,
"includeNullMetadata": true, "includeNullMetadata": true,
"instant": false, "instant": false,
"legendFormat": "{{kind}}", "legendFormat": "latest_processed",
"range": true, "range": true,
"refId": "A", "refId": "A",
"useBackend": false "useBackend": false
@ -1021,7 +952,7 @@
"uid": "PBFA97CFB590B2093" "uid": "PBFA97CFB590B2093"
}, },
"disableTextWrap": false, "disableTextWrap": false,
"editorMode": "code", "editorMode": "builder",
"expr": "sync_status_block_number{job=~\"$job\", instance=~\"$watcher\", kind=\"latest_canonical\"}", "expr": "sync_status_block_number{job=~\"$job\", instance=~\"$watcher\", kind=\"latest_canonical\"}",
"fullMetaSearch": false, "fullMetaSearch": false,
"hide": false, "hide": false,
@ -1464,7 +1395,7 @@
"uid": "PBFA97CFB590B2093" "uid": "PBFA97CFB590B2093"
}, },
"editorMode": "code", "editorMode": "code",
"expr": "sync_status_block_number{job=~\"$job\", instance=~\"$watcher\", kind=\"latest_processed\"}", "expr": "sync_status_block_number{job=~\"$job\", instance=~\"$watcher\", kind=\"latest_indexed\"}",
"hide": false, "hide": false,
"instant": false, "instant": false,
"legendFormat": "{{__name__}}", "legendFormat": "{{__name__}}",
@ -3284,6 +3215,6 @@
"timepicker": {}, "timepicker": {},
"timezone": "", "timezone": "",
"title": "Watchers", "title": "Watchers",
"version": 5, "version": 4,
"weekStart": "" "weekStart": ""
} }

View File

@ -849,81 +849,3 @@ groups:
annotations: annotations:
summary: Watcher {{ index $labels "instance" }} of group {{ index $labels "job" }} is falling behind external head by {{ index $values "diff" }} summary: Watcher {{ index $labels "instance" }} of group {{ index $labels "job" }} is falling behind external head by {{ index $values "diff" }}
isPaused: false isPaused: false
# Secured Finance
- uid: secured_finance_diff_external
title: secured_finance_watcher_head_tracking
condition: condition
data:
- refId: diff
relativeTimeRange:
from: 600
to: 0
datasourceUid: PBFA97CFB590B2093
model:
datasource:
type: prometheus
uid: PBFA97CFB590B2093
disableTextWrap: false
editorMode: code
expr: latest_block_number{instance="external"} - on(chain) group_right sync_status_block_number{job="secured-finance", instance="secured-finance", kind="latest_indexed"}
fullMetaSearch: false
includeNullMetadata: true
instant: true
intervalMs: 1000
legendFormat: __auto
maxDataPoints: 43200
range: false
refId: diff
useBackend: false
- refId: latest_external
relativeTimeRange:
from: 600
to: 0
datasourceUid: PBFA97CFB590B2093
model:
datasource:
type: prometheus
uid: PBFA97CFB590B2093
editorMode: code
expr: latest_block_number{chain="filecoin"}
hide: false
instant: true
legendFormat: __auto
range: false
refId: latest_external
- refId: condition
relativeTimeRange:
from: 600
to: 0
datasourceUid: __expr__
model:
conditions:
- evaluator:
params:
- 0
- 0
type: gt
operator:
type: and
query:
params: []
reducer:
params: []
type: avg
type: query
datasource:
name: Expression
type: __expr__
uid: __expr__
expression: ${diff} >= 16
intervalMs: 1000
maxDataPoints: 43200
refId: condition
type: math
noDataState: Alerting
execErrState: Alerting
for: 15m
annotations:
summary: Watcher {{ index $labels "instance" }} of group {{ index $labels "job" }} is falling behind external head by {{ index $values "diff" }}
isPaused: false

View File

@ -2,6 +2,7 @@
host = "0.0.0.0" host = "0.0.0.0"
port = 3008 port = 3008
kind = "active" kind = "active"
gqlPath = "/"
# Checkpointing state. # Checkpointing state.
checkpointing = true checkpointing = true
@ -21,30 +22,23 @@
# Interval in number of blocks at which to clear entities cache. # Interval in number of blocks at which to clear entities cache.
clearEntitiesCacheInterval = 1000 clearEntitiesCacheInterval = 1000
# Max block range for which to return events in eventsInRange GQL query.
# Use -1 for skipping check on block range.
maxEventsBlockRange = 1000
# Flag to specify whether RPC endpoint supports block hash as block tag parameter # Flag to specify whether RPC endpoint supports block hash as block tag parameter
rpcSupportsBlockHashParam = false rpcSupportsBlockHashParam = false
# Server GQL config # GQL cache settings
[server.gql] [server.gqlCache]
path = "/" enabled = true
# Max block range for which to return events in eventsInRange GQL query. # Max in-memory cache size (in bytes) (default 8 MB)
# Use -1 for skipping check on block range. # maxCacheSize
maxEventsBlockRange = 1000
# Log directory for GQL requests # GQL cache-control max-age settings (in seconds)
logDir = "./gql-logs" maxAge = 15
timeTravelMaxAge = 86400 # 1 day
# GQL cache settings
[server.gql.cache]
enabled = true
# Max in-memory cache size (in bytes) (default 8 MB)
# maxCacheSize
# GQL cache-control max-age settings (in seconds)
maxAge = 15
timeTravelMaxAge = 86400 # 1 day
[metrics] [metrics]
host = "0.0.0.0" host = "0.0.0.0"
@ -91,9 +85,6 @@
# Filecoin block time: https://docs.filecoin.io/basics/the-blockchain/blocks-and-tipsets#blocktime # Filecoin block time: https://docs.filecoin.io/basics/the-blockchain/blocks-and-tipsets#blocktime
blockDelayInMilliSecs = 30000 blockDelayInMilliSecs = 30000
# Number of blocks by which block processing lags behind head
blockProcessingOffset = 0
# Boolean to switch between modes of processing events when starting the server. # Boolean to switch between modes of processing events when starting the server.
# Setting to true will fetch filtered events and required blocks in a range of blocks and then process them. # Setting to true will fetch filtered events and required blocks in a range of blocks and then process them.
# Setting to false will fetch blocks consecutively with its events and then process them (Behaviour is followed in realtime processing near head). # Setting to false will fetch blocks consecutively with its events and then process them (Behaviour is followed in realtime processing near head).

View File

@ -1,7 +1,6 @@
[server] [server]
host = "0.0.0.0" host = "0.0.0.0"
[server.gql] maxSimultaneousRequests = -1
maxSimultaneousRequests = -1
[metrics] [metrics]
host = "0.0.0.0" host = "0.0.0.0"

View File

@ -2,6 +2,7 @@
host = "0.0.0.0" host = "0.0.0.0"
port = 3008 port = 3008
kind = "active" kind = "active"
gqlPath = '/'
# Checkpointing state. # Checkpointing state.
checkpointing = true checkpointing = true
@ -21,30 +22,23 @@
# Interval in number of blocks at which to clear entities cache. # Interval in number of blocks at which to clear entities cache.
clearEntitiesCacheInterval = 1000 clearEntitiesCacheInterval = 1000
# Max block range for which to return events in eventsInRange GQL query.
# Use -1 for skipping check on block range.
maxEventsBlockRange = 1000
# Flag to specify whether RPC endpoint supports block hash as block tag parameter # Flag to specify whether RPC endpoint supports block hash as block tag parameter
rpcSupportsBlockHashParam = false rpcSupportsBlockHashParam = false
# Server GQL config # GQL cache settings
[server.gql] [server.gqlCache]
path = "/" enabled = true
# Max block range for which to return events in eventsInRange GQL query. # Max in-memory cache size (in bytes) (default 8 MB)
# Use -1 for skipping check on block range. # maxCacheSize
maxEventsBlockRange = 1000
# Log directory for GQL requests # GQL cache-control max-age settings (in seconds)
logDir = "./gql-logs" maxAge = 15
timeTravelMaxAge = 86400 # 1 day
# GQL cache settings
[server.gql.cache]
enabled = true
# Max in-memory cache size (in bytes) (default 8 MB)
# maxCacheSize
# GQL cache-control max-age settings (in seconds)
maxAge = 15
timeTravelMaxAge = 86400 # 1 day
[metrics] [metrics]
host = "0.0.0.0" host = "0.0.0.0"
@ -91,9 +85,6 @@
# Filecoin block time: https://docs.filecoin.io/basics/the-blockchain/blocks-and-tipsets#blocktime # Filecoin block time: https://docs.filecoin.io/basics/the-blockchain/blocks-and-tipsets#blocktime
blockDelayInMilliSecs = 30000 blockDelayInMilliSecs = 30000
# Number of blocks by which block processing lags behind head
blockProcessingOffset = 0
# Boolean to switch between modes of processing events when starting the server. # Boolean to switch between modes of processing events when starting the server.
# Setting to true will fetch filtered events and required blocks in a range of blocks and then process them. # Setting to true will fetch filtered events and required blocks in a range of blocks and then process them.
# Setting to false will fetch blocks consecutively with its events and then process them (Behaviour is followed in realtime processing near head). # Setting to false will fetch blocks consecutively with its events and then process them (Behaviour is followed in realtime processing near head).

View File

@ -2,6 +2,7 @@
host = "0.0.0.0" host = "0.0.0.0"
port = 3008 port = 3008
kind = "active" kind = "active"
gqlPath = "/"
# Checkpointing state. # Checkpointing state.
checkpointing = true checkpointing = true
@ -21,30 +22,23 @@
# Interval in number of blocks at which to clear entities cache. # Interval in number of blocks at which to clear entities cache.
clearEntitiesCacheInterval = 1000 clearEntitiesCacheInterval = 1000
# Max block range for which to return events in eventsInRange GQL query.
# Use -1 for skipping check on block range.
maxEventsBlockRange = 1000
# Flag to specify whether RPC endpoint supports block hash as block tag parameter # Flag to specify whether RPC endpoint supports block hash as block tag parameter
rpcSupportsBlockHashParam = false rpcSupportsBlockHashParam = false
# Server GQL config # GQL cache settings
[server.gql] [server.gqlCache]
path = "/" enabled = true
# Max block range for which to return events in eventsInRange GQL query. # Max in-memory cache size (in bytes) (default 8 MB)
# Use -1 for skipping check on block range. # maxCacheSize
maxEventsBlockRange = 1000
# Log directory for GQL requests # GQL cache-control max-age settings (in seconds)
logDir = "./gql-logs" maxAge = 15
timeTravelMaxAge = 86400 # 1 day
# GQL cache settings
[server.gql.cache]
enabled = true
# Max in-memory cache size (in bytes) (default 8 MB)
# maxCacheSize
# GQL cache-control max-age settings (in seconds)
maxAge = 15
timeTravelMaxAge = 86400 # 1 day
[metrics] [metrics]
host = "0.0.0.0" host = "0.0.0.0"
@ -91,9 +85,6 @@
# Filecoin block time: https://docs.filecoin.io/basics/the-blockchain/blocks-and-tipsets#blocktime # Filecoin block time: https://docs.filecoin.io/basics/the-blockchain/blocks-and-tipsets#blocktime
blockDelayInMilliSecs = 30000 blockDelayInMilliSecs = 30000
# Number of blocks by which block processing lags behind head
blockProcessingOffset = 0
# Boolean to switch between modes of processing events when starting the server. # Boolean to switch between modes of processing events when starting the server.
# Setting to true will fetch filtered events and required blocks in a range of blocks and then process them. # Setting to true will fetch filtered events and required blocks in a range of blocks and then process them.
# Setting to false will fetch blocks consecutively with its events and then process them (Behaviour is followed in realtime processing near head). # Setting to false will fetch blocks consecutively with its events and then process them (Behaviour is followed in realtime processing near head).

View File

@ -68,5 +68,5 @@ ENV PATH="${PATH}:/scripts"
COPY entrypoint.sh . COPY entrypoint.sh .
ENTRYPOINT ["./entrypoint.sh"] ENTRYPOINT ["./entrypoint.sh"]
# Placeholder CMD : generally this will be overridden at run time like : # Placeholder CMD : generally this will be overridden at run time like :
# docker run -it -v /home/builder/cerc/registry-sdk:/workspace cerc/builder-js sh -c 'cd /workspace && yarn && yarn build' # docker run -it -v /home/builder/cerc/laconic-sdk:/workspace cerc/builder-js sh -c 'cd /workspace && yarn && yarn build'
CMD node --version CMD node --version

View File

@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Build cerc/laconic-console-host # Build cerc/laconic-registry-cli
source ${CERC_CONTAINER_BASE_DIR}/build-base.sh source ${CERC_CONTAINER_BASE_DIR}/build-base.sh

View File

@ -5,16 +5,16 @@ if [ -n "$CERC_SCRIPT_DEBUG" ]; then
set -x set -x
fi fi
registry_command="laconic registry" registry_command="laconic cns"
demo_records_dir="scripts/demo-records" demo_records_dir="scripts/demo-records"
# Check we have funds # Check we have funds
funds_response=$(${registry_command} account get --address $(cat my-address.txt)) funds_response=$(${registry_command} account get --address $(cat my-address.txt))
funds_balance=$(echo ${funds_response} | jq -r ".[0].balance[0].quantity") funds_balance=$(echo ${funds_response} | jq -r .[0].balance[0].quantity)
echo "Balance is: ${funds_balance}" echo "Balance is: ${funds_balance}"
# Create a bond # Create a bond
bond_create_result=$(${registry_command} bond create --type alnt --quantity 1000000000) bond_create_result=$(${registry_command} bond create --type aphoton --quantity 1000000000)
bond_id=$(echo ${bond_create_result} | jq -r .bondId) bond_id=$(echo ${bond_create_result} | jq -r .bondId)
echo "Created bond with id: ${bond_id}" echo "Created bond with id: ${bond_id}"

View File

@ -7,9 +7,9 @@ record:
env: env:
ENV_VAR_A: A ENV_VAR_A: A
ENV_VAR_B: B ENV_VAR_B: B
lrn: crn:
- lrn://foo.bar - crn://foo.bar
- lrn://bar.baz - crn://bar.baz
meta: meta:
foo: bar foo: bar
tags: tags:

View File

@ -28,8 +28,6 @@ RUN \
&& su ${USERNAME} -c "umask 0002 && npm install -g semver" \ && su ${USERNAME} -c "umask 0002 && npm install -g semver" \
# Install pnpm # Install pnpm
&& su ${USERNAME} -c "umask 0002 && npm install -g pnpm" \ && su ${USERNAME} -c "umask 0002 && npm install -g pnpm" \
# Install bun
&& su ${USERNAME} -c "umask 0002 && npm install -g bun@1.1.x" \
&& npm cache clean --force > /dev/null 2>&1 && npm cache clean --force > /dev/null 2>&1
# [Optional] Uncomment this section to install additional OS packages. # [Optional] Uncomment this section to install additional OS packages.

View File

@ -14,8 +14,6 @@ if [ -z "$CERC_BUILD_TOOL" ]; then
CERC_BUILD_TOOL=pnpm CERC_BUILD_TOOL=pnpm
elif [ -f "yarn.lock" ]; then elif [ -f "yarn.lock" ]; then
CERC_BUILD_TOOL=yarn CERC_BUILD_TOOL=yarn
elif [ -f "bun.lockb" ]; then
CERC_BUILD_TOOL=bun
else else
CERC_BUILD_TOOL=npm CERC_BUILD_TOOL=npm
fi fi

View File

@ -5,19 +5,14 @@ if [ -n "$CERC_SCRIPT_DEBUG" ]; then
fi fi
CERC_MIN_NEXTVER=13.4.2 CERC_MIN_NEXTVER=13.4.2
CERC_DEFAULT_WEBPACK_VER="5.93.0"
CERC_NEXT_VERSION="${CERC_NEXT_VERSION:-keep}" CERC_NEXT_VERSION="${CERC_NEXT_VERSION:-keep}"
CERC_WEBPACK_VERSION="${CERC_WEBPACK_VERSION:-keep}"
CERC_BUILD_TOOL="${CERC_BUILD_TOOL}" CERC_BUILD_TOOL="${CERC_BUILD_TOOL}"
if [ -z "$CERC_BUILD_TOOL" ]; then if [ -z "$CERC_BUILD_TOOL" ]; then
if [ -f "pnpm-lock.yaml" ]; then if [ -f "pnpm-lock.yaml" ]; then
CERC_BUILD_TOOL=pnpm CERC_BUILD_TOOL=pnpm
elif [ -f "yarn.lock" ]; then elif [ -f "yarn.lock" ]; then
CERC_BUILD_TOOL=yarn CERC_BUILD_TOOL=yarn
elif [ -f "bun.lockb" ]; then
CERC_BUILD_TOOL=bun
else else
CERC_BUILD_TOOL=npm CERC_BUILD_TOOL=npm
fi fi
@ -28,21 +23,13 @@ WORK_DIR="${1:-/app}"
cd "${WORK_DIR}" || exit 1 cd "${WORK_DIR}" || exit 1
if [ -f "next.config.mjs" ]; then
NEXT_CONFIG_JS="next.config.mjs"
IMPORT_OR_REQUIRE="import"
else
NEXT_CONFIG_JS="next.config.js"
IMPORT_OR_REQUIRE="require"
fi
# If this file doesn't exist at all, we'll get errors below. # If this file doesn't exist at all, we'll get errors below.
if [ ! -f "${NEXT_CONFIG_JS}" ]; then if [ ! -f "next.config.js" ]; then
touch ${NEXT_CONFIG_JS} touch next.config.js
fi fi
if [ ! -f "next.config.dist" ]; then if [ ! -f "next.config.dist" ]; then
cp $NEXT_CONFIG_JS next.config.dist cp next.config.js next.config.dist
fi fi
which js-beautify >/dev/null which js-beautify >/dev/null
@ -50,34 +37,17 @@ if [ $? -ne 0 ]; then
npm i -g js-beautify npm i -g js-beautify
fi fi
# js-beautify formats NEXTJS_CONFIG_FILE (ie next.config.js / next.config.mjs) so we can reliably transformable later js-beautify next.config.dist > next.config.js
js-beautify next.config.dist > ${NEXT_CONFIG_JS} echo "" >> next.config.js
echo "" >> ${NEXT_CONFIG_JS}
if [ "${IMPORT_OR_REQUIRE}" == "require" ]; then WEBPACK_REQ_LINE=$(grep -n "require([\'\"]webpack[\'\"])" next.config.js | cut -d':' -f1)
WEBPACK_REQ_LINE=$(grep -n "require([\'\"]webpack[\'\"])" ${NEXT_CONFIG_JS} | cut -d':' -f1) if [ -z "$WEBPACK_REQ_LINE" ]; then
if [ -z "$WEBPACK_REQ_LINE" ]; then cat > next.config.js.0 <<EOF
cat > ${NEXT_CONFIG_JS}.0 <<EOF
const webpack = require('webpack'); const webpack = require('webpack');
EOF EOF
fi
else
WEBPACK_IMPORT_LINE=$(grep -n "^import .*[\'\"]webpack[\'\"];?$" ${NEXT_CONFIG_JS} | cut -d':' -f1)
if [ -z "$WEBPACK_IMPORT_LINE" ]; then
cat > ${NEXT_CONFIG_JS}.0 <<EOF
import webpack from 'webpack';
EOF
fi
CREATE_REQUIRE_LINE=$(grep -n "require = createRequire" ${NEXT_CONFIG_JS} | cut -d':' -f1)
if [ -z "$CREATE_REQUIRE_LINE" ]; then
cat >> ${NEXT_CONFIG_JS}.0 <<EOF
import { createRequire } from "module";
const require = createRequire(import.meta.url);
EOF
fi
fi fi
cat > ${NEXT_CONFIG_JS}.1 <<EOF cat > next.config.js.1 <<EOF
let envMap; let envMap;
try { try {
// .env-list.json provides us a list of identifiers which should be replaced at runtime. // .env-list.json provides us a list of identifiers which should be replaced at runtime.
@ -85,8 +55,7 @@ try {
a[v] = \`"CERC_RUNTIME_ENV_\${v.split(/\./).pop()}"\`; a[v] = \`"CERC_RUNTIME_ENV_\${v.split(/\./).pop()}"\`;
return a; return a;
}, {}); }, {});
} catch (e) { } catch {
console.error(e);
// If .env-list.json cannot be loaded, we are probably running in dev mode, so use process.env instead. // If .env-list.json cannot be loaded, we are probably running in dev mode, so use process.env instead.
envMap = Object.keys(process.env).reduce((a, v) => { envMap = Object.keys(process.env).reduce((a, v) => {
if (v.startsWith('CERC_')) { if (v.startsWith('CERC_')) {
@ -95,80 +64,40 @@ try {
return a; return a;
}, {}); }, {});
} }
console.log(envMap);
EOF EOF
grep 'withPWA' ${NEXT_CONFIG_JS} >/dev/null && HAS_WITHPWA=true || HAS_WITHPWA=false CONFIG_LINES=$(wc -l next.config.js | awk '{ print $1 }')
ENV_LINE=$(grep -n 'env:' next.config.js | cut -d':' -f1)
WEBPACK_CONF_LINE=$(egrep -n 'webpack:\s+\([^,]+,' next.config.js | cut -d':' -f1)
NEXT_SECTION_ADJUSTMENT=0
if [ "$HAS_WITHPWA" == "true" ]; then if [ -n "$WEBPACK_CONF_LINE" ]; then
if [ "$IMPORT_OR_REQUIRE" == "import" ]; then WEBPACK_CONF_VAR=$(egrep -n 'webpack:\s+\([^,]+,' next.config.js | cut -d',' -f1 | cut -d'(' -f2)
cat > ${NEXT_CONFIG_JS}.2 <<EOF head -$(( ${WEBPACK_CONF_LINE} )) next.config.js > next.config.js.2
const __xPWA__ = (p) => { cat > next.config.js.3 <<EOF
const realPWA = withPWA(p); $WEBPACK_CONF_VAR.plugins.push(new webpack.DefinePlugin(envMap));
return (nextConfig) => { EOF
const modConfig = {...nextConfig}; NEXT_SECTION_LINE=$((WEBPACK_CONF_LINE))
elif [ -n "$ENV_LINE" ]; then
modConfig.webpack = (config) => { head -$(( ${ENV_LINE} - 1 )) next.config.js > next.config.js.2
cat > next.config.js.3 <<EOF
webpack: (config) => {
config.plugins.push(new webpack.DefinePlugin(envMap)); config.plugins.push(new webpack.DefinePlugin(envMap));
return nextConfig.webpack ? nextConfig.webpack(config) : config; return config;
}; },
return realPWA(modConfig);
};
};
EOF EOF
else NEXT_SECTION_ADJUSTMENT=1
cat > ${NEXT_CONFIG_JS}.3 <<EOF NEXT_SECTION_LINE=$ENV_LINE
const __xPWA__ = (nextConfig) => {
const modConfig = {...nextConfig};
modConfig.webpack = (config) => {
config.plugins.push(new webpack.DefinePlugin(envMap));
return nextConfig.webpack ? nextConfig.webpack(config) : config;
};
return withPWA(modConfig);
};
EOF
fi
cat ${NEXT_CONFIG_JS} | js-beautify | sed 's/withPWA(/__xPWA__(/g' > ${NEXT_CONFIG_JS}.4
else else
cat > ${NEXT_CONFIG_JS}.3 <<EOF echo "WARNING: Cannot find location to insert environment variable map in next.config.js" 1>&2
const __xCfg__ = (nextConfig) => { rm -f next.config.js.*
const modConfig = {...nextConfig}; NEXT_SECTION_LINE=0
modConfig.webpack = (config) => {
config.plugins.push(new webpack.DefinePlugin(envMap));
return nextConfig.webpack ? nextConfig.webpack(config) : config;
};
return modConfig;
};
EOF
if [ "$IMPORT_OR_REQUIRE" == "import" ]; then
cat ${NEXT_CONFIG_JS} | js-beautify | sed 's/export\s\+default\s\+/const __orig_cfg__ = /g' > ${NEXT_CONFIG_JS}.4
echo "export default __xCfg__(__orig_cfg__);" > ${NEXT_CONFIG_JS}.5
else
cat ${NEXT_CONFIG_JS} | js-beautify | sed 's/module.exports\s\+=\s\+/const __orig_cfg__ = /g' > ${NEXT_CONFIG_JS}.4
echo "module.exports = __xCfg__(__orig_cfg__);" > ${NEXT_CONFIG_JS}.5
fi
fi fi
tail -$(( ${CONFIG_LINES} - ${NEXT_SECTION_LINE} + ${NEXT_SECTION_ADJUSTMENT} )) next.config.js > next.config.js.5
rm -f ${NEXT_CONFIG_JS} cat next.config.js.* | sed 's/^ *//g' | js-beautify | grep -v 'process\.\env\.' | js-beautify > next.config.js
for ((i=0; i <= 10; i++)); do rm next.config.js.*
if [ -s "${NEXT_CONFIG_JS}.${i}" ]; then
if [ $i -le 2 ] ; then
cat ${NEXT_CONFIG_JS}.${i} >> ${NEXT_CONFIG_JS}
else
cat ${NEXT_CONFIG_JS}.${i} | sed 's/^ *//g' | js-beautify | grep -v 'process\.\env\.' | js-beautify >> ${NEXT_CONFIG_JS}
fi
fi
done
rm ${NEXT_CONFIG_JS}.*
cat ${NEXT_CONFIG_JS} | js-beautify > ${NEXT_CONFIG_JS}.pretty
mv ${NEXT_CONFIG_JS}.pretty ${NEXT_CONFIG_JS}
"${SCRIPT_DIR}/find-env.sh" "$(pwd)" > .env-list.json "${SCRIPT_DIR}/find-env.sh" "$(pwd)" > .env-list.json
@ -176,6 +105,8 @@ if [ ! -f "package.dist" ]; then
cp package.json package.dist cp package.json package.dist
fi fi
cat package.dist | jq '.scripts.cerc_compile = "next experimental-compile"' | jq '.scripts.cerc_generate = "next experimental-generate"' > package.json
CUR_NEXT_VERSION="`jq -r '.dependencies.next' package.json`" CUR_NEXT_VERSION="`jq -r '.dependencies.next' package.json`"
if [ "$CERC_NEXT_VERSION" != "keep" ] && [ "$CUR_NEXT_VERSION" != "$CERC_NEXT_VERSION" ]; then if [ "$CERC_NEXT_VERSION" != "keep" ] && [ "$CUR_NEXT_VERSION" != "$CERC_NEXT_VERSION" ]; then
@ -184,38 +115,10 @@ if [ "$CERC_NEXT_VERSION" != "keep" ] && [ "$CUR_NEXT_VERSION" != "$CERC_NEXT_VE
mv package.json.$$ package.json mv package.json.$$ package.json
fi fi
CUR_WEBPACK_VERSION="`jq -r '.dependencies.webpack' package.json`"
if [ -z "$CUR_WEBPACK_VERSION" ]; then
CUR_WEBPACK_VERSION="`jq -r '.devDependencies.webpack' package.json`"
fi
if [ "${CERC_WEBPACK_VERSION}" != "keep" ] || [ "${CUR_WEBPACK_VERSION}" == "null" ]; then
if [ -z "$CERC_WEBPACK_VERSION" ] || [ "$CERC_WEBPACK_VERSION" == "keep" ]; then
CERC_WEBPACK_VERSION="${CERC_DEFAULT_WEBPACK_VER}"
fi
echo "Webpack is required for env variable substitution. Adding to webpack@$CERC_WEBPACK_VERSION to dependencies..." 1>&2
cat package.json | jq ".dependencies.webpack = \"$CERC_WEBPACK_VERSION\"" > package.json.$$
mv package.json.$$ package.json
fi
time $CERC_BUILD_TOOL install || exit 1 time $CERC_BUILD_TOOL install || exit 1
CUR_NEXT_VERSION=`jq -r '.version' node_modules/next/package.json` CUR_NEXT_VERSION=`jq -r '.version' node_modules/next/package.json`
# See https://github.com/vercel/next.js/discussions/46544
semver -p -r ">=14.2.0" "$CUR_NEXT_VERSION"
if [ $? -eq 0 ]; then
# For >= 14.2.0
CERC_NEXT_COMPILE_COMMAND="next build --experimental-build-mode compile"
CERC_NEXT_GENERATE_COMMAND="next build --experimental-build-mode generate"
else
# For 13.4.2 to 14.1.x
CERC_NEXT_COMPILE_COMMAND="next experimental-compile"
CERC_NEXT_GENERATE_COMMAND="next experimental-generate"
fi
cat package.json | jq ".scripts.cerc_compile = \"$CERC_NEXT_COMPILE_COMMAND\"" | jq ".scripts.cerc_generate = \"$CERC_NEXT_GENERATE_COMMAND\"" > package.json.$$
mv package.json.$$ package.json
semver -p -r ">=$CERC_MIN_NEXTVER" $CUR_NEXT_VERSION semver -p -r ">=$CERC_MIN_NEXTVER" $CUR_NEXT_VERSION
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
cat <<EOF cat <<EOF

View File

@ -20,11 +20,9 @@ for d in $(find . -maxdepth 1 -type d | grep -v '\./\.' | grep '/' | cut -d'/' -
done done
done done
NEXT_CONF="next.config.mjs next.config.js next.config.dist" NEXT_CONF="next.config.js next.config.dist"
for f in $NEXT_CONF; do for f in $NEXT_CONF; do
if [ -f "$f" ]; then cat "$f" | tr -s '[:blank:]' '\n' | tr -s '[{},()]' '\n' | egrep -o 'process.env.[A-Za-z0-9_]+' >> $TMPF
cat "$f" | tr -s '[:blank:]' '\n' | tr -s '[{},()]' '\n' | egrep -o 'process.env.[A-Za-z0-9_]+' >> $TMPF
fi
done done
cat $TMPF | sort -u | jq --raw-input . | jq --slurp . cat $TMPF | sort -u | jq --raw-input . | jq --slurp .

View File

@ -5,7 +5,7 @@ fi
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
CERC_MAX_GENERATE_TIME=${CERC_MAX_GENERATE_TIME:-120} CERC_MAX_GENERATE_TIME=${CERC_MAX_GENERATE_TIME:-60}
tpid="" tpid=""
ctrl_c() { ctrl_c() {
@ -20,8 +20,6 @@ if [ -z "$CERC_BUILD_TOOL" ]; then
CERC_BUILD_TOOL=pnpm CERC_BUILD_TOOL=pnpm
elif [ -f "yarn.lock" ]; then elif [ -f "yarn.lock" ]; then
CERC_BUILD_TOOL=yarn CERC_BUILD_TOOL=yarn
elif [ -f "bun.lockb" ]; then
CERC_BUILD_TOOL=bun
else else
CERC_BUILD_TOOL=npm CERC_BUILD_TOOL=npm
fi fi

View File

@ -1,10 +0,0 @@
FROM cerc/ping-pub-base:local
COPY ./scripts/update-explorer-config.sh /scripts
COPY ./scripts/start-serving-explorer.sh /scripts
COPY ./config/laconic-chaindata-template.json /config/chains/laconic-chaindata-template.json
EXPOSE 5173
WORKDIR /app
CMD ["/scripts/start-serving-explorer.sh"]

View File

@ -1,8 +0,0 @@
FROM cerc/webapp-base:local
WORKDIR /app
COPY . .
RUN yarn

View File

@ -1,12 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Build the ping pub image # Build the ping pub image
source ${CERC_CONTAINER_BASE_DIR}/build-base.sh source ${CERC_CONTAINER_BASE_DIR}/build-base.sh
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# Two-stage build is to allow us to pick up both the upstream repo's files, and local files here for config docker build -t cerc/ping-pub:local ${build_command_args} -f $CERC_REPO_BASE_DIR/explorer/Dockerfile $CERC_REPO_BASE_DIR/explorer
docker build -t cerc/ping-pub-base:local ${build_command_args} -f $SCRIPT_DIR/Dockerfile.base $CERC_REPO_BASE_DIR/cosmos-explorer
if [[ $? -ne 0 ]]; then
echo "FATAL: Base container build failed, exiting"
exit 1
fi
docker build -t cerc/ping-pub:local ${build_command_args} -f $SCRIPT_DIR/Dockerfile $SCRIPT_DIR

View File

@ -1,22 +0,0 @@
{
"chain_name": "LACONIC_LACONICD_CHAIN_ID",
"registry_name": "LACONIC_LACONICD_CHAIN_ID",
"api": [
{"provider": "LX-one-tree-one-seven", "address": "LACONIC_LACONICD_API_URL"}
],
"rpc": [
{"provider": "LX-tendermint-rpc", "address": "LACONIC_LACONICD_RPC_URL"}
],
"sdk_version": "0.50.3",
"coin_type": "118",
"min_tx_fee": "800",
"addr_prefix": "laconic",
"logo": "/logos/cosmos.svg",
"assets": [{
"base": "alnt",
"symbol": "LNT",
"exponent": "18",
"coingecko_id": "cosmos",
"logo": "/logos/cosmos.svg"
}]
}

View File

@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -e
if [ -n "$CERC_SCRIPT_DEBUG" ]; then
set -x
fi
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
${SCRIPT_DIR}/update-explorer-config.sh
echo "Starting serving explorer"
# Force cache re-build because vite is dumb and can't be restarted otherwise
yarn serve --host --force

View File

@ -1,52 +0,0 @@
#!/usr/bin/env bash
set -e
if [ -n "$CERC_SCRIPT_DEBUG" ]; then
set -x
fi
# Verify that we have the config variables we need
if [[ -z ${LACONIC_LACONICD_API_URL} ]]; then
echo "Error: LACONIC_LACONICD_API_URL not defined"
exit 1
fi
if [[ -z ${LACONIC_LACONICD_RPC_URL} ]]; then
echo "Error: LACONIC_LACONICD_RPC_URL not defined"
exit 1
fi
if [[ -z ${LACONIC_LACONICD_CHAIN_ID} ]]; then
echo "Error: LACONIC_LACONICD_CHAIN_ID not defined"
exit 1
fi
# Ping-pub explorer has endlessly confusing behavior where it
# infers the directory from which to load chain configuration files
# by the presence or absense of the substring "testnet" in the host name
# (browser side -- the host name of the host in the address bar of the browser)
# Accordingly we configure our network in both directories in order to
# subvert this lunacy.
explorer_mainnet_config_dir=/app/chains/mainnet
explorer_testnet_config_dir=/app/chains/testnet
config_template_file=/config/chains/laconic-chaindata-template.json
chain_config_name=laconic.json
mainnet_config_file=${explorer_mainnet_config_dir}/${chain_config_name}
testnet_config_file=${explorer_testnet_config_dir}/${chain_config_name}
# Delete the stock config files
rm -f ${explorer_testnet_config_dir}/*
rm -f ${explorer_mainnet_config_dir}/*
# Copy in our template file
cp ${config_template_file} ${mainnet_config_file}
# Update the file with the config variables
sed -i "s#LACONIC_LACONICD_API_URL#${LACONIC_LACONICD_API_URL}#g" ${mainnet_config_file}
sed -i "s#LACONIC_LACONICD_RPC_URL#${LACONIC_LACONICD_RPC_URL}#g" ${mainnet_config_file}
sed -i "s#LACONIC_LACONICD_CHAIN_ID#${LACONIC_LACONICD_CHAIN_ID}#g" ${mainnet_config_file}
if [ -n "$CERC_SCRIPT_DEBUG" ]; then
echo "Updated chaindata file:"
cat ${mainnet_config_file}
fi
# Copy over to the testnet directory
cp ${mainnet_config_file} ${testnet_config_file}

View File

@ -0,0 +1,6 @@
FROM cerc/snowballtools-base-backend-base:local
WORKDIR /app/packages/backend
COPY run.sh .
ENTRYPOINT ["./run.sh"]

View File

@ -0,0 +1,26 @@
FROM ubuntu:22.04 as builder
RUN apt update && \
apt install -y --no-install-recommends --no-install-suggests \
ca-certificates curl gnupg
# Node
ARG NODE_MAJOR=20
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
apt update && apt install -y nodejs
# npm setup
RUN npm config set @cerc-io:registry https://git.vdb.to/api/packages/cerc-io/npm/ && npm install -g yarn
COPY . /app/
WORKDIR /app/
RUN find . -name 'node_modules' | xargs -n1 rm -rf
RUN yarn && yarn build --ignore frontend
FROM cerc/webapp-base:local
COPY --from=builder /app /app
WORKDIR /app/packages/backend

View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Build cerc/webapp-deployer-backend
source ${CERC_CONTAINER_BASE_DIR}/build-base.sh
# See: https://stackoverflow.com/a/246128/1701505
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
docker build -t cerc/snowballtools-base-backend-base:local ${build_command_args} -f ${SCRIPT_DIR}/Dockerfile-base ${CERC_REPO_BASE_DIR}/snowballtools-base
docker build -t cerc/snowballtools-base-backend:local ${build_command_args} ${SCRIPT_DIR}

View File

@ -0,0 +1,19 @@
#!/bin/bash
LACONIC_HOSTED_CONFIG_FILE=${LACONIC_HOSTED_CONFIG_FILE}
if [ -z "${LACONIC_HOSTED_CONFIG_FILE}" ]; then
if [ -f "/config/laconic-hosted-config.yml" ]; then
LACONIC_HOSTED_CONFIG_FILE="/config/laconic-hosted-config.yml"
elif [ -f "/config/config.yml" ]; then
LACONIC_HOSTED_CONFIG_FILE="/config/config.yml"
fi
fi
if [ -f "${LACONIC_HOSTED_CONFIG_FILE}" ]; then
/scripts/apply-webapp-config.sh $LACONIC_HOSTED_CONFIG_FILE "`pwd`/dist"
fi
/scripts/apply-runtime-env.sh "`pwd`/dist"
yarn start

View File

@ -28,13 +28,11 @@ RUN \
&& su ${USERNAME} -c "umask 0002 && npm install -g semver" \ && su ${USERNAME} -c "umask 0002 && npm install -g semver" \
# Install pnpm # Install pnpm
&& su ${USERNAME} -c "umask 0002 && npm install -g pnpm" \ && su ${USERNAME} -c "umask 0002 && npm install -g pnpm" \
# Install bun
&& su ${USERNAME} -c "umask 0002 && npm install -g bun@1.1.x" \
&& npm cache clean --force > /dev/null 2>&1 && npm cache clean --force > /dev/null 2>&1
# [Optional] Uncomment this section to install additional OS packages. # [Optional] Uncomment this section to install additional OS packages.
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends jq gettext-base git && apt-get -y install --no-install-recommends jq gettext-base
# [Optional] Uncomment if you want to install an additional version of node using nvm # [Optional] Uncomment if you want to install an additional version of node using nvm
# ARG EXTRA_NODE_VERSION=10 # ARG EXTRA_NODE_VERSION=10

View File

@ -4,8 +4,6 @@ if [ -n "$CERC_SCRIPT_DEBUG" ]; then
set -x set -x
fi fi
# TODO: document what this script does
WORK_DIR="${1:-./}" WORK_DIR="${1:-./}"
cd "${WORK_DIR}" || exit 1 cd "${WORK_DIR}" || exit 1

View File

@ -27,8 +27,6 @@ elif [ -f "${WORK_DIR}/package.json" ]; then
CERC_BUILD_TOOL=pnpm CERC_BUILD_TOOL=pnpm
elif [ -f "yarn.lock" ]; then elif [ -f "yarn.lock" ]; then
CERC_BUILD_TOOL=yarn CERC_BUILD_TOOL=yarn
elif [ -f "bun.lockb" ]; then
CERC_BUILD_TOOL=bun
else else
CERC_BUILD_TOOL=npm CERC_BUILD_TOOL=npm
fi fi

View File

@ -1,4 +1,4 @@
registry-sdk laconic-sdk
laconic-registry-cli laconic-registry-cli
laconic-console laconic-console
debug debug

View File

@ -7,7 +7,7 @@ github.com/cerc-io/ipld-eth-db-validator
github.com/cerc-io/ipld-eth-beacon-indexer github.com/cerc-io/ipld-eth-beacon-indexer
github.com/cerc-io/ipld-eth-beacon-db github.com/cerc-io/ipld-eth-beacon-db
github.com/cerc-io/laconicd github.com/cerc-io/laconicd
github.com/cerc-io/registry-sdk github.com/cerc-io/laconic-sdk
github.com/cerc-io/laconic-registry-cli github.com/cerc-io/laconic-registry-cli
github.com/cerc-io/laconic-console github.com/cerc-io/laconic-console
github.com/cerc-io/mobymask-watcher-ts github.com/cerc-io/mobymask-watcher-ts

View File

@ -2,7 +2,7 @@ version: "1.0"
name: ajna name: ajna
description: "Ajna watcher stack" description: "Ajna watcher stack"
repos: repos:
- git.vdb.to/cerc-io/ajna-watcher-ts@v0.1.13 - git.vdb.to/cerc-io/ajna-watcher-ts@v0.1.12
containers: containers:
- cerc/watcher-ajna - cerc/watcher-ajna
pods: pods:

View File

@ -58,5 +58,5 @@ Now npm packages can be built:
Ensure that `CERC_NPM_AUTH_TOKEN` is set with the token printed above when the package-registry stack was deployed (the actual token value will be different than shown in this example): Ensure that `CERC_NPM_AUTH_TOKEN` is set with the token printed above when the package-registry stack was deployed (the actual token value will be different than shown in this example):
``` ```
$ export CERC_NPM_AUTH_TOKEN=84fe66a73698bf11edbdccd0a338236b7d1d5c45 $ export CERC_NPM_AUTH_TOKEN=84fe66a73698bf11edbdccd0a338236b7d1d5c45
$ laconic-so build-npms --include registry-sdk,laconic-registry-cli $ laconic-so build-npms --include laconic-sdk,laconic-registry-cli
``` ```

View File

@ -49,7 +49,7 @@ $ laconic-so --stack fixturenet-laconic-loaded deploy logs
``` ```
## 6. Test with the Registry CLI ## 6. Test with the Registry CLI
``` ```
$ laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic registry status" $ laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic cns status"
``` ```
## 7. View the laconic console ## 7. View the laconic console
Get the URL for the console web app with this command (the port number will be different for each deployment): Get the URL for the console web app with this command (the port number will be different for each deployment):
@ -64,6 +64,5 @@ $ laconic-so --stack fixturenet-laconic-loaded deploy exec cli ./scripts/create-
Balance is: 99998999999999998999600000 Balance is: 99998999999999998999600000
Created bond with id: dd88e8d6f9567b32b28e70552aea4419c5dd3307ebae85a284d1fe38904e301a Created bond with id: dd88e8d6f9567b32b28e70552aea4419c5dd3307ebae85a284d1fe38904e301a
Published demo-record-1.yml with id: bafyreierh3xnfivexlscdwubvczmddsnf46uytyfvrbdhkjzztvsz6ruly Published demo-record-1.yml with id: bafyreierh3xnfivexlscdwubvczmddsnf46uytyfvrbdhkjzztvsz6ruly
...
``` ```
The published records should be visible in the console. The published record should be visible in the console.

View File

@ -7,11 +7,11 @@ repos:
- github.com/lirewine/crypto - github.com/lirewine/crypto
- github.com/lirewine/gem - github.com/lirewine/gem
- github.com/lirewine/sdk - github.com/lirewine/sdk
- git.vdb.to/cerc-io/registry-sdk - git.vdb.to/cerc-io/laconic-sdk
- git.vdb.to/cerc-io/laconic-registry-cli - git.vdb.to/cerc-io/laconic-registry-cli
- git.vdb.to/cerc-io/laconic-console - git.vdb.to/cerc-io/laconic-console
npms: npms:
- registry-sdk - laconic-sdk
- laconic-registry-cli - laconic-registry-cli
- debug - debug
- crypto - crypto
@ -30,3 +30,4 @@ config:
cli: cli:
key: laconicd.mykey key: laconicd.mykey
address: laconicd.myaddress address: laconicd.myaddress

View File

@ -44,5 +44,5 @@ $ laconic-so --stack fixturenet-laconicd deploy logs
``` ```
## 6. Test with the Registry CLI ## 6. Test with the Registry CLI
``` ```
$ laconic-so --stack fixturenet-laconicd deploy exec cli "laconic registry status" $ laconic-so --stack fixturenet-laconicd deploy exec cli "laconic cns status"
``` ```

View File

@ -3,10 +3,10 @@ name: fixturenet-laconicd
description: "A laconicd fixturenet" description: "A laconicd fixturenet"
repos: repos:
- git.vdb.to/cerc-io/laconicd - git.vdb.to/cerc-io/laconicd
- git.vdb.to/cerc-io/registry-sdk - git.vdb.to/cerc-io/laconic-sdk
- git.vdb.to/cerc-io/laconic-registry-cli - git.vdb.to/cerc-io/laconic-registry-cli
npms: npms:
- registry-sdk - laconic-sdk
- laconic-registry-cli - laconic-registry-cli
containers: containers:
- cerc/laconicd - cerc/laconicd

View File

@ -14,14 +14,14 @@
# along with this program. If not, see <http:#www.gnu.org/licenses/>. # along with this program. If not, see <http:#www.gnu.org/licenses/>.
from stack_orchestrator.util import get_yaml from stack_orchestrator.util import get_yaml
from stack_orchestrator.deploy.deploy_types import DeployCommandContext, LaconicStackSetupCommand from stack_orchestrator.deploy.deploy_types import DeployCommandContext, LaconicStackSetupCommand, DeploymentContext
from stack_orchestrator.deploy.deployment_context import DeploymentContext
from stack_orchestrator.deploy.stack_state import State from stack_orchestrator.deploy.stack_state import State
from stack_orchestrator.deploy.deploy_util import VolumeMapping, run_container_command from stack_orchestrator.deploy.deploy_util import VolumeMapping, run_container_command
from stack_orchestrator.opts import opts from stack_orchestrator.command_types import CommandOptions
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
from shutil import copyfile, copytree from shutil import copyfile, copytree
import json
import os import os
import sys import sys
import tomli import tomli
@ -33,9 +33,8 @@ default_spec_file_content = ""
class SetupPhase(Enum): class SetupPhase(Enum):
INITIALIZE = 1 INITIALIZE = 1
JOIN = 2 JOIN = 2
CONNECT = 3 CREATE = 3
CREATE = 4 ILLEGAL = 3
ILLEGAL = 5
def _client_toml_path(network_dir: Path): def _client_toml_path(network_dir: Path):
@ -62,23 +61,39 @@ def _get_node_moniker_from_config(network_dir: Path):
return moniker return moniker
def _get_node_key_from_gentx(options: CommandOptions, gentx_file_name: str):
gentx_file_path = Path(gentx_file_name)
if gentx_file_path.exists():
with open(Path(gentx_file_name), "rb") as f:
parsed_json = json.load(f)
return parsed_json['body']['messages'][0]['delegator_address']
else:
print(f"Error: gentx file: {gentx_file_name} does not exist")
sys.exit(1)
def _comma_delimited_to_list(list_str: str): def _comma_delimited_to_list(list_str: str):
return list_str.split(",") if list_str else [] return list_str.split(",") if list_str else []
def _get_node_keys_from_gentx_files(gentx_address_list: str): def _get_node_keys_from_gentx_files(options: CommandOptions, gentx_file_list: str):
gentx_addresses = _comma_delimited_to_list(gentx_address_list) node_keys = []
return gentx_addresses gentx_files = _comma_delimited_to_list(gentx_file_list)
for gentx_file in gentx_files:
node_key = _get_node_key_from_gentx(options, gentx_file)
if node_key:
node_keys.append(node_key)
return node_keys
def _copy_gentx_files(network_dir: Path, gentx_file_list: str): def _copy_gentx_files(options: CommandOptions, network_dir: Path, gentx_file_list: str):
gentx_files = _comma_delimited_to_list(gentx_file_list) gentx_files = _comma_delimited_to_list(gentx_file_list)
for gentx_file in gentx_files: for gentx_file in gentx_files:
gentx_file_path = Path(gentx_file) gentx_file_path = Path(gentx_file)
copyfile(gentx_file_path, os.path.join(network_dir, "config", "gentx", os.path.basename(gentx_file_path))) copyfile(gentx_file_path, os.path.join(network_dir, "config", "gentx", os.path.basename(gentx_file_path)))
def _remove_persistent_peers(network_dir: Path): def _remove_persistent_peers(options: CommandOptions, network_dir: Path):
config_file_path = _config_toml_path(network_dir) config_file_path = _config_toml_path(network_dir)
if not config_file_path.exists(): if not config_file_path.exists():
print("Error: config.toml not found") print("Error: config.toml not found")
@ -92,74 +107,20 @@ def _remove_persistent_peers(network_dir: Path):
output_file.write(config_file_content) output_file.write(config_file_content)
def _insert_persistent_peers(config_dir: Path, new_persistent_peers: str): def _insert_persistent_peers(options: CommandOptions, config_dir: Path, new_persistent_peers: str):
config_file_path = config_dir.joinpath("config.toml") config_file_path = config_dir.joinpath("config.toml")
if not config_file_path.exists(): if not config_file_path.exists():
print("Error: config.toml not found") print("Error: config.toml not found")
sys.exit(1) sys.exit(1)
with open(config_file_path, "r") as input_file: with open(config_file_path, "r") as input_file:
config_file_content = input_file.read() config_file_content = input_file.read()
persistent_peers_pattern = r'^persistent_peers = ""' persistent_peers_pattern = '^persistent_peers = ""'
replace_with = f"persistent_peers = \"{new_persistent_peers}\"" replace_with = f"persistent_peers = \"{new_persistent_peers}\""
config_file_content = re.sub(persistent_peers_pattern, replace_with, config_file_content, flags=re.MULTILINE) config_file_content = re.sub(persistent_peers_pattern, replace_with, config_file_content, flags=re.MULTILINE)
with open(config_file_path, "w") as output_file: with open(config_file_path, "w") as output_file:
output_file.write(config_file_content) output_file.write(config_file_content)
def _enable_cors(config_dir: Path):
config_file_path = config_dir.joinpath("config.toml")
if not config_file_path.exists():
print("Error: config.toml not found")
sys.exit(1)
with open(config_file_path, "r") as input_file:
config_file_content = input_file.read()
cors_pattern = r'^cors_allowed_origins = \[]'
replace_with = 'cors_allowed_origins = ["*"]'
config_file_content = re.sub(cors_pattern, replace_with, config_file_content, flags=re.MULTILINE)
with open(config_file_path, "w") as output_file:
output_file.write(config_file_content)
app_file_path = config_dir.joinpath("app.toml")
if not app_file_path.exists():
print("Error: app.toml not found")
sys.exit(1)
with open(app_file_path, "r") as input_file:
app_file_content = input_file.read()
cors_pattern = r'^enabled-unsafe-cors = false'
replace_with = "enabled-unsafe-cors = true"
app_file_content = re.sub(cors_pattern, replace_with, app_file_content, flags=re.MULTILINE)
with open(app_file_path, "w") as output_file:
output_file.write(app_file_content)
def _set_listen_address(config_dir: Path):
config_file_path = config_dir.joinpath("config.toml")
if not config_file_path.exists():
print("Error: config.toml not found")
sys.exit(1)
with open(config_file_path, "r") as input_file:
config_file_content = input_file.read()
existing_pattern = r'^laddr = "tcp://127.0.0.1:26657"'
replace_with = 'laddr = "tcp://0.0.0.0:26657"'
print(f"Replacing in: {config_file_path}")
config_file_content = re.sub(existing_pattern, replace_with, config_file_content, flags=re.MULTILINE)
with open(config_file_path, "w") as output_file:
output_file.write(config_file_content)
app_file_path = config_dir.joinpath("app.toml")
if not app_file_path.exists():
print("Error: app.toml not found")
sys.exit(1)
with open(app_file_path, "r") as input_file:
app_file_content = input_file.read()
existing_pattern1 = r'^address = "tcp://localhost:1317"'
replace_with1 = 'address = "tcp://0.0.0.0:1317"'
app_file_content = re.sub(existing_pattern1, replace_with1, app_file_content, flags=re.MULTILINE)
existing_pattern2 = r'^address = "localhost:9090"'
replace_with2 = 'address = "0.0.0.0:9090"'
app_file_content = re.sub(existing_pattern2, replace_with2, app_file_content, flags=re.MULTILINE)
with open(app_file_path, "w") as output_file:
output_file.write(app_file_content)
def _phase_from_params(parameters): def _phase_from_params(parameters):
phase = SetupPhase.ILLEGAL phase = SetupPhase.ILLEGAL
if parameters.initialize_network: if parameters.initialize_network:
@ -184,19 +145,14 @@ def _phase_from_params(parameters):
print("Can't supply --initialize-network or --join-network with --create-network") print("Can't supply --initialize-network or --join-network with --create-network")
sys.exit(1) sys.exit(1)
phase = SetupPhase.CREATE phase = SetupPhase.CREATE
elif parameters.connect_network:
if parameters.initialize_network or parameters.join_network:
print("Can't supply --initialize-network or --join-network with --connect-network")
sys.exit(1)
phase = SetupPhase.CONNECT
return phase return phase
def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCommand, extra_args): def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCommand, extra_args):
options = opts.o options = command_context.cluster_context.options
currency = "alnt" # Does this need to be a parameter? currency = "stake" # Does this need to be a parameter?
if options.debug: if options.debug:
print(f"parameters: {parameters}") print(f"parameters: {parameters}")
@ -221,12 +177,11 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
output, status = run_container_command( output, status = run_container_command(
command_context, command_context,
"laconicd", f"laconicd init {parameters.node_moniker} --home {laconicd_home_path_in_container}\ "laconicd", f"laconicd init {parameters.node_moniker} --home {laconicd_home_path_in_container}\
--chain-id {parameters.chain_id} --default-denom {currency}", mounts) --chain-id {parameters.chain_id}", mounts)
if options.debug: if options.debug:
print(f"Command output: {output}") print(f"Command output: {output}")
elif phase == SetupPhase.JOIN: elif phase == SetupPhase.JOIN:
# In the join phase (alternative to connect) we are participating in a genesis ceremony for the chain
if not os.path.exists(network_dir): if not os.path.exists(network_dir):
print(f"Error: network directory {network_dir} doesn't exist") print(f"Error: network directory {network_dir} doesn't exist")
sys.exit(1) sys.exit(1)
@ -241,7 +196,7 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
output2, status2 = run_container_command( output2, status2 = run_container_command(
command_context, command_context,
"laconicd", "laconicd",
f"laconicd genesis add-genesis-account {parameters.key_name} 12900000000000000000000{currency}\ f"laconicd add-genesis-account {parameters.key_name} 12900000000000000000000{currency}\
--home {laconicd_home_path_in_container} --keyring-backend test", --home {laconicd_home_path_in_container} --keyring-backend test",
mounts) mounts)
if options.debug: if options.debug:
@ -249,7 +204,7 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
output3, status3 = run_container_command( output3, status3 = run_container_command(
command_context, command_context,
"laconicd", "laconicd",
f"laconicd genesis gentx {parameters.key_name} 90000000000{currency} --home {laconicd_home_path_in_container}\ f"laconicd gentx {parameters.key_name} 90000000000{currency} --home {laconicd_home_path_in_container}\
--chain-id {chain_id} --keyring-backend test", --chain-id {chain_id} --keyring-backend test",
mounts) mounts)
if options.debug: if options.debug:
@ -259,28 +214,7 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
"laconicd", "laconicd",
f"laconicd keys show {parameters.key_name} -a --home {laconicd_home_path_in_container} --keyring-backend test", f"laconicd keys show {parameters.key_name} -a --home {laconicd_home_path_in_container} --keyring-backend test",
mounts) mounts)
print(f"Node account address: {output4}") print(f"Node validator address: {output4}")
elif phase == SetupPhase.CONNECT:
# In the connect phase (named to not conflict with join) we are making a node that syncs a chain with existing genesis.json
# but not with validator role. We need this kind of node in order to bootstrap it into a validator after it syncs
output1, status1 = run_container_command(
command_context, "laconicd", f"laconicd keys add {parameters.key_name} --home {laconicd_home_path_in_container}\
--keyring-backend test", mounts)
if options.debug:
print(f"Command output: {output1}")
output2, status2 = run_container_command(
command_context,
"laconicd",
f"laconicd keys show {parameters.key_name} -a --home {laconicd_home_path_in_container} --keyring-backend test",
mounts)
print(f"Node account address: {output2}")
output3, status3 = run_container_command(
command_context,
"laconicd",
f"laconicd cometbft show-validator --home {laconicd_home_path_in_container}",
mounts)
print(f"Node validator address: {output3}")
elif phase == SetupPhase.CREATE: elif phase == SetupPhase.CREATE:
if not os.path.exists(network_dir): if not os.path.exists(network_dir):
@ -299,30 +233,32 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
copyfile(genesis_file_path, os.path.join(network_dir, "config", os.path.basename(genesis_file_path))) copyfile(genesis_file_path, os.path.join(network_dir, "config", os.path.basename(genesis_file_path)))
else: else:
# We're generating the genesis file # We're generating the genesis file
if not parameters.gentx_file_list:
print("Error: --gentx-files must be supplied")
sys.exit(1)
# First look in the supplied gentx files for the other nodes' keys # First look in the supplied gentx files for the other nodes' keys
other_node_keys = _get_node_keys_from_gentx_files(parameters.gentx_address_list) other_node_keys = _get_node_keys_from_gentx_files(options, parameters.gentx_file_list)
# Add those keys to our genesis, with balances we determine here (why?) # Add those keys to our genesis, with balances we determine here (why?)
for other_node_key in other_node_keys: for other_node_key in other_node_keys:
outputk, statusk = run_container_command( outputk, statusk = run_container_command(
command_context, "laconicd", f"laconicd genesis add-genesis-account {other_node_key} \ command_context, "laconicd", f"laconicd add-genesis-account {other_node_key} 12900000000000000000000{currency}\
12900000000000000000000{currency}\
--home {laconicd_home_path_in_container} --keyring-backend test", mounts) --home {laconicd_home_path_in_container} --keyring-backend test", mounts)
if options.debug: if options.debug:
print(f"Command output: {outputk}") print(f"Command output: {outputk}")
# Copy the gentx json files into our network dir # Copy the gentx json files into our network dir
_copy_gentx_files(network_dir, parameters.gentx_file_list) _copy_gentx_files(options, network_dir, parameters.gentx_file_list)
# Now we can run collect-gentxs # Now we can run collect-gentxs
output1, status1 = run_container_command( output1, status1 = run_container_command(
command_context, "laconicd", f"laconicd genesis collect-gentxs --home {laconicd_home_path_in_container}", mounts) command_context, "laconicd", f"laconicd collect-gentxs --home {laconicd_home_path_in_container}", mounts)
if options.debug: if options.debug:
print(f"Command output: {output1}") print(f"Command output: {output1}")
print(f"Generated genesis file, please copy to other nodes as required: \ print(f"Generated genesis file, please copy to other nodes as required: \
{os.path.join(network_dir, 'config', 'genesis.json')}") {os.path.join(network_dir, 'config', 'genesis.json')}")
# Last thing, collect-gentxs puts a likely bogus set of persistent_peers in config.toml so we remove that now # Last thing, collect-gentxs puts a likely bogus set of persistent_peers in config.toml so we remove that now
_remove_persistent_peers(network_dir) _remove_persistent_peers(options, network_dir)
# In both cases we validate the genesis file now # In both cases we validate the genesis file now
output2, status1 = run_container_command( output2, status1 = run_container_command(
command_context, "laconicd", f"laconicd genesis validate-genesis --home {laconicd_home_path_in_container}", mounts) command_context, "laconicd", f"laconicd validate-genesis --home {laconicd_home_path_in_container}", mounts)
print(f"validate-genesis result: {output2}") print(f"validate-genesis result: {output2}")
else: else:
@ -330,7 +266,7 @@ def setup(command_context: DeployCommandContext, parameters: LaconicStackSetupCo
sys.exit(1) sys.exit(1)
def create(deployment_context: DeploymentContext, extra_args): def create(context: DeploymentContext, extra_args):
network_dir = extra_args[0] network_dir = extra_args[0]
if network_dir is None: if network_dir is None:
print("Error: --network-dir must be supplied") print("Error: --network-dir must be supplied")
@ -349,18 +285,15 @@ def create(deployment_context: DeploymentContext, extra_args):
sys.exit(1) sys.exit(1)
# Copy the network directory contents into our deployment # Copy the network directory contents into our deployment
# TODO: change this to work with non local paths # TODO: change this to work with non local paths
deployment_config_dir = deployment_context.deployment_dir.joinpath("data", "laconicd-config") deployment_config_dir = context.deployment_dir.joinpath("data", "laconicd-config")
copytree(config_dir_path, deployment_config_dir, dirs_exist_ok=True) copytree(config_dir_path, deployment_config_dir, dirs_exist_ok=True)
# If supplied, add the initial persistent peers to the config file # If supplied, add the initial persistent peers to the config file
if extra_args[1]: if extra_args[1]:
initial_persistent_peers = extra_args[1] initial_persistent_peers = extra_args[1]
_insert_persistent_peers(deployment_config_dir, initial_persistent_peers) _insert_persistent_peers(context.command_context.cluster_context.options, deployment_config_dir, initial_persistent_peers)
# Enable CORS headers so explorers and so on can talk to the node
_enable_cors(deployment_config_dir)
_set_listen_address(deployment_config_dir)
# Copy the data directory contents into our deployment # Copy the data directory contents into our deployment
# TODO: change this to work with non local paths # TODO: change this to work with non local paths
deployment_data_dir = deployment_context.deployment_dir.joinpath("data", "laconicd-data") deployment_data_dir = context.deployment_dir.joinpath("data", "laconicd-data")
copytree(data_dir_path, deployment_data_dir, dirs_exist_ok=True) copytree(data_dir_path, deployment_data_dir, dirs_exist_ok=True)
@ -370,6 +303,7 @@ def init(command_context: DeployCommandContext):
def get_state(command_context: DeployCommandContext): def get_state(command_context: DeployCommandContext):
print("Here we get state")
return State.CONFIGURED return State.CONFIGURED

View File

@ -2,17 +2,16 @@ version: "1.0"
name: mainnet-laconic name: mainnet-laconic
description: "Mainnet laconic node" description: "Mainnet laconic node"
repos: repos:
- git.vdb.to/cerc-io/laconicd - cerc-io/laconicd
- github.com/lirewine/debug - lirewine/debug
- github.com/lirewine/crypto - lirewine/crypto
- github.com/lirewine/gem - lirewine/gem
- github.com/lirewine/sdk - lirewine/sdk
- git.vdb.to/cerc-io/registry-sdk - cerc-io/laconic-sdk
- git.vdb.to/cerc-io/laconic-registry-cli - cerc-io/laconic-registry-cli
- git.vdb.to/cerc-io/laconic-console - cerc-io/laconic-console
- git.vdb.to/cerc-io/cosmos-explorer
npms: npms:
- registry-sdk - laconic-sdk
- laconic-registry-cli - laconic-registry-cli
- debug - debug
- crypto - crypto
@ -24,8 +23,7 @@ containers:
- cerc/laconic-registry-cli - cerc/laconic-registry-cli
- cerc/webapp-base - cerc/webapp-base
- cerc/laconic-console-host - cerc/laconic-console-host
- cerc/ping-pub
pods: pods:
- mainnet-laconicd - mainnet-laconicd
- fixturenet-laconic-console - fixturenet-laconic-console
- laconic-explorer

View File

@ -8,11 +8,8 @@ echo "Environment variables:"
env env
# Test laconic stack # Test laconic stack
echo "Running laconic stack test" echo "Running laconic stack test"
if [ "$1" == "from-path" ]; then # Bit of a hack, test the most recent package
TEST_TARGET_SO="laconic-so" TEST_TARGET_SO=$( ls -t1 ./package/laconic-so* | head -1 )
else
TEST_TARGET_SO=$( ls -t1 ./package/laconic-so* | head -1 )
fi
# Set a non-default repo dir # Set a non-default repo dir
export CERC_REPO_BASE_DIR=~/stack-orchestrator-test/repo-base-dir export CERC_REPO_BASE_DIR=~/stack-orchestrator-test/repo-base-dir
echo "Testing this package: $TEST_TARGET_SO" echo "Testing this package: $TEST_TARGET_SO"

View File

@ -2,7 +2,7 @@ version: "1.0"
name: merkl-sushiswap-v3 name: merkl-sushiswap-v3
description: "SushiSwap v3 watcher stack" description: "SushiSwap v3 watcher stack"
repos: repos:
- github.com/cerc-io/merkl-sushiswap-v3-watcher-ts@v0.1.14 - github.com/cerc-io/merkl-sushiswap-v3-watcher-ts@v0.1.13
containers: containers:
- cerc/watcher-merkl-sushiswap-v3 - cerc/watcher-merkl-sushiswap-v3
pods: pods:

View File

@ -109,7 +109,7 @@ Setup a test chain:
```bash ```bash
export CERC_NPM_REGISTRY_URL=https://git.vdb.to/api/packages/cerc-io/npm/ export CERC_NPM_REGISTRY_URL=https://git.vdb.to/api/packages/cerc-io/npm/
laconic-so --stack fixturenet-laconic-loaded setup-repositories --include git.vdb.to/cerc-io/laconicd,git.vdb.to/cerc-io/registry-sdk,git.vdb.to/cerc-io/laconic-registry-cli,git.vdb.to/cerc-io/laconic-console laconic-so --stack fixturenet-laconic-loaded setup-repositories --include git.vdb.to/cerc-io/laconicd,git.vdb.to/cerc-io/laconic-sdk,git.vdb.to/cerc-io/laconic-registry-cli,git.vdb.to/cerc-io/laconic-console
laconic-so --stack fixturenet-laconic-loaded build-containers laconic-so --stack fixturenet-laconic-loaded build-containers

View File

@ -2,50 +2,4 @@
The Package Registry Stack supports a build environment that requires a package registry (initially for NPM packages only). The Package Registry Stack supports a build environment that requires a package registry (initially for NPM packages only).
## Setup Setup instructions can be found [here](../build-support/README.md).
* Setup required repos and build containers:
```bash
laconic-so --stack package-registry setup-repositories
laconic-so --stack package-registry build-containers
```
* Create a deployment:
```bash
laconic-so --stack package-registry deploy init --output package-registry-spec.yml
# Update port mapping in the laconic-loaded.spec file to resolve port conflicts on host if any
laconic-so --stack package-registry deploy create --deployment-dir package-registry-deployment --spec-file package-registry-spec.yml
```
* Start the deployment:
```bash
laconic-so deployment --dir package-registry-deployment start
```
* The local gitea registry can now be accessed at <http://localhost:3000> (the username and password can be taken from the deployment logs)
* Configure the hostname `gitea.local`:
Update `/etc/hosts`:
```bash
sudo nano /etc/hosts
# Add the following line
127.0.0.1 gitea.local
```
Check resolution:
```bash
ping gitea.local
PING gitea.local (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.147 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.033 ms
...
```

View File

@ -0,0 +1,10 @@
version: "1.0"
name: snowballtools-base-backend
description: "snowballtools-base-backend"
repos:
- github.com/snowball-tools/snowballtools-base
containers:
- cerc/webapp-base
- cerc/snowballtools-base-backend
pods:
- snowballtools-base-backend

View File

@ -2,7 +2,7 @@ version: "1.0"
name: sushiswap-v3 name: sushiswap-v3
description: "SushiSwap v3 watcher stack" description: "SushiSwap v3 watcher stack"
repos: repos:
- github.com/cerc-io/sushiswap-v3-watcher-ts@v0.1.14 - github.com/cerc-io/sushiswap-v3-watcher-ts@v0.1.13
containers: containers:
- cerc/watcher-sushiswap-v3 - cerc/watcher-sushiswap-v3
pods: pods:

View File

@ -29,14 +29,14 @@ class DockerDeployer(Deployer):
compose_env_file=compose_env_file) compose_env_file=compose_env_file)
self.type = type self.type = type
def up(self, detach, skip_cluster_management, services): def up(self, detach, services):
if not opts.o.dry_run: if not opts.o.dry_run:
try: try:
return self.docker.compose.up(detach=detach, services=services) return self.docker.compose.up(detach=detach, services=services)
except DockerException as e: except DockerException as e:
raise DeployerException(e) raise DeployerException(e)
def down(self, timeout, volumes, skip_cluster_management): def down(self, timeout, volumes):
if not opts.o.dry_run: if not opts.o.dry_run:
try: try:
return self.docker.compose.down(timeout=timeout, volumes=volumes) return self.docker.compose.down(timeout=timeout, volumes=volumes)

View File

@ -26,15 +26,8 @@ import click
from pathlib import Path from pathlib import Path
from stack_orchestrator import constants from stack_orchestrator import constants
from stack_orchestrator.opts import opts from stack_orchestrator.opts import opts
from stack_orchestrator.util import ( from stack_orchestrator.util import include_exclude_check, get_parsed_stack_config, global_options2, get_dev_root_path
get_stack_path, from stack_orchestrator.util import resolve_compose_file
include_exclude_check,
get_parsed_stack_config,
global_options2,
get_dev_root_path,
stack_is_in_deployment,
resolve_compose_file,
)
from stack_orchestrator.deploy.deployer import Deployer, DeployerException from stack_orchestrator.deploy.deployer import Deployer, DeployerException
from stack_orchestrator.deploy.deployer_factory import getDeployer from stack_orchestrator.deploy.deployer_factory import getDeployer
from stack_orchestrator.deploy.deploy_types import ClusterContext, DeployCommandContext from stack_orchestrator.deploy.deploy_types import ClusterContext, DeployCommandContext
@ -67,7 +60,6 @@ def command(ctx, include, exclude, env_file, cluster, deploy_to):
if deploy_to is None: if deploy_to is None:
deploy_to = "compose" deploy_to = "compose"
stack = get_stack_path(stack)
ctx.obj = create_deploy_context(global_options2(ctx), None, stack, include, exclude, cluster, env_file, deploy_to) ctx.obj = create_deploy_context(global_options2(ctx), None, stack, include, exclude, cluster, env_file, deploy_to)
# Subcommand is executed now, by the magic of click # Subcommand is executed now, by the magic of click
@ -91,7 +83,7 @@ def create_deploy_context(
return DeployCommandContext(stack, cluster_context, deployer) return DeployCommandContext(stack, cluster_context, deployer)
def up_operation(ctx, services_list, stay_attached=False, skip_cluster_management=False): def up_operation(ctx, services_list, stay_attached=False):
global_context = ctx.parent.parent.obj global_context = ctx.parent.parent.obj
deploy_context = ctx.obj deploy_context = ctx.obj
cluster_context = deploy_context.cluster_context cluster_context = deploy_context.cluster_context
@ -102,18 +94,18 @@ def up_operation(ctx, services_list, stay_attached=False, skip_cluster_managemen
print(f"Running compose up with container_exec_env: {container_exec_env}, extra_args: {services_list}") print(f"Running compose up with container_exec_env: {container_exec_env}, extra_args: {services_list}")
for pre_start_command in cluster_context.pre_start_commands: for pre_start_command in cluster_context.pre_start_commands:
_run_command(global_context, cluster_context.cluster, pre_start_command) _run_command(global_context, cluster_context.cluster, pre_start_command)
deploy_context.deployer.up(detach=not stay_attached, skip_cluster_management=skip_cluster_management, services=services_list) deploy_context.deployer.up(detach=not stay_attached, services=services_list)
for post_start_command in cluster_context.post_start_commands: for post_start_command in cluster_context.post_start_commands:
_run_command(global_context, cluster_context.cluster, post_start_command) _run_command(global_context, cluster_context.cluster, post_start_command)
_orchestrate_cluster_config(global_context, cluster_context.config, deploy_context.deployer, container_exec_env) _orchestrate_cluster_config(global_context, cluster_context.config, deploy_context.deployer, container_exec_env)
def down_operation(ctx, delete_volumes, extra_args_list, skip_cluster_management=False): def down_operation(ctx, delete_volumes, extra_args_list):
timeout_arg = None timeout_arg = None
if extra_args_list: if extra_args_list:
timeout_arg = extra_args_list[0] timeout_arg = extra_args_list[0]
# Specify shutdown timeout (default 10s) to give services enough time to shutdown gracefully # Specify shutdown timeout (default 10s) to give services enough time to shutdown gracefully
ctx.obj.deployer.down(timeout=timeout_arg, volumes=delete_volumes, skip_cluster_management=skip_cluster_management) ctx.obj.deployer.down(timeout=timeout_arg, volumes=delete_volumes)
def status_operation(ctx): def status_operation(ctx):
@ -282,12 +274,16 @@ def _make_default_cluster_name(deployment, compose_dir, stack, include, exclude)
# stack has to be either PathLike pointing to a stack yml file, or a string with the name of a known stack # stack has to be either PathLike pointing to a stack yml file, or a string with the name of a known stack
def _make_cluster_context(ctx, stack, include, exclude, cluster, env_file): def _make_cluster_context(ctx, stack, include, exclude, cluster, env_file):
dev_root_path = get_dev_root_path(ctx) dev_root_path = get_dev_root_path(ctx)
# TODO: hack, this should be encapsulated by the deployment context. # TODO: huge hack, fix this
deployment = stack_is_in_deployment(stack) # If the caller passed a path for the stack file, then we know that we can get the compose files
if deployment: # from the same directory
compose_dir = stack.joinpath("compose") deployment = False
if isinstance(stack, os.PathLike):
compose_dir = stack.parent.joinpath("compose")
deployment = True
else: else:
# See: https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure # See: https://stackoverflow.com/questions/25389095/python-get-path-of-root-project-structure
compose_dir = Path(__file__).absolute().parent.parent.joinpath("data", "compose") compose_dir = Path(__file__).absolute().parent.parent.joinpath("data", "compose")

View File

@ -50,10 +50,8 @@ class LaconicStackSetupCommand:
key_name: str key_name: str
initialize_network: bool initialize_network: bool
join_network: bool join_network: bool
connect_network: bool
create_network: bool create_network: bool
gentx_file_list: str gentx_file_list: str
gentx_address_list: str
genesis_file: str genesis_file: str
network_dir: str network_dir: str

View File

@ -13,6 +13,7 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>. # along with this program. If not, see <http:#www.gnu.org/licenses/>.
import os
from typing import List, Any from typing import List, Any
from stack_orchestrator.deploy.deploy_types import DeployCommandContext, VolumeMapping from stack_orchestrator.deploy.deploy_types import DeployCommandContext, VolumeMapping
from stack_orchestrator.util import get_parsed_stack_config, get_yaml, get_pod_list, resolve_compose_file from stack_orchestrator.util import get_parsed_stack_config, get_yaml, get_pod_list, resolve_compose_file
@ -82,9 +83,7 @@ def run_container_command(ctx: DeployCommandContext, service: str, command: str,
docker_output = deployer.run( docker_output = deployer.run(
container_image, container_image,
["-c", command], entrypoint="sh", ["-c", command], entrypoint="sh",
# Current laconicd container has a bug where it crashes when run not as root user=f"{os.getuid()}:{os.getgid()}",
# Commented out line below is a workaround. Created files end up owned by root on the host
# user=f"{os.getuid()}:{os.getgid()}",
volumes=docker_volumes volumes=docker_volumes
) )
# There doesn't seem to be a way to get an exit code from docker.run() # There doesn't seem to be a way to get an exit code from docker.run()

View File

@ -20,11 +20,11 @@ from pathlib import Path
class Deployer(ABC): class Deployer(ABC):
@abstractmethod @abstractmethod
def up(self, detach, skip_cluster_management, services): def up(self, detach, services):
pass pass
@abstractmethod @abstractmethod
def down(self, timeout, volumes, skip_cluster_management): def down(self, timeout, volumes):
pass pass
@abstractmethod @abstractmethod

View File

@ -50,68 +50,58 @@ def command(ctx, dir):
def make_deploy_context(ctx) -> DeployCommandContext: def make_deploy_context(ctx) -> DeployCommandContext:
context: DeploymentContext = ctx.obj context: DeploymentContext = ctx.obj
stack_file_path = context.get_stack_file()
env_file = context.get_env_file() env_file = context.get_env_file()
cluster_name = context.get_cluster_id() cluster_name = context.get_cluster_id()
if constants.deploy_to_key in context.spec.obj: if constants.deploy_to_key in context.spec.obj:
deployment_type = context.spec.obj[constants.deploy_to_key] deployment_type = context.spec.obj[constants.deploy_to_key]
else: else:
deployment_type = constants.compose_deploy_type deployment_type = constants.compose_deploy_type
stack = context.deployment_dir return create_deploy_context(ctx.parent.parent.obj, context, stack_file_path, None, None, cluster_name, env_file,
return create_deploy_context(ctx.parent.parent.obj, context, stack, None, None, deployment_type)
cluster_name, env_file, deployment_type)
# TODO: remove legacy up command since it's an alias for start
@command.command() @command.command()
@click.option("--stay-attached/--detatch-terminal", default=False, help="detatch or not to see container stdout") @click.option("--stay-attached/--detatch-terminal", default=False, help="detatch or not to see container stdout")
@click.option("--skip-cluster-management/--perform-cluster-management",
default=False, help="Skip cluster initialization/tear-down (only for kind-k8s deployments)")
@click.argument('extra_args', nargs=-1) # help: command: up <service1> <service2> @click.argument('extra_args', nargs=-1) # help: command: up <service1> <service2>
@click.pass_context @click.pass_context
def up(ctx, stay_attached, skip_cluster_management, extra_args): def up(ctx, stay_attached, extra_args):
ctx.obj = make_deploy_context(ctx) ctx.obj = make_deploy_context(ctx)
services_list = list(extra_args) or None services_list = list(extra_args) or None
up_operation(ctx, services_list, stay_attached, skip_cluster_management) up_operation(ctx, services_list, stay_attached)
# start is the preferred alias for up # start is the preferred alias for up
@command.command() @command.command()
@click.option("--stay-attached/--detatch-terminal", default=False, help="detatch or not to see container stdout") @click.option("--stay-attached/--detatch-terminal", default=False, help="detatch or not to see container stdout")
@click.option("--skip-cluster-management/--perform-cluster-management",
default=False, help="Skip cluster initialization/tear-down (only for kind-k8s deployments)")
@click.argument('extra_args', nargs=-1) # help: command: up <service1> <service2> @click.argument('extra_args', nargs=-1) # help: command: up <service1> <service2>
@click.pass_context @click.pass_context
def start(ctx, stay_attached, skip_cluster_management, extra_args): def start(ctx, stay_attached, extra_args):
ctx.obj = make_deploy_context(ctx) ctx.obj = make_deploy_context(ctx)
services_list = list(extra_args) or None services_list = list(extra_args) or None
up_operation(ctx, services_list, stay_attached, skip_cluster_management) up_operation(ctx, services_list, stay_attached)
# TODO: remove legacy up command since it's an alias for stop
@command.command() @command.command()
@click.option("--delete-volumes/--preserve-volumes", default=False, help="delete data volumes") @click.option("--delete-volumes/--preserve-volumes", default=False, help="delete data volumes")
@click.option("--skip-cluster-management/--perform-cluster-management",
default=False, help="Skip cluster initialization/tear-down (only for kind-k8s deployments)")
@click.argument('extra_args', nargs=-1) # help: command: down <service1> <service2> @click.argument('extra_args', nargs=-1) # help: command: down <service1> <service2>
@click.pass_context @click.pass_context
def down(ctx, delete_volumes, skip_cluster_management, extra_args): def down(ctx, delete_volumes, extra_args):
# Get the stack config file name # Get the stack config file name
# TODO: add cluster name and env file here # TODO: add cluster name and env file here
ctx.obj = make_deploy_context(ctx) ctx.obj = make_deploy_context(ctx)
down_operation(ctx, delete_volumes, extra_args, skip_cluster_management) down_operation(ctx, delete_volumes, extra_args)
# stop is the preferred alias for down # stop is the preferred alias for down
@command.command() @command.command()
@click.option("--delete-volumes/--preserve-volumes", default=False, help="delete data volumes") @click.option("--delete-volumes/--preserve-volumes", default=False, help="delete data volumes")
@click.option("--skip-cluster-management/--perform-cluster-management",
default=False, help="Skip cluster initialization/tear-down (only for kind-k8s deployments)")
@click.argument('extra_args', nargs=-1) # help: command: down <service1> <service2> @click.argument('extra_args', nargs=-1) # help: command: down <service1> <service2>
@click.pass_context @click.pass_context
def stop(ctx, delete_volumes, skip_cluster_management, extra_args): def stop(ctx, delete_volumes, extra_args):
# TODO: add cluster name and env file here # TODO: add cluster name and env file here
ctx.obj = make_deploy_context(ctx) ctx.obj = make_deploy_context(ctx)
down_operation(ctx, delete_volumes, extra_args, skip_cluster_management) down_operation(ctx, delete_volumes, extra_args)
@command.command() @command.command()
@ -133,7 +123,6 @@ def push_images(ctx):
@click.argument('extra_args', nargs=-1) # help: command: port <service1> <service2> @click.argument('extra_args', nargs=-1) # help: command: port <service1> <service2>
@click.pass_context @click.pass_context
def port(ctx, extra_args): def port(ctx, extra_args):
ctx.obj = make_deploy_context(ctx)
port_operation(ctx, extra_args) port_operation(ctx, extra_args)

View File

@ -24,7 +24,7 @@ from secrets import token_hex
import sys import sys
from stack_orchestrator import constants from stack_orchestrator import constants
from stack_orchestrator.opts import opts from stack_orchestrator.opts import opts
from stack_orchestrator.util import (get_stack_path, get_parsed_deployment_spec, get_parsed_stack_config, from stack_orchestrator.util import (get_stack_file_path, get_parsed_deployment_spec, get_parsed_stack_config,
global_options, get_yaml, get_pod_list, get_pod_file_path, pod_has_scripts, global_options, get_yaml, get_pod_list, get_pod_file_path, pod_has_scripts,
get_pod_script_paths, get_plugin_code_paths, error_exit, env_var_map_from_file, get_pod_script_paths, get_plugin_code_paths, error_exit, env_var_map_from_file,
resolve_config_dir) resolve_config_dir)
@ -238,11 +238,6 @@ def _find_extra_config_dirs(parsed_pod_file, pod):
config_dir = host_path.split("/")[2] config_dir = host_path.split("/")[2]
if config_dir != pod: if config_dir != pod:
config_dirs.add(config_dir) config_dirs.add(config_dir)
for env_file in service_info.get("env_file", []):
if env_file.startswith("../config"):
config_dir = env_file.split("/")[2]
if config_dir != pod:
config_dirs.add(config_dir)
return config_dirs return config_dirs
@ -459,7 +454,7 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, netw
_check_volume_definitions(parsed_spec) _check_volume_definitions(parsed_spec)
stack_name = parsed_spec["stack"] stack_name = parsed_spec["stack"]
deployment_type = parsed_spec[constants.deploy_to_key] deployment_type = parsed_spec[constants.deploy_to_key]
stack_file = get_stack_path(stack_name).joinpath(constants.stack_file_name) stack_file = get_stack_file_path(stack_name)
parsed_stack = get_parsed_stack_config(stack_name) parsed_stack = get_parsed_stack_config(stack_name)
if opts.o.debug: if opts.o.debug:
print(f"parsed spec: {parsed_spec}") print(f"parsed spec: {parsed_spec}")
@ -472,7 +467,7 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, netw
os.mkdir(deployment_dir_path) os.mkdir(deployment_dir_path)
# Copy spec file and the stack file into the deployment dir # Copy spec file and the stack file into the deployment dir
copyfile(spec_file, deployment_dir_path.joinpath(constants.spec_file_name)) copyfile(spec_file, deployment_dir_path.joinpath(constants.spec_file_name))
copyfile(stack_file, deployment_dir_path.joinpath(constants.stack_file_name)) copyfile(stack_file, deployment_dir_path.joinpath(os.path.basename(stack_file)))
_create_deployment_file(deployment_dir_path) _create_deployment_file(deployment_dir_path)
# Copy any config varibles from the spec file into an env file suitable for compose # Copy any config varibles from the spec file into an env file suitable for compose
_write_config_file(spec_file, deployment_dir_path.joinpath(constants.config_file_name)) _write_config_file(spec_file, deployment_dir_path.joinpath(constants.config_file_name))
@ -514,23 +509,6 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, netw
os.mkdir(destination_script_dir) os.mkdir(destination_script_dir)
script_paths = get_pod_script_paths(parsed_stack, pod) script_paths = get_pod_script_paths(parsed_stack, pod)
_copy_files_to_directory(script_paths, destination_script_dir) _copy_files_to_directory(script_paths, destination_script_dir)
if parsed_spec.is_kubernetes_deployment():
for configmap in parsed_spec.get_configmaps():
source_config_dir = resolve_config_dir(stack_name, configmap)
if os.path.exists(source_config_dir):
destination_config_dir = deployment_dir_path.joinpath("configmaps", configmap)
copytree(source_config_dir, destination_config_dir, dirs_exist_ok=True)
else:
# TODO: We should probably only do this if the volume is marked :ro.
for volume_name, volume_path in parsed_spec.get_volumes().items():
source_config_dir = resolve_config_dir(stack_name, volume_name)
# Only copy if the source exists and is _not_ empty.
if os.path.exists(source_config_dir) and os.listdir(source_config_dir):
destination_config_dir = deployment_dir_path.joinpath(volume_path)
# Only copy if the destination exists and _is_ empty.
if os.path.exists(destination_config_dir) and not os.listdir(destination_config_dir):
copytree(source_config_dir, destination_config_dir, dirs_exist_ok=True)
# Delegate to the stack's Python code # Delegate to the stack's Python code
# The deploy create command doesn't require a --stack argument so we need to insert the # The deploy create command doesn't require a --stack argument so we need to insert the
# stack member here. # stack member here.
@ -552,17 +530,15 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, netw
@click.option("--chain-id", help="The new chain id") @click.option("--chain-id", help="The new chain id")
@click.option("--key-name", help="Name for new node key") @click.option("--key-name", help="Name for new node key")
@click.option("--gentx-files", help="List of comma-delimited gentx filenames from other nodes") @click.option("--gentx-files", help="List of comma-delimited gentx filenames from other nodes")
@click.option("--gentx-addresses", type=str, help="List of comma-delimited validator addresses for other nodes")
@click.option("--genesis-file", help="Genesis file for the network") @click.option("--genesis-file", help="Genesis file for the network")
@click.option("--initialize-network", is_flag=True, default=False, help="Initialize phase") @click.option("--initialize-network", is_flag=True, default=False, help="Initialize phase")
@click.option("--join-network", is_flag=True, default=False, help="Join phase") @click.option("--join-network", is_flag=True, default=False, help="Join phase")
@click.option("--connect-network", is_flag=True, default=False, help="Connect phase")
@click.option("--create-network", is_flag=True, default=False, help="Create phase") @click.option("--create-network", is_flag=True, default=False, help="Create phase")
@click.option("--network-dir", help="Directory for network files") @click.option("--network-dir", help="Directory for network files")
@click.argument('extra_args', nargs=-1) @click.argument('extra_args', nargs=-1)
@click.pass_context @click.pass_context
def setup(ctx, node_moniker, chain_id, key_name, gentx_files, gentx_addresses, genesis_file, initialize_network, join_network, def setup(ctx, node_moniker, chain_id, key_name, gentx_files, genesis_file, initialize_network, join_network, create_network,
connect_network, create_network, network_dir, extra_args): network_dir, extra_args):
parmeters = LaconicStackSetupCommand(chain_id, node_moniker, key_name, initialize_network, join_network, connect_network, parmeters = LaconicStackSetupCommand(chain_id, node_moniker, key_name, initialize_network, join_network, create_network,
create_network, gentx_files, gentx_addresses, genesis_file, network_dir) gentx_files, genesis_file, network_dir)
call_stack_deploy_setup(ctx.obj, parmeters, extra_args) call_stack_deploy_setup(ctx.obj, parmeters, extra_args)

View File

@ -29,29 +29,16 @@ def _image_needs_pushed(image: str):
return image.endswith(":local") return image.endswith(":local")
def _remote_tag_for_image(image: str, remote_repo_url: str):
# Turns image tags of the form: foo/bar:local into remote.repo/org/bar:deploy
major_parts = image.split("/", 2)
image_name_with_version = major_parts[1] if 2 == len(major_parts) else major_parts[0]
(image_name, image_version) = image_name_with_version.split(":")
if image_version == "local":
return f"{remote_repo_url}/{image_name}:deploy"
else:
return image
# Note: do not add any calls this function
def remote_image_exists(remote_repo_url: str, local_tag: str): def remote_image_exists(remote_repo_url: str, local_tag: str):
docker = DockerClient() docker = DockerClient()
try: try:
remote_tag = _remote_tag_for_image(local_tag, remote_repo_url) remote_tag = remote_tag_for_image(local_tag, remote_repo_url)
result = docker.manifest.inspect(remote_tag) result = docker.manifest.inspect(remote_tag)
return True if result else False return True if result else False
except Exception: # noqa: E722 except Exception: # noqa: E722
return False return False
# Note: do not add any calls this function
def add_tags_to_image(remote_repo_url: str, local_tag: str, *additional_tags): def add_tags_to_image(remote_repo_url: str, local_tag: str, *additional_tags):
if not additional_tags: if not additional_tags:
return return
@ -60,20 +47,18 @@ def add_tags_to_image(remote_repo_url: str, local_tag: str, *additional_tags):
raise Exception(f"{local_tag} does not exist in {remote_repo_url}") raise Exception(f"{local_tag} does not exist in {remote_repo_url}")
docker = DockerClient() docker = DockerClient()
remote_tag = _remote_tag_for_image(local_tag, remote_repo_url) remote_tag = remote_tag_for_image(local_tag, remote_repo_url)
new_remote_tags = [_remote_tag_for_image(tag, remote_repo_url) for tag in additional_tags] new_remote_tags = [remote_tag_for_image(tag, remote_repo_url) for tag in additional_tags]
docker.buildx.imagetools.create(sources=[remote_tag], tags=new_remote_tags) docker.buildx.imagetools.create(sources=[remote_tag], tags=new_remote_tags)
def remote_tag_for_image_unique(image: str, remote_repo_url: str, deployment_id: str): def remote_tag_for_image(image: str, remote_repo_url: str):
# Turns image tags of the form: foo/bar:local into remote.repo/org/bar:deploy # Turns image tags of the form: foo/bar:local into remote.repo/org/bar:deploy
major_parts = image.split("/", 2) major_parts = image.split("/", 2)
image_name_with_version = major_parts[1] if 2 == len(major_parts) else major_parts[0] image_name_with_version = major_parts[1] if 2 == len(major_parts) else major_parts[0]
(image_name, image_version) = image_name_with_version.split(":") (image_name, image_version) = image_name_with_version.split(":")
if image_version == "local": if image_version == "local":
# Salt the tag with part of the deployment id to make it unique to this deployment return f"{remote_repo_url}/{image_name}:deploy"
deployment_tag = deployment_id[-8:]
return f"{remote_repo_url}/{image_name}:deploy-{deployment_tag}"
else: else:
return image return image
@ -88,14 +73,14 @@ def push_images_operation(command_context: DeployCommandContext, deployment_cont
docker = DockerClient() docker = DockerClient()
for image in images: for image in images:
if _image_needs_pushed(image): if _image_needs_pushed(image):
remote_tag = remote_tag_for_image_unique(image, remote_repo_url, deployment_context.id) remote_tag = remote_tag_for_image(image, remote_repo_url)
if opts.o.verbose: if opts.o.verbose:
print(f"Tagging {image} to {remote_tag}") print(f"Tagging {image} to {remote_tag}")
docker.image.tag(image, remote_tag) docker.image.tag(image, remote_tag)
# Run docker push commands to upload # Run docker push commands to upload
for image in images: for image in images:
if _image_needs_pushed(image): if _image_needs_pushed(image):
remote_tag = remote_tag_for_image_unique(image, remote_repo_url, deployment_context.id) remote_tag = remote_tag_for_image(image, remote_repo_url)
if opts.o.verbose: if opts.o.verbose:
print(f"Pushing image {remote_tag}") print(f"Pushing image {remote_tag}")
docker.image.push(remote_tag) docker.image.push(remote_tag)

View File

@ -14,7 +14,6 @@
# along with this program. If not, see <http:#www.gnu.org/licenses/>. # along with this program. If not, see <http:#www.gnu.org/licenses/>.
import os import os
import base64
from kubernetes import client from kubernetes import client
from typing import Any, List, Set from typing import Any, List, Set
@ -27,7 +26,7 @@ from stack_orchestrator.deploy.k8s.helpers import envs_from_environment_variable
from stack_orchestrator.deploy.deploy_util import parsed_pod_files_map_from_file_names, images_for_deployment from stack_orchestrator.deploy.deploy_util import parsed_pod_files_map_from_file_names, images_for_deployment
from stack_orchestrator.deploy.deploy_types import DeployEnvVars from stack_orchestrator.deploy.deploy_types import DeployEnvVars
from stack_orchestrator.deploy.spec import Spec, Resources, ResourceLimits from stack_orchestrator.deploy.spec import Spec, Resources, ResourceLimits
from stack_orchestrator.deploy.images import remote_tag_for_image_unique from stack_orchestrator.deploy.images import remote_tag_for_image
DEFAULT_VOLUME_RESOURCES = Resources({ DEFAULT_VOLUME_RESOURCES = Resources({
"reservations": {"storage": "2Gi"} "reservations": {"storage": "2Gi"}
@ -79,40 +78,28 @@ class ClusterInfo:
if (opts.o.debug): if (opts.o.debug):
print(f"Env vars: {self.environment_variables.map}") print(f"Env vars: {self.environment_variables.map}")
def get_nodeports(self): def get_nodeport(self):
nodeports = []
for pod_name in self.parsed_pod_yaml_map: for pod_name in self.parsed_pod_yaml_map:
pod = self.parsed_pod_yaml_map[pod_name] pod = self.parsed_pod_yaml_map[pod_name]
services = pod["services"] services = pod["services"]
for service_name in services: for service_name in services:
service_info = services[service_name] service_info = services[service_name]
if "ports" in service_info: if "ports" in service_info:
for raw_port in [str(p) for p in service_info["ports"]]: port = int(service_info["ports"][0])
if opts.o.debug: if opts.o.debug:
print(f"service port: {raw_port}") print(f"service port: {port}")
if ":" in raw_port: service = client.V1Service(
parts = raw_port.split(":") metadata=client.V1ObjectMeta(name=f"{self.app_name}-nodeport"),
if len(parts) != 2: spec=client.V1ServiceSpec(
raise Exception(f"Invalid port definition: {raw_port}") type="NodePort",
node_port = int(parts[0]) ports=[client.V1ServicePort(
pod_port = int(parts[1]) port=port,
else: target_port=port
node_port = None )],
pod_port = int(raw_port) selector={"app": self.app_name}
service = client.V1Service( )
metadata=client.V1ObjectMeta(name=f"{self.app_name}-nodeport-{pod_port}"), )
spec=client.V1ServiceSpec( return service
type="NodePort",
ports=[client.V1ServicePort(
port=pod_port,
target_port=pod_port,
node_port=node_port
)],
selector={"app": self.app_name}
)
)
nodeports.append(service)
return nodeports
def get_ingress(self, use_tls=False, certificate=None, cluster_issuer="letsencrypt-prod"): def get_ingress(self, use_tls=False, certificate=None, cluster_issuer="letsencrypt-prod"):
# No ingress for a deployment that has no http-proxy defined, for now # No ingress for a deployment that has no http-proxy defined, for now
@ -261,12 +248,12 @@ class ClusterInfo:
for f in os.listdir(cfg_map_path): for f in os.listdir(cfg_map_path):
full_path = os.path.join(cfg_map_path, f) full_path = os.path.join(cfg_map_path, f)
if os.path.isfile(full_path): if os.path.isfile(full_path):
data[f] = base64.b64encode(open(full_path, 'rb').read()).decode('ASCII') data[f] = open(full_path, 'rt').read()
spec = client.V1ConfigMap( spec = client.V1ConfigMap(
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{cfg_map_name}", metadata=client.V1ObjectMeta(name=f"{self.app_name}-{cfg_map_name}",
labels={"configmap-label": cfg_map_name}), labels={"configmap-label": cfg_map_name}),
binary_data=data data=data
) )
result.append(spec) result.append(spec)
return result return result
@ -339,11 +326,8 @@ class ClusterInfo:
if opts.o.debug: if opts.o.debug:
print(f"Merged envs: {envs}") print(f"Merged envs: {envs}")
# Re-write the image tag for remote deployment # Re-write the image tag for remote deployment
# Note self.app_name has the same value as deployment_id image_to_use = remote_tag_for_image(
image_to_use = remote_tag_for_image_unique( image, self.spec.get_image_registry()) if self.spec.get_image_registry() is not None else image
image,
self.spec.get_image_registry(),
self.app_name) if self.spec.get_image_registry() is not None else image
volume_mounts = volume_mounts_for_service(self.parsed_pod_yaml_map, service_name) volume_mounts = volume_mounts_for_service(self.parsed_pod_yaml_map, service_name)
container = client.V1Container( container = client.V1Container(
name=container_name, name=container_name,
@ -366,8 +350,6 @@ class ClusterInfo:
annotations = None annotations = None
labels = {"app": self.app_name} labels = {"app": self.app_name}
affinity = None
tolerations = None
if self.spec.get_annotations(): if self.spec.get_annotations():
annotations = {} annotations = {}
@ -380,60 +362,17 @@ class ClusterInfo:
for service_name in services: for service_name in services:
labels[key.replace("{name}", service_name)] = value labels[key.replace("{name}", service_name)] = value
if self.spec.get_node_affinities():
affinities = []
for rule in self.spec.get_node_affinities():
# TODO add some input validation here
label_name = rule['label']
label_value = rule['value']
affinities.append(client.V1NodeSelectorTerm(
match_expressions=[client.V1NodeSelectorRequirement(
key=label_name,
operator="In",
values=[label_value]
)]
)
)
affinity = client.V1Affinity(
node_affinity=client.V1NodeAffinity(
required_during_scheduling_ignored_during_execution=client.V1NodeSelector(
node_selector_terms=affinities
))
)
if self.spec.get_node_tolerations():
tolerations = []
for toleration in self.spec.get_node_tolerations():
# TODO add some input validation here
toleration_key = toleration['key']
toleration_value = toleration['value']
tolerations.append(client.V1Toleration(
effect="NoSchedule",
key=toleration_key,
operator="Equal",
value=toleration_value
))
template = client.V1PodTemplateSpec( template = client.V1PodTemplateSpec(
metadata=client.V1ObjectMeta( metadata=client.V1ObjectMeta(
annotations=annotations, annotations=annotations,
labels=labels labels=labels
), ),
spec=client.V1PodSpec( spec=client.V1PodSpec(containers=containers, image_pull_secrets=image_pull_secrets, volumes=volumes),
containers=containers,
image_pull_secrets=image_pull_secrets,
volumes=volumes,
affinity=affinity,
tolerations=tolerations
),
) )
spec = client.V1DeploymentSpec( spec = client.V1DeploymentSpec(
replicas=self.spec.get_replicas(), replicas=1, template=template, selector={
template=template, selector={
"matchLabels": "matchLabels":
{"app": self.app_name} {"app": self.app_name}})
}
)
deployment = client.V1Deployment( deployment = client.V1Deployment(
api_version="apps/v1", api_version="apps/v1",

View File

@ -16,7 +16,6 @@ from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from kubernetes import client, config from kubernetes import client, config
from typing import List
from stack_orchestrator import constants from stack_orchestrator import constants
from stack_orchestrator.deploy.deployer import Deployer, DeployerConfigGenerator from stack_orchestrator.deploy.deployer import Deployer, DeployerConfigGenerator
@ -52,14 +51,12 @@ class K8sDeployer(Deployer):
networking_api: client.NetworkingV1Api networking_api: client.NetworkingV1Api
k8s_namespace: str = "default" k8s_namespace: str = "default"
kind_cluster_name: str kind_cluster_name: str
skip_cluster_management: bool
cluster_info: ClusterInfo cluster_info: ClusterInfo
deployment_dir: Path deployment_dir: Path
deployment_context: DeploymentContext deployment_context: DeploymentContext
def __init__(self, type, deployment_context: DeploymentContext, compose_files, compose_project_name, compose_env_file) -> None: def __init__(self, type, deployment_context: DeploymentContext, compose_files, compose_project_name, compose_env_file) -> None:
self.type = type self.type = type
self.skip_cluster_management = False
# TODO: workaround pending refactoring above to cope with being created with a null deployment_context # TODO: workaround pending refactoring above to cope with being created with a null deployment_context
if deployment_context is None: if deployment_context is None:
return return
@ -185,7 +182,6 @@ class K8sDeployer(Deployer):
if len(host_parts) == 2: if len(host_parts) == 2:
host_as_wild = f"*.{host_parts[1]}" host_as_wild = f"*.{host_parts[1]}"
# TODO: resolve method deprecation below
now = datetime.utcnow().replace(tzinfo=timezone.utc) now = datetime.utcnow().replace(tzinfo=timezone.utc)
fmt = "%Y-%m-%dT%H:%M:%S%z" fmt = "%Y-%m-%dT%H:%M:%S%z"
@ -206,16 +202,15 @@ class K8sDeployer(Deployer):
return cert return cert
return None return None
def up(self, detach, skip_cluster_management, services): def up(self, detach, services):
self.skip_cluster_management = skip_cluster_management
if not opts.o.dry_run: if not opts.o.dry_run:
if self.is_kind() and not self.skip_cluster_management: if self.is_kind():
# Create the kind cluster # Create the kind cluster
create_cluster(self.kind_cluster_name, self.deployment_dir.joinpath(constants.kind_config_filename)) create_cluster(self.kind_cluster_name, self.deployment_dir.joinpath(constants.kind_config_filename))
# Ensure the referenced containers are copied into kind # Ensure the referenced containers are copied into kind
load_images_into_kind(self.kind_cluster_name, self.cluster_info.image_set) load_images_into_kind(self.kind_cluster_name, self.cluster_info.image_set)
self.connect_api() self.connect_api()
if self.is_kind() and not self.skip_cluster_management: if self.is_kind():
# Now configure an ingress controller (not installed by default in kind) # Now configure an ingress controller (not installed by default in kind)
install_ingress_for_kind() install_ingress_for_kind()
# Wait for ingress to start (deployment provisioning will fail unless this is done) # Wait for ingress to start (deployment provisioning will fail unless this is done)
@ -251,8 +246,8 @@ class K8sDeployer(Deployer):
if opts.o.debug: if opts.o.debug:
print("No ingress configured") print("No ingress configured")
nodeports: List[client.V1Service] = self.cluster_info.get_nodeports() nodeport: client.V1Service = self.cluster_info.get_nodeport()
for nodeport in nodeports: if nodeport:
if opts.o.debug: if opts.o.debug:
print(f"Sending this nodeport: {nodeport}") print(f"Sending this nodeport: {nodeport}")
if not opts.o.dry_run: if not opts.o.dry_run:
@ -264,8 +259,7 @@ class K8sDeployer(Deployer):
print("NodePort created:") print("NodePort created:")
print(f"{nodeport_resp}") print(f"{nodeport_resp}")
def down(self, timeout, volumes, skip_cluster_management): # noqa: C901 def down(self, timeout, volumes): # noqa: C901
self.skip_cluster_management = skip_cluster_management
self.connect_api() self.connect_api()
# Delete the k8s objects # Delete the k8s objects
@ -348,10 +342,10 @@ class K8sDeployer(Deployer):
if opts.o.debug: if opts.o.debug:
print("No ingress to delete") print("No ingress to delete")
nodeports: List[client.V1Service] = self.cluster_info.get_nodeports() nodeport: client.V1Service = self.cluster_info.get_nodeport()
for nodeport in nodeports: if nodeport:
if opts.o.debug: if opts.o.debug:
print(f"Deleting this nodeport: {nodeport}") print(f"Deleting this nodeport: {ingress}")
try: try:
self.core_api.delete_namespaced_service( self.core_api.delete_namespaced_service(
namespace=self.k8s_namespace, namespace=self.k8s_namespace,
@ -363,7 +357,7 @@ class K8sDeployer(Deployer):
if opts.o.debug: if opts.o.debug:
print("No nodeport to delete") print("No nodeport to delete")
if self.is_kind() and not self.skip_cluster_management: if self.is_kind():
# Destroy the kind cluster # Destroy the kind cluster
destroy_cluster(self.kind_cluster_name) destroy_cluster(self.kind_cluster_name)

View File

@ -117,15 +117,6 @@ class Spec:
def get_annotations(self): def get_annotations(self):
return self.obj.get(constants.annotations_key, {}) return self.obj.get(constants.annotations_key, {})
def get_replicas(self):
return self.obj.get(constants.replicas_key, 1)
def get_node_affinities(self):
return self.obj.get(constants.node_affinities_key, [])
def get_node_tolerations(self):
return self.obj.get(constants.node_tolerations_key, [])
def get_labels(self): def get_labels(self):
return self.obj.get(constants.labels_key, {}) return self.obj.get(constants.labels_key, {})

View File

@ -21,30 +21,16 @@ import sys
import tempfile import tempfile
import time import time
import uuid import uuid
import yaml
import click import click
import gnupg
from stack_orchestrator.deploy.images import remote_image_exists from stack_orchestrator.deploy.images import remote_image_exists, add_tags_to_image
from stack_orchestrator.deploy.webapp import deploy_webapp from stack_orchestrator.deploy.webapp import deploy_webapp
from stack_orchestrator.deploy.webapp.util import ( from stack_orchestrator.deploy.webapp.util import (LaconicRegistryClient, TimedLogger,
AttrDict, build_container_image, push_container_image,
LaconicRegistryClient, file_hash, deploy_to_k8s, publish_deployment,
TimedLogger, hostname_for_deployment_request, generate_hostname_for_app,
build_container_image, match_owner, skip_by_tag)
confirm_auction,
push_container_image,
file_hash,
deploy_to_k8s,
publish_deployment,
hostname_for_deployment_request,
generate_hostname_for_app,
match_owner,
skip_by_tag,
confirm_payment,
load_known_requests,
)
def process_app_deployment_request( def process_app_deployment_request(
@ -59,19 +45,12 @@ def process_app_deployment_request(
image_registry, image_registry,
force_rebuild, force_rebuild,
fqdn_policy, fqdn_policy,
recreate_on_deploy, logger
webapp_deployer_record,
gpg,
private_key_passphrase,
config_upload_dir,
logger,
): ):
logger.log("BEGIN - process_app_deployment_request") logger.log("BEGIN - process_app_deployment_request")
# 1. look up application # 1. look up application
app = laconic.get_record( app = laconic.get_record(app_deployment_request.attributes.application, require=True)
app_deployment_request.attributes.application, require=True
)
logger.log(f"Retrieved app record {app_deployment_request.attributes.application}") logger.log(f"Retrieved app record {app_deployment_request.attributes.application}")
# 2. determine dns # 2. determine dns
@ -82,322 +61,153 @@ def process_app_deployment_request(
if "allow" == fqdn_policy or "preexisting" == fqdn_policy: if "allow" == fqdn_policy or "preexisting" == fqdn_policy:
fqdn = requested_name fqdn = requested_name
else: else:
raise Exception( raise Exception(f"{requested_name} is invalid: only unqualified hostnames are allowed.")
f"{requested_name} is invalid: only unqualified hostnames are allowed."
)
else: else:
fqdn = f"{requested_name}.{default_dns_suffix}" fqdn = f"{requested_name}.{default_dns_suffix}"
# Normalize case (just in case)
fqdn = fqdn.lower()
# 3. check ownership of existing dnsrecord vs this request # 3. check ownership of existing dnsrecord vs this request
dns_lrn = f"{dns_record_namespace}/{fqdn}" dns_crn = f"{dns_record_namespace}/{fqdn}"
dns_record = laconic.get_record(dns_lrn) dns_record = laconic.get_record(dns_crn)
if dns_record: if dns_record:
matched_owner = match_owner(app_deployment_request, dns_record) matched_owner = match_owner(app_deployment_request, dns_record)
if not matched_owner and dns_record.attributes.request: if not matched_owner and dns_record.attributes.request:
matched_owner = match_owner( matched_owner = match_owner(app_deployment_request, laconic.get_record(dns_record.attributes.request, require=True))
app_deployment_request,
laconic.get_record(dns_record.attributes.request, require=True),
)
if matched_owner: if matched_owner:
logger.log(f"Matched DnsRecord ownership: {matched_owner}") logger.log(f"Matched DnsRecord ownership: {matched_owner}")
else: else:
raise Exception( raise Exception("Unable to confirm ownership of DnsRecord %s for request %s" %
"Unable to confirm ownership of DnsRecord %s for request %s" (dns_crn, app_deployment_request.id))
% (dns_lrn, app_deployment_request.id)
)
elif "preexisting" == fqdn_policy: elif "preexisting" == fqdn_policy:
raise Exception( raise Exception(f"No pre-existing DnsRecord {dns_crn} could be found for request {app_deployment_request.id}.")
f"No pre-existing DnsRecord {dns_lrn} could be found for request {app_deployment_request.id}."
)
# 4. get build and runtime config from request # 4. get build and runtime config from request
env = {}
if app_deployment_request.attributes.config:
if "ref" in app_deployment_request.attributes.config:
with open(
f"{config_upload_dir}/{app_deployment_request.attributes.config.ref}",
"rb",
) as file:
record_owner = laconic.get_owner(app_deployment_request)
decrypted = gpg.decrypt_file(file, passphrase=private_key_passphrase)
parsed = AttrDict(yaml.safe_load(decrypted.data))
if record_owner not in parsed.authorized:
raise Exception(
f"{record_owner} not authorized to access config {app_deployment_request.attributes.config.ref}"
)
if "env" in parsed.config:
env.update(parsed.config.env)
if "env" in app_deployment_request.attributes.config:
env.update(app_deployment_request.attributes.config.env)
env_filename = None env_filename = None
if env: if app_deployment_request.attributes.config and "env" in app_deployment_request.attributes.config:
env_filename = tempfile.mktemp() env_filename = tempfile.mktemp()
with open(env_filename, "w") as file: with open(env_filename, 'w') as file:
for k, v in env.items(): for k, v in app_deployment_request.attributes.config["env"].items():
file.write("%s=%s\n" % (k, shlex.quote(str(v)))) file.write("%s=%s\n" % (k, shlex.quote(str(v))))
# 5. determine new or existing deployment # 5. determine new or existing deployment
# a. check for deployment lrn # a. check for deployment crn
app_deployment_lrn = f"{deployment_record_namespace}/{fqdn}" app_deployment_crn = f"{deployment_record_namespace}/{fqdn}"
if app_deployment_request.attributes.deployment: if app_deployment_request.attributes.deployment:
app_deployment_lrn = app_deployment_request.attributes.deployment app_deployment_crn = app_deployment_request.attributes.deployment
if not app_deployment_lrn.startswith(deployment_record_namespace): if not app_deployment_crn.startswith(deployment_record_namespace):
raise Exception( raise Exception("Deployment CRN %s is not in a supported namespace" % app_deployment_request.attributes.deployment)
"Deployment LRN %s is not in a supported namespace"
% app_deployment_request.attributes.deployment
)
deployment_record = laconic.get_record(app_deployment_lrn) deployment_record = laconic.get_record(app_deployment_crn)
deployment_dir = os.path.join(deployment_parent_dir, fqdn) deployment_dir = os.path.join(deployment_parent_dir, fqdn)
# At present we use this to generate a unique but stable ID for the app's host container
# TODO: implement support to derive this transparently from the already-unique deployment id
unique_deployment_id = hashlib.md5(fqdn.encode()).hexdigest()[:16]
deployment_config_file = os.path.join(deployment_dir, "config.env") deployment_config_file = os.path.join(deployment_dir, "config.env")
deployment_container_tag = "laconic-webapp/%s:local" % unique_deployment_id # TODO: Is there any reason not to simplify the hash input to the app_deployment_crn?
deployment_container_tag = "laconic-webapp/%s:local" % hashlib.md5(deployment_dir.encode()).hexdigest()
app_image_shared_tag = f"laconic-webapp/{app.id}:local" app_image_shared_tag = f"laconic-webapp/{app.id}:local"
# b. check for deployment directory (create if necessary) # b. check for deployment directory (create if necessary)
if not os.path.exists(deployment_dir): if not os.path.exists(deployment_dir):
if deployment_record: if deployment_record:
raise Exception( raise Exception("Deployment record %s exists, but not deployment dir %s. Please remove name." %
"Deployment record %s exists, but not deployment dir %s. Please remove name." (app_deployment_crn, deployment_dir))
% (app_deployment_lrn, deployment_dir) print("deploy_webapp", deployment_dir)
) deploy_webapp.create_deployment(ctx, deployment_dir, deployment_container_tag,
logger.log( f"https://{fqdn}", kube_config, image_registry, env_filename)
f"Creating webapp deployment in: {deployment_dir} with container id: {deployment_container_tag}"
)
deploy_webapp.create_deployment(
ctx,
deployment_dir,
deployment_container_tag,
f"https://{fqdn}",
kube_config,
image_registry,
env_filename,
)
elif env_filename: elif env_filename:
shutil.copyfile(env_filename, deployment_config_file) shutil.copyfile(env_filename, deployment_config_file)
needs_k8s_deploy = False needs_k8s_deploy = False
if force_rebuild:
logger.log(
"--force-rebuild is enabled so the container will always be built now, even if nothing has changed in the app"
)
# 6. build container (if needed) # 6. build container (if needed)
# TODO: add a comment that explains what this code is doing (not clear to me) if not deployment_record or deployment_record.attributes.application != app.id:
if (
not deployment_record
or deployment_record.attributes.application != app.id
or force_rebuild
):
needs_k8s_deploy = True needs_k8s_deploy = True
# check if the image already exists # check if the image already exists
shared_tag_exists = remote_image_exists(image_registry, app_image_shared_tag) shared_tag_exists = remote_image_exists(image_registry, app_image_shared_tag)
# Note: in the code below, calls to add_tags_to_image() won't work at present.
# This is because SO deployment code in general re-names the container image
# to be unique to the deployment. This is done transparently
# and so when we call add_tags_to_image() here and try to add tags to the remote image,
# we get the image name wrong. Accordingly I've disabled the relevant code for now.
# This is safe because we are running with --force-rebuild at present
if shared_tag_exists and not force_rebuild: if shared_tag_exists and not force_rebuild:
# simply add our unique tag to the existing image and we are done # simply add our unique tag to the existing image and we are done
logger.log( logger.log(f"Using existing app image {app_image_shared_tag} for {deployment_container_tag}")
f"(SKIPPED) Existing image found for this app: {app_image_shared_tag} " add_tags_to_image(image_registry, app_image_shared_tag, deployment_container_tag)
"tagging it with: {deployment_container_tag} to use in this deployment"
)
# add_tags_to_image(image_registry, app_image_shared_tag, deployment_container_tag)
logger.log("Tag complete") logger.log("Tag complete")
else: else:
extra_build_args = [] # TODO: pull from request extra_build_args = [] # TODO: pull from request
logger.log(f"Building container image: {deployment_container_tag}") logger.log(f"Building container image {deployment_container_tag}")
build_container_image( build_container_image(app, deployment_container_tag, extra_build_args, logger)
app, deployment_container_tag, extra_build_args, logger
)
logger.log("Build complete") logger.log("Build complete")
logger.log(f"Pushing container image: {deployment_container_tag}") logger.log(f"Pushing container image {deployment_container_tag}")
push_container_image(deployment_dir, logger) push_container_image(deployment_dir, logger)
logger.log("Push complete") logger.log("Push complete")
# The build/push commands above will use the unique deployment tag, so now we need to add the shared tag. # The build/push commands above will use the unique deployment tag, so now we need to add the shared tag.
logger.log( logger.log(f"Updating app image tag {app_image_shared_tag} from build of {deployment_container_tag}")
f"(SKIPPED) Adding global app image tag: {app_image_shared_tag} to newly built image: {deployment_container_tag}" add_tags_to_image(image_registry, deployment_container_tag, app_image_shared_tag)
)
# add_tags_to_image(image_registry, deployment_container_tag, app_image_shared_tag)
logger.log("Tag complete") logger.log("Tag complete")
else:
logger.log("Requested app is already deployed, skipping build and image push")
# 7. update config (if needed) # 7. update config (if needed)
if ( if not deployment_record or file_hash(deployment_config_file) != deployment_record.attributes.meta.config:
not deployment_record
or file_hash(deployment_config_file) != deployment_record.attributes.meta.config
):
needs_k8s_deploy = True needs_k8s_deploy = True
# 8. update k8s deployment # 8. update k8s deployment
if needs_k8s_deploy: if needs_k8s_deploy:
deploy_to_k8s(deployment_record, deployment_dir, recreate_on_deploy, logger) deploy_to_k8s(
deployment_record,
deployment_dir,
logger
)
logger.log("Publishing deployment to registry.") logger.log("Publishing deployment to registry.")
publish_deployment( publish_deployment(
laconic, laconic,
app, app,
deployment_record, deployment_record,
app_deployment_lrn, app_deployment_crn,
dns_record, dns_record,
dns_lrn, dns_crn,
deployment_dir, deployment_dir,
app_deployment_request, app_deployment_request,
webapp_deployer_record, logger
logger,
) )
logger.log("Publication complete.") logger.log("Publication complete.")
logger.log("END - process_app_deployment_request") logger.log("END - process_app_deployment_request")
def load_known_requests(filename):
if filename and os.path.exists(filename):
return json.load(open(filename, "r"))
return {}
def dump_known_requests(filename, requests, status="SEEN"): def dump_known_requests(filename, requests, status="SEEN"):
if not filename: if not filename:
return return
known_requests = load_known_requests(filename) known_requests = load_known_requests(filename)
for r in requests: for r in requests:
known_requests[r.id] = {"createTime": r.createTime, "status": status} known_requests[r.id] = {
"createTime": r.createTime,
"status": status
}
with open(filename, "w") as f: with open(filename, "w") as f:
json.dump(known_requests, f) json.dump(known_requests, f)
@click.command() @click.command()
@click.option("--kube-config", help="Provide a config file for a k8s deployment") @click.option("--kube-config", help="Provide a config file for a k8s deployment")
@click.option( @click.option("--laconic-config", help="Provide a config file for laconicd", required=True)
"--laconic-config", help="Provide a config file for laconicd", required=True @click.option("--image-registry", help="Provide a container image registry url for this k8s cluster")
) @click.option("--deployment-parent-dir", help="Create deployment directories beneath this directory", required=True)
@click.option(
"--image-registry",
help="Provide a container image registry url for this k8s cluster",
)
@click.option(
"--deployment-parent-dir",
help="Create deployment directories beneath this directory",
required=True,
)
@click.option("--request-id", help="The ApplicationDeploymentRequest to process") @click.option("--request-id", help="The ApplicationDeploymentRequest to process")
@click.option( @click.option("--discover", help="Discover and process all pending ApplicationDeploymentRequests", is_flag=True, default=False)
"--discover", @click.option("--state-file", help="File to store state about previously seen requests.")
help="Discover and process all pending ApplicationDeploymentRequests", @click.option("--only-update-state", help="Only update the state file, don't process any requests anything.", is_flag=True)
is_flag=True,
default=False,
)
@click.option(
"--state-file", help="File to store state about previously seen requests."
)
@click.option(
"--only-update-state",
help="Only update the state file, don't process any requests anything.",
is_flag=True,
)
@click.option("--dns-suffix", help="DNS domain to use eg, laconic.servesthe.world") @click.option("--dns-suffix", help="DNS domain to use eg, laconic.servesthe.world")
@click.option( @click.option("--fqdn-policy", help="How to handle requests with an FQDN: prohibit, allow, preexisting", default="prohibit")
"--fqdn-policy", @click.option("--record-namespace-dns", help="eg, crn://laconic/dns")
help="How to handle requests with an FQDN: prohibit, allow, preexisting", @click.option("--record-namespace-deployments", help="eg, crn://laconic/deployments")
default="prohibit", @click.option("--dry-run", help="Don't do anything, just report what would be done.", is_flag=True)
) @click.option("--include-tags", help="Only include requests with matching tags (comma-separated).", default="")
@click.option("--record-namespace-dns", help="eg, lrn://laconic/dns", required=True) @click.option("--exclude-tags", help="Exclude requests with matching tags (comma-separated).", default="")
@click.option( @click.option("--force-rebuild", help="Rebuild even if the image already exists.", is_flag=True)
"--record-namespace-deployments", @click.option("--log-dir", help="Output build/deployment logs to directory.", default=None)
help="eg, lrn://laconic/deployments",
required=True,
)
@click.option(
"--dry-run", help="Don't do anything, just report what would be done.", is_flag=True
)
@click.option(
"--include-tags",
help="Only include requests with matching tags (comma-separated).",
default="",
)
@click.option(
"--exclude-tags",
help="Exclude requests with matching tags (comma-separated).",
default="",
)
@click.option(
"--force-rebuild", help="Rebuild even if the image already exists.", is_flag=True
)
@click.option(
"--recreate-on-deploy",
help="Remove and recreate deployments instead of updating them.",
is_flag=True,
)
@click.option(
"--log-dir", help="Output build/deployment logs to directory.", default=None
)
@click.option(
"--min-required-payment",
help="Requests must have a minimum payment to be processed (in alnt)",
default=0,
)
@click.option("--lrn", help="The LRN of this deployer.", required=True)
@click.option(
"--all-requests",
help="Handle requests addressed to anyone (by default only requests to"
"my payment address are examined).",
is_flag=True,
)
@click.option(
"--auction-requests",
help="Handle requests with auction id set (skips payment confirmation).",
is_flag=True,
default=False,
)
@click.option(
"--config-upload-dir",
help="The directory containing uploaded config.",
required=True,
)
@click.option(
"--private-key-file", help="The private key for decrypting config.", required=True
)
@click.option(
"--registry-lock-file", help="File path to use for registry mutex lock", default=None
)
@click.option(
"--private-key-passphrase",
help="The passphrase for the private key.",
required=True,
)
@click.pass_context @click.pass_context
def command( # noqa: C901 def command(ctx, kube_config, laconic_config, image_registry, deployment_parent_dir, # noqa: C901
ctx, request_id, discover, state_file, only_update_state,
kube_config, dns_suffix, fqdn_policy, record_namespace_dns, record_namespace_deployments, dry_run,
laconic_config, include_tags, exclude_tags, force_rebuild, log_dir):
image_registry,
deployment_parent_dir,
request_id,
discover,
state_file,
only_update_state,
dns_suffix,
fqdn_policy,
record_namespace_dns,
record_namespace_deployments,
dry_run,
include_tags,
exclude_tags,
force_rebuild,
recreate_on_deploy,
log_dir,
min_required_payment,
lrn,
config_upload_dir,
private_key_file,
private_key_passphrase,
all_requests,
auction_requests,
registry_lock_file,
):
if request_id and discover: if request_id and discover:
print("Cannot specify both --request-id and --discover", file=sys.stderr) print("Cannot specify both --request-id and --discover", file=sys.stderr)
sys.exit(2) sys.exit(2)
@ -411,289 +221,140 @@ def command( # noqa: C901
sys.exit(2) sys.exit(2)
if not only_update_state: if not only_update_state:
if ( if not record_namespace_dns or not record_namespace_deployments or not dns_suffix:
not record_namespace_dns print("--dns-suffix, --record-namespace-dns, and --record-namespace-deployments are all required", file=sys.stderr)
or not record_namespace_deployments
or not dns_suffix
):
print(
"--dns-suffix, --record-namespace-dns, and --record-namespace-deployments are all required",
file=sys.stderr,
)
sys.exit(2) sys.exit(2)
if fqdn_policy not in ["prohibit", "allow", "preexisting"]: if fqdn_policy not in ["prohibit", "allow", "preexisting"]:
print( print("--fqdn-policy must be one of 'prohibit', 'allow', or 'preexisting'", file=sys.stderr)
"--fqdn-policy must be one of 'prohibit', 'allow', or 'preexisting'",
file=sys.stderr,
)
sys.exit(2) sys.exit(2)
tempdir = tempfile.mkdtemp() # Split CSV and clean up values.
gpg = gnupg.GPG(gnupghome=tempdir) include_tags = [tag.strip() for tag in include_tags.split(",") if tag]
exclude_tags = [tag.strip() for tag in exclude_tags.split(",") if tag]
# Import the deployer's public key laconic = LaconicRegistryClient(laconic_config)
result = gpg.import_keys(open(private_key_file, "rb").read())
if 1 != result.imported:
print(
f"Failed to load private key file: {private_key_file}.",
file=sys.stderr,
)
sys.exit(2)
main_logger = TimedLogger(file=sys.stderr) # Find deployment requests.
# single request
try: if request_id:
# Split CSV and clean up values. requests = [laconic.get_record(request_id, require=True)]
include_tags = [tag.strip() for tag in include_tags.split(",") if tag] # all requests
exclude_tags = [tag.strip() for tag in exclude_tags.split(",") if tag] elif discover:
requests = laconic.app_deployment_requests()
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file)
webapp_deployer_record = laconic.get_record(lrn, require=True)
payment_address = webapp_deployer_record.attributes.paymentAddress
main_logger.log(f"Payment address: {payment_address}")
if min_required_payment and not payment_address:
print(
f"Minimum payment required, but no payment address listed for deployer: {lrn}.",
file=sys.stderr,
)
sys.exit(2)
# Find deployment requests.
# single request
if request_id:
main_logger.log(f"Retrieving request {request_id}...")
requests = [laconic.get_record(request_id, require=True)]
# all requests
elif discover:
main_logger.log("Discovering deployment requests...")
if all_requests:
requests = laconic.app_deployment_requests()
else:
requests = laconic.app_deployment_requests({"deployer": lrn})
if only_update_state:
if not dry_run:
dump_known_requests(state_file, requests)
return
previous_requests = {}
if state_file:
main_logger.log(f"Loading known requests from {state_file}...")
previous_requests = load_known_requests(state_file)
# Collapse related requests.
requests.sort(key=lambda r: r.createTime)
requests.reverse()
requests_by_name = {}
skipped_by_name = {}
for r in requests:
main_logger.log(f"BEGIN: Examining request {r.id}")
result = "PENDING"
try:
if (
r.id in previous_requests
and previous_requests[r.id].get("status", "") != "RETRY"
):
main_logger.log(f"Skipping request {r.id}, we've already seen it.")
result = "SKIP"
continue
app = laconic.get_record(r.attributes.application)
if not app:
main_logger.log(f"Skipping request {r.id}, cannot locate app.")
result = "ERROR"
continue
requested_name = r.attributes.dns
if not requested_name:
requested_name = generate_hostname_for_app(app)
main_logger.log(
"Generating name %s for request %s." % (requested_name, r.id)
)
if (
requested_name in skipped_by_name
or requested_name in requests_by_name
):
main_logger.log(
"Ignoring request %s, it has been superseded." % r.id
)
result = "SKIP"
continue
if skip_by_tag(r, include_tags, exclude_tags):
main_logger.log(
"Skipping request %s, filtered by tag (include %s, exclude %s, present %s)"
% (r.id, include_tags, exclude_tags, r.attributes.tags)
)
skipped_by_name[requested_name] = r
result = "SKIP"
continue
main_logger.log(
"Found pending request %s to run application %s on %s."
% (r.id, r.attributes.application, requested_name)
)
requests_by_name[requested_name] = r
except Exception as e:
result = "ERROR"
main_logger.log(f"ERROR examining request {r.id}: " + str(e))
finally:
main_logger.log(f"DONE Examining request {r.id} with result {result}.")
if result in ["ERROR"]:
dump_known_requests(state_file, [r], status=result)
# Find deployments.
main_logger.log("Discovering existing app deployments...")
if all_requests:
deployments = laconic.app_deployments()
else:
deployments = laconic.app_deployments({"deployer": lrn})
deployments_by_request = {}
for d in deployments:
if d.attributes.request:
deployments_by_request[d.attributes.request] = d
# Find removal requests.
main_logger.log("Discovering deployment removal and cancellation requests...")
cancellation_requests = {}
removal_requests = laconic.app_deployment_removal_requests()
for r in removal_requests:
if r.attributes.request:
cancellation_requests[r.attributes.request] = r
requests_to_check_for_payment = []
for r in requests_by_name.values():
if r.id in cancellation_requests and match_owner(
cancellation_requests[r.id], r
):
main_logger.log(
f"Found deployment cancellation request for {r.id} at {cancellation_requests[r.id].id}"
)
elif r.id in deployments_by_request:
main_logger.log(
f"Found satisfied request for {r.id} at {deployments_by_request[r.id].id}"
)
else:
if (
r.id in previous_requests
and previous_requests[r.id].get("status", "") != "RETRY"
):
main_logger.log(
f"Skipping unsatisfied request {r.id} because we have seen it before."
)
else:
main_logger.log(f"Request {r.id} needs to processed.")
requests_to_check_for_payment.append(r)
requests_to_execute = []
for r in requests_to_check_for_payment:
if r.attributes.auction:
if auction_requests:
if confirm_auction(
laconic,
r,
lrn,
payment_address,
main_logger
):
main_logger.log(f"{r.id}: Auction confirmed.")
requests_to_execute.append(r)
else:
main_logger.log(
f"Skipping request {r.id}: unable to verify auction."
)
dump_known_requests(state_file, [r], status="SKIP")
else:
main_logger.log(
f"Skipping request {r.id}: not handling requests with auction."
)
dump_known_requests(state_file, [r], status="SKIP")
elif min_required_payment:
main_logger.log(f"{r.id}: Confirming payment...")
if confirm_payment(
laconic,
r,
payment_address,
min_required_payment,
main_logger,
):
main_logger.log(f"{r.id}: Payment confirmed.")
requests_to_execute.append(r)
else:
main_logger.log(
f"Skipping request {r.id}: unable to verify payment."
)
dump_known_requests(state_file, [r], status="UNPAID")
else:
requests_to_execute.append(r)
main_logger.log(
"Found %d unsatisfied request(s) to process." % len(requests_to_execute)
)
if only_update_state:
if not dry_run: if not dry_run:
for r in requests_to_execute: dump_known_requests(state_file, requests)
main_logger.log(f"DEPLOYING {r.id}: BEGIN") return
dump_known_requests(state_file, [r], "DEPLOYING")
status = "ERROR"
run_log_file = None
run_reg_client = laconic
try:
run_id = f"{r.id}-{str(time.time()).split('.')[0]}-{str(uuid.uuid4()).split('-')[0]}"
if log_dir:
run_log_dir = os.path.join(log_dir, r.id)
if not os.path.exists(run_log_dir):
os.mkdir(run_log_dir)
run_log_file_path = os.path.join(run_log_dir, f"{run_id}.log")
main_logger.log(
f"Directing deployment logs to: {run_log_file_path}"
)
run_log_file = open(run_log_file_path, "wt")
run_reg_client = LaconicRegistryClient(
laconic_config, log_file=run_log_file, mutex_lock_file=registry_lock_file
)
build_logger = TimedLogger(run_id, run_log_file) previous_requests = load_known_requests(state_file)
build_logger.log("Processing ...")
process_app_deployment_request( # Collapse related requests.
ctx, requests.sort(key=lambda r: r.createTime)
run_reg_client, requests.reverse()
r, requests_by_name = {}
record_namespace_deployments, skipped_by_name = {}
record_namespace_dns, for r in requests:
dns_suffix, if r.id in previous_requests and previous_requests[r.id].get("status", "") != "RETRY":
os.path.abspath(deployment_parent_dir), print(f"Skipping request {r.id}, we've already seen it.")
kube_config, continue
image_registry,
force_rebuild, app = laconic.get_record(r.attributes.application)
fqdn_policy, if not app:
recreate_on_deploy, print("Skipping request %s, cannot locate app." % r.id)
webapp_deployer_record, continue
gpg,
private_key_passphrase, requested_name = r.attributes.dns
config_upload_dir, if not requested_name:
build_logger, requested_name = generate_hostname_for_app(app)
) print("Generating name %s for request %s." % (requested_name, r.id))
status = "DEPLOYED"
except Exception as e: if requested_name in skipped_by_name or requested_name in requests_by_name:
main_logger.log(f"ERROR {r.id}:" + str(e)) print("Ignoring request %s, it has been superseded." % r.id)
build_logger.log("ERROR: " + str(e)) continue
finally:
main_logger.log(f"DEPLOYING {r.id}: END - {status}") if skip_by_tag(r, include_tags, exclude_tags):
if build_logger: print("Skipping request %s, filtered by tag (include %s, exclude %s, present %s)" % (r.id,
build_logger.log( include_tags,
f"DONE with status {status}", exclude_tags,
show_step_time=False, r.attributes.tags))
show_total_time=True, skipped_by_name[requested_name] = r
) continue
dump_known_requests(state_file, [r], status)
if run_log_file: print("Found request %s to run application %s on %s." % (r.id, r.attributes.application, requested_name))
run_log_file.close() requests_by_name[requested_name] = r
except Exception as e:
main_logger.log("UNCAUGHT ERROR:" + str(e)) # Find deployments.
raise e deployments = laconic.app_deployments()
finally: deployments_by_request = {}
shutil.rmtree(tempdir, ignore_errors=True) for d in deployments:
if d.attributes.request:
deployments_by_request[d.attributes.request] = d
# Find removal requests.
cancellation_requests = {}
removal_requests = laconic.app_deployment_removal_requests()
for r in removal_requests:
if r.attributes.request:
cancellation_requests[r.attributes.request] = r
requests_to_execute = []
for r in requests_by_name.values():
if r.id in cancellation_requests and match_owner(cancellation_requests[r.id], r):
print(f"Found deployment cancellation request for {r.id} at {cancellation_requests[r.id].id}")
elif r.id in deployments_by_request:
print(f"Found satisfied request for {r.id} at {deployments_by_request[r.id].id}")
else:
if r.id not in previous_requests:
print(f"Request {r.id} needs to processed.")
requests_to_execute.append(r)
else:
print(
f"Skipping unsatisfied request {r.id} because we have seen it before."
)
print("Found %d unsatisfied request(s) to process." % len(requests_to_execute))
if not dry_run:
for r in requests_to_execute:
dump_known_requests(state_file, [r], "DEPLOYING")
status = "ERROR"
run_log_file = None
run_reg_client = laconic
try:
run_id = f"{r.id}-{str(time.time()).split('.')[0]}-{str(uuid.uuid4()).split('-')[0]}"
if log_dir:
run_log_dir = os.path.join(log_dir, r.id)
if not os.path.exists(run_log_dir):
os.mkdir(run_log_dir)
run_log_file_path = os.path.join(run_log_dir, f"{run_id}.log")
print(f"Directing deployment logs to: {run_log_file_path}")
run_log_file = open(run_log_file_path, "wt")
run_reg_client = LaconicRegistryClient(laconic_config, log_file=run_log_file)
logger = TimedLogger(run_id, run_log_file)
logger.log("Processing ...")
process_app_deployment_request(
ctx,
run_reg_client,
r,
record_namespace_deployments,
record_namespace_dns,
dns_suffix,
os.path.abspath(deployment_parent_dir),
kube_config,
image_registry,
force_rebuild,
fqdn_policy,
logger
)
status = "DEPLOYED"
except Exception as e:
logger.log("ERROR: " + str(e))
finally:
if logger:
logger.log(f"DONE with status {status}", show_step_time=False, show_total_time=True)
dump_known_requests(state_file, [r], status)
if run_log_file:
run_log_file.close()

View File

@ -1,220 +0,0 @@
# Copyright ©2023 Vulcanize
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
import sys
import json
import click
from stack_orchestrator.deploy.webapp.util import (
AttrDict,
LaconicRegistryClient,
TimedLogger,
load_known_requests,
AUCTION_KIND_PROVIDER,
AuctionStatus,
)
def process_app_deployment_auction(
ctx,
laconic: LaconicRegistryClient,
request,
current_status,
reveal_file_path,
bid_amount,
logger,
):
# Fetch auction details
auction_id = request.attributes.auction
auction = laconic.get_auction(auction_id)
if not auction:
raise Exception(f"Unable to locate auction: {auction_id}")
# Check auction kind
if auction.kind != AUCTION_KIND_PROVIDER:
raise Exception(f"Auction kind needs to be ${AUCTION_KIND_PROVIDER}, got {auction.kind}")
if current_status == "PENDING":
# Skip if pending auction not in commit state
if auction.status != AuctionStatus.COMMIT:
logger.log(f"Skipping pending request, auction {auction_id} status: {auction.status}")
return "SKIP", ""
# Check max_price
bid_amount_int = int(bid_amount)
max_price_int = int(auction.maxPrice.quantity)
if max_price_int < bid_amount_int:
logger.log(f"Skipping auction {auction_id} with max_price ({max_price_int}) less than bid_amount ({bid_amount_int})")
return "SKIP", ""
# Bid on the auction
reveal_file_path = laconic.commit_bid(auction_id, bid_amount_int)
logger.log(f"Commited bid on auction {auction_id} with amount {bid_amount_int}")
return "COMMIT", reveal_file_path
if current_status == "COMMIT":
# Return if auction still in commit state
if auction.status == AuctionStatus.COMMIT:
logger.log(f"Auction {auction_id} status: {auction.status}")
return current_status, reveal_file_path
# Reveal bid
if auction.status == AuctionStatus.REVEAL:
laconic.reveal_bid(auction_id, reveal_file_path)
logger.log(f"Revealed bid on auction {auction_id}")
return "REVEAL", reveal_file_path
raise Exception(f"Unexpected auction {auction_id} status: {auction.status}")
if current_status == "REVEAL":
# Return if auction still in reveal state
if auction.status == AuctionStatus.REVEAL:
logger.log(f"Auction {auction_id} status: {auction.status}")
return current_status, reveal_file_path
# Return if auction is completed
if auction.status == AuctionStatus.COMPLETED:
logger.log(f"Auction {auction_id} completed")
return "COMPLETED", ""
raise Exception(f"Unexpected auction {auction_id} status: {auction.status}")
raise Exception(f"Got request with unexpected status: {current_status}")
def dump_known_auction_requests(filename, requests, status="SEEN"):
if not filename:
return
known_requests = load_known_requests(filename)
for r in requests:
known_requests[r.id] = {"revealFile": r.revealFile, "status": status}
with open(filename, "w") as f:
json.dump(known_requests, f)
@click.command()
@click.option(
"--laconic-config", help="Provide a config file for laconicd", required=True
)
@click.option(
"--state-file",
help="File to store state about previously seen auction requests.",
required=True,
)
@click.option(
"--bid-amount",
help="Bid to place on application deployment auctions (in alnt)",
required=True,
)
@click.option(
"--registry-lock-file", help="File path to use for registry mutex lock", default=None
)
@click.option(
"--dry-run", help="Don't do anything, just report what would be done.", is_flag=True
)
@click.pass_context
def command(
ctx,
laconic_config,
state_file,
bid_amount,
registry_lock_file,
dry_run,
):
if int(bid_amount) < 0:
print("--bid-amount cannot be less than 0", file=sys.stderr)
sys.exit(2)
logger = TimedLogger(file=sys.stderr)
try:
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file)
auctions_requests = laconic.app_deployment_auctions()
previous_requests = {}
logger.log(f"Loading known auctions from {state_file}...")
previous_requests = load_known_requests(state_file)
# Process new requests first
auctions_requests.sort(key=lambda r: r.createTime)
auctions_requests.reverse()
requests_to_execute = []
for r in auctions_requests:
logger.log(f"BEGIN: Examining request {r.id}")
result_status = "PENDING"
reveal_file_path = ""
try:
application = r.attributes.application
# Handle already seen requests
if r.id in previous_requests:
# If it's not in commit or reveal status, skip the request as we've already seen it
current_status = previous_requests[r.id].get("status", "")
result_status = current_status
if current_status not in ["COMMIT", "REVEAL"]:
logger.log(f"Skipping request {r.id}, we've already seen it.")
continue
reveal_file_path = previous_requests[r.id].get("revealFile", "")
logger.log(f"Found existing auction request {r.id} for application {application}, status {current_status}.")
else:
# It's a fresh request, check application record
app = laconic.get_record(application)
if not app:
logger.log(f"Skipping request {r.id}, cannot locate app.")
result_status = "ERROR"
continue
logger.log(f"Found pending auction request {r.id} for application {application}.")
# Add requests to be processed
requests_to_execute.append((r, result_status, reveal_file_path))
except Exception as e:
result_status = "ERROR"
logger.log(f"ERROR: examining request {r.id}: " + str(e))
finally:
logger.log(f"DONE: Examining request {r.id} with result {result_status}.")
if result_status in ["ERROR"]:
dump_known_auction_requests(state_file, [AttrDict({"id": r.id, "revealFile": reveal_file_path})], result_status)
logger.log(f"Found {len(requests_to_execute)} request(s) to process.")
if not dry_run:
for r, current_status, reveal_file_path in requests_to_execute:
logger.log(f"Processing {r.id}: BEGIN")
result_status = "ERROR"
try:
result_status, reveal_file_path = process_app_deployment_auction(
ctx,
laconic,
r,
current_status,
reveal_file_path,
bid_amount,
logger,
)
except Exception as e:
logger.log(f"ERROR {r.id}:" + str(e))
finally:
logger.log(f"Processing {r.id}: END - {result_status}")
dump_known_auction_requests(state_file, [AttrDict({"id": r.id, "revealFile": reveal_file_path})], result_status)
except Exception as e:
logger.log("UNCAUGHT ERROR:" + str(e))
raise e

View File

@ -1,124 +0,0 @@
# Copyright ©2023 Vulcanize
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
import sys
import click
import yaml
from stack_orchestrator.deploy.webapp.util import (
AUCTION_KIND_PROVIDER,
TOKEN_DENOM,
LaconicRegistryClient,
)
def fatal(msg: str):
print(msg, file=sys.stderr)
sys.exit(1)
@click.command()
@click.option(
"--laconic-config", help="Provide a config file for laconicd", required=True
)
@click.option(
"--app",
help="The LRN of the application to deploy.",
required=True,
)
@click.option(
"--commits-duration",
help="Auction commits duration (in seconds) (default: 600).",
default=600,
)
@click.option(
"--reveals-duration",
help="Auction reveals duration (in seconds) (default: 600).",
default=600,
)
@click.option(
"--commit-fee",
help="Auction bid commit fee (in alnt) (default: 100000).",
default=100000,
)
@click.option(
"--reveal-fee",
help="Auction bid reveal fee (in alnt) (default: 100000).",
default=100000,
)
@click.option(
"--max-price",
help="Max acceptable bid price (in alnt).",
required=True,
)
@click.option(
"--num-providers",
help="Max acceptable bid price (in alnt).",
required=True,
)
@click.option(
"--dry-run",
help="Don't publish anything, just report what would be done.",
is_flag=True,
)
@click.pass_context
def command(
ctx,
laconic_config,
app,
commits_duration,
reveals_duration,
commit_fee,
reveal_fee,
max_price,
num_providers,
dry_run,
):
laconic = LaconicRegistryClient(laconic_config)
app_record = laconic.get_record(app)
if not app_record:
fatal(f"Unable to locate app: {app}")
provider_auction_params = {
"kind": AUCTION_KIND_PROVIDER,
"commits_duration": commits_duration,
"reveals_duration": reveals_duration,
"denom": TOKEN_DENOM,
"commit_fee": commit_fee,
"reveal_fee": reveal_fee,
"max_price": max_price,
"num_providers": num_providers,
}
auction_id = laconic.create_deployment_auction(provider_auction_params)
print("Deployment auction created:", auction_id)
if not auction_id:
fatal("Unable to create a provider auction")
deployment_auction = {
"record": {
"type": "ApplicationDeploymentAuction",
"application": app,
"auction": auction_id,
}
}
if dry_run:
print(yaml.dump(deployment_auction))
return
# Publish the deployment auction record
laconic.publish(deployment_auction)

View File

@ -1,91 +0,0 @@
# Copyright ©2023 Vulcanize
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
import base64
import click
import sys
import yaml
from urllib.parse import urlparse
from stack_orchestrator.deploy.webapp.util import LaconicRegistryClient
@click.command()
@click.option(
"--laconic-config", help="Provide a config file for laconicd", required=True
)
@click.option("--api-url", help="The API URL of the deployer.", required=True)
@click.option(
"--public-key-file",
help="The public key to use. This should be a binary file.",
required=True,
)
@click.option(
"--lrn", help="eg, lrn://laconic/deployers/my.deployer.name", required=True
)
@click.option(
"--payment-address",
help="The address to which payments should be made. "
"Default is the current laconic account.",
default=None,
)
@click.option(
"--min-required-payment",
help="List the minimum required payment (in alnt) to process a deployment request.",
default=0,
)
@click.option(
"--dry-run",
help="Don't publish anything, just report what would be done.",
is_flag=True,
)
@click.pass_context
def command( # noqa: C901
ctx,
laconic_config,
api_url,
public_key_file,
lrn,
payment_address,
min_required_payment,
dry_run,
):
laconic = LaconicRegistryClient(laconic_config)
if not payment_address:
payment_address = laconic.whoami().address
pub_key = base64.b64encode(open(public_key_file, "rb").read()).decode("ASCII")
hostname = urlparse(api_url).hostname
webapp_deployer_record = {
"record": {
"type": "WebappDeployer",
"version": "1.0.0",
"apiUrl": api_url,
"name": hostname,
"publicKey": pub_key,
"paymentAddress": payment_address,
}
}
if min_required_payment:
webapp_deployer_record["record"][
"minimumPayment"
] = f"{min_required_payment}alnt"
if dry_run:
yaml.dump(webapp_deployer_record, sys.stdout)
return
laconic.publish(webapp_deployer_record, [lrn])

View File

@ -1,77 +0,0 @@
from functools import wraps
import os
import time
# Define default file path for the lock
DEFAULT_LOCK_FILE_PATH = "/tmp/registry_mutex_lock_file"
LOCK_TIMEOUT = 30
LOCK_RETRY_INTERVAL = 3
def acquire_lock(client, lock_file_path, timeout):
# Lock alreay acquired by the current client
if client.mutex_lock_acquired:
return
while True:
try:
# Check if lock file exists and is potentially stale
if os.path.exists(lock_file_path):
with open(lock_file_path, 'r') as lock_file:
timestamp = float(lock_file.read().strip())
# If lock is stale, remove the lock file
if time.time() - timestamp > timeout:
print(f"Stale lock detected, removing lock file {lock_file_path}")
os.remove(lock_file_path)
else:
print(f"Lock file {lock_file_path} exists and is recent, waiting...")
time.sleep(LOCK_RETRY_INTERVAL)
continue
# Try to create a new lock file with the current timestamp
fd = os.open(lock_file_path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
with os.fdopen(fd, 'w') as lock_file:
lock_file.write(str(time.time()))
client.mutex_lock_acquired = True
print(f"Registry lock acquired, {lock_file_path}")
# Lock successfully acquired
return
except FileExistsError:
print(f"Lock file {lock_file_path} exists, waiting...")
time.sleep(LOCK_RETRY_INTERVAL)
def release_lock(client, lock_file_path):
try:
os.remove(lock_file_path)
client.mutex_lock_acquired = False
print(f"Registry lock released, {lock_file_path}")
except FileNotFoundError:
# Lock file already removed
pass
def registry_mutex():
def decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
lock_file_path = DEFAULT_LOCK_FILE_PATH
if self.mutex_lock_file:
lock_file_path = self.mutex_lock_file
# Acquire the lock before running the function
acquire_lock(self, lock_file_path, LOCK_TIMEOUT)
try:
return func(self, *args, **kwargs)
finally:
# Release the lock after the function completes
release_lock(self, lock_file_path)
return wrapper
return decorator

View File

@ -1,262 +0,0 @@
# Copyright ©2023 Vulcanize
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
import shutil
import sys
import tempfile
from datetime import datetime
import base64
import gnupg
import click
import requests
import yaml
from stack_orchestrator.deploy.webapp.util import (
AUCTION_KIND_PROVIDER,
AuctionStatus,
LaconicRegistryClient,
)
from dotenv import dotenv_values
def fatal(msg: str):
print(msg, file=sys.stderr)
sys.exit(1)
@click.command()
@click.option(
"--laconic-config", help="Provide a config file for laconicd", required=True
)
@click.option(
"--app",
help="The LRN of the application to deploy.",
required=True,
)
@click.option(
"--auction-id",
help="Deployment auction id. Can be used instead of deployer and payment.",
)
@click.option(
"--deployer",
help="The LRN of the deployer to process this request.",
)
@click.option("--env-file", help="environment file for webapp")
@click.option("--config-ref", help="The ref of an existing config upload to use.")
@click.option(
"--make-payment",
help="The payment to make (in alnt). The value should be a number or 'auto' to use the deployer's minimum required payment.",
)
@click.option(
"--use-payment", help="The TX id of an existing, unused payment", default=None
)
@click.option("--dns", help="the DNS name to request (default is autogenerated)")
@click.option(
"--dry-run",
help="Don't publish anything, just report what would be done.",
is_flag=True,
)
@click.pass_context
def command( # noqa: C901
ctx,
laconic_config,
app,
auction_id,
deployer,
env_file,
config_ref,
make_payment,
use_payment,
dns,
dry_run,
):
if auction_id and deployer:
print("Cannot specify both --auction-id and --deployer", file=sys.stderr)
sys.exit(2)
if not auction_id and not deployer:
print("Must specify either --auction-id or --deployer", file=sys.stderr)
sys.exit(2)
if auction_id and (make_payment or use_payment):
print("Cannot specify --auction-id with --make-payment or --use-payment", file=sys.stderr)
sys.exit(2)
if env_file and config_ref:
fatal("Cannot use --env-file and --config-ref at the same time.")
laconic = LaconicRegistryClient(laconic_config)
app_record = laconic.get_record(app)
if not app_record:
fatal(f"Unable to locate app: {app}")
# Deployers to send requests to
deployer_records = []
auction = None
auction_winners = None
if auction_id:
# Fetch auction record for given auction
auction_records_by_id = laconic.app_deployment_auctions({"auction": auction_id})
if len(auction_records_by_id) == 0:
fatal(f"Unable to locate record for auction: {auction_id}")
# Cross check app against application in the auction record
auction_app = auction_records_by_id[0].attributes.application
if auction_app != app:
fatal(f"Requested application {app} does not match application from auction record {auction_app}")
# Fetch auction details
auction = laconic.get_auction(auction_id)
if not auction:
fatal(f"Unable to locate auction: {auction_id}")
# Check auction owner
if auction.ownerAddress != laconic.whoami().address:
fatal(f"Auction {auction_id} owner mismatch")
# Check auction kind
if auction.kind != AUCTION_KIND_PROVIDER:
fatal(f"Auction kind needs to be ${AUCTION_KIND_PROVIDER}, got {auction.kind}")
# Check auction status
if auction.status != AuctionStatus.COMPLETED:
fatal(f"Auction {auction_id} not completed yet, status {auction.status}")
# Check that winner list is not empty
if len(auction.winnerAddresses) == 0:
fatal(f"Auction {auction_id} has no winners")
auction_winners = auction.winnerAddresses
# Get deployer record for all the auction winners
for auction_winner in auction_winners:
# TODO: Match auction winner address with provider address?
deployer_records_by_owner = laconic.webapp_deployers({"paymentAddress": auction_winner})
if len(deployer_records_by_owner) == 0:
print(f"WARNING: Unable to locate deployer for auction winner {auction_winner}")
# Take first record with name set
target_deployer_record = deployer_records_by_owner[0]
for r in deployer_records_by_owner:
if len(r.names) > 0:
target_deployer_record = r
break
deployer_records.append(target_deployer_record)
else:
deployer_record = laconic.get_record(deployer)
if not deployer_record:
fatal(f"Unable to locate deployer: {deployer}")
deployer_records.append(deployer_record)
# Create and send request to each deployer
deployment_requests = []
for deployer_record in deployer_records:
# Upload config to deployers if env_file is passed
if env_file:
tempdir = tempfile.mkdtemp()
try:
gpg = gnupg.GPG(gnupghome=tempdir)
# Import the deployer's public key
result = gpg.import_keys(
base64.b64decode(deployer_record.attributes.publicKey)
)
if 1 != result.imported:
fatal("Failed to import deployer's public key.")
recip = gpg.list_keys()[0]["uids"][0]
# Wrap the config
config = {
# Include account (and payment?) details
"authorized": [laconic.whoami().address],
"config": {"env": dict(dotenv_values(env_file))},
}
serialized = yaml.dump(config)
# Encrypt
result = gpg.encrypt(serialized, recip, always_trust=True, armor=False)
if not result.ok:
fatal("Failed to encrypt config.")
# Upload it to the deployer's API
response = requests.post(
f"{deployer_record.attributes.apiUrl}/upload/config",
data=result.data,
headers={"Content-Type": "application/octet-stream"},
)
if not response.ok:
response.raise_for_status()
config_ref = response.json()["id"]
finally:
shutil.rmtree(tempdir, ignore_errors=True)
target_deployer = deployer
if (not deployer) and len(deployer_record.names):
target_deployer = deployer_record.names[0]
deployment_request = {
"record": {
"type": "ApplicationDeploymentRequest",
"application": app,
"version": "1.0.0",
"name": f"{app_record.attributes.name}@{app_record.attributes.version}",
"deployer": target_deployer,
"meta": {"when": str(datetime.utcnow())},
}
}
if auction_id:
deployment_request["record"]["auction"] = auction_id
if config_ref:
deployment_request["record"]["config"] = {"ref": config_ref}
if dns:
deployment_request["record"]["dns"] = dns.lower()
if make_payment:
amount = 0
if dry_run:
deployment_request["record"]["payment"] = "DRY_RUN"
elif "auto" == make_payment:
if "minimumPayment" in deployer_record.attributes:
amount = int(
deployer_record.attributes.minimumPayment.replace("alnt", "")
)
else:
amount = make_payment
if amount:
receipt = laconic.send_tokens(
deployer_record.attributes.paymentAddress, amount
)
deployment_request["record"]["payment"] = receipt.tx.hash
print("Payment TX:", receipt.tx.hash)
elif use_payment:
deployment_request["record"]["payment"] = use_payment
deployment_requests.append(deployment_request)
# Send all requests
for deployment_request in deployment_requests:
if dry_run:
print(yaml.dump(deployment_request))
continue
laconic.publish(deployment_request)

View File

@ -1,106 +0,0 @@
# Copyright ©2023 Vulcanize
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
import sys
import click
import yaml
from stack_orchestrator.deploy.webapp.util import (LaconicRegistryClient)
def fatal(msg: str):
print(msg, file=sys.stderr)
sys.exit(1)
@click.command()
@click.option(
"--laconic-config", help="Provide a config file for laconicd", required=True
)
@click.option(
"--deployer",
help="The LRN of the deployer to process this request.",
required=True
)
@click.option(
"--deployment",
help="Deployment record (ApplicationDeploymentRecord) id of the deployment to remove.",
required=True,
)
@click.option(
"--make-payment",
help="The payment to make (in alnt). The value should be a number or 'auto' to use the deployer's minimum required payment.",
)
@click.option(
"--use-payment", help="The TX id of an existing, unused payment", default=None
)
@click.option(
"--dry-run",
help="Don't publish anything, just report what would be done.",
is_flag=True,
)
@click.pass_context
def command(
ctx,
laconic_config,
deployer,
deployment,
make_payment,
use_payment,
dry_run,
):
if make_payment and use_payment:
fatal("Cannot use --make-payment and --use-payment at the same time.")
laconic = LaconicRegistryClient(laconic_config)
deployer_record = laconic.get_record(deployer)
if not deployer_record:
fatal(f"Unable to locate deployer: {deployer}")
undeployment_request = {
"record": {
"type": "ApplicationDeploymentRemovalRequest",
"version": "1.0.0",
"deployer": deployer,
"deployment": deployment,
}
}
if make_payment:
amount = 0
if dry_run:
undeployment_request["record"]["payment"] = "DRY_RUN"
elif "auto" == make_payment:
if "minimumPayment" in deployer_record.attributes:
amount = int(
deployer_record.attributes.minimumPayment.replace("alnt", "")
)
else:
amount = make_payment
if amount:
receipt = laconic.send_tokens(
deployer_record.attributes.paymentAddress, amount
)
undeployment_request["record"]["payment"] = receipt.tx.hash
print("Payment TX:", receipt.tx.hash)
elif use_payment:
undeployment_request["record"]["payment"] = use_payment
if dry_run:
print(yaml.dump(undeployment_request))
return
laconic.publish(undeployment_request)

View File

@ -20,33 +20,18 @@ import sys
import click import click
from stack_orchestrator.deploy.webapp.util import ( from stack_orchestrator.deploy.webapp.util import LaconicRegistryClient, match_owner, skip_by_tag
TimedLogger,
LaconicRegistryClient,
match_owner,
skip_by_tag,
confirm_payment,
)
main_logger = TimedLogger(file=sys.stderr)
def process_app_removal_request( def process_app_removal_request(ctx,
ctx, laconic: LaconicRegistryClient,
laconic: LaconicRegistryClient, app_removal_request,
app_removal_request, deployment_parent_dir,
deployment_parent_dir, delete_volumes,
delete_volumes, delete_names):
delete_names, deployment_record = laconic.get_record(app_removal_request.attributes.deployment, require=True)
webapp_deployer_record,
):
deployment_record = laconic.get_record(
app_removal_request.attributes.deployment, require=True
)
dns_record = laconic.get_record(deployment_record.attributes.dns, require=True) dns_record = laconic.get_record(deployment_record.attributes.dns, require=True)
deployment_dir = os.path.join( deployment_dir = os.path.join(deployment_parent_dir, dns_record.attributes.name)
deployment_parent_dir, dns_record.attributes.name.lower()
)
if not os.path.exists(deployment_dir): if not os.path.exists(deployment_dir):
raise Exception("Deployment directory %s does not exist." % deployment_dir) raise Exception("Deployment directory %s does not exist." % deployment_dir)
@ -56,18 +41,13 @@ def process_app_removal_request(
# Or of the original deployment request. # Or of the original deployment request.
if not matched_owner and deployment_record.attributes.request: if not matched_owner and deployment_record.attributes.request:
matched_owner = match_owner( matched_owner = match_owner(app_removal_request, laconic.get_record(deployment_record.attributes.request, require=True))
app_removal_request,
laconic.get_record(deployment_record.attributes.request, require=True),
)
if matched_owner: if matched_owner:
main_logger.log("Matched deployment ownership:", matched_owner) print("Matched deployment ownership:", matched_owner)
else: else:
raise Exception( raise Exception("Unable to confirm ownership of deployment %s for removal request %s" %
"Unable to confirm ownership of deployment %s for removal request %s" (deployment_record.id, app_removal_request.id))
% (deployment_record.id, app_removal_request.id)
)
# TODO(telackey): Call the function directly. The easiest way to build the correct click context is to # TODO(telackey): Call the function directly. The easiest way to build the correct click context is to
# exec the process, but it would be better to refactor so we could just call down_operation with the # exec the process, but it would be better to refactor so we could just call down_operation with the
@ -84,13 +64,8 @@ def process_app_removal_request(
"version": "1.0.0", "version": "1.0.0",
"request": app_removal_request.id, "request": app_removal_request.id,
"deployment": deployment_record.id, "deployment": deployment_record.id,
"deployer": webapp_deployer_record.names[0],
} }
} }
if app_removal_request.attributes.payment:
removal_record["record"]["payment"] = app_removal_request.attributes.payment
laconic.publish(removal_record) laconic.publish(removal_record)
if delete_names: if delete_names:
@ -122,84 +97,22 @@ def dump_known_requests(filename, requests):
@click.command() @click.command()
@click.option( @click.option("--laconic-config", help="Provide a config file for laconicd", required=True)
"--laconic-config", help="Provide a config file for laconicd", required=True @click.option("--deployment-parent-dir", help="Create deployment directories beneath this directory", required=True)
)
@click.option(
"--deployment-parent-dir",
help="Create deployment directories beneath this directory",
required=True,
)
@click.option("--request-id", help="The ApplicationDeploymentRemovalRequest to process") @click.option("--request-id", help="The ApplicationDeploymentRemovalRequest to process")
@click.option( @click.option("--discover", help="Discover and process all pending ApplicationDeploymentRemovalRequests",
"--discover", is_flag=True, default=False)
help="Discover and process all pending ApplicationDeploymentRemovalRequests", @click.option("--state-file", help="File to store state about previously seen requests.")
is_flag=True, @click.option("--only-update-state", help="Only update the state file, don't process any requests anything.", is_flag=True)
default=False, @click.option("--delete-names/--preserve-names", help="Delete all names associated with removed deployments.", default=True)
) @click.option("--delete-volumes/--preserve-volumes", default=True, help="delete data volumes")
@click.option( @click.option("--dry-run", help="Don't do anything, just report what would be done.", is_flag=True)
"--state-file", help="File to store state about previously seen requests." @click.option("--include-tags", help="Only include requests with matching tags (comma-separated).", default="")
) @click.option("--exclude-tags", help="Exclude requests with matching tags (comma-separated).", default="")
@click.option(
"--only-update-state",
help="Only update the state file, don't process any requests anything.",
is_flag=True,
)
@click.option(
"--delete-names/--preserve-names",
help="Delete all names associated with removed deployments.",
default=True,
)
@click.option(
"--delete-volumes/--preserve-volumes", default=True, help="delete data volumes"
)
@click.option(
"--dry-run", help="Don't do anything, just report what would be done.", is_flag=True
)
@click.option(
"--include-tags",
help="Only include requests with matching tags (comma-separated).",
default="",
)
@click.option(
"--exclude-tags",
help="Exclude requests with matching tags (comma-separated).",
default="",
)
@click.option(
"--min-required-payment",
help="Requests must have a minimum payment to be processed (in alnt)",
default=0,
)
@click.option("--lrn", help="The LRN of this deployer.", required=True)
@click.option(
"--all-requests",
help="Handle requests addressed to anyone (by default only requests to"
"my payment address are examined).",
is_flag=True,
)
@click.option(
"--registry-lock-file", help="File path to use for registry mutex lock", default=None
)
@click.pass_context @click.pass_context
def command( # noqa: C901 def command(ctx, laconic_config, deployment_parent_dir,
ctx, request_id, discover, state_file, only_update_state,
laconic_config, delete_names, delete_volumes, dry_run, include_tags, exclude_tags):
deployment_parent_dir,
request_id,
discover,
state_file,
only_update_state,
delete_names,
delete_volumes,
dry_run,
include_tags,
exclude_tags,
min_required_payment,
lrn,
all_requests,
registry_lock_file,
):
if request_id and discover: if request_id and discover:
print("Cannot specify both --request-id and --discover", file=sys.stderr) print("Cannot specify both --request-id and --discover", file=sys.stderr)
sys.exit(2) sys.exit(2)
@ -216,55 +129,34 @@ def command( # noqa: C901
include_tags = [tag.strip() for tag in include_tags.split(",") if tag] include_tags = [tag.strip() for tag in include_tags.split(",") if tag]
exclude_tags = [tag.strip() for tag in exclude_tags.split(",") if tag] exclude_tags = [tag.strip() for tag in exclude_tags.split(",") if tag]
laconic = LaconicRegistryClient(laconic_config, log_file=sys.stderr, mutex_lock_file=registry_lock_file) laconic = LaconicRegistryClient(laconic_config)
deployer_record = laconic.get_record(lrn, require=True)
payment_address = deployer_record.attributes.paymentAddress
main_logger.log(f"Payment address: {payment_address}")
if min_required_payment and not payment_address:
print(
f"Minimum payment required, but no payment address listed for deployer: {lrn}.",
file=sys.stderr,
)
sys.exit(2)
# Find deployment removal requests. # Find deployment removal requests.
# single request # single request
if request_id: if request_id:
main_logger.log(f"Retrieving request {request_id}...")
requests = [laconic.get_record(request_id, require=True)] requests = [laconic.get_record(request_id, require=True)]
# TODO: assert record type # TODO: assert record type
# all requests # all requests
elif discover: elif discover:
main_logger.log("Discovering removal requests...") requests = laconic.app_deployment_removal_requests()
if all_requests:
requests = laconic.app_deployment_removal_requests()
else:
requests = laconic.app_deployment_removal_requests({"deployer": lrn})
if only_update_state: if only_update_state:
if not dry_run: if not dry_run:
dump_known_requests(state_file, requests) dump_known_requests(state_file, requests)
return return
previous_requests = {} previous_requests = load_known_requests(state_file)
if state_file:
main_logger.log(f"Loading known requests from {state_file}...")
previous_requests = load_known_requests(state_file)
requests.sort(key=lambda r: r.createTime) requests.sort(key=lambda r: r.createTime)
requests.reverse() requests.reverse()
# Find deployments. # Find deployments.
named_deployments = {} deployments = {}
main_logger.log("Discovering app deployments...") for d in laconic.app_deployments(all=True):
for d in laconic.app_deployments(all=False): deployments[d.id] = d
named_deployments[d.id] = d
# Find removal requests. # Find removal requests.
removals_by_deployment = {} removals_by_deployment = {}
removals_by_request = {} removals_by_request = {}
main_logger.log("Discovering deployment removals...")
for r in laconic.app_deployment_removals(): for r in laconic.app_deployment_removals():
if r.attributes.deployment: if r.attributes.deployment:
# TODO: should we handle CRNs? # TODO: should we handle CRNs?
@ -273,70 +165,33 @@ def command( # noqa: C901
one_per_deployment = {} one_per_deployment = {}
for r in requests: for r in requests:
if not r.attributes.deployment: if not r.attributes.deployment:
main_logger.log( print(f"Skipping removal request {r.id} since it was a cancellation.")
f"Skipping removal request {r.id} since it was a cancellation."
)
elif r.attributes.deployment in one_per_deployment: elif r.attributes.deployment in one_per_deployment:
main_logger.log(f"Skipping removal request {r.id} since it was superseded.") print(f"Skipping removal request {r.id} since it was superseded.")
else: else:
one_per_deployment[r.attributes.deployment] = r one_per_deployment[r.attributes.deployment] = r
requests_to_check_for_payment = []
for r in one_per_deployment.values():
try:
if r.attributes.deployment not in named_deployments:
main_logger.log(
f"Skipping removal request {r.id} for {r.attributes.deployment} because it does"
f"not appear to refer to a live, named deployment."
)
elif skip_by_tag(r, include_tags, exclude_tags):
main_logger.log(
"Skipping removal request %s, filtered by tag (include %s, exclude %s, present %s)"
% (r.id, include_tags, exclude_tags, r.attributes.tags)
)
elif r.id in removals_by_request:
main_logger.log(
f"Found satisfied request for {r.id} at {removals_by_request[r.id].id}"
)
elif r.attributes.deployment in removals_by_deployment:
main_logger.log(
f"Found removal record for indicated deployment {r.attributes.deployment} at "
f"{removals_by_deployment[r.attributes.deployment].id}"
)
else:
if r.id not in previous_requests:
main_logger.log(f"Request {r.id} needs to processed.")
requests_to_check_for_payment.append(r)
else:
main_logger.log(
f"Skipping unsatisfied request {r.id} because we have seen it before."
)
except Exception as e:
main_logger.log(f"ERROR examining {r.id}: {e}")
requests_to_execute = [] requests_to_execute = []
# TODO: Handle requests with auction for r in one_per_deployment.values():
if min_required_payment: if skip_by_tag(r, include_tags, exclude_tags):
for r in requests_to_check_for_payment: print("Skipping removal request %s, filtered by tag (include %s, exclude %s, present %s)" % (r.id,
main_logger.log(f"{r.id}: Confirming payment...") include_tags,
if confirm_payment( exclude_tags,
laconic, r.attributes.tags))
r, elif r.id in removals_by_request:
payment_address, print(f"Found satisfied request for {r.id} at {removals_by_request[r.id].id}")
min_required_payment, elif r.attributes.deployment in removals_by_deployment:
main_logger, print(
): f"Found removal record for indicated deployment {r.attributes.deployment} at "
main_logger.log(f"{r.id}: Payment confirmed.") f"{removals_by_deployment[r.attributes.deployment].id}")
else:
if r.id not in previous_requests:
print(f"Request {r.id} needs to processed.")
requests_to_execute.append(r) requests_to_execute.append(r)
else: else:
main_logger.log(f"Skipping request {r.id}: unable to verify payment.") print(f"Skipping unsatisfied request {r.id} because we have seen it before.")
dump_known_requests(state_file, [r])
else:
requests_to_execute = requests_to_check_for_payment
main_logger.log( print("Found %d unsatisfied request(s) to process." % len(requests_to_execute))
"Found %d unsatisfied request(s) to process." % len(requests_to_execute)
)
if not dry_run: if not dry_run:
for r in requests_to_execute: for r in requests_to_execute:
@ -347,10 +202,7 @@ def command( # noqa: C901
r, r,
os.path.abspath(deployment_parent_dir), os.path.abspath(deployment_parent_dir),
delete_volumes, delete_volumes,
delete_names, delete_names
deployer_record,
) )
except Exception as e:
main_logger.log(f"ERROR processing removal request {r.id}: {e}")
finally: finally:
dump_known_requests(state_file, [r]) dump_known_requests(state_file, [r])

Some files were not shown because too many files have changed in this diff Show More