python3-cyberfusion-file-support 1.1.1.1__tar.gz → 1.2__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.
Files changed (14) hide show
  1. {python3-cyberfusion-file-support-1.1.1.1 → python3_cyberfusion_file_support-1.2}/PKG-INFO +5 -11
  2. {python3-cyberfusion-file-support-1.1.1.1 → python3_cyberfusion_file_support-1.2}/README.md +0 -6
  3. python3_cyberfusion_file_support-1.2/pyproject.toml +19 -0
  4. {python3-cyberfusion-file-support-1.1.1.1 → python3_cyberfusion_file_support-1.2}/src/cyberfusion/FileSupport/__init__.py +62 -28
  5. python3_cyberfusion_file_support-1.2/src/cyberfusion/FileSupport/encryption.py +65 -0
  6. python3_cyberfusion_file_support-1.2/src/cyberfusion/FileSupport/exceptions.py +13 -0
  7. {python3-cyberfusion-file-support-1.1.1.1 → python3_cyberfusion_file_support-1.2}/src/python3_cyberfusion_file_support.egg-info/PKG-INFO +5 -11
  8. {python3-cyberfusion-file-support-1.1.1.1 → python3_cyberfusion_file_support-1.2}/src/python3_cyberfusion_file_support.egg-info/SOURCES.txt +2 -1
  9. python3-cyberfusion-file-support-1.1.1.1/pyproject.toml +0 -21
  10. python3-cyberfusion-file-support-1.1.1.1/setup.py +0 -25
  11. {python3-cyberfusion-file-support-1.1.1.1 → python3_cyberfusion_file_support-1.2}/setup.cfg +0 -0
  12. {python3-cyberfusion-file-support-1.1.1.1 → python3_cyberfusion_file_support-1.2}/src/python3_cyberfusion_file_support.egg-info/dependency_links.txt +0 -0
  13. {python3-cyberfusion-file-support-1.1.1.1 → python3_cyberfusion_file_support-1.2}/src/python3_cyberfusion_file_support.egg-info/requires.txt +0 -0
  14. {python3-cyberfusion-file-support-1.1.1.1 → python3_cyberfusion_file_support-1.2}/src/python3_cyberfusion_file_support.egg-info/top_level.txt +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python3-cyberfusion-file-support
3
- Version: 1.1.1.1
3
+ Version: 1.2
4
4
  Summary: Library for idempotent writing to files.
5
- Home-page: https://github.com/CyberfusionIO/python3-cyberfusion-file-support
6
- Author: Cyberfusion
7
- Author-email: support@cyberfusion.io
8
- Platform: linux
5
+ Author-email: Cyberfusion <support@cyberfusion.io>
6
+ Project-URL: Source, https://github.com/CyberfusionIO/python3-cyberfusion-file-support
9
7
  Description-Content-Type: text/markdown
8
+ Requires-Dist: python3-cyberfusion-common~=2.0
9
+ Requires-Dist: python3-cyberfusion-queue-support~=1.1
10
10
 
11
11
  # python3-cyberfusion-file-support
12
12
 
@@ -20,12 +20,6 @@ Run the following command to install the package from PyPI:
20
20
 
21
21
  pip3 install python3-cyberfusion-file-support
22
22
 
23
- ## Generic
24
-
25
- Run the following command to create a source distribution:
26
-
27
- python3 setup.py sdist
28
-
29
23
  ## Debian
30
24
 
31
25
  Run the following commands to build a Debian package:
@@ -10,12 +10,6 @@ Run the following command to install the package from PyPI:
10
10
 
11
11
  pip3 install python3-cyberfusion-file-support
12
12
 
13
- ## Generic
14
-
15
- Run the following command to create a source distribution:
16
-
17
- python3 setup.py sdist
18
-
19
13
  ## Debian
20
14
 
21
15
  Run the following commands to build a Debian package:
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "python3-cyberfusion-file-support"
7
+ version = "1.2"
8
+ description = "Library for idempotent writing to files."
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Cyberfusion", email = "support@cyberfusion.io" },
12
+ ]
13
+ dependencies = [
14
+ "python3-cyberfusion-common~=2.0",
15
+ "python3-cyberfusion-queue-support~=1.1",
16
+ ]
17
+
18
+ [project.urls]
19
+ "Source" = "https://github.com/CyberfusionIO/python3-cyberfusion-file-support"
@@ -1,8 +1,9 @@
1
1
  """Classes for files."""
2
2
 
3
3
  import difflib
4
- import filecmp
4
+
5
5
  import os
6
+
6
7
  from typing import List, Optional, Union
7
8
 
8
9
  from cyberfusion.Common import get_tmp_file
@@ -11,13 +12,27 @@ from cyberfusion.QueueSupport.items.command import CommandItem
11
12
  from cyberfusion.QueueSupport.items.copy import CopyItem
12
13
  from cyberfusion.QueueSupport.items.unlink import UnlinkItem
13
14
 
15
+ from cyberfusion.FileSupport.encryption import (
16
+ EncryptionProperties,
17
+ encrypt_file,
18
+ decrypt_file,
19
+ )
20
+ from cyberfusion.FileSupport.exceptions import DecryptionError
21
+
14
22
 
15
23
  class _DestinationFile:
16
24
  """Represents destination file."""
17
25
 
18
- def __init__(self, *, path: str) -> None:
19
- """Set attributes."""
26
+ def __init__(
27
+ self, *, path: str, encryption_properties: Optional[EncryptionProperties] = None
28
+ ) -> None:
29
+ """Set attributes.
30
+
31
+ If 'encryption_properties' is specified, and the destination file already
32
+ exists, it must be encrypted using the same properties (it is decrypted).
33
+ """
20
34
  self.path = path
35
+ self.encryption_properties = encryption_properties
21
36
 
22
37
  @property
23
38
  def exists(self) -> bool:
@@ -27,11 +42,19 @@ class _DestinationFile:
27
42
  @property
28
43
  def contents(self) -> Optional[str]:
29
44
  """Get contents."""
30
- if self.exists:
45
+ if not self.exists:
46
+ return None
47
+
48
+ if not self.encryption_properties:
31
49
  with open(self.path, "r") as f:
32
50
  return f.read()
33
51
 
34
- return None
52
+ try:
53
+ return decrypt_file(self.encryption_properties, self.path)
54
+ except DecryptionError as e:
55
+ raise DecryptionError(
56
+ f"Decrypting the destination file at '{self.path}' failed. Note that the file must already be encrypted using the specified encryption properties."
57
+ ) from e
35
58
 
36
59
 
37
60
  class DestinationFileReplacement:
@@ -41,33 +64,37 @@ class DestinationFileReplacement:
41
64
  self,
42
65
  queue: Queue,
43
66
  *,
44
- contents: Union[str, bytes],
67
+ contents: str,
45
68
  destination_file_path: str,
46
69
  default_comment_character: Optional[str] = None,
47
70
  command: Optional[List[str]] = None,
48
71
  reference: Optional[str] = None,
72
+ encryption_properties: Optional[EncryptionProperties] = None,
49
73
  ) -> None:
50
74
  """Set attributes.
51
75
 
52
76
  'default_comment_character' has no effect when 'contents' is not string.
77
+
78
+ If 'encryption_properties' is specified, and the destination file already
79
+ exists, it must be encrypted using the same properties (it is decrypted).
53
80
  """
54
81
  self.queue = queue
55
82
  self._contents = contents
56
83
  self.default_comment_character = default_comment_character
57
84
  self.command = command
58
85
  self.reference = reference
86
+ self.encryption_properties = encryption_properties
59
87
 
60
88
  self.tmp_path = get_tmp_file()
61
- self.destination_file = _DestinationFile(path=destination_file_path)
89
+ self.destination_file = _DestinationFile(
90
+ path=destination_file_path, encryption_properties=encryption_properties
91
+ )
62
92
 
63
- self._write_to_tmp_file()
93
+ self.write_to_file(self.tmp_path)
64
94
 
65
95
  @property
66
- def contents(self) -> Union[str, bytes]:
96
+ def contents(self) -> str:
67
97
  """Get contents."""
68
- if not isinstance(self._contents, str):
69
- return self._contents
70
-
71
98
  if self._contents != "" and not self._contents.endswith(
72
99
  "\n"
73
100
  ): # Some programs require newline to consider last line completed
@@ -77,27 +104,39 @@ class DestinationFileReplacement:
77
104
  return self._contents
78
105
 
79
106
  default_comment = f"{self.default_comment_character} Update this file via your management interface.\n"
80
- default_comment += f"{self.default_comment_character} Your changes will be overwritten.\n"
107
+ default_comment += (
108
+ f"{self.default_comment_character} Your changes will be overwritten.\n"
109
+ )
81
110
  default_comment += "\n"
82
111
 
83
112
  return default_comment + self._contents
84
113
 
85
- def _write_to_tmp_file(self) -> None:
86
- """Write contents to tmp file."""
87
- if isinstance(self.contents, bytes):
114
+ def write_to_file(self, path: str) -> None:
115
+ """Write contents to file."""
116
+ contents: Union[str, bytes]
117
+
118
+ if self.encryption_properties:
88
119
  open_mode = "wb"
120
+
121
+ contents = encrypt_file(
122
+ self.encryption_properties,
123
+ self.contents,
124
+ )
89
125
  else:
90
126
  open_mode = "w"
91
127
 
92
- with open(self.tmp_path, open_mode) as f:
93
- f.write(self.contents)
128
+ contents = self.contents
129
+
130
+ with open(path, open_mode) as f:
131
+ f.write(contents)
94
132
 
95
133
  @property
96
134
  def changed(self) -> bool:
97
135
  """Get if destination file will change."""
98
- return not self.destination_file.exists or not filecmp.cmp(
99
- self.tmp_path, self.destination_file.path
100
- )
136
+ if not self.destination_file.exists:
137
+ return True
138
+
139
+ return self.destination_file.contents != self.contents
101
140
 
102
141
  @property
103
142
  def differences(self) -> List[str]:
@@ -105,9 +144,6 @@ class DestinationFileReplacement:
105
144
 
106
145
  No differences are returned when contents is not string.
107
146
  """
108
- if not isinstance(self._contents, str):
109
- return []
110
-
111
147
  results = []
112
148
 
113
149
  for line in difflib.unified_diff(
@@ -116,7 +152,7 @@ class DestinationFileReplacement:
116
152
  if self.destination_file.contents
117
153
  else []
118
154
  ),
119
- self.contents.splitlines(), # type: ignore[arg-type]
155
+ self.contents.splitlines(),
120
156
  fromfile=self.tmp_path,
121
157
  tofile=self.destination_file.path,
122
158
  lineterm="",
@@ -146,9 +182,7 @@ class DestinationFileReplacement:
146
182
 
147
183
  if self.command:
148
184
  self.queue.add(
149
- CommandItem(
150
- command=self.command, reference=self.reference
151
- ),
185
+ CommandItem(command=self.command, reference=self.reference),
152
186
  )
153
187
 
154
188
  self.queue.add(
@@ -0,0 +1,65 @@
1
+ """Utilities for file encryption."""
2
+
3
+ import subprocess
4
+ from dataclasses import dataclass
5
+ from enum import Enum
6
+
7
+ from cyberfusion.FileSupport.exceptions import EncryptionError, DecryptionError
8
+
9
+
10
+ class MessageDigestEnum(str, Enum):
11
+ """Message digests supported by OpenSSL."""
12
+
13
+ MD2 = "md2"
14
+ MD5 = "md5"
15
+ SHA = "sha"
16
+ SHA1 = "sha1"
17
+
18
+
19
+ @dataclass
20
+ class EncryptionProperties:
21
+ """Properties to encrypt files, needed by OpenSSL."""
22
+
23
+ cipher_name: str # Get options with `openssl list -cipher-algorithms`
24
+ message_digest: MessageDigestEnum
25
+ password_file_path: str # Create password with `openssl rand -hex 128`
26
+
27
+
28
+ def encrypt_file(encryption_properties: EncryptionProperties, contents: str) -> bytes:
29
+ """Get contents for file to encrypt."""
30
+ try:
31
+ return subprocess.check_output(
32
+ [
33
+ "openssl",
34
+ "enc",
35
+ "-" + encryption_properties.cipher_name,
36
+ "-md",
37
+ encryption_properties.message_digest,
38
+ "-pass",
39
+ "file:" + encryption_properties.password_file_path,
40
+ ],
41
+ input=contents.encode(),
42
+ )
43
+ except subprocess.CalledProcessError as e:
44
+ raise EncryptionError from e
45
+
46
+
47
+ def decrypt_file(encryption_properties: EncryptionProperties, path: str) -> str:
48
+ """Get contents of encrypted file."""
49
+ try:
50
+ return subprocess.check_output(
51
+ [
52
+ "openssl",
53
+ "enc",
54
+ "-d",
55
+ "-" + encryption_properties.cipher_name,
56
+ "-md",
57
+ encryption_properties.message_digest,
58
+ "-pass",
59
+ "file:" + encryption_properties.password_file_path,
60
+ "-in",
61
+ path,
62
+ ]
63
+ ).decode()
64
+ except subprocess.CalledProcessError as e:
65
+ raise DecryptionError from e
@@ -0,0 +1,13 @@
1
+ """Exceptions."""
2
+
3
+
4
+ class EncryptionError(Exception):
5
+ """Encrypting failed."""
6
+
7
+ pass
8
+
9
+
10
+ class DecryptionError(Exception):
11
+ """Decrypting failed."""
12
+
13
+ pass
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python3-cyberfusion-file-support
3
- Version: 1.1.1.1
3
+ Version: 1.2
4
4
  Summary: Library for idempotent writing to files.
5
- Home-page: https://github.com/CyberfusionIO/python3-cyberfusion-file-support
6
- Author: Cyberfusion
7
- Author-email: support@cyberfusion.io
8
- Platform: linux
5
+ Author-email: Cyberfusion <support@cyberfusion.io>
6
+ Project-URL: Source, https://github.com/CyberfusionIO/python3-cyberfusion-file-support
9
7
  Description-Content-Type: text/markdown
8
+ Requires-Dist: python3-cyberfusion-common~=2.0
9
+ Requires-Dist: python3-cyberfusion-queue-support~=1.1
10
10
 
11
11
  # python3-cyberfusion-file-support
12
12
 
@@ -20,12 +20,6 @@ Run the following command to install the package from PyPI:
20
20
 
21
21
  pip3 install python3-cyberfusion-file-support
22
22
 
23
- ## Generic
24
-
25
- Run the following command to create a source distribution:
26
-
27
- python3 setup.py sdist
28
-
29
23
  ## Debian
30
24
 
31
25
  Run the following commands to build a Debian package:
@@ -1,8 +1,9 @@
1
1
  README.md
2
2
  pyproject.toml
3
3
  setup.cfg
4
- setup.py
5
4
  src/cyberfusion/FileSupport/__init__.py
5
+ src/cyberfusion/FileSupport/encryption.py
6
+ src/cyberfusion/FileSupport/exceptions.py
6
7
  src/python3_cyberfusion_file_support.egg-info/PKG-INFO
7
8
  src/python3_cyberfusion_file_support.egg-info/SOURCES.txt
8
9
  src/python3_cyberfusion_file_support.egg-info/dependency_links.txt
@@ -1,21 +0,0 @@
1
- [tool.isort]
2
- profile = "black"
3
- line_length = 79
4
- known_first_party = ["cyberfusion"]
5
- default_section = "THIRDPARTY"
6
-
7
- [tool.black]
8
- line-length = 79
9
- exclude = '''
10
- (
11
- /(
12
- \.eggs # exclude a few common directories in the
13
- | \.git # root of the project
14
- | \.hg
15
- | \.mypy_cache
16
- | \.tox
17
- | \.venv
18
- | venv
19
- )/
20
- )
21
- '''
@@ -1,25 +0,0 @@
1
- """A setuptools based setup module."""
2
-
3
- from setuptools import setup
4
-
5
- with open("README.md", "r", encoding="utf-8") as fh:
6
- long_description = fh.read()
7
-
8
- setup(
9
- name="python3-cyberfusion-file-support",
10
- version="1.1.1.1",
11
- description="Library for idempotent writing to files.",
12
- long_description=long_description,
13
- long_description_content_type="text/markdown",
14
- author="Cyberfusion",
15
- author_email="support@cyberfusion.io",
16
- url="https://github.com/CyberfusionIO/python3-cyberfusion-file-support",
17
- platforms=["linux"],
18
- packages=["cyberfusion.FileSupport"],
19
- package_dir={"": "src"},
20
- data_files=[],
21
- install_requires=[
22
- "python3-cyberfusion-common~=2.0",
23
- "python3-cyberfusion-queue-support~=1.1",
24
- ],
25
- )