Merge ~silverdrake11/landscape-charm:update_apt_lib into landscape-charm:main

Proposed by Kevin Nasto
Status: Merged
Merged at revision: 14c34233b846d584ba47b2ce3ba75d6d6722c4e7
Proposed branch: ~silverdrake11/landscape-charm:update_apt_lib
Merge into: landscape-charm:main
Diff against target: 340 lines (+90/-59)
1 file modified
lib/charms/operator_libs_linux/v0/apt.py (+90/-59)
Reviewer Review Type Date Requested Status
Mitch Burton Approve
Review via email: mp+442181@code.launchpad.net

Commit message

Applied latest update to apt library, which fixes the noninteractive mode issue some were experiencing

To post a comment you must log in.
Revision history for this message
Mitch Burton (mitchburton) wrote :

+1 LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/charms/operator_libs_linux/v0/apt.py b/lib/charms/operator_libs_linux/v0/apt.py
index 2f921f0..7afb183 100644
--- a/lib/charms/operator_libs_linux/v0/apt.py
+++ b/lib/charms/operator_libs_linux/v0/apt.py
@@ -78,7 +78,6 @@ Keys are constructed as `{repo_type}-{}-{release}` in order to uniquely identify
78Repositories can be added with explicit values through a Python constructor.78Repositories can be added with explicit values through a Python constructor.
7979
80Example:80Example:
81
82```python81```python
83repositories = apt.RepositoryMapping()82repositories = apt.RepositoryMapping()
8483
@@ -91,7 +90,6 @@ Alternatively, any valid `sources.list` line may be used to construct a new
91`DebianRepository`.90`DebianRepository`.
9291
93Example:92Example:
94
95```python93```python
96repositories = apt.RepositoryMapping()94repositories = apt.RepositoryMapping()
9795
@@ -124,7 +122,7 @@ LIBAPI = 0
124122
125# Increment this PATCH version before using `charmcraft publish-lib` or reset123# Increment this PATCH version before using `charmcraft publish-lib` or reset
126# to 0 if you are raising the major API version124# to 0 if you are raising the major API version
127LIBPATCH = 8125LIBPATCH = 11
128126
129127
130VALID_SOURCE_TYPES = ("deb", "deb-src")128VALID_SOURCE_TYPES = ("deb", "deb-src")
@@ -135,7 +133,7 @@ class Error(Exception):
135 """Base class of most errors raised by this library."""133 """Base class of most errors raised by this library."""
136134
137 def __repr__(self):135 def __repr__(self):
138 """String representation of Error."""136 """Represent the Error."""
139 return "<{}.{} {}>".format(type(self).__module__, type(self).__name__, self.args)137 return "<{}.{} {}>".format(type(self).__module__, type(self).__name__, self.args)
140138
141 @property139 @property
@@ -212,15 +210,15 @@ class DebianPackage:
212 ) == (other._name, other._version.number)210 ) == (other._name, other._version.number)
213211
214 def __hash__(self):212 def __hash__(self):
215 """A basic hash so this class can be used in Mappings and dicts."""213 """Return a hash of this package."""
216 return hash((self._name, self._version.number))214 return hash((self._name, self._version.number))
217215
218 def __repr__(self):216 def __repr__(self):
219 """A representation of the package."""217 """Represent the package."""
220 return "<{}.{}: {}>".format(self.__module__, self.__class__.__name__, self.__dict__)218 return "<{}.{}: {}>".format(self.__module__, self.__class__.__name__, self.__dict__)
221219
222 def __str__(self):220 def __str__(self):
223 """A human-readable representation of the package."""221 """Return a human-readable representation of the package."""
224 return "<{}: {}-{}.{} -- {}>".format(222 return "<{}: {}-{}.{} -- {}>".format(
225 self.__class__.__name__,223 self.__class__.__name__,
226 self._name,224 self._name,
@@ -250,7 +248,8 @@ class DebianPackage:
250 package_names = [package_names]248 package_names = [package_names]
251 _cmd = ["apt-get", "-y", *optargs, command, *package_names]249 _cmd = ["apt-get", "-y", *optargs, command, *package_names]
252 try:250 try:
253 env = {"DEBIAN_FRONTEND": "noninteractive"}251 env = os.environ.copy()
252 env["DEBIAN_FRONTEND"] = "noninteractive"
254 check_call(_cmd, env=env, stderr=PIPE, stdout=PIPE)253 check_call(_cmd, env=env, stderr=PIPE, stdout=PIPE)
255 except CalledProcessError as e:254 except CalledProcessError as e:
256 raise PackageError(255 raise PackageError(
@@ -266,7 +265,7 @@ class DebianPackage:
266 )265 )
267266
268 def _remove(self) -> None:267 def _remove(self) -> None:
269 """Removes a package from the system. Implementation-specific."""268 """Remove a package from the system. Implementation-specific."""
270 return self._apt("remove", "{}={}".format(self.name, self.version))269 return self._apt("remove", "{}={}".format(self.name, self.version))
271270
272 @property271 @property
@@ -275,7 +274,7 @@ class DebianPackage:
275 return self._name274 return self._name
276275
277 def ensure(self, state: PackageState):276 def ensure(self, state: PackageState):
278 """Ensures that a package is in a given state.277 """Ensure that a package is in a given state.
279278
280 Args:279 Args:
281 state: a `PackageState` to reconcile the package to280 state: a `PackageState` to reconcile the package to
@@ -307,7 +306,7 @@ class DebianPackage:
307306
308 @state.setter307 @state.setter
309 def state(self, state: PackageState) -> None:308 def state(self, state: PackageState) -> None:
310 """Sets the package state to a given value.309 """Set the package state to a given value.
311310
312 Args:311 Args:
313 state: a `PackageState` to reconcile the package to312 state: a `PackageState` to reconcile the package to
@@ -356,7 +355,7 @@ class DebianPackage:
356355
357 Args:356 Args:
358 package: a string representing the package357 package: a string representing the package
359 version: an optional string if a specific version isr equested358 version: an optional string if a specific version is requested
360 arch: an optional architecture, defaulting to `dpkg --print-architecture`. If an359 arch: an optional architecture, defaulting to `dpkg --print-architecture`. If an
361 architecture is not specified, this will be used for selection.360 architecture is not specified, this will be used for selection.
362361
@@ -389,7 +388,7 @@ class DebianPackage:
389388
390 Args:389 Args:
391 package: a string representing the package390 package: a string representing the package
392 version: an optional string if a specific version isr equested391 version: an optional string if a specific version is requested
393 arch: an optional architecture, defaulting to `dpkg --print-architecture`.392 arch: an optional architecture, defaulting to `dpkg --print-architecture`.
394 If an architecture is not specified, this will be used for selection.393 If an architecture is not specified, this will be used for selection.
395 """394 """
@@ -459,7 +458,7 @@ class DebianPackage:
459458
460 Args:459 Args:
461 package: a string representing the package460 package: a string representing the package
462 version: an optional string if a specific version isr equested461 version: an optional string if a specific version is requested
463 arch: an optional architecture, defaulting to `dpkg --print-architecture`.462 arch: an optional architecture, defaulting to `dpkg --print-architecture`.
464 If an architecture is not specified, this will be used for selection.463 If an architecture is not specified, this will be used for selection.
465 """464 """
@@ -515,7 +514,7 @@ class Version:
515 """An abstraction around package versions.514 """An abstraction around package versions.
516515
517 This seems like it should be strictly unnecessary, except that `apt_pkg` is not usable inside a516 This seems like it should be strictly unnecessary, except that `apt_pkg` is not usable inside a
518 venv, and wedging version comparisions into `DebianPackage` would overcomplicate it.517 venv, and wedging version comparisons into `DebianPackage` would overcomplicate it.
519518
520 This class implements the algorithm found here:519 This class implements the algorithm found here:
521 https://www.debian.org/doc/debian-policy/ch-controlfields.html#version520 https://www.debian.org/doc/debian-policy/ch-controlfields.html#version
@@ -526,11 +525,11 @@ class Version:
526 self._epoch = epoch or ""525 self._epoch = epoch or ""
527526
528 def __repr__(self):527 def __repr__(self):
529 """A representation of the package."""528 """Represent the package."""
530 return "<{}.{}: {}>".format(self.__module__, self.__class__.__name__, self.__dict__)529 return "<{}.{}: {}>".format(self.__module__, self.__class__.__name__, self.__dict__)
531530
532 def __str__(self):531 def __str__(self):
533 """A human-readable representation of the package."""532 """Return human-readable representation of the package."""
534 return "{}{}".format("{}:".format(self._epoch) if self._epoch else "", self._version)533 return "{}{}".format("{}:".format(self._epoch) if self._epoch else "", self._version)
535534
536 @property535 @property
@@ -731,13 +730,16 @@ def add_package(
731 """Add a package or list of packages to the system.730 """Add a package or list of packages to the system.
732731
733 Args:732 Args:
733 package_names: single package name, or list of package names
734 name: the name(s) of the package(s)734 name: the name(s) of the package(s)
735 version: an (Optional) version as a string. Defaults to the latest known735 version: an (Optional) version as a string. Defaults to the latest known
736 arch: an optional architecture for the package736 arch: an optional architecture for the package
737 update_cache: whether or not to run `apt-get update` prior to operating737 update_cache: whether or not to run `apt-get update` prior to operating
738738
739 Raises:739 Raises:
740 TypeError if no package name is given, or explicit version is set for multiple packages
740 PackageNotFoundError if the package is not in the cache.741 PackageNotFoundError if the package is not in the cache.
742 PackageError if packages fail to install
741 """743 """
742 cache_refreshed = False744 cache_refreshed = False
743 if update_cache:745 if update_cache:
@@ -785,7 +787,7 @@ def _add(
785 version: Optional[str] = "",787 version: Optional[str] = "",
786 arch: Optional[str] = "",788 arch: Optional[str] = "",
787) -> Tuple[Union[DebianPackage, str], bool]:789) -> Tuple[Union[DebianPackage, str], bool]:
788 """Adds a package.790 """Add a package to the system.
789791
790 Args:792 Args:
791 name: the name(s) of the package(s)793 name: the name(s) of the package(s)
@@ -806,7 +808,7 @@ def _add(
806def remove_package(808def remove_package(
807 package_names: Union[str, List[str]]809 package_names: Union[str, List[str]]
808) -> Union[DebianPackage, List[DebianPackage]]:810) -> Union[DebianPackage, List[DebianPackage]]:
809 """Removes a package from the system.811 """Remove package(s) from the system.
810812
811 Args:813 Args:
812 package_names: the name of a package814 package_names: the name of a package
@@ -834,10 +836,72 @@ def remove_package(
834836
835837
836def update() -> None:838def update() -> None:
837 """Updates the apt cache via `apt-get update`."""839 """Update the apt cache via `apt-get update`."""
838 check_call(["apt-get", "update"], stderr=PIPE, stdout=PIPE)840 check_call(["apt-get", "update"], stderr=PIPE, stdout=PIPE)
839841
840842
843def import_key(key: str) -> str:
844 """Import an ASCII Armor key.
845
846 A Radix64 format keyid is also supported for backwards
847 compatibility. In this case Ubuntu keyserver will be
848 queried for a key via HTTPS by its keyid. This method
849 is less preferable because https proxy servers may
850 require traffic decryption which is equivalent to a
851 man-in-the-middle attack (a proxy server impersonates
852 keyserver TLS certificates and has to be explicitly
853 trusted by the system).
854
855 Args:
856 key: A GPG key in ASCII armor format, including BEGIN
857 and END markers or a keyid.
858
859 Returns:
860 The GPG key filename written.
861
862 Raises:
863 GPGKeyError if the key could not be imported
864 """
865 key = key.strip()
866 if "-" in key or "\n" in key:
867 # Send everything not obviously a keyid to GPG to import, as
868 # we trust its validation better than our own. eg. handling
869 # comments before the key.
870 logger.debug("PGP key found (looks like ASCII Armor format)")
871 if (
872 "-----BEGIN PGP PUBLIC KEY BLOCK-----" in key
873 and "-----END PGP PUBLIC KEY BLOCK-----" in key
874 ):
875 logger.debug("Writing provided PGP key in the binary format")
876 key_bytes = key.encode("utf-8")
877 key_name = DebianRepository._get_keyid_by_gpg_key(key_bytes)
878 key_gpg = DebianRepository._dearmor_gpg_key(key_bytes)
879 gpg_key_filename = "/etc/apt/trusted.gpg.d/{}.gpg".format(key_name)
880 DebianRepository._write_apt_gpg_keyfile(
881 key_name=gpg_key_filename, key_material=key_gpg
882 )
883 return gpg_key_filename
884 else:
885 raise GPGKeyError("ASCII armor markers missing from GPG key")
886 else:
887 logger.warning(
888 "PGP key found (looks like Radix64 format). "
889 "SECURELY importing PGP key from keyserver; "
890 "full key not provided."
891 )
892 # as of bionic add-apt-repository uses curl with an HTTPS keyserver URL
893 # to retrieve GPG keys. `apt-key adv` command is deprecated as is
894 # apt-key in general as noted in its manpage. See lp:1433761 for more
895 # history. Instead, /etc/apt/trusted.gpg.d is used directly to drop
896 # gpg
897 key_asc = DebianRepository._get_key_by_keyid(key)
898 # write the key in GPG format so that apt-key list shows it
899 key_gpg = DebianRepository._dearmor_gpg_key(key_asc.encode("utf-8"))
900 gpg_key_filename = "/etc/apt/trusted.gpg.d/{}.gpg".format(key)
901 DebianRepository._write_apt_gpg_keyfile(key_name=gpg_key_filename, key_material=key_gpg)
902 return gpg_key_filename
903
904
841class InvalidSourceError(Error):905class InvalidSourceError(Error):
842 """Exceptions for invalid source entries."""906 """Exceptions for invalid source entries."""
843907
@@ -901,7 +965,7 @@ class DebianRepository:
901965
902 @filename.setter966 @filename.setter
903 def filename(self, fname: str) -> None:967 def filename(self, fname: str) -> None:
904 """Sets the filename used when a repo is written back to diskself.968 """Set the filename used when a repo is written back to disk.
905969
906 Args:970 Args:
907 fname: a filename to write the repository information to.971 fname: a filename to write the repository information to.
@@ -1004,7 +1068,7 @@ class DebianRepository:
1004 A Radix64 format keyid is also supported for backwards1068 A Radix64 format keyid is also supported for backwards
1005 compatibility. In this case Ubuntu keyserver will be1069 compatibility. In this case Ubuntu keyserver will be
1006 queried for a key via HTTPS by its keyid. This method1070 queried for a key via HTTPS by its keyid. This method
1007 is less preferrable because https proxy servers may1071 is less preferable because https proxy servers may
1008 require traffic decryption which is equivalent to a1072 require traffic decryption which is equivalent to a
1009 man-in-the-middle attack (a proxy server impersonates1073 man-in-the-middle attack (a proxy server impersonates
1010 keyserver TLS certificates and has to be explicitly1074 keyserver TLS certificates and has to be explicitly
@@ -1017,40 +1081,7 @@ class DebianRepository:
1017 Raises:1081 Raises:
1018 GPGKeyError if the key could not be imported1082 GPGKeyError if the key could not be imported
1019 """1083 """
1020 key = key.strip()1084 self._gpg_key_filename = import_key(key)
1021 if "-" in key or "\n" in key:
1022 # Send everything not obviously a keyid to GPG to import, as
1023 # we trust its validation better than our own. eg. handling
1024 # comments before the key.
1025 logger.debug("PGP key found (looks like ASCII Armor format)")
1026 if (
1027 "-----BEGIN PGP PUBLIC KEY BLOCK-----" in key
1028 and "-----END PGP PUBLIC KEY BLOCK-----" in key
1029 ):
1030 logger.debug("Writing provided PGP key in the binary format")
1031 key_bytes = key.encode("utf-8")
1032 key_name = self._get_keyid_by_gpg_key(key_bytes)
1033 key_gpg = self._dearmor_gpg_key(key_bytes)
1034 self._gpg_key_filename = "/etc/apt/trusted.gpg.d/{}.gpg".format(key_name)
1035 self._write_apt_gpg_keyfile(key_name=self._gpg_key_filename, key_material=key_gpg)
1036 else:
1037 raise GPGKeyError("ASCII armor markers missing from GPG key")
1038 else:
1039 logger.warning(
1040 "PGP key found (looks like Radix64 format). "
1041 "SECURELY importing PGP key from keyserver; "
1042 "full key not provided."
1043 )
1044 # as of bionic add-apt-repository uses curl with an HTTPS keyserver URL
1045 # to retrieve GPG keys. `apt-key adv` command is deprecated as is
1046 # apt-key in general as noted in its manpage. See lp:1433761 for more
1047 # history. Instead, /etc/apt/trusted.gpg.d is used directly to drop
1048 # gpg
1049 key_asc = self._get_key_by_keyid(key)
1050 # write the key in GPG format so that apt-key list shows it
1051 key_gpg = self._dearmor_gpg_key(key_asc.encode("utf-8"))
1052 self._gpg_key_filename = "/etc/apt/trusted.gpg.d/{}.gpg".format(key)
1053 self._write_apt_gpg_keyfile(key_name=key, key_material=key_gpg)
10541085
1055 @staticmethod1086 @staticmethod
1056 def _get_keyid_by_gpg_key(key_material: bytes) -> str:1087 def _get_keyid_by_gpg_key(key_material: bytes) -> str:
@@ -1116,7 +1147,7 @@ class DebianRepository:
11161147
1117 @staticmethod1148 @staticmethod
1118 def _dearmor_gpg_key(key_asc: bytes) -> bytes:1149 def _dearmor_gpg_key(key_asc: bytes) -> bytes:
1119 """Converts a GPG key in the ASCII armor format to the binary format.1150 """Convert a GPG key in the ASCII armor format to the binary format.
11201151
1121 Args:1152 Args:
1122 key_asc: A GPG key in ASCII armor format.1153 key_asc: A GPG key in ASCII armor format.
@@ -1140,7 +1171,7 @@ class DebianRepository:
11401171
1141 @staticmethod1172 @staticmethod
1142 def _write_apt_gpg_keyfile(key_name: str, key_material: bytes) -> None:1173 def _write_apt_gpg_keyfile(key_name: str, key_material: bytes) -> None:
1143 """Writes GPG key material into a file at a provided path.1174 """Write GPG key material into a file at a provided path.
11441175
1145 Args:1176 Args:
1146 key_name: A key name to use for a key file (could be a fingerprint)1177 key_name: A key name to use for a key file (could be a fingerprint)
@@ -1188,7 +1219,7 @@ class RepositoryMapping(Mapping):
1188 return len(self._repository_map)1219 return len(self._repository_map)
11891220
1190 def __iter__(self) -> Iterable[DebianRepository]:1221 def __iter__(self) -> Iterable[DebianRepository]:
1191 """Iterator magic method for RepositoryMapping."""1222 """Return iterator for RepositoryMapping."""
1192 return iter(self._repository_map.values())1223 return iter(self._repository_map.values())
11931224
1194 def __getitem__(self, repository_uri: str) -> DebianRepository:1225 def __getitem__(self, repository_uri: str) -> DebianRepository:

Subscribers

People subscribed via source and target branches

to all changes: