Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

# Copyright: (c) 2017, Ansible Project 

# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 

 

from __future__ import (absolute_import, division, print_function) 

__metaclass__ = type 

 

import os 

import sys 

import tempfile 

 

from collections import namedtuple 

 

from yaml import load as yaml_load 

try: 

# use C version if possible for speedup 

from yaml import CSafeLoader as SafeLoader 

except ImportError: 

from yaml import SafeLoader 

 

from ansible.config.data import ConfigData 

from ansible.errors import AnsibleOptionsError, AnsibleError 

from ansible.module_utils.six import string_types 

from ansible.module_utils.six.moves import configparser 

from ansible.module_utils._text import to_text, to_bytes, to_native 

from ansible.module_utils.parsing.convert_bool import boolean 

from ansible.parsing.quoting import unquote 

from ansible.utils.path import unfrackpath 

from ansible.utils.path import makedirs_safe 

 

Plugin = namedtuple('Plugin', 'name type') 

Setting = namedtuple('Setting', 'name value origin type') 

 

 

# FIXME: see if we can unify in module_utils with similar function used by argspec 

def ensure_type(value, value_type, origin=None): 

''' return a configuration variable with casting 

:arg value: The value to ensure correct typing of 

:kwarg value_type: The type of the value. This can be any of the following strings: 

:boolean: sets the value to a True or False value 

:integer: Sets the value to an integer or raises a ValueType error 

:float: Sets the value to a float or raises a ValueType error 

:list: Treats the value as a comma separated list. Split the value 

and return it as a python list. 

:none: Sets the value to None 

:path: Expands any environment variables and tilde's in the value. 

:tmp_path: Create a unique temporary directory inside of the directory 

specified by value and return its path. 

:pathlist: Treat the value as a typical PATH string. (On POSIX, this 

means colon separated strings.) Split the value and then expand 

each part for environment variables and tildes. 

''' 

 

basedir = None 

if origin and os.path.isabs(origin) and os.path.exists(origin): 

basedir = origin 

 

if value_type: 

value_type = value_type.lower() 

 

if value_type in ('boolean', 'bool'): 

value = boolean(value, strict=False) 

 

elif value: 

if value_type in ('integer', 'int'): 

value = int(value) 

 

elif value_type == 'float': 

value = float(value) 

 

elif value_type == 'list': 

if isinstance(value, string_types): 

value = [x.strip() for x in value.split(',')] 

 

74 ↛ 75line 74 didn't jump to line 75, because the condition on line 74 was never true elif value_type == 'none': 

if value == "None": 

value = None 

 

elif value_type == 'path': 

value = resolve_path(value, basedir=basedir) 

 

elif value_type in ('tmp', 'temppath', 'tmppath'): 

value = resolve_path(value, basedir=basedir) 

83 ↛ 84line 83 didn't jump to line 84, because the condition on line 83 was never true if not os.path.exists(value): 

makedirs_safe(value, 0o700) 

prefix = 'ansible-local-%s' % os.getpid() 

value = tempfile.mkdtemp(prefix=prefix, dir=value) 

 

elif value_type == 'pathspec': 

89 ↛ 91line 89 didn't jump to line 91, because the condition on line 89 was never false if isinstance(value, string_types): 

value = value.split(os.pathsep) 

value = [resolve_path(x, basedir=basedir) for x in value] 

 

elif value_type == 'pathlist': 

94 ↛ 96line 94 didn't jump to line 96, because the condition on line 94 was never false if isinstance(value, string_types): 

value = value.split(',') 

value = [resolve_path(x, basedir=basedir) for x in value] 

 

# defaults to string types 

elif isinstance(value, string_types): 

value = unquote(value) 

 

return to_text(value, errors='surrogate_or_strict', nonstring='passthru') 

 

 

# FIXME: see if this can live in utils/path 

def resolve_path(path, basedir=None): 

''' resolve relative or 'varaible' paths ''' 

108 ↛ 109line 108 didn't jump to line 109, because the condition on line 108 was never true if '{{CWD}}' in path: # allow users to force CWD using 'magic' {{CWD}} 

path = path.replace('{{CWD}}', os.getcwd()) 

 

return unfrackpath(path, follow=False, basedir=basedir) 

 

 

# FIXME: generic file type? 

def get_config_type(cfile): 

 

ftype = None 

118 ↛ 127line 118 didn't jump to line 127, because the condition on line 118 was never false if cfile is not None: 

ext = os.path.splitext(cfile)[-1] 

120 ↛ 122line 120 didn't jump to line 122, because the condition on line 120 was never false if ext in ('.ini', '.cfg'): 

ftype = 'ini' 

elif ext in ('.yaml', '.yml'): 

ftype = 'yaml' 

else: 

raise AnsibleOptionsError("Unsupported configuration file extension for %s: %s" % (cfile, to_native(ext))) 

 

return ftype 

 

 

# FIXME: can move to module_utils for use for ini plugins also? 

def get_ini_config_value(p, entry): 

''' returns the value of last ini entry found ''' 

value = None 

134 ↛ 139line 134 didn't jump to line 139, because the condition on line 134 was never false if p is not None: 

try: 

value = p.get(entry.get('section', 'defaults'), entry.get('key', ''), raw=True) 

except Exception: # FIXME: actually report issues here 

pass 

return value 

 

 

def find_ini_config_file(): 

''' Load INI Config File order(first found is used): ENV, CWD, HOME, /etc/ansible ''' 

# FIXME: eventually deprecate ini configs 

 

path0 = os.getenv("ANSIBLE_CONFIG", None) 

147 ↛ 151line 147 didn't jump to line 151, because the condition on line 147 was never false if path0 is not None: 

path0 = unfrackpath(path0, follow=False) 

149 ↛ 150line 149 didn't jump to line 150, because the condition on line 149 was never true if os.path.isdir(path0): 

path0 += "/ansible.cfg" 

try: 

path1 = os.getcwd() + "/ansible.cfg" 

except OSError: 

path1 = None 

path2 = unfrackpath("~/.ansible.cfg", follow=False) 

path3 = "/etc/ansible/ansible.cfg" 

 

158 ↛ 162line 158 didn't jump to line 162, because the loop on line 158 didn't complete for path in [path0, path1, path2, path3]: 

159 ↛ 158line 159 didn't jump to line 158, because the condition on line 159 was never false if path is not None and os.path.exists(path): 

break 

else: 

path = None 

 

return path 

 

 

class ConfigManager(object): 

 

UNABLE = [] 

DEPRECATED = [] 

 

def __init__(self, conf_file=None, defs_file=None): 

 

self._base_defs = {} 

self._plugins = {} 

self._parser = None 

 

self._config_file = conf_file 

self.data = ConfigData() 

 

181 ↛ 185line 181 didn't jump to line 185, because the condition on line 181 was never false if defs_file is None: 

# Create configuration definitions from source 

b_defs_file = to_bytes('%s/base.yml' % os.path.dirname(__file__)) 

else: 

b_defs_file = to_bytes(defs_file) 

 

# consume definitions 

188 ↛ 192line 188 didn't jump to line 192, because the condition on line 188 was never false if os.path.exists(b_defs_file): 

with open(b_defs_file, 'rb') as config_def: 

self._base_defs = yaml_load(config_def, Loader=SafeLoader) 

else: 

raise AnsibleError("Missing base configuration definition file (bad install?): %s" % to_native(b_defs_file)) 

 

194 ↛ 199line 194 didn't jump to line 199, because the condition on line 194 was never false if self._config_file is None: 

# set config using ini 

self._config_file = find_ini_config_file() 

 

# consume configuration 

199 ↛ 205line 199 didn't jump to line 205, because the condition on line 199 was never false if self._config_file: 

200 ↛ 205line 200 didn't jump to line 205, because the condition on line 200 was never false if os.path.exists(self._config_file): 

# initialize parser and read config 

self._parse_config_file() 

 

# update constants 

self.update_config_data() 

 

def _parse_config_file(self, cfile=None): 

''' return flat configuration settings from file(s) ''' 

# TODO: take list of files with merge/nomerge 

 

if cfile is None: 

cfile = self._config_file 

 

ftype = get_config_type(cfile) 

215 ↛ exitline 215 didn't return from function '_parse_config_file', because the condition on line 215 was never false if cfile is not None: 

216 ↛ 227line 216 didn't jump to line 227, because the condition on line 216 was never false if ftype == 'ini': 

self._parser = configparser.ConfigParser() 

try: 

self._parser.read(cfile) 

except configparser.Error as e: 

raise AnsibleOptionsError("Error reading config file (%s): %s" % (cfile, to_native(e))) 

# FIXME: this should eventually handle yaml config files 

# elif ftype == 'yaml': 

# with open(cfile, 'rb') as config_stream: 

# self._parser = yaml.safe_load(config_stream) 

else: 

raise AnsibleOptionsError("Unsupported configuration file type: %s" % to_native(ftype)) 

 

def _find_yaml_config_files(self): 

''' Load YAML Config Files in order, check merge flags, keep origin of settings''' 

pass 

 

def get_plugin_options(self, plugin_type, name, keys=None, variables=None): 

 

options = {} 

defs = self.get_configuration_definitions(plugin_type, name) 

for option in defs: 

options[option] = self.get_config_value(option, plugin_type=plugin_type, plugin_name=name, keys=keys, variables=variables) 

 

return options 

 

def get_plugin_vars(self, plugin_type, name): 

 

pvars = [] 

for pdef in self.get_configuration_definitions(plugin_type, name).values(): 

if 'vars' in pdef and pdef['vars']: 

for var_entry in pdef['vars']: 

pvars.append(var_entry['name']) 

return pvars 

 

def get_configuration_definitions(self, plugin_type=None, name=None): 

''' just list the possible settings, either base or for specific plugins or plugin ''' 

 

ret = {} 

255 ↛ 256line 255 didn't jump to line 256, because the condition on line 255 was never true if plugin_type is None: 

ret = self._base_defs 

257 ↛ 258line 257 didn't jump to line 258, because the condition on line 257 was never true elif name is None: 

ret = self._plugins.get(plugin_type, {}) 

else: 

ret = self._plugins.get(plugin_type, {}).get(name, {}) 

 

return ret 

 

def _loop_entries(self, container, entry_list): 

''' repeat code for value entry assignment ''' 

 

value = None 

origin = None 

for entry in entry_list: 

name = entry.get('name') 

temp_value = container.get(name, None) 

if temp_value is not None: # only set if env var is defined 

value = temp_value 

origin = name 

 

# deal with deprecation of setting source, if used 

277 ↛ 278line 277 didn't jump to line 278, because the condition on line 277 was never true if 'deprecated' in entry: 

self.DEPRECATED.append((entry['name'], entry['deprecated'])) 

 

return value, origin 

 

def get_config_value(self, config, cfile=None, plugin_type=None, plugin_name=None, keys=None, variables=None): 

''' wrapper ''' 

value, _drop = self.get_config_value_and_origin(config, cfile=cfile, plugin_type=plugin_type, plugin_name=plugin_name, keys=keys, variables=variables) 

return value 

 

def get_config_value_and_origin(self, config, cfile=None, plugin_type=None, plugin_name=None, keys=None, variables=None): 

''' Given a config key figure out the actual value and report on the origin of the settings ''' 

 

if cfile is None: 

cfile = self._config_file 

else: 

self._parse_config_file(cfile) 

 

# Note: sources that are lists listed in low to high precedence (last one wins) 

value = None 

origin = None 

defs = {} 

if plugin_type is None: 

defs = self._base_defs 

301 ↛ 302line 301 didn't jump to line 302, because the condition on line 301 was never true elif plugin_name is None: 

defs = self._plugins[plugin_type] 

else: 

defs = self._plugins[plugin_type][plugin_name] 

 

306 ↛ 360line 306 didn't jump to line 360, because the condition on line 306 was never false if config in defs: 

# Use 'variable overrides' if present, highest precedence, but only present when querying running play 

if variables and defs[config].get('vars'): 

value, origin = self._loop_entries(variables, defs[config]['vars']) 

origin = 'var: %s' % origin 

 

# use playbook keywords if you have em 

313 ↛ 314line 313 didn't jump to line 314, because the condition on line 313 was never true if value is None and keys: 

value, origin = self._loop_entries(keys, defs[config]['keywords']) 

origin = 'keyword: %s' % origin 

 

# env vars are next precedence 

if value is None and defs[config].get('env'): 

value, origin = self._loop_entries(os.environ, defs[config]['env']) 

origin = 'env: %s' % origin 

 

# try config file entries next, if we have one 

if value is None and cfile is not None: 

ftype = get_config_type(cfile) 

if ftype and defs[config].get(ftype): 

326 ↛ 338line 326 didn't jump to line 338, because the condition on line 326 was never false if ftype == 'ini': 

# load from ini config 

try: # FIXME: generalize _loop_entries to allow for files also, most of this code is dupe 

for ini_entry in defs[config]['ini']: 

temp_value = get_ini_config_value(self._parser, ini_entry) 

if temp_value is not None: 

value = temp_value 

origin = cfile 

334 ↛ 335line 334 didn't jump to line 335, because the condition on line 334 was never true if 'deprecated' in ini_entry: 

self.DEPRECATED.append(('[%s]%s' % (ini_entry['section'], ini_entry['key']), ini_entry['deprecated'])) 

except Exception as e: 

sys.stderr.write("Error while loading ini config %s: %s" % (cfile, to_native(e))) 

elif ftype == 'yaml': 

# FIXME: implement, also , break down key from defs (. notation???) 

origin = cfile 

 

# set default if we got here w/o a value 

if value is None: 

value = defs[config].get('default') 

origin = 'default' 

# skip typing as this is a temlated default that will be resolved later in constants, which has needed vars 

if plugin_type is None and isinstance(value, string_types) and (value.startswith('{{') and value.endswith('}}')): 

return value, origin 

 

# ensure correct type 

try: 

value = ensure_type(value, defs[config].get('type'), origin=origin) 

except Exception as e: 

self.UNABLE.append(config) 

 

# deal with deprecation of the setting 

357 ↛ 358line 357 didn't jump to line 358, because the condition on line 357 was never true if 'deprecated' in defs[config] and origin != 'default': 

self.DEPRECATED.append((config, defs[config].get('deprecated'))) 

else: 

raise AnsibleError('Requested option %s was not defined in configuration' % to_native(config)) 

 

return value, origin 

 

def initialize_plugin_configuration_definitions(self, plugin_type, name, defs): 

 

if plugin_type not in self._plugins: 

self._plugins[plugin_type] = {} 

 

self._plugins[plugin_type][name] = defs 

 

def update_config_data(self, defs=None, configfile=None): 

''' really: update constants ''' 

 

374 ↛ 377line 374 didn't jump to line 377, because the condition on line 374 was never false if defs is None: 

defs = self._base_defs 

 

377 ↛ 380line 377 didn't jump to line 380, because the condition on line 377 was never false if configfile is None: 

configfile = self._config_file 

 

380 ↛ 381line 380 didn't jump to line 381, because the condition on line 380 was never true if not isinstance(defs, dict): 

raise AnsibleOptionsError("Invalid configuration definition type: %s for %s" % (type(defs), defs)) 

 

# update the constant for config file 

self.data.update_setting(Setting('CONFIG_FILE', configfile, '', 'string')) 

 

origin = None 

# env and config defs can have several entries, ordered in list from lowest to highest precedence 

for config in defs: 

389 ↛ 390line 389 didn't jump to line 390, because the condition on line 389 was never true if not isinstance(defs[config], dict): 

raise AnsibleOptionsError("Invalid configuration definition '%s': type is %s" % (to_native(config), type(defs[config]))) 

 

# get value and origin 

value, origin = self.get_config_value_and_origin(config, configfile) 

 

# set the constant 

self.data.update_setting(Setting(config, value, origin, defs[config].get('type', 'string')))