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

Subscribers

People subscribed via source and target branches

to all changes: