pyinfra 3.0b0__py2.py3-none-any.whl → 3.0b2__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.
Files changed (115) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +12 -5
  3. pyinfra/api/arguments_typed.py +19 -6
  4. pyinfra/api/command.py +5 -3
  5. pyinfra/api/config.py +115 -13
  6. pyinfra/api/connectors.py +5 -2
  7. pyinfra/api/exceptions.py +19 -0
  8. pyinfra/api/facts.py +34 -33
  9. pyinfra/api/host.py +51 -12
  10. pyinfra/api/inventory.py +4 -0
  11. pyinfra/api/operation.py +88 -42
  12. pyinfra/api/operations.py +10 -11
  13. pyinfra/api/state.py +11 -2
  14. pyinfra/api/util.py +24 -16
  15. pyinfra/connectors/base.py +4 -7
  16. pyinfra/connectors/chroot.py +5 -6
  17. pyinfra/connectors/docker.py +13 -19
  18. pyinfra/connectors/dockerssh.py +5 -4
  19. pyinfra/connectors/local.py +7 -7
  20. pyinfra/connectors/ssh.py +46 -25
  21. pyinfra/connectors/terraform.py +9 -6
  22. pyinfra/connectors/util.py +7 -8
  23. pyinfra/connectors/vagrant.py +11 -10
  24. pyinfra/context.py +1 -0
  25. pyinfra/facts/apk.py +2 -0
  26. pyinfra/facts/apt.py +2 -0
  27. pyinfra/facts/brew.py +2 -0
  28. pyinfra/facts/bsdinit.py +2 -0
  29. pyinfra/facts/cargo.py +2 -0
  30. pyinfra/facts/choco.py +3 -1
  31. pyinfra/facts/deb.py +9 -4
  32. pyinfra/facts/dnf.py +2 -0
  33. pyinfra/facts/docker.py +2 -0
  34. pyinfra/facts/files.py +2 -0
  35. pyinfra/facts/gem.py +2 -0
  36. pyinfra/facts/gpg.py +2 -0
  37. pyinfra/facts/hardware.py +30 -22
  38. pyinfra/facts/launchd.py +2 -0
  39. pyinfra/facts/lxd.py +2 -0
  40. pyinfra/facts/mysql.py +12 -6
  41. pyinfra/facts/npm.py +1 -0
  42. pyinfra/facts/openrc.py +2 -0
  43. pyinfra/facts/pacman.py +6 -2
  44. pyinfra/facts/pip.py +2 -0
  45. pyinfra/facts/pkg.py +2 -0
  46. pyinfra/facts/pkgin.py +2 -0
  47. pyinfra/facts/postgres.py +168 -0
  48. pyinfra/facts/postgresql.py +5 -162
  49. pyinfra/facts/rpm.py +12 -9
  50. pyinfra/facts/server.py +10 -13
  51. pyinfra/facts/snap.py +2 -0
  52. pyinfra/facts/systemd.py +28 -10
  53. pyinfra/facts/upstart.py +2 -0
  54. pyinfra/facts/util/packaging.py +3 -2
  55. pyinfra/facts/vzctl.py +2 -0
  56. pyinfra/facts/xbps.py +2 -0
  57. pyinfra/facts/yum.py +2 -0
  58. pyinfra/facts/zypper.py +2 -0
  59. pyinfra/operations/apk.py +3 -1
  60. pyinfra/operations/apt.py +16 -18
  61. pyinfra/operations/brew.py +10 -8
  62. pyinfra/operations/bsdinit.py +5 -3
  63. pyinfra/operations/cargo.py +3 -1
  64. pyinfra/operations/choco.py +3 -1
  65. pyinfra/operations/dnf.py +15 -19
  66. pyinfra/operations/files.py +86 -69
  67. pyinfra/operations/gem.py +3 -1
  68. pyinfra/operations/git.py +18 -16
  69. pyinfra/operations/iptables.py +33 -25
  70. pyinfra/operations/launchd.py +5 -6
  71. pyinfra/operations/lxd.py +7 -4
  72. pyinfra/operations/mysql.py +57 -53
  73. pyinfra/operations/npm.py +8 -1
  74. pyinfra/operations/openrc.py +5 -3
  75. pyinfra/operations/pacman.py +4 -5
  76. pyinfra/operations/pip.py +16 -9
  77. pyinfra/operations/pkg.py +3 -1
  78. pyinfra/operations/pkgin.py +3 -1
  79. pyinfra/operations/postgres.py +349 -0
  80. pyinfra/operations/postgresql.py +18 -335
  81. pyinfra/operations/puppet.py +3 -1
  82. pyinfra/operations/python.py +7 -3
  83. pyinfra/operations/selinux.py +42 -16
  84. pyinfra/operations/server.py +48 -43
  85. pyinfra/operations/snap.py +3 -1
  86. pyinfra/operations/ssh.py +12 -10
  87. pyinfra/operations/systemd.py +13 -9
  88. pyinfra/operations/sysvinit.py +6 -4
  89. pyinfra/operations/upstart.py +5 -3
  90. pyinfra/operations/util/files.py +24 -16
  91. pyinfra/operations/util/packaging.py +53 -37
  92. pyinfra/operations/util/service.py +18 -13
  93. pyinfra/operations/vzctl.py +12 -10
  94. pyinfra/operations/xbps.py +3 -1
  95. pyinfra/operations/yum.py +14 -18
  96. pyinfra/operations/zypper.py +8 -9
  97. pyinfra/version.py +5 -2
  98. {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/METADATA +31 -29
  99. pyinfra-3.0b2.dist-info/RECORD +163 -0
  100. {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/WHEEL +1 -1
  101. pyinfra_cli/commands.py +3 -2
  102. pyinfra_cli/inventory.py +38 -19
  103. pyinfra_cli/main.py +2 -0
  104. pyinfra_cli/prints.py +27 -105
  105. pyinfra_cli/util.py +3 -1
  106. tests/test_api/test_api_deploys.py +5 -5
  107. tests/test_api/test_api_operations.py +5 -5
  108. tests/test_connectors/test_ssh.py +105 -0
  109. tests/test_connectors/test_terraform.py +11 -8
  110. tests/test_connectors/test_vagrant.py +6 -6
  111. pyinfra-3.0b0.dist-info/RECORD +0 -162
  112. pyinfra_cli/inventory_dsl.py +0 -23
  113. {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/LICENSE.md +0 -0
  114. {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/entry_points.txt +0 -0
  115. {pyinfra-3.0b0.dist-info → pyinfra-3.0b2.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,8 @@ See the example/mysql.py
13
13
 
14
14
  """
15
15
 
16
+ from __future__ import annotations
17
+
16
18
  from pyinfra import host
17
19
  from pyinfra.api import MaskString, OperationError, QuoteString, StringCommand, operation
18
20
  from pyinfra.facts.mysql import (
@@ -26,13 +28,13 @@ from pyinfra.facts.mysql import (
26
28
 
27
29
  @operation(is_idempotent=False)
28
30
  def sql(
29
- sql,
30
- database=None,
31
+ sql: str,
32
+ database: str | None = None,
31
33
  # Details for speaking to MySQL via `mysql` CLI
32
- mysql_user=None,
33
- mysql_password=None,
34
- mysql_host=None,
35
- mysql_port=None,
34
+ mysql_user: str | None = None,
35
+ mysql_password: str | None = None,
36
+ mysql_host: str | None = None,
37
+ mysql_port: int | None = None,
36
38
  ):
37
39
  """
38
40
  Execute arbitrary SQL against MySQL.
@@ -54,27 +56,28 @@ def sql(
54
56
 
55
57
  @operation()
56
58
  def user(
57
- user,
58
- present=True,
59
- user_hostname="localhost",
60
- password=None,
61
- privileges=None,
59
+ user: str,
60
+ present: bool = True,
61
+ user_hostname: str = "localhost",
62
+ password: str | None = None,
63
+ privileges: str | list[str] | None = None,
62
64
  # MySQL REQUIRE SSL/TLS options
63
- require=None, # SSL or X509
64
- require_cipher=False,
65
- require_issuer=False,
66
- require_subject=False,
65
+ require: str | None = None, # SSL or X509
66
+ require_cipher: str | None = None,
67
+ require_issuer: str | None = None,
68
+ require_subject: str | None = None,
67
69
  # MySQL WITH resource limit options
68
- max_connections=None,
69
- max_queries_per_hour=None,
70
- max_updates_per_hour=None,
71
- max_connections_per_hour=None,
70
+ max_connections: int | None = None,
71
+ max_queries_per_hour: int | None = None,
72
+ max_updates_per_hour: int | None = None,
73
+ max_connections_per_hour: int | None = None,
72
74
  # Details for speaking to MySQL via `mysql` CLI via `mysql` CLI
73
- mysql_user=None,
74
- mysql_password=None,
75
- mysql_host=None,
76
- mysql_port=None,
75
+ mysql_user: str | None = None,
76
+ mysql_password: str | None = None,
77
+ mysql_host: str | None = None,
78
+ mysql_port: int | None = None,
77
79
  ):
80
+ ...
78
81
  """
79
82
  Add/remove/update MySQL users.
80
83
 
@@ -282,24 +285,25 @@ def user(
282
285
 
283
286
  @operation()
284
287
  def database(
285
- database,
288
+ database: str,
286
289
  # Desired database settings
287
- present=True,
288
- collate=None,
289
- charset=None,
290
- user=None,
291
- user_hostname="localhost",
292
- user_privileges="ALL",
290
+ present: bool = True,
291
+ collate: str | None = None,
292
+ charset: str | None = None,
293
+ user: str | None = None,
294
+ user_hostname: str = "localhost",
295
+ user_privileges: str | list[str] = "ALL",
293
296
  # Details for speaking to MySQL via `mysql` CLI
294
- mysql_user=None,
295
- mysql_password=None,
296
- mysql_host=None,
297
- mysql_port=None,
297
+ mysql_user: str | None = None,
298
+ mysql_password: str | None = None,
299
+ mysql_host: str | None = None,
300
+ mysql_port: int | None = None,
298
301
  ):
302
+ ...
299
303
  """
300
304
  Add/remove MySQL databases.
301
305
 
302
- + name: the name of the database
306
+ + database: the name of the database
303
307
  + present: whether the database should exist or not
304
308
  + collate: the collate to use when creating the database
305
309
  + charset: the charset to use when creating the database
@@ -384,18 +388,18 @@ def database(
384
388
 
385
389
  @operation()
386
390
  def privileges(
387
- user,
388
- privileges,
391
+ user: str,
392
+ privileges: str | list[str] | set[str],
389
393
  user_hostname="localhost",
390
394
  database="*",
391
395
  table="*",
392
396
  flush=True,
393
397
  with_grant_option=False,
394
398
  # Details for speaking to MySQL via `mysql` CLI
395
- mysql_user=None,
396
- mysql_password=None,
397
- mysql_host=None,
398
- mysql_port=None,
399
+ mysql_user: str | None = None,
400
+ mysql_password: str | None = None,
401
+ mysql_host: str | None = None,
402
+ mysql_port: int | None = None,
399
403
  ):
400
404
  """
401
405
  Add/remove MySQL privileges for a user, either global, database or table specific.
@@ -524,13 +528,13 @@ _privileges = privileges # noqa: E305 (for use where kwarg is the same)
524
528
 
525
529
  @operation(is_idempotent=False)
526
530
  def dump(
527
- dest,
528
- database=None,
531
+ dest: str,
532
+ database: str | None = None,
529
533
  # Details for speaking to MySQL via `mysql` CLI
530
- mysql_user=None,
531
- mysql_password=None,
532
- mysql_host=None,
533
- mysql_port=None,
534
+ mysql_user: str | None = None,
535
+ mysql_password: str | None = None,
536
+ mysql_host: str | None = None,
537
+ mysql_port: int | None = None,
534
538
  ):
535
539
  """
536
540
  Dump a MySQL database into a ``.sql`` file. Requires ``mysqldump``.
@@ -565,13 +569,13 @@ def dump(
565
569
 
566
570
  @operation(is_idempotent=False)
567
571
  def load(
568
- src,
569
- database=None,
572
+ src: str,
573
+ database: str | None = None,
570
574
  # Details for speaking to MySQL via `mysql` CLI
571
- mysql_user=None,
572
- mysql_password=None,
573
- mysql_host=None,
574
- mysql_port=None,
575
+ mysql_user: str | None = None,
576
+ mysql_password: str | None = None,
577
+ mysql_host: str | None = None,
578
+ mysql_port: int | None = None,
575
579
  ):
576
580
  """
577
581
  Load ``.sql`` file into a database.
pyinfra/operations/npm.py CHANGED
@@ -2,6 +2,8 @@
2
2
  Manage npm (aka node aka Node.js) packages.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.npm import NpmPackages
@@ -10,7 +12,12 @@ from .util.packaging import ensure_packages
10
12
 
11
13
 
12
14
  @operation()
13
- def packages(packages=None, present=True, latest=False, directory=None):
15
+ def packages(
16
+ packages: str | list[str] | None = None,
17
+ present=True,
18
+ latest=False,
19
+ directory: str | None = None,
20
+ ):
14
21
  """
15
22
  Install/remove/update npm packages.
16
23
 
@@ -2,6 +2,8 @@
2
2
  Manage OpenRC init services.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.openrc import OpenrcEnabled, OpenrcStatus
@@ -11,12 +13,12 @@ from .util.service import handle_service_control
11
13
 
12
14
  @operation()
13
15
  def service(
14
- service,
16
+ service: str,
15
17
  running=True,
16
18
  restarted=False,
17
19
  reloaded=False,
18
- command=None,
19
- enabled=None,
20
+ command: str | None = None,
21
+ enabled: bool | None = None,
20
22
  runlevel="default",
21
23
  ):
22
24
  """
@@ -2,6 +2,8 @@
2
2
  Manage pacman packages. (Arch Linux package manager)
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.pacman import PacmanPackages, PacmanUnpackGroup
@@ -35,7 +37,7 @@ _update = update._inner # noqa: E305
35
37
 
36
38
  @operation()
37
39
  def packages(
38
- packages=None,
40
+ packages: str | list[str] | None = None,
39
41
  present=True,
40
42
  update=False,
41
43
  upgrade=False,
@@ -75,8 +77,5 @@ def packages(
75
77
  present,
76
78
  install_command="pacman --noconfirm -S",
77
79
  uninstall_command="pacman --noconfirm -R",
78
- expand_package_fact=lambda package: host.get_fact(
79
- PacmanUnpackGroup,
80
- name=package,
81
- ),
80
+ expand_package_fact=lambda package: host.get_fact(PacmanUnpackGroup, package=package),
82
81
  )
pyinfra/operations/pip.py CHANGED
@@ -3,6 +3,8 @@ Manage pip (python) packages. Compatible globally or inside
3
3
  a virtualenv (virtual environment).
4
4
  """
5
5
 
6
+ from __future__ import annotations
7
+
6
8
  from pyinfra import host
7
9
  from pyinfra.api import operation
8
10
  from pyinfra.facts.files import File
@@ -14,8 +16,8 @@ from .util.packaging import ensure_packages
14
16
 
15
17
  @operation()
16
18
  def virtualenv(
17
- path,
18
- python=None,
19
+ path: str,
20
+ python: str | None = None,
19
21
  venv=False,
20
22
  site_packages=False,
21
23
  always_copy=False,
@@ -81,8 +83,8 @@ _virtualenv = virtualenv._inner # noqa
81
83
 
82
84
  @operation()
83
85
  def venv(
84
- path,
85
- python=None,
86
+ path: str,
87
+ python: str | None = None,
86
88
  site_packages=False,
87
89
  always_copy=False,
88
90
  present=True,
@@ -117,14 +119,14 @@ def venv(
117
119
 
118
120
  @operation()
119
121
  def packages(
120
- packages=None,
122
+ packages: str | list[str] | None = None,
121
123
  present=True,
122
124
  latest=False,
123
- requirements=None,
125
+ requirements: str | None = None,
124
126
  pip="pip",
125
- virtualenv=None,
126
- virtualenv_kwargs=None,
127
- extra_install_args=None,
127
+ virtualenv: str | None = None,
128
+ virtualenv_kwargs: dict | None = None,
129
+ extra_install_args: str | None = None,
128
130
  ):
129
131
  """
130
132
  Install/remove/update pip packages.
@@ -185,6 +187,11 @@ def packages(
185
187
  if packages:
186
188
  current_packages = host.get_fact(PipPackages, pip=pip)
187
189
 
190
+ # PEP-0426 states that Python packages should be compared using lowercase, so lowercase both
191
+ # the input packages and the fact packages before comparison.
192
+ packages = [pkg.lower() for pkg in packages]
193
+ current_packages = {pkg.lower(): versions for pkg, versions in current_packages.items()}
194
+
188
195
  yield from ensure_packages(
189
196
  host,
190
197
  packages,
pyinfra/operations/pkg.py CHANGED
@@ -2,6 +2,8 @@
2
2
  Manage BSD packages and repositories. Note that BSD package names are case-sensitive.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.files import File
@@ -12,7 +14,7 @@ from .util.packaging import ensure_packages
12
14
 
13
15
 
14
16
  @operation()
15
- def packages(packages=None, present=True, pkg_path=None):
17
+ def packages(packages: str | list[str] | None = None, present=True, pkg_path: str | None = None):
16
18
  """
17
19
  Install/remove/update pkg packages. This will use ``pkg ...`` where available
18
20
  (FreeBSD) and the ``pkg_*`` variants elsewhere.
@@ -2,6 +2,8 @@
2
2
  Manage pkgin packages.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.pkgin import PkginPackages
@@ -35,7 +37,7 @@ _update = update._inner # noqa: E305
35
37
 
36
38
  @operation()
37
39
  def packages(
38
- packages=None,
40
+ packages: str | list[str] | None = None,
39
41
  present=True,
40
42
  latest=False,
41
43
  update=False,
@@ -0,0 +1,349 @@
1
+ """
2
+ The PostgreSQL modules manage PostgreSQL databases, users and privileges.
3
+
4
+ Requires the ``psql`` CLI executable on the target host(s).
5
+
6
+ All operations in this module take four optional arguments:
7
+ + ``psql_user``: the username to connect to postgresql to
8
+ + ``psql_password``: the password for the connecting user
9
+ + ``psql_host``: the hostname of the server to connect to
10
+ + ``psql_port``: the port of the server to connect to
11
+
12
+ See example/postgresql.py for detailed example
13
+
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from pyinfra import host
19
+ from pyinfra.api import MaskString, StringCommand, operation
20
+ from pyinfra.facts.postgres import (
21
+ PostgresDatabases,
22
+ PostgresRoles,
23
+ make_execute_psql_command,
24
+ make_psql_command,
25
+ )
26
+
27
+
28
+ @operation(is_idempotent=False)
29
+ def sql(
30
+ sql: str,
31
+ database: str | None = None,
32
+ # Details for speaking to PostgreSQL via `psql` CLI
33
+ psql_user: str | None = None,
34
+ psql_password: str | None = None,
35
+ psql_host: str | None = None,
36
+ psql_port: int | None = None,
37
+ ):
38
+ """
39
+ Execute arbitrary SQL against PostgreSQL.
40
+
41
+ + sql: SQL command(s) to execute
42
+ + database: optional database to execute against
43
+ + psql_*: global module arguments, see above
44
+ """
45
+
46
+ yield make_execute_psql_command(
47
+ sql,
48
+ database=database,
49
+ user=psql_user,
50
+ password=psql_password,
51
+ host=psql_host,
52
+ port=psql_port,
53
+ )
54
+
55
+
56
+ @operation()
57
+ def role(
58
+ role: str,
59
+ present=True,
60
+ password: str | None = None,
61
+ login=True,
62
+ superuser=False,
63
+ inherit=False,
64
+ createdb=False,
65
+ createrole=False,
66
+ replication=False,
67
+ connection_limit: int | None = None,
68
+ # Details for speaking to PostgreSQL via `psql` CLI
69
+ psql_user: str | None = None,
70
+ psql_password: str | None = None,
71
+ psql_host: str | None = None,
72
+ psql_port: int | None = None,
73
+ ):
74
+ """
75
+ Add/remove PostgreSQL roles.
76
+
77
+ + role: name of the role
78
+ + present: whether the role should be present or absent
79
+ + password: the password for the role
80
+ + login: whether the role can login
81
+ + superuser: whether role will be a superuser
82
+ + inherit: whether the role inherits from other roles
83
+ + createdb: whether the role is allowed to create databases
84
+ + createrole: whether the role is allowed to create new roles
85
+ + replication: whether this role is allowed to replicate
86
+ + connection_limit: the connection limit for the role
87
+ + psql_*: global module arguments, see above
88
+
89
+ Updates:
90
+ pyinfra will not attempt to change existing roles - it will either
91
+ create or drop roles, but not alter them (if the role exists this
92
+ operation will make no changes).
93
+
94
+ **Example:**
95
+
96
+ .. code:: python
97
+
98
+ postgresql.role(
99
+ name="Create the pyinfra PostgreSQL role",
100
+ role="pyinfra",
101
+ password="somepassword",
102
+ superuser=True,
103
+ login=True,
104
+ sudo_user="postgres",
105
+ )
106
+
107
+ """
108
+
109
+ roles = host.get_fact(
110
+ PostgresRoles,
111
+ psql_user=psql_user,
112
+ psql_password=psql_password,
113
+ psql_host=psql_host,
114
+ psql_port=psql_port,
115
+ )
116
+
117
+ is_present = role in roles
118
+
119
+ # User not wanted?
120
+ if not present:
121
+ if is_present:
122
+ yield make_execute_psql_command(
123
+ 'DROP ROLE "{0}"'.format(role),
124
+ user=psql_user,
125
+ password=psql_password,
126
+ host=psql_host,
127
+ port=psql_port,
128
+ )
129
+ else:
130
+ host.noop("postgresql role {0} does not exist".format(role))
131
+ return
132
+
133
+ # If we want the user and they don't exist
134
+ if not is_present:
135
+ sql_bits = ['CREATE ROLE "{0}"'.format(role)]
136
+
137
+ for key, value in (
138
+ ("LOGIN", login),
139
+ ("SUPERUSER", superuser),
140
+ ("INHERIT", inherit),
141
+ ("CREATEDB", createdb),
142
+ ("CREATEROLE", createrole),
143
+ ("REPLICATION", replication),
144
+ ):
145
+ if value:
146
+ sql_bits.append(key)
147
+
148
+ if connection_limit:
149
+ sql_bits.append("CONNECTION LIMIT {0}".format(connection_limit))
150
+
151
+ if password:
152
+ sql_bits.append(MaskString("PASSWORD '{0}'".format(password)))
153
+
154
+ yield make_execute_psql_command(
155
+ StringCommand(*sql_bits),
156
+ user=psql_user,
157
+ password=psql_password,
158
+ host=psql_host,
159
+ port=psql_port,
160
+ )
161
+ else:
162
+ host.noop("postgresql role {0} exists".format(role))
163
+
164
+
165
+ @operation()
166
+ def database(
167
+ database: str,
168
+ present=True,
169
+ owner: str | None = None,
170
+ template: str | None = None,
171
+ encoding: str | None = None,
172
+ lc_collate: str | None = None,
173
+ lc_ctype: str | None = None,
174
+ tablespace: str | None = None,
175
+ connection_limit: int | None = None,
176
+ # Details for speaking to PostgreSQL via `psql` CLI
177
+ psql_user: str | None = None,
178
+ psql_password: str | None = None,
179
+ psql_host: str | None = None,
180
+ psql_port: int | None = None,
181
+ ):
182
+ """
183
+ Add/remove PostgreSQL databases.
184
+
185
+ + name: name of the database
186
+ + present: whether the database should exist or not
187
+ + owner: the PostgreSQL role that owns the database
188
+ + template: name of the PostgreSQL template to use
189
+ + encoding: encoding of the database
190
+ + lc_collate: lc_collate of the database
191
+ + lc_ctype: lc_ctype of the database
192
+ + tablespace: the tablespace to use for the template
193
+ + connection_limit: the connection limit to apply to the database
194
+ + psql_*: global module arguments, see above
195
+
196
+ Updates:
197
+ pyinfra will not attempt to change existing databases - it will either
198
+ create or drop databases, but not alter them (if the db exists this
199
+ operation will make no changes).
200
+
201
+ **Example:**
202
+
203
+ .. code:: python
204
+
205
+ postgresql.database(
206
+ name="Create the pyinfra_stuff database",
207
+ database="pyinfra_stuff",
208
+ owner="pyinfra",
209
+ encoding="UTF8",
210
+ sudo_user="postgres",
211
+ )
212
+
213
+ """
214
+
215
+ current_databases = host.get_fact(
216
+ PostgresDatabases,
217
+ psql_user=psql_user,
218
+ psql_password=psql_password,
219
+ psql_host=psql_host,
220
+ psql_port=psql_port,
221
+ )
222
+
223
+ is_present = database in current_databases
224
+
225
+ if not present:
226
+ if is_present:
227
+ yield make_execute_psql_command(
228
+ 'DROP DATABASE "{0}"'.format(database),
229
+ user=psql_user,
230
+ password=psql_password,
231
+ host=psql_host,
232
+ port=psql_port,
233
+ )
234
+ else:
235
+ host.noop("postgresql database {0} does not exist".format(database))
236
+ return
237
+
238
+ # We want the database but it doesn't exist
239
+ if present and not is_present:
240
+ sql_bits = ['CREATE DATABASE "{0}"'.format(database)]
241
+
242
+ for key, value in (
243
+ ("OWNER", '"{0}"'.format(owner) if owner else owner),
244
+ ("TEMPLATE", template),
245
+ ("ENCODING", encoding),
246
+ ("LC_COLLATE", lc_collate),
247
+ ("LC_CTYPE", lc_ctype),
248
+ ("TABLESPACE", tablespace),
249
+ ("CONNECTION LIMIT", connection_limit),
250
+ ):
251
+ if value:
252
+ sql_bits.append("{0} {1}".format(key, value))
253
+
254
+ yield make_execute_psql_command(
255
+ StringCommand(*sql_bits),
256
+ user=psql_user,
257
+ password=psql_password,
258
+ host=psql_host,
259
+ port=psql_port,
260
+ )
261
+ else:
262
+ host.noop("postgresql database {0} exists".format(database))
263
+
264
+
265
+ @operation(is_idempotent=False)
266
+ def dump(
267
+ dest: str,
268
+ database: str | None = None,
269
+ # Details for speaking to PostgreSQL via `psql` CLI
270
+ psql_user: str | None = None,
271
+ psql_password: str | None = None,
272
+ psql_host: str | None = None,
273
+ psql_port: int | None = None,
274
+ ):
275
+ """
276
+ Dump a PostgreSQL database into a ``.sql`` file. Requires ``pg_dump``.
277
+
278
+ + dest: name of the file to dump the SQL to
279
+ + database: name of the database to dump
280
+ + psql_*: global module arguments, see above
281
+
282
+ **Example:**
283
+
284
+ .. code:: python
285
+
286
+ postgresql.dump(
287
+ name="Dump the pyinfra_stuff database",
288
+ dest="/tmp/pyinfra_stuff.dump",
289
+ database="pyinfra_stuff",
290
+ sudo_user="postgres",
291
+ )
292
+
293
+ """
294
+
295
+ yield StringCommand(
296
+ make_psql_command(
297
+ executable="pg_dump",
298
+ database=database,
299
+ user=psql_user,
300
+ password=psql_password,
301
+ host=psql_host,
302
+ port=psql_port,
303
+ ),
304
+ ">",
305
+ dest,
306
+ )
307
+
308
+
309
+ @operation(is_idempotent=False)
310
+ def load(
311
+ src: str,
312
+ database: str | None = None,
313
+ # Details for speaking to PostgreSQL via `psql` CLI
314
+ psql_user: str | None = None,
315
+ psql_password: str | None = None,
316
+ psql_host: str | None = None,
317
+ psql_port: int | None = None,
318
+ ):
319
+ """
320
+ Load ``.sql`` file into a database.
321
+
322
+ + src: the filename to read from
323
+ + database: name of the database to import into
324
+ + psql_*: global module arguments, see above
325
+
326
+ **Example:**
327
+
328
+ .. code:: python
329
+
330
+ postgresql.load(
331
+ name="Import the pyinfra_stuff dump into pyinfra_stuff_copy",
332
+ src="/tmp/pyinfra_stuff.dump",
333
+ database="pyinfra_stuff_copy",
334
+ sudo_user="postgres",
335
+ )
336
+
337
+ """
338
+
339
+ yield StringCommand(
340
+ make_psql_command(
341
+ database=database,
342
+ user=psql_user,
343
+ password=psql_password,
344
+ host=psql_host,
345
+ port=psql_port,
346
+ ),
347
+ "<",
348
+ src,
349
+ )