Compare commits

..

No commits in common. "main" and "ag-check-payments" have entirely different histories.

14 changed files with 79 additions and 170 deletions

View File

@ -26,14 +26,8 @@ fi
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
WORK_DIR="${1:-/app}" WORK_DIR="${1:-/app}"
if [ -f "${WORK_DIR}/build-webapp.sh" ]; then
echo "Building webapp with ${WORK_DIR}/build-webapp.sh ..."
cd "${WORK_DIR}" || exit 1 cd "${WORK_DIR}" || exit 1
./build-webapp.sh || exit 1
exit 0
fi
if [ -f "next.config.mjs" ]; then if [ -f "next.config.mjs" ]; then
NEXT_CONFIG_JS="next.config.mjs" NEXT_CONFIG_JS="next.config.mjs"
IMPORT_OR_REQUIRE="import" IMPORT_OR_REQUIRE="import"

View File

@ -30,18 +30,11 @@ fi
CERC_WEBAPP_FILES_DIR="${CERC_WEBAPP_FILES_DIR:-/app}" CERC_WEBAPP_FILES_DIR="${CERC_WEBAPP_FILES_DIR:-/app}"
cd "$CERC_WEBAPP_FILES_DIR" cd "$CERC_WEBAPP_FILES_DIR"
if [ -f "./run-webapp.sh" ]; then "$SCRIPT_DIR/apply-runtime-env.sh" "`pwd`" .next .next-r
echo "Running webapp with run-webapp.sh ..." mv .next .next.old
cd "${WORK_DIR}" || exit 1 mv .next-r/.next .
./run-webapp.sh &
tpid=$!
wait $tpid
else
"$SCRIPT_DIR/apply-runtime-env.sh" "`pwd`" .next .next-r
mv .next .next.old
mv .next-r/.next .
if [ "$CERC_NEXTJS_SKIP_GENERATE" != "true" ]; then if [ "$CERC_NEXTJS_SKIP_GENERATE" != "true" ]; then
jq -e '.scripts.cerc_generate' package.json >/dev/null jq -e '.scripts.cerc_generate' package.json >/dev/null
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
npm run cerc_generate > gen.out 2>&1 & npm run cerc_generate > gen.out 2>&1 &
@ -67,7 +60,6 @@ else
kill $tpid $(ps -ef | grep node | grep next | grep generate | awk '{print $2}') 2>/dev/null kill $tpid $(ps -ef | grep node | grep next | grep generate | awk '{print $2}') 2>/dev/null
tpid="" tpid=""
fi fi
fi
$CERC_BUILD_TOOL start . -- -p ${CERC_LISTEN_PORT:-80}
fi fi
$CERC_BUILD_TOOL start . -- -p ${CERC_LISTEN_PORT:-80}

View File

@ -14,6 +14,7 @@
# 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.deploy.deployment_context import DeploymentContext from stack_orchestrator.deploy.deployment_context import DeploymentContext
from ruamel.yaml import YAML
def create(context: DeploymentContext, extra_args): def create(context: DeploymentContext, extra_args):
@ -22,12 +23,17 @@ def create(context: DeploymentContext, extra_args):
# deterministic-deployment-proxy contract, which itself is a prereq for Optimism contract deployment # deterministic-deployment-proxy contract, which itself is a prereq for Optimism contract deployment
fixturenet_eth_compose_file = context.deployment_dir.joinpath('compose', 'docker-compose-fixturenet-eth.yml') fixturenet_eth_compose_file = context.deployment_dir.joinpath('compose', 'docker-compose-fixturenet-eth.yml')
with open(fixturenet_eth_compose_file, 'r') as yaml_file:
yaml = YAML()
yaml_data = yaml.load(yaml_file)
new_script = '../config/fixturenet-optimism/run-geth.sh:/opt/testnet/run.sh' new_script = '../config/fixturenet-optimism/run-geth.sh:/opt/testnet/run.sh'
def add_geth_volume(yaml_data):
if new_script not in yaml_data['services']['fixturenet-eth-geth-1']['volumes']: if new_script not in yaml_data['services']['fixturenet-eth-geth-1']['volumes']:
yaml_data['services']['fixturenet-eth-geth-1']['volumes'].append(new_script) yaml_data['services']['fixturenet-eth-geth-1']['volumes'].append(new_script)
context.modify_yaml(fixturenet_eth_compose_file, add_geth_volume) with open(fixturenet_eth_compose_file, 'w') as yaml_file:
yaml = YAML()
yaml.dump(yaml_data, yaml_file)
return None return None

