1 package context
2
3 import (
4 "context"
5 "fmt"
6 "hash/adler32"
7 "os"
8 "strconv"
9 "strings"
10 "sync"
11 "time"
12
13 "github.com/spf13/pflag"
14
15 "github.com/redhat-developer/odo/pkg/kclient"
16 "github.com/redhat-developer/odo/pkg/platform"
17 "github.com/redhat-developer/odo/pkg/podman"
18
19 dfutil "github.com/devfile/library/v2/pkg/util"
20
21 "k8s.io/klog"
22 )
23
24 const (
25 Caller = "caller"
26 ComponentType = "componentType"
27 ClusterType = "clusterType"
28 PreviousTelemetryStatus = "wasTelemetryEnabled"
29 TelemetryStatus = "isTelemetryEnabled"
30 DevfileName = "devfileName"
31 Language = "language"
32 ProjectType = "projectType"
33 NOTFOUND = "not-found"
34 InteractiveMode = "interactive"
35 ExperimentalMode = "experimental"
36 Flags = "flags"
37 Platform = "platform"
38 PlatformVersion = "platformVersion"
39 PreferenceParameter = "parameter"
40 PreferenceValue = "value"
41 )
42
43 const (
44 VSCode = "vscode"
45 IntelliJ = "intellij"
46 JBoss = "jboss"
47 )
48
49
50 var clearTextPreferenceParams = []string{
51 "ConsentTelemetry",
52 "Ephemeral",
53 "PushTimeout",
54 "RegistryCacheTime",
55 "Timeout",
56 "UpdateNotification",
57 }
58
59 type contextKey struct{}
60
61 var key = contextKey{}
62
63
64 type properties struct {
65 lock sync.Mutex
66 storage map[string]interface{}
67 }
68
69
70 func NewContext(ctx context.Context) context.Context {
71 return context.WithValue(ctx, key, &properties{storage: make(map[string]interface{})})
72 }
73
74
75 func GetContextProperties(ctx context.Context) map[string]interface{} {
76 cProperties := propertiesFromContext(ctx)
77 if cProperties == nil {
78 return make(map[string]interface{})
79 }
80 return cProperties.values()
81 }
82
83
84 func SetComponentType(ctx context.Context, value string) {
85 setContextProperty(ctx, ComponentType, dfutil.ExtractComponentType(value))
86 }
87
88
89 func SetClusterType(ctx context.Context, client kclient.ClientInterface) {
90 var value string
91 if client == nil {
92 value = NOTFOUND
93 } else {
94
95
96 isOC, err := client.IsProjectSupported()
97 if err != nil {
98 klog.V(3).Info(fmt.Errorf("unable to detect project support: %w", err))
99 value = NOTFOUND
100 } else {
101 if isOC {
102 isOC4, err := client.IsCSVSupported()
103
104 if err != nil {
105 value = "openshift"
106 } else {
107 if isOC4 {
108 value = "openshift4"
109 } else {
110 value = "openshift3"
111 }
112 }
113 } else {
114 value = "kubernetes"
115 }
116 }
117 }
118 setContextProperty(ctx, ClusterType, value)
119 }
120
121
122 func SetPlatform(ctx context.Context, client platform.Client) {
123 switch client := client.(type) {
124 case kclient.ClientInterface:
125 setPlatformCluster(ctx, client)
126 case podman.Client:
127 setPlatformPodman(ctx, client)
128 }
129 }
130
131 func setPlatformCluster(ctx context.Context, client kclient.ClientInterface) {
132 var value string
133 if client == nil {
134 value = NOTFOUND
135 } else {
136
137
138
139 isOC, err := client.IsProjectSupported()
140 if err != nil {
141 klog.V(3).Info(fmt.Errorf("unable to detect project support: %w", err))
142 value = NOTFOUND
143 } else {
144 if isOC {
145 value = "openshift"
146 ocVersion, err := client.GetOCVersion()
147 if err == nil {
148 setContextProperty(ctx, PlatformVersion, ocVersion)
149 } else {
150 klog.V(3).Info(fmt.Errorf("unable to detect platform version: %w", err))
151 }
152 } else {
153 value = "kubernetes"
154 serverInfo, err := client.GetServerVersion(time.Second)
155 if err == nil {
156 setContextProperty(ctx, PlatformVersion, serverInfo.KubernetesVersion)
157 } else {
158 klog.V(3).Info(fmt.Errorf("unable to detect platform version: %w", err))
159 }
160 }
161 }
162 }
163 setContextProperty(ctx, Platform, value)
164 }
165
166 func setPlatformPodman(ctx context.Context, client podman.Client) {
167 setContextProperty(ctx, Platform, "podman")
168 version, err := client.Version(ctx)
169 if err != nil {
170 klog.V(3).Info(fmt.Errorf("unable to get podman version: %w", err))
171 return
172 }
173 setContextProperty(ctx, PlatformVersion, version.Client.Version)
174 }
175
176
177 func SetPreviousTelemetryStatus(ctx context.Context, isEnabled bool) {
178 setContextProperty(ctx, PreviousTelemetryStatus, isEnabled)
179 }
180
181
182 func SetTelemetryStatus(ctx context.Context, isEnabled bool) {
183 setContextProperty(ctx, TelemetryStatus, isEnabled)
184 }
185
186 func SetSignal(ctx context.Context, signal os.Signal) {
187 setContextProperty(ctx, "receivedSignal", signal.String())
188 }
189
190 func SetDevfileName(ctx context.Context, devfileName string) {
191 setContextProperty(ctx, DevfileName, devfileName)
192 }
193
194 func SetLanguage(ctx context.Context, language string) {
195 setContextProperty(ctx, Language, language)
196 }
197
198 func SetProjectType(ctx context.Context, projectType string) {
199 setContextProperty(ctx, ProjectType, projectType)
200 }
201
202 func SetInteractive(ctx context.Context, interactive bool) {
203 setContextProperty(ctx, InteractiveMode, interactive)
204 }
205
206 func SetExperimentalMode(ctx context.Context, value bool) {
207 setContextProperty(ctx, ExperimentalMode, value)
208 }
209
210
211 func SetFlags(ctx context.Context, flags *pflag.FlagSet) {
212 var changedFlags []string
213 flags.VisitAll(func(f *pflag.Flag) {
214 if f.Changed {
215 if f.Name == "logtostderr" {
216
217 return
218 }
219 changedFlags = append(changedFlags, f.Name)
220 }
221 })
222
223 setContextProperty(ctx, Flags, strings.Join(changedFlags, " "))
224 }
225
226
227
228
229
230 func SetCaller(ctx context.Context, caller string) error {
231 var err error
232 s := strings.TrimSpace(strings.ToLower(caller))
233 switch s {
234 case "", VSCode, IntelliJ, JBoss:
235
236 err = nil
237 default:
238
239 err = fmt.Errorf("unknown caller type: %q", caller)
240 }
241 setContextProperty(ctx, Caller, s)
242 return err
243 }
244
245
246
247
248 func SetPreferenceParameter(ctx context.Context, param string, value *string) {
249 setContextProperty(ctx, PreferenceParameter, param)
250
251 if value == nil {
252 return
253 }
254
255 isClearTextParam := func() bool {
256 for _, clearTextParam := range clearTextPreferenceParams {
257 if strings.EqualFold(param, clearTextParam) {
258 return true
259 }
260 }
261 return false
262 }
263
264 recordedValue := *value
265 if !isClearTextParam() {
266
267
268 recordedValue = strconv.FormatUint(uint64(adler32.Checksum([]byte(recordedValue))), 16)
269 }
270 setContextProperty(ctx, PreferenceValue, recordedValue)
271 }
272
273
274 func GetPreviousTelemetryStatus(ctx context.Context) bool {
275 wasEnabled, ok := GetContextProperties(ctx)[PreviousTelemetryStatus]
276 if ok {
277 return wasEnabled.(bool)
278 }
279 return false
280 }
281
282
283 func GetTelemetryStatus(ctx context.Context) bool {
284 isEnabled, ok := GetContextProperties(ctx)[TelemetryStatus]
285 if ok {
286 return isEnabled.(bool)
287 }
288 return false
289 }
290
291
292 func (p *properties) set(name string, value interface{}) {
293 p.lock.Lock()
294 defer p.lock.Unlock()
295 p.storage[name] = value
296 }
297
298
299 func (p *properties) values() map[string]interface{} {
300 p.lock.Lock()
301 defer p.lock.Unlock()
302 ret := make(map[string]interface{})
303 for k, v := range p.storage {
304 ret[k] = v
305 }
306 return ret
307 }
308
309
310 func propertiesFromContext(ctx context.Context) *properties {
311 value := ctx.Value(key)
312 if cast, ok := value.(*properties); ok {
313 return cast
314 }
315 return nil
316 }
317
318
319 func setContextProperty(ctx context.Context, key string, value interface{}) {
320 cProperties := propertiesFromContext(ctx)
321 if cProperties != nil {
322 cProperties.set(key, value)
323 }
324 }
325
View as plain text