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

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

# This code is part of Ansible, but is an independent component. 

# This particular file snippet, and this file snippet only, is BSD licensed. 

# Modules you write using this snippet, which is embedded dynamically by Ansible 

# still belong to the author of the module, and may assign their own license 

# to the complete work. 

# 

# (c) 2016 Red Hat Inc. 

# 

# Redistribution and use in source and binary forms, with or without modification, 

# are permitted provided that the following conditions are met: 

# 

# * Redistributions of source code must retain the above copyright 

# notice, this list of conditions and the following disclaimer. 

# * Redistributions in binary form must reproduce the above copyright notice, 

# this list of conditions and the following disclaimer in the documentation 

# and/or other materials provided with the distribution. 

# 

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 

# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 

# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 

# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 

# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 

# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 

# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 

# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 

# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

# 

import re 

import hashlib 

 

from ansible.module_utils.six.moves import zip 

from ansible.module_utils._text import to_bytes, to_native 

from ansible.module_utils.network.common.utils import to_list 

 

DEFAULT_COMMENT_TOKENS = ['#', '!', '/*', '*/', 'echo'] 

 

DEFAULT_IGNORE_LINES_RE = set([ 

re.compile(r"Using \d+ out of \d+ bytes"), 

re.compile(r"Building configuration"), 

re.compile(r"Current configuration : \d+ bytes") 

]) 

 

 

class ConfigLine(object): 

 

def __init__(self, raw): 

self.text = str(raw).strip() 

self.raw = raw 

self._children = list() 

self._parents = list() 

 

def __str__(self): 

return self.raw 

 

def __eq__(self, other): 

return self.line == other.line 

 

def __ne__(self, other): 

return not self.__eq__(other) 

 

def __getitem__(self, key): 

for item in self._children: 

if item.text == key: 

return item 

raise KeyError(key) 

 

@property 

def line(self): 

line = self.parents 

line.append(self.text) 

return ' '.join(line) 

 

@property 

def children(self): 

return _obj_to_text(self._children) 

 

@property 

def child_objs(self): 

return self._children 

 

@property 

def parents(self): 

return _obj_to_text(self._parents) 

 

@property 

def path(self): 

config = _obj_to_raw(self._parents) 

config.append(self.raw) 

return '\n'.join(config) 

 

@property 

def has_children(self): 

return len(self._children) > 0 

 

@property 

def has_parents(self): 

return len(self._parents) > 0 

 

def add_child(self, obj): 

100 ↛ 101line 100 didn't jump to line 101, because the condition on line 100 was never true if not isinstance(obj, ConfigLine): 

raise AssertionError('child must be of type `ConfigLine`') 

self._children.append(obj) 

 

 

def ignore_line(text, tokens=None): 

for item in (tokens or DEFAULT_COMMENT_TOKENS): 

if text.startswith(item): 

return True 

for regex in DEFAULT_IGNORE_LINES_RE: 

110 ↛ 111line 110 didn't jump to line 111, because the condition on line 110 was never true if regex.match(text): 

return True 

 

 

def _obj_to_text(x): 

return [o.text for o in x] 

 

 

def _obj_to_raw(x): 

return [o.raw for o in x] 

 

 

def _obj_to_block(objects, visited=None): 

items = list() 

for o in objects: 

if o not in items: 

items.append(o) 

for child in o._children: 

if child not in items: 

items.append(child) 

return _obj_to_raw(items) 

 

 

def dumps(objects, output='block', comments=False): 

134 ↛ 135line 134 didn't jump to line 135, because the condition on line 134 was never true if output == 'block': 

items = _obj_to_block(objects) 

136 ↛ 139line 136 didn't jump to line 139, because the condition on line 136 was never false elif output == 'commands': 

items = _obj_to_text(objects) 

else: 

raise TypeError('unknown value supplied for keyword output') 

 

141 ↛ 142line 141 didn't jump to line 142, because the condition on line 141 was never true if output != 'commands': 

if comments: 

for index, item in enumerate(items): 

nextitem = index + 1 

if nextitem < len(items) and not item.startswith(' ') and items[nextitem].startswith(' '): 

item = '!\n%s' % item 

items[index] = item 

items.append('!') 

items.append('end') 

 

return '\n'.join(items) 

 

 

class NetworkConfig(object): 

 

def __init__(self, indent=1, contents=None, ignore_lines=None): 

self._indent = indent 

self._items = list() 

self._config_text = None 

 

161 ↛ 162line 161 didn't jump to line 162, because the condition on line 161 was never true if ignore_lines: 

for item in ignore_lines: 

if not isinstance(item, re._pattern_type): 

item = re.compile(item) 

DEFAULT_IGNORE_LINES_RE.add(item) 

 

if contents: 

self.load(contents) 

 

@property 

def items(self): 

return self._items 

 

@property 

def config_text(self): 

return self._config_text 

 

@property 

def sha1(self): 

sha1 = hashlib.sha1() 

sha1.update(to_bytes(str(self), errors='surrogate_or_strict')) 

return sha1.digest() 

 

def __getitem__(self, key): 

for line in self: 

if line.text == key: 

return line 

raise KeyError(key) 

 

def __iter__(self): 

return iter(self._items) 

 

def __str__(self): 

return '\n'.join([c.raw for c in self.items]) 

 

def __len__(self): 

return len(self._items) 

 

def load(self, s): 

self._config_text = s 

self._items = self.parse(s) 

 

def loadfp(self, fp): 

return self.load(open(fp).read()) 

 

def parse(self, lines, comment_tokens=None): 

toplevel = re.compile(r'\S') 

childline = re.compile(r'^\s*(.+)$') 

entry_reg = re.compile(r'([{};])') 

 

ancestors = list() 

config = list() 

 

curlevel = 0 

prevlevel = 0 

 

for linenum, line in enumerate(to_native(lines, errors='surrogate_or_strict').split('\n')): 

text = entry_reg.sub('', line).strip() 

 

cfg = ConfigLine(line) 

 

if not text or ignore_line(text, comment_tokens): 

continue 

 

# handle top level commands 

if toplevel.match(line): 

ancestors = [cfg] 

prevlevel = curlevel 

curlevel = 0 

 

# handle sub level commands 

else: 

match = childline.match(line) 

line_indent = match.start(1) 

 

prevlevel = curlevel 

curlevel = int(line_indent / self._indent) 

 

if (curlevel - 1) > prevlevel: 

curlevel = prevlevel + 1 

 

parent_level = curlevel - 1 

 

cfg._parents = ancestors[:curlevel] 

 

246 ↛ 247line 246 didn't jump to line 247, because the condition on line 246 was never true if curlevel > len(ancestors): 

config.append(cfg) 

continue 

 

for i in range(curlevel, len(ancestors)): 

ancestors.pop() 

 

ancestors.append(cfg) 

ancestors[parent_level].add_child(cfg) 

 

config.append(cfg) 

 

return config 

 

def get_object(self, path): 

for item in self.items: 

if item.text == path[-1]: 

263 ↛ 261line 263 didn't jump to line 261, because the condition on line 263 was never false if item.parents == path[:-1]: 

return item 

 

def get_block(self, path): 

267 ↛ 268line 267 didn't jump to line 268, because the condition on line 267 was never true if not isinstance(path, list): 

raise AssertionError('path argument must be a list object') 

obj = self.get_object(path) 

if not obj: 

raise ValueError('path does not exist in config') 

return self._expand_block(obj) 

 

def get_block_config(self, path): 

block = self.get_block(path) 

return dumps(block, 'block') 

 

def _expand_block(self, configobj, S=None): 

if S is None: 

S = list() 

S.append(configobj) 

for child in configobj._children: 

283 ↛ 284line 283 didn't jump to line 284, because the condition on line 283 was never true if child in S: 

continue 

self._expand_block(child, S) 

return S 

 

def _diff_line(self, other): 

updates = list() 

for item in self.items: 

if item not in other: 

updates.append(item) 

return updates 

 

def _diff_strict(self, other): 

updates = list() 

for index, line in enumerate(self.items): 

try: 

299 ↛ 300line 299 didn't jump to line 300, because the condition on line 299 was never true if str(line).strip() != str(other[index]).strip(): 

updates.append(line) 

except (AttributeError, IndexError): 

updates.append(line) 

return updates 

 

def _diff_exact(self, other): 

updates = list() 

307 ↛ 310line 307 didn't jump to line 310, because the condition on line 307 was never false if len(other) != len(self.items): 

updates.extend(self.items) 

else: 

for ours, theirs in zip(self.items, other): 

if ours != theirs: 

updates.extend(self.items) 

break 

return updates 

 

def difference(self, other, match='line', path=None, replace=None): 

"""Perform a config diff against the another network config 

 

:param other: instance of NetworkConfig to diff against 

:param match: type of diff to perform. valid values are 'line', 

'strict', 'exact' 

:param path: context in the network config to filter the diff 

:param replace: the method used to generate the replacement lines. 

valid values are 'block', 'line' 

 

:returns: a string of lines that are different 

""" 

if path and match != 'line': 

try: 

other = other.get_block(path) 

except ValueError: 

other = list() 

else: 

other = other.items 

 

# generate a list of ConfigLines that aren't in other 

meth = getattr(self, '_diff_%s' % match) 

updates = meth(other) 

 

if replace == 'block': 

parents = list() 

for item in updates: 

if not item.has_parents: 

parents.append(item) 

else: 

for p in item._parents: 

347 ↛ 348line 347 didn't jump to line 348, because the condition on line 347 was never true if p not in parents: 

parents.append(p) 

 

updates = list() 

for item in parents: 

updates.extend(self._expand_block(item)) 

 

visited = set() 

expanded = list() 

 

for item in updates: 

for p in item._parents: 

if p.line not in visited: 

visited.add(p.line) 

expanded.append(p) 

expanded.append(item) 

visited.add(item.line) 

 

return expanded 

 

def add(self, lines, parents=None): 

ancestors = list() 

offset = 0 

obj = None 

 

# global config command 

if not parents: 

for line in lines: 

item = ConfigLine(line) 

item.raw = line 

377 ↛ 374line 377 didn't jump to line 374, because the condition on line 377 was never false if item not in self.items: 

self.items.append(item) 

 

else: 

for index, p in enumerate(parents): 

try: 

i = index + 1 

obj = self.get_block(parents[:i])[0] 

ancestors.append(obj) 

 

except ValueError: 

# add parent to config 

offset = index * self._indent 

obj = ConfigLine(p) 

obj.raw = p.rjust(len(p) + offset) 

392 ↛ 393line 392 didn't jump to line 393, because the condition on line 392 was never true if ancestors: 

obj._parents = list(ancestors) 

ancestors[-1]._children.append(obj) 

self.items.append(obj) 

ancestors.append(obj) 

 

# add child objects 

for line in lines: 

# check if child already exists 

for child in ancestors[-1]._children: 

402 ↛ 403line 402 didn't jump to line 403, because the condition on line 402 was never true if child.text == line: 

break 

else: 

offset = len(parents) * self._indent 

item = ConfigLine(line) 

item.raw = line.rjust(len(line) + offset) 

item._parents = ancestors 

ancestors[-1]._children.append(item) 

self.items.append(item) 

 

 

class CustomNetworkConfig(NetworkConfig): 

 

def items_text(self): 

return [item.text for item in self.items] 

 

def expand_section(self, configobj, S=None): 

if S is None: 

S = list() 

S.append(configobj) 

for child in configobj.child_objs: 

423 ↛ 424line 423 didn't jump to line 424, because the condition on line 423 was never true if child in S: 

continue 

self.expand_section(child, S) 

return S 

 

def to_block(self, section): 

return '\n'.join([item.raw for item in section]) 

 

def get_section(self, path): 

try: 

section = self.get_section_objects(path) 

return self.to_block(section) 

except ValueError: 

return list() 

 

def get_section_objects(self, path): 

if not isinstance(path, list): 

path = [path] 

obj = self.get_object(path) 

if not obj: 

raise ValueError('path does not exist in config') 

return self.expand_section(obj)