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

Proposed by Jose Guedez
Status: Merged
Approved by: James Troup
Approved revision: 31d208a66a156e1f279f9d8026840b9d2de1a097
Merged at revision: 737b1c02c3a8cbfe2100d9f7d8f275e53c34d9e5
Proposed branch: ~jfguedez/juju-lint:bug/1896551
Merge into: juju-lint:master
Diff against target: 611 lines (+427/-7)
22 files modified
README.md (+3/-2)
contrib/canonical-rules.yaml (+1/-0)
contrib/includes/aggregator-openstack.yaml (+20/-0)
contrib/includes/base.yaml (+40/-0)
contrib/includes/database/mysql.yaml (+10/-0)
contrib/includes/database/percona-cluster.yaml (+9/-0)
contrib/includes/kubernetes.yaml (+24/-0)
contrib/includes/networking/ovn.yaml (+9/-0)
contrib/includes/networking/ovs.yaml (+4/-0)
contrib/includes/openstack.yaml (+105/-0)
contrib/includes/operations.yaml (+49/-0)
contrib/includes/operations/bionic.yaml (+2/-0)
contrib/includes/operations/focal.yaml (+1/-0)
contrib/includes/saas.yaml (+7/-0)
contrib/kubernetes.yaml (+22/-0)
contrib/openstack-bionic-ovn.yaml (+13/-0)
contrib/openstack-bionic-ovs.yaml (+13/-0)
contrib/openstack-focal-ovn.yaml (+13/-0)
contrib/openstack-focal-ovs.yaml (+13/-0)
jujulint/lint.py (+38/-5)
jujulint/util.py (+4/-0)
tests/test_jujulint.py (+27/-0)
Reviewer Review Type Date Requested Status
Juju Lint maintainers Pending
Review via email: mp+408765@code.launchpad.net

Commit message

Add support for includes, as well as several reference rules files

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 :

Merge proposal is approved, but source revision has changed, setting status to needs review.

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

Change successfully merged at revision 737b1c02c3a8cbfe2100d9f7d8f275e53c34d9e5

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/README.md b/README.md
2index 00d737d..d7ef4c7 100644
3--- a/README.md
4+++ b/README.md
5@@ -2,7 +2,7 @@
6
7 == Introduction ==
8
9-This is intended to be run against a yaml dump of Juju status, a YAML dump of
10+This is intended to be run against a yaml dump of Juju status, a YAML dump of
11 a juju bundle or a remote cloud or clouds via SSH.
12
13 To generate a status if you just want to audit placement:
14@@ -49,7 +49,8 @@ Supported top-level options for your rules file:
15 3. `operations [mandatory|optional|subordinate]`
16 4. `openstack [mandatory|optional|subordinate]`
17 5. `config` - application configuration auditing
18- 5. `[openstack|kubernetes] config` - config auditing for specific cloud types.
19+ 6. `[openstack|kubernetes] config` - config auditing for specific cloud types.
20+ 7. `!include <relative path>` - Extension to yaml to include files.
21
22 == License ==
23
24diff --git a/contrib/canonical-rules.yaml b/contrib/canonical-rules.yaml
25index 6c1a101..b655f34 100644
26--- a/contrib/canonical-rules.yaml
27+++ b/contrib/canonical-rules.yaml
28@@ -1,4 +1,5 @@
29 ---
30+# legacy rules file, there are also more specific configurations available
31 kubernetes config:
32 kubernetes-master:
33 authorization-mode:
34diff --git a/contrib/includes/aggregator-openstack.yaml b/contrib/includes/aggregator-openstack.yaml
35new file mode 100644
36index 0000000..6982a91
37--- /dev/null
38+++ b/contrib/includes/aggregator-openstack.yaml
39@@ -0,0 +1,20 @@
40+operations charms: &operations-charms
41+ - *operations-mandatory-charms
42+ - *operations-mandatory-deps
43+ - *operations-mandatory-subs
44+ - *operations-optional-charms
45+ - *operations-optional-subs
46+ - *operations-openstack-mandatory-charms
47+
48+openstack charms: &openstack-charms
49+ - *openstack-mandatory-charms
50+ - *openstack-mandatory-deps
51+ - *openstack-mandatory-subs
52+ - *openstack-optional-charms
53+ - *cisco-aci-charms
54+ - *trilio-charms
55+
56+known charms:
57+ - ubuntu
58+ - *openstack-charms
59+ - *operations-charms
60diff --git a/contrib/includes/base.yaml b/contrib/includes/base.yaml
61new file mode 100644
62index 0000000..1815ee2
63--- /dev/null
64+++ b/contrib/includes/base.yaml
65@@ -0,0 +1,40 @@
66+config:
67+ hacluster:
68+ cluster_count:
69+ gte: 3
70+ ntp:
71+ auto_peers:
72+ eq: false
73+ nrpe:
74+ lacp_bonds:
75+ neq: ""
76+ netlinks:
77+ neq: ""
78+ landscape-client:
79+ disable-unattended-upgrades:
80+ eq: true
81+
82+subordinates:
83+ telegraf:
84+ where: all except prometheus
85+ # and prometheus-ceph-exporter and prometheus-openstack-exporter
86+ landscape-client:
87+ where: all except landscape-server
88+ filebeat:
89+ where: all except graylog
90+ canonical-livepatch:
91+ where: host only
92+ nrpe:
93+ where: container aware
94+ host-suffixes: [host, physical, guest]
95+ container-suffixes: [lxd, container]
96+ exceptions: [nagios]
97+ ntp:
98+ # You don't want NTP in a container dueling with ntp in the host
99+ where: host only
100+ thruk-agent:
101+ where: on nagios
102+ hw-health:
103+ where: host only
104+ logrotated:
105+ where: all
106diff --git a/contrib/includes/database/mysql.yaml b/contrib/includes/database/mysql.yaml
107new file mode 100644
108index 0000000..ef827e3
109--- /dev/null
110+++ b/contrib/includes/database/mysql.yaml
111@@ -0,0 +1,10 @@
112+openstack database config: &openstack-config-database
113+ mysql-innodb-cluster:
114+ innodb-buffer-pool-size:
115+ gte: 6G
116+ max-connections:
117+ gte: 2000
118+
119+openstack mandatory deps database: &openstack-mandatory-deps-database
120+ - mysql-innodb-cluster
121+ - mysql-router
122diff --git a/contrib/includes/database/percona-cluster.yaml b/contrib/includes/database/percona-cluster.yaml
123new file mode 100644
124index 0000000..7102f52
125--- /dev/null
126+++ b/contrib/includes/database/percona-cluster.yaml
127@@ -0,0 +1,9 @@
128+openstack database config: &openstack-config-database
129+ percona-cluster:
130+ innodb-buffer-pool-size:
131+ gte: 6G
132+ max-connections:
133+ gte: 2000
134+
135+openstack mandatory deps database: &openstack-mandatory-deps-database
136+ - percona-cluster
137diff --git a/contrib/includes/kubernetes.yaml b/contrib/includes/kubernetes.yaml
138new file mode 100644
139index 0000000..02d5fa0
140--- /dev/null
141+++ b/contrib/includes/kubernetes.yaml
142@@ -0,0 +1,24 @@
143+kubernetes config:
144+ kubernetes-master:
145+ authorization-mode:
146+ eq: "RBAC,Node"
147+ canal:
148+ cidr:
149+ isset: false
150+
151+kubernetes mandatory: &kubernetes-mandatory-charms
152+ - containerd
153+ - kubeapi-load-balancer
154+ - kubernetes-master
155+ - kubernetes-worker
156+
157+kubernetes optional charms: &kubernetes-optional-charms
158+ - calico
159+ - canal
160+ - coredns
161+ - easyrsa
162+ - etcd
163+ - flannel
164+ - kubernetes-dashboard
165+ - openstack-integrator
166+ - vsphere-integrator
167diff --git a/contrib/includes/networking/ovn.yaml b/contrib/includes/networking/ovn.yaml
168new file mode 100644
169index 0000000..b4f4140
170--- /dev/null
171+++ b/contrib/includes/networking/ovn.yaml
172@@ -0,0 +1,9 @@
173+openstack config networking: &openstack-config-networking
174+ ovn-central:
175+ ovsdb-server-election-timer:
176+ gte: 4
177+
178+openstack mandatory charms networking: &openstack-mandatory-charms-networking
179+ - ovn-central
180+ - ovn-chassis
181+ - neutron-api-plugin-ovn
182diff --git a/contrib/includes/networking/ovs.yaml b/contrib/includes/networking/ovs.yaml
183new file mode 100644
184index 0000000..493195e
185--- /dev/null
186+++ b/contrib/includes/networking/ovs.yaml
187@@ -0,0 +1,4 @@
188+openstack config networking: &openstack-config-networking {}
189+
190+openstack mandatory charms networking: &openstack-mandatory-charms-networking
191+ - neutron-openvswitch
192diff --git a/contrib/includes/openstack.yaml b/contrib/includes/openstack.yaml
193new file mode 100644
194index 0000000..5235aea
195--- /dev/null
196+++ b/contrib/includes/openstack.yaml
197@@ -0,0 +1,105 @@
198+openstack config base: &openstack-config-base
199+ ceph-radosgw:
200+ ceph-osd-replication-count:
201+ eq: 3
202+ cinder-ceph:
203+ ceph-osd-replication-count:
204+ eq: 3
205+ glance:
206+ ceph-osd-replication-count:
207+ eq: 3
208+ neutron-api:
209+ path-mtu:
210+ eq: 9000
211+ global-physnet-mtu:
212+ eq: 9000
213+ nova-compute:
214+ cpu-model:
215+ neq: ""
216+ ceph-osd-replication-count:
217+ eq: 3
218+ rabbitmq-server:
219+ cluster-partition-handling:
220+ eq: "pause_minority"
221+ keystone:
222+ token-expiration:
223+ gte: 86400
224+ sysconfig:
225+ governor:
226+ eq: "performance"
227+
228+openstack config:
229+ << : [ *openstack-config-base, *openstack-config-networking, *openstack-config-database ]
230+
231+openstack mandatory: &openstack-mandatory-charms
232+ - ceilometer
233+ - ceilometer-agent
234+ - ceph-mon
235+ - ceph-osd
236+ - cinder
237+ - cinder-ceph
238+ - glance
239+ - heat
240+ - keystone
241+ - neutron-api
242+ - nova-cloud-controller
243+ - nova-compute
244+ - openstack-dashboard
245+ - *openstack-mandatory-charms-networking
246+
247+openstack mandatory deps base: &openstack-mandatory-deps-base
248+ - haproxy
249+ - memcached
250+ - rabbitmq-server
251+
252+openstack mandatory deps: &openstack-mandatory-deps
253+ - *openstack-mandatory-deps-base
254+ - *openstack-mandatory-deps-database
255+
256+openstack mandatory subordinates: &openstack-mandatory-subs
257+ - hacluster
258+
259+openstack optional charms: &openstack-optional-charms
260+ - aodh
261+ - barbican
262+ - barbican-vault
263+ - ceph-fs
264+ - ceph-radosgw
265+ - cinder-backup
266+ - designate
267+ - designate-bind
268+ - easyrsa
269+ - etcd
270+ - glance-simplestreams-sync
271+ - glance-sync-slave
272+ - gnocchi
273+ - ironic-api
274+ - ironic-conductor
275+ - keystone-ldap
276+ - manila
277+ - manila-dashboard
278+ - manila-ganesha
279+ - masakari
280+ - masakari-monitors
281+ - mongodb # Optional since Gnocchi
282+ - neutron-gateway
283+ - neutron-api-plugin-ironic
284+ - octavia
285+ - octavia-dashboard
286+ - octavia-diskimage-retrofit
287+ - pacemaker-remote
288+ - placement
289+ - swift-proxy
290+ - swift-storage
291+ - vault
292+ - cinder-lvm
293+
294+cisco-aci-charms: &cisco-aci-charms
295+ - neutron-api-plugin-aci
296+ - openstack-dashboard-plugin-gbp
297+
298+trilio-charms: &trilio-charms
299+ - trilio-dm-api
300+ - trilio-horizon-plugin
301+ - trilio-data-mover
302+ - trilio-wlm
303diff --git a/contrib/includes/operations.yaml b/contrib/includes/operations.yaml
304new file mode 100644
305index 0000000..40b628b
306--- /dev/null
307+++ b/contrib/includes/operations.yaml
308@@ -0,0 +1,49 @@
309+operations mandatory: &operations-mandatory-charms
310+ - elasticsearch
311+ - grafana
312+ - graylog
313+ - landscape-server
314+ - nagios
315+ - prometheus2
316+
317+operations optional: &operations-optional-charms
318+ - infra-node
319+ - cloudstats
320+ - juju-lint
321+
322+operations openstack mandatory base: &operations-openstack-mandatory-base
323+ - openstack-service-checks
324+ - prometheus-libvirt-exporter
325+ - prometheus-openstack-exporter
326+ - prometheus-grok-exporter
327+
328+operations openstack mandatory: &operations-openstack-mandatory-charms
329+ - *operations-openstack-mandatory-base
330+ - *operations-openstack-mandatory-series
331+
332+operations mandatory dependencies: &operations-mandatory-deps
333+ - postgresql
334+
335+operations subordinates: &operations-mandatory-subs
336+ - canonical-livepatch
337+ - filebeat
338+ - ksplice
339+ - landscape-client
340+ - lldpd
341+ - nrpe
342+ - ntp
343+ - telegraf
344+ - thruk-agent
345+ - hw-health
346+ - logrotated
347+
348+operations optional subordinates: &operations-optional-subs
349+ - policy-routing
350+ - bcache-tuning
351+ - sysconfig
352+ - logrotate-charm
353+ - advanced-routing
354+ - rsyslog-forwarder-ha
355+
356+operations kubernetes mandatory: &operations-kubernetes-mandatory-charms
357+ - kubernetes-service-checks
358diff --git a/contrib/includes/operations/bionic.yaml b/contrib/includes/operations/bionic.yaml
359new file mode 100644
360index 0000000..a6baf9e
361--- /dev/null
362+++ b/contrib/includes/operations/bionic.yaml
363@@ -0,0 +1,2 @@
364+operations openstack mandatory series: &operations-openstack-mandatory-series
365+ - prometheus-ceph-exporter
366diff --git a/contrib/includes/operations/focal.yaml b/contrib/includes/operations/focal.yaml
367new file mode 100644
368index 0000000..d658661
369--- /dev/null
370+++ b/contrib/includes/operations/focal.yaml
371@@ -0,0 +1 @@
372+operations openstack mandatory series: &operations-openstack-mandatory-series []
373diff --git a/contrib/includes/saas.yaml b/contrib/includes/saas.yaml
374new file mode 100644
375index 0000000..a3ed08f
376--- /dev/null
377+++ b/contrib/includes/saas.yaml
378@@ -0,0 +1,7 @@
379+saas:
380+ - elasticsearch
381+ - grafana
382+ - graylog
383+ - landscape-server
384+ - nagios
385+ - prometheus2
386diff --git a/contrib/kubernetes.yaml b/contrib/kubernetes.yaml
387new file mode 100644
388index 0000000..c7460b1
389--- /dev/null
390+++ b/contrib/kubernetes.yaml
391@@ -0,0 +1,22 @@
392+---
393+!include includes/base.yaml
394+!include includes/operations.yaml
395+!include includes/kubernetes.yaml
396+!include includes/saas.yaml
397+
398+operations charms: &operations-charms
399+ - *operations-kubernetes-mandatory-charms
400+ - *operations-mandatory-charms
401+ - *operations-mandatory-deps
402+ - *operations-mandatory-subs
403+ - *operations-optional-charms
404+ - *operations-optional-subs
405+
406+kubernetes charms: &kubernetes-charms
407+ - *kubernetes-mandatory-charms
408+ - *kubernetes-optional-charms
409+
410+known charms:
411+ - ubuntu
412+ - *operations-charms
413+ - *kubernetes-charms
414diff --git a/contrib/openstack-bionic-ovn.yaml b/contrib/openstack-bionic-ovn.yaml
415new file mode 100644
416index 0000000..d1a600c
417--- /dev/null
418+++ b/contrib/openstack-bionic-ovn.yaml
419@@ -0,0 +1,13 @@
420+---
421+# Openstack rule set for a Bionic OVN Cloud
422+# * Use OVN, not OVS
423+# * Uses percona-cluster, not mysql-innodb-cluster/router
424+# * Includes prometheus-ceph-exporter
425+!include includes/base.yaml
426+!include includes/networking/ovn.yaml
427+!include includes/database/percona-cluster.yaml
428+!include includes/operations/bionic.yaml
429+!include includes/openstack.yaml
430+!include includes/operations.yaml
431+!include includes/saas.yaml
432+!include includes/aggregator-openstack.yaml
433diff --git a/contrib/openstack-bionic-ovs.yaml b/contrib/openstack-bionic-ovs.yaml
434new file mode 100644
435index 0000000..60304e3
436--- /dev/null
437+++ b/contrib/openstack-bionic-ovs.yaml
438@@ -0,0 +1,13 @@
439+---
440+# Openstack rule set for a Bionic OVS Cloud
441+# * Use OVS, not OVN
442+# * Uses percona-cluster, not mysql-innodb-cluster/router
443+# * Includes prometheus-ceph-exporter
444+!include includes/base.yaml
445+!include includes/networking/ovs.yaml
446+!include includes/database/percona-cluster.yaml
447+!include includes/operations/bionic.yaml
448+!include includes/openstack.yaml
449+!include includes/operations.yaml
450+!include includes/saas.yaml
451+!include includes/aggregator-openstack.yaml
452diff --git a/contrib/openstack-focal-ovn.yaml b/contrib/openstack-focal-ovn.yaml
453new file mode 100644
454index 0000000..3c0a562
455--- /dev/null
456+++ b/contrib/openstack-focal-ovn.yaml
457@@ -0,0 +1,13 @@
458+---
459+# Openstack rule set for a Focal OVN Cloud
460+# * Use OVN, not OVS
461+# * Uses mysql-innodb-cluster/router, not percona-cluster
462+# * No prometheus-ceph-exporter
463+!include includes/base.yaml
464+!include includes/networking/ovn.yaml
465+!include includes/database/mysql.yaml
466+!include includes/operations/focal.yaml
467+!include includes/openstack.yaml
468+!include includes/operations.yaml
469+!include includes/saas.yaml
470+!include includes/aggregator-openstack.yaml
471diff --git a/contrib/openstack-focal-ovs.yaml b/contrib/openstack-focal-ovs.yaml
472new file mode 100644
473index 0000000..e1039cb
474--- /dev/null
475+++ b/contrib/openstack-focal-ovs.yaml
476@@ -0,0 +1,13 @@
477+---
478+# Openstack rule set for a Focal OVS Cloud
479+# * Use OVS, not OVN
480+# * Uses mysql-innodb-cluster/router, not percona-cluster
481+# * No prometheus-ceph-exporter
482+!include includes/base.yaml
483+!include includes/networking/ovs.yaml
484+!include includes/database/mysql.yaml
485+!include includes/operations/focal.yaml
486+!include includes/openstack.yaml
487+!include includes/operations.yaml
488+!include includes/saas.yaml
489+!include includes/aggregator-openstack.yaml
490diff --git a/jujulint/lint.py b/jujulint/lint.py
491index 3add8bf..38dfc00 100755
492--- a/jujulint/lint.py
493+++ b/jujulint/lint.py
494@@ -118,8 +118,11 @@ class Linter:
495 def read_rules(self):
496 """Read and parse rules from YAML, optionally processing provided overrides."""
497 if os.path.isfile(self.filename):
498- with open(self.filename, "r") as yaml_file:
499- self.lint_rules = yaml.safe_load(yaml_file)
500+ with open(self.filename, "r") as rules_file:
501+ raw_rules_txt = rules_file.read()
502+
503+ self.lint_rules = self._process_includes_in_rules(raw_rules_txt)
504+
505 if self.overrides:
506 for override in self.overrides.split("#"):
507 (name, where) = override.split(":")
508@@ -133,9 +136,10 @@ class Linter:
509 )
510 )
511 self.lint_rules["subordinates"][name] = dict(where=where)
512- self.lint_rules["known charms"] = flatten_list(
513- self.lint_rules["known charms"]
514- )
515+
516+ # Flatten all entries (to account for nesting due to YAML anchors (templating)
517+ self.lint_rules = {k: flatten_list(v) for k, v in self.lint_rules.items()}
518+
519 self.logger.debug(
520 "[{}] [{}/{}] Lint Rules: {}".format(
521 self.cloud_name,
522@@ -1163,3 +1167,32 @@ class Linter:
523 )
524 if self.collect_errors:
525 self.collect(error)
526+
527+ def _process_includes_in_rules(self, yaml_txt):
528+ """
529+ Process any includes in the rules file.
530+
531+ Only top level includes are supported (without recursion), with relative paths.
532+
533+ Example syntax:
534+
535+ !include foo.yaml
536+ """
537+ collector = []
538+ for line in yaml_txt.splitlines():
539+ if line.startswith("!include"):
540+ try:
541+ _, rel_path = line.split()
542+ except ValueError:
543+ self.logger.warn("invalid include in rules, ignored: '{}'".format(line))
544+ continue
545+
546+ include_path = os.path.join(os.path.dirname(self.filename), rel_path)
547+
548+ if os.path.isfile(include_path):
549+ with open(include_path, "r") as f:
550+ collector.append(f.read())
551+ else:
552+ collector.append(line)
553+
554+ return yaml.safe_load("\n".join(collector))
555diff --git a/jujulint/util.py b/jujulint/util.py
556index b8c1359..c6eb80e 100644
557--- a/jujulint/util.py
558+++ b/jujulint/util.py
559@@ -29,6 +29,10 @@ class InvalidCharmNameError(Exception):
560
561 def flatten_list(lumpy_list):
562 """Flatten a list potentially containing other lists."""
563+ # Ensure we only operate on lists, otherwise will affect other iterables
564+ if not isinstance(lumpy_list, list):
565+ return lumpy_list
566+
567 flat_list = []
568 for item in lumpy_list:
569 if not isinstance(item, list):
570diff --git a/tests/test_jujulint.py b/tests/test_jujulint.py
571index 1e200d2..ce123ee 100644
572--- a/tests/test_jujulint.py
573+++ b/tests/test_jujulint.py
574@@ -16,6 +16,12 @@ def test_flatten_list(utils):
575 assert flattened_list == utils.flatten_list(unflattened_list)
576
577
578+def test_flatten_list_non_list_iterable(utils):
579+ """Test the utils flatten_list function."""
580+ iterable = {1: 2}
581+ assert iterable == utils.flatten_list(iterable)
582+
583+
584 def test_map_charms(linter, utils):
585 """Test the charm name validation code."""
586 applications = {
587@@ -434,3 +440,24 @@ class TestLinter:
588 assert error is not None
589 assert error["id"] == "ops-charm-missing"
590 assert error["charm"] == "grafana"
591+
592+ def test_read_rules_plain_yaml(self, linter, tmp_path):
593+ """Test that a simple rules YAML is imported as expected."""
594+ rules_path = tmp_path / "rules.yaml"
595+ rules_path.write_text('---\nkey:\n "value"')
596+
597+ linter.filename = str(rules_path)
598+ linter.read_rules()
599+ assert linter.lint_rules == {"key": "value"}
600+
601+ def test_read_rules_include(self, linter, tmp_path):
602+ """Test that rules YAML with an include is imported as expected."""
603+ include_path = tmp_path / "include.yaml"
604+ include_path.write_text('key-inc:\n "value2"')
605+
606+ rules_path = tmp_path / "rules.yaml"
607+ rules_path.write_text('---\n!include include.yaml\nkey:\n "value"')
608+
609+ linter.filename = str(rules_path)
610+ linter.read_rules()
611+ assert linter.lint_rules == {"key": "value", "key-inc": "value2"}

Subscribers

People subscribed via source and target branches