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

#!/usr/bin/python 

# -*- coding: utf-8 -*- 

 

# Copyright: (c) 2018, 2019 Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net> 

# 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 

 

ANSIBLE_METADATA = { 

'metadata_version': '1.1', 

'status': ['preview'], 

'supported_by': 'community' 

} 

 

DOCUMENTATION = r''' 

--- 

module: meraki_network 

short_description: Manage networks in the Meraki cloud 

version_added: "2.6" 

description: 

- Allows for creation, management, and visibility into networks within Meraki. 

 

options: 

state: 

description: 

- Create or modify an organization. 

choices: [ absent, present, query ] 

default: present 

type: str 

net_name: 

description: 

- Name of a network. 

aliases: [ name, network ] 

type: str 

net_id: 

description: 

- ID number of a network. 

type: str 

type: 

description: 

- Type of network device network manages. 

- Required when creating a network. 

- As of Ansible 2.8, C(combined) type is no longer accepted. 

- As of Ansible 2.8, changes to this parameter are no longer idempotent. 

choices: [ appliance, switch, wireless ] 

aliases: [ net_type ] 

type: list 

tags: 

type: list 

description: 

- List of tags to assign to network. 

- C(tags) name conflicts with the tags parameter in Ansible. Indentation problems may cause unexpected behaviors. 

- Ansible 2.8 converts this to a list from a comma separated list. 

timezone: 

description: 

- Timezone associated to network. 

- See U(https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for a list of valid timezones. 

type: str 

enable_vlans: 

description: 

- Boolean value specifying whether VLANs should be supported on a network. 

- Requires C(net_name) or C(net_id) to be specified. 

type: bool 

version_added: '2.9' 

disable_my_meraki: 

description: > 

- Disables the local device status pages (U[my.meraki.com](my.meraki.com), U[ap.meraki.com](ap.meraki.com), U[switch.meraki.com](switch.meraki.com), 

U[wired.meraki.com](wired.meraki.com)). 

- Mutually exclusive of C(enable_my_meraki). 

- Will be deprecated in Ansible 2.13 in favor of C(enable_my_meraki). 

type: bool 

version_added: '2.7' 

enable_my_meraki: 

description: > 

- Enables the local device status pages (U[my.meraki.com](my.meraki.com), U[ap.meraki.com](ap.meraki.com), U[switch.meraki.com](switch.meraki.com), 

U[wired.meraki.com](wired.meraki.com)). 

- Ansible 2.7 had this parameter as C(disable_my_meraki). 

type: bool 

version_added: '2.9' 

enable_remote_status_page: 

description: 

- Enables access to the device status page (U(http://device LAN IP)). 

- Can only be set if C(enable_my_meraki:) is set to C(yes). 

type: bool 

version_added: '2.9' 

 

author: 

- Kevin Breit (@kbreit) 

extends_documentation_fragment: meraki 

''' 

 

EXAMPLES = r''' 

- delegate_to: localhost 

block: 

- name: List all networks associated to the YourOrg organization 

meraki_network: 

auth_key: abc12345 

state: query 

org_name: YourOrg 

- name: Query network named MyNet in the YourOrg organization 

meraki_network: 

auth_key: abc12345 

state: query 

org_name: YourOrg 

net_name: MyNet 

- name: Create network named MyNet in the YourOrg organization 

meraki_network: 

auth_key: abc12345 

state: present 

org_name: YourOrg 

net_name: MyNet 

type: switch 

timezone: America/Chicago 

tags: production, chicago 

- name: Create combined network named MyNet in the YourOrg organization 

meraki_network: 

auth_key: abc12345 

state: present 

org_name: YourOrg 

net_name: MyNet 

type: 

- switch 

- appliance 

timezone: America/Chicago 

tags: production, chicago 

- name: Enable VLANs on a network 

meraki_network: 

auth_key: abc12345 

state: query 

org_name: YourOrg 

net_name: MyNet 

enable_vlans: yes 

''' 

 

RETURN = r''' 

data: 

description: Information about the created or manipulated object. 

returned: info 

type: complex 

contains: 

id: 

description: Identification string of network. 

returned: success 

type: str 

sample: N_12345 

name: 

description: Written name of network. 

returned: success 

type: str 

sample: YourNet 

organization_id: 

description: Organization ID which owns the network. 

returned: success 

type: str 

sample: 0987654321 

tags: 

description: Space delimited tags assigned to network. 

returned: success 

type: str 

sample: " production wireless " 

time_zone: 

description: Timezone where network resides. 

returned: success 

type: str 

sample: America/Chicago 

type: 

description: Functional type of network. 

returned: success 

type: str 

sample: switch 

disable_my_meraki_com: 

description: States whether U(my.meraki.com) and other device portals should be disabled. 

returned: success 

type: bool 

sample: true 

disableRemoteStatusPage: 

description: Disables access to the device status page. 

returned: success 

type: bool 

sample: true 

''' 

 

from ansible.module_utils.basic import AnsibleModule, json 

from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec 

 

 

def is_net_valid(data, net_name=None, net_id=None): 

189 ↛ 190line 189 didn't jump to line 190, because the condition on line 189 was never true if net_name is None and net_id is None: 

return False 

191 ↛ 198line 191 didn't jump to line 198, because the loop on line 191 didn't complete for n in data: 

192 ↛ 195line 192 didn't jump to line 195, because the condition on line 192 was never false if net_name: 

if n['name'] == net_name: 

return True 

elif net_id: 

if n['id'] == net_id: 

return True 

return False 

 

 

def construct_tags(tags): 

formatted_tags = ' '.join(tags) 

return ' {0} '.format(formatted_tags) # Meraki needs space padding 

 

 

def list_to_string(data): 

new_string = str() 

for i, item in enumerate(data): 

209 ↛ 210line 209 didn't jump to line 210, because the condition on line 209 was never true if i == len(new_string) - 1: 

new_string += i 

else: 

new_string = "{0}{1} ".format(new_string, item) 

return new_string.strip() 

 

 

def main(): 

 

# define the available arguments/parameters that a user can pass to 

# the module 

 

argument_spec = meraki_argument_spec() 

argument_spec.update( 

net_id=dict(type='str'), 

type=dict(type='list', choices=['wireless', 'switch', 'appliance'], aliases=['net_type']), 

tags=dict(type='list'), 

timezone=dict(type='str'), 

net_name=dict(type='str', aliases=['name', 'network']), 

state=dict(type='str', choices=['present', 'query', 'absent'], default='present'), 

enable_vlans=dict(type='bool'), 

disable_my_meraki=dict(type='bool', removed_in_version=2.13), 

enable_my_meraki=dict(type='bool'), 

enable_remote_status_page=dict(type='bool'), 

) 

 

# the AnsibleModule object will be our abstraction working with Ansible 

# this includes instantiation, a couple of common attr would be the 

# args/params passed to the execution, as well as if the module 

# supports check mode 

module = AnsibleModule(argument_spec=argument_spec, 

supports_check_mode=True, 

mutually_exclusive=[('disable_my_meraki', 'enable_my_meraki'), 

] 

) 

 

meraki = MerakiModule(module, function='network') 

module.params['follow_redirects'] = 'all' 

payload = None 

 

create_urls = {'network': '/organizations/{org_id}/networks'} 

update_urls = {'network': '/networks/{net_id}'} 

delete_urls = {'network': '/networks/{net_id}'} 

enable_vlans_urls = {'network': '/networks/{net_id}/vlansEnabledState'} 

get_vlan_status_urls = {'network': '/networks/{net_id}/vlansEnabledState'} 

meraki.url_catalog['create'] = create_urls 

meraki.url_catalog['update'] = update_urls 

meraki.url_catalog['delete'] = delete_urls 

meraki.url_catalog['enable_vlans'] = enable_vlans_urls 

meraki.url_catalog['status_vlans'] = get_vlan_status_urls 

 

260 ↛ 261line 260 didn't jump to line 261, because the condition on line 260 was never true if not meraki.params['org_name'] and not meraki.params['org_id']: 

