Compare commits

..
Author SHA1 Message Date
zramsay 87251ba65b modifications to accept atom payments 2025-04-30 11:06:01 -04:00
prathamesh 873a6d472c Update webapp deployment flow for supporting custom domains (#963)
Part of https://www.notion.so/Support-custom-domains-in-deploy-laconic-com-18aa6b22d4728067a44ae27090c02ce5 and cerc-io/snowballtools-base#47

- Set `value` (IP address for `A` resource) in the DNS records
- Update `deploy-webapp-from-registry` command with an option to pass k8s cluster IP address (only required with fqdn policy `allow`)

Reviewed-on: cerc-io/stack-orchestrator#963
Reviewed-by: ashwin <ashwin@noreply.git.vdb.to>
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2025-02-04 13:26:31 +00:00
4 changed files with 158 additions and 86 deletions
@@ -172,7 +172,6 @@ def process_app_deployment_request(
logger.log(
f"Creating webapp deployment in: {deployment_dir} with container id: {deployment_container_tag}"
)
# CREATES DEPLOYMENT DIR, NOT SKIPPING FOR TESTING
deploy_webapp.create_deployment(
ctx,
deployment_dir,
@@ -215,24 +214,21 @@ def process_app_deployment_request(
# add_tags_to_image(image_registry, app_image_shared_tag, deployment_container_tag)
logger.log("Tag complete")
else:
# SKIP BUILD
logger.log("TESTING: Skipping container build.")
# extra_build_args = [] # TODO: pull from request
# logger.log(f"Building container image: {deployment_container_tag}")
# build_container_image(
# app, deployment_container_tag, extra_build_args, logger
# )
# logger.log("Build complete")
# logger.log(f"Pushing container image: {deployment_container_tag}")
# push_container_image(deployment_dir, logger)
# logger.log("Push complete")
# # The build/push commands above will use the unique deployment tag, so now we need to add the shared tag.
# logger.log(
# f"(SKIPPED) Adding global app image tag: {app_image_shared_tag} to newly built image: {deployment_container_tag}"
# )
# # add_tags_to_image(image_registry, deployment_container_tag, app_image_shared_tag)
# logger.log("Tag complete")
extra_build_args = [] # TODO: pull from request
logger.log(f"Building container image: {deployment_container_tag}")
build_container_image(
app, deployment_container_tag, extra_build_args, logger
)
logger.log("Build complete")
logger.log(f"Pushing container image: {deployment_container_tag}")
push_container_image(deployment_dir, logger)
logger.log("Push complete")
# The build/push commands above will use the unique deployment tag, so now we need to add the shared tag.
logger.log(
f"(SKIPPED) Adding global app image tag: {app_image_shared_tag} to newly built image: {deployment_container_tag}"
)
# add_tags_to_image(image_registry, deployment_container_tag, app_image_shared_tag)
logger.log("Tag complete")
else:
logger.log("Requested app is already deployed, skipping build and image push")
@@ -245,9 +241,7 @@ def process_app_deployment_request(
# 8. update k8s deployment
if needs_k8s_deploy:
# SKIP DEPLOY
logger.log("TESTING: Skipping deployment to k8s.")
# deploy_to_k8s(deployment_record, deployment_dir, recreate_on_deploy, logger)
deploy_to_k8s(deployment_record, deployment_dir, recreate_on_deploy, logger)
logger.log("Publishing deployment to registry.")
publish_deployment(
@@ -348,6 +342,17 @@ def dump_known_requests(filename, requests, status="SEEN"):
help="Requests must have a minimum payment to be processed (in alnt)",
default=0,
)
@click.option(
"--atom-payment-address",
help="Cosmos ATOM address to receive payments",
default=None,
)
@click.option(
"--min-atom-payment",
help="Minimum required ATOM payment amount",
default=1,
type=float,
)
@click.option("--lrn", help="The LRN of this deployer.", required=True)
@click.option(
"--all-requests",
@@ -400,6 +405,8 @@ def command( # noqa: C901
recreate_on_deploy,
log_dir,
min_required_payment,
atom_payment_address,
min_atom_payment,
lrn,
config_upload_dir,
private_key_file,
@@ -636,6 +643,8 @@ def command( # noqa: C901
payment_address,
min_required_payment,
main_logger,
atom_payment_address,
min_atom_payment,
):
main_logger.log(f"{r.id}: Payment confirmed.")
requests_to_execute.append(r)
@@ -46,6 +46,17 @@ from stack_orchestrator.deploy.webapp.util import LaconicRegistryClient
help="List the minimum required payment (in alnt) to process a deployment request.",
default=0,
)
@click.option(
"--atom-payment-address",
help="The Cosmos ATOM address to which payments should be made.",
default=None,
)
@click.option(
"--min-atom-payment",
help="List the minimum required payment (in ATOM) to process a deployment request.",
default=1,
type=float,
)
@click.option(
"--dry-run",
help="Don't publish anything, just report what would be done.",
@@ -60,6 +71,8 @@ def command( # noqa: C901
lrn,
payment_address,
min_required_payment,
atom_payment_address,
min_atom_payment,
dry_run,
):
laconic = LaconicRegistryClient(laconic_config)
@@ -83,6 +96,10 @@ def command( # noqa: C901
webapp_deployer_record["record"][
"minimumPayment"
] = f"{min_required_payment}alnt"
if atom_payment_address:
webapp_deployer_record["record"]["atomPaymentAddress"] = atom_payment_address
webapp_deployer_record["record"]["minimumAtomPayment"] = min_atom_payment
if dry_run:
yaml.dump(webapp_deployer_record, sys.stdout)
@@ -72,12 +72,11 @@ def process_app_removal_request(
# TODO(telackey): Call the function directly. The easiest way to build the correct click context is to
# exec the process, but it would be better to refactor so we could just call down_operation with the
# necessary parameters
main_logger.log("TESTING: Skipping stopping deployment.")
# down_command = [sys.argv[0], "deployment", "--dir", deployment_dir, "down"]
# if delete_volumes:
# down_command.append("--delete-volumes")
# result = subprocess.run(down_command)
# result.check_returncode()
down_command = [sys.argv[0], "deployment", "--dir", deployment_dir, "down"]
if delete_volumes:
down_command.append("--delete-volumes")
result = subprocess.run(down_command)
result.check_returncode()
removal_record = {
"record": {
+105 -58
View File
@@ -801,7 +801,7 @@ def skip_by_tag(r, include_tags, exclude_tags):
return False
def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min_amount, logger):
def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min_amount, logger, atom_payment_address=None, atom_min_amount=None):
req_owner = laconic.get_owner(record)
if req_owner == payment_address:
# No need to confirm payment if the sender and recipient are the same account.
@@ -811,67 +811,114 @@ def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min
logger.log(f"{record.id}: no payment tx info")
return False
# Try to verify as a laconic payment first
tx = laconic.get_tx(record.attributes.payment)
if not tx:
logger.log(f"{record.id}: cannot locate payment tx")
return False
if tx.code != 0:
logger.log(
f"{record.id}: payment tx {tx.hash} was not successful - code: {tx.code}, log: {tx.log}"
)
return False
if tx.sender != req_owner:
logger.log(
f"{record.id}: payment sender {tx.sender} in tx {tx.hash} does not match deployment "
f"request owner {req_owner}"
)
return False
if tx.recipient != payment_address:
logger.log(
f"{record.id}: payment recipient {tx.recipient} in tx {tx.hash} does not match {payment_address}"
)
return False
pay_denom = "".join([i for i in tx.amount if not i.isdigit()])
if pay_denom != "alnt":
logger.log(
f"{record.id}: {pay_denom} in tx {tx.hash} is not an expected payment denomination"
)
return False
pay_amount = int("".join([i for i in tx.amount if i.isdigit()]))
if pay_amount < min_amount:
logger.log(
f"{record.id}: payment amount {tx.amount} is less than minimum {min_amount}"
)
return False
# Check if the payment was already used on a deployment
used = laconic.app_deployments(
{"deployer": record.attributes.deployer, "payment": tx.hash}, all=True
)
if len(used):
# Fetch the app name from request record
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}")
if tx:
if tx.code != 0:
logger.log(
f"{record.id}: payment tx {tx.hash} was not successful - code: {tx.code}, log: {tx.log}"
)
return False
used = laconic.app_deployment_removals(
{"deployer": record.attributes.deployer, "payment": tx.hash}, all=True
)
if len(used):
logger.log(
f"{record.id}: payment {tx.hash} already used on deployment removal {used}"
)
return False
if tx.sender != req_owner:
logger.log(
f"{record.id}: payment sender {tx.sender} in tx {tx.hash} does not match deployment "
f"request owner {req_owner}"
)
return False
return True
if tx.recipient != payment_address:
logger.log(
f"{record.id}: payment recipient {tx.recipient} in tx {tx.hash} does not match {payment_address}"
)
return False
pay_denom = "".join([i for i in tx.amount if not i.isdigit()])
if pay_denom != "alnt":
logger.log(
f"{record.id}: {pay_denom} in tx {tx.hash} is not an expected payment denomination"
)
return False
pay_amount = int("".join([i for i in tx.amount if i.isdigit()]))
if pay_amount < min_amount:
logger.log(
f"{record.id}: payment amount {tx.amount} is less than minimum {min_amount}"
)
return False
# Check if the payment was already used on a deployment
used = laconic.app_deployments(
{"deployer": record.attributes.deployer, "payment": tx.hash}, all=True
)
if len(used):
# Fetch the app name from request record
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
used = laconic.app_deployment_removals(
{"deployer": record.attributes.deployer, "payment": tx.hash}, all=True
)
if len(used):
logger.log(
f"{record.id}: payment {tx.hash} already used on deployment removal {used}"
)
return False
return True
# If we get here, the transaction hash wasn't found in the laconic testnet
# Let's check if it's a valid Cosmos ATOM payment if configuration is available
if atom_payment_address:
logger.log(f"{record.id}: checking if payment is a valid Cosmos ATOM transaction")
try:
import requests
# Use the webapp-deployment-status-api to verify the ATOM payment
deployer_record = laconic.get_record(record.attributes.deployer)
if not deployer_record or not deployer_record.attributes.apiUrl:
logger.log(f"{record.id}: cannot find deployer API URL to verify ATOM payment")
return False
api_url = deployer_record.attributes.apiUrl
verify_url = f"{api_url}/verify/atom-payment"
# Make a request to the API to verify the ATOM payment
# Pass markAsUsed=true to prevent this transaction from being used again
response = requests.post(
verify_url,
json={
"txHash": record.attributes.payment,
"minAmount": atom_min_amount,
"markAsUsed": True
},
timeout=10
)
if response.status_code != 200:
logger.log(f"{record.id}: ATOM payment verification API request failed with status {response.status_code}")
return False
result = response.json()
if not result.get("valid", False):
logger.log(f"{record.id}: ATOM payment verification failed: {result.get('reason', 'unknown reason')}")
return False
# Payment is valid
logger.log(f"{record.id}: ATOM payment verified successfully, amount: {result.get('amount')} ATOM")
return True
except Exception as e:
logger.log(f"{record.id}: error verifying ATOM payment: {str(e)}")
return False
logger.log(f"{record.id}: payment tx {record.attributes.payment} not found in laconic testnet and ATOM payment verification not configured")
return False
def confirm_auction(laconic: LaconicRegistryClient, record, deployer_lrn, payment_address, logger):