github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/cmd/state-svc/internal/resolver/resolver.go (about) 1 package resolver 2 3 import ( 4 "context" 5 "encoding/json" 6 "os" 7 "runtime/debug" 8 "sort" 9 "strconv" 10 "time" 11 12 "github.com/ActiveState/cli/cmd/state-svc/internal/messages" 13 "github.com/ActiveState/cli/cmd/state-svc/internal/rtwatcher" 14 genserver "github.com/ActiveState/cli/cmd/state-svc/internal/server/generated" 15 "github.com/ActiveState/cli/internal/analytics/client/sync" 16 anaConsts "github.com/ActiveState/cli/internal/analytics/constants" 17 "github.com/ActiveState/cli/internal/analytics/dimensions" 18 "github.com/ActiveState/cli/internal/cache/projectcache" 19 "github.com/ActiveState/cli/internal/config" 20 "github.com/ActiveState/cli/internal/constants" 21 "github.com/ActiveState/cli/internal/errs" 22 "github.com/ActiveState/cli/internal/graph" 23 "github.com/ActiveState/cli/internal/logging" 24 configMediator "github.com/ActiveState/cli/internal/mediators/config" 25 "github.com/ActiveState/cli/internal/multilog" 26 "github.com/ActiveState/cli/internal/poller" 27 "github.com/ActiveState/cli/internal/rtutils/ptr" 28 "github.com/ActiveState/cli/internal/updater" 29 "github.com/ActiveState/cli/pkg/platform/authentication" 30 "github.com/ActiveState/cli/pkg/projectfile" 31 ) 32 33 type Resolver struct { 34 cfg *config.Instance 35 messages *messages.Messages 36 updatePoller *poller.Poller 37 authPoller *poller.Poller 38 projectIDCache *projectcache.ID 39 an *sync.Client 40 anForClient *sync.Client // Use separate client for events sent through service so we don't contaminate one with the other 41 rtwatch *rtwatcher.Watcher 42 auth *authentication.Auth 43 } 44 45 // var _ genserver.ResolverRoot = &Resolver{} // Must implement ResolverRoot 46 47 func New(cfg *config.Instance, an *sync.Client, auth *authentication.Auth) (*Resolver, error) { 48 msg, err := messages.New(cfg, auth) 49 if err != nil { 50 return nil, errs.Wrap(err, "Could not initialize messages") 51 } 52 53 upchecker := updater.NewDefaultChecker(cfg, an) 54 pollUpdate := poller.New(1*time.Hour, func() (interface{}, error) { 55 logging.Debug("Poller checking for update info") 56 return upchecker.CheckFor(constants.ChannelName, "") 57 }) 58 59 pollRate := time.Minute.Milliseconds() 60 if override := os.Getenv(constants.SvcAuthPollingRateEnvVarName); override != "" { 61 overrideInt, err := strconv.ParseInt(override, 10, 64) 62 if err != nil { 63 return nil, errs.New("Failed to parse svc polling time override: %v", err) 64 } 65 pollRate = overrideInt 66 } 67 68 pollAuth := poller.New(time.Duration(int64(time.Millisecond)*pollRate), func() (interface{}, error) { 69 if auth.SyncRequired() { 70 return nil, auth.Sync() 71 } 72 return nil, nil 73 }) 74 75 // Note: source does not matter here, as analytics sent via the resolver have a source 76 // (e.g. State Tool or Executor), and that source will be used. 77 anForClient := sync.New(anaConsts.SrcStateTool, cfg, auth, nil) 78 return &Resolver{ 79 cfg, 80 msg, 81 pollUpdate, 82 pollAuth, 83 projectcache.NewID(), 84 an, 85 anForClient, 86 rtwatcher.New(cfg, anForClient), 87 auth, 88 }, nil 89 } 90 91 func (r *Resolver) Close() error { 92 r.messages.Close() 93 r.updatePoller.Close() 94 r.authPoller.Close() 95 r.anForClient.Close() 96 return r.rtwatch.Close() 97 } 98 99 // Seems gqlgen supplies this so you can separate your resolver and query resolver logic 100 // So far no need for this, so we're pointing back at ourselves.. 101 func (r *Resolver) Query() genserver.QueryResolver { return r } 102 103 func (r *Resolver) Version(ctx context.Context) (*graph.Version, error) { 104 defer func() { handlePanics(recover(), debug.Stack()) }() 105 106 r.an.EventWithLabel(anaConsts.CatStateSvc, "endpoint", "Version") 107 logging.Debug("Version resolver") 108 return &graph.Version{ 109 State: &graph.StateVersion{ 110 License: constants.LibraryLicense, 111 Version: constants.Version, 112 Channel: constants.ChannelName, 113 Revision: constants.RevisionHash, 114 Date: constants.Date, 115 }, 116 }, nil 117 } 118 119 func (r *Resolver) AvailableUpdate(ctx context.Context, desiredChannel, desiredVersion string) (*graph.AvailableUpdate, error) { 120 defer func() { handlePanics(recover(), debug.Stack()) }() 121 122 if desiredChannel == "" { 123 desiredChannel = constants.ChannelName 124 } 125 126 r.an.EventWithLabel(anaConsts.CatStateSvc, "endpoint", "AvailableUpdate") 127 logging.Debug("AvailableUpdate resolver: %s/%s", desiredChannel, desiredVersion) 128 defer logging.Debug("AvailableUpdate done") 129 130 var ( 131 avUpdate *updater.AvailableUpdate 132 ok bool 133 err error 134 ) 135 136 switch { 137 case desiredChannel == constants.ChannelName && desiredVersion == "": 138 avUpdate, ok = r.updatePoller.ValueFromCache().(*updater.AvailableUpdate) 139 if !ok || avUpdate == nil { 140 logging.Debug("No update info in poller cache") 141 return nil, nil 142 } 143 144 logging.Debug("Update info pulled from poller cache") 145 146 default: 147 logging.Debug("Update info requested for specific channel/version") 148 149 upchecker := updater.NewDefaultChecker(r.cfg, r.an) 150 avUpdate, err = upchecker.CheckFor(desiredChannel, desiredVersion) 151 if err != nil { 152 return nil, errs.Wrap(err, "Failed to check for specified channel/version: %s/%s", desiredChannel, desiredVersion) 153 } 154 } 155 156 availableUpdate := &graph.AvailableUpdate{ 157 Version: avUpdate.Version, 158 Channel: avUpdate.Channel, 159 Path: avUpdate.Path, 160 Platform: avUpdate.Platform, 161 Sha256: avUpdate.Sha256, 162 } 163 164 return availableUpdate, nil 165 } 166 167 func (r *Resolver) Projects(ctx context.Context) ([]*graph.Project, error) { 168 defer func() { handlePanics(recover(), debug.Stack()) }() 169 170 r.an.EventWithLabel(anaConsts.CatStateSvc, "endpoint", "Projects") 171 logging.Debug("Projects resolver") 172 var projects []*graph.Project 173 localConfigProjects := projectfile.GetProjectMapping(r.cfg) 174 for ns, locations := range localConfigProjects { 175 projects = append(projects, &graph.Project{ 176 Namespace: ns, 177 Locations: locations, 178 }) 179 } 180 sort.Slice(projects, func(i, j int) bool { 181 return projects[i].Namespace < projects[j].Namespace 182 }) 183 184 return projects, nil 185 } 186 187 func (r *Resolver) AnalyticsEvent(_ context.Context, category, action, source string, _label *string, dimensionsJson string) (*graph.AnalyticsEventResponse, error) { 188 defer func() { handlePanics(recover(), debug.Stack()) }() 189 190 logging.Debug("Analytics event resolver: %s - %s: %s (%s)", category, action, ptr.From(_label, "NIL"), source) 191 192 label := "" 193 if _label != nil { 194 label = *_label 195 } 196 197 var dims *dimensions.Values 198 if err := json.Unmarshal([]byte(dimensionsJson), &dims); err != nil { 199 return &graph.AnalyticsEventResponse{Sent: false}, errs.Wrap(err, "Could not unmarshal") 200 } 201 202 // Resolve the project ID - this is a little awkward since I had to work around an import cycle 203 dims.RegisterPreProcessor(func(values *dimensions.Values) error { 204 values.ProjectID = nil 205 if values.ProjectNameSpace == nil || *values.ProjectNameSpace == "" { 206 return nil 207 } 208 id, err := r.projectIDCache.FromNamespace(*values.ProjectNameSpace, r.auth) 209 if err != nil { 210 logging.Error("Could not resolve project ID for analytics: %s", errs.JoinMessage(err)) 211 } 212 values.ProjectID = &id 213 return nil 214 }) 215 216 r.anForClient.EventWithSourceAndLabel(category, action, source, label, dims) 217 218 return &graph.AnalyticsEventResponse{Sent: true}, nil 219 } 220 221 func (r *Resolver) ReportRuntimeUsage(_ context.Context, pid int, exec, source string, dimensionsJSON string) (*graph.ReportRuntimeUsageResponse, error) { 222 defer func() { handlePanics(recover(), debug.Stack()) }() 223 224 logging.Debug("Runtime usage resolver: %d - %s", pid, exec) 225 var dims *dimensions.Values 226 if err := json.Unmarshal([]byte(dimensionsJSON), &dims); err != nil { 227 return &graph.ReportRuntimeUsageResponse{Received: false}, errs.Wrap(err, "Could not unmarshal") 228 } 229 230 r.rtwatch.Watch(pid, exec, source, dims) 231 232 return &graph.ReportRuntimeUsageResponse{Received: true}, nil 233 } 234 235 func (r *Resolver) CheckMessages(ctx context.Context, command string, flags []string) ([]*graph.MessageInfo, error) { 236 defer func() { handlePanics(recover(), debug.Stack()) }() 237 logging.Debug("Check messages resolver") 238 return r.messages.Check(command, flags) 239 } 240 241 func (r *Resolver) ConfigChanged(ctx context.Context, key string) (*graph.ConfigChangedResponse, error) { 242 defer func() { handlePanics(recover(), debug.Stack()) }() 243 244 go configMediator.NotifyListeners(key) 245 return &graph.ConfigChangedResponse{Received: true}, nil 246 } 247 248 func (r *Resolver) FetchLogTail(ctx context.Context) (string, error) { 249 defer func() { handlePanics(recover(), debug.Stack()) }() 250 251 return logging.ReadTail(), nil 252 } 253 254 func (r *Resolver) GetProcessesInUse(ctx context.Context, execDir string) ([]*graph.ProcessInfo, error) { 255 defer func() { handlePanics(recover(), debug.Stack()) }() 256 257 inUse := r.rtwatch.GetProcessesInUse(execDir) 258 processes := make([]*graph.ProcessInfo, 0, len(inUse)) 259 for _, entry := range inUse { 260 processes = append(processes, &graph.ProcessInfo{entry.Exec, entry.PID}) 261 } 262 return processes, nil 263 } 264 265 func handlePanics(recovered interface{}, stack []byte) { 266 if recovered != nil { 267 multilog.Error("Panic: %v", recovered) 268 logging.Debug("Stack: %s", string(stack)) 269 panic(recovered) // We're only logging the panic, not interrupting it 270 } 271 }