meraki.fail_json(msg='org_name or org_id parameters are required') 

262 ↛ 265line 262 didn't jump to line 265, because the condition on line 262 was never false if meraki.params['state'] != 'query': 

263 ↛ 264line 263 didn't jump to line 264, because the condition on line 263 was never true if not meraki.params['net_name'] and not meraki.params['net_id']: 

meraki.fail_json(msg='net_name or net_id is required for present or absent states') 

265 ↛ 266line 265 didn't jump to line 266, because the condition on line 265 was never true if meraki.params['net_name'] and meraki.params['net_id']: 

meraki.fail_json(msg='net_name and net_id are mutually exclusive') 

267 ↛ 268line 267 didn't jump to line 268, because the condition on line 267 was never true if not meraki.params['net_name'] and not meraki.params['net_id']: 

if meraki.params['enable_vlans']: 

meraki.fail_json(msg="The parameter 'enable_vlans' requires 'net_name' or 'net_id' to be specified") 

270 ↛ 271line 270 didn't jump to line 271, because the condition on line 270 was never true if meraki.params['enable_my_meraki'] is True and meraki.params['enable_remote_status_page'] is False: 

meraki.fail_json(msg='enable_my_meraki must be true when setting enable_remote_status_page') 

 

# Construct payload 

274 ↛ 301line 274 didn't jump to line 301, because the condition on line 274 was never false if meraki.params['state'] == 'present': 

payload = dict() 

276 ↛ 278line 276 didn't jump to line 278, because the condition on line 276 was never false if meraki.params['net_name']: 

payload['name'] = meraki.params['net_name'] 

278 ↛ 280line 278 didn't jump to line 280, because the condition on line 278 was never false if meraki.params['type']: 

payload['type'] = list_to_string(meraki.params['type']) 

280 ↛ 281line 280 didn't jump to line 281, because the condition on line 280 was never true if meraki.params['tags']: 

payload['tags'] = construct_tags(meraki.params['tags']) 

282 ↛ 283line 282 didn't jump to line 283, because the condition on line 282 was never true if meraki.params['timezone']: 

payload['timeZone'] = meraki.params['timezone'] 

284 ↛ 285line 284 didn't jump to line 285, because the condition on line 284 was never true if meraki.params['enable_my_meraki'] is not None: 

if meraki.params['enable_my_meraki'] is True: 

payload['disableMyMerakiCom'] = False 

else: 

payload['disableMyMerakiCom'] = True 

289 ↛ 290line 289 didn't jump to line 290, because the condition on line 289 was never true elif meraki.params['disable_my_meraki'] is not None: 

payload['disableMyMerakiCom'] = meraki.params['disable_my_meraki'] 

291 ↛ 292line 291 didn't jump to line 292, because the condition on line 291 was never true if meraki.params['enable_remote_status_page'] is not None: 

if meraki.params['enable_remote_status_page'] is True: 

payload['disableRemoteStatusPage'] = False 

# meraki.fail_json(msg="Debug", payload=payload) 

else: 

payload['disableRemoteStatusPage'] = True 

 

# manipulate or modify the state as needed (this is going to be the 

# part where your module will do what it needs to do) 

 

org_id = meraki.params['org_id'] 

302 ↛ 304line 302 didn't jump to line 304, because the condition on line 302 was never false if not org_id: 

org_id = meraki.get_org_id(meraki.params['org_name']) 

nets = meraki.get_nets(org_id=org_id) 

 

# check if network is created 

net_id = meraki.params['net_id'] 

net_exists = False 

309 ↛ 310line 309 didn't jump to line 310, because the condition on line 309 was never true if net_id is not None: 

if is_net_valid(nets, net_id=net_id) is False: 

meraki.fail_json(msg="Network specified by net_id does not exist.") 

net_exists = True 

313 ↛ 318line 313 didn't jump to line 318, because the condition on line 313 was never false elif meraki.params['net_name']: 

