saltext.alpine 1.0.0__py2.py3-none-any.whl

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.
@@ -0,0 +1,28 @@
1
+ # pylint: disable=missing-module-docstring
2
+ import pathlib
3
+
4
+ PACKAGE_ROOT = pathlib.Path(__file__).resolve().parent
5
+ try:
6
+ from .version import __version__
7
+ except ImportError: # pragma: no cover
8
+ __version__ = "0.0.0.not-installed"
9
+ try:
10
+ from importlib.metadata import version, PackageNotFoundError
11
+
12
+ try:
13
+ __version__ = version(__name__)
14
+ except PackageNotFoundError:
15
+ # package is not installed
16
+ pass
17
+ except ImportError:
18
+ try:
19
+ from pkg_resources import get_distribution, DistributionNotFound
20
+
21
+ try:
22
+ __version__ = get_distribution(__name__).version
23
+ except DistributionNotFound:
24
+ # package is not installed
25
+ pass
26
+ except ImportError:
27
+ # pkg resources isn't even available?!
28
+ pass
File without changes
@@ -0,0 +1,652 @@
1
+ """
2
+ Package management for Alpine Linux via ``apk``.
3
+
4
+ This module registers under Salt's ``pkg`` virtual name and is loaded
5
+ automatically on Alpine minions. Existing states such as ``pkg.installed``
6
+ and ``pkg.removed`` work without any changes to your state files.
7
+
8
+ .. important::
9
+ If Salt is not using this module on an Alpine minion (or you see an error
10
+ such as *'pkg.install' is not available*), see :ref:`here
11
+ <module-provider-override>`.
12
+
13
+ Version strings
14
+ ---------------
15
+
16
+ Alpine packages follow the ``version-revision`` convention, for example
17
+ ``3.3.2-r0``. This is what :py:func:`list_pkgs`, :py:func:`version`, and
18
+ related functions return.
19
+
20
+ Hyphenated package names such as ``py3-requests`` and ``ca-certificates``
21
+ are handled correctly throughout. The module identifies the boundary between
22
+ name and version by locating the first dash-separated component that begins
23
+ with a digit.
24
+
25
+ Known limitations
26
+ -----------------
27
+
28
+ - Installing a specific package version is not yet supported. The version
29
+ component of any ``pkgs`` dict entry is accepted but silently ignored.
30
+ - Package hold and pin operations are not yet implemented.
31
+ - Repository management (adding, removing, or listing ``/etc/apk/repositories``
32
+ entries) is not yet implemented.
33
+ """
34
+
35
+ import copy
36
+ import logging
37
+
38
+ import salt.utils.data
39
+ import salt.utils.itertools
40
+ from salt.exceptions import CommandExecutionError
41
+
42
+ log = logging.getLogger(__name__)
43
+
44
+ # Define the module's virtual name
45
+ __virtualname__ = "pkg"
46
+
47
+
48
+ def __virtual__():
49
+ """
50
+ Confirm this module is running on an Alpine Linux distribution
51
+ """
52
+ if __grains__.get("os_family", False) == "Alpine":
53
+ return __virtualname__
54
+ return (False, "Module apk only works on Alpine Linux based systems")
55
+
56
+
57
+ # def autoremove(list_only=False, purge=False):
58
+ # return 'Not available'
59
+ # def hold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613
60
+ # return 'Not available'
61
+ # def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613
62
+ # return 'Not available'
63
+ # def upgrade_available(name):
64
+ # return 'Not available'
65
+ # def version_cmp(pkg1, pkg2, ignore_epoch=False):
66
+ # return 'Not available'
67
+ # def list_repos():
68
+ # return 'Not available'
69
+ # def get_repo(repo, **kwargs):
70
+ # return 'Not available'
71
+ # def del_repo(repo, **kwargs):
72
+ # return 'Not available'
73
+ # def del_repo_key(name=None, **kwargs):
74
+ # return 'Not available'
75
+ # def mod_repo(repo, saltenv='base', **kwargs):
76
+ # return 'Not available'
77
+ # def expand_repo_def(**kwargs):
78
+ # return 'Not available'
79
+ # def get_selections(pattern=None, state=None):
80
+ # return 'Not available'
81
+ # def set_selections(path=None, selection=None, clear=False, saltenv='base'):
82
+ # return 'Not available'
83
+ # def info_installed(*names):
84
+ # return 'Not available'
85
+
86
+
87
+ def version(*names, **kwargs):
88
+ """
89
+ Returns a string representing the package version or an empty string if not
90
+ installed. If more than one package name is specified, a dict of
91
+ name/version pairs is returned.
92
+
93
+ CLI Example:
94
+
95
+ .. code-block:: bash
96
+
97
+ salt '*' pkg.version <package name>
98
+ salt '*' pkg.version <package1> <package2> <package3> ...
99
+ """
100
+ return __salt__["pkg_resource.version"](*names, **kwargs)
101
+
102
+
103
+ def refresh_db(**kwargs):
104
+ """
105
+ Updates the package list
106
+
107
+ - ``True``: Database updated successfully
108
+ - ``False``: Problem updating database
109
+
110
+ CLI Example:
111
+
112
+ .. code-block:: bash
113
+
114
+ salt '*' pkg.refresh_db
115
+ """
116
+ ret = {}
117
+ cmd = ["apk", "update"]
118
+ call = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
119
+ if call["retcode"] == 0:
120
+ errors = []
121
+ ret = True
122
+ else:
123
+ errors = [call["stdout"]]
124
+ ret = False
125
+
126
+ if errors:
127
+ raise CommandExecutionError(
128
+ "Problem encountered updating the package database",
129
+ info={"errors": errors, "changes": ret},
130
+ )
131
+
132
+ return ret
133
+
134
+
135
+ def _list_pkgs_from_context(versions_as_list):
136
+ """
137
+ Use pkg list from __context__
138
+ """
139
+ if versions_as_list:
140
+ return __context__["pkg.list_pkgs"]
141
+ ret = copy.deepcopy(__context__["pkg.list_pkgs"])
142
+ __salt__["pkg_resource.stringify"](ret)
143
+ return ret
144
+
145
+
146
+ def list_pkgs(versions_as_list=False, **kwargs):
147
+ """
148
+ List the packages currently installed in a dict::
149
+
150
+ {'<package_name>': '<version>'}
151
+
152
+ CLI Example:
153
+
154
+ .. code-block:: bash
155
+
156
+ salt '*' pkg.list_pkgs
157
+ salt '*' pkg.list_pkgs versions_as_list=True
158
+ """
159
+ versions_as_list = salt.utils.data.is_true(versions_as_list)
160
+ # not yet implemented or not applicable
161
+ if any(salt.utils.data.is_true(kwargs.get(x)) for x in ("removed", "purge_desired")):
162
+ return {}
163
+
164
+ if "pkg.list_pkgs" in __context__ and kwargs.get("use_context", True):
165
+ return _list_pkgs_from_context(versions_as_list)
166
+
167
+ cmd = ["apk", "info", "-v"]
168
+ ret = {}
169
+ out = __salt__["cmd.run"](cmd, output_loglevel="trace", python_shell=False)
170
+ for line in salt.utils.itertools.split(out, "\n"):
171
+ pkg_version = "-".join(line.split("-")[-2:])
172
+ pkg_name = "-".join(line.split("-")[:-2])
173
+ __salt__["pkg_resource.add_pkg"](ret, pkg_name, pkg_version)
174
+
175
+ __salt__["pkg_resource.sort_pkglist"](ret)
176
+ __context__["pkg.list_pkgs"] = copy.deepcopy(ret)
177
+ if not versions_as_list:
178
+ __salt__["pkg_resource.stringify"](ret)
179
+ return ret
180
+
181
+
182
+ def latest_version(*names, **kwargs):
183
+ """
184
+ Return the latest version of the named package available for upgrade or
185
+ installation. If more than one package name is specified, a dict of
186
+ name/version pairs is returned.
187
+
188
+ If the latest version of a given package is already installed, an empty
189
+ string will be returned for that package.
190
+
191
+ CLI Example:
192
+
193
+ .. code-block:: bash
194
+
195
+ salt '*' pkg.latest_version <package name>
196
+ salt '*' pkg.latest_version <package name>
197
+ salt '*' pkg.latest_version <package1> <package2> <package3> ...
198
+ """
199
+ refresh = salt.utils.data.is_true(kwargs.pop("refresh", True))
200
+
201
+ if not names:
202
+ return ""
203
+
204
+ ret = {}
205
+ for name in names:
206
+ ret[name] = ""
207
+ pkgs = list_pkgs()
208
+
209
+ # Refresh before looking for the latest version available
210
+ if refresh:
211
+ refresh_db()
212
+
213
+ # Upgrade check
214
+ cmd = ["apk", "upgrade", "-s"]
215
+ out = __salt__["cmd.run_stdout"](cmd, output_loglevel="trace", python_shell=False)
216
+ for line in salt.utils.itertools.split(out, "\n"):
217
+ try:
218
+ name = line.split(" ")[2]
219
+ _oldversion = line.split(" ")[3].strip("(")
220
+ newversion = line.split(" ")[5].strip(")")
221
+ if name in names:
222
+ ret[name] = newversion
223
+ except (ValueError, IndexError):
224
+ pass
225
+
226
+ # If version is empty, the package may not be installed; search for it
227
+ for pkg, current_version in ret.items():
228
+ if not current_version:
229
+ installed = pkgs.get(pkg)
230
+ cmd = ["apk", "search", pkg]
231
+ out = __salt__["cmd.run_stdout"](cmd, output_loglevel="trace", python_shell=False)
232
+ for line in salt.utils.itertools.split(out, "\n"):
233
+ try:
234
+ pkg_version = "-".join(line.split("-")[-2:])
235
+ pkg_name = "-".join(line.split("-")[:-2])
236
+ if pkg == pkg_name:
237
+ if installed == pkg_version:
238
+ ret[pkg] = ""
239
+ else:
240
+ ret[pkg] = pkg_version
241
+ except ValueError:
242
+ pass
243
+
244
+ # Return a string if only one package name passed
245
+ if len(names) == 1:
246
+ return ret[names[0]]
247
+ return ret
248
+
249
+
250
+ # TODO: Support specific version installation
251
+ def install(name=None, refresh=False, pkgs=None, sources=None, **kwargs):
252
+ """
253
+ Install the passed package, add refresh=True to update the apk database.
254
+
255
+ name
256
+ The name of the package to be installed. Note that this parameter is
257
+ ignored if either "pkgs" or "sources" is passed. Additionally, please
258
+ note that this option can only be used to install packages from a
259
+ software repository. To install a package file manually, use the
260
+ "sources" option.
261
+
262
+ CLI Example:
263
+
264
+ .. code-block:: bash
265
+
266
+ salt '*' pkg.install <package name>
267
+
268
+ refresh
269
+ Whether or not to refresh the package database before installing.
270
+
271
+
272
+ Multiple Package Installation Options:
273
+
274
+ pkgs
275
+ A list of packages to install from a software repository. Must be
276
+ passed as a python list.
277
+
278
+ CLI Example:
279
+
280
+ .. code-block:: bash
281
+
282
+ salt '*' pkg.install pkgs='["foo", "bar"]'
283
+
284
+ sources
285
+ A list of .apk packages to install. Must be passed as a list of dicts,
286
+ with the keys being package names, and the values being the source URI
287
+ or local path to the package. Dependencies are automatically resolved
288
+ and marked as auto-installed.
289
+
290
+ CLI Example:
291
+
292
+ .. code-block:: bash
293
+
294
+ salt '*' pkg.install sources='[{"foo": "salt://foo.apk"},{"bar": "salt://bar.apk"}]'
295
+
296
+ Returns a dict containing the new package names and versions::
297
+
298
+ {'<package>': {'old': '<old-version>',
299
+ 'new': '<new-version>'}}
300
+ """
301
+ refreshdb = salt.utils.data.is_true(refresh)
302
+ pkg_to_install = []
303
+
304
+ old = list_pkgs()
305
+
306
+ if name and not (pkgs or sources):
307
+ if "," in name:
308
+ pkg_to_install = name.split(",")
309
+ else:
310
+ pkg_to_install = [name]
311
+
312
+ if pkgs:
313
+ # Specific version installation is not yet supported, so extract
314
+ # just the package name from any dict entries in the list.
315
+ pkgs = [next(iter(p)) for p in pkgs if isinstance(p, dict)]
316
+ pkg_to_install.extend(pkgs)
317
+
318
+ if not pkg_to_install:
319
+ return {}
320
+
321
+ if refreshdb:
322
+ refresh_db()
323
+
324
+ cmd = ["apk", "add"]
325
+
326
+ # Add the upgrade flag if any requested package is already installed,
327
+ # since apk will otherwise treat the install as a no-op for those packages.
328
+ for _pkg in pkg_to_install:
329
+ if old.get(_pkg):
330
+ cmd.append("-u")
331
+ break
332
+
333
+ cmd.extend(pkg_to_install)
334
+
335
+ out = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
336
+
337
+ if out["retcode"] != 0 and out["stderr"]:
338
+ errors = [out["stderr"]]
339
+ else:
340
+ errors = []
341
+
342
+ __context__.pop("pkg.list_pkgs", None)
343
+ new = list_pkgs()
344
+ ret = salt.utils.data.compare_dicts(old, new)
345
+
346
+ if errors:
347
+ raise CommandExecutionError(
348
+ "Problem encountered installing package(s)",
349
+ info={"errors": errors, "changes": ret},
350
+ )
351
+
352
+ return ret
353
+
354
+
355
+ def purge(name=None, pkgs=None, **kwargs):
356
+ """
357
+ Remove packages and their configuration files using ``apk del --purge``.
358
+
359
+ name
360
+ The name of the package to be purged.
361
+
362
+ pkgs
363
+ A list of packages to purge. Must be passed as a python list. The
364
+ ``name`` parameter will be ignored if this option is passed.
365
+
366
+ Returns a dict containing the changes.
367
+
368
+ CLI Example:
369
+
370
+ .. code-block:: bash
371
+
372
+ salt '*' pkg.purge <package name>
373
+ salt '*' pkg.purge pkgs='["foo", "bar"]'
374
+ """
375
+ return remove(name=name, pkgs=pkgs, purge=True)
376
+
377
+
378
+ def remove(name=None, pkgs=None, purge=False, **kwargs): # pylint: disable=unused-argument
379
+ """
380
+ Remove packages using ``apk del``.
381
+
382
+ name
383
+ The name of the package to be deleted.
384
+
385
+
386
+ Multiple Package Options:
387
+
388
+ pkgs
389
+ A list of packages to delete. Must be passed as a python list. The
390
+ ``name`` parameter will be ignored if this option is passed.
391
+
392
+ Returns a dict containing the changes.
393
+
394
+ CLI Example:
395
+
396
+ .. code-block:: bash
397
+
398
+ salt '*' pkg.remove <package name>
399
+ salt '*' pkg.remove <package1>,<package2>,<package3>
400
+ salt '*' pkg.remove pkgs='["foo", "bar"]'
401
+ """
402
+ old = list_pkgs()
403
+ pkg_to_remove = []
404
+
405
+ if name:
406
+ if "," in name:
407
+ pkg_to_remove = name.split(",")
408
+ else:
409
+ pkg_to_remove = [name]
410
+
411
+ if pkgs:
412
+ pkg_to_remove.extend(pkgs)
413
+
414
+ if not pkg_to_remove:
415
+ return {}
416
+
417
+ if purge:
418
+ cmd = ["apk", "del", "--purge"]
419
+ else:
420
+ cmd = ["apk", "del"]
421
+
422
+ cmd.extend(pkg_to_remove)
423
+
424
+ out = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
425
+ if out["retcode"] != 0 and out["stderr"]:
426
+ errors = [out["stderr"]]
427
+ else:
428
+ errors = []
429
+
430
+ __context__.pop("pkg.list_pkgs", None)
431
+ new = list_pkgs()
432
+ ret = salt.utils.data.compare_dicts(old, new)
433
+
434
+ if errors:
435
+ raise CommandExecutionError(
436
+ "Problem encountered removing package(s)",
437
+ info={"errors": errors, "changes": ret},
438
+ )
439
+
440
+ return ret
441
+
442
+
443
+ def upgrade(name=None, pkgs=None, refresh=True, **kwargs):
444
+ """
445
+ Upgrades all packages via ``apk upgrade`` or a specific package if name or
446
+ pkgs is specified. Name is ignored if pkgs is specified
447
+
448
+ Returns a dict containing the changes.
449
+
450
+ {'<package>': {'old': '<old-version>',
451
+ 'new': '<new-version>'}}
452
+
453
+ CLI Example:
454
+
455
+ .. code-block:: bash
456
+
457
+ salt '*' pkg.upgrade
458
+ """
459
+ ret = {
460
+ "changes": {},
461
+ "result": True,
462
+ "comment": "",
463
+ }
464
+
465
+ if salt.utils.data.is_true(refresh):
466
+ refresh_db()
467
+
468
+ old = list_pkgs()
469
+
470
+ pkg_to_upgrade = []
471
+
472
+ if name and not pkgs:
473
+ if "," in name:
474
+ pkg_to_upgrade = name.split(",")
475
+ else:
476
+ pkg_to_upgrade = [name]
477
+
478
+ if pkgs:
479
+ pkg_to_upgrade.extend(pkgs)
480
+
481
+ if pkg_to_upgrade:
482
+ cmd = ["apk", "add", "-u"]
483
+ cmd.extend(pkg_to_upgrade)
484
+ else:
485
+ cmd = ["apk", "upgrade"]
486
+
487
+ call = __salt__["cmd.run_all"](
488
+ cmd, output_loglevel="trace", python_shell=False, redirect_stderr=True
489
+ )
490
+
491
+ if call["retcode"] != 0:
492
+ ret["result"] = False
493
+ if call["stdout"]:
494
+ ret["comment"] = call["stdout"]
495
+
496
+ __context__.pop("pkg.list_pkgs", None)
497
+ new = list_pkgs()
498
+ ret["changes"] = salt.utils.data.compare_dicts(old, new)
499
+
500
+ return ret
501
+
502
+
503
+ def list_upgrades(refresh=True, **kwargs):
504
+ """
505
+ List all available package upgrades.
506
+
507
+ CLI Example:
508
+
509
+ .. code-block:: bash
510
+
511
+ salt '*' pkg.list_upgrades
512
+ """
513
+ ret = {}
514
+ if salt.utils.data.is_true(refresh):
515
+ refresh_db()
516
+
517
+ cmd = ["apk", "upgrade", "-s"]
518
+ call = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
519
+
520
+ if call["retcode"] != 0:
521
+ comment = ""
522
+ if "stderr" in call:
523
+ comment += call["stderr"]
524
+ if "stdout" in call:
525
+ comment += call["stdout"]
526
+ raise CommandExecutionError(comment)
527
+ out = call["stdout"]
528
+
529
+ for line in out.splitlines():
530
+ if "Upgrading" in line:
531
+ name = line.split(" ")[2]
532
+ _oldversion = line.split(" ")[3].strip("(")
533
+ newversion = line.split(" ")[5].strip(")")
534
+ ret[name] = newversion
535
+
536
+ return ret
537
+
538
+
539
+ def file_list(*packages, **kwargs):
540
+ """
541
+ List the files that belong to a package as a flat list. Not specifying
542
+ any packages will return a list of every file on the system's package
543
+ database (not generally recommended).
544
+
545
+ CLI Examples:
546
+
547
+ .. code-block:: bash
548
+
549
+ salt '*' pkg.file_list httpd
550
+ salt '*' pkg.file_list httpd postfix
551
+ salt '*' pkg.file_list
552
+ """
553
+ if not packages:
554
+ return "Package name should be provided"
555
+ result = file_dict(*packages)
556
+ files = []
557
+ for pkg_files in result.get("packages", {}).values():
558
+ files.extend(pkg_files)
559
+ return files
560
+
561
+
562
+ def file_dict(*packages, **kwargs):
563
+ """
564
+ List the files that belong to a package, grouped by package. Not
565
+ specifying any packages will return a list of every file on the system's
566
+ package database (not generally recommended).
567
+
568
+ CLI Examples:
569
+
570
+ .. code-block:: bash
571
+
572
+ salt '*' pkg.file_dict httpd
573
+ salt '*' pkg.file_dict httpd postfix
574
+ salt '*' pkg.file_dict
575
+ """
576
+ errors = []
577
+ ret = {}
578
+ cmd_files = ["apk", "info", "-L"]
579
+
580
+ if not packages:
581
+ return "Package name should be provided"
582
+
583
+ for package in packages:
584
+ files = []
585
+ cmd = cmd_files[:]
586
+ cmd.append(package)
587
+ out = __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
588
+ for line in out["stdout"].splitlines():
589
+ if line.endswith("contains:"):
590
+ continue
591
+ files.append(line)
592
+ if files:
593
+ ret[package] = files
594
+
595
+ return {"errors": errors, "packages": ret}
596
+
597
+
598
+ def _pkg_name_from_apk_string(pkg_string):
599
+ """
600
+ Extract the package name from a string in apk's name-version-revision
601
+ format (e.g. 'openssl-3.3.2-r0' -> 'openssl',
602
+ 'py3-requests-2.28.0-r0' -> 'py3-requests').
603
+
604
+ Alpine package names never start a component with a digit, so the name
605
+ ends at the first dash-separated component that begins with a digit.
606
+ """
607
+ parts = pkg_string.split("-")
608
+ name_parts = []
609
+ for part in parts:
610
+ if part and part[0].isdigit():
611
+ break
612
+ name_parts.append(part)
613
+ return "-".join(name_parts) if name_parts else pkg_string
614
+
615
+
616
+ def owner(*paths, **kwargs):
617
+ """
618
+ Return the name of the package that owns the file. Multiple file paths can
619
+ be passed. Like :mod:`pkg.version <salt.modules.apk.version`, if a single
620
+ path is passed, a string will be returned, and if multiple paths are passed,
621
+ a dictionary of file/package name pairs will be returned.
622
+
623
+ If the file is not owned by a package, or is not present on the minion,
624
+ then an empty string will be returned for that path.
625
+
626
+ CLI Example:
627
+
628
+ .. code-block:: bash
629
+
630
+ salt '*' pkg.owner /usr/bin/apachectl
631
+ salt '*' pkg.owner /usr/bin/apachectl /usr/bin/basename
632
+ """
633
+ if not paths:
634
+ return "You must provide a path"
635
+
636
+ ret = {}
637
+ cmd_search = ["apk", "info", "-W"]
638
+ for path in paths:
639
+ cmd = cmd_search[:]
640
+ cmd.append(path)
641
+ output = __salt__["cmd.run_stdout"](cmd, output_loglevel="trace", python_shell=False)
642
+ if output:
643
+ if "ERROR:" in output:
644
+ ret[path] = "Could not find owner package"
645
+ else:
646
+ # apk outputs "<path> is owned by <name>-<version>-<revision>"
647
+ pkg_full = output.split("by ")[1].strip()
648
+ ret[path] = _pkg_name_from_apk_string(pkg_full)
649
+ else:
650
+ ret[path] = f"Error running {cmd}"
651
+
652
+ return ret