88'ec deploy <ioc_name> <ioc_version> and then managing the network with a
99tool like Portainer is a decent workflow.
1010"""
11-
11+ import re
1212import shutil
1313from datetime import datetime
1414from pathlib import Path
1515from tempfile import mkdtemp
1616from typing import Optional
1717
18+ import requests
1819import typer
1920
2021import epics_containers_cli .globals as glob_vars
2122from epics_containers_cli .docker import Docker
22- from epics_containers_cli .globals import CONFIG_FOLDER , IOC_CONFIG_FOLDER , Context
23+ from epics_containers_cli .globals import (
24+ CONFIG_FILE ,
25+ CONFIG_FOLDER ,
26+ IOC_CONFIG_FOLDER ,
27+ Context ,
28+ )
2329from epics_containers_cli .logging import log
2430from epics_containers_cli .shell import check_beamline_repo , run_command
25- from epics_containers_cli .utils import check_ioc_instance_path , get_instance_image_name
31+ from epics_containers_cli .utils import (
32+ check_ioc_instance_path ,
33+ generic_ioc_from_image ,
34+ get_instance_image_name ,
35+ )
2636
2737
2838class IocLocalCommands :
2939 """
3040 A class for implementing the ioc command namespace for local docker/podman
3141 """
3242
33- def __init__ (self , ctx : Optional [Context ], ioc_name : str = "" ):
43+ def __init__ (
44+ self , ctx : Optional [Context ], ioc_name : str = "" , with_docker : bool = True
45+ ):
3446 self .beamline_repo : str = ""
3547 if ctx is not None :
3648 self .beamline_repo = ctx .beamline_repo
@@ -39,7 +51,7 @@ def __init__(self, ctx: Optional[Context], ioc_name: str = ""):
3951
4052 self .tmp = Path (mkdtemp ())
4153 self .ioc_folder = self .tmp / "iocs" / ioc_name
42- self .docker = Docker ()
54+ self .docker = Docker (check = with_docker )
4355
4456 def __del__ (self ):
4557 # keep the tmp folder if debug is enabled for inspection_del
@@ -160,3 +172,56 @@ def ps(self, all: bool, wide: bool):
160172
161173 for row in rows :
162174 print ("{0: <20.20} {1: <20.20} {2: <23.23} {3}" .format (* row ))
175+
176+ def validate_instance (self , ioc_instance : Path ):
177+ check_ioc_instance_path (ioc_instance )
178+
179+ typer .echo (f"Validating { ioc_instance } " )
180+
181+ ioc_config_file = ioc_instance / CONFIG_FOLDER / CONFIG_FILE
182+ image = get_instance_image_name (ioc_instance )
183+ image_name , image_tag = image .split (":" )
184+
185+ tmp = Path (mkdtemp ())
186+ schema_file = tmp / "schema.json"
187+
188+ # not all IOCs have a config file so no config validation for them
189+ if ioc_config_file .exists ():
190+ config = ioc_config_file .read_text ()
191+ matches = re .findall (r"#.* \$schema=(.*)" , config )
192+ if not matches :
193+ raise RuntimeError ("No schema modeline found in {ioc_config_file}" )
194+
195+ schema_url = matches [0 ]
196+
197+ with requests .get (schema_url , allow_redirects = True ) as r :
198+ schema_file .write_text (r .content .decode ())
199+
200+ if not run_command ("yajsv -v" , interactive = False , error_OK = True ):
201+ typer .echo (
202+ "yajsv, used for schema validation of ioc.yaml, is not installed. "
203+ "Please install from https://github.com/neilpa/yajsv"
204+ )
205+ raise typer .Exit (1 )
206+
207+ run_command (f"yajsv -s { schema_file } { ioc_config_file } " , interactive = False )
208+
209+ # check that the image name and the schema are from the same generic IOC
210+ if image_tag not in schema_url :
211+ log .error (f"image version { image_tag } and { schema_url } do not match" )
212+ raise typer .Exit (1 )
213+
214+ # make sure that generic IOC name matches the schema
215+ generic_ioc = generic_ioc_from_image (image_name )
216+ if generic_ioc not in schema_url :
217+ log .error (
218+ f"ioc.yaml schema { schema_url } does not match generic IOC { generic_ioc } "
219+ )
220+ raise typer .Exit (1 )
221+
222+ # verify that the values.yaml file points to a container image that exists
223+ run_command (f"{ self .docker .docker } image inspect { image } " , interactive = False )
224+
225+ shutil .rmtree (tmp , ignore_errors = True )
226+
227+ typer .echo (f"{ ioc_instance } validated successfully" )
0 commit comments