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  }