Merge ~gabrielcocenza/juju-lint:bug/1980019-B into juju-lint:master

Proposed by Gabriel Cocenza
Status: Merged
Approved by: Martin Kalcok
Approved revision: cd3899ec2a0523792ae7d2e44e15b7d860d8b504
Merged at revision: 428f0c6f11ff1092806d1d09f7b11fa2c53faac4
Proposed branch: ~gabrielcocenza/juju-lint:bug/1980019-B
Merge into: juju-lint:master
Diff against target: 195 lines (+116/-9)
4 files modified
jujulint/cli.py (+30/-7)
jujulint/config.py (+2/-2)
jujulint/lint.py (+48/-0)
tests/test_jujulint.py (+36/-0)
Reviewer Review Type Date Requested Status
Martin Kalcok (community) Approve
Eric Chen Approve
Review via email: mp+427224@code.launchpad.net

Commit message

Added check for cloud_type.

- check if cloud_type passed is known.
- in case cloud_type it's not passed in the cli, it tries to detect
  by the charms in the deployment.

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
Eric Chen (eric-chen) :
review: Needs Fixing
Revision history for this message
Gabriel Cocenza (gabrielcocenza) wrote :

Thanks for your suggestions Eric.

Revision history for this message
Eric Chen (eric-chen) :
review: Approve
Revision history for this message
Martin Kalcok (martin-kalcok) wrote :

+1

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

