gnetcli-adapter 2.5.2__tar.gz → 2.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gnetcli_adapter
3
- Version: 2.5.2
3
+ Version: 2.6.0
4
4
  Summary: Gnetcli-server adapter for Annet
5
5
  Author-email: Aleksandr Balezin <gescheit12@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  License-File: LICENSE
15
15
  Requires-Dist: exceptiongroup>=1.1.3; python_version<'3.11'
16
16
  Requires-Dist: annet>=3.2.0
17
- Requires-Dist: gnetclisdk>=1.0.90
17
+ Requires-Dist: gnetclisdk>=1.1.0
18
18
  Requires-Dist: pydantic_settings
19
19
  Requires-Dist: pydantic
20
20
  Requires-Dist: bandit[toml]==1.7.5 ; extra == "test"
@@ -42,3 +42,46 @@ Provides-Extra: test
42
42
  # gnetcli_adapter
43
43
  This package provides deployer and fetcher adapters for Annet
44
44
 
45
+ # Examples
46
+
47
+ ## Using specified login and password
48
+
49
+ cat ~/.annet/context.yml
50
+ ```yaml
51
+ fetcher:
52
+ default:
53
+ adapter: gnetcli
54
+ params: &gnetcli
55
+ dev_login: mylogin
56
+ dev_password: mypassword
57
+ deployer:
58
+ default:
59
+ adapter: gnetcli
60
+ params:
61
+ <<: *gnetcli
62
+ ...
63
+ context:
64
+ default:
65
+ fetcher: default
66
+ deployer: default
67
+ selected_context: default
68
+ ```
69
+
70
+ ## Using tunnel through master SSH-connection
71
+
72
+ https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing
73
+
74
+ cat ~/.ssh/context.yml
75
+ ```
76
+ Host myhost*
77
+ ProxyJump mybastion
78
+
79
+ Host mybastion
80
+ ControlMaster auto
81
+ ControlPath ~/.ssh/mastersockets/%r@%h:%p
82
+ ControlPersist 120m
83
+ ```
84
+
85
+ `~/.annet/context.yml` the same because gnetcli read .ssh/config by default.
86
+
87
+
@@ -0,0 +1,45 @@
1
+ # gnetcli_adapter
2
+ This package provides deployer and fetcher adapters for Annet
3
+
4
+ # Examples
5
+
6
+ ## Using specified login and password
7
+
8
+ cat ~/.annet/context.yml
9
+ ```yaml
10
+ fetcher:
11
+ default:
12
+ adapter: gnetcli
13
+ params: &gnetcli
14
+ dev_login: mylogin
15
+ dev_password: mypassword
16
+ deployer:
17
+ default:
18
+ adapter: gnetcli
19
+ params:
20
+ <<: *gnetcli
21
+ ...
22
+ context:
23
+ default:
24
+ fetcher: default
25
+ deployer: default
26
+ selected_context: default
27
+ ```
28
+
29
+ ## Using tunnel through master SSH-connection
30
+
31
+ https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing
32
+
33
+ cat ~/.ssh/context.yml
34
+ ```
35
+ Host myhost*
36
+ ProxyJump mybastion
37
+
38
+ Host mybastion
39
+ ControlMaster auto
40
+ ControlPath ~/.ssh/mastersockets/%r@%h:%p
41
+ ControlPersist 120m
42
+ ```
43
+
44
+ `~/.annet/context.yml` the same because gnetcli read .ssh/config by default.
45
+
@@ -7,7 +7,7 @@ name = "gnetcli_adapter"
7
7
  dependencies = [
8
8
  "exceptiongroup>=1.1.3; python_version<'3.11'",
9
9
  "annet>=3.2.0",
10
- "gnetclisdk>=1.0.90",
10
+ "gnetclisdk>=1.1.0",
11
11
  "pydantic_settings",
12
12
  "pydantic"
13
13
  ]
@@ -1,9 +1,11 @@
1
1
  import traceback
2
2
 
3
3
  import asyncio
