Skip to content

Commit 88532bb

Browse files
committed
Merge remote-tracking branch 'upstream/master' into improve-settings-xml
# Conflicts: # artifactory/utils/maven/settingsxml.go # artifactory/utils/maven/settingsxml_test.go
2 parents f9dfbc7 + 678250a commit 88532bb

File tree

11 files changed

+1358
-19
lines changed

11 files changed

+1358
-19
lines changed

common/commands/command.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/jfrog/jfrog-cli-core/v2/utils/usage/visibility"
1212
rtClient "github.com/jfrog/jfrog-client-go/artifactory"
1313
"github.com/jfrog/jfrog-client-go/artifactory/usage"
14+
"github.com/jfrog/jfrog-client-go/jfconnect/services"
1415
"github.com/jfrog/jfrog-client-go/utils/log"
1516
)
1617

@@ -29,6 +30,9 @@ type Command interface {
2930
}
3031

3132
func Exec(command Command) error {
33+
commandName := command.CommandName()
34+
flags := GetContextFlags()
35+
CollectMetrics(commandName, flags)
3236
channel := make(chan bool)
3337
// Triggers the report usage.
3438
go reportUsage(command, channel)
@@ -39,10 +43,13 @@ func Exec(command Command) error {
3943
return err
4044
}
4145

42-
// ExecAndThenReportUsage runs the command and then triggers a usage report
43-
// Is used for commands which don't have the full server details before execution
46+
// ExecAndThenReportUsage runs the command and then triggers a usage report.
47+
// Used for commands which don't have the full server details before execution.
4448
// For example: oidc exchange command, which will get access token only after execution.
4549
func ExecAndThenReportUsage(cc Command) (err error) {
50+
commandName := cc.CommandName()
51+
flags := GetContextFlags()
52+
CollectMetrics(commandName, flags)
4653
if err = cc.Run(); err != nil {
4754
return
4855
}
@@ -102,8 +109,25 @@ func reportUsage(command Command, channel chan<- bool) {
102109
wg.Wait()
103110
}
104111

112+
// reportUsageToVisibilitySystem sends enhanced metrics to the visibility system
105113
func reportUsageToVisibilitySystem(command Command, serverDetails *config.ServerDetails) {
106-
commandsCountMetric := visibility.NewCommandsCountMetric(command.CommandName())
114+
var commandsCountMetric services.VisibilityMetric
115+
116+
commandName := command.CommandName()
117+
metricsData := GetCollectedMetrics(commandName)
118+
var visibilityMetricsData *visibility.MetricsData
119+
if metricsData != nil {
120+
visibilityMetricsData = &visibility.MetricsData{
121+
Flags: metricsData.Flags,
122+
Platform: metricsData.Platform,
123+
Architecture: metricsData.Architecture,
124+
IsCI: metricsData.IsCI,
125+
CISystem: metricsData.CISystem,
126+
IsContainer: metricsData.IsContainer,
127+
}
128+
}
129+
commandsCountMetric = visibility.NewCommandsCountMetricWithEnhancedData(commandName, visibilityMetricsData)
130+
107131
if err := visibility.NewVisibilitySystemManager(serverDetails).SendUsage(commandsCountMetric); err != nil {
108132
log.Debug("Visibility System Usage reporting:", err.Error())
109133
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package commands
2+
3+
import (
4+
"os"
5+
"runtime"
6+
"strings"
7+
"sync"
8+
9+
metrics "github.com/jfrog/jfrog-cli-core/v2/utils/metrics"
10+
)
11+
12+
// MetricsData is shared from utils/metrics to avoid import cycles.
13+
type MetricsData = metrics.MetricsData
14+
15+
// metricsCollector provides thread-safe collection and storage of command metrics
16+
type metricsCollector struct {
17+
mu sync.RWMutex
18+
metricsData map[string]*MetricsData
19+
}
20+
21+
var contextFlags []string
22+
var globalMetricsCollector = &metricsCollector{
23+
metricsData: make(map[string]*MetricsData),
24+
}
25+
26+
// CollectMetrics stores enhanced metrics information for a command execution.
27+
// Collects system information, CI environment details, and container detection.
28+
func CollectMetrics(commandName string, flags []string) {
29+
globalMetricsCollector.mu.Lock()
30+
defer globalMetricsCollector.mu.Unlock()
31+
32+
ciSystem := detectCISystem()
33+
isCI := ciSystem != ""
34+
35+
metricsData := &MetricsData{
36+
Flags: flags,
37+
Platform: runtime.GOOS,
38+
Architecture: runtime.GOARCH,
39+
IsCI: isCI,
40+
CISystem: func() string {
41+
if isCI {
42+
return ciSystem
43+
}
44+
return ""
45+
}(),
46+
IsContainer: isRunningInContainer(),
47+
}
48+
49+
globalMetricsCollector.metricsData[commandName] = metricsData
50+
}
51+
52+
// GetCollectedMetrics retrieves collected metrics for a command.
53+
// Returns a copy of the metrics data without clearing the original.
54+
func GetCollectedMetrics(commandName string) *MetricsData {
55+
globalMetricsCollector.mu.RLock()
56+
metrics, exists := globalMetricsCollector.metricsData[commandName]
57+
globalMetricsCollector.mu.RUnlock()
58+
59+
if !exists {
60+
return nil
61+
}
62+
63+
return &MetricsData{
64+
Flags: append([]string(nil), metrics.Flags...),
65+
Platform: metrics.Platform,
66+
Architecture: metrics.Architecture,
67+
IsCI: metrics.IsCI,
68+
CISystem: metrics.CISystem,
69+
IsContainer: metrics.IsContainer,
70+
}
71+
}
72+
73+
// detectCISystem identifies the CI environment and returns the system name
74+
func detectCISystem() string {
75+
ciEnvVars := map[string]string{
76+
"JENKINS_URL": "jenkins",
77+
"TRAVIS": "travis",
78+
"CIRCLECI": "circleci",
79+
"GITHUB_ACTIONS": "github_actions",
80+
"GITLAB_CI": "gitlab",
81+
"BUILDKITE": "buildkite",
82+
"BAMBOO_BUILD_KEY": "bamboo",
83+
"TF_BUILD": "azure_devops",
84+
"TEAMCITY_VERSION": "teamcity",
85+
"DRONE": "drone",
86+
"BITBUCKET_BUILD_NUMBER": "bitbucket",
87+
"CODEBUILD_BUILD_ID": "aws_codebuild",
88+
}
89+
90+
for envVar, system := range ciEnvVars {
91+
if os.Getenv(envVar) != "" {
92+
return system
93+
}
94+
}
95+
96+
genericCIVars := []string{
97+
"CI",
98+
"CONTINUOUS_INTEGRATION",
99+
"BUILD_ID",
100+
"BUILD_NUMBER",
101+
}
102+
103+
for _, envVar := range genericCIVars {
104+
if os.Getenv(envVar) != "" {
105+
return "unknown"
106+
}
107+
}
108+
109+
return ""
110+
}
111+
112+
// isRunningInContainer detects if the CLI is running inside a container
113+
func isRunningInContainer() bool {
114+
if _, err := os.Stat("/.dockerenv"); err == nil {
115+
return true
116+
}
117+
118+
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" {
119+
return true
120+
}
121+
122+
if os.Getenv("container") != "" {
123+
return true
124+
}
125+
126+
if data, err := os.ReadFile("/proc/1/cgroup"); err == nil {
127+
content := string(data)
128+
if strings.Contains(content, "docker") ||
129+
strings.Contains(content, "containerd") ||
130+
strings.Contains(content, "kubepods") ||
131+
strings.Contains(content, "lxc") {
132+
return true
133+
}
134+
}
135+
136+
if data, err := os.ReadFile("/proc/self/cgroup"); err == nil {
137+
content := string(data)
138+
if strings.Contains(content, "docker") ||
139+
strings.Contains(content, "containerd") ||
140+
strings.Contains(content, "kubepods") {
141+
return true
142+
}
143+
}
144+
145+
if data, err := os.ReadFile("/proc/self/mountinfo"); err == nil {
146+
content := string(data)
147+
if strings.Contains(content, "docker") ||
148+
strings.Contains(content, "overlay") {
149+
return true
150+
}
151+
}
152+
153+
return false
154+
}
155+
156+
// SetContextFlags stores flags for the current command execution
157+
func SetContextFlags(flags []string) {
158+
contextFlags = append([]string(nil), flags...)
159+
}
160+
161+
// GetContextFlags retrieves and clears the stored flags
162+
func GetContextFlags() []string {
163+
flags := contextFlags
164+
contextFlags = nil
165+
return flags
166+
}

0 commit comments

Comments
 (0)