Change successfully merged at revision 428f0c6f11ff1092806d1d09f7b11fa2c53faac4

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/jujulint/cli.py b/jujulint/cli.py
2index 2d76832..97e5a34 100755
3--- a/jujulint/cli.py
4+++ b/jujulint/cli.py
5@@ -64,17 +64,45 @@ class Cli:
6 self.logger.error("Cloud not locate rules file {}".format(rules_file))
7 sys.exit(1)
8
9+ @property
10+ def cloud_type(self):
11+ """Get the cloud type passed in the CLI.
12+
13+ :return: cloud-type of the deployment or None.
14+ :rtype: str
15+ """
16+ cloud_type = None
17+ if "cloud-type" in self.config:
18+ cloud_type = self.config["cloud-type"].get()
19+ return cloud_type
20+
21+ @property
22+ def manual_file(self):
23+ """Get the manual file passed in the CLI.
24+
25+ :return: path to manual file to lint or None.
26+ :rtype: str
27+ """
28+ manual_file = None
29+ if "manual-file" in self.config:
30+ manual_file = self.config["manual-file"].get()
31+ return manual_file
32+
33 def startup_message(self):
34 """Print startup message to log."""
35 self.logger.info(
36 (
37 "juju-lint version {} starting...\n"
38 "\t* Config directory: {}\n"
39+ "\t* Cloud type: {}\n"
40+ "\t* Manual file: {}\n"
41 "\t* Rules file: {}\n"
42 "\t* Log level: {}\n"
43 ).format(
44 self.version,
45 self.config.config_dir(),
46+ self.cloud_type or "Unknown",
47+ self.manual_file or False,
48 self.lint_rules,
49 self.config["logging"]["loglevel"].get(),
50 )
51@@ -161,13 +189,8 @@ def main():
52 """Program entry point."""
53 cli = Cli()
54 cli.startup_message()
55- if "manual-file" in cli.config:
56- manual_file = cli.config["manual-file"].get()
57- if "manual-type" in cli.config:
58- manual_type = cli.config["manual-type"].get()
59- cli.audit_file(manual_file, cloud_type=manual_type)
60- else:
61- cli.audit_file(manual_file)
62+ if cli.manual_file:
63+ cli.audit_file(cli.manual_file, cloud_type=cli.cloud_type)
64 elif "clouds" in cli.config:
65 cli.audit_all()
66 else:
67diff --git a/jujulint/config.py b/jujulint/config.py
68index d87677a..a4822ee 100644
69--- a/jujulint/config.py
70+++ b/jujulint/config.py
71@@ -80,9 +80,9 @@ class Config(Configuration):
72 "-t",
73 "--cloud-type",
74 help=(
75- "Sets the cloud type when specifying a YAML file to audit with -f or --cloud-file."
76+ "Sets the cloud type when specifying a YAML file to audit with -t or --cloud-type."
77 ),
78- dest="manual-type",
79+ dest="cloud-type",
80 )
81 self.parser.add_argument(
82 "-o",
83diff --git a/jujulint/lint.py b/jujulint/lint.py
84index 4fb14bb..8f25617 100755
85--- a/jujulint/lint.py
86+++ b/jujulint/lint.py
87@@ -720,6 +720,51 @@ class Linter:
88 }
89 )
90
91+ def check_cloud_type(self, deployment_charms):
92+ """Check cloud_type or detect depending on the deployed charms.
93+
94+ :param deployment_charms: charms present in the bundle and/or status.
95+ :type deployment_charms: Set
96+ """
97+ typical_cloud_charms = {
98+ "openstack": {
99+ "keystone",
100+ "nova-compute",
101+ "nova-cloud-controller",
102+ "glance",
103+ "openstack-dashboard",
104+ "neutron-api",
105+ },
106+ "kubernetes": {
107+ "kubernetes-worker",
108+ "kubernetes-control-plane",
109+ "containerd",
110+ "calico",
111+ "canal",
112+ "etcd",
113+ },
114+ }
115+ if self.cloud_type:
116+ if self.cloud_type not in typical_cloud_charms.keys():
117+ self._log_with_header(
118+ "Cloud type {} is unknown".format(self.cloud_type),
119+ level=logging.WARN,
120+ )
121+ return
122+
123+ for cloud_type, charms in typical_cloud_charms.items():
124+ match = deployment_charms.intersection(charms)
125+ if len(match) >= 2:
126+ self._log_with_header(
127+ (
128+ "Setting cloud-type to '{}'. "
129+ "Deployment has these charms: {} that are typically from {}."
130+ ).format(cloud_type, match, cloud_type),
131+ level=logging.WARN,
132+ )
133+ self.cloud_type = cloud_type
134+ return
135+
136 def check_spaces(self, parsed_yaml):
137 """Check that relations end with the same endpoint."""
138 space_checks = self.lint_rules.get("space checks", {})
139@@ -1133,6 +1178,9 @@ class Linter:
140 # Build a list of deployed charms and mapping of charms <-> applications
141 self.map_charms(parsed_yaml[applications])
142
143+ # Automatically detects cloud type if it's not passed as argument
144+ self.check_cloud_type(self.model.charms)
145+
146 # Parse SAAS / remote-applications
147 self.parse_cmr_apps(parsed_yaml)
148
149diff --git a/tests/test_jujulint.py b/tests/test_jujulint.py
150index a66c174..e99401a 100644
151--- a/tests/test_jujulint.py
152+++ b/tests/test_jujulint.py
153@@ -119,6 +119,42 @@ class TestLinter:
154 with pytest.raises(utils.InvalidCharmNameError):
155 linter.map_charms(applications)
156
157+ def test_check_cloud_type(self, linter, mocker):
158+ """Test cloud_type detection on different scenarios."""
159+ mock_log = mocker.patch("jujulint.lint.Linter._log_with_header")
160+ # test models with more or equal than two matches
161+ model_charms = {
162+ "openstack": {"keystone", "nova-compute", "glance", "foo"},
163+ "kubernetes": {"kubernetes-worker", "kubernetes-control-plane", "bar"},
164+ }
165+ # detect cloud_type depending on the deployment charms and
166+ # if it's not passe as an argument in the cli.
167+ for cloud_type, charms in model_charms.items():
168+ linter.cloud_type = None
169+ linter.check_cloud_type(charms)
170+ assert linter.cloud_type == cloud_type
171+
172+ # in case cloud_type is passed doesn't change the value
173+ linter.cloud_type = "openstack"
174+ for cloud_type, charms in model_charms.items():
175+ linter.check_cloud_type(charms)
176+ assert linter.cloud_type == "openstack"
177+
178+ # if it's an invalid cloud_type log warn to user
179+ linter.cloud_type = "foo-bar"
180+ linter.check_cloud_type({"foo", "bar"})
181+ mock_log.assert_called_with("Cloud type foo-bar is unknown", level=logging.WARN)
182+
183+ # test models with less than 2 matches without passing cloud_type in the cli
184+ model_charms = {
185+ "openstack": {"keystone", "foo", "bar"},
186+ "kubernetes": {"foo", "bar"},
187+ }
188+ for cloud_type, charms in model_charms.items():
189+ linter.cloud_type = None
190+ linter.check_cloud_type(charms)
191+ assert linter.cloud_type is None
192+
193 def test_juju_status_unexpected(self, linter, juju_status):
194 """Test that juju and workload status is expected."""
195 # inject invalid status to the application

Subscribers

People subscribed via source and target branches