44import argparse
55import configobj
66import contextlib
7+ import functools
8+ import json
79import logging
810import os
9- import json
1011import re
1112import uuid
1213import yaml
1314
15+ import jinja2
16+
1417from copy import deepcopy
1518from io import BytesIO , StringIO
1619from tarfile import ReadError
2730
2831# these items we use from ceph.py should probably eventually move elsewhere
2932from tasks .ceph import get_mons , healthy
30- from tasks .vip import subst_vip
3133
3234CEPH_ROLE_TYPES = ['mon' , 'mgr' , 'osd' , 'mds' , 'rgw' , 'prometheus' ]
3335
3436log = logging .getLogger (__name__ )
3537
3638
39+ def _convert_strs_in (o , conv ):
40+ """A function to walk the contents of a dict/list and recurisvely apply
41+ a conversion function (`conv`) to the strings within.
42+ """
43+ if isinstance (o , str ):
44+ return conv (o )
45+ if isinstance (o , dict ):
46+ for k in o :
47+ o [k ] = _convert_strs_in (o [k ], conv )
48+ if isinstance (o , list ):
49+ o [:] = [_convert_strs_in (v , conv ) for v in o ]
50+ return o
51+
52+
53+ def _apply_template (jinja_env , rctx , template ):
54+ """Apply jinja2 templating to the template string `template` via the jinja
55+ environment `jinja_env`, passing a dictionary containing top-level context
56+ to render into the template.
57+ """
58+ if '{{' in template or '{%' in template :
59+ return jinja_env .from_string (template ).render (** rctx )
60+ return template
61+
62+
63+ def _template_transform (ctx , config , target ):
64+ """Apply jinja2 based templates to strings within the target object,
65+ returning a transformed target. Target objects may be a list or dict or
66+ str.
67+
68+ Note that only string values in the list or dict objects are modified.
69+ Therefore one can read & parse yaml or json that contain templates in
70+ string values without the risk of changing the structure of the yaml/json.
71+ """
72+ jenv = getattr (ctx , '_jinja_env' , None )
73+ if jenv is None :
74+ loader = jinja2 .BaseLoader ()
75+ jenv = jinja2 .Environment (loader = loader )
76+ setattr (ctx , '_jinja_env' , jenv )
77+ rctx = dict (ctx = ctx , config = config , cluster_name = config .get ('cluster' , '' ))
78+ _vip_vars (rctx )
79+ conv = functools .partial (_apply_template , jenv , rctx )
80+ return _convert_strs_in (target , conv )
81+
82+
83+ def _vip_vars (rctx ):
84+ """For backwards compat with the previous subst_vip function."""
85+ ctx = rctx ['ctx' ]
86+ if 'vnet' in getattr (ctx , 'vip' , {}):
87+ rctx ['VIPPREFIXLEN' ] = str (ctx .vip ["vnet" ].prefixlen )
88+ rctx ['VIPSUBNET' ] = str (ctx .vip ["vnet" ].network_address )
89+ if 'vips' in getattr (ctx , 'vip' , {}):
90+ vips = ctx .vip ['vips' ]
91+ for idx , vip in enumerate (vips ):
92+ rctx [f'VIP{ idx } ' ] = str (vip )
93+
94+
3795def _shell (ctx , cluster_name , remote , args , extra_cephadm_args = [], ** kwargs ):
3896 teuthology .get_testdir (ctx )
3997 return remote .run (
@@ -1356,18 +1414,19 @@ def shell(ctx, config):
13561414 roles = teuthology .all_roles (ctx .cluster )
13571415 config = dict ((id_ , a ) for id_ in roles if id_ .startswith ('host.' ))
13581416
1417+ config = _template_transform (ctx , config , config )
13591418 for role , cmd in config .items ():
13601419 (remote ,) = ctx .cluster .only (role ).remotes .keys ()
13611420 log .info ('Running commands on role %s host %s' , role , remote .name )
13621421 if isinstance (cmd , list ):
13631422 for c in cmd :
13641423 _shell (ctx , cluster_name , remote ,
1365- ['bash' , '-c' , subst_vip ( ctx , c ) ],
1424+ ['bash' , '-c' , c ],
13661425 extra_cephadm_args = args )
13671426 else :
13681427 assert isinstance (cmd , str )
13691428 _shell (ctx , cluster_name , remote ,
1370- ['bash' , '-ex' , '-c' , subst_vip ( ctx , cmd ) ],
1429+ ['bash' , '-ex' , '-c' , cmd ],
13711430 extra_cephadm_args = args )
13721431
13731432
@@ -1393,7 +1452,8 @@ def apply(ctx, config):
13931452 cluster_name = config .get ('cluster' , 'ceph' )
13941453
13951454 specs = config .get ('specs' , [])
1396- y = subst_vip (ctx , yaml .dump_all (specs ))
1455+ specs = _template_transform (ctx , config , specs )
1456+ y = yaml .dump_all (specs )
13971457
13981458 log .info (f'Applying spec(s):\n { y } ' )
13991459 _shell (
0 commit comments