pyinfra 0.11.dev3__py3-none-any.whl → 3.6__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 (204) hide show
  1. pyinfra/__init__.py +9 -12
  2. pyinfra/__main__.py +4 -0
  3. pyinfra/api/__init__.py +19 -3
  4. pyinfra/api/arguments.py +413 -0
  5. pyinfra/api/arguments_typed.py +79 -0
  6. pyinfra/api/command.py +274 -0
  7. pyinfra/api/config.py +222 -28
  8. pyinfra/api/connect.py +33 -13
  9. pyinfra/api/connectors.py +27 -0
  10. pyinfra/api/deploy.py +65 -66
  11. pyinfra/api/exceptions.py +73 -18
  12. pyinfra/api/facts.py +267 -200
  13. pyinfra/api/host.py +416 -50
  14. pyinfra/api/inventory.py +121 -160
  15. pyinfra/api/metadata.py +69 -0
  16. pyinfra/api/operation.py +432 -262
  17. pyinfra/api/operations.py +273 -260
  18. pyinfra/api/state.py +302 -248
  19. pyinfra/api/util.py +309 -369
  20. pyinfra/connectors/base.py +173 -0
  21. pyinfra/connectors/chroot.py +212 -0
  22. pyinfra/connectors/docker.py +405 -0
  23. pyinfra/connectors/dockerssh.py +297 -0
  24. pyinfra/connectors/local.py +238 -0
  25. pyinfra/connectors/scp/__init__.py +1 -0
  26. pyinfra/connectors/scp/client.py +204 -0
  27. pyinfra/connectors/ssh.py +727 -0
  28. pyinfra/connectors/ssh_util.py +114 -0
  29. pyinfra/connectors/sshuserclient/client.py +309 -0
  30. pyinfra/connectors/sshuserclient/config.py +102 -0
  31. pyinfra/connectors/terraform.py +135 -0
  32. pyinfra/connectors/util.py +417 -0
  33. pyinfra/connectors/vagrant.py +183 -0
  34. pyinfra/context.py +145 -0
  35. pyinfra/facts/__init__.py +7 -6
  36. pyinfra/facts/apk.py +22 -7
  37. pyinfra/facts/apt.py +117 -60
  38. pyinfra/facts/brew.py +100 -15
  39. pyinfra/facts/bsdinit.py +23 -0
  40. pyinfra/facts/cargo.py +37 -0
  41. pyinfra/facts/choco.py +47 -0
  42. pyinfra/facts/crontab.py +195 -0
  43. pyinfra/facts/deb.py +94 -0
  44. pyinfra/facts/dnf.py +48 -0
  45. pyinfra/facts/docker.py +96 -23
  46. pyinfra/facts/efibootmgr.py +113 -0
  47. pyinfra/facts/files.py +629 -58
  48. pyinfra/facts/flatpak.py +77 -0
  49. pyinfra/facts/freebsd.py +70 -0
  50. pyinfra/facts/gem.py +19 -6
  51. pyinfra/facts/git.py +59 -14
  52. pyinfra/facts/gpg.py +150 -0
  53. pyinfra/facts/hardware.py +313 -167
  54. pyinfra/facts/iptables.py +72 -62
  55. pyinfra/facts/launchd.py +44 -0
  56. pyinfra/facts/lxd.py +17 -4
  57. pyinfra/facts/mysql.py +122 -86
  58. pyinfra/facts/npm.py +17 -9
  59. pyinfra/facts/openrc.py +71 -0
  60. pyinfra/facts/opkg.py +246 -0
  61. pyinfra/facts/pacman.py +50 -7
  62. pyinfra/facts/pip.py +24 -7
  63. pyinfra/facts/pipx.py +82 -0
  64. pyinfra/facts/pkg.py +15 -6
  65. pyinfra/facts/pkgin.py +35 -0
  66. pyinfra/facts/podman.py +54 -0
  67. pyinfra/facts/postgres.py +178 -0
  68. pyinfra/facts/postgresql.py +6 -147
  69. pyinfra/facts/rpm.py +105 -0
  70. pyinfra/facts/runit.py +77 -0
  71. pyinfra/facts/selinux.py +161 -0
  72. pyinfra/facts/server.py +762 -285
  73. pyinfra/facts/snap.py +88 -0
  74. pyinfra/facts/systemd.py +139 -0
  75. pyinfra/facts/sysvinit.py +59 -0
  76. pyinfra/facts/upstart.py +35 -0
  77. pyinfra/facts/util/__init__.py +17 -0
  78. pyinfra/facts/util/databases.py +4 -6
  79. pyinfra/facts/util/packaging.py +37 -6
  80. pyinfra/facts/util/units.py +30 -0
  81. pyinfra/facts/util/win_files.py +99 -0
  82. pyinfra/facts/vzctl.py +20 -13
  83. pyinfra/facts/xbps.py +35 -0
  84. pyinfra/facts/yum.py +34 -40
  85. pyinfra/facts/zfs.py +77 -0
  86. pyinfra/facts/zypper.py +42 -0
  87. pyinfra/local.py +45 -83
  88. pyinfra/operations/__init__.py +12 -0
  89. pyinfra/operations/apk.py +99 -0
  90. pyinfra/operations/apt.py +496 -0
  91. pyinfra/operations/brew.py +232 -0
  92. pyinfra/operations/bsdinit.py +59 -0
  93. pyinfra/operations/cargo.py +45 -0
  94. pyinfra/operations/choco.py +61 -0
  95. pyinfra/operations/crontab.py +194 -0
  96. pyinfra/operations/dnf.py +213 -0
  97. pyinfra/operations/docker.py +492 -0
  98. pyinfra/operations/files.py +2014 -0
  99. pyinfra/operations/flatpak.py +95 -0
  100. pyinfra/operations/freebsd/__init__.py +12 -0
  101. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  102. pyinfra/operations/freebsd/pkg.py +219 -0
  103. pyinfra/operations/freebsd/service.py +116 -0
  104. pyinfra/operations/freebsd/sysrc.py +92 -0
  105. pyinfra/operations/gem.py +48 -0
  106. pyinfra/operations/git.py +420 -0
  107. pyinfra/operations/iptables.py +312 -0
  108. pyinfra/operations/launchd.py +45 -0
  109. pyinfra/operations/lxd.py +69 -0
  110. pyinfra/operations/mysql.py +610 -0
  111. pyinfra/operations/npm.py +57 -0
  112. pyinfra/operations/openrc.py +63 -0
  113. pyinfra/operations/opkg.py +89 -0
  114. pyinfra/operations/pacman.py +82 -0
  115. pyinfra/operations/pip.py +206 -0
  116. pyinfra/operations/pipx.py +103 -0
  117. pyinfra/operations/pkg.py +71 -0
  118. pyinfra/operations/pkgin.py +92 -0
  119. pyinfra/operations/postgres.py +437 -0
  120. pyinfra/operations/postgresql.py +30 -0
  121. pyinfra/operations/puppet.py +41 -0
  122. pyinfra/operations/python.py +73 -0
  123. pyinfra/operations/runit.py +184 -0
  124. pyinfra/operations/selinux.py +190 -0
  125. pyinfra/operations/server.py +1100 -0
  126. pyinfra/operations/snap.py +118 -0
  127. pyinfra/operations/ssh.py +217 -0
  128. pyinfra/operations/systemd.py +150 -0
  129. pyinfra/operations/sysvinit.py +142 -0
  130. pyinfra/operations/upstart.py +68 -0
  131. pyinfra/operations/util/__init__.py +12 -0
  132. pyinfra/operations/util/docker.py +407 -0
  133. pyinfra/operations/util/files.py +247 -0
  134. pyinfra/operations/util/packaging.py +338 -0
  135. pyinfra/operations/util/service.py +46 -0
  136. pyinfra/operations/vzctl.py +137 -0
  137. pyinfra/operations/xbps.py +78 -0
  138. pyinfra/operations/yum.py +213 -0
  139. pyinfra/operations/zfs.py +176 -0
  140. pyinfra/operations/zypper.py +193 -0
  141. pyinfra/progress.py +44 -32
  142. pyinfra/py.typed +0 -0
  143. pyinfra/version.py +9 -1
  144. pyinfra-3.6.dist-info/METADATA +142 -0
  145. pyinfra-3.6.dist-info/RECORD +160 -0
  146. {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info}/WHEEL +1 -2
  147. pyinfra-3.6.dist-info/entry_points.txt +12 -0
  148. {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info/licenses}/LICENSE.md +1 -1
  149. pyinfra_cli/__init__.py +1 -0
  150. pyinfra_cli/cli.py +793 -0
  151. pyinfra_cli/commands.py +66 -0
  152. pyinfra_cli/exceptions.py +155 -65
  153. pyinfra_cli/inventory.py +233 -89
  154. pyinfra_cli/log.py +39 -43
  155. pyinfra_cli/main.py +26 -495
  156. pyinfra_cli/prints.py +215 -156
  157. pyinfra_cli/util.py +172 -105
  158. pyinfra_cli/virtualenv.py +25 -20
  159. pyinfra/api/connectors/__init__.py +0 -21
  160. pyinfra/api/connectors/ansible.py +0 -99
  161. pyinfra/api/connectors/docker.py +0 -178
  162. pyinfra/api/connectors/local.py +0 -169
  163. pyinfra/api/connectors/ssh.py +0 -402
  164. pyinfra/api/connectors/sshuserclient/client.py +0 -105
  165. pyinfra/api/connectors/sshuserclient/config.py +0 -90
  166. pyinfra/api/connectors/util.py +0 -63
  167. pyinfra/api/connectors/vagrant.py +0 -155
  168. pyinfra/facts/init.py +0 -176
  169. pyinfra/facts/util/files.py +0 -102
  170. pyinfra/hook.py +0 -41
  171. pyinfra/modules/__init__.py +0 -11
  172. pyinfra/modules/apk.py +0 -64
  173. pyinfra/modules/apt.py +0 -272
  174. pyinfra/modules/brew.py +0 -122
  175. pyinfra/modules/files.py +0 -711
  176. pyinfra/modules/gem.py +0 -30
  177. pyinfra/modules/git.py +0 -115
  178. pyinfra/modules/init.py +0 -344
  179. pyinfra/modules/iptables.py +0 -271
  180. pyinfra/modules/lxd.py +0 -45
  181. pyinfra/modules/mysql.py +0 -347
  182. pyinfra/modules/npm.py +0 -47
  183. pyinfra/modules/pacman.py +0 -60
  184. pyinfra/modules/pip.py +0 -99
  185. pyinfra/modules/pkg.py +0 -43
  186. pyinfra/modules/postgresql.py +0 -245
  187. pyinfra/modules/puppet.py +0 -20
  188. pyinfra/modules/python.py +0 -37
  189. pyinfra/modules/server.py +0 -524
  190. pyinfra/modules/ssh.py +0 -150
  191. pyinfra/modules/util/files.py +0 -52
  192. pyinfra/modules/util/packaging.py +0 -118
  193. pyinfra/modules/vzctl.py +0 -133
  194. pyinfra/modules/yum.py +0 -171
  195. pyinfra/pseudo_modules.py +0 -64
  196. pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
  197. pyinfra-0.11.dev3.dist-info/METADATA +0 -135
  198. pyinfra-0.11.dev3.dist-info/RECORD +0 -95
  199. pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
  200. pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
  201. pyinfra_cli/__main__.py +0 -40
  202. pyinfra_cli/config.py +0 -92
  203. /pyinfra/{modules/util → connectors}/__init__.py +0 -0
  204. /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
@@ -0,0 +1,184 @@
1
+ """
2
+ Manage runit services.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Optional
8
+
9
+ from pyinfra import host
10
+ from pyinfra.api import operation
11
+ from pyinfra.facts.files import File
12
+ from pyinfra.facts.runit import RunitManaged, RunitStatus
13
+
14
+ from .files import file, link
15
+ from .util.service import handle_service_control
16
+
17
+
18
+ @operation()
19
+ def service(
20
+ service: str,
21
+ running: bool = True,
22
+ restarted: bool = False,
23
+ reloaded: bool = False,
24
+ command: Optional[str] = None,
25
+ enabled: Optional[bool] = None,
26
+ managed: bool = True,
27
+ svdir: str = "/var/service",
28
+ sourcedir: str = "/etc/sv",
29
+ ):
30
+ """
31
+ Manage the state of runit services.
32
+
33
+ + service: name of the service to manage
34
+ + running: whether the service should be running
35
+ + restarted: whether the service should be restarted
36
+ + reloaded: whether the service should be reloaded
37
+ + command: custom command to pass like: ``sv <command> <service>``
38
+ + enabled: whether this service should be enabled/disabled on boot
39
+ + managed: whether runit should manage this service
40
+
41
+ For services to be controlled, they first need to be managed by runit by
42
+ adding a symlink to the service in ``SVDIR``.
43
+ By setting ``managed=False`` the symlink will be removed.
44
+ Other options won't have any effect after that.
45
+ Although the ``<service>/down`` file can still be controlled with the
46
+ ``enabled`` option.
47
+
48
+ + svdir: alternative ``SVDIR``
49
+
50
+ An alternative ``SVDIR`` can be specified. This can be used for user services.
51
+
52
+ + sourcedir: where to search for available services
53
+
54
+ An alternative directory for available services can be specified.
55
+ Example: ``sourcedir=/etc/sv.local`` for services managed by the administrator.
56
+ """
57
+
58
+ was_managed = service in host.get_fact(RunitManaged, service=service, svdir=svdir)
59
+ was_auto = not host.get_fact(File, path="{0}/{1}/down".format(sourcedir, service))
60
+
61
+ # Disable autostart for previously unmanaged services.
62
+ #
63
+ # Where ``running=False`` is requested, this prevents one case of briefly
64
+ # starting and stopping the service.
65
+ if not was_managed and managed and was_auto:
66
+ yield from auto._inner(
67
+ service=service,
68
+ auto=False,
69
+ sourcedir=sourcedir,
70
+ )
71
+
72
+ yield from manage._inner(
73
+ service=service,
74
+ managed=managed,
75
+ svdir=svdir,
76
+ sourcedir=sourcedir,
77
+ )
78
+
79
+ # Service wasn't managed before, so wait for ``runsv`` to start.
80
+ # ``runsvdir`` will check at least every 5 seconds for new services.
81
+ # Wait for at most 10 seconds for the service to be managed, otherwise fail.
82
+ if not was_managed and managed:
83
+ yield from wait_runsv._inner(
84
+ service=service,
85
+ svdir=svdir,
86
+ )
87
+
88
+ if isinstance(enabled, bool):
89
+ yield from auto._inner(
90
+ service=service,
91
+ auto=enabled,
92
+ sourcedir=sourcedir,
93
+ )
94
+ else:
95
+ # restore previous state of ``<service>/down``
96
+ yield from auto._inner(
97
+ service=service,
98
+ auto=was_auto,
99
+ sourcedir=sourcedir,
100
+ )
101
+
102
+ # Services need to be managed by ``runit`` for the other options to make sense.
103
+ if not managed:
104
+ return
105
+
106
+ yield from handle_service_control(
107
+ host,
108
+ service,
109
+ host.get_fact(RunitStatus, service=service, svdir=svdir),
110
+ "SVDIR={0} sv {{1}} {{0}}".format(svdir),
111
+ running,
112
+ restarted,
113
+ reloaded,
114
+ command,
115
+ )
116
+
117
+
118
+ @operation()
119
+ def manage(
120
+ service: str,
121
+ managed: bool = True,
122
+ svdir: str = "/var/service",
123
+ sourcedir: str = "/etc/sv",
124
+ ):
125
+ """
126
+ Manage runit svdir links.
127
+
128
+ + service: name of the service to manage
129
+ + managed: whether the link should exist
130
+ + svdir: alternative ``SVDIR``
131
+ + sourcedir: where to search for available services
132
+ """
133
+
134
+ yield from link._inner(
135
+ path="{0}/{1}".format(svdir, service),
136
+ target="{0}/{1}".format(sourcedir, service),
137
+ present=managed,
138
+ create_remote_dir=False,
139
+ )
140
+
141
+
142
+ @operation(is_idempotent=False)
143
+ def wait_runsv(
144
+ service: str,
145
+ svdir: str = "/var/service",
146
+ timeout: int = 10,
147
+ ):
148
+ """
149
+ Wait for runsv for ``service`` to be available.
150
+
151
+ + service: name of the service to manage
152
+ + svdir: alternative ``SVDIR``
153
+ + timeout: time in seconds to wait
154
+ """
155
+
156
+ yield (
157
+ "export SVDIR={0}\n"
158
+ "for i in $(seq {1}); do\n"
159
+ " sv status {2} > /dev/null && exit 0\n"
160
+ " sleep 1;\n"
161
+ "done\n"
162
+ "exit 1"
163
+ ).format(svdir, timeout, service)
164
+
165
+
166
+ @operation()
167
+ def auto(
168
+ service: str,
169
+ auto: bool = True,
170
+ sourcedir: str = "/etc/sv",
171
+ ):
172
+ """
173
+ Start service automatically by managing the ``service/down`` file.
174
+
175
+ + service: name of the service to manage
176
+ + auto: whether the service should start automatically
177
+ + sourcedir: where to search for available services
178
+ """
179
+
180
+ yield from file._inner(
181
+ path="{0}/{1}/down".format(sourcedir, service),
182
+ present=not auto,
183
+ create_remote_dir=False,
184
+ )
@@ -0,0 +1,190 @@
1
+ """
2
+ Provides operations to set SELinux file contexts, booleans and port types.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from enum import Enum
8
+
9
+ from pyinfra import host
10
+ from pyinfra.api import OperationValueError, QuoteString, StringCommand, operation
11
+ from pyinfra.facts.selinux import FileContext, FileContextMapping, SEBoolean, SEPort, SEPorts
12
+ from pyinfra.facts.server import Which
13
+
14
+
15
+ class Boolean(Enum):
16
+ ON = "on"
17
+ OFF = "off"
18
+
19
+
20
+ class Protocol(Enum):
21
+ UDP = "udp"
22
+ TCP = "tcp"
23
+ SCTP = "sctp"
24
+ DCCP = "dccp"
25
+
26
+
27
+ @operation()
28
+ def boolean(bool_name: str, value: Boolean, persistent=False):
29
+ """
30
+ Set the specified SELinux boolean to the desired state.
31
+
32
+ + boolean: name of an SELinux boolean
33
+ + value: desired state of the boolean
34
+ + persistent: whether to write updated policy or not
35
+
36
+ Note: This operation requires root privileges.
37
+
38
+ **Example:**
39
+
40
+ .. code:: python
41
+
42
+ from pyinfra.operations import selinux
43
+ selinux.boolean(
44
+ name='Allow Apache to connect to LDAP server',
45
+ 'httpd_can_network_connect',
46
+ Boolean.ON,
47
+ persistent=True
48
+ )
49
+ """
50
+
51
+ value_str: str
52
+ if value in ["on", "off"]: # compatibility with the old version
53
+ assert isinstance(value, str)
54
+ value_str = value
55
+ elif value is Boolean.ON:
56
+ value_str = "on"
57
+ elif value is Boolean.OFF:
58
+ value_str = "off"
59
+ else:
60
+ raise OperationValueError(f"Invalid value '{value}' for boolean operation")
61
+
62
+ if host.get_fact(SEBoolean, boolean=bool_name) != value_str:
63
+ persist = "-P " if persistent else ""
64
+ yield StringCommand("setsebool", f"{persist}{bool_name}", value_str)
65
+ else:
66
+ host.noop(f"boolean '{bool_name}' already had the value '{value_str}'")
67
+
68
+
69
+ @operation()
70
+ def file_context(path: str, se_type: str):
71
+ """
72
+ Set the SELinux type for the specified path to the specified value.
73
+
74
+ + path: the target path (expression) for the context
75
+ + se_type: the SELinux type for the given target
76
+
77
+ **Example:**
78
+
79
+ .. code:: python
80
+
81
+ selinux.file_context(
82
+ name='Allow /foo/bar to be served by the web server',
83
+ '/foo/bar',
84
+ 'httpd_sys_content_t'
85
+ )
86
+ """
87
+
88
+ current = host.get_fact(FileContext, path=path) or {}
89
+ if se_type != current.get("type", ""):
90
+ yield StringCommand("chcon", "-t", se_type, QuoteString(path))
91
+ else:
92
+ host.noop(f"file_context: '{path}' already had type '{se_type}'")
93
+
94
+
95
+ @operation()
96
+ def file_context_mapping(target: str, se_type: str | None = None, present=True):
97
+ """
98
+ Set the SELinux file context mapping for paths matching the target.
99
+
100
+ + target: the target path (expression) for the context
101
+ + se_type: the SELinux type for the given target
102
+ + present: whether to add or remove the target -> context mapping
103
+
104
+ Note: `file_context` does not change the SELinux file context for existing files
105
+ so `restorecon` may need to be run manually if the file contexts cannot be created
106
+ before the related files.
107
+
108
+ **Example:**
109
+
110
+ .. code:: python
111
+
112
+ selinux.file_context_mapping(
113
+ name='Allow Apache to serve content from the /web directory',
114
+ r'/web(/.*)?',
115
+ se_type='httpd_sys_content_t'
116
+ )
117
+ """
118
+ if present and (se_type is None):
119
+ raise ValueError("se_type must have a valid value if present is set")
120
+
121
+ current = host.get_fact(FileContextMapping, target=target)
122
+ if present:
123
+ option = "-a" if len(current) == 0 else ("-m" if current.get("type") != se_type else "")
124
+ if option != "":
125
+ yield StringCommand("semanage", "fcontext", option, "-t", se_type, QuoteString(target))
126
+ else:
127
+ host.noop(f"mapping for '{target}' -> '{se_type}' already present")
128
+ else:
129
+ if len(current) > 0:
130
+ yield StringCommand("semanage", "fcontext", "-d", QuoteString(target))
131
+ else:
132
+ host.noop(f"no existing mapping for '{target}'")
133
+
134
+
135
+ @operation()
136
+ def port(protocol: Protocol | str, port_num: int, se_type: str | None = None, present=True):
137
+ """
138
+ Set the SELinux type for the specified protocol and port.
139
+
140
+ + protocol: the protocol: (udp|tcp|sctp|dccp)
141
+ + port: the port
142
+ + se_type: the SELinux type for the given port
143
+ + present: whether to add or remove the SELinux type for the port
144
+
145
+ Note: This operation requires root privileges.
146
+
147
+ **Example:**
148
+
149
+ .. code:: python
150
+
151
+ selinux.port(
152
+ name='Allow Apache to provide service on port 2222',
153
+ Protocol.TCP,
154
+ 2222,
155
+ 'http_port_t',
156
+ )
157
+ """
158
+
159
+ if protocol is Protocol:
160
+ assert isinstance(protocol, Protocol)
161
+ protocol = protocol.value
162
+
163
+ if present and (se_type is None):
164
+ raise ValueError("se_type must have a valid value if present is set")
165
+
166
+ new_type = se_type if present else ""
167
+ direct_get = len(host.get_fact(Which, command="sepolicy") or "") > 0
168
+ if direct_get:
169
+ current = host.get_fact(SEPort, protocol=protocol, port=port_num)
170
+ else:
171
+ port_info = host.get_fact(SEPorts)
172
+ current = port_info.get(protocol, {}).get(str(port_num), "")
173
+
174
+ if present:
175
+ option = "-a" if current == "" else ("-m" if current != se_type else "")
176
+ if option != "":
177
+ yield StringCommand("semanage", "port", option, "-t", se_type, "-p", protocol, port_num)
178
+ else:
179
+ host.noop(f"setype for '{protocol}/{port_num}' is already '{se_type}'")
180
+ else:
181
+ if current != "":
182
+ yield StringCommand("semanage", "port", "-d", "-p", protocol, port_num)
183
+ else:
184
+ host.noop(f"setype for '{protocol}/{port_num}' is already unset")
185
+
186
+ if (present and (option != "")) or (not present and (current != "")):
187
+ if not direct_get:
188
+ if protocol not in port_info:
189
+ port_info[protocol] = {}
190
+ port_info[protocol][str(port_num)] = new_type