View File

@ -2,6 +2,7 @@ version: "1.0"
name: test name: test
description: "A test stack" description: "A test stack"
repos: repos:
- git.vdb.to/cerc-io/laconicd
- git.vdb.to/cerc-io/test-project@test-branch - git.vdb.to/cerc-io/test-project@test-branch
containers: containers:
- cerc/test-container - cerc/test-container

View File

@ -45,20 +45,18 @@ class DeploymentContext:
def get_compose_dir(self): def get_compose_dir(self):
return self.deployment_dir.joinpath(constants.compose_dir_name) return self.deployment_dir.joinpath(constants.compose_dir_name)
def get_compose_file(self, name: str):
return self.get_compose_dir() / f"docker-compose-{name}.yml"
def get_cluster_id(self): def get_cluster_id(self):
return self.id return self.id
def init(self, dir: Path): def init(self, dir):
self.deployment_dir = dir.absolute() self.deployment_dir = dir
self.spec = Spec() self.spec = Spec()
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() deployment_file_path = self.get_deployment_file()
if deployment_file_path.exists(): if deployment_file_path.exists():
with deployment_file_path:
obj = get_yaml().load(open(deployment_file_path, "r")) obj = get_yaml().load(open(deployment_file_path, "r"))
self.id = obj[constants.cluster_id_key] self.id = obj[constants.cluster_id_key]
# Handle the case of a legacy deployment with no file # Handle the case of a legacy deployment with no file
@ -69,19 +67,3 @@ class DeploymentContext:
unique_cluster_descriptor = f"{path},{self.get_stack_file()},None,None" unique_cluster_descriptor = f"{path},{self.get_stack_file()},None,None"
hash = hashlib.md5(unique_cluster_descriptor.encode()).hexdigest()[:16] hash = hashlib.md5(unique_cluster_descriptor.encode()).hexdigest()[:16]
self.id = f"{constants.cluster_name_prefix}{hash}" self.id = f"{constants.cluster_name_prefix}{hash}"
def modify_yaml(self, file_path: Path, modifier_func):
"""
Load a YAML from the deployment, apply a modification function, and write it back.
"""
if not file_path.absolute().is_relative_to(self.deployment_dir):
raise ValueError(f"File is not inside deployment directory: {file_path}")
yaml = get_yaml()
with open(file_path, 'r') as f:
yaml_data = yaml.load(f)
modifier_func(yaml_data)
with open(file_path, 'w') as f:
yaml.dump(yaml_data, f)

View File

