daktari 0.0.254__py3-none-any.whl → 0.0.255__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of daktari might be problematic. Click here for more details.
- daktari/__init__.py +1 -1
- daktari/checks/__init__.py +0 -0
- daktari/checks/android.py +26 -0
- daktari/checks/aws.py +34 -0
- daktari/checks/certs.py +34 -0
- daktari/checks/conan.py +96 -0
- daktari/checks/direnv.py +59 -0
- daktari/checks/docker.py +50 -0
- daktari/checks/files.py +72 -0
- daktari/checks/flutter.py +46 -0
- daktari/checks/git.py +198 -0
- daktari/checks/google.py +113 -0
- daktari/checks/intellij_idea.py +248 -0
- daktari/checks/java.py +104 -0
- daktari/checks/kubernetes.py +154 -0
- daktari/checks/misc.py +302 -0
- daktari/checks/nodejs.py +92 -0
- daktari/checks/onepassword.py +105 -0
- daktari/checks/python.py +16 -0
- daktari/checks/ssh.py +33 -0
- daktari/checks/terraform.py +70 -0
- daktari/checks/test_certs.py +32 -0
- daktari/checks/test_intellij_idea.py +66 -0
- daktari/checks/test_java.py +88 -0
- daktari/checks/test_misc.py +18 -0
- daktari/checks/test_onepassword.py +31 -0
- daktari/checks/test_ssh.py +22 -0
- daktari/checks/test_yarn.py +86 -0
- daktari/checks/xml.py +33 -0
- daktari/checks/yarn.py +156 -0
- {daktari-0.0.254.dist-info → daktari-0.0.255.dist-info}/METADATA +2 -2
- daktari-0.0.255.dist-info/RECORD +64 -0
- daktari-0.0.254.dist-info/RECORD +0 -35
- {daktari-0.0.254.dist-info → daktari-0.0.255.dist-info}/LICENSE.txt +0 -0
- {daktari-0.0.254.dist-info → daktari-0.0.255.dist-info}/WHEEL +0 -0
- {daktari-0.0.254.dist-info → daktari-0.0.255.dist-info}/entry_points.txt +0 -0
- {daktari-0.0.254.dist-info → daktari-0.0.255.dist-info}/top_level.txt +0 -0
daktari/checks/misc.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from os.path import expanduser
|
|
3
|
+
from typing import Dict, Optional
|
|
4
|
+
|
|
5
|
+
from python_hosts import Hosts
|
|
6
|
+
from tabulate import tabulate
|
|
7
|
+
|
|
8
|
+
from daktari.check import Check, CheckResult
|
|
9
|
+
from daktari.os import OS, check_env_var_exists, get_env_var_value
|
|
10
|
+
from daktari.version_utils import get_simple_cli_version
|
|
11
|
+
from daktari.command_utils import can_run_command
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WatchmanInstalled(Check):
|
|
15
|
+
name = "watchman.installed"
|
|
16
|
+
|
|
17
|
+
suggestions = {
|
|
18
|
+
OS.OS_X: "<cmd>brew install watchman</cmd>",
|
|
19
|
+
OS.GENERIC: "Install watchman: https://facebook.github.io/watchman/docs/install.html#buildinstall",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def check(self) -> CheckResult:
|
|
23
|
+
return self.verify_install("watchman")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MkcertInstalled(Check):
|
|
27
|
+
name = "mkcert.installed"
|
|
28
|
+
|
|
29
|
+
suggestions = {
|
|
30
|
+
OS.OS_X: """
|
|
31
|
+
Install mkcert:
|
|
32
|
+
<cmd>brew install mkcert</cmd>
|
|
33
|
+
Install the local CA in the system trust store:
|
|
34
|
+
<cmd>mkcert -install</cmd>
|
|
35
|
+
""",
|
|
36
|
+
OS.GENERIC: """
|
|
37
|
+
Install mkcert: https://mkcert.dev/#installation
|
|
38
|
+
Install the local CA in the system trust store:
|
|
39
|
+
<cmd>mkcert -install</cmd>
|
|
40
|
+
""",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def check(self) -> CheckResult:
|
|
44
|
+
return self.verify_install("mkcert")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class KtlintInstalled(Check):
|
|
48
|
+
name = "ktlint.installed"
|
|
49
|
+
|
|
50
|
+
suggestions = {
|
|
51
|
+
OS.OS_X: "<cmd>brew install ktlint</cmd>",
|
|
52
|
+
OS.GENERIC: "Install ktlint: https://github.com/pinterest/ktlint#installation",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def __init__(self, required_version: Optional[str] = None, recommended_version: Optional[str] = None):
|
|
56
|
+
self.required_version = required_version
|
|
57
|
+
self.recommended_version = recommended_version
|
|
58
|
+
|
|
59
|
+
def check(self) -> CheckResult:
|
|
60
|
+
installed_version = get_simple_cli_version("ktlint")
|
|
61
|
+
return self.validate_semver_expression(
|
|
62
|
+
"ktlint", installed_version, self.required_version, self.recommended_version
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CmakeInstalled(Check):
|
|
67
|
+
name = "cmake.installed"
|
|
68
|
+
|
|
69
|
+
suggestions = {
|
|
70
|
+
OS.OS_X: "<cmd>brew install cmake</cmd>",
|
|
71
|
+
OS.UBUNTU: "<cmd>apt-get install cmake</cmd>",
|
|
72
|
+
OS.GENERIC: "Install cmake: https://cmake.org/install/",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def __init__(self, required_version: Optional[str] = None, recommended_version: Optional[str] = None):
|
|
76
|
+
self.required_version = required_version
|
|
77
|
+
self.recommended_version = recommended_version
|
|
78
|
+
|
|
79
|
+
def check(self) -> CheckResult:
|
|
80
|
+
installed_version = get_simple_cli_version("cmake")
|
|
81
|
+
return self.validate_semver_expression(
|
|
82
|
+
"cmake", installed_version, self.required_version, self.recommended_version
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class JqInstalled(Check):
|
|
87
|
+
name = "jq.installed"
|
|
88
|
+
|
|
89
|
+
suggestions = {
|
|
90
|
+
OS.OS_X: "<cmd>brew install jq</cmd>",
|
|
91
|
+
OS.UBUNTU: "<cmd>sudo apt install jq</cmd>",
|
|
92
|
+
OS.GENERIC: "Install jq: https://stedolan.github.io/jq/download/",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
def check(self) -> CheckResult:
|
|
96
|
+
return self.verify_install("jq")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class FlywayInstalled(Check):
|
|
100
|
+
name = "flyway.installed"
|
|
101
|
+
|
|
102
|
+
suggestions = {
|
|
103
|
+
OS.OS_X: "<cmd>brew install flyway</cmd>",
|
|
104
|
+
OS.UBUNTU: "<cmd>snap install flyway</cmd>",
|
|
105
|
+
OS.GENERIC: "Install flyway: https://flywaydb.org/documentation/usage/commandline/#download-and-installation",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
def check(self) -> CheckResult:
|
|
109
|
+
return self.verify_install("flyway", "-v")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ShellcheckInstalled(Check):
|
|
113
|
+
name = "shellcheck.installed"
|
|
114
|
+
|
|
115
|
+
suggestions = {
|
|
116
|
+
OS.OS_X: "<cmd>brew install shellcheck</cmd>",
|
|
117
|
+
OS.UBUNTU: "<cmd>sudo apt install shellcheck</cmd>",
|
|
118
|
+
OS.GENERIC: "Install shellcheck: https://github.com/koalaman/shellcheck#user-content-installing",
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
def check(self) -> CheckResult:
|
|
122
|
+
return self.verify_install("shellcheck")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class MakeInstalled(Check):
|
|
126
|
+
name = "make.installed"
|
|
127
|
+
|
|
128
|
+
suggestions = {
|
|
129
|
+
OS.OS_X: "<cmd>xcode-select --install</cmd>",
|
|
130
|
+
OS.UBUNTU: "<cmd>sudo apt install make</cmd>",
|
|
131
|
+
OS.GENERIC: "Install make: https://www.gnu.org/software/make/",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
def check(self) -> CheckResult:
|
|
135
|
+
return self.verify_install("make")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class GccInstalled(Check):
|
|
139
|
+
name = "gcc.installed"
|
|
140
|
+
|
|
141
|
+
suggestions = {
|
|
142
|
+
OS.OS_X: "<cmd>xcode-select --install</cmd>",
|
|
143
|
+
OS.UBUNTU: "<cmd>sudo apt install gcc</cmd>",
|
|
144
|
+
OS.GENERIC: "Install gcc: https://gcc.gnu.org/",
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
def check(self) -> CheckResult:
|
|
148
|
+
return self.verify_install("gcc")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class EnvVarSet(Check):
|
|
152
|
+
def __init__(self, variable_name: str, variable_value: Optional[str] = "", provision_command: str = ""):
|
|
153
|
+
self.name = f"env.variableSet.{variable_name}"
|
|
154
|
+
self.suggestions = {OS.GENERIC: provision_command}
|
|
155
|
+
self.variable_name = variable_name
|
|
156
|
+
self.variable_value = variable_value
|
|
157
|
+
|
|
158
|
+
def check(self) -> CheckResult:
|
|
159
|
+
if self.variable_value:
|
|
160
|
+
return self.verify(
|
|
161
|
+
get_env_var_value(self.variable_name) == self.variable_value,
|
|
162
|
+
f"{self.variable_name} has <not/> got the required value of {self.variable_value}",
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
return self.verify(check_env_var_exists(self.variable_name), f"{self.variable_name} is <not/> set")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class ShfmtInstalled(Check):
|
|
169
|
+
name = "shfmt.installed"
|
|
170
|
+
|
|
171
|
+
suggestions = {
|
|
172
|
+
OS.OS_X: "<cmd>brew install shfmt</cmd>",
|
|
173
|
+
OS.UBUNTU: "<cmd>snap install shfmt</cmd>",
|
|
174
|
+
OS.GENERIC: "Install shfmt: https://github.com/mvdan/sh",
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
def check(self) -> CheckResult:
|
|
178
|
+
return self.verify_install("shfmt")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class Md5SumInstalled(Check):
|
|
182
|
+
name = "md5sum.installed"
|
|
183
|
+
|
|
184
|
+
suggestions = {OS.OS_X: "<cmd>brew install md5sha1sum</cmd>", OS.GENERIC: "Install md5sum"}
|
|
185
|
+
|
|
186
|
+
def check(self) -> CheckResult:
|
|
187
|
+
return self.verify_install("md5sum")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class HostAliasesConfigured(Check):
|
|
191
|
+
name = "hostAliases.configured"
|
|
192
|
+
|
|
193
|
+
def __init__(self, required_aliases: Dict[str, str]):
|
|
194
|
+
self.required_aliases = required_aliases
|
|
195
|
+
hosts_path = Hosts.determine_hosts_path()
|
|
196
|
+
entries_text = tabulate([(addr, name) for (name, addr) in self.required_aliases.items()], tablefmt="plain")
|
|
197
|
+
self.suggestions = {OS.GENERIC: f"Add the following entries to {hosts_path}:\n\n{entries_text}"}
|
|
198
|
+
|
|
199
|
+
def check(self) -> CheckResult:
|
|
200
|
+
hosts = Hosts()
|
|
201
|
+
entries = [e for e in hosts.entries if e.entry_type in ("ipv4", "ipv6")]
|
|
202
|
+
entries_dict = {}
|
|
203
|
+
for entry in entries:
|
|
204
|
+
for name in entry.names:
|
|
205
|
+
entries_dict[name] = entry.address
|
|
206
|
+
logging.debug(f"Hosts file entries: {entries_dict}")
|
|
207
|
+
for name, address in self.required_aliases.items():
|
|
208
|
+
if entries_dict.get(name) != address:
|
|
209
|
+
return self.failed(f"{hosts.path} alias {name} -> {address} not present")
|
|
210
|
+
|
|
211
|
+
return self.passed(f"{hosts.path} aliases present")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class DirectoryIsOnPath(Check):
|
|
215
|
+
name = "directory.on.path"
|
|
216
|
+
|
|
217
|
+
def __init__(self, directory: str):
|
|
218
|
+
self.directory = expanduser(directory) # $PATH won't auto-expand ~
|
|
219
|
+
self.suggestions = {
|
|
220
|
+
OS.GENERIC: f"""
|
|
221
|
+
Append the following line to your profile (~/.bashrc or ~/.zshrc):
|
|
222
|
+
export PATH="{self.directory}:$PATH
|
|
223
|
+
""",
|
|
224
|
+
OS.UBUNTU: f"""
|
|
225
|
+
Append the following line to your profile (~/.bashrc):
|
|
226
|
+
export PATH="{self.directory}:$PATH"
|
|
227
|
+
For first time setup, you can run this:
|
|
228
|
+
<cmd>echo 'export PATH="{self.directory}:$PATH"' >> ~/.bashrc && source ~/.bashrc</cmd>
|
|
229
|
+
""",
|
|
230
|
+
OS.OS_X: f"""
|
|
231
|
+
Append the following line to your profile (~/.zshrc):
|
|
232
|
+
export PATH="{self.directory}:$PATH"
|
|
233
|
+
For first time setup, you can run this:
|
|
234
|
+
<cmd>echo 'export PATH="{self.directory}:$PATH"' >> ~/.zshrc && source ~/.zshrc</cmd>
|
|
235
|
+
""",
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
def check(self) -> CheckResult:
|
|
239
|
+
path_value = get_env_var_value("PATH")
|
|
240
|
+
return self.verify(path_value.__contains__(self.directory), f"{self.directory} is <not/> on the $PATH")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class DetektInstalled(Check):
|
|
244
|
+
name = "detekt.installed"
|
|
245
|
+
|
|
246
|
+
def __init__(
|
|
247
|
+
self,
|
|
248
|
+
required_version: Optional[str] = None,
|
|
249
|
+
recommended_version: Optional[str] = None,
|
|
250
|
+
install_version: Optional[str] = None,
|
|
251
|
+
):
|
|
252
|
+
self.install_version = install_version
|
|
253
|
+
self.required_version = required_version
|
|
254
|
+
self.recommended_version = recommended_version
|
|
255
|
+
self.suggestions = {
|
|
256
|
+
OS.OS_X: self.get_install_cmd(),
|
|
257
|
+
OS.UBUNTU: self.get_install_cmd(),
|
|
258
|
+
OS.GENERIC: "Install detekt: https://detekt.dev/cli.html#install-the-cli",
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
def get_install_cmd(self) -> str:
|
|
262
|
+
version = self.install_version or "[desired version - see https://github.com/detekt/detekt/releases]"
|
|
263
|
+
url = "https://github.com/detekt/detekt/releases/download/v$DETEKT_VERSION/detekt-cli-$DETEKT_VERSION.zip"
|
|
264
|
+
return f"""
|
|
265
|
+
<cmd>DETEKT_VERSION={version}</cmd>
|
|
266
|
+
<cmd>LOCAL_BIN=~/.local/bin</cmd>
|
|
267
|
+
<cmd>TEMP_FILE=$(mktemp)</cmd>
|
|
268
|
+
<cmd>mkdir -p "$LOCAL_BIN"</cmd>
|
|
269
|
+
<cmd>curl -L {url} --output "$TEMP_FILE"</cmd>
|
|
270
|
+
<cmd>unzip -u "$TEMP_FILE" -d "$LOCAL_BIN"</cmd>
|
|
271
|
+
<cmd>rm "$TEMP_FILE"</cmd>
|
|
272
|
+
<cmd>chmod +x "$LOCAL_BIN/detekt-cli-$DETEKT_VERSION/bin/detekt-cli"</cmd>
|
|
273
|
+
<cmd>ln -f -s "$LOCAL_BIN/detekt-cli-$DETEKT_VERSION/bin/detekt-cli" "$LOCAL_BIN/detekt"</cmd>
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
def check(self) -> CheckResult:
|
|
277
|
+
installed_version = get_simple_cli_version("detekt")
|
|
278
|
+
return self.validate_semver_expression(
|
|
279
|
+
"detekt", installed_version, self.required_version, self.recommended_version
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class TaskInstalled(Check):
|
|
284
|
+
name = "task.installed"
|
|
285
|
+
suggestions = {
|
|
286
|
+
OS.OS_X: "<cmd>brew install go-task</cmd>",
|
|
287
|
+
OS.GENERIC: "Install task: https://taskfile.dev/installation/",
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
def check(self) -> CheckResult:
|
|
291
|
+
return self.verify_install("task")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class Rosetta2Installed(Check):
|
|
295
|
+
name = "rosetta2.installed"
|
|
296
|
+
run_on = OS.OS_X
|
|
297
|
+
suggestions = {OS.OS_X: "<cmd>softwareupdate --install-rosetta</cmd>"}
|
|
298
|
+
|
|
299
|
+
def check(self) -> CheckResult:
|
|
300
|
+
return self.verify(
|
|
301
|
+
can_run_command("arch -x86_64 true"), "Rosetta 2 is installed or not required", "Rosetta 2 is not installed"
|
|
302
|
+
)
|
daktari/checks/nodejs.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from semver import VersionInfo
|
|
5
|
+
|
|
6
|
+
from daktari.check import Check, CheckResult
|
|
7
|
+
from daktari.command_utils import get_stdout, run_command
|
|
8
|
+
from daktari.os import OS
|
|
9
|
+
from daktari.version_utils import try_parse_semver
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_nodejs_version() -> Optional[VersionInfo]:
|
|
13
|
+
version_output = get_stdout("node --version")
|
|
14
|
+
version_output = None if version_output is None else version_output.lstrip("v")
|
|
15
|
+
return try_parse_semver(version_output)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run_nvm(nvm_args: List[str]):
|
|
19
|
+
return run_command(["sh", "-c", '. "$NVM_DIR/nvm.sh"; nvm "$@"', "--", *nvm_args])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def can_run_nvm() -> bool:
|
|
23
|
+
try:
|
|
24
|
+
run_nvm(["--version"])
|
|
25
|
+
return True
|
|
26
|
+
except Exception:
|
|
27
|
+
logging.debug("Exception running nvm", exc_info=True)
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_nvmrc_version() -> Optional[str]:
|
|
32
|
+
try:
|
|
33
|
+
with open(".nvmrc", "r") as nvmrc_file:
|
|
34
|
+
return nvmrc_file.readline().strip()
|
|
35
|
+
except IOError:
|
|
36
|
+
logging.debug("Could not read .nvmrc", exc_info=True)
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class NodeJsVersion(Check):
|
|
41
|
+
name = "nodejs.version"
|
|
42
|
+
|
|
43
|
+
def __init__(self, required_version: str, recommended_version: Optional[str] = None):
|
|
44
|
+
self.required_version = required_version
|
|
45
|
+
self.recommended_version = recommended_version
|
|
46
|
+
|
|
47
|
+
suggestions = {
|
|
48
|
+
OS.GENERIC: "Install node.js",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def check(self) -> CheckResult:
|
|
52
|
+
nodejs_version = get_nodejs_version()
|
|
53
|
+
logging.info(f"node.js version: {nodejs_version}")
|
|
54
|
+
return self.validate_semver_expression(
|
|
55
|
+
"node.js", nodejs_version, self.required_version, self.recommended_version
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class NvmInstalled(Check):
|
|
60
|
+
name = "nvm.installed"
|
|
61
|
+
|
|
62
|
+
suggestions = {OS.GENERIC: "Install nvm from: https://nvm.sh"}
|
|
63
|
+
|
|
64
|
+
def check(self) -> CheckResult:
|
|
65
|
+
return self.verify(can_run_nvm(), "nvm is <not/> installed")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class NodeJsVersionMatchesNvmrc(Check):
|
|
69
|
+
name = "nodejs.nvmrc.version"
|
|
70
|
+
|
|
71
|
+
suggestions = {
|
|
72
|
+
OS.GENERIC: """
|
|
73
|
+
Run: <cmd>nvm install</cmd> (automatically picks up the right version from .nvmrc)
|
|
74
|
+
Run: <cmd>nvm use</cmd>
|
|
75
|
+
Run: <cmd>nvm alias default <version></cmd> to set the default node version to the newly
|
|
76
|
+
installed one
|
|
77
|
+
"""
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def check(self) -> CheckResult:
|
|
81
|
+
nvmrc_version = get_nvmrc_version()
|
|
82
|
+
if nvmrc_version is None:
|
|
83
|
+
return self.failed("Missing or invalid .nvmrc file")
|
|
84
|
+
|
|
85
|
+
active_version = get_nodejs_version()
|
|
86
|
+
if active_version is None:
|
|
87
|
+
return self.failed(f'node.js version "{nvmrc_version}" is not installed')
|
|
88
|
+
|
|
89
|
+
if active_version != nvmrc_version:
|
|
90
|
+
return self.failed(f'the active node.js version is {active_version}, "{nvmrc_version}" is required')
|
|
91
|
+
|
|
92
|
+
return self.passed(f"node.js version is {active_version}")
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import grp
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from stat import S_IMODE, S_ISGID
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from daktari.check import Check, CheckResult
|
|
8
|
+
from daktari.os import OS
|
|
9
|
+
from daktari.command_utils import get_stdout
|
|
10
|
+
from daktari.version_utils import get_simple_cli_version
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OnePasswordCliInstalled(Check):
|
|
14
|
+
name = "onePasswordCli.installed"
|
|
15
|
+
|
|
16
|
+
def __init__(self, required_version: Optional[str] = None, recommended_version: Optional[str] = None):
|
|
17
|
+
self.required_version = required_version
|
|
18
|
+
self.recommended_version = recommended_version
|
|
19
|
+
self.suggestions = {
|
|
20
|
+
OS.GENERIC: """
|
|
21
|
+
Install the 1Password CLI (op):
|
|
22
|
+
https://support.1password.com/command-line-getting-started/#set-up-the-command-line-tool""",
|
|
23
|
+
OS.OS_X: """
|
|
24
|
+
Use these commands to update 1pass-cli to correct version:
|
|
25
|
+
<cmd>brew tap glean-notes/homebrew-tap git@github.com:glean-notes/homebrew-tap</cmd>
|
|
26
|
+
<cmd>brew reinstall glean-notes/homebrew-tap/1password-cli</cmd>""",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def check(self) -> CheckResult:
|
|
30
|
+
installed_version = get_simple_cli_version("op")
|
|
31
|
+
return self.validate_semver_expression(
|
|
32
|
+
"1Password CLI", installed_version, self.required_version, self.recommended_version
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OnePasswordAccountConfigured(Check):
|
|
37
|
+
depends_on = [OnePasswordCliInstalled]
|
|
38
|
+
name = "onePassword.accountConfigured"
|
|
39
|
+
|
|
40
|
+
def __init__(self, account_shorthand: str):
|
|
41
|
+
self.account_shorthand = account_shorthand
|
|
42
|
+
self.account_url = f"{account_shorthand}.1password.com"
|
|
43
|
+
self.suggestions = {
|
|
44
|
+
OS.GENERIC: f"""
|
|
45
|
+
Use the 1Password desktop app integration: https://developer.1password.com/docs/cli/get-started/#sign-in
|
|
46
|
+
Otherwise:
|
|
47
|
+
<cmd>op signin -f --account {self.account_url}</cmd>""",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def check(self) -> CheckResult:
|
|
51
|
+
output = get_stdout("op account list")
|
|
52
|
+
if output is None:
|
|
53
|
+
return self.failed("1Password CLI command failed. Make sure it's installed and configured.")
|
|
54
|
+
|
|
55
|
+
account_present = contains_account(output, self.account_url)
|
|
56
|
+
|
|
57
|
+
if account_present:
|
|
58
|
+
return self.passed(f"{self.account_shorthand} is configured with OP CLI for the current user")
|
|
59
|
+
else:
|
|
60
|
+
return self.failed(f"{self.account_shorthand} is not configured with OP CLI for the current user")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def contains_account(op_account_list_output: str, account_url: str) -> bool:
|
|
64
|
+
return account_url in op_account_list_output
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# If not set up, this breaks the integration between cli and desktop app (at least on Ubuntu)
|
|
68
|
+
# https://github.com/NeoHsu/asdf-1password-cli/issues/6#issuecomment-1587502411
|
|
69
|
+
class OnePasswordCliOwnedByCorrectGroup(Check):
|
|
70
|
+
depends_on = [OnePasswordCliInstalled]
|
|
71
|
+
name = "onePasswordCli.ownedByCorrectGroup"
|
|
72
|
+
run_on = OS.UBUNTU
|
|
73
|
+
|
|
74
|
+
def __init__(self):
|
|
75
|
+
self.suggestions = {
|
|
76
|
+
OS.UBUNTU: """
|
|
77
|
+
Ensure the onepassword-cli group exists:
|
|
78
|
+
<cmd>sudo groupadd -f onepassword-cli</cmd>
|
|
79
|
+
Then update the group ownership and set group id when executing:
|
|
80
|
+
<cmd>sudo chgrp onepassword-cli $(asdf which op) && sudo chmod g+s $(asdf which op)</cmd>
|
|
81
|
+
""",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def check(self) -> CheckResult:
|
|
85
|
+
op_path = get_stdout(["sh", "-c", "asdf which op"])
|
|
86
|
+
if op_path is None:
|
|
87
|
+
return self.failed("op not found")
|
|
88
|
+
|
|
89
|
+
op_stat = os.stat(op_path)
|
|
90
|
+
group_id = op_stat.st_gid
|
|
91
|
+
group_name = grp.getgrgid(group_id)[0]
|
|
92
|
+
|
|
93
|
+
if group_name != "onepassword-cli":
|
|
94
|
+
return self.failed(f"op group should be onepassword-cli, but was {group_name}")
|
|
95
|
+
|
|
96
|
+
if S_IMODE(op_stat.st_mode) & S_ISGID == 0:
|
|
97
|
+
return self.failed("op does not set groupid when running")
|
|
98
|
+
|
|
99
|
+
return self.passed("op has correct group and sets groupid when running")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def account_exists(path: str, account_shorthand: str) -> bool:
|
|
103
|
+
with open(path) as f:
|
|
104
|
+
config = json.load(f)
|
|
105
|
+
return any(account.get("shorthand") == account_shorthand for account in config.get("accounts", []))
|
daktari/checks/python.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from daktari.check import Check, CheckResult
|
|
2
|
+
from daktari.os import OS
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PythonInstalled(Check):
|
|
6
|
+
def __init__(self, required_version: int):
|
|
7
|
+
self.required_version = required_version
|
|
8
|
+
self.name = f"python{required_version}.installed"
|
|
9
|
+
self.suggestions = {
|
|
10
|
+
OS.OS_X: f"<cmd>brew install python{required_version}</cmd>",
|
|
11
|
+
OS.UBUNTU: f"<cmd>sudo apt install python{required_version}-dev</cmd>",
|
|
12
|
+
OS.GENERIC: f"Download python {required_version}: https://www.python.org/downloads/",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def check(self) -> CheckResult:
|
|
16
|
+
return self.verify_install(f"python{self.required_version}")
|
daktari/checks/ssh.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from daktari.check import Check, CheckResult
|
|
2
|
+
from daktari.file_utils import file_contains_text_regex, get_absolute_path
|
|
3
|
+
from daktari.os import OS, detect_os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def is_ssh_configured_to_use_macos_keychain(ssh_config_path: str = "~/.ssh/config") -> bool:
|
|
7
|
+
absolute_ssh_config_path = get_absolute_path(ssh_config_path)
|
|
8
|
+
return file_contains_text_regex(
|
|
9
|
+
absolute_ssh_config_path, "IgnoreUnknown\\s+UseKeychain"
|
|
10
|
+
) and file_contains_text_regex(absolute_ssh_config_path, "UseKeychain\\s+yes")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SSHConfigSetup(Check):
|
|
14
|
+
name = "ssh.config.setup"
|
|
15
|
+
|
|
16
|
+
suggestions = {
|
|
17
|
+
OS.OS_X: """
|
|
18
|
+
Add "IgnoreUnknown UseKeychain" and "UseKeychain yes" to ~/.ssh/config.
|
|
19
|
+
E.g:
|
|
20
|
+
Host *
|
|
21
|
+
IgnoreUnknown UseKeychain
|
|
22
|
+
UseKeychain yes
|
|
23
|
+
IdentityFile ~/.ssh/id_rsa
|
|
24
|
+
"""
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def check(self) -> CheckResult:
|
|
28
|
+
if detect_os() != OS.OS_X:
|
|
29
|
+
return self.passed("Setup not required on non-Macbook devices")
|
|
30
|
+
else:
|
|
31
|
+
if not is_ssh_configured_to_use_macos_keychain():
|
|
32
|
+
return self.failed("'IgnoreUnknown UseKeychain' or 'UseKeychain yes' not present in ~/.ssh/config")
|
|
33
|
+
return self.passed("~/.ssh/config setup correctly")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from semver import VersionInfo
|
|
6
|
+
|
|
7
|
+
from daktari.check import Check, CheckResult
|
|
8
|
+
from daktari.command_utils import get_stdout
|
|
9
|
+
from daktari.os import OS
|
|
10
|
+
from daktari.version_utils import try_parse_semver
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TfenvInstalled(Check):
|
|
14
|
+
name = "tfenv.installed"
|
|
15
|
+
|
|
16
|
+
suggestions = {
|
|
17
|
+
OS.OS_X: "<cmd>brew install tfenv</cmd>",
|
|
18
|
+
OS.UBUNTU: """
|
|
19
|
+
<cmd>git clone https://github.com/tfutils/tfenv.git ~/.tfenv</cmd>
|
|
20
|
+
<cmd>sudo ln -s ~/.tfenv/bin/* /usr/local/bin</cmd>
|
|
21
|
+
""",
|
|
22
|
+
OS.GENERIC: "Install tfenv: https://github.com/tfutils/tfenv",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def check(self) -> CheckResult:
|
|
26
|
+
return self.verify_install("tfenv")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TerraformInstalled(Check):
|
|
30
|
+
def __init__(
|
|
31
|
+
self, required_version: Optional[str] = None, recommended_version: Optional[str] = None, use_tfenv: bool = False
|
|
32
|
+
):
|
|
33
|
+
self.required_version = required_version
|
|
34
|
+
self.recommended_version = recommended_version
|
|
35
|
+
self.name = "terraform.installed"
|
|
36
|
+
self.use_tfenv = use_tfenv
|
|
37
|
+
|
|
38
|
+
if use_tfenv:
|
|
39
|
+
self.depends_on = [TfenvInstalled]
|
|
40
|
+
|
|
41
|
+
# Read the required tf-version from tfenv (i.e. .terraform-version files)
|
|
42
|
+
tfenv_version = open(".terraform-version", "r").read().strip()
|
|
43
|
+
logging.debug(f"Terraform version required from .terraform-version file is: {tfenv_version}")
|
|
44
|
+
self.required_version = f"=={tfenv_version}"
|
|
45
|
+
self.suggestions = {OS.GENERIC: "<cmd>tfenv install</cmd>"}
|
|
46
|
+
else:
|
|
47
|
+
self.suggestions = {
|
|
48
|
+
OS.OS_X: "<cmd>brew tap hashicorp/tap && brew install hashicorp/tap/terraform</cmd>",
|
|
49
|
+
OS.GENERIC: "Install Terraform: https://learn.hashicorp.com/tutorials/terraform/install-cli",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def check(self) -> CheckResult:
|
|
53
|
+
installed_version = get_terraform_version()
|
|
54
|
+
return self.validate_semver_expression(
|
|
55
|
+
"Terraform", installed_version, self.required_version, self.recommended_version
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
version_pattern = re.compile("Terraform v([0-9\\.]+)")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_terraform_version() -> Optional[VersionInfo]:
|
|
63
|
+
raw_version = get_stdout("terraform version")
|
|
64
|
+
if raw_version:
|
|
65
|
+
match = version_pattern.search(raw_version)
|
|
66
|
+
if match:
|
|
67
|
+
version = try_parse_semver(match.group(1))
|
|
68
|
+
logging.debug(f"Terraform version: {version}")
|
|
69
|
+
return version
|
|
70
|
+
return None
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from unittest import mock
|
|
3
|
+
|
|
4
|
+
from daktari.check import CheckStatus
|
|
5
|
+
from daktari.checks.certs import CertificateIsNotExpired
|
|
6
|
+
from daktari.resource_utils import get_resource_path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestCertificateIsNotExpired(unittest.TestCase):
|
|
10
|
+
@mock.patch("daktari.checks.certs.crypto.load_certificate")
|
|
11
|
+
def test_passes_with_warning_if_cannot_determine_expiry(self, mock_get_not_after):
|
|
12
|
+
mock_get_not_after.return_value.get_notAfter.return_value = None
|
|
13
|
+
check = CertificateIsNotExpired(get_resource_path("mock_cert.pem").__str__())
|
|
14
|
+
result = check.check()
|
|
15
|
+
self.assertEqual(result.status, CheckStatus.PASS_WITH_WARNING)
|
|
16
|
+
self.assertEqual(result.summary, "Unable to determine expiry date of mock_cert.pem")
|
|
17
|
+
|
|
18
|
+
@mock.patch("daktari.checks.certs.crypto.load_certificate")
|
|
19
|
+
def test_passes_for_valid_cert(self, mock_get_not_after):
|
|
20
|
+
mock_get_not_after.return_value.get_notAfter.return_value = b"20501231235959Z"
|
|
21
|
+
check = CertificateIsNotExpired(get_resource_path("mock_cert.pem").__str__())
|
|
22
|
+
result = check.check()
|
|
23
|
+
self.assertEqual(result.status, CheckStatus.PASS)
|
|
24
|
+
self.assertEqual(result.summary, "mock_cert.pem is not expired")
|
|
25
|
+
|
|
26
|
+
@mock.patch("daktari.checks.certs.crypto.load_certificate")
|
|
27
|
+
def test_fails_for_expired_cert(self, mock_get_not_after):
|
|
28
|
+
mock_get_not_after.return_value.get_notAfter.return_value = b"20201231235959Z"
|
|
29
|
+
check = CertificateIsNotExpired(get_resource_path("mock_cert.pem").__str__())
|
|
30
|
+
result = check.check()
|
|
31
|
+
self.assertEqual(result.status, CheckStatus.FAIL)
|
|
32
|
+
self.assertEqual(result.summary, "mock_cert.pem expired on 2020-12-31 23:59:59")
|