Merge branch 'main' into telackey/deployer
# Conflicts: # stack_orchestrator/deploy/compose/deploy_docker.py # stack_orchestrator/deploy/deploy.py # stack_orchestrator/deploy/deployment.py
This commit is contained in:
commit
2c5159ff93
@ -13,12 +13,16 @@
|
|||||||
# 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/>.
|
||||||
|
|
||||||
|
cluster_name_prefix = "laconic-"
|
||||||
stack_file_name = "stack.yml"
|
stack_file_name = "stack.yml"
|
||||||
spec_file_name = "spec.yml"
|
spec_file_name = "spec.yml"
|
||||||
config_file_name = "config.env"
|
config_file_name = "config.env"
|
||||||
|
deployment_file_name = "deployment.yml"
|
||||||
|
compose_dir_name = "compose"
|
||||||
compose_deploy_type = "compose"
|
compose_deploy_type = "compose"
|
||||||
k8s_kind_deploy_type = "k8s-kind"
|
k8s_kind_deploy_type = "k8s-kind"
|
||||||
k8s_deploy_type = "k8s"
|
k8s_deploy_type = "k8s"
|
||||||
|
cluster_id_key = "cluster-id"
|
||||||
kube_config_key = "kube-config"
|
kube_config_key = "kube-config"
|
||||||
deploy_to_key = "deploy-to"
|
deploy_to_key = "deploy-to"
|
||||||
network_key = "network"
|
network_key = "network"
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
version: "3.2"
|
||||||
|
|
||||||
|
services:
|
||||||
|
proxy-server:
|
||||||
|
image: cerc/watcher-ts:local
|
||||||
|
restart: on-failure
|
||||||
|
working_dir: /app/packages/cli
|
||||||
|
environment:
|
||||||
|
ENABLE_PROXY: ${ENABLE_PROXY:-true}
|
||||||
|
PROXY_UPSTREAM: ${CERC_PROXY_UPSTREAM}
|
||||||
|
PROXY_ORIGIN_HEADER: ${CERC_PROXY_ORIGIN_HEADER}
|
||||||
|
command: ["sh", "-c", "./run.sh"]
|
||||||
|
volumes:
|
||||||
|
- ../config/proxy-server/run.sh:/app/packages/cli/run.sh
|
||||||
|
ports:
|
||||||
|
- "4000"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "nc", "-v", "localhost", "4000"]
|
||||||
|
interval: 20s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 15
|
||||||
|
start_period: 10s
|
@ -12,38 +12,6 @@ services:
|
|||||||
- app_builds:/app-builds
|
- app_builds:/app-builds
|
||||||
- ../config/uniswap-interface/build-app.sh:/app/build-app.sh
|
- ../config/uniswap-interface/build-app.sh:/app/build-app.sh
|
||||||
|
|
||||||
uniswap-glob-host:
|
|
||||||
image: cerc/urbit-globs-host:local
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
uniswap-interface:
|
|
||||||
condition: service_completed_successfully
|
|
||||||
command: ["./host-uniswap-glob.sh"]
|
|
||||||
volumes:
|
|
||||||
- app_globs:/app-globs
|
|
||||||
- ../config/uniswap-interface/host-uniswap-glob.sh:/app/host-uniswap-glob.sh
|
|
||||||
ports:
|
|
||||||
- "3000"
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "nc", "-v", "localhost", "3000"]
|
|
||||||
interval: 20s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 15
|
|
||||||
start_period: 10s
|
|
||||||
|
|
||||||
uniswap-gql-proxy:
|
|
||||||
image: cerc/uniswap-interface:local
|
|
||||||
restart: on-failure
|
|
||||||
command: ["bash", "-c", "yarn proxy-gql"]
|
|
||||||
ports:
|
|
||||||
- "4000"
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "nc", "-v", "localhost", "4000"]
|
|
||||||
interval: 20s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 15
|
|
||||||
start_period: 10s
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
app_builds:
|
app_builds:
|
||||||
app_globs:
|
app_globs:
|
||||||
|
@ -4,6 +4,9 @@ services:
|
|||||||
urbit-fake-ship:
|
urbit-fake-ship:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: tloncorp/vere
|
image: tloncorp/vere
|
||||||
|
environment:
|
||||||
|
CERC_IPFS_GLOB_HOST_ENDPOINT: ${CERC_IPFS_GLOB_HOST_ENDPOINT:-http://ipfs-glob-host:5001}
|
||||||
|
CERC_IPFS_SERVER_ENDPOINT: ${CERC_IPFS_SERVER_ENDPOINT:-http://ipfs-glob-host:8080}
|
||||||
entrypoint: ["bash", "-c", "./run-urbit-ship.sh && ./deploy-uniswap-app.sh && tail -f /dev/null"]
|
entrypoint: ["bash", "-c", "./run-urbit-ship.sh && ./deploy-uniswap-app.sh && tail -f /dev/null"]
|
||||||
volumes:
|
volumes:
|
||||||
- urbit_data:/urbit
|
- urbit_data:/urbit
|
||||||
@ -20,7 +23,24 @@ services:
|
|||||||
retries: 15
|
retries: 15
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
|
|
||||||
|
ipfs-glob-host:
|
||||||
|
image: ipfs/kubo:master-2023-02-20-714a968
|
||||||
|
volumes:
|
||||||
|
- ipfs-import:/import
|
||||||
|
- ipfs-data:/data/ipfs
|
||||||
|
ports:
|
||||||
|
- "8080"
|
||||||
|
- "5001"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "nc", "-v", "localhost", "5001"]
|
||||||
|
interval: 20s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 15
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
urbit_data:
|
urbit_data:
|
||||||
app_builds:
|
app_builds:
|
||||||
app_globs:
|
app_globs:
|
||||||
|
ipfs-import:
|
||||||
|
ipfs-data:
|
||||||
|
9
stack_orchestrator/data/config/proxy-server/run.sh
Executable file
9
stack_orchestrator/data/config/proxy-server/run.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [ "$ENABLE_PROXY" = "true" ]; then
|
||||||
|
echo "Proxy server enabled"
|
||||||
|
yarn proxy
|
||||||
|
else
|
||||||
|
echo "Proxy server disabled, exiting"
|
||||||
|
exit 0
|
||||||
|
fi
|
@ -4,6 +4,11 @@ if [ -n "$CERC_SCRIPT_DEBUG" ]; then
|
|||||||
set -x
|
set -x
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "Using IPFS endpoint ${CERC_IPFS_GLOB_HOST_ENDPOINT} for hosting globs"
|
||||||
|
echo "Using IPFS server endpoint ${CERC_IPFS_SERVER_ENDPOINT} for reading glob files"
|
||||||
|
ipfs_host_endpoint=${CERC_IPFS_GLOB_HOST_ENDPOINT}
|
||||||
|
ipfs_server_endpoint=${CERC_IPFS_SERVER_ENDPOINT}
|
||||||
|
|
||||||
uniswap_app_build='/app-builds/uniswap/build'
|
uniswap_app_build='/app-builds/uniswap/build'
|
||||||
uniswap_desk_dir='/urbit/zod/uniswap'
|
uniswap_desk_dir='/urbit/zod/uniswap'
|
||||||
|
|
||||||
@ -96,15 +101,17 @@ rm "${uniswap_desk_dir}/desk.ship"
|
|||||||
hood "commit %uniswap"
|
hood "commit %uniswap"
|
||||||
dojo "-landscape!make-glob %uniswap /build"
|
dojo "-landscape!make-glob %uniswap /build"
|
||||||
|
|
||||||
echo "Copying over glob file to mounted volume"
|
|
||||||
mkdir -p /app-globs/uniswap
|
|
||||||
cp /urbit/zod/.urb/put/* /app-globs/uniswap/
|
|
||||||
|
|
||||||
glob_file=$(ls -1 -c zod/.urb/put | head -1)
|
glob_file=$(ls -1 -c zod/.urb/put | head -1)
|
||||||
echo "Glob filename: ${glob_file}"
|
echo "Created glob file: ${glob_file}"
|
||||||
|
|
||||||
|
upload_response=$(curl -X POST -F file=@./zod/.urb/put/${glob_file} ${ipfs_host_endpoint}/api/v0/add)
|
||||||
|
glob_cid=$(echo "$upload_response" | grep -o '"Hash":"[^"]*' | sed 's/"Hash":"//')
|
||||||
|
|
||||||
|
echo "Glob file uploaded to IFPS:"
|
||||||
|
echo "{ cid: ${glob_cid}, filename: ${glob_file} }"
|
||||||
|
|
||||||
# Curl and wait for the glob to be hosted
|
# Curl and wait for the glob to be hosted
|
||||||
glob_url="http://uniswap-glob-host:3000/${glob_file}"
|
glob_url="${ipfs_server_endpoint}/ipfs/${glob_cid}?filename=${glob_file}"
|
||||||
|
|
||||||
echo "Checking if glob file hosted at ${glob_url}"
|
echo "Checking if glob file hosted at ${glob_url}"
|
||||||
while true; do
|
while true; do
|
||||||
@ -128,7 +135,7 @@ cat << EOF > "${uniswap_desk_dir}/desk.docket-0"
|
|||||||
color+0xcd.75df
|
color+0xcd.75df
|
||||||
image+'https://logowik.com/content/uploads/images/uniswap-uni7403.jpg'
|
image+'https://logowik.com/content/uploads/images/uniswap-uni7403.jpg'
|
||||||
base+'uniswap'
|
base+'uniswap'
|
||||||
glob-http+['http://uniswap-glob-host:3000/${glob_file}' ${glob_hash}]
|
glob-http+['${glob_url}' ${glob_hash}]
|
||||||
version+[0 0 1]
|
version+[0 0 1]
|
||||||
website+'https://uniswap.org/'
|
website+'https://uniswap.org/'
|
||||||
license+'MIT'
|
license+'MIT'
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
if [ -n "$CERC_SCRIPT_DEBUG" ]; then
|
|
||||||
set -x
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use config from mounted volume (when running web-app along with watcher stack)
|
|
||||||
echo "Waiting for uniswap app glob"
|
|
||||||
while [ ! -d /app-globs/uniswap ]; do
|
|
||||||
echo "Glob directory not found, retrying in 5 seconds..."
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
# Copy to a new globs directory
|
|
||||||
mkdir -p globs
|
|
||||||
cp -r /app-globs/uniswap/* ./globs
|
|
||||||
|
|
||||||
# Serve the glob file
|
|
||||||
cd globs
|
|
||||||
echo "Hosting glob file at port 3000"
|
|
||||||
python3 -m http.server 3000 --bind 0.0.0.0
|
|
@ -1,21 +1,22 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# $1: Glob file URL (eg. https://xyz.com/glob-abcd.glob)
|
# $1: Glob file URL (eg. https://xyz.com/glob-0vabcd.glob)
|
||||||
# $2: Uniswap desk dir (default: ./zod/uniswap)
|
# $2: Glob file hash (eg. 0vabcd)
|
||||||
|
# $3: Urbit ship's pier dir (default: ./zod)
|
||||||
|
|
||||||
if [ -z "$1" ]; then
|
if [ "$#" -lt 2 ]; then
|
||||||
echo "Glob file URL arg not provided"
|
echo "Insufficient arguments"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
glob_url=$1
|
glob_url=$1
|
||||||
glob_file=$(basename "$glob_url")
|
glob_hash=$2
|
||||||
glob_hash=$(echo "$glob_file" | sed "s/glob-\([a-z0-9\.]*\).glob/\1/")
|
echo "Using glob file from ${glob_url} with hash ${glob_hash}"
|
||||||
echo "Using glob file ${glob_file}"
|
|
||||||
|
|
||||||
|
# Default pier dir: ./zod
|
||||||
# Default desk dir: ./zod/uniswap
|
# Default desk dir: ./zod/uniswap
|
||||||
uniswap_desk_dir="${2:-./zod/uniswap}"
|
pier_dir="${3:-./zod}"
|
||||||
|
uniswap_desk_dir="${pier_dir}/uniswap"
|
||||||
echo "Using ${uniswap_desk_dir} as the Uniswap desk dir path"
|
echo "Using ${uniswap_desk_dir} as the Uniswap desk dir path"
|
||||||
|
|
||||||
# Fire curl requests to perform operations on the ship
|
# Fire curl requests to perform operations on the ship
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# $1: Remote user host
|
# $1: Remote user host
|
||||||
# $2: Path to run the app installation in (where urbit ship dir is located)
|
# $2: Remote Urbit ship's pier dir path (eg. /home/user/zod)
|
||||||
# $3: Glob file URL (eg. https://xyz.com/glob-abcd.glob)
|
# $3: Glob file URL (eg. https://xyz.com/glob-0vabcd.glob)
|
||||||
|
# $4: Glob file hash (eg. 0vabcd)
|
||||||
|
|
||||||
if [ "$#" -ne 3 ]; then
|
if [ "$#" -ne 4 ]; then
|
||||||
echo "Usage: $0 <username@remote_host> </path/to/remote/folder> <glob_url>"
|
echo "Incorrect number of arguments"
|
||||||
|
echo "Usage: $0 <username@remote_host> </path/to/remote/pier/folder> <glob_url> <glob_hash>"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
remote_user_host="$1"
|
remote_user_host="$1"
|
||||||
remote_folder="$2"
|
remote_pier_folder="$2"
|
||||||
glob_url="$3"
|
glob_url="$3"
|
||||||
|
glob_hash="$4"
|
||||||
|
|
||||||
installation_script="./install-uniswap-app.sh"
|
installation_script="./install-uniswap-app.sh"
|
||||||
|
|
||||||
ssh "$remote_user_host" "cd $remote_folder && bash -s $glob_url" < "$installation_script"
|
ssh "$remote_user_host" "bash -s $glob_url $glob_hash $remote_pier_folder" < "$installation_script"
|
||||||
|
79
stack_orchestrator/data/stacks/proxy-server/README.md
Normal file
79
stack_orchestrator/data/stacks/proxy-server/README.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Proxy Server
|
||||||
|
|
||||||
|
Instructions to setup and deploy a HTTP proxy server
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Clone required repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
laconic-so --stack proxy-server setup-repositories --pull
|
||||||
|
|
||||||
|
# If this throws an error as a result of being already checked out to a branch/tag in a repo, remove the repositories mentioned below and re-run the command
|
||||||
|
```
|
||||||
|
|
||||||
|
Build the container image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
laconic-so --stack proxy-server build-containers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create a deployment
|
||||||
|
|
||||||
|
* First, create a spec file for the deployment, which will allow mapping the stack's ports and volumes to the host:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
laconic-so --stack proxy-server deploy init --output proxy-server-spec.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
* Edit `network` in spec file to map container ports to same ports in host:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
...
|
||||||
|
network:
|
||||||
|
ports:
|
||||||
|
proxy-server:
|
||||||
|
- '4000:4000'
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
* Once you've made any needed changes to the spec file, create a deployment from it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
laconic-so --stack proxy-server deploy create --spec-file proxy-server-spec.yml --deployment-dir proxy-server-deployment
|
||||||
|
```
|
||||||
|
|
||||||
|
* Inside the deployment directory, open the file `config.env` and set the following env variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Whether to run the proxy server (Optional) (Default: true)
|
||||||
|
ENABLE_PROXY=
|
||||||
|
|
||||||
|
# Upstream endpoint
|
||||||
|
# (Eg. https://api.example.org)
|
||||||
|
CERC_PROXY_UPSTREAM=
|
||||||
|
|
||||||
|
# Origin header to be used (Optional)
|
||||||
|
# (Eg. https://app.example.org)
|
||||||
|
CERC_PROXY_ORIGIN_HEADER=
|
||||||
|
```
|
||||||
|
|
||||||
|
## Start the stack
|
||||||
|
|
||||||
|
Start the deployment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
laconic-so deployment --dir proxy-server-deployment start
|
||||||
|
```
|
||||||
|
|
||||||
|
* List and check the health status of the container using `docker ps`
|
||||||
|
|
||||||
|
* The proxy server will now be listening at http://localhost:4000
|
||||||
|
|
||||||
|
## Clean up
|
||||||
|
|
||||||
|
To stop the service running in background:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
laconic-so deployment --dir proxy-server-deployment stop
|
||||||
|
```
|
8
stack_orchestrator/data/stacks/proxy-server/stack.yml
Normal file
8
stack_orchestrator/data/stacks/proxy-server/stack.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
version: "0.1"
|
||||||
|
name: proxy-server
|
||||||
|
repos:
|
||||||
|
- github.com/cerc-io/watcher-ts@v0.2.78
|
||||||
|
containers:
|
||||||
|
- cerc/watcher-ts
|
||||||
|
pods:
|
||||||
|
- proxy-server
|
@ -41,10 +41,11 @@ network:
|
|||||||
ports:
|
ports:
|
||||||
urbit-fake-ship:
|
urbit-fake-ship:
|
||||||
- '8080:80'
|
- '8080:80'
|
||||||
uniswap-glob-host:
|
proxy-server:
|
||||||
- '3000:3000'
|
|
||||||
uniswap-gql-proxy:
|
|
||||||
- '4000:4000'
|
- '4000:4000'
|
||||||
|
ipfs-glob-host:
|
||||||
|
- '8081:8080'
|
||||||
|
- '5001:5001'
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ laconic-so --stack uniswap-urbit-app deploy create --spec-file uniswap-urbit-app
|
|||||||
|
|
||||||
## Set env variables
|
## Set env variables
|
||||||
|
|
||||||
Inside the deployment directory, open the file `config.env` and add variable for infura key :
|
Inside the deployment directory, open the file `config.env` and set the following env variables:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# External RPC endpoints
|
# External RPC endpoints
|
||||||
@ -72,8 +73,36 @@ Inside the deployment directory, open the file `config.env` and add variable for
|
|||||||
|
|
||||||
# Uniswap API GQL Endpoint
|
# Uniswap API GQL Endpoint
|
||||||
# Set this to GQL proxy server endpoint for uniswap app
|
# Set this to GQL proxy server endpoint for uniswap app
|
||||||
# (Eg. http://localhost:4000/graphql)
|
# (Eg. http://localhost:4000/v1/graphql)
|
||||||
|
# (Eg. https://abc.xyz.com/v1/graphql)
|
||||||
CERC_UNISWAP_GQL=
|
CERC_UNISWAP_GQL=
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
|
||||||
|
# Whether to run the proxy GQL server
|
||||||
|
# (Disable only if proxy not required to be run) (Default: true)
|
||||||
|
ENABLE_PROXY=
|
||||||
|
|
||||||
|
# Proxy server configuration
|
||||||
|
# Used only if proxy is enabled
|
||||||
|
|
||||||
|
# Upstream API URL
|
||||||
|
# (Eg. https://api.example.org)
|
||||||
|
CERC_PROXY_UPSTREAM=https://api.uniswap.org
|
||||||
|
|
||||||
|
# Origin header to be used in the proxy
|
||||||
|
# (Eg. https://app.example.org)
|
||||||
|
CERC_PROXY_ORIGIN_HEADER=https://app.uniswap.org
|
||||||
|
|
||||||
|
# IPFS configuration
|
||||||
|
|
||||||
|
# IFPS endpoint to host the glob file on
|
||||||
|
# (Default: http://ipfs-glob-host:5001 pointing to in-stack IPFS node)
|
||||||
|
CERC_IPFS_GLOB_HOST_ENDPOINT=
|
||||||
|
|
||||||
|
# IFPS endpoint to fetch the glob file from
|
||||||
|
# (Default: http://ipfs-glob-host:8080 pointing to in-stack IPFS node)
|
||||||
|
CERC_IPFS_SERVER_ENDPOINT=
|
||||||
```
|
```
|
||||||
|
|
||||||
## Start the stack
|
## Start the stack
|
||||||
@ -109,7 +138,7 @@ laconic-so deployment --dir uniswap-urbit-app-deployment start
|
|||||||
|
|
||||||
## Clean up
|
## Clean up
|
||||||
|
|
||||||
To stop all uniswap-urbit-app services running in the background, while preserving chain data:
|
To stop all uniswap-urbit-app services running in the background, while preserving data:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
laconic-so deployment --dir uniswap-urbit-app-deployment stop
|
laconic-so deployment --dir uniswap-urbit-app-deployment stop
|
||||||
|
@ -2,9 +2,12 @@ version: "0.1"
|
|||||||
name: uniswap-urbit-app
|
name: uniswap-urbit-app
|
||||||
repos:
|
repos:
|
||||||
- github.com/cerc-io/uniswap-interface@laconic # TODO: Use release
|
- github.com/cerc-io/uniswap-interface@laconic # TODO: Use release
|
||||||
|
- github.com/cerc-io/watcher-ts@v0.2.78
|
||||||
containers:
|
containers:
|
||||||
- cerc/uniswap-interface
|
- cerc/uniswap-interface
|
||||||
|
- cerc/watcher-ts
|
||||||
- cerc/urbit-globs-host
|
- cerc/urbit-globs-host
|
||||||
pods:
|
pods:
|
||||||
- uniswap-interface
|
- uniswap-interface
|
||||||
|
- proxy-server
|
||||||
- uniswap-urbit
|
- uniswap-urbit
|
||||||
|
@ -46,6 +46,13 @@ class DockerDeployer(Deployer):
|
|||||||
except DockerException as e:
|
except DockerException as e:
|
||||||
raise DeployerException(e)
|
raise DeployerException(e)
|
||||||
|
|
||||||
|
def status(self):
|
||||||
|
try:
|
||||||
|
for p in self.docker.compose.ps():
|
||||||
|
print(f"{p.name}\t{p.state.status}")
|
||||||
|
except DockerException as e:
|
||||||
|
raise DeployerException(e)
|
||||||
|
|
||||||
def ps(self):
|
def ps(self):
|
||||||
try:
|
try:
|
||||||
return self.docker.compose.ps()
|
return self.docker.compose.ps()
|
||||||
|
@ -24,6 +24,8 @@ from importlib import resources
|
|||||||
import subprocess
|
import subprocess
|
||||||
import click
|
import click
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from stack_orchestrator import constants
|
||||||
|
from stack_orchestrator.opts import opts
|
||||||
from stack_orchestrator.util import include_exclude_check, get_parsed_stack_config, global_options2, get_dev_root_path
|
from stack_orchestrator.util import include_exclude_check, get_parsed_stack_config, global_options2, get_dev_root_path
|
||||||
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
|
||||||
@ -70,6 +72,9 @@ def create_deploy_context(
|
|||||||
cluster,
|
cluster,
|
||||||
env_file,
|
env_file,
|
||||||
deploy_to) -> DeployCommandContext:
|
deploy_to) -> DeployCommandContext:
|
||||||
|
# Extract the cluster name from the deployment, if we have one
|
||||||
|
if deployment_context and cluster is None:
|
||||||
|
cluster = deployment_context.get_cluster_id()
|
||||||
cluster_context = _make_cluster_context(global_context, stack, include, exclude, cluster, env_file)
|
cluster_context = _make_cluster_context(global_context, stack, include, exclude, cluster, env_file)
|
||||||
deployer = getDeployer(deploy_to, deployment_context, compose_files=cluster_context.compose_files,
|
deployer = getDeployer(deploy_to, deployment_context, compose_files=cluster_context.compose_files,
|
||||||
compose_project_name=cluster_context.cluster,
|
compose_project_name=cluster_context.cluster,
|
||||||
@ -107,6 +112,14 @@ def down_operation(ctx, delete_volumes, extra_args_list):
|
|||||||
ctx.obj.deployer.down(timeout=timeout_arg, volumes=delete_volumes)
|
ctx.obj.deployer.down(timeout=timeout_arg, volumes=delete_volumes)
|
||||||
|
|
||||||
|
|
||||||
|
def status_operation(ctx):
|
||||||
|
global_context = ctx.parent.parent.obj
|
||||||
|
if not global_context.dry_run:
|
||||||
|
if global_context.verbose:
|
||||||
|
print("Running compose status")
|
||||||
|
ctx.obj.deployer.status()
|
||||||
|
|
||||||
|
|
||||||
def update_operation(ctx):
|
def update_operation(ctx):
|
||||||
global_context = ctx.parent.parent.obj
|
global_context = ctx.parent.parent.obj
|
||||||
if not global_context.dry_run:
|
if not global_context.dry_run:
|
||||||
@ -261,6 +274,22 @@ def _make_runtime_env(ctx):
|
|||||||
return container_exec_env
|
return container_exec_env
|
||||||
|
|
||||||
|
|
||||||
|
def _make_default_cluster_name(deployment, compose_dir, stack, include, exclude):
|
||||||
|
# Create default unique, stable cluster name from confile file path and stack name if provided
|
||||||
|
if deployment:
|
||||||
|
path = os.path.realpath(os.path.abspath(compose_dir))
|
||||||
|
else:
|
||||||
|
path = "internal"
|
||||||
|
unique_cluster_descriptor = f"{path},{stack},{include},{exclude}"
|
||||||
|
if opts.o.debug:
|
||||||
|
print(f"pre-hash descriptor: {unique_cluster_descriptor}")
|
||||||
|
hash = hashlib.md5(unique_cluster_descriptor.encode()).hexdigest()[:16]
|
||||||
|
cluster = f"{constants.cluster_name_prefix}{hash}"
|
||||||
|
if opts.o.debug:
|
||||||
|
print(f"Using cluster name: {cluster}")
|
||||||
|
return cluster
|
||||||
|
|
||||||
|
|
||||||
# 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):
|
||||||
|
|
||||||
@ -278,18 +307,9 @@ def _make_cluster_context(ctx, stack, include, exclude, cluster, env_file):
|
|||||||
compose_dir = Path(__file__).absolute().parent.parent.joinpath("data", "compose")
|
compose_dir = Path(__file__).absolute().parent.parent.joinpath("data", "compose")
|
||||||
|
|
||||||
if cluster is None:
|
if cluster is None:
|
||||||
# Create default unique, stable cluster name from confile file path and stack name if provided
|
cluster = _make_default_cluster_name(deployment, compose_dir, stack, include, exclude)
|
||||||
if deployment:
|
else:
|
||||||
path = os.path.realpath(os.path.abspath(compose_dir))
|
_make_default_cluster_name(deployment, compose_dir, stack, include, exclude)
|
||||||
else:
|
|
||||||
path = "internal"
|
|
||||||
unique_cluster_descriptor = f"{path},{stack},{include},{exclude}"
|
|
||||||
if ctx.debug:
|
|
||||||
print(f"pre-hash descriptor: {unique_cluster_descriptor}")
|
|
||||||
hash = hashlib.md5(unique_cluster_descriptor.encode()).hexdigest()[:16]
|
|
||||||
cluster = f"laconic-{hash}"
|
|
||||||
if ctx.verbose:
|
|
||||||
print(f"Using cluster name: {cluster}")
|
|
||||||
|
|
||||||
# See: https://stackoverflow.com/a/20885799/1701505
|
# See: https://stackoverflow.com/a/20885799/1701505
|
||||||
from stack_orchestrator import data
|
from stack_orchestrator import data
|
||||||
|
@ -35,6 +35,10 @@ class Deployer(ABC):
|
|||||||
def ps(self):
|
def ps(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def status(self):
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def port(self, service, private_port):
|
def port(self, service, private_port):
|
||||||
pass
|
pass
|
||||||
|
@ -20,6 +20,8 @@ from stack_orchestrator import constants
|
|||||||
from stack_orchestrator.deploy.images import push_images_operation
|
from stack_orchestrator.deploy.images import push_images_operation
|
||||||
from stack_orchestrator.deploy.deploy import up_operation, down_operation, ps_operation, port_operation
|
from stack_orchestrator.deploy.deploy import up_operation, down_operation, ps_operation, port_operation
|
||||||
from stack_orchestrator.deploy.deploy import exec_operation, logs_operation, create_deploy_context, update_operation
|
from stack_orchestrator.deploy.deploy import exec_operation, logs_operation, create_deploy_context, update_operation
|
||||||
|
from stack_orchestrator.deploy.deploy import up_operation, down_operation, ps_operation, port_operation, status_operation
|
||||||
|
from stack_orchestrator.deploy.deploy import exec_operation, logs_operation, create_deploy_context
|
||||||
from stack_orchestrator.deploy.deploy_types import DeployCommandContext
|
from stack_orchestrator.deploy.deploy_types import DeployCommandContext
|
||||||
from stack_orchestrator.deploy.deployment_context import DeploymentContext
|
from stack_orchestrator.deploy.deployment_context import DeploymentContext
|
||||||
from stack_orchestrator.deploy.webapp import update_from_registry as webapp_update
|
from stack_orchestrator.deploy.webapp import update_from_registry as webapp_update
|
||||||
@ -53,7 +55,7 @@ def make_deploy_context(ctx) -> DeployCommandContext:
|
|||||||
context: DeploymentContext = ctx.obj
|
context: DeploymentContext = ctx.obj
|
||||||
stack_file_path = context.get_stack_file()
|
stack_file_path = context.get_stack_file()
|
||||||
env_file = context.get_env_file()
|
env_file = context.get_env_file()
|
||||||
cluster_name = context.get_cluster_name()
|
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:
|
||||||
@ -148,7 +150,8 @@ def logs(ctx, tail, follow, extra_args):
|
|||||||
@command.command()
|
@command.command()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def status(ctx):
|
def status(ctx):
|
||||||
print(f"Context: {ctx.parent.obj}")
|
ctx.obj = make_deploy_context(ctx)
|
||||||
|
status_operation(ctx)
|
||||||
|
|
||||||
|
|
||||||
@command.command()
|
@command.command()
|
||||||
|
@ -14,15 +14,19 @@
|
|||||||
# 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 hashlib
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from stack_orchestrator import constants
|
from stack_orchestrator import constants
|
||||||
|
from stack_orchestrator.util import get_yaml
|
||||||
from stack_orchestrator.deploy.stack import Stack
|
from stack_orchestrator.deploy.stack import Stack
|
||||||
from stack_orchestrator.deploy.spec import Spec
|
from stack_orchestrator.deploy.spec import Spec
|
||||||
|
|
||||||
|
|
||||||
class DeploymentContext:
|
class DeploymentContext:
|
||||||
deployment_dir: Path
|
deployment_dir: Path
|
||||||
|
id: str
|
||||||
spec: Spec
|
spec: Spec
|
||||||
stack: Stack
|
stack: Stack
|
||||||
|
|
||||||
@ -35,9 +39,14 @@ class DeploymentContext:
|
|||||||
def get_env_file(self):
|
def get_env_file(self):
|
||||||
return self.deployment_dir.joinpath(constants.config_file_name)
|
return self.deployment_dir.joinpath(constants.config_file_name)
|
||||||
|
|
||||||
# TODO: implement me
|
def get_deployment_file(self):
|
||||||
def get_cluster_name(self):
|
return self.deployment_dir.joinpath(constants.deployment_file_name)
|
||||||
return None
|
|
||||||
|
def get_compose_dir(self):
|
||||||
|
return self.deployment_dir.joinpath(constants.compose_dir_name)
|
||||||
|
|
||||||
|
def get_cluster_id(self):
|
||||||
|
return self.id
|
||||||
|
|
||||||
def init(self, dir):
|
def init(self, dir):
|
||||||
self.deployment_dir = dir
|
self.deployment_dir = dir
|
||||||
@ -45,3 +54,16 @@ class DeploymentContext:
|
|||||||
self.spec.init_from_file(self.get_spec_file())
|
self.spec.init_from_file(self.get_spec_file())
|
||||||
self.stack = Stack(self.spec.obj["stack"])
|
self.stack = Stack(self.spec.obj["stack"])
|
||||||
self.stack.init_from_file(self.get_stack_file())
|
self.stack.init_from_file(self.get_stack_file())
|
||||||
|
deployment_file_path = self.get_deployment_file()
|
||||||
|
if deployment_file_path.exists():
|
||||||
|
with deployment_file_path:
|
||||||
|
obj = get_yaml().load(open(deployment_file_path, "r"))
|
||||||
|
self.id = obj[constants.cluster_id_key]
|
||||||
|
# Handle the case of a legacy deployment with no file
|
||||||
|
# Code below is intended to match the output from _make_default_cluster_name()
|
||||||
|
# TODO: remove when we no longer need to support legacy deployments
|
||||||
|
else:
|
||||||
|
path = os.path.realpath(os.path.abspath(self.get_compose_dir()))
|
||||||
|
unique_cluster_descriptor = f"{path},{self.get_stack_file()},None,None"
|
||||||
|
hash = hashlib.md5(unique_cluster_descriptor.encode()).hexdigest()[:16]
|
||||||
|
self.id = f"{constants.cluster_name_prefix}{hash}"
|
||||||
|
@ -20,6 +20,7 @@ from pathlib import Path
|
|||||||
from typing import List
|
from typing import List
|
||||||
import random
|
import random
|
||||||
from shutil import copy, copyfile, copytree
|
from shutil import copy, copyfile, copytree
|
||||||
|
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
|
||||||
@ -276,7 +277,7 @@ def init(ctx, config, config_file, kube_config, image_registry, output, map_port
|
|||||||
# call it from other commands, bypassing the click decoration stuff
|
# call it from other commands, bypassing the click decoration stuff
|
||||||
def init_operation(deploy_command_context, stack, deployer_type, config,
|
def init_operation(deploy_command_context, stack, deployer_type, config,
|
||||||
config_file, kube_config, image_registry, output, map_ports_to_host):
|
config_file, kube_config, image_registry, output, map_ports_to_host):
|
||||||
yaml = get_yaml()
|
|
||||||
default_spec_file_content = call_stack_deploy_init(deploy_command_context)
|
default_spec_file_content = call_stack_deploy_init(deploy_command_context)
|
||||||
spec_file_content = {"stack": stack, constants.deploy_to_key: deployer_type}
|
spec_file_content = {"stack": stack, constants.deploy_to_key: deployer_type}
|
||||||
if deployer_type == "k8s":
|
if deployer_type == "k8s":
|
||||||
@ -311,8 +312,6 @@ def init_operation(deploy_command_context, stack, deployer_type, config,
|
|||||||
new_config = config_file_variables
|
new_config = config_file_variables
|
||||||
merged_config = {**new_config, **orig_config}
|
merged_config = {**new_config, **orig_config}
|
||||||
spec_file_content.update({"config": merged_config})
|
spec_file_content.update({"config": merged_config})
|
||||||
if opts.o.debug:
|
|
||||||
print(f"Creating spec file for stack: {stack} with content: {spec_file_content}")
|
|
||||||
|
|
||||||
ports = _get_mapped_ports(stack, map_ports_to_host)
|
ports = _get_mapped_ports(stack, map_ports_to_host)
|
||||||
spec_file_content.update({"network": {"ports": ports}})
|
spec_file_content.update({"network": {"ports": ports}})
|
||||||
@ -324,8 +323,11 @@ def init_operation(deploy_command_context, stack, deployer_type, config,
|
|||||||
volume_descriptors[named_volume] = f"./data/{named_volume}"
|
volume_descriptors[named_volume] = f"./data/{named_volume}"
|
||||||
spec_file_content["volumes"] = volume_descriptors
|
spec_file_content["volumes"] = volume_descriptors
|
||||||
|
|
||||||
|
if opts.o.debug:
|
||||||
|
print(f"Creating spec file for stack: {stack} with content: {spec_file_content}")
|
||||||
|
|
||||||
with open(output, "w") as output_file:
|
with open(output, "w") as output_file:
|
||||||
yaml.dump(spec_file_content, output_file)
|
get_yaml().dump(spec_file_content, output_file)
|
||||||
|
|
||||||
|
|
||||||
def _write_config_file(spec_file: Path, config_env_file: Path):
|
def _write_config_file(spec_file: Path, config_env_file: Path):
|
||||||
@ -351,6 +353,13 @@ def _copy_files_to_directory(file_paths: List[Path], directory: Path):
|
|||||||
copy(path, os.path.join(directory, os.path.basename(path)))
|
copy(path, os.path.join(directory, os.path.basename(path)))
|
||||||
|
|
||||||
|
|
||||||
|
def _create_deployment_file(deployment_dir: Path):
|
||||||
|
deployment_file_path = deployment_dir.joinpath(constants.deployment_file_name)
|
||||||
|
cluster = f"{constants.cluster_name_prefix}{token_hex(8)}"
|
||||||
|
with open(deployment_file_path, "w") as output_file:
|
||||||
|
output_file.write(f"{constants.cluster_id_key}: {cluster}\n")
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("--spec-file", required=True, help="Spec file to use to create this deployment")
|
@click.option("--spec-file", required=True, help="Spec file to use to create this deployment")
|
||||||
@click.option("--deployment-dir", help="Create deployment files in this directory")
|
@click.option("--deployment-dir", help="Create deployment files in this directory")
|
||||||
@ -383,6 +392,7 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, netw
|
|||||||
# 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(os.path.basename(stack_file)))
|
copyfile(stack_file, deployment_dir_path.joinpath(os.path.basename(stack_file)))
|
||||||
|
_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))
|
||||||
# Copy any k8s config file into the deployment dir
|
# Copy any k8s config file into the deployment dir
|
||||||
|
@ -28,6 +28,12 @@ from stack_orchestrator.deploy.deployment_context import DeploymentContext
|
|||||||
from stack_orchestrator.util import error_exit
|
from stack_orchestrator.util import error_exit
|
||||||
|
|
||||||
|
|
||||||
|
class AttrDict(dict):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(AttrDict, self).__init__(*args, **kwargs)
|
||||||
|
self.__dict__ = self
|
||||||
|
|
||||||
|
|
||||||
def _check_delete_exception(e: client.exceptions.ApiException):
|
def _check_delete_exception(e: client.exceptions.ApiException):
|
||||||
if e.status == 404:
|
if e.status == 404:
|
||||||
if opts.o.debug:
|
if opts.o.debug:
|
||||||
@ -74,6 +80,7 @@ class K8sDeployer(Deployer):
|
|||||||
self.core_api = client.CoreV1Api()
|
self.core_api = client.CoreV1Api()
|
||||||
self.networking_api = client.NetworkingV1Api()
|
self.networking_api = client.NetworkingV1Api()
|
||||||
self.apps_api = client.AppsV1Api()
|
self.apps_api = client.AppsV1Api()
|
||||||
|
self.custom_obj_api = client.CustomObjectsApi()
|
||||||
|
|
||||||
def up(self, detach, services):
|
def up(self, detach, services):
|
||||||
|
|
||||||
@ -204,15 +211,82 @@ class K8sDeployer(Deployer):
|
|||||||
# Destroy the kind cluster
|
# Destroy the kind cluster
|
||||||
destroy_cluster(self.kind_cluster_name)
|
destroy_cluster(self.kind_cluster_name)
|
||||||
|
|
||||||
def ps(self):
|
def status(self):
|
||||||
self.connect_api()
|
self.connect_api()
|
||||||
# Call whatever API we need to get the running container list
|
# Call whatever API we need to get the running container list
|
||||||
ret = self.core_api.list_pod_for_all_namespaces(watch=False)
|
all_pods = self.core_api.list_pod_for_all_namespaces(watch=False)
|
||||||
if ret.items:
|
pods = []
|
||||||
for i in ret.items:
|
|
||||||
print("%s\t%s\t%s" % (i.status.pod_ip, i.metadata.namespace, i.metadata.name))
|
if all_pods.items:
|
||||||
ret = self.core_api.list_node(pretty=True, watch=False)
|
for p in all_pods.items:
|
||||||
return []
|
if self.cluster_info.app_name in p.metadata.name:
|
||||||
|
pods.append(p)
|
||||||
|
|
||||||
|
if not pods:
|
||||||
|
return
|
||||||
|
|
||||||
|
hostname = "?"
|
||||||
|
ip = "?"
|
||||||
|
tls = "?"
|
||||||
|
try:
|
||||||
|
ingress = self.networking_api.read_namespaced_ingress(namespace=self.k8s_namespace,
|
||||||
|
name=self.cluster_info.get_ingress().metadata.name)
|
||||||
|
|
||||||
|
cert = self.custom_obj_api.get_namespaced_custom_object(
|
||||||
|
group="cert-manager.io",
|
||||||
|
version="v1",
|
||||||
|
namespace=self.k8s_namespace,
|
||||||
|
plural="certificates",
|
||||||
|
name=ingress.spec.tls[0].secret_name
|
||||||
|
)
|
||||||
|
|
||||||
|
hostname = ingress.spec.tls[0].hosts[0]
|
||||||
|
ip = ingress.status.load_balancer.ingress[0].ip
|
||||||
|
tls = "notBefore: %s, notAfter: %s" % (cert["status"]["notBefore"], cert["status"]["notAfter"])
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
|
||||||
|
print("Ingress:")
|
||||||
|
print("\tHostname:", hostname)
|
||||||
|
print("\tIP:", ip)
|
||||||
|
print("\tTLS:", tls)
|
||||||
|
print("")
|
||||||
|
print("Pods:")
|
||||||
|
|
||||||
|
for p in pods:
|
||||||
|
if p.metadata.deletion_timestamp:
|
||||||
|
print(f"\t{p.metadata.namespace}/{p.metadata.name}: Terminating ({p.metadata.deletion_timestamp})")
|
||||||
|
else:
|
||||||
|
print(f"\t{p.metadata.namespace}/{p.metadata.name}: Running ({p.metadata.creation_timestamp})")
|
||||||
|
|
||||||
|
def ps(self):
|
||||||
|
self.connect_api()
|
||||||
|
pods = self.core_api.list_pod_for_all_namespaces(watch=False)
|
||||||
|
|
||||||
|
ret = []
|
||||||
|
|
||||||
|
for p in pods.items:
|
||||||
|
if self.cluster_info.app_name in p.metadata.name:
|
||||||
|
pod_ip = p.status.pod_ip
|
||||||
|
ports = AttrDict()
|
||||||
|
for c in p.spec.containers:
|
||||||
|
if c.ports:
|
||||||
|
for prt in c.ports:
|
||||||
|
ports[str(prt.container_port)] = [AttrDict({
|
||||||
|
"HostIp": pod_ip,
|
||||||
|
"HostPort": prt.container_port
|
||||||
|
})]
|
||||||
|
|
||||||
|
ret.append(AttrDict({
|
||||||
|
"id": f"{p.metadata.namespace}/{p.metadata.name}",
|
||||||
|
"name": p.metadata.name,
|
||||||
|
"namespace": p.metadata.namespace,
|
||||||
|
"network_settings": AttrDict({
|
||||||
|
"ports": ports
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
def port(self, service, private_port):
|
def port(self, service, private_port):
|
||||||
# Since we handle the port mapping, need to figure out where this comes from
|
# Since we handle the port mapping, need to figure out where this comes from
|
||||||
|
Loading…
Reference in New Issue
Block a user