Merge ~jfguedez/juju-lint:bug/1897262 into juju-lint:master

Proposed by Jose Guedez
Status: Merged
Approved by: James Troup
Approved revision: cb3ab926ebcc671d04d9226f7febaacc7f4e4917
Merged at revision: 4c8331f73b5fdc1927b30a265be8c88ac0e07426
Proposed branch: ~jfguedez/juju-lint:bug/1897262
Merge into: juju-lint:master
Diff against target: 195 lines (+130/-10)
3 files modified
contrib/canonical-rules.yaml (+8/-0)
jujulint/lint.py (+50/-10)
tests/test_jujulint.py (+72/-0)
Reviewer Review Type Date Requested Status
Juju Lint maintainers Pending
Review via email: mp+408597@code.launchpad.net

Commit message

Add support for CMR applications (SAAS)

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 4c8331f73b5fdc1927b30a265be8c88ac0e07426

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/contrib/canonical-rules.yaml b/contrib/canonical-rules.yaml
2index 74b7c21..6c1a101 100644
3--- a/contrib/canonical-rules.yaml
4+++ b/contrib/canonical-rules.yaml
5@@ -260,3 +260,11 @@ known charms:
6 - *openstack-charms
7 - *operations-charms
8 - *kubernetes-charms
9+
10+saas:
11+ - elasticsearch
12+ - grafana
13+ - graylog
14+ - landscape-server
15+ - nagios
16+ - prometheus2
17diff --git a/jujulint/lint.py b/jujulint/lint.py
18index 5328723..6564ab3 100755
19--- a/jujulint/lint.py
20+++ b/jujulint/lint.py
21@@ -63,6 +63,7 @@ class ModelInfo(object):
22 """Represent information obtained from juju status data."""
23
24 charms = attrib(default=attr.Factory(set))
25+ cmr_apps = attrib(default=attr.Factory(set))
26 app_to_charm = attrib(default=attr.Factory(dict))
27 subs_on_machines = attrib(default=attr.Factory(dict))
28 apps_on_machines = attrib(default=attr.Factory(dict))
29@@ -668,6 +669,31 @@ class Linter:
30 if not self.model.extraneous_subs[sub]:
31 del self.model.extraneous_subs[sub]
32
33+ def check_charms_ops_mandatory(self, charm):
34+ """
35+ Check if a mandatory ops charms is present in the model.
36+
37+ First check if the charm is installed in the model, if not
38+ then check the CMRs. If no errors, returns None
39+ """
40+ if charm in self.model.charms:
41+ return None
42+
43+ rules_saas = self.lint_rules.get("saas", {})
44+ for cmr_app in self.model.cmr_apps:
45+ # the remote might not be called exactly as the charm, e.g. prometheus
46+ # or prometheus2, so we naively check the beginning for a start.
47+ if charm in rules_saas and charm.startswith(cmr_app):
48+ return None
49+
50+ return {
51+ "id": "ops-charm-missing",
52+ "tags": ["missing", "ops", "charm", "mandatory", "principal"],
53+ "description": "An Ops charm is missing",
54+ "charm": charm,
55+ "message": "Ops charm '{}' is missing".format(charm),
56+ }
57+
58 def check_charms(self):
59 """Check we recognise the charms which are in the model."""
60 for charm in self.model.charms:
61@@ -683,16 +709,9 @@ class Linter:
62 )
63 # Then look for charms we require
64 for charm in self.lint_rules["operations mandatory"]:
65- if charm not in self.model.charms:
66- self.handle_error(
67- {
68- "id": "ops-charm-missing",
69- "tags": ["missing", "ops", "charm", "mandatory", "principal"],
70- "description": "An Ops charm is missing",
71- "charm": charm,
72- "message": "Ops charm '{}' is missing".format(charm),
73- }
74- )
75+ error = self.check_charms_ops_mandatory(charm)
76+ if error:
77+ self.handle_error(error)
78 if self.cloud_type == "openstack":
79 for charm in self.lint_rules["openstack mandatory"]:
80 if charm not in self.model.charms:
81@@ -861,6 +880,24 @@ class Linter:
82 }
83 )
84
85+ def parse_cmr_apps(self, parsed_yaml):
86+ """Parse the apps from cross-model relations."""
87+ cmr_keys = (
88+ "saas", # Pattern used by juju export-bundle
89+ "application-endpoints", # Pattern used by jsfy
90+ "remote-applications", # Pattern used by libjuju (charm-lint-juju)
91+ )
92+
93+ for key in cmr_keys:
94+ if key in parsed_yaml:
95+ for name in parsed_yaml[key]:
96+ self.model.cmr_apps.add(name)
97+
98+ # Handle the special case of dependencies for graylog
99+ if name.startswith("graylog"):
100+ self.model.cmr_apps.add("elasticsearch")
101+ return
102+
103 def map_machines_to_az(self, machines):
104 """Map machines in the model to their availability zone."""
105 for machine in machines:
106@@ -1076,6 +1113,9 @@ class Linter:
107 # Build a list of deployed charms and mapping of charms <-> applications
108 self.map_charms(parsed_yaml[applications])
109
110+ # Parse SAAS / remote-applications
111+ self.parse_cmr_apps(parsed_yaml)
112+
113 # Check configuration
114 self.check_configuration(parsed_yaml[applications])
115
116diff --git a/tests/test_jujulint.py b/tests/test_jujulint.py
117index cd41c58..78555ce 100644
118--- a/tests/test_jujulint.py
119+++ b/tests/test_jujulint.py
120@@ -353,3 +353,75 @@ class TestLinter:
121 assert errors[0]["id"] == "config-isset-check-true"
122 assert errors[0]["application"] == "ubuntu"
123 assert errors[0]["rule"] == "fake-opt"
124+
125+ def test_parse_cmr_apps_export_bundle(self, linter):
126+ """Test the charm CMR parsing for bundles."""
127+ parsed_yaml = {
128+ "saas": {
129+ "grafana": {"url": "foundations-maas:admin/lma.grafana"},
130+ "nagios": {"url": "foundations-maas:admin/lma.nagios-monitors"},
131+ }
132+ }
133+ linter.parse_cmr_apps(parsed_yaml)
134+ assert linter.model.cmr_apps == {"grafana", "nagios"}
135+
136+ def test_parse_cmr_apps_jsfy(self, linter):
137+ """Test the charm CMR parsing for juju status."""
138+ parsed_yaml = {
139+ "application-endpoints": {
140+ "grafana": {"url": "foundations-maas:admin/lma.grafana"},
141+ "nagios": {"url": "foundations-maas:admin/lma.nagios-monitors"},
142+ }
143+ }
144+ linter.parse_cmr_apps(parsed_yaml)
145+ assert linter.model.cmr_apps == {"grafana", "nagios"}
146+
147+ def test_parse_cmr_apps_libjuju(self, linter):
148+ """Test the charm CMR parsing for libjuju."""
149+ parsed_yaml = {
150+ "remote-applications": {
151+ "grafana": {"url": "foundations-maas:admin/lma.grafana"},
152+ "nagios": {"url": "foundations-maas:admin/lma.nagios-monitors"},
153+ }
154+ }
155+ linter.parse_cmr_apps(parsed_yaml)
156+ assert linter.model.cmr_apps == {"grafana", "nagios"}
157+
158+ def test_check_charms_ops_mandatory_crm_success(self, linter):
159+ """
160+ Test the logic for checking ops mandatory charms provided via CMR.
161+
162+ The app is in the saas rules and has a CMR, no error is expected
163+ """
164+ linter.lint_rules["saas"] = ["grafana"]
165+ linter.model.cmr_apps.add("grafana")
166+ error = linter.check_charms_ops_mandatory("grafana")
167+
168+ assert error is None
169+
170+ def test_check_charms_ops_mandatory_crm_fail1(self, linter):
171+ """
172+ Test the logic for checking ops mandatory charms provided via CMR.
173+
174+ The app is not in the saas rules, should report an error
175+ """
176+ linter.model.cmr_apps.add("grafana")
177+ error = linter.check_charms_ops_mandatory("grafana")
178+
179+ # The app is not in the rules, should report an error
180+ assert error is not None
181+ assert error["id"] == "ops-charm-missing"
182+ assert error["charm"] == "grafana"
183+
184+ def test_check_charms_ops_mandatory_crm_fail2(self, linter):
185+ """
186+ Test the logic for checking ops mandatory charms provided via CMR.
187+
188+ The app is in the saas rules, but no CMR in place, should report error
189+ """
190+ linter.lint_rules["saas"] = ["grafana"]
191+ error = linter.check_charms_ops_mandatory("grafana")
192+
193+ assert error is not None
194+ assert error["id"] == "ops-charm-missing"
195+ assert error["charm"] == "grafana"

Subscribers

People subscribed via source and target branches