4
- import json
5
- import subprocess
6
- import time
4
+ import base64
5
+ from collections.abc import AsyncIterator, Iterable
6
+ from contextlib import asynccontextmanager
7
+ import logging
8
+ from typing import Any, Dict, List, Optional, Tuple
7
9
 
8
10
  import annet.annlib.command
9
11
 
@@ -12,26 +14,21 @@ from annet.annlib.command import Command, CommandList
12
14
  from annet.annlib.netdev.views.hardware import HardwareView
13
15
  from annet.adapters.netbox.common.models import NetboxDevice
14
16
  from annet.rulebook import common
15
-
16
17
  from annet.connectors import AdapterWithConfig, AdapterWithName
17
- from typing import Dict, List, Any, Optional, Tuple
18
18
  from annet.storage import Device
19
-
20
- from gnetcli_adapter.progress_tracker import (
21
- ProgressTracker, ProgressBarTracker, FileProgressTracker, LogProgressTracker, CompositeTracker,
22
- )
23
- from gnetclisdk.client import Credentials, Gnetcli, HostParams, QA, File, GnetcliSessionCmd
19
+ from gnetclisdk.config import Config
20
+ from gnetclisdk.starter import DEFAULT_GNETCLI_SERVER_CONF, GnetcliStarter
21
+ from gnetclisdk.client import Credentials, Gnetcli, HostParams, QA, File
24
22
  from gnetclisdk.exceptions import EOFError
25
23
  import gnetclisdk.proto.server_pb2 as pb
26
24
  from pydantic import Field, field_validator, FieldValidationInfo
27
25
  from pydantic_core import PydanticUndefined
28
26
  from pydantic_settings import BaseSettings, SettingsConfigDict
29
- import base64
30
- import logging
31
- import threading
32
- import atexit
33
- import shutil
34
- import os.path
27
+
28
+ from gnetcli_adapter.progress_tracker import (
29
+ ProgressTracker, ProgressBarTracker, FileProgressTracker, LogProgressTracker, CompositeTracker,
30
+ )
31
+
35
32
  try:
36
33
  from builtins import ( # type: ignore[attr-defined, unused-ignore]
37
34
  ExceptionGroup,
@@ -55,23 +52,9 @@ breed_to_device = {
55
52
  "vrp55": "huawei",
56
53
  }
57
54
 
58
- DEFAULT_GNETCLI_SERVER_CONF = """
59
- logging:
60
- level: debug
61
- json: true
62
- port: 0
63
- dev_auth:
64
- use_agent: true
65
- ssh_config: true
66
-
67
- """
68
-
69
- _local_gnetcli: Optional[threading.Thread] = None
70
- _local_gnetcli_p: Optional[subprocess.Popen] = None
71
- _local_gnetcli_url: Optional[str] = None
72
- LOG_FORMAT = "%(asctime)s - l:%(lineno)d - %(funcName)s() - %(levelname)s - %(message)s"
73
- DATE_FMT = "%Y-%m-%d %H:%M:%S"
55
+
74
56
  DEFAULT_GNETCLI_SERVER_PATH = "gnetcli_server"
57
+
75
58
  _logger = logging.getLogger(__name__)
76
59
 
77
60
 
@@ -80,7 +63,7 @@ class AppSettings(BaseSettings):
80
63
 
81
64
  url: Optional[str] = None
82
65
  server_path: str = DEFAULT_GNETCLI_SERVER_PATH
83
- server_conf: str = DEFAULT_GNETCLI_SERVER_CONF
66
+ server_conf: Config = DEFAULT_GNETCLI_SERVER_CONF
84
67
  insecure_grpc: bool = Field(default=True)
85
68
  login: Optional[str] = None
86
69
  password: Optional[str] = None
@@ -128,29 +111,6 @@ async def get_config(breed: str) -> List[str]:
128
111
  raise Exception("unknown breed %r" % breed)
129
112
 
130
113
 
131
- def check_gnetcli_server(server_path: str, config: str = DEFAULT_GNETCLI_SERVER_CONF):
132
- global _local_gnetcli
133
- if not _local_gnetcli:
134
- t = threading.Thread(target=run_gnetcli_server,
135
- kwargs={"server_path": server_path, "config": config})
136
- t.daemon = True
137
- t.start()
138
- time.sleep(1)
139
- _local_gnetcli = t
140
- if _local_gnetcli_p is None:
141
- raise Exception("server failed")
142
-
143
-
144
- def cleanup():
145
- if _local_gnetcli_p is not None:
146
- _local_gnetcli_p.terminate()
147
- time.sleep(0.05)
148
- _local_gnetcli_p.kill()
149
- time.sleep(0.05)
150
-
151
- atexit.register(cleanup)
152
-
153
-
154
114
  async def gather_with_concurrency(n: int, *coros: list[asyncio.Task]):
155
115
  if n == 0:
156
116
  return await asyncio.gather(*coros, return_exceptions=True)
@@ -183,52 +143,6 @@ def get_device_ip(dev: Device) -> Optional[str]:
183
143
  return None
184
144
 
185
145
 
186
- def run_gnetcli_server(server_path: str, config: str = DEFAULT_GNETCLI_SERVER_CONF):
187
- global _local_gnetcli_p
188
- global _local_gnetcli_url
189
- abs_path: Optional[str] = shutil.which(server_path)
190
- if not abs_path and server_path == DEFAULT_GNETCLI_SERVER_PATH:
191
- abs_path = shutil.which(os.path.expanduser("~/go/bin/gnetcli_server"))
192
- gnetcli_abs_path: str = abs_path or server_path
193
- _logger.info("starting gnetcli server %s", gnetcli_abs_path)
194
- try:
195
- proc = subprocess.Popen(
196
- [gnetcli_abs_path, "--conf-file", "-"],
197
- stdout=subprocess.DEVNULL, # we do not read stdout
198
- stderr=subprocess.PIPE,
199
- stdin=subprocess.PIPE,
200
- bufsize=1,
201
- universal_newlines=True,
202
- )
203
- except Exception as e:
204
- logging.exception("server exec error %s", e)
205
- raise
206
- proc.stdin.write(config)
207
- proc.stdin.close()
208
- _local_gnetcli_p = proc
209
- while True:
210
- output = proc.stderr.readline()
211
- if output == "" and proc.poll() is not None:
212
- break
213
- if output:
214
- _logger.debug("gnetcli output: %s", output.strip())
215
- if _local_gnetcli_url is None:
216
- try:
217
- data = json.loads(output)
218
- except Exception:
219
- pass
220
- else:
221
- if data.get("msg") == "init tcp socket":
222
- _local_gnetcli_url = data.get("address")
223
- if data.get("msg") == "init unix socket":
224
- _local_gnetcli_url = "unix:" + data.get("path")
225
- if data.get("level") == "panic":
226
- _logger.error("gnetcli error %s", data)
227
- _logger.debug("set gnetcli server exit code %s", proc.returncode)
228
- rc = proc.poll()
229
- return rc
230
-
231
-
232
146
  class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
233
147
  def __init__(
234
148
  self,
@@ -239,7 +153,7 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
239
153
  dev_password: Optional[str] = None,
240
154
  ssh_agent_enabled: bool = True,
241
155
  server_path: Optional[str] = None,
242
- server_conf: str = DEFAULT_GNETCLI_SERVER_CONF,
156
+ server_conf: Config = DEFAULT_GNETCLI_SERVER_CONF,
243
157
  ):
244
158
  conf_args = {
245
159
  "login": login,
@@ -252,7 +166,6 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
252
166
  "ssh_agent_enabled": ssh_agent_enabled,
253
167
  }
254
168
  self.conf = AppSettings(**{k: v for k,v in conf_args.items() if v is not None})
255
- self.api = make_api(self.conf)
256
169
 
257
170
  @classmethod
258
171
  def name(cls) -> str:
@@ -268,11 +181,25 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
268
181
  # TODO: implement fetch_packages
269
182
  return {}, {}
270
183
 
271
- async def fetch(self,
272
- devices: List[Device],
273
- files_to_download: Optional[Dict[Device, List[str]]] = None,
274
- processes: int = 1,
275
- max_slots: int = 0,
184
+ async def fetch(
185
+ self,
186
+ devices: List[Device],
187
+ files_to_download: Optional[Dict[Device, List[str]]] = None,
188
+ processes: int = 1,
189
+ max_slots: int = 0,
190
+ ):
191
+ if not devices:
192
+ return {}, {}
193
+ async with make_api(self.conf) as api:
194
+ return await self._fetch(api, devices, files_to_download, processes, max_slots)
195
+
196
+ async def _fetch(
197
+ self,
198
+ api: Gnetcli,
199
+ devices: List[Device],
200
+ files_to_download: Optional[Dict[Device, List[str]]] = None,
201
+ processes: int = 1,
202
+ max_slots: int = 0,
276
203
  ):
277
204
  running = {}
278
205
  failed_running = {}
@@ -282,12 +209,12 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
282
209
  if files_to_download or device.is_pc():
283
210
  if files_to_download:
284
211
  files = files_to_download.get(device, [])
285
- task = self.adownload_dev(device=device, files=files)
212
+ task = self.adownload_dev(api=api, device=device, files=files)
286
213
  tasks[task] = device
287
214
  else:
288
215
  running[device] = {}
289
216
  else:
290
- task = self.afetch_dev(device=device)
217
+ task = self.afetch_dev(api=api, device=device)
291
218
  tasks[task] = device
292
219
 
293
220
  if not tasks:
@@ -306,14 +233,14 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
306
233
  running[device] = dev_res
307
234
  return running, failed_running
308
235
 
309
- async def afetch_dev(self, device: Device) -> str:
236
+ async def afetch_dev(self, api: Gnetcli, device: Device) -> str:
310
237
  cmds = await get_config(breed=device.breed)
311
238
  # map annet breed to gnetcli device type
312
239
  gnetcli_device = breed_to_device.get(device.breed, device.breed)
313
240
  dev_result = []
314
241
  ip = get_device_ip(device)
315
242
  for cmd in cmds:
316
- res = await self.api.cmd(
243
+ res = await api.cmd(
317
244
  hostname=device.fqdn,
318
245
  cmd=cmd,
319
246
  host_params=HostParams(
@@ -327,10 +254,10 @@ class GnetcliFetcher(Fetcher, AdapterWithConfig, AdapterWithName):
327
254
  dev_result.append(res.out)
328
255
  return b"\n".join(dev_result).decode()
329
256
 
330
- async def adownload_dev(self, device: Device, files: List[str]) -> Dict[str, str]:
257
+ async def adownload_dev(self, api: Gnetcli, device: Device, files: List[str]) -> Dict[str, str]:
331
258
  gnetcli_device = breed_to_device.get(device.breed, device.breed)
332
259
  ip = get_device_ip(device)
333
- downloaded = await self.api.download(
260
+ downloaded = await api.download(
334
261
  hostname=device.fqdn,
335
262
  paths=files,
336
263
  host_params=HostParams(
@@ -355,27 +282,15 @@ def parse_annet_qa(qa: list[annet.annlib.command.Question]) -> list[QA]:
355
282
  return res
356
283
 
357
284
 
358
- def make_api(conf: AppSettings) -> Gnetcli:
359
- gnetcli_url = _local_gnetcli_url
360
- if not conf.url:
361
- check_gnetcli_server(server_path=conf.server_path, config=conf.server_conf)
362
- if _local_gnetcli_url is None:
363
- _logger.info("waiting for _local_gnetcli_url appears")
364
- start = time.monotonic()
365
- while time.monotonic() - start < 5:
366
- if _local_gnetcli_p is not None and _local_gnetcli_p.returncode is not None:
367
- raise Exception("gnetcli server died with code %s" % _local_gnetcli_p.returncode)
368
- gnetcli_url = _local_gnetcli_url
369
- else:
370
- gnetcli_url = conf.url
371
- auth_token = conf.make_server_credentials()
372
- api = Gnetcli(
373
- server=gnetcli_url,
374
- auth_token=auth_token,
375
- insecure_grpc=conf.insecure_grpc,
376
- user_agent="annet",
377
- )
378
- return api
285
+ @asynccontextmanager
286
+ async def make_api(conf: AppSettings) -> AsyncIterator[Gnetcli]:
287
+ async with GnetcliStarter(conf.server_path, conf.server_conf) as gnetcli_url:
288
+ yield Gnetcli(
289
+ server=gnetcli_url,
290
+ auth_token= conf.make_server_credentials(),
291
+ insecure_grpc=conf.insecure_grpc,
292
+ user_agent="annet",
293
+ )
379
294
 
380
295
 
381
296
  class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
@@ -388,7 +303,7 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
388
303
  dev_password: Optional[str] = None,
389
304
  ssh_agent_enabled: bool = True,
390
305
  server_path: Optional[str] = None,
391
- server_conf: Optional[str] = DEFAULT_GNETCLI_SERVER_CONF,
306
+ server_conf: Optional[Config] = DEFAULT_GNETCLI_SERVER_CONF,
392
307
  logs_dir: Optional[str] = None,
393
308
  ):
394
309
  conf_args = {
@@ -402,7 +317,6 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
402
317
  "ssh_agent_enabled": ssh_agent_enabled,
403
318
  }
404
319
  self.conf = AppSettings(**{k: v for k,v in conf_args.items() if v is not None})
405
- self.api = make_api(self.conf)
406
320
  self.logs_dir = logs_dir
407
321
 
408
322
  @classmethod
@@ -413,7 +327,29 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
413
327
  def with_config(cls, **kwargs: Any) -> DeployDriver:
414
328
  return cls(**kwargs)
415
329
 
416
- async def bulk_deploy(self, deploy_cmds: dict[Device, CommandList], args: DeployOptions, progress_bar: ProgressBar | None = None) -> DeployResult:
330
+ async def bulk_deploy(
331
+ self,
332
+ deploy_cmds: dict[Device, CommandList],
333
+ args: DeployOptions,
334
+ progress_bar: ProgressBar | None = None,
335
+ ) -> DeployResult:
336
+ if not deploy_cmds:
337
+ return DeployResult(hostnames=[], results={}, durations={}, original_states={})
338
+ async with make_api(self.conf) as api:
339
+ return await self._bulk_deploy(
340
+ api=api,
341
+ deploy_cmds=deploy_cmds,
342
+ args=args,
343
+ progress_bar=progress_bar,
344
+ )
345
+
346
+ async def _bulk_deploy(
347
+ self,
348
+ api: Gnetcli,
349
+ deploy_cmds: dict[Device, CommandList],
350
+ args: DeployOptions,
351
+ progress_bar: ProgressBar | None = None,
352
+ ) -> DeployResult:
417
353
  if progress_bar:
418
354
  for host, cmds in deploy_cmds.items():
419
355
  progress_bar.set_progress(host.fqdn, 0, len(cmds))
@@ -427,11 +363,11 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
427
363
  pass
428
364
  deploy_items = deploy_cmds.items()
429
365
  if max_parallel == 1:
430
- result = await self.serial_deploy(deploy_items, args, progress_bar)
366
+ result = await self.serial_deploy(api, deploy_items, args, progress_bar)
431
367
  else:
432
368
  result = await gather_with_concurrency(
433
369
  max_parallel,
434
- *[self.deploy(device, cmds, args, progress_bar) for device, cmds in deploy_items],
370
+ *[self.deploy(api, device, cmds, args, progress_bar) for device, cmds in deploy_items],
435
371
  )
436
372
  res = DeployResult(hostnames=[], results={}, durations={}, original_states={})
437
373
  res.add_results(results={dev.fqdn: dev_res for (dev, _), dev_res in zip(deploy_items, result)})
@@ -452,7 +388,9 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
452
388
  return reload_cmds
453
389
 
454
390
  def _get_total(
455
- self, command_groups: list[tuple[str, CommandList]], files: dict[str, File],
391
+ self,
392
+ command_groups: list[tuple[str, CommandList]],
393
+ files: dict[str, File],
456
394
  ) -> int:
457
395
  run_cmds = 0
458
396
  for _, cmds in command_groups:
@@ -461,7 +399,11 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
461
399
  run_cmds += 1
462
400
  return run_cmds
463
401
 
464
- def _init_progress_tracker(self, device: Device, progress_bar: ProgressBar | None) -> ProgressTracker:
402
+ def _init_progress_tracker(
403
+ self,
404
+ device: Device,
405
+ progress_bar: ProgressBar | None,
406
+ ) -> ProgressTracker:
465
407
  tracker = CompositeTracker(LogProgressTracker(device))
466
408
  if progress_bar:
467
409
  tracker.add_tracker(ProgressBarTracker(device, progress_bar))
@@ -469,7 +411,13 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
469
411
  tracker.add_tracker(FileProgressTracker(device, self.logs_dir))
470
412
  return tracker
471
413
 
472
- async def serial_deploy(self, deploy_items, args, progress_bar: ProgressBar | None = None):
414
+ async def serial_deploy(
415
+ self,
416
+ api: Gnetcli,
417
+ deploy_items: Iterable[tuple[Device, CommandList]],
418
+ args: DeployOptions,
419
+ progress_bar: ProgressBar | None = None,
420
+ ):
473
421
  res = {}
474
422
  for device, cmds in deploy_items:
475
423
  if progress_bar:
@@ -481,16 +429,17 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
481
429
  break
482
430
  except Exception:
483
431
  pass
484
- dev_res = await self.deploy(device, cmds, args, progress_bar)
432
+ dev_res = await self.deploy(api, device, cmds, args, progress_bar)
485
433
  res[device] = dev_res
486
434
  return res
487
435
 
488
436
  async def deploy(
489
- self,
490
- device: Device,
491
- cmds: CommandList,
492
- args: DeployOptions,
493
- progress_bar: ProgressBar | None = None,
437
+ self,
438
+ api: Gnetcli,
439
+ device: Device,
440
+ cmds: CommandList,
441
+ args: DeployOptions,
442
+ progress_bar: ProgressBar | None = None,
494
443
  ) -> Exception | None:
495
444
  gnetcli_device = breed_to_device.get(device.breed, device.breed)
496
445
  ip = get_device_ip(device)
@@ -515,6 +464,7 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
515
464
  tracker.set_total(self._get_total(command_groups, files))
516
465
  try:
517
466
  seen_exc, results = await self._deploy(
467
+ api=api,
518
468
  device=device,
519
469
  host_params=host_params,
520
470
  command_groups=command_groups,
@@ -537,6 +487,7 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
537
487
 
538
488
  async def _deploy(
539
489
  self,
490
+ api: Gnetcli,
540
491
  device: Device,
541
492
  host_params: HostParams,
542
493
  command_groups: list[tuple[str, CommandList]],
@@ -547,11 +498,11 @@ class GnetcliDeployer(DeployDriver, AdapterWithConfig, AdapterWithName):
547
498
  results = []
548
499
  if files:
549
500
  tracker.upload_files(list(files))
550
- await self.api.upload(hostname=device.fqdn, files=files, host_params=host_params)
501
+ await api.upload(hostname=device.fqdn, files=files, host_params=host_params)
551
502
  tracker.files_uploaded()
552
503
 
553
504
  old_group_name = ""
554
- async with self.api.cmd_session(hostname=device.fqdn) as session:
505
+ async with api.cmd_session(hostname=device.fqdn) as session:
555
506
  for group_number, (group_name, cmdlist) in enumerate(command_groups):
556
507
  if group_name != old_group_name:
557
508
  tracker.start_group(group_name)
@@ -1,2 +0,0 @@
1
- # gnetcli_adapter
2
- This package provides deployer and fetcher adapters for Annet
File without changes