314 ↛ 318line 314 didn't jump to line 318, because the condition on line 314 was never false if is_net_valid(nets, net_name=meraki.params['net_name']) is True: 

net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets) 

net_exists = True 

 

318 ↛ 319line 318 didn't jump to line 319, because the condition on line 318 was never true if meraki.params['state'] == 'query': 

if not meraki.params['net_name'] and not meraki.params['net_id']: 

meraki.result['data'] = nets 

elif meraki.params['net_name'] or meraki.params['net_id'] is not None: 

meraki.result['data'] = meraki.get_net(meraki.params['org_name'], 

meraki.params['net_name'], 

data=nets 

) 

326 ↛ 389line 326 didn't jump to line 389, because the condition on line 326 was never false elif meraki.params['state'] == 'present': 

327 ↛ 328line 327 didn't jump to line 328, because the condition on line 327 was never true if net_exists is False: # Network needs to be created 

if 'type' not in meraki.params or meraki.params['type'] is None: 

meraki.fail_json(msg="type parameter is required when creating a network.") 

if meraki.check_mode is True: 

data = payload 

data['id'] = 'N_12345' 

data['organization_id'] = org_id 

meraki.result['data'] = data 

meraki.result['changed'] = True 

meraki.exit_json(**meraki.result) 

path = meraki.construct_path('create', 

org_id=org_id 

) 

r = meraki.request(path, 

method='POST', 

payload=json.dumps(payload) 

) 

if meraki.status == 201: 

meraki.result['data'] = r 

meraki.result['changed'] = True 

else: # Network exists, make changes 

348 ↛ 349line 348 didn't jump to line 349, because the condition on line 348 was never true if meraki.params['enable_vlans'] is not None: # Modify VLANs configuration 

status_path = meraki.construct_path('status_vlans', net_id=net_id) 

status = meraki.request(status_path, method='GET') 

payload = {'enabled': meraki.params['enable_vlans']} 

if meraki.is_update_required(status, payload): 

if meraki.check_mode is True: 

data = {'enabled': meraki.params['enable_vlans'], 

'network_id': net_id, 

} 

meraki.result['data'] = data 

meraki.result['changed'] = True 

meraki.exit_json(**meraki.result) 

path = meraki.construct_path('enable_vlans', net_id=net_id) 

r = meraki.request(path, 

method='PUT', 

payload=json.dumps(payload)) 

if meraki.status == 200: 

meraki.result['data'] = r 

meraki.result['changed'] = True 

meraki.exit_json(**meraki.result) 

else: 

meraki.result['data'] = status 

meraki.exit_json(**meraki.result) 

net = meraki.get_net(meraki.params['org_name'], net_id=net_id, data=nets) 

372 ↛ 373line 372 didn't jump to line 373, because the condition on line 372 was never true if meraki.is_update_required(net, payload): 

if meraki.check_mode is True: 

data = net 

net.update(payload) 

meraki.result['data'] = net 

meraki.result['changed'] = True 

meraki.exit_json(**meraki.result) 

path = meraki.construct_path('update', net_id=net_id) 

# meraki.fail_json(msg="Payload", path=path, payload=payload) 

r = meraki.request(path, 

method='PUT', 

payload=json.dumps(payload)) 

if meraki.status == 200: 

meraki.result['data'] = r 

meraki.result['changed'] = True 

else: 

meraki.result['data'] = net 

elif meraki.params['state'] == 'absent': 

if is_net_valid(nets, net_id=net_id) is True: 

if meraki.check_mode is True: 

meraki.result['data'] = {} 

meraki.result['changed'] = True 

meraki.exit_json(**meraki.result) 

path = meraki.construct_path('delete', net_id=net_id) 

r = meraki.request(path, method='DELETE') 

if meraki.status == 204: 

meraki.result['changed'] = True 

 

# in the event of a successful module execution, you will want to 

# simple AnsibleModule.exit_json(), passing the key/value results 

meraki.exit_json(**meraki.result) 

 

 

405 ↛ exitline 405 didn't exit the module, because the condition on line 405 was never falseif __name__ == '__main__': 

main()