|
| 1 | +package clients |
| 2 | + |
| 3 | +import ( |
| 4 | + "os" |
| 5 | + "path/filepath" |
| 6 | + "runtime" |
| 7 | + "strings" |
| 8 | + |
| 9 | + "github.com/qase-tms/qase-go/pkg/qase-go/domain" |
| 10 | + "golang.org/x/mod/modfile" |
| 11 | +) |
| 12 | + |
| 13 | +// HostData contains information about the host system and package versions |
| 14 | +type HostData struct { |
| 15 | + System string // OS name (e.g., "Linux", "Darwin", "Windows") |
| 16 | + MachineName string // Machine/hostname |
| 17 | + Release string // OS release version |
| 18 | + Version string // OS version |
| 19 | + Arch string // Architecture (e.g., "amd64", "arm64") |
| 20 | + Framework string // Testing framework name (e.g., "testing", "testify") |
| 21 | + FrameworkVersion string // Testing framework version |
| 22 | + Reporter string // Reporter name (e.g., "qase-go") |
| 23 | + ReporterVersion string // Reporter version |
| 24 | + Commons string // Commons/core version |
| 25 | + APIClientV1 string // API client v1 version |
| 26 | + APIClientV2 string // API client v2 version |
| 27 | +} |
| 28 | + |
| 29 | +// GetHostInfo collects system information and package versions |
| 30 | +// frameworkPackage: name of the testing framework (e.g., "testing", "testify") |
| 31 | +// frameworkVersion: version of the testing framework |
| 32 | +// reporterName: name of the reporter (e.g., "qase-go") |
| 33 | +// reporterVersion: version of the reporter |
| 34 | +func GetHostInfo() *HostData { |
| 35 | + hostname, _ := os.Hostname() |
| 36 | + if hostname == "" { |
| 37 | + hostname = "unknown" |
| 38 | + } |
| 39 | + |
| 40 | + // Get OS information |
| 41 | + osName := runtime.GOOS |
| 42 | + arch := runtime.GOARCH |
| 43 | + |
| 44 | + // Get Go version and remove "go" prefix |
| 45 | + goVersion := runtime.Version() |
| 46 | + goVersion = strings.TrimPrefix(goVersion, "go") |
| 47 | + |
| 48 | + // Try to get module versions from go.mod |
| 49 | + apiClientV1Version, apiClientV2Version := getModuleVersionsFromGoMod() |
| 50 | + |
| 51 | + return &HostData{ |
| 52 | + System: osName, |
| 53 | + MachineName: hostname, |
| 54 | + Release: "", |
| 55 | + Version: "", |
| 56 | + Arch: arch, |
| 57 | + Framework: "go", |
| 58 | + FrameworkVersion: goVersion, |
| 59 | + Reporter: "qase-go", |
| 60 | + ReporterVersion: domain.Version, |
| 61 | + Commons: domain.Version, |
| 62 | + APIClientV1: apiClientV1Version, |
| 63 | + APIClientV2: apiClientV2Version, |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +// getModuleVersionsFromGoMod reads go.mod file and extracts versions of API client modules |
| 68 | +// Returns versions for qase-api-client and qase-api-v2-client |
| 69 | +func getModuleVersionsFromGoMod() (string, string) { |
| 70 | + var apiClientV1Version, apiClientV2Version string |
| 71 | + |
| 72 | + var goModPath string |
| 73 | + |
| 74 | + // First, try to find go.mod relative to this package file |
| 75 | + // This is more reliable than using working directory |
| 76 | + _, filename, _, ok := runtime.Caller(1) // Get caller (GetHostInfo) location |
| 77 | + if ok { |
| 78 | + packageDir := filepath.Dir(filename) |
| 79 | + // Go up to find go.mod (pkg/qase-go/clients -> pkg/qase-go -> go.mod) |
| 80 | + for i := 0; i < 2; i++ { |
| 81 | + packageDir = filepath.Dir(packageDir) |
| 82 | + candidate := filepath.Join(packageDir, "go.mod") |
| 83 | + if _, err := os.Stat(candidate); err == nil { |
| 84 | + goModPath = candidate |
| 85 | + break |
| 86 | + } |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + // Fallback: try to find go.mod file starting from current directory and going up |
| 91 | + if goModPath == "" { |
| 92 | + dir, err := os.Getwd() |
| 93 | + if err == nil { |
| 94 | + for { |
| 95 | + candidate := filepath.Join(dir, "go.mod") |
| 96 | + if _, err := os.Stat(candidate); err == nil { |
| 97 | + goModPath = candidate |
| 98 | + break |
| 99 | + } |
| 100 | + |
| 101 | + parent := filepath.Dir(dir) |
| 102 | + if parent == dir { |
| 103 | + // Reached root directory |
| 104 | + break |
| 105 | + } |
| 106 | + dir = parent |
| 107 | + } |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + if goModPath == "" { |
| 112 | + return "", "" |
| 113 | + } |
| 114 | + |
| 115 | + // Read go.mod file |
| 116 | + data, err := os.ReadFile(goModPath) |
| 117 | + if err != nil { |
| 118 | + return "", "" |
| 119 | + } |
| 120 | + |
| 121 | + // Parse go.mod file |
| 122 | + file, err := modfile.Parse(goModPath, data, nil) |
| 123 | + if err != nil { |
| 124 | + return "", "" |
| 125 | + } |
| 126 | + |
| 127 | + // Find versions of API client modules |
| 128 | + for _, req := range file.Require { |
| 129 | + modulePath := req.Mod.Path |
| 130 | + version := req.Mod.Version |
| 131 | + |
| 132 | + if strings.Contains(modulePath, "qase-api-client") && !strings.Contains(modulePath, "qase-api-v2-client") { |
| 133 | + // Remove "v" prefix if present, we'll add it later in buildXClientHeader |
| 134 | + apiClientV1Version = strings.TrimPrefix(version, "v") |
| 135 | + } else if strings.Contains(modulePath, "qase-api-v2-client") { |
| 136 | + // Remove "v" prefix if present, we'll add it later in buildXClientHeader |
| 137 | + apiClientV2Version = strings.TrimPrefix(version, "v") |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + return apiClientV1Version, apiClientV2Version |
| 142 | +} |
| 143 | + |
| 144 | +// buildXClientHeader builds the X-Client header value from HostData |
| 145 | +// Format: reporter={reporter_name};reporter_version=v{reporter_version};framework={framework};framework_version={framework_version};client_version_v1=v{api_client_v1_version};client_version_v2=v{api_client_v2_version};core_version=v{commons_version} |
| 146 | +func buildXClientHeader(hostData *HostData) string { |
| 147 | + if hostData == nil { |
| 148 | + return "" |
| 149 | + } |
| 150 | + |
| 151 | + var parts []string |
| 152 | + |
| 153 | + if hostData.Reporter != "" { |
| 154 | + parts = append(parts, "reporter="+hostData.Reporter) |
| 155 | + } |
| 156 | + |
| 157 | + if hostData.ReporterVersion != "" { |
| 158 | + version := hostData.ReporterVersion |
| 159 | + if !strings.HasPrefix(version, "v") { |
| 160 | + version = "v" + version |
| 161 | + } |
| 162 | + parts = append(parts, "reporter_version="+version) |
| 163 | + } |
| 164 | + |
| 165 | + if hostData.Framework != "" { |
| 166 | + parts = append(parts, "framework="+hostData.Framework) |
| 167 | + } |
| 168 | + |
| 169 | + if hostData.FrameworkVersion != "" { |
| 170 | + version := hostData.FrameworkVersion |
| 171 | + if !strings.HasPrefix(version, "v") { |
| 172 | + version = "v" + version |
| 173 | + } |
| 174 | + parts = append(parts, "framework_version="+version) |
| 175 | + } |
| 176 | + |
| 177 | + if hostData.APIClientV1 != "" { |
| 178 | + version := hostData.APIClientV1 |
| 179 | + if !strings.HasPrefix(version, "v") { |
| 180 | + version = "v" + version |
| 181 | + } |
| 182 | + parts = append(parts, "client_version_v1="+version) |
| 183 | + } |
| 184 | + |
| 185 | + if hostData.APIClientV2 != "" { |
| 186 | + version := hostData.APIClientV2 |
| 187 | + if !strings.HasPrefix(version, "v") { |
| 188 | + version = "v" + version |
| 189 | + } |
| 190 | + parts = append(parts, "client_version_v2="+version) |
| 191 | + } |
| 192 | + |
| 193 | + if hostData.Commons != "" { |
| 194 | + version := hostData.Commons |
| 195 | + if !strings.HasPrefix(version, "v") { |
| 196 | + version = "v" + version |
| 197 | + } |
| 198 | + parts = append(parts, "core_version="+version) |
| 199 | + } |
| 200 | + |
| 201 | + return strings.Join(parts, ";") |
| 202 | +} |
| 203 | + |
| 204 | +// buildXPlatformHeader builds the X-Platform header value from HostData |
| 205 | +// Format: os={os_name};arch={arch};{language}={language_version};{package_manager}={package_manager_version} |
| 206 | +func buildXPlatformHeader(hostData *HostData) string { |
| 207 | + if hostData == nil { |
| 208 | + return "" |
| 209 | + } |
| 210 | + |
| 211 | + var parts []string |
| 212 | + |
| 213 | + if hostData.System != "" { |
| 214 | + parts = append(parts, "os="+hostData.System) |
| 215 | + } |
| 216 | + |
| 217 | + if hostData.Arch != "" { |
| 218 | + parts = append(parts, "arch="+hostData.Arch) |
| 219 | + } |
| 220 | + |
| 221 | + // For Go, we use "go" as the language |
| 222 | + // Go version from runtime.Version() includes "go" prefix (e.g., "go1.21.0") |
| 223 | + goVersion := runtime.Version() |
| 224 | + if goVersion != "" { |
| 225 | + parts = append(parts, "go="+goVersion) |
| 226 | + } |
| 227 | + |
| 228 | + // Package manager for Go would be "go" with version, but we can't easily get it at runtime |
| 229 | + // So we'll skip it for now |
| 230 | + |
| 231 | + return strings.Join(parts, ";") |
| 232 | +} |
0 commit comments