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/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.0.
|
|
1
|
+
__version__ = "0.0.255"
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from daktari.check import Check, CheckResult
|
|
2
|
+
from daktari.os import OS, get_env_var_value
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AndroidNdkHomeSet(Check):
|
|
6
|
+
name = "android.ndkHomeSet"
|
|
7
|
+
|
|
8
|
+
def __init__(self, expected_version):
|
|
9
|
+
self.variable_name = "ANDROID_NDK_HOME"
|
|
10
|
+
self.expected_version = expected_version
|
|
11
|
+
self.suggestions = {
|
|
12
|
+
OS.GENERIC: f"""
|
|
13
|
+
Export {self.variable_name} in your shell config.
|
|
14
|
+
The expected value is ANDROID_SDK_HOME/ndk/{self.expected_version}.
|
|
15
|
+
If you manage your android sdk using Android Studio, you can find your ANDROID_SDK_HOME by going to
|
|
16
|
+
Tools > SDK Manager and copying the Android SDK location.
|
|
17
|
+
You may need to check under SDK Tools that you have the NDK installed.
|
|
18
|
+
"""
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def check(self) -> CheckResult:
|
|
22
|
+
expected_substring = f"ndk/{self.expected_version}"
|
|
23
|
+
return self.verify(
|
|
24
|
+
expected_substring in get_env_var_value(self.variable_name),
|
|
25
|
+
f"{self.variable_name} is <not/> set with expected version {self.expected_version}",
|
|
26
|
+
)
|
daktari/checks/aws.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from daktari.check import Check, CheckResult
|
|
2
|
+
from daktari.command_utils import can_run_command, get_stdout
|
|
3
|
+
from daktari.os import OS
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AWSCLIInstalled(Check):
|
|
7
|
+
name = "aws.cliInstalled"
|
|
8
|
+
|
|
9
|
+
suggestions = {
|
|
10
|
+
OS.OS_X: """<cmd>curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg" && \
|
|
11
|
+
sudo installer -pkg AWSCLIV2.pkg -target /</cmd>""",
|
|
12
|
+
OS.UBUNTU: """"<cmd>curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
|
|
13
|
+
unzip awscliv2.zip && \
|
|
14
|
+
sudo ./aws/install</cmd>""",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def check(self) -> CheckResult:
|
|
18
|
+
return self.verify(can_run_command("aws --version"), "AWS CLI is <not/> installed and on $PATH")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AWSProfileExists(Check):
|
|
22
|
+
depends_on = [AWSCLIInstalled]
|
|
23
|
+
|
|
24
|
+
def __init__(self, profile_name: str, suggestions: dict[str, str]):
|
|
25
|
+
self.profile_name = profile_name
|
|
26
|
+
self.name = f"aws.profileExists.{profile_name}"
|
|
27
|
+
self.suggestions = suggestions
|
|
28
|
+
|
|
29
|
+
def check(self) -> CheckResult:
|
|
30
|
+
output = get_stdout("aws configure list-profiles")
|
|
31
|
+
passed = bool(output and self.profile_name in output)
|
|
32
|
+
if not passed:
|
|
33
|
+
return self.failed(f"{self.profile_name} is not configured for the current user")
|
|
34
|
+
return self.passed(f"{self.profile_name} is configured for the current user")
|
daktari/checks/certs.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from OpenSSL import crypto
|
|
5
|
+
|
|
6
|
+
from daktari.check import Check, CheckResult
|
|
7
|
+
from daktari.os import OS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CertificateIsNotExpired(Check):
|
|
11
|
+
name = "certificate.isNotExpired"
|
|
12
|
+
|
|
13
|
+
def __init__(self, certificate_path: str):
|
|
14
|
+
self.certificate_path = certificate_path
|
|
15
|
+
self.suggestions = {
|
|
16
|
+
OS.GENERIC: f"Regenerate the certificate at {certificate_path}",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def check(self) -> CheckResult:
|
|
20
|
+
with open(self.certificate_path, "rb") as f:
|
|
21
|
+
cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
|
|
22
|
+
logging.debug(f"Raw expiry: {cert.get_notAfter()!r}")
|
|
23
|
+
|
|
24
|
+
expiry_bytes = cert.get_notAfter()
|
|
25
|
+
if expiry_bytes is None:
|
|
26
|
+
return self.passed_with_warning(
|
|
27
|
+
f"Unable to determine expiry date of {os.path.basename(self.certificate_path)}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
expiry = datetime.strptime(expiry_bytes.decode(), "%Y%m%d%H%M%SZ")
|
|
31
|
+
if expiry > datetime.now():
|
|
32
|
+
return self.passed(f"{os.path.basename(self.certificate_path)} is not expired")
|
|
33
|
+
else:
|
|
34
|
+
return self.failed(f"{os.path.basename(self.certificate_path)} expired on {expiry}")
|
daktari/checks/conan.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from daktari.check import Check, CheckResult
|
|
6
|
+
from daktari.command_utils import get_stdout
|
|
7
|
+
from daktari.os import OS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConanInstalled(Check):
|
|
11
|
+
name = "conan.installed"
|
|
12
|
+
|
|
13
|
+
def __init__(self, required_version: Optional[str] = None, recommended_version: Optional[str] = None):
|
|
14
|
+
self.required_version = required_version
|
|
15
|
+
self.recommended_version = recommended_version
|
|
16
|
+
self.suggestions = {OS.GENERIC: "Install conan: <cmd>pip install conan</cmd>"}
|
|
17
|
+
|
|
18
|
+
def check(self) -> CheckResult:
|
|
19
|
+
return self.verify_install("conan")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ConanProfileDetected(Check):
|
|
23
|
+
name = "conan.profileDetected"
|
|
24
|
+
|
|
25
|
+
def __init__(self, expected_string: str):
|
|
26
|
+
self.suggestions = {OS.GENERIC: "<cmd>conan profile detect</cmd>"}
|
|
27
|
+
self.expected_string = expected_string
|
|
28
|
+
self.depends_on = [ConanInstalled]
|
|
29
|
+
|
|
30
|
+
def check(self) -> CheckResult:
|
|
31
|
+
output = get_stdout("conan profile list")
|
|
32
|
+
expected_profile_detected = output is not None and self.expected_string in output
|
|
33
|
+
return self.verify(expected_profile_detected, f"conan profile {self.expected_string} <not/> detected")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ConanRemoteDetected(Check):
|
|
37
|
+
name = "conan.remoteDetected"
|
|
38
|
+
|
|
39
|
+
def __init__(self, remote_name: str, remote_url: str):
|
|
40
|
+
self.suggestions = {OS.GENERIC: f"<cmd>conan remote add {remote_name} {remote_url}</cmd>"}
|
|
41
|
+
self.remote_name = remote_name
|
|
42
|
+
self.remote_url = remote_url
|
|
43
|
+
self.depends_on = [ConanInstalled]
|
|
44
|
+
|
|
45
|
+
def check(self) -> CheckResult:
|
|
46
|
+
output = get_stdout("conan remote list -f json")
|
|
47
|
+
if output is None:
|
|
48
|
+
return self.failed("No conan remotes configured for the current user.")
|
|
49
|
+
remote_json = json.loads(output)
|
|
50
|
+
remote = next(filter(lambda remote_details: remote_details.get("name") == self.remote_name, remote_json), None)
|
|
51
|
+
if remote is None:
|
|
52
|
+
return self.failed(f"{self.remote_name} conan remote is not configured for the current user.")
|
|
53
|
+
|
|
54
|
+
configured_url = remote["url"].strip("/")
|
|
55
|
+
logging.debug(f"{self.remote_name} conan remote is configured with URL {configured_url}.")
|
|
56
|
+
|
|
57
|
+
if configured_url != self.remote_url:
|
|
58
|
+
self.suggestions = {
|
|
59
|
+
OS.GENERIC: f"<cmd>conan remote update --url {self.remote_url} {self.remote_name}</cmd>"
|
|
60
|
+
}
|
|
61
|
+
return self.failed(
|
|
62
|
+
f"{self.remote_name} conan remote is configured with URL {configured_url}, expected {self.remote_url}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if not remote["enabled"]:
|
|
66
|
+
self.suggestions = {OS.GENERIC: f"<cmd>conan remote enable {self.remote_name}</cmd>"}
|
|
67
|
+
return self.failed(f"{self.remote_name} conan remote is not enabled.")
|
|
68
|
+
|
|
69
|
+
return self.passed(f"{self.remote_name} conan remote is configured for the current user.")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ConanRemoteAuthenticated(Check):
|
|
73
|
+
name = "conan.remoteAuthenticated"
|
|
74
|
+
|
|
75
|
+
def __init__(self, remote_name: str, authentication_command: Optional[str] = None):
|
|
76
|
+
self.suggestions = (
|
|
77
|
+
{OS.GENERIC: authentication_command}
|
|
78
|
+
if authentication_command
|
|
79
|
+
else {OS.GENERIC: f"<cmd>conan remote login {remote_name}</cmd>"}
|
|
80
|
+
)
|
|
81
|
+
self.remote_name = remote_name
|
|
82
|
+
self.depends_on = [ConanRemoteDetected]
|
|
83
|
+
|
|
84
|
+
def check(self) -> CheckResult:
|
|
85
|
+
output = get_stdout("conan remote list-users -f json")
|
|
86
|
+
if output is None:
|
|
87
|
+
return self.failed("No conan remotes configured for the current user.")
|
|
88
|
+
remote_json = json.loads(output)
|
|
89
|
+
remote = next(filter(lambda remote_details: remote_details.get("name") == self.remote_name, remote_json), None)
|
|
90
|
+
if remote is None:
|
|
91
|
+
return self.failed(f"{self.remote_name} conan remote is not configured for the current user.")
|
|
92
|
+
|
|
93
|
+
if not remote["authenticated"]:
|
|
94
|
+
return self.failed(f"{self.remote_name} conan remote is not authenticated.")
|
|
95
|
+
|
|
96
|
+
return self.passed(f"{self.remote_name} conan remote is authenticated for the current user.")
|
daktari/checks/direnv.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from daktari.command_utils import get_stdout
|
|
4
|
+
|
|
5
|
+
from daktari.os import OS
|
|
6
|
+
from daktari.check import Check, CheckResult
|
|
7
|
+
from daktari.version_utils import get_simple_cli_version
|
|
8
|
+
from daktari.file_utils import file_contains_text
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from os import getcwd
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DirenvInstalled(Check):
|
|
14
|
+
name = "direnv.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: "Install direnv: https://direnv.net/#getting-started",
|
|
21
|
+
OS.OS_X: "Install direnv using brew: <cmd>brew install direnv</cmd>",
|
|
22
|
+
OS.UBUNTU: "Install direnv using apt-get: <cmd>sudo apt-get install direnv</cmd>",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def check(self) -> CheckResult:
|
|
26
|
+
installed_version = get_simple_cli_version("direnv")
|
|
27
|
+
return self.validate_semver_expression(
|
|
28
|
+
"direnv", installed_version, self.required_version, self.recommended_version
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class EnvrcContainsText(Check):
|
|
33
|
+
name = "direnv.envrc.containsText"
|
|
34
|
+
|
|
35
|
+
def __init__(self, expected_string: str, suggestion: str):
|
|
36
|
+
self.file_path = f"{getcwd()}/.envrc"
|
|
37
|
+
self.expected_string = expected_string
|
|
38
|
+
self.pass_fail_message = f"{self.file_path} does <not/> contain '{expected_string}'"
|
|
39
|
+
self.suggestions = {OS.GENERIC: suggestion}
|
|
40
|
+
|
|
41
|
+
def check(self) -> CheckResult:
|
|
42
|
+
return self.verify(file_contains_text(self.file_path, self.expected_string), self.pass_fail_message)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DirenvAllowed(Check):
|
|
46
|
+
name = "direnv.allowed"
|
|
47
|
+
depends_on = [DirenvInstalled, EnvrcContainsText]
|
|
48
|
+
|
|
49
|
+
def __init__(self):
|
|
50
|
+
self.suggestions = {OS.GENERIC: "<cmd>direnv allow .</cmd>"}
|
|
51
|
+
|
|
52
|
+
def check(self) -> CheckResult:
|
|
53
|
+
direnv_status = get_stdout("direnv status")
|
|
54
|
+
if direnv_status is None:
|
|
55
|
+
return self.failed("direnv status returned no output")
|
|
56
|
+
cwd = getcwd()
|
|
57
|
+
query = f"Found RC path {cwd}/.envrc(\n.*)*Found RC allowed (true|0)"
|
|
58
|
+
direnv_allowed = re.search(query, direnv_status) is not None
|
|
59
|
+
return self.verify(direnv_allowed, f"{cwd} is <not/> allowed to use direnv")
|
daktari/checks/docker.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
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 DockerInstalled(Check):
|
|
14
|
+
name = "docker.installed"
|
|
15
|
+
|
|
16
|
+
suggestions = {
|
|
17
|
+
OS.GENERIC: "Install docker: https://docs.docker.com/get-docker/",
|
|
18
|
+
OS.OS_X: "Install docker: https://docs.docker.com/docker-for-mac/install/",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def __init__(self, required_version: Optional[str] = None):
|
|
22
|
+
self.required_version = required_version
|
|
23
|
+
|
|
24
|
+
def check(self) -> CheckResult:
|
|
25
|
+
installed_version = get_docker_version()
|
|
26
|
+
return self.validate_semver_expression("Docker", installed_version, self.required_version)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
major_version_pattern = re.compile("Docker version ([0-9.]+)")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_docker_version() -> Optional[VersionInfo]:
|
|
33
|
+
raw_version = get_stdout("docker --version")
|
|
34
|
+
if raw_version:
|
|
35
|
+
match = major_version_pattern.search(raw_version)
|
|
36
|
+
if match:
|
|
37
|
+
version_string = match.group(1)
|
|
38
|
+
version = try_parse_semver(version_string)
|
|
39
|
+
logging.debug(f"Docker version - raw: {version_string}, parsed: {version}")
|
|
40
|
+
return version
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DockerComposeInstalled(Check):
|
|
45
|
+
name = "docker-compose.installed"
|
|
46
|
+
|
|
47
|
+
suggestions = {OS.GENERIC: "Install docker-compose: https://docs.docker.com/compose/install/"}
|
|
48
|
+
|
|
49
|
+
def check(self) -> CheckResult:
|
|
50
|
+
return self.verify_install("docker-compose")
|
daktari/checks/files.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from os.path import expanduser
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from daktari.check import Check, CheckResult
|
|
5
|
+
from daktari.file_utils import dir_exists, file_exists, get_file_owner
|
|
6
|
+
from daktari.os import OS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FilesExist(Check):
|
|
10
|
+
name = "files.exist"
|
|
11
|
+
file_paths: List[str] = []
|
|
12
|
+
pass_fail_message = ""
|
|
13
|
+
|
|
14
|
+
def check(self) -> CheckResult:
|
|
15
|
+
files_exist = all([file_exists(expanduser(file_path)) for file_path in self.file_paths])
|
|
16
|
+
return self.verify(files_exist, self.pass_fail_message)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FileExists(FilesExist):
|
|
20
|
+
name = "file.exists"
|
|
21
|
+
|
|
22
|
+
def __init__(self, file_path: str, suggestion: str):
|
|
23
|
+
self.file_paths = [file_path]
|
|
24
|
+
self.pass_fail_message = f"{file_path} is <not/> present"
|
|
25
|
+
self.suggestions = {OS.GENERIC: suggestion}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DirsExist(Check):
|
|
29
|
+
name = "directories.exist"
|
|
30
|
+
dir_paths: List[str] = []
|
|
31
|
+
pass_fail_message = ""
|
|
32
|
+
|
|
33
|
+
def check(self) -> CheckResult:
|
|
34
|
+
dirs_exist = all([dir_exists(expanduser(dir_path)) for dir_path in self.dir_paths])
|
|
35
|
+
return self.verify(dirs_exist, self.pass_fail_message)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DirExists(DirsExist):
|
|
39
|
+
name = "dir.exists"
|
|
40
|
+
|
|
41
|
+
def __init__(self, dir_path: str, suggestion: str):
|
|
42
|
+
self.dir_paths = [dir_path]
|
|
43
|
+
self.pass_fail_message = f"{dir_path} is <not/> present"
|
|
44
|
+
self.suggestions = {OS.GENERIC: suggestion}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FilesOwnedByUser(Check):
|
|
48
|
+
name = "files.ownedByUser"
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
file_paths: List[str],
|
|
53
|
+
expected_owner: str = "root",
|
|
54
|
+
pass_fail_message: str = "",
|
|
55
|
+
follow_symlinks: bool = False,
|
|
56
|
+
):
|
|
57
|
+
self.file_paths = file_paths
|
|
58
|
+
self.expected_owner = expected_owner
|
|
59
|
+
file_paths_str = ", ".join(file_paths)
|
|
60
|
+
self.pass_fail_message = pass_fail_message or f"{file_paths_str} are <not/> owned by {expected_owner}"
|
|
61
|
+
self.follow_symlinks = follow_symlinks
|
|
62
|
+
|
|
63
|
+
def check(self) -> CheckResult:
|
|
64
|
+
for file_path in self.file_paths:
|
|
65
|
+
expanded_file_path = expanduser(file_path)
|
|
66
|
+
if file_exists(expanded_file_path):
|
|
67
|
+
if get_file_owner(expanded_file_path, self.follow_symlinks) != self.expected_owner:
|
|
68
|
+
return self.verify(False, self.pass_fail_message)
|
|
69
|
+
else:
|
|
70
|
+
return self.failed(f"{expanded_file_path} is not present")
|
|
71
|
+
|
|
72
|
+
return self.verify(True, self.pass_fail_message)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from semver import VersionInfo
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from daktari.check import Check, CheckResult
|
|
7
|
+
from daktari.command_utils import get_stdout
|
|
8
|
+
from daktari.os import OS
|
|
9
|
+
from daktari.version_utils import try_parse_semver
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
flutter_version_pattern = re.compile(r"Flutter\s+([\d\.]+)")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_flutter_version_output(version_output: Optional[str]) -> Optional[VersionInfo]:
|
|
16
|
+
if version_output:
|
|
17
|
+
match = flutter_version_pattern.search(version_output)
|
|
18
|
+
if match:
|
|
19
|
+
version_string = match.group(1)
|
|
20
|
+
logging.debug(f"Flutter version string: {version_string}")
|
|
21
|
+
return try_parse_semver(version_string)
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_flutter_version() -> Optional[VersionInfo]:
|
|
26
|
+
version_output = get_stdout("flutter --version")
|
|
27
|
+
return parse_flutter_version_output(version_output)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FlutterInstalled(Check):
|
|
31
|
+
name = "flutter.installed"
|
|
32
|
+
|
|
33
|
+
suggestions = {
|
|
34
|
+
OS.GENERIC: "Install Flutter: https://flutter.dev/docs/get-started/install",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def __init__(self, required_version: Optional[str] = None, recommended_version: Optional[str] = None):
|
|
38
|
+
self.required_version = required_version
|
|
39
|
+
self.recommended_version = recommended_version
|
|
40
|
+
|
|
41
|
+
def check(self) -> CheckResult:
|
|
42
|
+
flutter_version = get_flutter_version()
|
|
43
|
+
logging.info(f"Flutter version: {flutter_version}")
|
|
44
|
+
return self.validate_semver_expression(
|
|
45
|
+
"Flutter", flutter_version, self.required_version, self.recommended_version
|
|
46
|
+
)
|
daktari/checks/git.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from daktari.check import Check, CheckResult
|
|
5
|
+
from daktari.command_utils import can_run_command, get_stdout
|
|
6
|
+
from daktari.file_utils import file_contains_text, is_ascii
|
|
7
|
+
from daktari.os import OS
|
|
8
|
+
from daktari.version_utils import get_simple_cli_version
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GitInstalled(Check):
|
|
12
|
+
name = "git.installed"
|
|
13
|
+
|
|
14
|
+
suggestions = {
|
|
15
|
+
OS.OS_X: "<cmd>brew install git</cmd>",
|
|
16
|
+
OS.UBUNTU: "<cmd>sudo apt install git</cmd>",
|
|
17
|
+
OS.GENERIC: "Install Git: https://git-scm.com/downloads",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def check(self) -> CheckResult:
|
|
21
|
+
if can_run_command("git version"):
|
|
22
|
+
return self.passed("Git is installed")
|
|
23
|
+
else:
|
|
24
|
+
return self.failed("Could not find git on the path")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GitLfsInstalled(Check):
|
|
28
|
+
name = "git.lfs.installed"
|
|
29
|
+
depends_on = [GitInstalled]
|
|
30
|
+
|
|
31
|
+
suggestions = {
|
|
32
|
+
OS.OS_X: "<cmd>brew install git-lfs</cmd>",
|
|
33
|
+
OS.UBUNTU: "<cmd>sudo apt install git-lfs</cmd>",
|
|
34
|
+
OS.GENERIC: "Install Git LFS: https://github.com/git-lfs/git-lfs/wiki/Installation",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def check(self) -> CheckResult:
|
|
38
|
+
return self.verify(can_run_command("git lfs version"), "Git LFS is <not/> installed")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class GitLfsSetUpForUser(Check):
|
|
42
|
+
name = "git.lfs.setUpForUser"
|
|
43
|
+
depends_on = [GitLfsInstalled]
|
|
44
|
+
|
|
45
|
+
suggestions = {
|
|
46
|
+
OS.GENERIC: """
|
|
47
|
+
Set up Git LFS for your user account:
|
|
48
|
+
<cmd>git lfs install</cmd>
|
|
49
|
+
"""
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def check(self) -> CheckResult:
|
|
53
|
+
output = get_stdout("git lfs env")
|
|
54
|
+
passed = bool(output and "git-lfs filter-process" in output)
|
|
55
|
+
return self.verify(passed, "Git LFS is <not/> set up for the current user")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class GitLfsFilesDownloaded(Check):
|
|
59
|
+
name = "git.lfs.filesDownloaded"
|
|
60
|
+
depends_on = [GitLfsSetUpForUser]
|
|
61
|
+
|
|
62
|
+
suggestions = {
|
|
63
|
+
OS.GENERIC: """
|
|
64
|
+
Download all Git LFS files and update working copy with the downloaded content:
|
|
65
|
+
<cmd>git lfs pull</cmd>
|
|
66
|
+
"""
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def check(self) -> CheckResult:
|
|
70
|
+
output = get_stdout("git lfs ls-files") or ""
|
|
71
|
+
files_not_downloaded = [line.split()[2] for line in output.splitlines() if line.split()[1] == "-"]
|
|
72
|
+
for file in files_not_downloaded:
|
|
73
|
+
logging.info(f"Git LFS file not downloaded: {file}")
|
|
74
|
+
passed = len(files_not_downloaded) == 0
|
|
75
|
+
return self.verify(passed, "Git LFS files have <not/> been downloaded")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class GitCryptInstalled(Check):
|
|
79
|
+
name = "git.crypt.installed"
|
|
80
|
+
depends_on = [GitInstalled]
|
|
81
|
+
|
|
82
|
+
suggestions = {
|
|
83
|
+
OS.OS_X: "<cmd>brew install git-crypt</cmd>",
|
|
84
|
+
OS.UBUNTU: "<cmd>sudo apt install git-crypt</cmd>",
|
|
85
|
+
OS.GENERIC: "Install git-crypt: https://www.agwa.name/projects/git-crypt/",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
def check(self) -> CheckResult:
|
|
89
|
+
return self.verify(can_run_command("git crypt version"), "git-crypt is <not/> installed")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class GitCryptUnlocked(Check):
|
|
93
|
+
name = "git.crypt.unlocked"
|
|
94
|
+
depends_on = [GitCryptInstalled]
|
|
95
|
+
|
|
96
|
+
def __init__(self, fileToCheck: str):
|
|
97
|
+
self.fileToCheck = fileToCheck
|
|
98
|
+
|
|
99
|
+
suggestions = {
|
|
100
|
+
OS.GENERIC: """
|
|
101
|
+
Unlock this repository with:
|
|
102
|
+
<cmd>git-crypt unlock</cmd>
|
|
103
|
+
""",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def check(self) -> CheckResult:
|
|
107
|
+
is_unlocked = is_ascii(self.fileToCheck)
|
|
108
|
+
return self.verify(is_unlocked, "Encrypted files have <not/> been unlocked")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class PreCommitInstalled(Check):
|
|
112
|
+
name = "preCommit.installed"
|
|
113
|
+
depends_on = [GitInstalled]
|
|
114
|
+
|
|
115
|
+
def __init__(self, required_version: Optional[str] = None, recommended_version: Optional[str] = None):
|
|
116
|
+
self.required_version = required_version
|
|
117
|
+
self.recommended_version = recommended_version
|
|
118
|
+
|
|
119
|
+
suggestions = {
|
|
120
|
+
OS.OS_X: "<cmd>brew install pre-commit</cmd>",
|
|
121
|
+
OS.GENERIC: "Install pre-commit: https://pre-commit.com/#installation",
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
def check(self) -> CheckResult:
|
|
125
|
+
installed_version = get_simple_cli_version("pre-commit")
|
|
126
|
+
return self.validate_semver_expression(
|
|
127
|
+
"pre-commit", installed_version, self.required_version, self.recommended_version
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class PreCommitGitHooksInstalled(Check):
|
|
132
|
+
name = "preCommit.gitHooksInstalled"
|
|
133
|
+
depends_on = [PreCommitInstalled]
|
|
134
|
+
|
|
135
|
+
suggestions = {
|
|
136
|
+
OS.GENERIC: "<cmd>pre-commit install</cmd>",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
def check(self) -> CheckResult:
|
|
140
|
+
git_hooks_installed = file_contains_text(".git/hooks/pre-commit", "pre-commit.com")
|
|
141
|
+
return self.verify(git_hooks_installed, "pre-commit Git hooks are <not/> installed")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class GpgInstalled(Check):
|
|
145
|
+
name = "gpg.installed"
|
|
146
|
+
|
|
147
|
+
suggestions = {
|
|
148
|
+
OS.OS_X: "<cmd>brew install gpg2 gnupg pinentry-mac</cmd>",
|
|
149
|
+
OS.UBUNTU: "<cmd>sudo apt install gpg</cmd>",
|
|
150
|
+
OS.GENERIC: "Install gpg: https://gnupg.org/",
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
def check(self) -> CheckResult:
|
|
154
|
+
return self.verify_install("gpg")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class GitCommitSigningSetUp(Check):
|
|
158
|
+
name = "git.commitSigningSetUp"
|
|
159
|
+
|
|
160
|
+
suggestions = {
|
|
161
|
+
OS.OS_X: "Follow instructions to set up commit signing: "
|
|
162
|
+
"https://gist.github.com/troyfontaine/18c9146295168ee9ca2b30c00bd1b41e#file-2-using-gpg-md",
|
|
163
|
+
OS.UBUNTU: "Follow instructions to set up commit signing: "
|
|
164
|
+
"https://brain2life.hashnode.dev/how-to-sign-your-git-commits-in-ubuntu-2004-and-why-you-need-it",
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def check(self) -> CheckResult:
|
|
168
|
+
key = get_stdout("git config user.signingkey")
|
|
169
|
+
passed = key is not None and key != ""
|
|
170
|
+
return self.verify(passed, "user.signingkey is <not/> set")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class GitCommitAutoSigningEnabled(Check):
|
|
174
|
+
name = "git.commitAutoSigningEnabled"
|
|
175
|
+
depends_on = [GitCommitSigningSetUp]
|
|
176
|
+
|
|
177
|
+
suggestions = {OS.GENERIC: "<cmd>git config commit.gpgsign true</cmd>"}
|
|
178
|
+
|
|
179
|
+
def check(self) -> CheckResult:
|
|
180
|
+
setting = get_stdout("git config commit.gpgsign")
|
|
181
|
+
passed = setting == "true"
|
|
182
|
+
return self.verify(passed, "commit.gpgsign is <not/> enabled")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class GitCommitSigningFormat(Check):
|
|
186
|
+
name = "git.commitSigningFormat"
|
|
187
|
+
|
|
188
|
+
def __init__(self, required_format: str, suggestion: str):
|
|
189
|
+
self.required_format = required_format
|
|
190
|
+
self.suggestions = {OS.GENERIC: suggestion}
|
|
191
|
+
|
|
192
|
+
def check(self) -> CheckResult:
|
|
193
|
+
format_setting = get_stdout("git config gpg.format")
|
|
194
|
+
return self.verify(
|
|
195
|
+
format_setting == self.required_format,
|
|
196
|
+
f"gpg.format is {self.required_format}",
|
|
197
|
+
f"gpg.format is not {self.required_format}: {format_setting}",
|
|
198
|
+
)
|