33import logging
44from ipaddress import IPv6Interface
55from json import JSONDecodeError
6- from typing import Optional
6+ from typing import Any , Optional
77
88import aiohttp
99from aleph .sdk import AlephHttpClient
1414from pydantic import ValidationError
1515
1616from aleph_client .commands import help_strings
17+ from aleph_client .commands .files import ipfs_content
1718from aleph_client .commands .node import NodeInfo , _fetch_nodes
1819from aleph_client .commands .utils import safe_getattr
1920from aleph_client .models import MachineUsage
20- from aleph_client .utils import AsyncTyper , fetch_json , sanitize_url
21+ from aleph_client .utils import fetch_json , sanitize_url
2122
2223logger = logging .getLogger (__name__ )
2324
@@ -68,7 +69,7 @@ async def fetch_crn_info(node_url: str) -> dict | None:
6869 return None
6970
7071
71- async def fetch_vm_info (message : InstanceMessage , node_list : NodeInfo ) -> tuple [str , dict [str , object ]]:
72+ async def fetch_vm_info (message : InstanceMessage , node_list : NodeInfo ) -> tuple [str , dict [str , Any ]]:
7273 """
7374 Fetches VM information given an instance message and the node list.
7475
@@ -93,6 +94,7 @@ async def fetch_vm_info(message: InstanceMessage, node_list: NodeInfo) -> tuple[
9394 allocation_type = "" ,
9495 ipv6_logs = "" ,
9596 crn_url = "" ,
97+ terms_and_conditions = dict (hash = None , url = None , accepted = False , info = None ),
9698 )
9799 try :
98100 # Fetch from the scheduler API directly if no payment or no receiver (hold-tier non-confidential)
@@ -118,6 +120,30 @@ async def fetch_vm_info(message: InstanceMessage, node_list: NodeInfo) -> tuple[
118120 for node in node_list .nodes :
119121 if node ["hash" ] == safe_getattr (message , "content.requirements.node.node_hash" ):
120122 info ["crn_url" ] = node ["address" ].rstrip ("/" )
123+
124+ # Terms and conditions
125+ if "terms_and_conditions" in node :
126+ latest_tac_hash = str (node ["terms_and_conditions" ])
127+ latest_tac_url = (
128+ await ipfs_content (latest_tac_hash , verbose = False ) or f"missing → { latest_tac_hash } "
129+ )
130+ vm_tac_hash = safe_getattr (message , "content.requirements.node.terms_and_conditions" )
131+ tac_accepted = vm_tac_hash and latest_tac_hash == str (vm_tac_hash )
132+ tac_info = None
133+ if not tac_accepted :
134+ vm_tac_url = (
135+ (await ipfs_content (vm_tac_hash , verbose = False ) or f"missing → { vm_tac_hash } " )
136+ if vm_tac_hash
137+ else None
138+ )
139+ tac_info = dict (
140+ outdated = f"Outdated: { vm_tac_hash } \n URL: { vm_tac_url } " ,
141+ latest = f"Latest: { latest_tac_hash } \n URL: { latest_tac_url } " ,
142+ )
143+ info ["terms_and_conditions" ] = dict (
144+ hash = latest_tac_hash , url = latest_tac_url , accepted = tac_accepted , info = tac_info
145+ )
146+
121147 path = f"{ node ['address' ].rstrip ('/' )} /about/executions/list"
122148 executions = await fetch_json (session , path )
123149 if message .item_hash in executions :
0 commit comments