github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/platform/model/svc.go (about) 1 package model 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net/http" 9 "time" 10 11 "github.com/ActiveState/cli/internal/condition" 12 "github.com/ActiveState/cli/internal/errs" 13 "github.com/ActiveState/cli/internal/gqlclient" 14 "github.com/ActiveState/cli/internal/graph" 15 "github.com/ActiveState/cli/internal/logging" 16 "github.com/ActiveState/cli/internal/profile" 17 "github.com/ActiveState/cli/pkg/platform/api/svc/request" 18 "github.com/ActiveState/graphql" 19 ) 20 21 var SvcTimeoutMinimal = time.Millisecond * 500 22 23 type SvcModel struct { 24 client *gqlclient.Client 25 } 26 27 // NewSvcModel returns a model for all client connections to a State Svc. This function returns an error if the State service is not yet ready to communicate. 28 func NewSvcModel(port string) *SvcModel { 29 localURL := "http://127.0.0.1" + port + "/query" 30 31 return &SvcModel{ 32 client: gqlclient.NewWithOpts(localURL, 0, graphql.WithHTTPClient(&http.Client{})), 33 } 34 } 35 36 // EnableDebugLog turns on debug logging 37 func (m *SvcModel) EnableDebugLog() { 38 m.client.EnableDebugLog() 39 } 40 41 func (m *SvcModel) request(ctx context.Context, request gqlclient.Request, resp interface{}) error { 42 defer profile.Measure("SvcModel:request", time.Now()) 43 44 err := m.client.RunWithContext(ctx, request, resp) 45 if err != nil { 46 reqError := &gqlclient.RequestError{} 47 if errors.As(err, &reqError) && (!condition.BuiltViaCI() || condition.InTest()) { 48 vars, err := request.Vars() 49 if err != nil { 50 return errs.Wrap(err, "Could not get variables") 51 } 52 logging.Debug( 53 "svc client gql request failed - query: %q, vars: %q", 54 reqError.Request.Query(), 55 jsonFromMap(vars), 56 ) 57 } 58 return err 59 } 60 61 return nil 62 } 63 64 func (m *SvcModel) StateVersion(ctx context.Context) (*graph.Version, error) { 65 r := request.NewVersionRequest() 66 resp := graph.VersionResponse{} 67 if err := m.request(ctx, r, &resp); err != nil { 68 return nil, err 69 } 70 return &resp.Version, nil 71 } 72 73 func (m *SvcModel) LocalProjects(ctx context.Context) ([]*graph.Project, error) { 74 r := request.NewLocalProjectsRequest() 75 response := graph.ProjectsResponse{Projects: []*graph.Project{}} 76 if err := m.request(ctx, r, &response); err != nil { 77 return nil, err 78 } 79 return response.Projects, nil 80 } 81 82 // CheckUpdate returns cached update information. There is no guarantee that 83 // available information is immediately cached. For instance, if this info is 84 // requested shortly after the service is started up, the data may return 85 // empty for a little while. 86 func (m *SvcModel) CheckUpdate(ctx context.Context, desiredChannel, desiredVersion string) (*graph.AvailableUpdate, error) { 87 defer profile.Measure("svc:CheckUpdate", time.Now()) 88 r := request.NewAvailableUpdate(desiredChannel, desiredVersion) 89 u := graph.AvailableUpdateResponse{} 90 if err := m.request(ctx, r, &u); err != nil { 91 return nil, errs.Wrap(err, "Error checking if update is available.") 92 } 93 94 return &u.AvailableUpdate, nil 95 } 96 97 func (m *SvcModel) Ping() error { 98 _, err := m.StateVersion(context.Background()) 99 return err 100 } 101 102 func (m *SvcModel) AnalyticsEvent(ctx context.Context, category, action, source, label string, dimJson string) error { 103 defer profile.Measure("svc:analyticsEvent", time.Now()) 104 105 r := request.NewAnalyticsEvent(category, action, source, label, dimJson) 106 u := graph.AnalyticsEventResponse{} 107 if err := m.request(ctx, r, &u); err != nil { 108 return errs.Wrap(err, "Error sending analytics event via state-svc") 109 } 110 111 return nil 112 } 113 114 func (m *SvcModel) ReportRuntimeUsage(ctx context.Context, pid int, exec, source string, dimJson string) error { 115 defer profile.Measure("svc:ReportRuntimeUsage", time.Now()) 116 117 r := request.NewReportRuntimeUsage(pid, exec, source, dimJson) 118 u := graph.ReportRuntimeUsageResponse{} 119 if err := m.request(ctx, r, &u); err != nil { 120 return errs.Wrap(err, "Error sending report runtime usage event via state-svc") 121 } 122 123 return nil 124 } 125 126 func (m *SvcModel) CheckMessages(ctx context.Context, command string, flags []string) ([]*graph.MessageInfo, error) { 127 logging.Debug("Checking for messages") 128 defer profile.Measure("svc:CheckMessages", time.Now()) 129 130 r := request.NewMessagingRequest(command, flags) 131 resp := graph.CheckMessagesResponse{} 132 if err := m.request(ctx, r, &resp); err != nil { 133 return nil, errs.Wrap(err, "Error sending messages request") 134 } 135 136 return resp.Messages, nil 137 } 138 139 func (m *SvcModel) ConfigChanged(ctx context.Context, key string) error { 140 defer profile.Measure("svc:ConfigChanged", time.Now()) 141 142 r := request.NewConfigChanged(key) 143 u := graph.ConfigChangedResponse{} 144 if err := m.request(ctx, r, &u); err != nil { 145 return errs.Wrap(err, "Error sending configchanged event via state-svc") 146 } 147 148 return nil 149 } 150 151 func (m *SvcModel) FetchLogTail(ctx context.Context) (string, error) { 152 logging.Debug("Fetching log svc log") 153 defer profile.Measure("svc:FetchLogTail", time.Now()) 154 155 req := request.NewFetchLogTail() 156 response := make(map[string]string) 157 if err := m.request(ctx, req, &response); err != nil { 158 return "", errs.Wrap(err, "Error sending FetchLogTail request to state-svc") 159 } 160 if log, ok := response["fetchLogTail"]; ok { 161 return log, nil 162 } 163 return "", errs.New("svcModel.FetchLogTail() did not return an expected value") 164 } 165 166 func (m *SvcModel) GetProcessesInUse(ctx context.Context, execDir string) ([]*graph.ProcessInfo, error) { 167 logging.Debug("Checking if runtime is in use for %s", execDir) 168 defer profile.Measure("svc:GetProcessesInUse", time.Now()) 169 170 req := request.NewGetProcessesInUse(execDir) 171 response := graph.GetProcessesInUseResponse{} 172 if err := m.request(ctx, req, &response); err != nil { 173 return nil, errs.Wrap(err, "Error sending GetProcessesInUse request to state-svc") 174 } 175 176 return response.Processes, nil 177 } 178 179 func jsonFromMap(m map[string]interface{}) string { 180 d, err := json.Marshal(m) 181 if err != nil { 182 return fmt.Sprintf("cannot marshal map (%q) as json: %v", stringFromMap(m), err) 183 } 184 return string(d) 185 } 186 187 func stringFromMap(m map[string]interface{}) string { 188 var s, sep string 189 for k, v := range m { 190 s += fmt.Sprintf("%s%s: %#v", sep, k, v) 191 sep = ", " 192 } 193 return s 194 }