version.py 5 KB
Newer Older
Dmitry Volodin's avatar
Dmitry Volodin committed
1
2
3
# ---------------------------------------------------------------------
# NOC components versions
# ---------------------------------------------------------------------
4
# Copyright (C) 2007-2022 The NOC Project
Dmitry Volodin's avatar
Dmitry Volodin committed
5
6
7
8
9
10
11
12
# See LICENSE for details
# ---------------------------------------------------------------------

# Python modules
import os
import sys
import subprocess
import platform
13
from typing import Optional, Dict
Dmitry Volodin's avatar
Dmitry Volodin committed
14

Dmitry Volodin's avatar
Dmitry Volodin committed
15
16
17
18
# NOC modules
from noc.config import config

CHANGESET_LEN = 8
19
BRAND_PATH = config.get_customized_paths("BRAND", prefer_custom=True)
20
21
22
WHICH = "which"
if os.name == "nt":
    WHICH = "where"
Dmitry Volodin's avatar
Dmitry Volodin committed
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42


class cachedproperty(object):
    def __init__(self, f):
        self.f = f
        self.n = "_%s" % f.__name__
        self.__doc__ = f.__doc__

    def __get__(self, instance, owner):
        if instance is None:
            return self
        v = getattr(instance, self.n, None)
        if v is None:
            v = self.f(instance)
            setattr(instance, self.n, v)
        return v


class Version(object):
    @cachedproperty
43
    def has_git(self) -> bool:
Dmitry Volodin's avatar
Dmitry Volodin committed
44
45
46
47
48
49
        """
        Check .git directory is exists and git executable is in $PATH
        :return:
        """
        if os.path.exists(".git"):
            with open(os.devnull, "w") as null:
50
                return subprocess.call([WHICH, "git"], stdout=null) == 0
Dmitry Volodin's avatar
Dmitry Volodin committed
51
52
53
        return False

    @cachedproperty
54
    def branch(self) -> str:
Dmitry Volodin's avatar
Dmitry Volodin committed
55
56
57
58
59
        """
        Returns current branch
        :return:
        """
        if self.has_git:
60
61
62
63
            return subprocess.check_output(
                ["git", "rev-parse", "--abbrev-ref", "HEAD"], encoding="utf-8"
            ).strip()
        return ""
Dmitry Volodin's avatar
Dmitry Volodin committed
64
65

    @cachedproperty
66
    def changeset(self) -> str:
Dmitry Volodin's avatar
Dmitry Volodin committed
67
68
69
70
71
        """
        Returns current changeset
        :return:
        """
        if self.has_git:
72
73
74
75
            return (subprocess.check_output(["git", "rev-parse", "HEAD"], encoding="utf-8"))[
                :CHANGESET_LEN
            ]
        return ""
Dmitry Volodin's avatar
Dmitry Volodin committed
76
77

    @cachedproperty
78
79
80
81
82
    def version(self) -> str:
        def static_version() -> str:
            """
            Read VERSION file
            """
Dmitry Volodin's avatar
Dmitry Volodin committed
83
84
85
            with open("VERSION") as f:
                return f.read().strip()

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
        if not self.has_git:
            return static_version()
        try:
            v = subprocess.check_output(
                ["git", "describe", "--tags", f"--abbrev={CHANGESET_LEN}"], encoding="utf-8"
            )
        except subprocess.CalledProcessError:
            return static_version()  # Git is broken, fallback
        if "-" not in v:
            return v.strip()
        r = v.rsplit("-", 2)
        if len(r) < 3:
            return v.strip()
        v, n, cs = r
        kw = {
            "version": v,
            "branch": self.branch,
            "number": n,
            "changeset": cs[1 : CHANGESET_LEN + 1],
        }
        return config.version_format % kw

Dmitry Volodin's avatar
Dmitry Volodin committed
108
    @cachedproperty
109
    def brand(self) -> str:
110
111
112
113
        for p in BRAND_PATH:
            if os.path.exists(p):
                with open(p) as f:
                    return f.read().strip()
114
        return config.brand
Dmitry Volodin's avatar
Dmitry Volodin committed
115
116

    @cachedproperty
117
    def os_version(self) -> str:
Dmitry Volodin's avatar
Dmitry Volodin committed
118
119
120
        return " ".join(os.uname())

    @cachedproperty
121
    def os_brand(self) -> Optional[str]:
Dmitry Volodin's avatar
Dmitry Volodin committed
122
123
124
125
126
127
        o = os.uname()[0].lower()
        if o == "linux":
            # os-release
            if os.path.exists("/etc/os-release"):
                vdata = {}
                with open("/etc/os-release") as f:
Dmitry Volodin's avatar
Dmitry Volodin committed
128
129
                    for line in f:
                        if "=" not in line:
Dmitry Volodin's avatar
Dmitry Volodin committed
130
                            continue
Dmitry Volodin's avatar
Dmitry Volodin committed
131
132
                        line = line.strip()
                        k, v = line.split("=", 1)
Dmitry Volodin's avatar
Dmitry Volodin committed
133
                        if v.startswith('"') and v.endswith('"'):
Dmitry Volodin's avatar
Dmitry Volodin committed
134
135
136
137
138
139
140
141
142
143
                            v = v[1:-1]
                        vdata[k] = v
                return "%s %s" % (vdata["NAME"], vdata["VERSION_ID"])
            # Old SuSE?
            if os.path.exists("/etc/SuSE-release"):
                # SuSE
                with open("/etc/SuSE-release") as f:
                    return f.readline().strip()
            # try lsb_release -d
            try:
144
                b = subprocess.check_output(["lsb_release", "-d"], encoding="utf-8")
Dmitry Volodin's avatar
Dmitry Volodin committed
145
146
147
148
149
150
151
152
153
154
155
156
157
                return b.split(":", 1)[1].strip()
            except OSError:
                pass
            return "Unknown Linux"
        elif o == "freebsd":
            u = os.uname()
            return "%s %s" % (u[0], u[2])
        elif o == "darwin":
            # OS X
            return "Mac OS X %s" % platform.mac_ver()[0]
        return None

    @cachedproperty
158
    def python_version(self) -> str:
Dmitry Volodin's avatar
Dmitry Volodin committed
159
160
161
        return sys.version.split()[0]

    @cachedproperty
162
    def process(self) -> str:
Dmitry Volodin's avatar
Dmitry Volodin committed
163
164
165
166
167
168
169
170
        argv = [v for v in sys.argv if v]
        if not argv:
            return sys.executable
        if argv[0].endswith("python") and len(argv) > 1:
            return argv[1]
        return argv[0]

    @cachedproperty
171
    def package_versions(self) -> Dict[str, str]:
Dmitry Volodin's avatar
Dmitry Volodin committed
172
        return {"Python": self.python_version}
Dmitry Volodin's avatar
Dmitry Volodin committed
173

Dmitry Volodin's avatar
Dmitry Volodin committed
174

Dmitry Volodin's avatar
Dmitry Volodin committed
175
176
# Singleton instance
version = Version()