forked from cerc-io/stack-orchestrator
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc4396d593 |
@@ -6,8 +6,6 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- '!**'
|
- '!**'
|
||||||
- '.gitea/workflows/triggers/fixturenet-eth-plugeth-test'
|
- '.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 * * *'
|
|
||||||
|
|
||||||
# Needed until we can incorporate docker startup into the executor container
|
# Needed until we can incorporate docker startup into the executor container
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- '!**'
|
- '!**'
|
||||||
- '.gitea/workflows/triggers/fixturenet-laconicd-test'
|
- '.gitea/workflows/triggers/fixturenet-laconicd-test'
|
||||||
schedule:
|
|
||||||
- cron: '1 13 * * *'
|
# Needed until we can incorporate docker startup into the executor container
|
||||||
|
env:
|
||||||
|
DOCKER_HOST: unix:///var/run/dind.sock
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@@ -44,5 +47,9 @@ jobs:
|
|||||||
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: Start dockerd # Also needed until we can incorporate into the executor
|
||||||
|
run: |
|
||||||
|
dockerd -H $DOCKER_HOST --userland-proxy=false &
|
||||||
|
sleep 5
|
||||||
- name: "Run fixturenet-laconicd tests"
|
- name: "Run fixturenet-laconicd tests"
|
||||||
run: ./tests/fixturenet-laconicd/run-test.sh
|
run: ./tests/fixturenet-laconicd/run-test.sh
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ on:
|
|||||||
branches: '*'
|
branches: '*'
|
||||||
paths:
|
paths:
|
||||||
- '!**'
|
- '!**'
|
||||||
- '.gitea/workflows/triggers/test-k8s-deploy'
|
- '.gitea/workflows/triggers/fixturenet-laconicd-test'
|
||||||
- '.gitea/workflows/test-k8s-deploy.yml'
|
|
||||||
- 'tests/k8s-deploy/run-deploy-test.sh'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: "Run deploy test suite on kind/k8s"
|
name: "Run deploy test suite on kind/k8s"
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04-with-syn-ethdb
|
||||||
steps:
|
steps:
|
||||||
- name: "Clone project repository"
|
- name: "Clone project repository"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -36,15 +34,9 @@ jobs:
|
|||||||
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"
|
|
||||||
run: mount | grep cgroup
|
|
||||||
- name: "Install kind"
|
- name: "Install kind"
|
||||||
run: ./tests/scripts/install-kind.sh
|
run: ./tests/scripts/install-kind.sh
|
||||||
- name: "Install Kubectl"
|
- name: "Install Kubectl"
|
||||||
run: ./tests/scripts/install-kubectl.sh
|
run: ./tests/scripts/install-kubectl.sh
|
||||||
- name: "Run k8s deployment test"
|
- name: "Run k8s deployment test"
|
||||||
run: |
|
run: ./tests/k8s-deploy/run-deploy-test.sh
|
||||||
source /opt/bash-utils/cgroup-helper.sh
|
|
||||||
join_cgroup
|
|
||||||
./tests/k8s-deploy/run-deploy-test.sh
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
Change this file to trigger running the fixturenet-eth-plugeth-test CI job
|
Change this file to trigger running the fixturenet-eth-plugeth-test CI job
|
||||||
trigger
|
trigger
|
||||||
trigger
|
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
Change this file to trigger running the fixturenet-laconicd-test CI job
|
Change this file to trigger running the fixturenet-laconicd-test CI job
|
||||||
Trigger
|
|
||||||
Trigger
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ from stack_orchestrator.base import get_npm_registry_url
|
|||||||
# TODO: find a place for this
|
# TODO: find a place for this
|
||||||
# epilog="Config provided either in .env or settings.ini or env vars: CERC_REPO_BASE_DIR (defaults to ~/cerc)"
|
# epilog="Config provided either in .env or settings.ini or env vars: CERC_REPO_BASE_DIR (defaults to ~/cerc)"
|
||||||
|
|
||||||
|
|
||||||
def make_container_build_env(dev_root_path: str,
|
def make_container_build_env(dev_root_path: str,
|
||||||
container_build_dir: str,
|
container_build_dir: str,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
@@ -105,9 +104,6 @@ def process_container(stack: str,
|
|||||||
build_command = os.path.join(container_build_dir,
|
build_command = os.path.join(container_build_dir,
|
||||||
"default-build.sh") + f" {default_container_tag} {repo_dir_or_build_dir}"
|
"default-build.sh") + f" {default_container_tag} {repo_dir_or_build_dir}"
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
# No PATH at all causes failures with podman.
|
|
||||||
if "PATH" not in container_build_env:
|
|
||||||
container_build_env["PATH"] = os.environ["PATH"]
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print(f"Executing: {build_command} with environment: {container_build_env}")
|
print(f"Executing: {build_command} with environment: {container_build_env}")
|
||||||
build_result = subprocess.run(build_command, shell=True, env=container_build_env)
|
build_result = subprocess.run(build_command, shell=True, env=container_build_env)
|
||||||
@@ -123,7 +119,6 @@ def process_container(stack: str,
|
|||||||
else:
|
else:
|
||||||
print("Skipped")
|
print("Skipped")
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option('--include', help="only build these containers")
|
@click.option('--include', help="only build these containers")
|
||||||
@click.option('--exclude', help="don\'t build these containers")
|
@click.option('--exclude', help="don\'t build these containers")
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ services:
|
|||||||
laconicd:
|
laconicd:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: cerc/laconicd:local
|
image: cerc/laconicd:local
|
||||||
command: ["sh", "/docker-entrypoint-scripts.d/create-fixturenet.sh"]
|
entrypoint: ["sh", "/docker-entrypoint-scripts.d/create-fixturenet.sh"]
|
||||||
volumes:
|
volumes:
|
||||||
# The cosmos-sdk node's database directory:
|
# The cosmos-sdk node's database directory:
|
||||||
- laconicd-data:/root/.laconicd
|
- laconicd-data:/root/.laconicd
|
||||||
@@ -15,12 +15,12 @@ services:
|
|||||||
- "6060"
|
- "6060"
|
||||||
- "26657"
|
- "26657"
|
||||||
- "26656"
|
- "26656"
|
||||||
- "9473"
|
- "9473:9473"
|
||||||
- "8545"
|
- "8545"
|
||||||
- "8546"
|
- "8546"
|
||||||
- "9090"
|
- "9090"
|
||||||
- "9091"
|
- "9091"
|
||||||
- "1317"
|
- "1317:1317"
|
||||||
cli:
|
cli:
|
||||||
image: cerc/laconic-registry-cli:local
|
image: cerc/laconic-registry-cli:local
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
version: "3.2"
|
||||||
|
|
||||||
|
services:
|
||||||
|
geojson:
|
||||||
|
image: cerc/geojson:local
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 0.0.0.0:8080:8080
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build the birbit image
|
||||||
|
source ${CERC_CONTAINER_BASE_DIR}/build-base.sh
|
||||||
|
docker build -t cerc/birbit:local -f ${CERC_REPO_BASE_DIR}/birbit/Dockerfile ${build_command_args} ${CERC_REPO_BASE_DIR}/birbit
|
||||||
@@ -10,10 +10,9 @@ COPY genesis /opt/genesis
|
|||||||
COPY --from=ethgen /usr/local/bin/eth2-testnet-genesis /usr/local/bin/
|
COPY --from=ethgen /usr/local/bin/eth2-testnet-genesis /usr/local/bin/
|
||||||
COPY --from=ethgen /usr/local/bin/eth2-val-tools /usr/local/bin/
|
COPY --from=ethgen /usr/local/bin/eth2-val-tools /usr/local/bin/
|
||||||
COPY --from=ethgen /apps /apps
|
COPY --from=ethgen /apps /apps
|
||||||
RUN cd /apps/el-gen && pip3 install --break-system-packages -r requirements.txt
|
RUN cd /apps/el-gen && pip3 install -r requirements.txt
|
||||||
# web3==5.24.0 used by el-gen is broken on python 3.11
|
# web3==5.24.0 used by el-gen is broken on python 3.11
|
||||||
RUN pip3 install --break-system-packages --upgrade "web3==6.5.0"
|
RUN pip3 install --upgrade "web3==6.5.0"
|
||||||
RUN pip3 install --break-system-packages --upgrade "typing-extensions"
|
|
||||||
|
|
||||||
# Build genesis config
|
# Build genesis config
|
||||||
RUN apk add --no-cache make bash envsubst jq
|
RUN apk add --no-cache make bash envsubst jq
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
FROM node:14
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN npm install && npm run build
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["npm", "run", "serve"]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build the geojson image
|
||||||
|
source ${CERC_CONTAINER_BASE_DIR}/build-base.sh
|
||||||
|
docker build -t cerc/geojson:local -f ${CERC_CONTAINER_BASE_DIR}/cerc-geojson/Dockerfile ${build_command_args} ${CERC_REPO_BASE_DIR}/geojson.io
|
||||||
|
#docker build -t cerc/geojson:local -f ${CERC_REPO_BASE_DIR}/geojson.io/Dockerfile ${build_command_args} ${CERC_REPO_BASE_DIR}/geojson.io
|
||||||
@@ -30,7 +30,7 @@ RUN \
|
|||||||
|
|
||||||
# [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 procps
|
&& apt-get -y install --no-install-recommends jq gettext-base
|
||||||
|
|
||||||
# [Optional] Uncomment if you want to install more global node modules
|
# [Optional] Uncomment if you want to install more global node modules
|
||||||
# RUN su node -c "npm install -g <your-package-list-here>"
|
# RUN su node -c "npm install -g <your-package-list-here>"
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# birbit
|
||||||
|
|
||||||
|
takes geo-tagged images, publishes the metadata on chain, then generates a geojson front end of that geo data
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
version: "0.1"
|
||||||
|
name: birbit
|
||||||
|
repos:
|
||||||
|
#- github.com/zramsay/birbit
|
||||||
|
- github.com/mapbox/geojson.io
|
||||||
|
containers:
|
||||||
|
#- cerc/birbit
|
||||||
|
- cerc/geojson
|
||||||
|
pods:
|
||||||
|
# configurable process that reads from chain and generates a view for the front end
|
||||||
|
# for MVP is basically does nothing, b/c FE view is reading all records
|
||||||
|
# imagine a 'watcher' for a specific view
|
||||||
|
#- birbit
|
||||||
|
# front end, gets its file from kubo;
|
||||||
|
# https://github.com/mapbox/geojson.io/blob/main/API.md#datadatatextx-url
|
||||||
|
# deployed via kubernetes
|
||||||
|
- geojson
|
||||||
|
# hosts all the dot geojson files that are required to generated views
|
||||||
|
- kubo
|
||||||
@@ -54,44 +54,19 @@ def _get_ports(stack):
|
|||||||
|
|
||||||
def _get_named_volumes(stack):
|
def _get_named_volumes(stack):
|
||||||
# Parse the compose files looking for named volumes
|
# Parse the compose files looking for named volumes
|
||||||
named_volumes = {
|
named_volumes = []
|
||||||
"rw": [],
|
|
||||||
"ro": []
|
|
||||||
}
|
|
||||||
parsed_stack = get_parsed_stack_config(stack)
|
parsed_stack = get_parsed_stack_config(stack)
|
||||||
pods = get_pod_list(parsed_stack)
|
pods = get_pod_list(parsed_stack)
|
||||||
yaml = get_yaml()
|
yaml = get_yaml()
|
||||||
|
|
||||||
def find_vol_usage(parsed_pod_file, vol):
|
|
||||||
ret = {}
|
|
||||||
if "services" in parsed_pod_file:
|
|
||||||
for svc_name, svc in parsed_pod_file["services"].items():
|
|
||||||
if "volumes" in svc:
|
|
||||||
for svc_volume in svc["volumes"]:
|
|
||||||
parts = svc_volume.split(":")
|
|
||||||
if parts[0] == vol:
|
|
||||||
ret[svc_name] = {
|
|
||||||
"volume": parts[0],
|
|
||||||
"mount": parts[1],
|
|
||||||
"options": parts[2] if len(parts) == 3 else None
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
for pod in pods:
|
for pod in pods:
|
||||||
pod_file_path = get_pod_file_path(parsed_stack, pod)
|
pod_file_path = get_pod_file_path(parsed_stack, pod)
|
||||||
parsed_pod_file = yaml.load(open(pod_file_path, "r"))
|
parsed_pod_file = yaml.load(open(pod_file_path, "r"))
|
||||||
if "volumes" in parsed_pod_file:
|
if "volumes" in parsed_pod_file:
|
||||||
volumes = parsed_pod_file["volumes"]
|
volumes = parsed_pod_file["volumes"]
|
||||||
for volume in volumes.keys():
|
for volume in volumes.keys():
|
||||||
for vu in find_vol_usage(parsed_pod_file, volume).values():
|
# Volume definition looks like:
|
||||||
read_only = vu["options"] == "ro"
|
# 'laconicd-data': None
|
||||||
if read_only:
|
named_volumes.append(volume)
|
||||||
if vu["volume"] not in named_volumes["rw"] and vu["volume"] not in named_volumes["ro"]:
|
|
||||||
named_volumes["ro"].append(vu["volume"])
|
|
||||||
else:
|
|
||||||
if vu["volume"] not in named_volumes["rw"]:
|
|
||||||
named_volumes["rw"].append(vu["volume"])
|
|
||||||
|
|
||||||
return named_volumes
|
return named_volumes
|
||||||
|
|
||||||
|
|
||||||
@@ -123,24 +98,12 @@ def _fixup_pod_file(pod, spec, compose_dir):
|
|||||||
_create_bind_dir_if_relative(volume, volume_spec, compose_dir)
|
_create_bind_dir_if_relative(volume, volume_spec, compose_dir)
|
||||||
new_volume_spec = {"driver": "local",
|
new_volume_spec = {"driver": "local",
|
||||||
"driver_opts": {
|
"driver_opts": {
|
||||||
"type": "none",
|
"type": "none",
|
||||||
"device": volume_spec_fixedup,
|
"device": volume_spec_fixedup,
|
||||||
"o": "bind"
|
"o": "bind"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pod["volumes"][volume] = new_volume_spec
|
pod["volumes"][volume] = new_volume_spec
|
||||||
|
|
||||||
# Fix up configmaps
|
|
||||||
if "configmaps" in spec:
|
|
||||||
spec_cfgmaps = spec["configmaps"]
|
|
||||||
if "volumes" in pod:
|
|
||||||
pod_volumes = pod["volumes"]
|
|
||||||
for volume in pod_volumes.keys():
|
|
||||||
if volume in spec_cfgmaps:
|
|
||||||
volume_cfg = spec_cfgmaps[volume]
|
|
||||||
# Just make the dir (if necessary)
|
|
||||||
_create_bind_dir_if_relative(volume, volume_cfg, compose_dir)
|
|
||||||
|
|
||||||
# Fix up ports
|
# Fix up ports
|
||||||
if "network" in spec and "ports" in spec["network"]:
|
if "network" in spec and "ports" in spec["network"]:
|
||||||
spec_ports = spec["network"]["ports"]
|
spec_ports = spec["network"]["ports"]
|
||||||
@@ -356,18 +319,9 @@ def init_operation(deploy_command_context, stack, deployer_type, config,
|
|||||||
named_volumes = _get_named_volumes(stack)
|
named_volumes = _get_named_volumes(stack)
|
||||||
if named_volumes:
|
if named_volumes:
|
||||||
volume_descriptors = {}
|
volume_descriptors = {}
|
||||||
configmap_descriptors = {}
|
for named_volume in named_volumes:
|
||||||
for named_volume in named_volumes["rw"]:
|
|
||||||
volume_descriptors[named_volume] = f"./data/{named_volume}"
|
volume_descriptors[named_volume] = f"./data/{named_volume}"
|
||||||
for named_volume in named_volumes["ro"]:
|
|
||||||
if "k8s" in deployer_type:
|
|
||||||
if "config" in named_volume:
|
|
||||||
configmap_descriptors[named_volume] = f"./data/{named_volume}"
|
|
||||||
else:
|
|
||||||
volume_descriptors[named_volume] = f"./data/{named_volume}"
|
|
||||||
spec_file_content["volumes"] = volume_descriptors
|
spec_file_content["volumes"] = volume_descriptors
|
||||||
if configmap_descriptors:
|
|
||||||
spec_file_content["configmaps"] = configmap_descriptors
|
|
||||||
|
|
||||||
if opts.o.debug:
|
if opts.o.debug:
|
||||||
print(f"Creating spec file for stack: {stack} with content: {spec_file_content}")
|
print(f"Creating spec file for stack: {stack} with content: {spec_file_content}")
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ def _image_needs_pushed(image: str):
|
|||||||
|
|
||||||
def remote_tag_for_image(image: str, remote_repo_url: 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)
|
(org, image_name_with_version) = image.split("/")
|
||||||
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":
|
||||||
return f"{remote_repo_url}/{image_name}:deploy"
|
return f"{remote_repo_url}/{image_name}:deploy"
|
||||||
|
|||||||
@@ -13,8 +13,6 @@
|
|||||||
# 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 kubernetes import client
|
from kubernetes import client
|
||||||
from typing import Any, List, Set
|
from typing import Any, List, Set
|
||||||
|
|
||||||
@@ -114,10 +112,9 @@ class ClusterInfo:
|
|||||||
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:
|
port = int(service_info["ports"][0])
|
||||||
port = int(service_info["ports"][0])
|
if opts.o.debug:
|
||||||
if opts.o.debug:
|
print(f"service port: {port}")
|
||||||
print(f"service port: {port}")
|
|
||||||
service = client.V1Service(
|
service = client.V1Service(
|
||||||
metadata=client.V1ObjectMeta(name=f"{self.app_name}-service"),
|
metadata=client.V1ObjectMeta(name=f"{self.app_name}-service"),
|
||||||
spec=client.V1ServiceSpec(
|
spec=client.V1ServiceSpec(
|
||||||
@@ -133,70 +130,30 @@ class ClusterInfo:
|
|||||||
|
|
||||||
def get_pvcs(self):
|
def get_pvcs(self):
|
||||||
result = []
|
result = []
|
||||||
spec_volumes = self.spec.get_volumes()
|
volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map)
|
||||||
named_volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map)
|
|
||||||
if opts.o.debug:
|
if opts.o.debug:
|
||||||
print(f"Spec Volumes: {spec_volumes}")
|
print(f"Volumes: {volumes}")
|
||||||
print(f"Named Volumes: {named_volumes}")
|
for volume_name in volumes:
|
||||||
for volume_name in spec_volumes:
|
|
||||||
if volume_name not in named_volumes:
|
|
||||||
if opts.o.debug:
|
|
||||||
print(f"{volume_name} not in pod files")
|
|
||||||
continue
|
|
||||||
spec = client.V1PersistentVolumeClaimSpec(
|
spec = client.V1PersistentVolumeClaimSpec(
|
||||||
access_modes=["ReadWriteOnce"],
|
access_modes=["ReadWriteOnce"],
|
||||||
storage_class_name="manual",
|
storage_class_name="manual",
|
||||||
resources=client.V1ResourceRequirements(
|
resources=client.V1ResourceRequirements(
|
||||||
requests={"storage": "2Gi"}
|
requests={"storage": "2Gi"}
|
||||||
),
|
),
|
||||||
volume_name=f"{self.app_name}-{volume_name}"
|
volume_name=volume_name
|
||||||
)
|
)
|
||||||
pvc = client.V1PersistentVolumeClaim(
|
pvc = client.V1PersistentVolumeClaim(
|
||||||
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{volume_name}",
|
metadata=client.V1ObjectMeta(name=volume_name,
|
||||||
labels={"volume-label": f"{self.app_name}-{volume_name}"}),
|
labels={"volume-label": volume_name}),
|
||||||
spec=spec,
|
spec=spec,
|
||||||
)
|
)
|
||||||
result.append(pvc)
|
result.append(pvc)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_configmaps(self):
|
|
||||||
result = []
|
|
||||||
spec_configmaps = self.spec.get_configmaps()
|
|
||||||
named_volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map)
|
|
||||||
for cfg_map_name, cfg_map_path in spec_configmaps.items():
|
|
||||||
if cfg_map_name not in named_volumes:
|
|
||||||
if opts.o.debug:
|
|
||||||
print(f"{cfg_map_name} not in pod files")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not cfg_map_path.startswith("/"):
|
|
||||||
cfg_map_path = os.path.join(os.path.dirname(self.spec.file_path), cfg_map_path)
|
|
||||||
|
|
||||||
# Read in all the files at a single-level of the directory. This mimics the behavior
|
|
||||||
# of `kubectl create configmap foo --from-file=/path/to/dir`
|
|
||||||
data = {}
|
|
||||||
for f in os.listdir(cfg_map_path):
|
|
||||||
full_path = os.path.join(cfg_map_path, f)
|
|
||||||
if os.path.isfile(full_path):
|
|
||||||
data[f] = open(full_path, 'rt').read()
|
|
||||||
|
|
||||||
spec = client.V1ConfigMap(
|
|
||||||
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{cfg_map_name}",
|
|
||||||
labels={"configmap-label": cfg_map_name}),
|
|
||||||
data=data
|
|
||||||
)
|
|
||||||
result.append(spec)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_pvs(self):
|
def get_pvs(self):
|
||||||
result = []
|
result = []
|
||||||
spec_volumes = self.spec.get_volumes()
|
volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map)
|
||||||
named_volumes = named_volumes_from_pod_files(self.parsed_pod_yaml_map)
|
for volume_name in volumes:
|
||||||
for volume_name in spec_volumes:
|
|
||||||
if volume_name not in named_volumes:
|
|
||||||
if opts.o.debug:
|
|
||||||
print(f"{volume_name} not in pod files")
|
|
||||||
continue
|
|
||||||
spec = client.V1PersistentVolumeSpec(
|
spec = client.V1PersistentVolumeSpec(
|
||||||
storage_class_name="manual",
|
storage_class_name="manual",
|
||||||
access_modes=["ReadWriteOnce"],
|
access_modes=["ReadWriteOnce"],
|
||||||
@@ -204,8 +161,8 @@ class ClusterInfo:
|
|||||||
host_path=client.V1HostPathVolumeSource(path=get_node_pv_mount_path(volume_name))
|
host_path=client.V1HostPathVolumeSource(path=get_node_pv_mount_path(volume_name))
|
||||||
)
|
)
|
||||||
pv = client.V1PersistentVolume(
|
pv = client.V1PersistentVolume(
|
||||||
metadata=client.V1ObjectMeta(name=f"{self.app_name}-{volume_name}",
|
metadata=client.V1ObjectMeta(name=volume_name,
|
||||||
labels={"volume-label": f"{self.app_name}-{volume_name}"}),
|
labels={"volume-label": volume_name}),
|
||||||
spec=spec,
|
spec=spec,
|
||||||
)
|
)
|
||||||
result.append(pv)
|
result.append(pv)
|
||||||
@@ -221,11 +178,10 @@ class ClusterInfo:
|
|||||||
container_name = service_name
|
container_name = service_name
|
||||||
service_info = services[service_name]
|
service_info = services[service_name]
|
||||||
image = service_info["image"]
|
image = service_info["image"]
|
||||||
if "ports" in service_info:
|
port = int(service_info["ports"][0])
|
||||||
port = int(service_info["ports"][0])
|
if opts.o.debug:
|
||||||
if opts.o.debug:
|
print(f"image: {image}")
|
||||||
print(f"image: {image}")
|
print(f"service port: {port}")
|
||||||
print(f"service port: {port}")
|
|
||||||
# Re-write the image tag for remote deployment
|
# Re-write the image tag for remote deployment
|
||||||
image_to_use = remote_tag_for_image(
|
image_to_use = remote_tag_for_image(
|
||||||
image, self.spec.get_image_registry()) if self.spec.get_image_registry() is not None else image
|
image, self.spec.get_image_registry()) if self.spec.get_image_registry() is not None else image
|
||||||
@@ -239,11 +195,11 @@ class ClusterInfo:
|
|||||||
volume_mounts=volume_mounts,
|
volume_mounts=volume_mounts,
|
||||||
resources=client.V1ResourceRequirements(
|
resources=client.V1ResourceRequirements(
|
||||||
requests={"cpu": "100m", "memory": "200Mi"},
|
requests={"cpu": "100m", "memory": "200Mi"},
|
||||||
limits={"cpu": "1000m", "memory": "2000Mi"},
|
limits={"cpu": "500m", "memory": "500Mi"},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
containers.append(container)
|
containers.append(container)
|
||||||
volumes = volumes_for_pod_files(self.parsed_pod_yaml_map, self.spec)
|
volumes = volumes_for_pod_files(self.parsed_pod_yaml_map)
|
||||||
image_pull_secrets = [client.V1LocalObjectReference(name="laconic-registry")]
|
image_pull_secrets = [client.V1LocalObjectReference(name="laconic-registry")]
|
||||||
template = client.V1PodTemplateSpec(
|
template = client.V1PodTemplateSpec(
|
||||||
metadata=client.V1ObjectMeta(labels={"app": self.app_name}),
|
metadata=client.V1ObjectMeta(labels={"app": self.app_name}),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# Copyright © 2023 Vulcanize
|
# Copyright © 2023 Vulcanize
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# 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
|
# 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
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
@@ -109,20 +110,6 @@ class K8sDeployer(Deployer):
|
|||||||
if opts.o.debug:
|
if opts.o.debug:
|
||||||
print("PVCs created:")
|
print("PVCs created:")
|
||||||
print(f"{pvc_resp}")
|
print(f"{pvc_resp}")
|
||||||
|
|
||||||
# Figure out the ConfigMaps for this deployment
|
|
||||||
config_maps = self.cluster_info.get_configmaps()
|
|
||||||
for cfg_map in config_maps:
|
|
||||||
if opts.o.debug:
|
|
||||||
print(f"Sending this ConfigMap: {cfg_map}")
|
|
||||||
cfg_rsp = self.core_api.create_namespaced_config_map(
|
|
||||||
body=cfg_map,
|
|
||||||
namespace=self.k8s_namespace
|
|
||||||
)
|
|
||||||
if opts.o.debug:
|
|
||||||
print("ConfigMap created:")
|
|
||||||
print(f"{cfg_rsp}")
|
|
||||||
|
|
||||||
# Process compose files into a Deployment
|
# Process compose files into a Deployment
|
||||||
deployment = self.cluster_info.get_deployment(image_pull_policy=None if self.is_kind() else "Always")
|
deployment = self.cluster_info.get_deployment(image_pull_policy=None if self.is_kind() else "Always")
|
||||||
# Create the k8s objects
|
# Create the k8s objects
|
||||||
@@ -148,21 +135,17 @@ class K8sDeployer(Deployer):
|
|||||||
if not self.is_kind():
|
if not self.is_kind():
|
||||||
ingress: client.V1Ingress = self.cluster_info.get_ingress()
|
ingress: client.V1Ingress = self.cluster_info.get_ingress()
|
||||||
|
|
||||||
if ingress:
|
if opts.o.debug:
|
||||||
if opts.o.debug:
|
print(f"Sending this ingress: {ingress}")
|
||||||
print(f"Sending this ingress: {ingress}")
|
ingress_resp = self.networking_api.create_namespaced_ingress(
|
||||||
ingress_resp = self.networking_api.create_namespaced_ingress(
|
namespace=self.k8s_namespace,
|
||||||
namespace=self.k8s_namespace,
|
body=ingress
|
||||||
body=ingress
|
)
|
||||||
)
|
if opts.o.debug:
|
||||||
if opts.o.debug:
|
print("Ingress created:")
|
||||||
print("Ingress created:")
|
print(f"{ingress_resp}")
|
||||||
print(f"{ingress_resp}")
|
|
||||||
else:
|
|
||||||
if opts.o.debug:
|
|
||||||
print("No ingress configured")
|
|
||||||
|
|
||||||
def down(self, timeout, volumes): # noqa: C901
|
def down(self, timeout, volumes):
|
||||||
self.connect_api()
|
self.connect_api()
|
||||||
# Delete the k8s objects
|
# Delete the k8s objects
|
||||||
# Create the host-path-mounted PVs for this deployment
|
# Create the host-path-mounted PVs for this deployment
|
||||||
@@ -192,22 +175,6 @@ class K8sDeployer(Deployer):
|
|||||||
print(f"{pvc_resp}")
|
print(f"{pvc_resp}")
|
||||||
except client.exceptions.ApiException as e:
|
except client.exceptions.ApiException as e:
|
||||||
_check_delete_exception(e)
|
_check_delete_exception(e)
|
||||||
|
|
||||||
# Figure out the ConfigMaps for this deployment
|
|
||||||
cfg_maps = self.cluster_info.get_configmaps()
|
|
||||||
for cfg_map in cfg_maps:
|
|
||||||
if opts.o.debug:
|
|
||||||
print(f"Deleting this ConfigMap: {cfg_map}")
|
|
||||||
try:
|
|
||||||
cfg_map_resp = self.core_api.delete_namespaced_config_map(
|
|
||||||
name=cfg_map.metadata.name, namespace=self.k8s_namespace
|
|
||||||
)
|
|
||||||
if opts.o.debug:
|
|
||||||
print("ConfigMap deleted:")
|
|
||||||
print(f"{cfg_map_resp}")
|
|
||||||
except client.exceptions.ApiException as e:
|
|
||||||
_check_delete_exception(e)
|
|
||||||
|
|
||||||
deployment = self.cluster_info.get_deployment()
|
deployment = self.cluster_info.get_deployment()
|
||||||
if opts.o.debug:
|
if opts.o.debug:
|
||||||
print(f"Deleting this deployment: {deployment}")
|
print(f"Deleting this deployment: {deployment}")
|
||||||
@@ -231,18 +198,14 @@ class K8sDeployer(Deployer):
|
|||||||
|
|
||||||
if not self.is_kind():
|
if not self.is_kind():
|
||||||
ingress: client.V1Ingress = self.cluster_info.get_ingress()
|
ingress: client.V1Ingress = self.cluster_info.get_ingress()
|
||||||
if ingress:
|
if opts.o.debug:
|
||||||
if opts.o.debug:
|
print(f"Deleting this ingress: {ingress}")
|
||||||
print(f"Deleting this ingress: {ingress}")
|
try:
|
||||||
try:
|
self.networking_api.delete_namespaced_ingress(
|
||||||
self.networking_api.delete_namespaced_ingress(
|
name=ingress.metadata.name, namespace=self.k8s_namespace
|
||||||
name=ingress.metadata.name, namespace=self.k8s_namespace
|
)
|
||||||
)
|
except client.exceptions.ApiException as e:
|
||||||
except client.exceptions.ApiException as e:
|
_check_delete_exception(e)
|
||||||
_check_delete_exception(e)
|
|
||||||
else:
|
|
||||||
if opts.o.debug:
|
|
||||||
print("No ingress to delete")
|
|
||||||
|
|
||||||
if self.is_kind():
|
if self.is_kind():
|
||||||
# Destroy the kind cluster
|
# Destroy the kind cluster
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ def named_volumes_from_pod_files(parsed_pod_files):
|
|||||||
parsed_pod_file = parsed_pod_files[pod]
|
parsed_pod_file = parsed_pod_files[pod]
|
||||||
if "volumes" in parsed_pod_file:
|
if "volumes" in parsed_pod_file:
|
||||||
volumes = parsed_pod_file["volumes"]
|
volumes = parsed_pod_file["volumes"]
|
||||||
for volume, value in volumes.items():
|
for volume in volumes.keys():
|
||||||
# Volume definition looks like:
|
# Volume definition looks like:
|
||||||
# 'laconicd-data': None
|
# 'laconicd-data': None
|
||||||
named_volumes.append(volume)
|
named_volumes.append(volume)
|
||||||
@@ -98,31 +98,22 @@ def volume_mounts_for_service(parsed_pod_files, service):
|
|||||||
volumes = service_obj["volumes"]
|
volumes = service_obj["volumes"]
|
||||||
for mount_string in volumes:
|
for mount_string in volumes:
|
||||||
# Looks like: test-data:/data
|
# Looks like: test-data:/data
|
||||||
parts = mount_string.split(":")
|
(volume_name, mount_path) = mount_string.split(":")
|
||||||
volume_name = parts[0]
|
volume_device = client.V1VolumeMount(mount_path=mount_path, name=volume_name)
|
||||||
mount_path = parts[1]
|
|
||||||
mount_options = parts[2] if len(parts) == 3 else None
|
|
||||||
volume_device = client.V1VolumeMount(
|
|
||||||
mount_path=mount_path, name=volume_name, read_only="ro" == mount_options)
|
|
||||||
result.append(volume_device)
|
result.append(volume_device)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def volumes_for_pod_files(parsed_pod_files, spec):
|
def volumes_for_pod_files(parsed_pod_files):
|
||||||
result = []
|
result = []
|
||||||
for pod in parsed_pod_files:
|
for pod in parsed_pod_files:
|
||||||
parsed_pod_file = parsed_pod_files[pod]
|
parsed_pod_file = parsed_pod_files[pod]
|
||||||
if "volumes" in parsed_pod_file:
|
if "volumes" in parsed_pod_file:
|
||||||
volumes = parsed_pod_file["volumes"]
|
volumes = parsed_pod_file["volumes"]
|
||||||
for volume_name in volumes.keys():
|
for volume_name in volumes.keys():
|
||||||
if volume_name in spec.get_configmaps():
|
claim = client.V1PersistentVolumeClaimVolumeSource(claim_name=volume_name)
|
||||||
config_map = client.V1ConfigMapVolumeSource(name=volume_name)
|
volume = client.V1Volume(name=volume_name, persistent_volume_claim=claim)
|
||||||
volume = client.V1Volume(name=volume_name, config_map=config_map)
|
result.append(volume)
|
||||||
result.append(volume)
|
|
||||||
else:
|
|
||||||
claim = client.V1PersistentVolumeClaimVolumeSource(claim_name=volume_name)
|
|
||||||
volume = client.V1Volume(name=volume_name, persistent_volume_claim=claim)
|
|
||||||
result.append(volume)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -167,7 +158,7 @@ def _generate_kind_mounts(parsed_pod_files, deployment_dir):
|
|||||||
volume_definitions.append(
|
volume_definitions.append(
|
||||||
f" - hostPath: {_make_absolute_host_path(volume_host_path_map[volume_name], deployment_dir)}\n"
|
f" - hostPath: {_make_absolute_host_path(volume_host_path_map[volume_name], deployment_dir)}\n"
|
||||||
f" containerPath: {get_node_pv_mount_path(volume_name)}"
|
f" containerPath: {get_node_pv_mount_path(volume_name)}"
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
"" if len(volume_definitions) == 0 else (
|
"" if len(volume_definitions) == 0 else (
|
||||||
" extraMounts:\n"
|
" extraMounts:\n"
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ from stack_orchestrator import constants
|
|||||||
class Spec:
|
class Spec:
|
||||||
|
|
||||||
obj: typing.Any
|
obj: typing.Any
|
||||||
file_path: Path
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
pass
|
pass
|
||||||
@@ -30,23 +29,12 @@ class Spec:
|
|||||||
def init_from_file(self, file_path: Path):
|
def init_from_file(self, file_path: Path):
|
||||||
with file_path:
|
with file_path:
|
||||||
self.obj = get_yaml().load(open(file_path, "r"))
|
self.obj = get_yaml().load(open(file_path, "r"))
|
||||||
self.file_path = file_path
|
|
||||||
|
|
||||||
def get_image_registry(self):
|
def get_image_registry(self):
|
||||||
return (self.obj[constants.image_resigtry_key]
|
return (self.obj[constants.image_resigtry_key]
|
||||||
if self.obj and constants.image_resigtry_key in self.obj
|
if self.obj and constants.image_resigtry_key in self.obj
|
||||||
else None)
|
else None)
|
||||||
|
|
||||||
def get_volumes(self):
|
|
||||||
return (self.obj["volumes"]
|
|
||||||
if self.obj and "volumes" in self.obj
|
|
||||||
else {})
|
|
||||||
|
|
||||||
def get_configmaps(self):
|
|
||||||
return (self.obj["configmaps"]
|
|
||||||
if self.obj and "configmaps" in self.obj
|
|
||||||
else {})
|
|
||||||
|
|
||||||
def get_http_proxy(self):
|
def get_http_proxy(self):
|
||||||
return (self.obj[constants.network_key][constants.http_proxy_key]
|
return (self.obj[constants.network_key][constants.http_proxy_key]
|
||||||
if self.obj and constants.network_key in self.obj
|
if self.obj and constants.network_key in self.obj
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ def process_app_deployment_request(
|
|||||||
dns_record = laconic.get_record(dns_crn)
|
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.request:
|
||||||
matched_owner = match_owner(app_deployment_request, laconic.get_record(dns_record.attributes.request, require=True))
|
matched_owner = match_owner(app_deployment_request, laconic.get_record(dns_record.request, require=True))
|
||||||
|
|
||||||
if matched_owner:
|
if matched_owner:
|
||||||
print("Matched DnsRecord ownership:", matched_owner)
|
print("Matched DnsRecord ownership:", matched_owner)
|
||||||
@@ -136,17 +136,13 @@ def load_known_requests(filename):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def dump_known_requests(filename, requests, status="SEEN"):
|
def dump_known_requests(filename, requests):
|
||||||
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] = {
|
known_requests[r.id] = r.createTime
|
||||||
"createTime": r.createTime,
|
json.dump(known_requests, open(filename, "w"))
|
||||||
"status": status
|
|
||||||
}
|
|
||||||
with open(filename, "w") as f:
|
|
||||||
json.dump(known_requests, f)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@@ -205,7 +201,6 @@ def command(ctx, kube_config, laconic_config, image_registry, deployment_parent_
|
|||||||
requests.reverse()
|
requests.reverse()
|
||||||
requests_by_name = {}
|
requests_by_name = {}
|
||||||
for r in requests:
|
for r in requests:
|
||||||
# TODO: Do this _after_ filtering deployments and cancellations to minimize round trips.
|
|
||||||
app = laconic.get_record(r.attributes.application)
|
app = laconic.get_record(r.attributes.application)
|
||||||
if not app:
|
if not app:
|
||||||
print("Skipping request %s, cannot locate app." % r.id)
|
print("Skipping request %s, cannot locate app." % r.id)
|
||||||
@@ -261,8 +256,6 @@ def command(ctx, kube_config, laconic_config, image_registry, deployment_parent_
|
|||||||
|
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
for r in requests_to_execute:
|
for r in requests_to_execute:
|
||||||
dump_known_requests(state_file, [r], "DEPLOYING")
|
|
||||||
status = "ERROR"
|
|
||||||
try:
|
try:
|
||||||
process_app_deployment_request(
|
process_app_deployment_request(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -275,6 +268,5 @@ def command(ctx, kube_config, laconic_config, image_registry, deployment_parent_
|
|||||||
kube_config,
|
kube_config,
|
||||||
image_registry
|
image_registry
|
||||||
)
|
)
|
||||||
status = "DEPLOYED"
|
|
||||||
finally:
|
finally:
|
||||||
dump_known_requests(state_file, [r], status)
|
dump_known_requests(state_file, [r])
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ def process_app_removal_request(ctx,
|
|||||||
matched_owner = match_owner(app_removal_request, deployment_record, dns_record)
|
matched_owner = match_owner(app_removal_request, deployment_record, dns_record)
|
||||||
|
|
||||||
# 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.request:
|
||||||
matched_owner = match_owner(app_removal_request, laconic.get_record(deployment_record.attributes.request, require=True))
|
matched_owner = match_owner(app_removal_request, laconic.get_record(deployment_record.request, require=True))
|
||||||
|
|
||||||
if matched_owner:
|
if matched_owner:
|
||||||
print("Matched deployment ownership:", matched_owner)
|
print("Matched deployment ownership:", matched_owner)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ fi
|
|||||||
|
|
||||||
# Helper functions: TODO move into a separate file
|
# Helper functions: TODO move into a separate file
|
||||||
wait_for_pods_started () {
|
wait_for_pods_started () {
|
||||||
for i in {1..50}
|
for i in {1..5}
|
||||||
do
|
do
|
||||||
local ps_output=$( $TEST_TARGET_SO deployment --dir $test_deployment_dir ps )
|
local ps_output=$( $TEST_TARGET_SO deployment --dir $test_deployment_dir ps )
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ wait_for_pods_started () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wait_for_log_output () {
|
wait_for_log_output () {
|
||||||
for i in {1..50}
|
for i in {1..5}
|
||||||
do
|
do
|
||||||
|
|
||||||
local log_output=$( $TEST_TARGET_SO deployment --dir $test_deployment_dir logs )
|
local log_output=$( $TEST_TARGET_SO deployment --dir $test_deployment_dir logs )
|
||||||
@@ -120,8 +120,7 @@ fi
|
|||||||
# Stop then start again and check the volume was preserved
|
# Stop then start again and check the volume was preserved
|
||||||
$TEST_TARGET_SO deployment --dir $test_deployment_dir stop
|
$TEST_TARGET_SO deployment --dir $test_deployment_dir stop
|
||||||
# Sleep a bit just in case
|
# Sleep a bit just in case
|
||||||
# sleep for longer to check if that's why the subsequent create cluster fails
|
sleep 2
|
||||||
sleep 20
|
|
||||||
$TEST_TARGET_SO deployment --dir $test_deployment_dir start
|
$TEST_TARGET_SO deployment --dir $test_deployment_dir start
|
||||||
wait_for_pods_started
|
wait_for_pods_started
|
||||||
wait_for_log_output
|
wait_for_log_output
|
||||||
|
|||||||
Reference in New Issue
Block a user