@ -443,16 +443,18 @@ def _check_volume_definitions(spec):
@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")
@click.argument('extra_args', nargs=-1, type=click.UNPROCESSED) # TODO: Hack
@click.option("--network-dir", help="Network configuration supplied in this directory")
@click.option("--initial-peers", help="Initial set of persistent peers")
@click.pass_context @click.pass_context
def create(ctx, spec_file, deployment_dir, extra_args): def create(ctx, spec_file, deployment_dir, network_dir, initial_peers):
deployment_command_context = ctx.obj deployment_command_context = ctx.obj
return create_operation(deployment_command_context, spec_file, deployment_dir, extra_args) return create_operation(deployment_command_context, spec_file, deployment_dir, network_dir, initial_peers)
# The init command's implementation is in a separate function so that we can # The init command's implementation is in a separate function so that we can
# call it from other commands, bypassing the click decoration stuff # call it from other commands, bypassing the click decoration stuff
def create_operation(deployment_command_context, spec_file, deployment_dir, extra_args): def create_operation(deployment_command_context, spec_file, deployment_dir, network_dir, initial_peers):
parsed_spec = Spec(os.path.abspath(spec_file), get_parsed_deployment_spec(spec_file)) parsed_spec = Spec(os.path.abspath(spec_file), get_parsed_deployment_spec(spec_file))
_check_volume_definitions(parsed_spec) _check_volume_definitions(parsed_spec)
stack_name = parsed_spec["stack"] stack_name = parsed_spec["stack"]
@ -539,7 +541,7 @@ def create_operation(deployment_command_context, spec_file, deployment_dir, extr
deployer_config_generator = getDeployerConfigGenerator(deployment_type, deployment_context) deployer_config_generator = getDeployerConfigGenerator(deployment_type, deployment_context)
# TODO: make deployment_dir_path a Path above # TODO: make deployment_dir_path a Path above
deployer_config_generator.generate(deployment_dir_path) deployer_config_generator.generate(deployment_dir_path)
call_stack_deploy_create(deployment_context, extra_args) call_stack_deploy_create(deployment_context, [network_dir, initial_peers, deployment_command_context])
# TODO: this code should be in the stack .py files but # TODO: this code should be in the stack .py files but

View File

@ -92,6 +92,7 @@ class Spec:
return self.obj.get(item, default) return self.obj.get(item, default)
def init_from_file(self, file_path: Path): def init_from_file(self, file_path: 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 self.file_path = file_path

View File

@ -27,4 +27,5 @@ class Stack:
self.name = name self.name = name
def init_from_file(self, file_path: Path): def init_from_file(self, file_path: Path):
with file_path:
self.obj = get_yaml().load(open(file_path, "r")) self.obj = get_yaml().load(open(file_path, "r"))

View File

@ -92,6 +92,7 @@ def create_deployment(ctx, deployment_dir, image, url, kube_config, image_regist
spec_file_name, spec_file_name,
deployment_dir, deployment_dir,
None, None,
None
) )
# Fix up the container tag inside the deployment compose file # Fix up the container tag inside the deployment compose file
_fixup_container_tag(deployment_dir, image) _fixup_container_tag(deployment_dir, image)

View File

@ -54,7 +54,6 @@ def process_app_deployment_request(
deployment_record_namespace, deployment_record_namespace,
dns_record_namespace, dns_record_namespace,
default_dns_suffix, default_dns_suffix,
dns_value,
deployment_parent_dir, deployment_parent_dir,
kube_config, kube_config,
image_registry, image_registry,
@ -252,7 +251,6 @@ def process_app_deployment_request(
dns_record, dns_record,
dns_lrn, dns_lrn,
deployment_dir, deployment_dir,
dns_value,
app_deployment_request, app_deployment_request,
webapp_deployer_record, webapp_deployer_record,
logger, logger,
@ -306,7 +304,6 @@ def dump_known_requests(filename, requests, status="SEEN"):
help="How to handle requests with an FQDN: prohibit, allow, preexisting", help="How to handle requests with an FQDN: prohibit, allow, preexisting",
default="prohibit", default="prohibit",
) )
@click.option("--ip", help="IP address of the k8s deployment (to be set in DNS record)", default=None)
@click.option("--record-namespace-dns", help="eg, lrn://laconic/dns", required=True) @click.option("--record-namespace-dns", help="eg, lrn://laconic/dns", required=True)
@click.option( @click.option(
"--record-namespace-deployments", "--record-namespace-deployments",
@ -384,7 +381,6 @@ def command( # noqa: C901
only_update_state, only_update_state,
dns_suffix, dns_suffix,
fqdn_policy, fqdn_policy,
ip,
record_namespace_dns, record_namespace_dns,
record_namespace_deployments, record_namespace_deployments,
dry_run, dry_run,
@ -433,13 +429,6 @@ def command( # noqa: C901
) )
sys.exit(2) sys.exit(2)
if fqdn_policy == "allow" and not ip:
print(
"--ip is required with 'allow' fqdn-policy",
file=sys.stderr,
)
sys.exit(2)
tempdir = tempfile.mkdtemp() tempdir = tempfile.mkdtemp()
gpg = gnupg.GPG(gnupghome=tempdir) gpg = gnupg.GPG(gnupghome=tempdir)
@ -676,7 +665,6 @@ def command( # noqa: C901
record_namespace_deployments, record_namespace_deployments,
record_namespace_dns, record_namespace_dns,
dns_suffix, dns_suffix,
ip,
os.path.abspath(deployment_parent_dir), os.path.abspath(deployment_parent_dir),
kube_config, kube_config,
image_registry, image_registry,

View File

@ -1,59 +1,8 @@
import fcntl
from functools import wraps from functools import wraps
import os
import time
# Define default file path for the lock # Define default file path for the lock
DEFAULT_LOCK_FILE_PATH = "/tmp/registry_mutex_lock_file" 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 registry_mutex():
@ -64,13 +13,18 @@ def registry_mutex():
if self.mutex_lock_file: if self.mutex_lock_file:
lock_file_path = self.mutex_lock_file lock_file_path = self.mutex_lock_file
# Acquire the lock before running the function with open(lock_file_path, 'w') as lock_file:
acquire_lock(self, lock_file_path, LOCK_TIMEOUT)
try: try:
return func(self, *args, **kwargs) # Try to acquire the lock
fcntl.flock(lock_file, fcntl.LOCK_EX)
# Call the actual function
result = func(self, *args, **kwargs)
finally: finally:
# Release the lock after the function completes # Always release the lock
release_lock(self, lock_file_path) fcntl.flock(lock_file, fcntl.LOCK_UN)
return result
return wrapper return wrapper

View File

@ -117,6 +117,7 @@ class LaconicRegistryClient:
def __init__(self, config_file, log_file=None, mutex_lock_file=None): def __init__(self, config_file, log_file=None, mutex_lock_file=None):
self.config_file = config_file self.config_file = config_file
self.log_file = log_file self.log_file = log_file
self.mutex_lock_file = mutex_lock_file
self.cache = AttrDict( self.cache = AttrDict(
{ {
"name_or_id": {}, "name_or_id": {},
@ -125,9 +126,6 @@ class LaconicRegistryClient:
} }
) )
self.mutex_lock_file = mutex_lock_file
self.mutex_lock_acquired = False
def whoami(self, refresh=False): def whoami(self, refresh=False):
if not refresh and "whoami" in self.cache: if not refresh and "whoami" in self.cache:
return self.cache["whoami"] return self.cache["whoami"]
@ -689,7 +687,6 @@ def publish_deployment(
dns_record, dns_record,
dns_lrn, dns_lrn,
deployment_dir, deployment_dir,
dns_value=None,
app_deployment_request=None, app_deployment_request=None,
webapp_deployer_record=None, webapp_deployer_record=None,
logger=None, logger=None,
@ -722,8 +719,6 @@ def publish_deployment(
} }
if app_deployment_request: if app_deployment_request:
new_dns_record["record"]["request"] = app_deployment_request.id new_dns_record["record"]["request"] = app_deployment_request.id
if dns_value:
new_dns_record["record"]["value"] = dns_value
if logger: if logger:
logger.log("Publishing DnsRecord.") logger.log("Publishing DnsRecord.")
@ -849,21 +844,16 @@ def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min
) )
return False return False
# Check if the payment was already used on a deployment # Check if the payment was already used on a
used = laconic.app_deployments( used = laconic.app_deployments(
{"deployer": record.attributes.deployer, "payment": tx.hash}, all=True {"deployer": payment_address, "payment": tx.hash}, all=True
) )
if len(used): if len(used):
# Fetch the app name from request record logger.log(f"{record.id}: payment {tx.hash} already used on deployment {used}")
used_request = laconic.get_record(used[0].attributes.request, require=True)
# Check that payment was used for deployment of same application
if record.attributes.application != used_request.attributes.application:
logger.log(f"{record.id}: payment {tx.hash} already used on a different application deployment {used}")
return False return False
used = laconic.app_deployment_removals( used = laconic.app_deployment_removals(
{"deployer": record.attributes.deployer, "payment": tx.hash}, all=True {"deployer": payment_address, "payment": tx.hash}, all=True
) )
if len(used): if len(used):
logger.log( logger.log(

View File

@ -180,7 +180,9 @@ def get_k8s_dir():
def get_parsed_deployment_spec(spec_file): def get_parsed_deployment_spec(spec_file):
spec_file_path = Path(spec_file) spec_file_path = Path(spec_file)
try: try:
return get_yaml().load(open(spec_file_path, "r")) with spec_file_path:
deploy_spec = get_yaml().load(open(spec_file_path, "r"))
return deploy_spec
except FileNotFoundError as error: except FileNotFoundError as error:
# We try here to generate a useful diagnostic error # We try here to generate a useful diagnostic error
print(f"Error: spec file: {spec_file_path} does not exist") print(f"Error: spec file: {spec_file_path} does not exist")

View File

@ -14,13 +14,8 @@ delete_cluster_exit () {
# Test basic stack-orchestrator deploy # Test basic stack-orchestrator deploy
echo "Running stack-orchestrator deploy test" echo "Running stack-orchestrator deploy test"
# Bit of a hack, test the most recent package
if [ "$1" == "from-path" ]; then TEST_TARGET_SO=$( ls -t1 ./package/laconic-so* | head -1 )
TEST_TARGET_SO="laconic-so"
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"