Compare commits
No commits in common. "main" and "v1.1.0-abc0c24-202406061147" have entirely different histories.
main
...
v1.1.0-abc
@ -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
|
57
.gitea/workflows/fixturenet-eth-plugeth-test.yml
Normal file
57
.gitea/workflows/fixturenet-eth-plugeth-test.yml
Normal 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 }}
|
55
.gitea/workflows/fixturenet-eth-test.yml
Normal file
55
.gitea/workflows/fixturenet-eth-test.yml
Normal 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 }}
|
@ -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"
|
||||||
|
@ -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: |
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
Change this file to trigger running the fixturenet-eth-plugeth-arm-test CI job
|
||||||
|
|
3
.gitea/workflows/triggers/fixturenet-eth-plugeth-test
Normal file
3
.gitea/workflows/triggers/fixturenet-eth-plugeth-test
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Change this file to trigger running the fixturenet-eth-plugeth-test CI job
|
||||||
|
trigger
|
||||||
|
trigger
|
2
.gitea/workflows/triggers/fixturenet-eth-test
Normal file
2
.gitea/workflows/triggers/fixturenet-eth-test
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Change this file to trigger running the fixturenet-eth-test CI job
|
||||||
|
|
@ -4,7 +4,3 @@ Trigger
|
|||||||
Trigger
|
Trigger
|
||||||
Trigger
|
Trigger
|
||||||
Trigger
|
Trigger
|
||||||
Trigger
|
|
||||||
Trigger
|
|
||||||
Trigger
|
|
||||||
Trigger
|
|
||||||
|
@ -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:
|
||||||
```
|
```
|
||||||
|
@ -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).
|
||||||
|
@ -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`
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
@ -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"
|
||||||
```
|
```
|
||||||
|
@ -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
|
|
||||||
|
4
setup.py
4
setup.py
@ -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',
|
||||||
|
@ -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,10 +33,7 @@ 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}')
|
||||||
|
@ -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"
|
||||||
|
@ -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:
|
||||||
|
@ -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"
|
|
@ -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:
|
@ -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:
|
|
||||||
|
@ -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:
|
|
||||||
|
@ -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:
|
|
||||||
|
@ -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:
|
|
||||||
|
@ -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
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -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": ""
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
|
@ -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).
|
||||||
|
@ -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"
|
||||||
|
@ -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).
|
||||||
|
@ -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).
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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}"
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 .
|
||||||
|
@ -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
|
||||||
|
@ -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"]
|
|
@ -1,8 +0,0 @@
|
|||||||
FROM cerc/webapp-base:local
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN yarn
|
|
||||||
|
|
@ -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
|
|
||||||
|
@ -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"
|
|
||||||
}]
|
|
||||||
}
|
|
@ -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
|
|
@ -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}
|
|
@ -0,0 +1,6 @@
|
|||||||
|
FROM cerc/snowballtools-base-backend-base:local
|
||||||
|
|
||||||
|
WORKDIR /app/packages/backend
|
||||||
|
COPY run.sh .
|
||||||
|
|
||||||
|
ENTRYPOINT ["./run.sh"]
|
@ -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
|
@ -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}
|
@ -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
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
registry-sdk
|
laconic-sdk
|
||||||
laconic-registry-cli
|
laconic-registry-cli
|
||||||
laconic-console
|
laconic-console
|
||||||
debug
|
debug
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
```
|
```
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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"
|
||||||
```
|
```
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
|
@ -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"
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
@ -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
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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",
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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, {})
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
|
@ -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)
|
|
@ -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])
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
Loading…
Reference in New Issue
Block a user