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
diff --git a/contrib/canonical-rules.yaml b/contrib/canonical-rules.yaml
index 74b7c21..6c1a101 100644
--- a/contrib/canonical-rules.yaml
+++ b/contrib/canonical-rules.yaml
@@ -260,3 +260,11 @@ known charms:
260 - *openstack-charms260 - *openstack-charms
261 - *operations-charms261 - *operations-charms
262 - *kubernetes-charms262 - *kubernetes-charms
263
264saas:
265 - elasticsearch
266 - grafana
267 - graylog
268 - landscape-server
269 - nagios
270 - prometheus2
diff --git a/jujulint/lint.py b/jujulint/lint.py
index 5328723..6564ab3 100755
--- a/jujulint/lint.py
+++ b/jujulint/lint.py
@@ -63,6 +63,7 @@ class ModelInfo(object):
63 """Represent information obtained from juju status data."""63 """Represent information obtained from juju status data."""
6464
65 charms = attrib(default=attr.Factory(set))65 charms = attrib(default=attr.Factory(set))
66 cmr_apps = attrib(default=attr.Factory(set))
66 app_to_charm = attrib(default=attr.Factory(dict))67 app_to_charm = attrib(default=attr.Factory(dict))
67 subs_on_machines = attrib(default=attr.Factory(dict))68 subs_on_machines = attrib(default=attr.Factory(dict))
68 apps_on_machines = attrib(default=attr.Factory(dict))69 apps_on_machines = attrib(default=attr.Factory(dict))
@@ -668,6 +669,31 @@ class Linter:
668 if not self.model.extraneous_subs[sub]:669 if not self.model.extraneous_subs[sub]:
669 del self.model.extraneous_subs[sub]670 del self.model.extraneous_subs[sub]
670671
672 def check_charms_ops_mandatory(self, charm):
673 """
674 Check if a mandatory ops charms is present in the model.
675
676 First check if the charm is installed in the model, if not
677 then check the CMRs. If no errors, returns None
678 """
679 if charm in self.model.charms:
680 return None
681
682 rules_saas = self.lint_rules.get("saas", {})
683 for cmr_app in self.model.cmr_apps:
684 # the remote might not be called exactly as the charm, e.g. prometheus
685 # or prometheus2, so we naively check the beginning for a start.
686 if charm in rules_saas and charm.startswith(cmr_app):
687 return None
688
689 return {
690 "id": "ops-charm-missing",
691 "tags": ["missing", "ops", "charm", "mandatory", "principal"],
692 "description": "An Ops charm is missing",
693 "charm": charm,
694 "message": "Ops charm '{}' is missing".format(charm),
695 }
696
671 def check_charms(self):697 def check_charms(self):
672 """Check we recognise the charms which are in the model."""698 """Check we recognise the charms which are in the model."""
673 for charm in self.model.charms:699 for charm in self.model.charms:
@@ -683,16 +709,9 @@ class Linter:
683 )709 )
684 # Then look for charms we require710 # Then look for charms we require
685 for charm in self.lint_rules["operations mandatory"]:711 for charm in self.lint_rules["operations mandatory"]:
686 if charm not in self.model.charms:712 error = self.check_charms_ops_mandatory(charm)
687 self.handle_error(713 if error:
688 {714 self.handle_error(error)
689 "id": "ops-charm-missing",
690 "tags": ["missing", "ops", "charm", "mandatory", "principal"],
691 "description": "An Ops charm is missing",
692 "charm": charm,
693 "message": "Ops charm '{}' is missing".format(charm),
694 }
695 )
696 if self.cloud_type == "openstack":715 if self.cloud_type == "openstack":
697 for charm in self.lint_rules["openstack mandatory"]:716 for charm in self.lint_rules["openstack mandatory"]:
698 if charm not in self.model.charms:717 if charm not in self.model.charms:
@@ -861,6 +880,24 @@ class Linter:
861 }880 }
862 )881 )
863882
883 def parse_cmr_apps(self, parsed_yaml):
884 """Parse the apps from cross-model relations."""
885 cmr_keys = (
886 "saas", # Pattern used by juju export-bundle
887 "application-endpoints", # Pattern used by jsfy
888 "remote-applications", # Pattern used by libjuju (charm-lint-juju)
889 )
890
891 for key in cmr_keys:
892 if key in parsed_yaml:
893 for name in parsed_yaml[key]:
894 self.model.cmr_apps.add(name)
895
896 # Handle the special case of dependencies for graylog
897 if name.startswith("graylog"):
898 self.model.cmr_apps.add("elasticsearch")
899 return
900
864 def map_machines_to_az(self, machines):901 def map_machines_to_az(self, machines):
865 """Map machines in the model to their availability zone."""902 """Map machines in the model to their availability zone."""
866 for machine in machines:903 for machine in machines:
@@ -1076,6 +1113,9 @@ class Linter:
1076 # Build a list of deployed charms and mapping of charms <-> applications1113 # Build a list of deployed charms and mapping of charms <-> applications
1077 self.map_charms(parsed_yaml[applications])1114 self.map_charms(parsed_yaml[applications])
10781115
1116 # Parse SAAS / remote-applications
1117 self.parse_cmr_apps(parsed_yaml)
1118
1079 # Check configuration1119 # Check configuration
1080 self.check_configuration(parsed_yaml[applications])1120 self.check_configuration(parsed_yaml[applications])
10811121
diff --git a/tests/test_jujulint.py b/tests/test_jujulint.py
index cd41c58..78555ce 100644
--- a/tests/test_jujulint.py
+++ b/tests/test_jujulint.py
@@ -353,3 +353,75 @@ class TestLinter:
353 assert errors[0]["id"] == "config-isset-check-true"353 assert errors[0]["id"] == "config-isset-check-true"
354 assert errors[0]["application"] == "ubuntu"354 assert errors[0]["application"] == "ubuntu"
355 assert errors[0]["rule"] == "fake-opt"355 assert errors[0]["rule"] == "fake-opt"
356
357 def test_parse_cmr_apps_export_bundle(self, linter):
358 """Test the charm CMR parsing for bundles."""
359 parsed_yaml = {
360 "saas": {
361 "grafana": {"url": "foundations-maas:admin/lma.grafana"},
362 "nagios": {"url": "foundations-maas:admin/lma.nagios-monitors"},
363 }
364 }
365 linter.parse_cmr_apps(parsed_yaml)
366 assert linter.model.cmr_apps == {"grafana", "nagios"}
367
368 def test_parse_cmr_apps_jsfy(self, linter):
369 """Test the charm CMR parsing for juju status."""
370 parsed_yaml = {
371 "application-endpoints": {
372 "grafana": {"url": "foundations-maas:admin/lma.grafana"},
373 "nagios": {"url": "foundations-maas:admin/lma.nagios-monitors"},
374 }
375 }
376 linter.parse_cmr_apps(parsed_yaml)
377 assert linter.model.cmr_apps == {"grafana", "nagios"}
378
379 def test_parse_cmr_apps_libjuju(self, linter):
380 """Test the charm CMR parsing for libjuju."""
381 parsed_yaml = {
382 "remote-applications": {
383 "grafana": {"url": "foundations-maas:admin/lma.grafana"},
384 "nagios": {"url": "foundations-maas:admin/lma.nagios-monitors"},
385 }
386 }
387 linter.parse_cmr_apps(parsed_yaml)
388 assert linter.model.cmr_apps == {"grafana", "nagios"}
389
390 def test_check_charms_ops_mandatory_crm_success(self, linter):
391 """
392 Test the logic for checking ops mandatory charms provided via CMR.
393
394 The app is in the saas rules and has a CMR, no error is expected
395 """
396 linter.lint_rules["saas"] = ["grafana"]
397 linter.model.cmr_apps.add("grafana")
398 error = linter.check_charms_ops_mandatory("grafana")
399
400 assert error is None
401
402 def test_check_charms_ops_mandatory_crm_fail1(self, linter):
403 """
404 Test the logic for checking ops mandatory charms provided via CMR.
405
406 The app is not in the saas rules, should report an error
407 """
408 linter.model.cmr_apps.add("grafana")
409 error = linter.check_charms_ops_mandatory("grafana")
410
411 # The app is not in the rules, should report an error
412 assert error is not None
413 assert error["id"] == "ops-charm-missing"
414 assert error["charm"] == "grafana"
415
416 def test_check_charms_ops_mandatory_crm_fail2(self, linter):
417 """
418 Test the logic for checking ops mandatory charms provided via CMR.
419
420 The app is in the saas rules, but no CMR in place, should report error
421 """
422 linter.lint_rules["saas"] = ["grafana"]
423 error = linter.check_charms_ops_mandatory("grafana")
424
425 assert error is not None
426 assert error["id"] == "ops-charm-missing"
427 assert error["charm"] == "grafana"

Subscribers

People subscribed via source and target branches