Merge ~jfguedez/charm-juju-lint:bug/1943093 into charm-juju-lint:master

Proposed by Jose Guedez
Status: Merged
Approved by: James Troup
Approved revision: 387bf2c9133c82d80ea450bc14960e1c948ba8bf
Merged at revision: f676a63d16d77e6f82e1e6e42e5e9ec18d51308d
Proposed branch: ~jfguedez/charm-juju-lint:bug/1943093
Merge into: charm-juju-lint:master
Diff against target: 316 lines (+49/-124)
6 files modified
lib/lib_jujulint.py (+15/-54)
requirements.txt (+1/-0)
scripts/templates/auto_lint.py (+10/-7)
src/charm.py (+0/-1)
tests/unit/test_auto_lint.py (+3/-3)
tests/unit/test_charm.py (+20/-59)
Reviewer Review Type Date Requested Status
BootStack Reviewers Pending
BootStack Reviewers Pending
Review via email: mp+408341@code.launchpad.net

Commit message

Remove requirement on pypi repositories for installation

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision f676a63d16d77e6f82e1e6e42e5e9ec18d51308d

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lib_jujulint.py b/lib/lib_jujulint.py
2index 69a20fb..bce0fb4 100644
3--- a/lib/lib_jujulint.py
4+++ b/lib/lib_jujulint.py
5@@ -15,7 +15,6 @@ from charmhelpers.contrib.charmsupport.nrpe import NRPE
6 from charmhelpers.core import hookenv
7 from charmhelpers.core.host import rsync
8 from charmhelpers.fetch import snap
9-from charmhelpers.fetch import apt_install
10 from ops.model import (
11 ActiveStatus,
12 MaintenanceStatus,
13@@ -33,7 +32,6 @@ class Paths:
14 AUTO_LINT_SCRIPT_PATH = USR_LOCAL / "bin/auto_lint.py"
15 AUTO_LINT_CRONTAB_PATH = pathlib.Path("/etc/cron.d/juju-lint")
16 NAGIOS_PLUGINS_DIR = USR_LOCAL / "lib/nagios/plugins/"
17- VENV_DIR = pathlib.Path("/opt/juju_venvs/jujulint")
18
19
20 CHECK_SHORTNAME = "juju_lint_results"
21@@ -82,7 +80,21 @@ class JujuLintHelper:
22 """
23 charm_dir = pathlib.Path(hookenv.charm_dir())
24 scripts_dir = charm_dir / "scripts"
25- rsync(str(scripts_dir / "auto_lint.py"), str(Paths.AUTO_LINT_SCRIPT_PATH))
26+ templates_dir = scripts_dir / "templates"
27+
28+ # Create the auto_lint.py script from the template with the right permissions
29+ with open(str(templates_dir / "auto_lint.py")) as f:
30+ auto_lint_template = f.read()
31+
32+ auto_lint_script = auto_lint_template.replace(
33+ "REPLACE_CHARMDIR", str(charm_dir)
34+ )
35+
36+ fd = os.open(str(Paths.AUTO_LINT_SCRIPT_PATH), os.O_CREAT | os.O_WRONLY, 0o755)
37+
38+ with open(fd, "w") as f:
39+ f.write(auto_lint_script)
40+
41 # install all files in scripts/plugins/ into nagios plugins dir
42 plugins_dir = scripts_dir / "plugins"
43 # Note that we need to add 'options' here to remove the flag '--delete' that
44@@ -133,57 +145,6 @@ class JujuLintHelper:
45 with Paths.AUTO_LINT_CONFIG_PATH.open("w") as fp:
46 json.dump(dict(self.charm_config), fp, indent=4) # cast from ConfigData
47
48- def create_venv(self):
49- """Create a virtual environment for the auto-lint script."""
50- apt_install(["python3-venv"])
51- subprocess.check_call(["python3", "-m", "venv", str(Paths.VENV_DIR)])
52- self._pip_install_venv("pip", upgrade=True)
53- self._pip_install_venv("juju", "pyyaml", upgrade=True)
54-
55- @staticmethod
56- def _pip_install_venv(*pkgs, upgrade=False):
57- """Install packages in a virtual environment."""
58- # This charm originally used charmhelpers for this. However the ops
59- # framework runs the hooks with a modified PYTHONPATH already, which
60- # includes pyyaml. This caused the pip installation to skip pyyaml as
61- # it's considered already installed
62- # see the following bug reports for details:
63- # * https://bugs.launchpad.net/charm-juju-lint/+bug/1928337
64- # * https://github.com/juju/charm-helpers/issues/607
65- command = [str(Paths.VENV_DIR / "bin/pip"), "install"]
66-
67- if upgrade:
68- command.append("--upgrade")
69-
70- command.extend(pkgs)
71- logging.debug("installing packages using: {}".format(command))
72-
73- # drop PYTHONPATH if present
74- env = os.environ.copy()
75- if "PYTHONPATH" in env:
76- env.pop("PYTHONPATH")
77-
78- # handle juju special proxy environment variables: JUJU_CHARM_*_PROXY
79- # this is not handled in charmhelpers, see the following resources
80- # for details and overall references on the issue:
81- # * https://discourse.charmhub.io/t/command-model-config/1768
82- # * https://github.com/juju/charm-helpers/issues/614
83- # * https://github.com/juju/charm-helpers/pull/248/
84- # * https://bugs.launchpad.net/charm-octavia-diskimage-retrofit/+bug/1843510
85- proxy_envvars = hookenv.env_proxy_settings()
86- logging.debug("proxy_envvars: {}".format(proxy_envvars))
87-
88- if proxy_envvars is not None:
89- env.update(proxy_envvars)
90-
91- try:
92- pip_output = subprocess.check_output(command, env=env).decode("utf8")
93- except subprocess.CalledProcessError as error:
94- logging.error("pip install failed:\n{}".format(error.output.decode("utf8")))
95- raise error
96-
97- logging.debug("pip install succeeded:\n{}".format(pip_output))
98-
99 @staticmethod
100 def run_auto_lint():
101 """Run auto lint."""
102diff --git a/requirements.txt b/requirements.txt
103index b12b148..6093c4e 100644
104--- a/requirements.txt
105+++ b/requirements.txt
106@@ -1,2 +1,3 @@
107 charmhelpers
108+juju
109 ops
110diff --git a/scripts/auto_lint.py b/scripts/templates/auto_lint.py
111similarity index 93%
112rename from scripts/auto_lint.py
113rename to scripts/templates/auto_lint.py
114index cbc4389..9476682 100755
115--- a/scripts/auto_lint.py
116+++ b/scripts/templates/auto_lint.py
117@@ -1,4 +1,4 @@
118-#!/opt/juju_venvs/jujulint/bin/python
119+#!/usr/bin/env python3
120
121 """
122 Automatically grab `juju status` output and analyse it with Juju Lint.
123@@ -26,11 +26,14 @@ import logging
124 from os.path import join
125 import os
126 import subprocess
127-from sys import exit
128+import sys
129
130-from juju import loop
131-from juju.model import Model
132-from juju.client.facade import TypeEncoder
133+# The path below is templated in during charm install
134+sys.path.append("REPLACE_CHARMDIR/venv")
135+
136+from juju import loop # noqa E402
137+from juju.model import Model # noqa E402
138+from juju.client.facade import TypeEncoder # noqa E402
139
140 VAR_LIB = "/var/snap/juju-lint/common"
141 LINT_CONFIG_PATH = join(VAR_LIB, "auto-lint-config.json")
142@@ -65,7 +68,7 @@ def verify_auto_lint_config(auto_lint_config):
143 )
144 for option in required:
145 if not auto_lint_config[option]:
146- exit("Juju Lint: Please set model connection config values.")
147+ sys.exit("Juju Lint: Please set model connection config values.")
148
149
150 async def get_juju_status(auto_lint_config):
151@@ -250,7 +253,7 @@ def main():
152 pid = str(os.getpid())
153
154 if os.path.isfile(PID_FILENAME):
155- exit("{} already exists, exiting".format(PID_FILENAME))
156+ sys.exit("{} already exists, exiting".format(PID_FILENAME))
157
158 with open(PID_FILENAME, "w") as f:
159 f.write(pid)
160diff --git a/src/charm.py b/src/charm.py
161index cc6b77e..2b8b543 100755
162--- a/src/charm.py
163+++ b/src/charm.py
164@@ -48,7 +48,6 @@ class JujuLintCharm(CharmBase):
165 def on_install(self, event):
166 """Install charm and perform initial configuration."""
167 self.helper.install_snap(event)
168- self.helper.create_venv()
169 self.helper.create_install_dirs()
170 self.helper.deploy_scripts()
171 self.helper.update_config()
172diff --git a/tests/unit/test_auto_lint.py b/tests/unit/test_auto_lint.py
173index 00189ae..edbed7c 100644
174--- a/tests/unit/test_auto_lint.py
175+++ b/tests/unit/test_auto_lint.py
176@@ -4,7 +4,7 @@ import unittest
177
178 import mock
179 from juju import loop
180-from scripts.auto_lint import (
181+from scripts.templates.auto_lint import (
182 add_charm_config_to_juju_status,
183 lint_juju,
184 process_juju_status,
185@@ -117,8 +117,8 @@ class TestAutoLint(unittest.TestCase):
186
187 self.assertRaises(KeyError, test_helper)
188
189- @mock.patch("scripts.auto_lint.write_file")
190- @mock.patch("scripts.auto_lint.subprocess.check_output")
191+ @mock.patch("scripts.templates.auto_lint.write_file")
192+ @mock.patch("scripts.templates.auto_lint.subprocess.check_output")
193 def test_nrpe_split_json(self, mock_check_output, mock_write_file):
194 """Check that juju-lint outputs json when running in split checks mode."""
195 auto_lint_config = {
196diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
197index aff54f8..2eec635 100644
198--- a/tests/unit/test_charm.py
199+++ b/tests/unit/test_charm.py
200@@ -5,12 +5,13 @@ import pathlib
201 import tempfile
202 import unittest
203
204-from ops.testing import Harness
205+from charmhelpers.core.hookenv import charm_dir
206 import mock
207+from ops.testing import Harness
208 import yaml
209
210 from src.charm import JujuLintCharm
211-from lib_jujulint import Paths, JujuLintHelper
212+from lib_jujulint import Paths
213
214
215 def reparent(pathsobj, name):
216@@ -90,10 +91,14 @@ class TestCharm(unittest.TestCase):
217 self.assertFalse(self.harness.charm.state.installed)
218
219 @mock.patch("charmhelpers.fetch.snap.snap_install")
220+ @mock.patch("charmhelpers.core.hookenv.charm_dir", wraps=charm_dir)
221 @mock.patch("lib_jujulint.subprocess")
222- @mock.patch("lib_jujulint.apt_install")
223- def test_05_install(self, mock_apt_install, mock_subprocess, mock_snap_install):
224+ def test_05_install(self, mock_subprocess, mock_charm_dir, mock_snap_install):
225 """Test: charm install."""
226+ tmp = pathlib.Path(self.tmpdir.name)
227+ charm_dir_abs = os.path.abspath(charm_dir())
228+ mock_charm_dir.return_value = charm_dir_abs
229+
230 self.harness.begin()
231 self.harness.charm.on.install.emit()
232 self.assertEqual(self.harness.charm.unit.status.name, "active")
233@@ -107,11 +112,21 @@ class TestCharm(unittest.TestCase):
234 "usr/local/lib/nagios/plugins/check_juju_lint_results.py",
235 "var/snap/juju-lint/common/auto-lint-config.json",
236 ]
237- tmp = pathlib.Path(self.tmpdir.name)
238+
239 for expect in expected_files:
240 p = tmp / expect
241 self.assertTrue(p.is_file())
242
243+ # Test that auto_lint.py was templated correctly
244+ with open(str(tmp / "usr/local/bin/auto_lint.py")) as f:
245+ auto_lint_script = f.read()
246+
247+ self.assertIn(
248+ "{}/venv".format(charm_dir_abs),
249+ auto_lint_script,
250+ )
251+ self.assertNotIn("REPLACE_CHARMDIR", auto_lint_script)
252+
253 def test_09_config_changed_defer(self):
254 """Test: config changed event is deferred if charm not installed."""
255 self.harness.begin()
256@@ -230,60 +245,6 @@ class TestCharm(unittest.TestCase):
257 {"juju_lint_results-misc", "juju_lint_results-config"},
258 )
259
260- @mock.patch("lib_jujulint.os")
261- @mock.patch("lib_jujulint.subprocess")
262- def test_25_pip_install_env(self, mock_subprocess, mock_os):
263- """Test: pip install function."""
264- # make sure the environment has a PYTHONPATH variable
265- mock_os.environ.copy.return_value = {"PYTHONPATH": "lib:venv"}
266-
267- # get the full path to pip within the tempdir for the test
268- tmp_pip_path = str(pathlib.Path(self.tmpdir.name) / Paths.VENV_DIR / "bin/pip")
269-
270- # generate the expected inputs to subprocess.check_output
271- expected_command = [tmp_pip_path, "install", "pyyaml"]
272- expected_env = {}
273-
274- # invoke the package install and validate the inputs
275- JujuLintHelper._pip_install_venv("pyyaml")
276- mock_subprocess.check_output.assert_called_with(
277- expected_command, env=expected_env
278- )
279-
280- # test the upgrade flag
281- expected_command = [tmp_pip_path, "install", "--upgrade", "pip"]
282- JujuLintHelper._pip_install_venv("pip", upgrade=True)
283- mock_subprocess.check_output.assert_called_with(
284- expected_command, env=expected_env
285- )
286-
287- @mock.patch("lib_jujulint.os")
288- @mock.patch("lib_jujulint.subprocess")
289- @mock.patch("charmhelpers.core.hookenv.env_proxy_settings")
290- def test_26_pip_install_proxy_vars(
291- self, mock_env_proxy_settings, mock_subprocess, mock_os
292- ):
293- """Test: handling of special juju proxy variables."""
294- mock_os.environ.copy.return_value = {
295- "HTTPS_PROXY": "https://invalid.proxy:3128"
296- }
297- mock_env_proxy_settings.return_value = {
298- "HTTPS_PROXY": "https://squid.internals:3128"
299- }
300-
301- # get the full path to pip within the tempdir for the test
302- tmp_pip_path = str(pathlib.Path(self.tmpdir.name) / Paths.VENV_DIR / "bin/pip")
303-
304- # generate the expected inputs to subprocess.check_output
305- expected_command = [tmp_pip_path, "install", "pyyaml"]
306- expected_env = {"HTTPS_PROXY": "https://squid.internals:3128"}
307-
308- # invoke the package install and validate the inputs
309- JujuLintHelper._pip_install_venv("pyyaml")
310- mock_subprocess.check_output.assert_called_with(
311- expected_command, env=expected_env
312- )
313-
314 def get_notice_count(self, hook):
315 """Return the notice count for a given charm hook."""
316 notice_count = 0

Subscribers

People subscribed via source and target branches

to all changes: