diff --git a/.github/workflows/docs-ci.yaml b/.github/workflows/docs-ci.yaml index ef7114a8e2..37e8c56268 100644 --- a/.github/workflows/docs-ci.yaml +++ b/.github/workflows/docs-ci.yaml @@ -15,6 +15,9 @@ on: - "**.md" - ".github/workflows/docs-ci.yaml" +permissions: + contents: read + jobs: docs: runs-on: ubuntu-latest diff --git a/agent/agent.go b/agent/agent.go index b05648c9c8..65ca33a962 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -41,8 +41,10 @@ import ( "github.com/coder/coder/v2/agent/agentscripts" "github.com/coder/coder/v2/agent/agentssh" "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/agent/proto/resourcesmonitor" "github.com/coder/coder/v2/agent/reconnectingpty" "github.com/coder/coder/v2/buildinfo" + "github.com/coder/coder/v2/cli/clistat" "github.com/coder/coder/v2/cli/gitauth" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/codersdk" @@ -50,6 +52,7 @@ import ( "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/tailnet" tailnetproto "github.com/coder/coder/v2/tailnet/proto" + "github.com/coder/quartz" ) const ( @@ -89,8 +92,8 @@ type Options struct { } type Client interface { - ConnectRPC23(ctx context.Context) ( - proto.DRPCAgentClient23, tailnetproto.DRPCTailnetClient23, error, + ConnectRPC24(ctx context.Context) ( + proto.DRPCAgentClient24, tailnetproto.DRPCTailnetClient24, error, ) RewriteDERPMap(derpMap *tailcfg.DERPMap) } @@ -410,7 +413,7 @@ func (t *trySingleflight) Do(key string, fn func()) { fn() } -func (a *agent) reportMetadata(ctx context.Context, aAPI proto.DRPCAgentClient23) error { +func (a *agent) reportMetadata(ctx context.Context, aAPI proto.DRPCAgentClient24) error { tickerDone := make(chan struct{}) collectDone := make(chan struct{}) ctx, cancel := context.WithCancel(ctx) @@ -626,7 +629,7 @@ func (a *agent) reportMetadata(ctx context.Context, aAPI proto.DRPCAgentClient23 // reportLifecycle reports the current lifecycle state once. All state // changes are reported in order. -func (a *agent) reportLifecycle(ctx context.Context, aAPI proto.DRPCAgentClient23) error { +func (a *agent) reportLifecycle(ctx context.Context, aAPI proto.DRPCAgentClient24) error { for { select { case <-a.lifecycleUpdate: @@ -708,7 +711,7 @@ func (a *agent) setLifecycle(state codersdk.WorkspaceAgentLifecycle) { // fetchServiceBannerLoop fetches the service banner on an interval. It will // not be fetched immediately; the expectation is that it is primed elsewhere // (and must be done before the session actually starts). -func (a *agent) fetchServiceBannerLoop(ctx context.Context, aAPI proto.DRPCAgentClient23) error { +func (a *agent) fetchServiceBannerLoop(ctx context.Context, aAPI proto.DRPCAgentClient24) error { ticker := time.NewTicker(a.announcementBannersRefreshInterval) defer ticker.Stop() for { @@ -744,7 +747,7 @@ func (a *agent) run() (retErr error) { a.sessionToken.Store(&sessionToken) // ConnectRPC returns the dRPC connection we use for the Agent and Tailnet v2+ APIs - aAPI, tAPI, err := a.client.ConnectRPC23(a.hardCtx) + aAPI, tAPI, err := a.client.ConnectRPC24(a.hardCtx) if err != nil { return err } @@ -761,7 +764,7 @@ func (a *agent) run() (retErr error) { connMan := newAPIConnRoutineManager(a.gracefulCtx, a.hardCtx, a.logger, aAPI, tAPI) connMan.startAgentAPI("init notification banners", gracefulShutdownBehaviorStop, - func(ctx context.Context, aAPI proto.DRPCAgentClient23) error { + func(ctx context.Context, aAPI proto.DRPCAgentClient24) error { bannersProto, err := aAPI.GetAnnouncementBanners(ctx, &proto.GetAnnouncementBannersRequest{}) if err != nil { return xerrors.Errorf("fetch service banner: %w", err) @@ -778,7 +781,7 @@ func (a *agent) run() (retErr error) { // sending logs gets gracefulShutdownBehaviorRemain because we want to send logs generated by // shutdown scripts. connMan.startAgentAPI("send logs", gracefulShutdownBehaviorRemain, - func(ctx context.Context, aAPI proto.DRPCAgentClient23) error { + func(ctx context.Context, aAPI proto.DRPCAgentClient24) error { err := a.logSender.SendLoop(ctx, aAPI) if xerrors.Is(err, agentsdk.LogLimitExceededError) { // we don't want this error to tear down the API connection and propagate to the @@ -796,6 +799,25 @@ func (a *agent) run() (retErr error) { // metadata reporting can cease as soon as we start gracefully shutting down connMan.startAgentAPI("report metadata", gracefulShutdownBehaviorStop, a.reportMetadata) + // resources monitor can cease as soon as we start gracefully shutting down. + connMan.startAgentAPI("resources monitor", gracefulShutdownBehaviorStop, func(ctx context.Context, aAPI proto.DRPCAgentClient24) error { + logger := a.logger.Named("resources_monitor") + clk := quartz.NewReal() + config, err := aAPI.GetResourcesMonitoringConfiguration(ctx, &proto.GetResourcesMonitoringConfigurationRequest{}) + if err != nil { + return xerrors.Errorf("failed to get resources monitoring configuration: %w", err) + } + + statfetcher, err := clistat.New() + if err != nil { + return xerrors.Errorf("failed to create resources fetcher: %w", err) + } + resourcesFetcher := resourcesmonitor.NewFetcher(statfetcher) + + resourcesmonitor := resourcesmonitor.NewResourcesMonitor(logger, clk, config, resourcesFetcher, aAPI) + return resourcesmonitor.Start(ctx) + }) + // channels to sync goroutines below // handle manifest // | @@ -818,7 +840,7 @@ func (a *agent) run() (retErr error) { connMan.startAgentAPI("handle manifest", gracefulShutdownBehaviorStop, a.handleManifest(manifestOK)) connMan.startAgentAPI("app health reporter", gracefulShutdownBehaviorStop, - func(ctx context.Context, aAPI proto.DRPCAgentClient23) error { + func(ctx context.Context, aAPI proto.DRPCAgentClient24) error { if err := manifestOK.wait(ctx); err != nil { return xerrors.Errorf("no manifest: %w", err) } @@ -833,7 +855,7 @@ func (a *agent) run() (retErr error) { a.createOrUpdateNetwork(manifestOK, networkOK)) connMan.startTailnetAPI("coordination", gracefulShutdownBehaviorStop, - func(ctx context.Context, tAPI tailnetproto.DRPCTailnetClient23) error { + func(ctx context.Context, tAPI tailnetproto.DRPCTailnetClient24) error { if err := networkOK.wait(ctx); err != nil { return xerrors.Errorf("no network: %w", err) } @@ -842,7 +864,7 @@ func (a *agent) run() (retErr error) { ) connMan.startTailnetAPI("derp map subscriber", gracefulShutdownBehaviorStop, - func(ctx context.Context, tAPI tailnetproto.DRPCTailnetClient23) error { + func(ctx context.Context, tAPI tailnetproto.DRPCTailnetClient24) error { if err := networkOK.wait(ctx); err != nil { return xerrors.Errorf("no network: %w", err) } @@ -851,7 +873,7 @@ func (a *agent) run() (retErr error) { connMan.startAgentAPI("fetch service banner loop", gracefulShutdownBehaviorStop, a.fetchServiceBannerLoop) - connMan.startAgentAPI("stats report loop", gracefulShutdownBehaviorStop, func(ctx context.Context, aAPI proto.DRPCAgentClient23) error { + connMan.startAgentAPI("stats report loop", gracefulShutdownBehaviorStop, func(ctx context.Context, aAPI proto.DRPCAgentClient24) error { if err := networkOK.wait(ctx); err != nil { return xerrors.Errorf("no network: %w", err) } @@ -864,8 +886,8 @@ func (a *agent) run() (retErr error) { } // handleManifest returns a function that fetches and processes the manifest -func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context, aAPI proto.DRPCAgentClient23) error { - return func(ctx context.Context, aAPI proto.DRPCAgentClient23) error { +func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context, aAPI proto.DRPCAgentClient24) error { + return func(ctx context.Context, aAPI proto.DRPCAgentClient24) error { var ( sentResult = false err error @@ -974,8 +996,8 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context, // createOrUpdateNetwork waits for the manifest to be set using manifestOK, then creates or updates // the tailnet using the information in the manifest -func (a *agent) createOrUpdateNetwork(manifestOK, networkOK *checkpoint) func(context.Context, proto.DRPCAgentClient23) error { - return func(ctx context.Context, _ proto.DRPCAgentClient23) (retErr error) { +func (a *agent) createOrUpdateNetwork(manifestOK, networkOK *checkpoint) func(context.Context, proto.DRPCAgentClient24) error { + return func(ctx context.Context, _ proto.DRPCAgentClient24) (retErr error) { if err := manifestOK.wait(ctx); err != nil { return xerrors.Errorf("no manifest: %w", err) } @@ -1279,7 +1301,7 @@ func (a *agent) createTailnet(ctx context.Context, agentID uuid.UUID, derpMap *t // runCoordinator runs a coordinator and returns whether a reconnect // should occur. -func (a *agent) runCoordinator(ctx context.Context, tClient tailnetproto.DRPCTailnetClient23, network *tailnet.Conn) error { +func (a *agent) runCoordinator(ctx context.Context, tClient tailnetproto.DRPCTailnetClient24, network *tailnet.Conn) error { defer a.logger.Debug(ctx, "disconnected from coordination RPC") // we run the RPC on the hardCtx so that we have a chance to send the disconnect message if we // gracefully shut down. @@ -1326,7 +1348,7 @@ func (a *agent) runCoordinator(ctx context.Context, tClient tailnetproto.DRPCTai } // runDERPMapSubscriber runs a coordinator and returns if a reconnect should occur. -func (a *agent) runDERPMapSubscriber(ctx context.Context, tClient tailnetproto.DRPCTailnetClient23, network *tailnet.Conn) error { +func (a *agent) runDERPMapSubscriber(ctx context.Context, tClient tailnetproto.DRPCTailnetClient24, network *tailnet.Conn) error { defer a.logger.Debug(ctx, "disconnected from derp map RPC") ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -1696,8 +1718,8 @@ const ( type apiConnRoutineManager struct { logger slog.Logger - aAPI proto.DRPCAgentClient23 - tAPI tailnetproto.DRPCTailnetClient23 + aAPI proto.DRPCAgentClient24 + tAPI tailnetproto.DRPCTailnetClient24 eg *errgroup.Group stopCtx context.Context remainCtx context.Context @@ -1705,7 +1727,7 @@ type apiConnRoutineManager struct { func newAPIConnRoutineManager( gracefulCtx, hardCtx context.Context, logger slog.Logger, - aAPI proto.DRPCAgentClient23, tAPI tailnetproto.DRPCTailnetClient23, + aAPI proto.DRPCAgentClient24, tAPI tailnetproto.DRPCTailnetClient24, ) *apiConnRoutineManager { // routines that remain in operation during graceful shutdown use the remainCtx. They'll still // exit if the errgroup hits an error, which usually means a problem with the conn. @@ -1738,7 +1760,7 @@ func newAPIConnRoutineManager( // but for Tailnet. func (a *apiConnRoutineManager) startAgentAPI( name string, behavior gracefulShutdownBehavior, - f func(context.Context, proto.DRPCAgentClient23) error, + f func(context.Context, proto.DRPCAgentClient24) error, ) { logger := a.logger.With(slog.F("name", name)) var ctx context.Context @@ -1775,7 +1797,7 @@ func (a *apiConnRoutineManager) startAgentAPI( // but for the Agent API. func (a *apiConnRoutineManager) startTailnetAPI( name string, behavior gracefulShutdownBehavior, - f func(context.Context, tailnetproto.DRPCTailnetClient23) error, + f func(context.Context, tailnetproto.DRPCTailnetClient24) error, ) { logger := a.logger.With(slog.F("name", name)) var ctx context.Context diff --git a/agent/agenttest/client.go b/agent/agenttest/client.go index fbd1ce7060..ea53ae9a67 100644 --- a/agent/agenttest/client.go +++ b/agent/agenttest/client.go @@ -97,8 +97,8 @@ func (c *Client) Close() { c.derpMapOnce.Do(func() { close(c.derpMapUpdates) }) } -func (c *Client) ConnectRPC23(ctx context.Context) ( - agentproto.DRPCAgentClient23, proto.DRPCTailnetClient23, error, +func (c *Client) ConnectRPC24(ctx context.Context) ( + agentproto.DRPCAgentClient24, proto.DRPCTailnetClient24, error, ) { conn, lis := drpcsdk.MemTransportPipe() c.LastWorkspaceAgent = func() { @@ -172,7 +172,9 @@ type FakeAgentAPI struct { metadata map[string]agentsdk.Metadata timings []*agentproto.Timing - getAnnouncementBannersFunc func() ([]codersdk.BannerConfig, error) + getAnnouncementBannersFunc func() ([]codersdk.BannerConfig, error) + getResourcesMonitoringConfigurationFunc func() (*agentproto.GetResourcesMonitoringConfigurationResponse, error) + pushResourcesMonitoringUsageFunc func(*agentproto.PushResourcesMonitoringUsageRequest) (*agentproto.PushResourcesMonitoringUsageResponse, error) } func (f *FakeAgentAPI) GetManifest(context.Context, *agentproto.GetManifestRequest) (*agentproto.Manifest, error) { @@ -213,6 +215,33 @@ func (f *FakeAgentAPI) GetAnnouncementBanners(context.Context, *agentproto.GetAn return &agentproto.GetAnnouncementBannersResponse{AnnouncementBanners: bannersProto}, nil } +func (f *FakeAgentAPI) GetResourcesMonitoringConfiguration(_ context.Context, _ *agentproto.GetResourcesMonitoringConfigurationRequest) (*agentproto.GetResourcesMonitoringConfigurationResponse, error) { + f.Lock() + defer f.Unlock() + + if f.getResourcesMonitoringConfigurationFunc == nil { + return &agentproto.GetResourcesMonitoringConfigurationResponse{ + Config: &agentproto.GetResourcesMonitoringConfigurationResponse_Config{ + CollectionIntervalSeconds: 10, + NumDatapoints: 20, + }, + }, nil + } + + return f.getResourcesMonitoringConfigurationFunc() +} + +func (f *FakeAgentAPI) PushResourcesMonitoringUsage(_ context.Context, req *agentproto.PushResourcesMonitoringUsageRequest) (*agentproto.PushResourcesMonitoringUsageResponse, error) { + f.Lock() + defer f.Unlock() + + if f.pushResourcesMonitoringUsageFunc == nil { + return &agentproto.PushResourcesMonitoringUsageResponse{}, nil + } + + return f.pushResourcesMonitoringUsageFunc(req) +} + func (f *FakeAgentAPI) UpdateStats(ctx context.Context, req *agentproto.UpdateStatsRequest) (*agentproto.UpdateStatsResponse, error) { f.logger.Debug(ctx, "update stats called", slog.F("req", req)) // empty request is sent to get the interval; but our tests don't want empty stats requests diff --git a/agent/proto/agent.pb.go b/agent/proto/agent.pb.go index 4b90e0cf60..613ce3d2d6 100644 --- a/agent/proto/agent.pb.go +++ b/agent/proto/agent.pb.go @@ -2304,6 +2304,192 @@ func (x *Timing) GetStatus() Timing_Status { return Timing_OK } +type GetResourcesMonitoringConfigurationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetResourcesMonitoringConfigurationRequest) Reset() { + *x = GetResourcesMonitoringConfigurationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetResourcesMonitoringConfigurationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResourcesMonitoringConfigurationRequest) ProtoMessage() {} + +func (x *GetResourcesMonitoringConfigurationRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetResourcesMonitoringConfigurationRequest.ProtoReflect.Descriptor instead. +func (*GetResourcesMonitoringConfigurationRequest) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{28} +} + +type GetResourcesMonitoringConfigurationResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Config *GetResourcesMonitoringConfigurationResponse_Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + Memory *GetResourcesMonitoringConfigurationResponse_Memory `protobuf:"bytes,2,opt,name=memory,proto3,oneof" json:"memory,omitempty"` + Volumes []*GetResourcesMonitoringConfigurationResponse_Volume `protobuf:"bytes,3,rep,name=volumes,proto3" json:"volumes,omitempty"` +} + +func (x *GetResourcesMonitoringConfigurationResponse) Reset() { + *x = GetResourcesMonitoringConfigurationResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetResourcesMonitoringConfigurationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResourcesMonitoringConfigurationResponse) ProtoMessage() {} + +func (x *GetResourcesMonitoringConfigurationResponse) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetResourcesMonitoringConfigurationResponse.ProtoReflect.Descriptor instead. +func (*GetResourcesMonitoringConfigurationResponse) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{29} +} + +func (x *GetResourcesMonitoringConfigurationResponse) GetConfig() *GetResourcesMonitoringConfigurationResponse_Config { + if x != nil { + return x.Config + } + return nil +} + +func (x *GetResourcesMonitoringConfigurationResponse) GetMemory() *GetResourcesMonitoringConfigurationResponse_Memory { + if x != nil { + return x.Memory + } + return nil +} + +func (x *GetResourcesMonitoringConfigurationResponse) GetVolumes() []*GetResourcesMonitoringConfigurationResponse_Volume { + if x != nil { + return x.Volumes + } + return nil +} + +type PushResourcesMonitoringUsageRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Datapoints []*PushResourcesMonitoringUsageRequest_Datapoint `protobuf:"bytes,1,rep,name=datapoints,proto3" json:"datapoints,omitempty"` +} + +func (x *PushResourcesMonitoringUsageRequest) Reset() { + *x = PushResourcesMonitoringUsageRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushResourcesMonitoringUsageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushResourcesMonitoringUsageRequest) ProtoMessage() {} + +func (x *PushResourcesMonitoringUsageRequest) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushResourcesMonitoringUsageRequest.ProtoReflect.Descriptor instead. +func (*PushResourcesMonitoringUsageRequest) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{30} +} + +func (x *PushResourcesMonitoringUsageRequest) GetDatapoints() []*PushResourcesMonitoringUsageRequest_Datapoint { + if x != nil { + return x.Datapoints + } + return nil +} + +type PushResourcesMonitoringUsageResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PushResourcesMonitoringUsageResponse) Reset() { + *x = PushResourcesMonitoringUsageResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushResourcesMonitoringUsageResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushResourcesMonitoringUsageResponse) ProtoMessage() {} + +func (x *PushResourcesMonitoringUsageResponse) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushResourcesMonitoringUsageResponse.ProtoReflect.Descriptor instead. +func (*PushResourcesMonitoringUsageResponse) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{31} +} + type WorkspaceApp_Healthcheck struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2317,7 +2503,7 @@ type WorkspaceApp_Healthcheck struct { func (x *WorkspaceApp_Healthcheck) Reset() { *x = WorkspaceApp_Healthcheck{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[28] + mi := &file_agent_proto_agent_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2330,7 +2516,7 @@ func (x *WorkspaceApp_Healthcheck) String() string { func (*WorkspaceApp_Healthcheck) ProtoMessage() {} func (x *WorkspaceApp_Healthcheck) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[28] + mi := &file_agent_proto_agent_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2381,7 +2567,7 @@ type WorkspaceAgentMetadata_Result struct { func (x *WorkspaceAgentMetadata_Result) Reset() { *x = WorkspaceAgentMetadata_Result{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[29] + mi := &file_agent_proto_agent_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2394,7 +2580,7 @@ func (x *WorkspaceAgentMetadata_Result) String() string { func (*WorkspaceAgentMetadata_Result) ProtoMessage() {} func (x *WorkspaceAgentMetadata_Result) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[29] + mi := &file_agent_proto_agent_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2453,7 +2639,7 @@ type WorkspaceAgentMetadata_Description struct { func (x *WorkspaceAgentMetadata_Description) Reset() { *x = WorkspaceAgentMetadata_Description{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[30] + mi := &file_agent_proto_agent_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2466,7 +2652,7 @@ func (x *WorkspaceAgentMetadata_Description) String() string { func (*WorkspaceAgentMetadata_Description) ProtoMessage() {} func (x *WorkspaceAgentMetadata_Description) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[30] + mi := &file_agent_proto_agent_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2531,7 +2717,7 @@ type Stats_Metric struct { func (x *Stats_Metric) Reset() { *x = Stats_Metric{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[33] + mi := &file_agent_proto_agent_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2544,7 +2730,7 @@ func (x *Stats_Metric) String() string { func (*Stats_Metric) ProtoMessage() {} func (x *Stats_Metric) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[33] + mi := &file_agent_proto_agent_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2600,7 +2786,7 @@ type Stats_Metric_Label struct { func (x *Stats_Metric_Label) Reset() { *x = Stats_Metric_Label{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[34] + mi := &file_agent_proto_agent_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2613,7 +2799,7 @@ func (x *Stats_Metric_Label) String() string { func (*Stats_Metric_Label) ProtoMessage() {} func (x *Stats_Metric_Label) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[34] + mi := &file_agent_proto_agent_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2655,7 +2841,7 @@ type BatchUpdateAppHealthRequest_HealthUpdate struct { func (x *BatchUpdateAppHealthRequest_HealthUpdate) Reset() { *x = BatchUpdateAppHealthRequest_HealthUpdate{} if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_agent_proto_msgTypes[35] + mi := &file_agent_proto_agent_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2668,7 +2854,7 @@ func (x *BatchUpdateAppHealthRequest_HealthUpdate) String() string { func (*BatchUpdateAppHealthRequest_HealthUpdate) ProtoMessage() {} func (x *BatchUpdateAppHealthRequest_HealthUpdate) ProtoReflect() protoreflect.Message { - mi := &file_agent_proto_agent_proto_msgTypes[35] + mi := &file_agent_proto_agent_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2698,6 +2884,344 @@ func (x *BatchUpdateAppHealthRequest_HealthUpdate) GetHealth() AppHealth { return AppHealth_APP_HEALTH_UNSPECIFIED } +type GetResourcesMonitoringConfigurationResponse_Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NumDatapoints int32 `protobuf:"varint,1,opt,name=num_datapoints,json=numDatapoints,proto3" json:"num_datapoints,omitempty"` + CollectionIntervalSeconds int32 `protobuf:"varint,2,opt,name=collection_interval_seconds,json=collectionIntervalSeconds,proto3" json:"collection_interval_seconds,omitempty"` +} + +func (x *GetResourcesMonitoringConfigurationResponse_Config) Reset() { + *x = GetResourcesMonitoringConfigurationResponse_Config{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetResourcesMonitoringConfigurationResponse_Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResourcesMonitoringConfigurationResponse_Config) ProtoMessage() {} + +func (x *GetResourcesMonitoringConfigurationResponse_Config) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[40] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetResourcesMonitoringConfigurationResponse_Config.ProtoReflect.Descriptor instead. +func (*GetResourcesMonitoringConfigurationResponse_Config) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{29, 0} +} + +func (x *GetResourcesMonitoringConfigurationResponse_Config) GetNumDatapoints() int32 { + if x != nil { + return x.NumDatapoints + } + return 0 +} + +func (x *GetResourcesMonitoringConfigurationResponse_Config) GetCollectionIntervalSeconds() int32 { + if x != nil { + return x.CollectionIntervalSeconds + } + return 0 +} + +type GetResourcesMonitoringConfigurationResponse_Memory struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` +} + +func (x *GetResourcesMonitoringConfigurationResponse_Memory) Reset() { + *x = GetResourcesMonitoringConfigurationResponse_Memory{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetResourcesMonitoringConfigurationResponse_Memory) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResourcesMonitoringConfigurationResponse_Memory) ProtoMessage() {} + +func (x *GetResourcesMonitoringConfigurationResponse_Memory) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[41] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetResourcesMonitoringConfigurationResponse_Memory.ProtoReflect.Descriptor instead. +func (*GetResourcesMonitoringConfigurationResponse_Memory) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{29, 1} +} + +func (x *GetResourcesMonitoringConfigurationResponse_Memory) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type GetResourcesMonitoringConfigurationResponse_Volume struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` +} + +func (x *GetResourcesMonitoringConfigurationResponse_Volume) Reset() { + *x = GetResourcesMonitoringConfigurationResponse_Volume{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetResourcesMonitoringConfigurationResponse_Volume) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResourcesMonitoringConfigurationResponse_Volume) ProtoMessage() {} + +func (x *GetResourcesMonitoringConfigurationResponse_Volume) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[42] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetResourcesMonitoringConfigurationResponse_Volume.ProtoReflect.Descriptor instead. +func (*GetResourcesMonitoringConfigurationResponse_Volume) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{29, 2} +} + +func (x *GetResourcesMonitoringConfigurationResponse_Volume) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *GetResourcesMonitoringConfigurationResponse_Volume) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +type PushResourcesMonitoringUsageRequest_Datapoint struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CollectedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=collected_at,json=collectedAt,proto3" json:"collected_at,omitempty"` + Memory *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage `protobuf:"bytes,2,opt,name=memory,proto3,oneof" json:"memory,omitempty"` + Volumes []*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage `protobuf:"bytes,3,rep,name=volumes,proto3" json:"volumes,omitempty"` +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint) Reset() { + *x = PushResourcesMonitoringUsageRequest_Datapoint{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushResourcesMonitoringUsageRequest_Datapoint) ProtoMessage() {} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[43] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushResourcesMonitoringUsageRequest_Datapoint.ProtoReflect.Descriptor instead. +func (*PushResourcesMonitoringUsageRequest_Datapoint) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{30, 0} +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint) GetCollectedAt() *timestamppb.Timestamp { + if x != nil { + return x.CollectedAt + } + return nil +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint) GetMemory() *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage { + if x != nil { + return x.Memory + } + return nil +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint) GetVolumes() []*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage { + if x != nil { + return x.Volumes + } + return nil +} + +type PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Used int64 `protobuf:"varint,1,opt,name=used,proto3" json:"used,omitempty"` + Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) Reset() { + *x = PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) ProtoMessage() {} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[44] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage.ProtoReflect.Descriptor instead. +func (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{30, 0, 0} +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) GetUsed() int64 { + if x != nil { + return x.Used + } + return 0 +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +type PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Volume string `protobuf:"bytes,1,opt,name=volume,proto3" json:"volume,omitempty"` + Used int64 `protobuf:"varint,2,opt,name=used,proto3" json:"used,omitempty"` + Total int64 `protobuf:"varint,3,opt,name=total,proto3" json:"total,omitempty"` +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) Reset() { + *x = PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage{} + if protoimpl.UnsafeEnabled { + mi := &file_agent_proto_agent_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) ProtoMessage() {} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) ProtoReflect() protoreflect.Message { + mi := &file_agent_proto_agent_proto_msgTypes[45] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage.ProtoReflect.Descriptor instead. +func (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) Descriptor() ([]byte, []int) { + return file_agent_proto_agent_proto_rawDescGZIP(), []int{30, 0, 1} +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) GetVolume() string { + if x != nil { + return x.Volume + } + return "" +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) GetUsed() int64 { + if x != nil { + return x.Used + } + return 0 +} + +func (x *PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + var File_agent_proto_agent_proto protoreflect.FileDescriptor var file_agent_proto_agent_proto_rawDesc = []byte{ @@ -3092,79 +3616,173 @@ var file_agent_proto_agent_proto_rawDesc = []byte{ 0x0c, 0x45, 0x58, 0x49, 0x54, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x49, 0x4d, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x49, 0x50, 0x45, 0x53, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x5f, 0x4f, 0x50, 0x45, - 0x4e, 0x10, 0x03, 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x55, - 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, - 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, - 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, - 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x48, - 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, 0xef, 0x07, 0x0a, 0x05, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, - 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, - 0x5a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, - 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x0b, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, + 0x4e, 0x10, 0x03, 0x22, 0x2c, 0x0a, 0x2a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0xa0, 0x04, 0x0a, 0x2b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x5a, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, + 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5f, 0x0a, + 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, + 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, + 0x79, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, 0x12, 0x5c, + 0x0a, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, + 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x56, 0x6f, 0x6c, + 0x75, 0x6d, 0x65, 0x52, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x1a, 0x6f, 0x0a, 0x06, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x64, 0x61, + 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, + 0x6e, 0x75, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x3e, 0x0a, + 0x1b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x19, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x1a, 0x22, 0x0a, + 0x06, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x1a, 0x36, 0x0a, 0x06, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, + 0x6d, 0x6f, 0x72, 0x79, 0x22, 0xb3, 0x04, 0x0a, 0x23, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, + 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5d, 0x0a, 0x0a, + 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x3d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, + 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, + 0x0a, 0x64, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x1a, 0xac, 0x03, 0x0a, 0x09, + 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x66, 0x0a, 0x06, 0x6d, 0x65, 0x6d, 0x6f, + 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, + 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, + 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, + 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x88, 0x01, 0x01, + 0x12, 0x63, 0x0a, 0x07, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x49, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x76, 0x6f, + 0x6c, 0x75, 0x6d, 0x65, 0x73, 0x1a, 0x37, 0x0a, 0x0b, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x1a, 0x4f, + 0x0a, 0x0b, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x76, + 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, + 0x09, 0x0a, 0x07, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22, 0x26, 0x0a, 0x24, 0x50, 0x75, + 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, + 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, + 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, + 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x49, + 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x48, + 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x48, 0x45, + 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, 0x9c, 0x0a, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, + 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x5a, + 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, + 0x65, 0x72, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, + 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x0b, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, + 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, + 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4c, + 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x42, 0x61, 0x74, 0x63, + 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, + 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x66, - 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69, - 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x42, 0x61, 0x74, - 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, - 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, - 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, - 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x24, - 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x6e, 0x0a, - 0x13, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, - 0x0f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, - 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, - 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x77, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x2e, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, - 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x64, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x24, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x6e, 0x0a, 0x13, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0f, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x12, + 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, + 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x77, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, - 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x0f, 0x53, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x34, 0x2e, - 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, - 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, - 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x64, 0x65, + 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, + 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x0f, 0x53, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x34, 0x2e, 0x63, + 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2e, 0x76, 0x32, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x41, 0x67, 0x65, + 0x6e, 0x74, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9e, 0x01, 0x0a, 0x23, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, + 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x3a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, + 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, + 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 0x2e, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, + 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x1c, 0x50, + 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, + 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x33, 0x2e, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x75, 0x73, + 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, + 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x34, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, + 0x32, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x4d, + 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3180,122 +3798,143 @@ func file_agent_proto_agent_proto_rawDescGZIP() []byte { } var file_agent_proto_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 9) -var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 46) var file_agent_proto_agent_proto_goTypes = []interface{}{ - (AppHealth)(0), // 0: coder.agent.v2.AppHealth - (WorkspaceApp_SharingLevel)(0), // 1: coder.agent.v2.WorkspaceApp.SharingLevel - (WorkspaceApp_Health)(0), // 2: coder.agent.v2.WorkspaceApp.Health - (Stats_Metric_Type)(0), // 3: coder.agent.v2.Stats.Metric.Type - (Lifecycle_State)(0), // 4: coder.agent.v2.Lifecycle.State - (Startup_Subsystem)(0), // 5: coder.agent.v2.Startup.Subsystem - (Log_Level)(0), // 6: coder.agent.v2.Log.Level - (Timing_Stage)(0), // 7: coder.agent.v2.Timing.Stage - (Timing_Status)(0), // 8: coder.agent.v2.Timing.Status - (*WorkspaceApp)(nil), // 9: coder.agent.v2.WorkspaceApp - (*WorkspaceAgentScript)(nil), // 10: coder.agent.v2.WorkspaceAgentScript - (*WorkspaceAgentMetadata)(nil), // 11: coder.agent.v2.WorkspaceAgentMetadata - (*Manifest)(nil), // 12: coder.agent.v2.Manifest - (*GetManifestRequest)(nil), // 13: coder.agent.v2.GetManifestRequest - (*ServiceBanner)(nil), // 14: coder.agent.v2.ServiceBanner - (*GetServiceBannerRequest)(nil), // 15: coder.agent.v2.GetServiceBannerRequest - (*Stats)(nil), // 16: coder.agent.v2.Stats - (*UpdateStatsRequest)(nil), // 17: coder.agent.v2.UpdateStatsRequest - (*UpdateStatsResponse)(nil), // 18: coder.agent.v2.UpdateStatsResponse - (*Lifecycle)(nil), // 19: coder.agent.v2.Lifecycle - (*UpdateLifecycleRequest)(nil), // 20: coder.agent.v2.UpdateLifecycleRequest - (*BatchUpdateAppHealthRequest)(nil), // 21: coder.agent.v2.BatchUpdateAppHealthRequest - (*BatchUpdateAppHealthResponse)(nil), // 22: coder.agent.v2.BatchUpdateAppHealthResponse - (*Startup)(nil), // 23: coder.agent.v2.Startup - (*UpdateStartupRequest)(nil), // 24: coder.agent.v2.UpdateStartupRequest - (*Metadata)(nil), // 25: coder.agent.v2.Metadata - (*BatchUpdateMetadataRequest)(nil), // 26: coder.agent.v2.BatchUpdateMetadataRequest - (*BatchUpdateMetadataResponse)(nil), // 27: coder.agent.v2.BatchUpdateMetadataResponse - (*Log)(nil), // 28: coder.agent.v2.Log - (*BatchCreateLogsRequest)(nil), // 29: coder.agent.v2.BatchCreateLogsRequest - (*BatchCreateLogsResponse)(nil), // 30: coder.agent.v2.BatchCreateLogsResponse - (*GetAnnouncementBannersRequest)(nil), // 31: coder.agent.v2.GetAnnouncementBannersRequest - (*GetAnnouncementBannersResponse)(nil), // 32: coder.agent.v2.GetAnnouncementBannersResponse - (*BannerConfig)(nil), // 33: coder.agent.v2.BannerConfig - (*WorkspaceAgentScriptCompletedRequest)(nil), // 34: coder.agent.v2.WorkspaceAgentScriptCompletedRequest - (*WorkspaceAgentScriptCompletedResponse)(nil), // 35: coder.agent.v2.WorkspaceAgentScriptCompletedResponse - (*Timing)(nil), // 36: coder.agent.v2.Timing - (*WorkspaceApp_Healthcheck)(nil), // 37: coder.agent.v2.WorkspaceApp.Healthcheck - (*WorkspaceAgentMetadata_Result)(nil), // 38: coder.agent.v2.WorkspaceAgentMetadata.Result - (*WorkspaceAgentMetadata_Description)(nil), // 39: coder.agent.v2.WorkspaceAgentMetadata.Description - nil, // 40: coder.agent.v2.Manifest.EnvironmentVariablesEntry - nil, // 41: coder.agent.v2.Stats.ConnectionsByProtoEntry - (*Stats_Metric)(nil), // 42: coder.agent.v2.Stats.Metric - (*Stats_Metric_Label)(nil), // 43: coder.agent.v2.Stats.Metric.Label - (*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 44: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate - (*durationpb.Duration)(nil), // 45: google.protobuf.Duration - (*proto.DERPMap)(nil), // 46: coder.tailnet.v2.DERPMap - (*timestamppb.Timestamp)(nil), // 47: google.protobuf.Timestamp + (AppHealth)(0), // 0: coder.agent.v2.AppHealth + (WorkspaceApp_SharingLevel)(0), // 1: coder.agent.v2.WorkspaceApp.SharingLevel + (WorkspaceApp_Health)(0), // 2: coder.agent.v2.WorkspaceApp.Health + (Stats_Metric_Type)(0), // 3: coder.agent.v2.Stats.Metric.Type + (Lifecycle_State)(0), // 4: coder.agent.v2.Lifecycle.State + (Startup_Subsystem)(0), // 5: coder.agent.v2.Startup.Subsystem + (Log_Level)(0), // 6: coder.agent.v2.Log.Level + (Timing_Stage)(0), // 7: coder.agent.v2.Timing.Stage + (Timing_Status)(0), // 8: coder.agent.v2.Timing.Status + (*WorkspaceApp)(nil), // 9: coder.agent.v2.WorkspaceApp + (*WorkspaceAgentScript)(nil), // 10: coder.agent.v2.WorkspaceAgentScript + (*WorkspaceAgentMetadata)(nil), // 11: coder.agent.v2.WorkspaceAgentMetadata + (*Manifest)(nil), // 12: coder.agent.v2.Manifest + (*GetManifestRequest)(nil), // 13: coder.agent.v2.GetManifestRequest + (*ServiceBanner)(nil), // 14: coder.agent.v2.ServiceBanner + (*GetServiceBannerRequest)(nil), // 15: coder.agent.v2.GetServiceBannerRequest + (*Stats)(nil), // 16: coder.agent.v2.Stats + (*UpdateStatsRequest)(nil), // 17: coder.agent.v2.UpdateStatsRequest + (*UpdateStatsResponse)(nil), // 18: coder.agent.v2.UpdateStatsResponse + (*Lifecycle)(nil), // 19: coder.agent.v2.Lifecycle + (*UpdateLifecycleRequest)(nil), // 20: coder.agent.v2.UpdateLifecycleRequest + (*BatchUpdateAppHealthRequest)(nil), // 21: coder.agent.v2.BatchUpdateAppHealthRequest + (*BatchUpdateAppHealthResponse)(nil), // 22: coder.agent.v2.BatchUpdateAppHealthResponse + (*Startup)(nil), // 23: coder.agent.v2.Startup + (*UpdateStartupRequest)(nil), // 24: coder.agent.v2.UpdateStartupRequest + (*Metadata)(nil), // 25: coder.agent.v2.Metadata + (*BatchUpdateMetadataRequest)(nil), // 26: coder.agent.v2.BatchUpdateMetadataRequest + (*BatchUpdateMetadataResponse)(nil), // 27: coder.agent.v2.BatchUpdateMetadataResponse + (*Log)(nil), // 28: coder.agent.v2.Log + (*BatchCreateLogsRequest)(nil), // 29: coder.agent.v2.BatchCreateLogsRequest + (*BatchCreateLogsResponse)(nil), // 30: coder.agent.v2.BatchCreateLogsResponse + (*GetAnnouncementBannersRequest)(nil), // 31: coder.agent.v2.GetAnnouncementBannersRequest + (*GetAnnouncementBannersResponse)(nil), // 32: coder.agent.v2.GetAnnouncementBannersResponse + (*BannerConfig)(nil), // 33: coder.agent.v2.BannerConfig + (*WorkspaceAgentScriptCompletedRequest)(nil), // 34: coder.agent.v2.WorkspaceAgentScriptCompletedRequest + (*WorkspaceAgentScriptCompletedResponse)(nil), // 35: coder.agent.v2.WorkspaceAgentScriptCompletedResponse + (*Timing)(nil), // 36: coder.agent.v2.Timing + (*GetResourcesMonitoringConfigurationRequest)(nil), // 37: coder.agent.v2.GetResourcesMonitoringConfigurationRequest + (*GetResourcesMonitoringConfigurationResponse)(nil), // 38: coder.agent.v2.GetResourcesMonitoringConfigurationResponse + (*PushResourcesMonitoringUsageRequest)(nil), // 39: coder.agent.v2.PushResourcesMonitoringUsageRequest + (*PushResourcesMonitoringUsageResponse)(nil), // 40: coder.agent.v2.PushResourcesMonitoringUsageResponse + (*WorkspaceApp_Healthcheck)(nil), // 41: coder.agent.v2.WorkspaceApp.Healthcheck + (*WorkspaceAgentMetadata_Result)(nil), // 42: coder.agent.v2.WorkspaceAgentMetadata.Result + (*WorkspaceAgentMetadata_Description)(nil), // 43: coder.agent.v2.WorkspaceAgentMetadata.Description + nil, // 44: coder.agent.v2.Manifest.EnvironmentVariablesEntry + nil, // 45: coder.agent.v2.Stats.ConnectionsByProtoEntry + (*Stats_Metric)(nil), // 46: coder.agent.v2.Stats.Metric + (*Stats_Metric_Label)(nil), // 47: coder.agent.v2.Stats.Metric.Label + (*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 48: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate + (*GetResourcesMonitoringConfigurationResponse_Config)(nil), // 49: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config + (*GetResourcesMonitoringConfigurationResponse_Memory)(nil), // 50: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory + (*GetResourcesMonitoringConfigurationResponse_Volume)(nil), // 51: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume + (*PushResourcesMonitoringUsageRequest_Datapoint)(nil), // 52: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint + (*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage)(nil), // 53: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage + (*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage)(nil), // 54: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage + (*durationpb.Duration)(nil), // 55: google.protobuf.Duration + (*proto.DERPMap)(nil), // 56: coder.tailnet.v2.DERPMap + (*timestamppb.Timestamp)(nil), // 57: google.protobuf.Timestamp } var file_agent_proto_agent_proto_depIdxs = []int32{ 1, // 0: coder.agent.v2.WorkspaceApp.sharing_level:type_name -> coder.agent.v2.WorkspaceApp.SharingLevel - 37, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.Healthcheck + 41, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.Healthcheck 2, // 2: coder.agent.v2.WorkspaceApp.health:type_name -> coder.agent.v2.WorkspaceApp.Health - 45, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration - 38, // 4: coder.agent.v2.WorkspaceAgentMetadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result - 39, // 5: coder.agent.v2.WorkspaceAgentMetadata.description:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description - 40, // 6: coder.agent.v2.Manifest.environment_variables:type_name -> coder.agent.v2.Manifest.EnvironmentVariablesEntry - 46, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap + 55, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration + 42, // 4: coder.agent.v2.WorkspaceAgentMetadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result + 43, // 5: coder.agent.v2.WorkspaceAgentMetadata.description:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description + 44, // 6: coder.agent.v2.Manifest.environment_variables:type_name -> coder.agent.v2.Manifest.EnvironmentVariablesEntry + 56, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap 10, // 8: coder.agent.v2.Manifest.scripts:type_name -> coder.agent.v2.WorkspaceAgentScript 9, // 9: coder.agent.v2.Manifest.apps:type_name -> coder.agent.v2.WorkspaceApp - 39, // 10: coder.agent.v2.Manifest.metadata:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description - 41, // 11: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry - 42, // 12: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric + 43, // 10: coder.agent.v2.Manifest.metadata:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description + 45, // 11: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry + 46, // 12: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric 16, // 13: coder.agent.v2.UpdateStatsRequest.stats:type_name -> coder.agent.v2.Stats - 45, // 14: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration + 55, // 14: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration 4, // 15: coder.agent.v2.Lifecycle.state:type_name -> coder.agent.v2.Lifecycle.State - 47, // 16: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp + 57, // 16: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp 19, // 17: coder.agent.v2.UpdateLifecycleRequest.lifecycle:type_name -> coder.agent.v2.Lifecycle - 44, // 18: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate + 48, // 18: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate 5, // 19: coder.agent.v2.Startup.subsystems:type_name -> coder.agent.v2.Startup.Subsystem 23, // 20: coder.agent.v2.UpdateStartupRequest.startup:type_name -> coder.agent.v2.Startup - 38, // 21: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result + 42, // 21: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result 25, // 22: coder.agent.v2.BatchUpdateMetadataRequest.metadata:type_name -> coder.agent.v2.Metadata - 47, // 23: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp + 57, // 23: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp 6, // 24: coder.agent.v2.Log.level:type_name -> coder.agent.v2.Log.Level 28, // 25: coder.agent.v2.BatchCreateLogsRequest.logs:type_name -> coder.agent.v2.Log 33, // 26: coder.agent.v2.GetAnnouncementBannersResponse.announcement_banners:type_name -> coder.agent.v2.BannerConfig 36, // 27: coder.agent.v2.WorkspaceAgentScriptCompletedRequest.timing:type_name -> coder.agent.v2.Timing - 47, // 28: coder.agent.v2.Timing.start:type_name -> google.protobuf.Timestamp - 47, // 29: coder.agent.v2.Timing.end:type_name -> google.protobuf.Timestamp + 57, // 28: coder.agent.v2.Timing.start:type_name -> google.protobuf.Timestamp + 57, // 29: coder.agent.v2.Timing.end:type_name -> google.protobuf.Timestamp 7, // 30: coder.agent.v2.Timing.stage:type_name -> coder.agent.v2.Timing.Stage 8, // 31: coder.agent.v2.Timing.status:type_name -> coder.agent.v2.Timing.Status - 45, // 32: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration - 47, // 33: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp - 45, // 34: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration - 45, // 35: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration - 3, // 36: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type - 43, // 37: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label - 0, // 38: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth - 13, // 39: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest - 15, // 40: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest - 17, // 41: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest - 20, // 42: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest - 21, // 43: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest - 24, // 44: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest - 26, // 45: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest - 29, // 46: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest - 31, // 47: coder.agent.v2.Agent.GetAnnouncementBanners:input_type -> coder.agent.v2.GetAnnouncementBannersRequest - 34, // 48: coder.agent.v2.Agent.ScriptCompleted:input_type -> coder.agent.v2.WorkspaceAgentScriptCompletedRequest - 12, // 49: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest - 14, // 50: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner - 18, // 51: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse - 19, // 52: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle - 22, // 53: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse - 23, // 54: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup - 27, // 55: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse - 30, // 56: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse - 32, // 57: coder.agent.v2.Agent.GetAnnouncementBanners:output_type -> coder.agent.v2.GetAnnouncementBannersResponse - 35, // 58: coder.agent.v2.Agent.ScriptCompleted:output_type -> coder.agent.v2.WorkspaceAgentScriptCompletedResponse - 49, // [49:59] is the sub-list for method output_type - 39, // [39:49] is the sub-list for method input_type - 39, // [39:39] is the sub-list for extension type_name - 39, // [39:39] is the sub-list for extension extendee - 0, // [0:39] is the sub-list for field type_name + 49, // 32: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.config:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Config + 50, // 33: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.memory:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Memory + 51, // 34: coder.agent.v2.GetResourcesMonitoringConfigurationResponse.volumes:type_name -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse.Volume + 52, // 35: coder.agent.v2.PushResourcesMonitoringUsageRequest.datapoints:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint + 55, // 36: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration + 57, // 37: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp + 55, // 38: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration + 55, // 39: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration + 3, // 40: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type + 47, // 41: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label + 0, // 42: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth + 57, // 43: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.collected_at:type_name -> google.protobuf.Timestamp + 53, // 44: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.memory:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.MemoryUsage + 54, // 45: coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.volumes:type_name -> coder.agent.v2.PushResourcesMonitoringUsageRequest.Datapoint.VolumeUsage + 13, // 46: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest + 15, // 47: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest + 17, // 48: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest + 20, // 49: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest + 21, // 50: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest + 24, // 51: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest + 26, // 52: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest + 29, // 53: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest + 31, // 54: coder.agent.v2.Agent.GetAnnouncementBanners:input_type -> coder.agent.v2.GetAnnouncementBannersRequest + 34, // 55: coder.agent.v2.Agent.ScriptCompleted:input_type -> coder.agent.v2.WorkspaceAgentScriptCompletedRequest + 37, // 56: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:input_type -> coder.agent.v2.GetResourcesMonitoringConfigurationRequest + 39, // 57: coder.agent.v2.Agent.PushResourcesMonitoringUsage:input_type -> coder.agent.v2.PushResourcesMonitoringUsageRequest + 12, // 58: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest + 14, // 59: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner + 18, // 60: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse + 19, // 61: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle + 22, // 62: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse + 23, // 63: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup + 27, // 64: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse + 30, // 65: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse + 32, // 66: coder.agent.v2.Agent.GetAnnouncementBanners:output_type -> coder.agent.v2.GetAnnouncementBannersResponse + 35, // 67: coder.agent.v2.Agent.ScriptCompleted:output_type -> coder.agent.v2.WorkspaceAgentScriptCompletedResponse + 38, // 68: coder.agent.v2.Agent.GetResourcesMonitoringConfiguration:output_type -> coder.agent.v2.GetResourcesMonitoringConfigurationResponse + 40, // 69: coder.agent.v2.Agent.PushResourcesMonitoringUsage:output_type -> coder.agent.v2.PushResourcesMonitoringUsageResponse + 58, // [58:70] is the sub-list for method output_type + 46, // [46:58] is the sub-list for method input_type + 46, // [46:46] is the sub-list for extension type_name + 46, // [46:46] is the sub-list for extension extendee + 0, // [0:46] is the sub-list for field type_name } func init() { file_agent_proto_agent_proto_init() } @@ -3641,7 +4280,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceApp_Healthcheck); i { + switch v := v.(*GetResourcesMonitoringConfigurationRequest); i { case 0: return &v.state case 1: @@ -3653,7 +4292,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceAgentMetadata_Result); i { + switch v := v.(*GetResourcesMonitoringConfigurationResponse); i { case 0: return &v.state case 1: @@ -3665,7 +4304,31 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkspaceAgentMetadata_Description); i { + switch v := v.(*PushResourcesMonitoringUsageRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushResourcesMonitoringUsageResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WorkspaceApp_Healthcheck); i { case 0: return &v.state case 1: @@ -3677,7 +4340,7 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Stats_Metric); i { + switch v := v.(*WorkspaceAgentMetadata_Result); i { case 0: return &v.state case 1: @@ -3689,6 +4352,30 @@ func file_agent_proto_agent_proto_init() { } } file_agent_proto_agent_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WorkspaceAgentMetadata_Description); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Stats_Metric); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Stats_Metric_Label); i { case 0: return &v.state @@ -3700,7 +4387,7 @@ func file_agent_proto_agent_proto_init() { return nil } } - file_agent_proto_agent_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + file_agent_proto_agent_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BatchUpdateAppHealthRequest_HealthUpdate); i { case 0: return &v.state @@ -3712,14 +4399,88 @@ func file_agent_proto_agent_proto_init() { return nil } } + file_agent_proto_agent_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetResourcesMonitoringConfigurationResponse_Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetResourcesMonitoringConfigurationResponse_Memory); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetResourcesMonitoringConfigurationResponse_Volume); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_agent_proto_agent_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } + file_agent_proto_agent_proto_msgTypes[29].OneofWrappers = []interface{}{} + file_agent_proto_agent_proto_msgTypes[43].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_agent_proto_agent_proto_rawDesc, NumEnums: 9, - NumMessages: 36, + NumMessages: 46, NumExtensions: 0, NumServices: 1, }, diff --git a/agent/proto/agent.proto b/agent/proto/agent.proto index f307066fcb..6bb802d966 100644 --- a/agent/proto/agent.proto +++ b/agent/proto/agent.proto @@ -295,6 +295,51 @@ message Timing { Status status = 6; } +message GetResourcesMonitoringConfigurationRequest { +} + +message GetResourcesMonitoringConfigurationResponse { + message Config { + int32 num_datapoints = 1; + int32 collection_interval_seconds = 2; + } + Config config = 1; + + message Memory { + bool enabled = 1; + } + optional Memory memory = 2; + + message Volume { + bool enabled = 1; + string path = 2; + } + repeated Volume volumes = 3; +} + +message PushResourcesMonitoringUsageRequest { + message Datapoint { + message MemoryUsage { + int64 used = 1; + int64 total = 2; + } + message VolumeUsage { + string volume = 1; + int64 used = 2; + int64 total = 3; + } + + google.protobuf.Timestamp collected_at = 1; + optional MemoryUsage memory = 2; + repeated VolumeUsage volumes = 3; + + } + repeated Datapoint datapoints = 1; +} + +message PushResourcesMonitoringUsageResponse { +} + service Agent { rpc GetManifest(GetManifestRequest) returns (Manifest); rpc GetServiceBanner(GetServiceBannerRequest) returns (ServiceBanner); @@ -306,4 +351,6 @@ service Agent { rpc BatchCreateLogs(BatchCreateLogsRequest) returns (BatchCreateLogsResponse); rpc GetAnnouncementBanners(GetAnnouncementBannersRequest) returns (GetAnnouncementBannersResponse); rpc ScriptCompleted(WorkspaceAgentScriptCompletedRequest) returns (WorkspaceAgentScriptCompletedResponse); + rpc GetResourcesMonitoringConfiguration(GetResourcesMonitoringConfigurationRequest) returns (GetResourcesMonitoringConfigurationResponse); + rpc PushResourcesMonitoringUsage(PushResourcesMonitoringUsageRequest) returns (PushResourcesMonitoringUsageResponse); } diff --git a/agent/proto/agent_drpc.pb.go b/agent/proto/agent_drpc.pb.go index 9f7e21c962..2a90380185 100644 --- a/agent/proto/agent_drpc.pb.go +++ b/agent/proto/agent_drpc.pb.go @@ -48,6 +48,8 @@ type DRPCAgentClient interface { BatchCreateLogs(ctx context.Context, in *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error) GetAnnouncementBanners(ctx context.Context, in *GetAnnouncementBannersRequest) (*GetAnnouncementBannersResponse, error) ScriptCompleted(ctx context.Context, in *WorkspaceAgentScriptCompletedRequest) (*WorkspaceAgentScriptCompletedResponse, error) + GetResourcesMonitoringConfiguration(ctx context.Context, in *GetResourcesMonitoringConfigurationRequest) (*GetResourcesMonitoringConfigurationResponse, error) + PushResourcesMonitoringUsage(ctx context.Context, in *PushResourcesMonitoringUsageRequest) (*PushResourcesMonitoringUsageResponse, error) } type drpcAgentClient struct { @@ -150,6 +152,24 @@ func (c *drpcAgentClient) ScriptCompleted(ctx context.Context, in *WorkspaceAgen return out, nil } +func (c *drpcAgentClient) GetResourcesMonitoringConfiguration(ctx context.Context, in *GetResourcesMonitoringConfigurationRequest) (*GetResourcesMonitoringConfigurationResponse, error) { + out := new(GetResourcesMonitoringConfigurationResponse) + err := c.cc.Invoke(ctx, "/coder.agent.v2.Agent/GetResourcesMonitoringConfiguration", drpcEncoding_File_agent_proto_agent_proto{}, in, out) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *drpcAgentClient) PushResourcesMonitoringUsage(ctx context.Context, in *PushResourcesMonitoringUsageRequest) (*PushResourcesMonitoringUsageResponse, error) { + out := new(PushResourcesMonitoringUsageResponse) + err := c.cc.Invoke(ctx, "/coder.agent.v2.Agent/PushResourcesMonitoringUsage", drpcEncoding_File_agent_proto_agent_proto{}, in, out) + if err != nil { + return nil, err + } + return out, nil +} + type DRPCAgentServer interface { GetManifest(context.Context, *GetManifestRequest) (*Manifest, error) GetServiceBanner(context.Context, *GetServiceBannerRequest) (*ServiceBanner, error) @@ -161,6 +181,8 @@ type DRPCAgentServer interface { BatchCreateLogs(context.Context, *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error) GetAnnouncementBanners(context.Context, *GetAnnouncementBannersRequest) (*GetAnnouncementBannersResponse, error) ScriptCompleted(context.Context, *WorkspaceAgentScriptCompletedRequest) (*WorkspaceAgentScriptCompletedResponse, error) + GetResourcesMonitoringConfiguration(context.Context, *GetResourcesMonitoringConfigurationRequest) (*GetResourcesMonitoringConfigurationResponse, error) + PushResourcesMonitoringUsage(context.Context, *PushResourcesMonitoringUsageRequest) (*PushResourcesMonitoringUsageResponse, error) } type DRPCAgentUnimplementedServer struct{} @@ -205,9 +227,17 @@ func (s *DRPCAgentUnimplementedServer) ScriptCompleted(context.Context, *Workspa return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented) } +func (s *DRPCAgentUnimplementedServer) GetResourcesMonitoringConfiguration(context.Context, *GetResourcesMonitoringConfigurationRequest) (*GetResourcesMonitoringConfigurationResponse, error) { + return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented) +} + +func (s *DRPCAgentUnimplementedServer) PushResourcesMonitoringUsage(context.Context, *PushResourcesMonitoringUsageRequest) (*PushResourcesMonitoringUsageResponse, error) { + return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented) +} + type DRPCAgentDescription struct{} -func (DRPCAgentDescription) NumMethods() int { return 10 } +func (DRPCAgentDescription) NumMethods() int { return 12 } func (DRPCAgentDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) { switch n { @@ -301,6 +331,24 @@ func (DRPCAgentDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, in1.(*WorkspaceAgentScriptCompletedRequest), ) }, DRPCAgentServer.ScriptCompleted, true + case 10: + return "/coder.agent.v2.Agent/GetResourcesMonitoringConfiguration", drpcEncoding_File_agent_proto_agent_proto{}, + func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { + return srv.(DRPCAgentServer). + GetResourcesMonitoringConfiguration( + ctx, + in1.(*GetResourcesMonitoringConfigurationRequest), + ) + }, DRPCAgentServer.GetResourcesMonitoringConfiguration, true + case 11: + return "/coder.agent.v2.Agent/PushResourcesMonitoringUsage", drpcEncoding_File_agent_proto_agent_proto{}, + func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { + return srv.(DRPCAgentServer). + PushResourcesMonitoringUsage( + ctx, + in1.(*PushResourcesMonitoringUsageRequest), + ) + }, DRPCAgentServer.PushResourcesMonitoringUsage, true default: return "", nil, nil, nil, false } @@ -469,3 +517,35 @@ func (x *drpcAgent_ScriptCompletedStream) SendAndClose(m *WorkspaceAgentScriptCo } return x.CloseSend() } + +type DRPCAgent_GetResourcesMonitoringConfigurationStream interface { + drpc.Stream + SendAndClose(*GetResourcesMonitoringConfigurationResponse) error +} + +type drpcAgent_GetResourcesMonitoringConfigurationStream struct { + drpc.Stream +} + +func (x *drpcAgent_GetResourcesMonitoringConfigurationStream) SendAndClose(m *GetResourcesMonitoringConfigurationResponse) error { + if err := x.MsgSend(m, drpcEncoding_File_agent_proto_agent_proto{}); err != nil { + return err + } + return x.CloseSend() +} + +type DRPCAgent_PushResourcesMonitoringUsageStream interface { + drpc.Stream + SendAndClose(*PushResourcesMonitoringUsageResponse) error +} + +type drpcAgent_PushResourcesMonitoringUsageStream struct { + drpc.Stream +} + +func (x *drpcAgent_PushResourcesMonitoringUsageStream) SendAndClose(m *PushResourcesMonitoringUsageResponse) error { + if err := x.MsgSend(m, drpcEncoding_File_agent_proto_agent_proto{}); err != nil { + return err + } + return x.CloseSend() +} diff --git a/agent/proto/agent_drpc_old.go b/agent/proto/agent_drpc_old.go index f46afaba42..f1db351428 100644 --- a/agent/proto/agent_drpc_old.go +++ b/agent/proto/agent_drpc_old.go @@ -40,3 +40,11 @@ type DRPCAgentClient23 interface { DRPCAgentClient22 ScriptCompleted(ctx context.Context, in *WorkspaceAgentScriptCompletedRequest) (*WorkspaceAgentScriptCompletedResponse, error) } + +// DRPCAgentClient24 is the Agent API at v2.4. It adds the GetResourcesMonitoringConfiguration and +// PushResourcesMonitoringUsage RPCs. Compatible with Coder v2.19+ +type DRPCAgentClient24 interface { + DRPCAgentClient23 + GetResourcesMonitoringConfiguration(ctx context.Context, in *GetResourcesMonitoringConfigurationRequest) (*GetResourcesMonitoringConfigurationResponse, error) + PushResourcesMonitoringUsage(ctx context.Context, in *PushResourcesMonitoringUsageRequest) (*PushResourcesMonitoringUsageResponse, error) +} diff --git a/agent/proto/resourcesmonitor/fetcher.go b/agent/proto/resourcesmonitor/fetcher.go new file mode 100644 index 0000000000..495a249fe9 --- /dev/null +++ b/agent/proto/resourcesmonitor/fetcher.go @@ -0,0 +1,49 @@ +package resourcesmonitor + +import ( + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/cli/clistat" +) + +type Fetcher interface { + FetchMemory() (total int64, used int64, err error) + FetchVolume(volume string) (total int64, used int64, err error) +} + +type fetcher struct { + *clistat.Statter +} + +//nolint:revive +func NewFetcher(f *clistat.Statter) *fetcher { + return &fetcher{ + f, + } +} + +func (f *fetcher) FetchMemory() (total int64, used int64, err error) { + mem, err := f.HostMemory(clistat.PrefixDefault) + if err != nil { + return 0, 0, xerrors.Errorf("failed to fetch memory: %w", err) + } + + if mem.Total == nil { + return 0, 0, xerrors.New("memory total is nil - can not fetch memory") + } + + return int64(*mem.Total), int64(mem.Used), nil +} + +func (f *fetcher) FetchVolume(volume string) (total int64, used int64, err error) { + vol, err := f.Disk(clistat.PrefixDefault, volume) + if err != nil { + return 0, 0, err + } + + if vol.Total == nil { + return 0, 0, xerrors.New("volume total is nil - can not fetch volume") + } + + return int64(*vol.Total), int64(vol.Used), nil +} diff --git a/agent/proto/resourcesmonitor/queue.go b/agent/proto/resourcesmonitor/queue.go new file mode 100644 index 0000000000..9f463509f2 --- /dev/null +++ b/agent/proto/resourcesmonitor/queue.go @@ -0,0 +1,85 @@ +package resourcesmonitor + +import ( + "time" + + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/coder/coder/v2/agent/proto" +) + +type Datapoint struct { + CollectedAt time.Time + Memory *MemoryDatapoint + Volumes []*VolumeDatapoint +} + +type MemoryDatapoint struct { + Total int64 + Used int64 +} + +type VolumeDatapoint struct { + Path string + Total int64 + Used int64 +} + +// Queue represents a FIFO queue with a fixed size +type Queue struct { + items []Datapoint + size int +} + +// newQueue creates a new Queue with the given size +func NewQueue(size int) *Queue { + return &Queue{ + items: make([]Datapoint, 0, size), + size: size, + } +} + +// Push adds a new item to the queue +func (q *Queue) Push(item Datapoint) { + if len(q.items) >= q.size { + // Remove the first item (FIFO) + q.items = q.items[1:] + } + q.items = append(q.items, item) +} + +func (q *Queue) IsFull() bool { + return len(q.items) == q.size +} + +func (q *Queue) Items() []Datapoint { + return q.items +} + +func (q *Queue) ItemsAsProto() []*proto.PushResourcesMonitoringUsageRequest_Datapoint { + items := make([]*proto.PushResourcesMonitoringUsageRequest_Datapoint, 0, len(q.items)) + + for _, item := range q.items { + protoItem := &proto.PushResourcesMonitoringUsageRequest_Datapoint{ + CollectedAt: timestamppb.New(item.CollectedAt), + } + if item.Memory != nil { + protoItem.Memory = &proto.PushResourcesMonitoringUsageRequest_Datapoint_MemoryUsage{ + Total: item.Memory.Total, + Used: item.Memory.Used, + } + } + + for _, volume := range item.Volumes { + protoItem.Volumes = append(protoItem.Volumes, &proto.PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage{ + Volume: volume.Path, + Total: volume.Total, + Used: volume.Used, + }) + } + + items = append(items, protoItem) + } + + return items +} diff --git a/agent/proto/resourcesmonitor/queue_test.go b/agent/proto/resourcesmonitor/queue_test.go new file mode 100644 index 0000000000..a3a8fbc0d0 --- /dev/null +++ b/agent/proto/resourcesmonitor/queue_test.go @@ -0,0 +1,92 @@ +package resourcesmonitor_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/agent/proto/resourcesmonitor" +) + +func TestResourceMonitorQueue(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pushCount int + expected []resourcesmonitor.Datapoint + }{ + { + name: "Push zero", + pushCount: 0, + expected: []resourcesmonitor.Datapoint{}, + }, + { + name: "Push less than capacity", + pushCount: 3, + expected: []resourcesmonitor.Datapoint{ + {Memory: &resourcesmonitor.MemoryDatapoint{Total: 1, Used: 1}}, + {Memory: &resourcesmonitor.MemoryDatapoint{Total: 2, Used: 2}}, + {Memory: &resourcesmonitor.MemoryDatapoint{Total: 3, Used: 3}}, + }, + }, + { + name: "Push exactly capacity", + pushCount: 20, + expected: func() []resourcesmonitor.Datapoint { + var result []resourcesmonitor.Datapoint + for i := 1; i <= 20; i++ { + result = append(result, resourcesmonitor.Datapoint{ + Memory: &resourcesmonitor.MemoryDatapoint{ + Total: int64(i), + Used: int64(i), + }, + }) + } + return result + }(), + }, + { + name: "Push more than capacity", + pushCount: 25, + expected: func() []resourcesmonitor.Datapoint { + var result []resourcesmonitor.Datapoint + for i := 6; i <= 25; i++ { + result = append(result, resourcesmonitor.Datapoint{ + Memory: &resourcesmonitor.MemoryDatapoint{ + Total: int64(i), + Used: int64(i), + }, + }) + } + return result + }(), + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + queue := resourcesmonitor.NewQueue(20) + for i := 1; i <= tt.pushCount; i++ { + queue.Push(resourcesmonitor.Datapoint{ + Memory: &resourcesmonitor.MemoryDatapoint{ + Total: int64(i), + Used: int64(i), + }, + }) + } + + if tt.pushCount < 20 { + require.False(t, queue.IsFull()) + } else { + require.True(t, queue.IsFull()) + require.Equal(t, 20, len(queue.Items())) + } + + require.EqualValues(t, tt.expected, queue.Items()) + }) + } +} diff --git a/agent/proto/resourcesmonitor/resources_monitor.go b/agent/proto/resourcesmonitor/resources_monitor.go new file mode 100644 index 0000000000..7dea49614c --- /dev/null +++ b/agent/proto/resourcesmonitor/resources_monitor.go @@ -0,0 +1,93 @@ +package resourcesmonitor + +import ( + "context" + "time" + + "cdr.dev/slog" + "github.com/coder/coder/v2/agent/proto" + "github.com/coder/quartz" +) + +type monitor struct { + logger slog.Logger + clock quartz.Clock + config *proto.GetResourcesMonitoringConfigurationResponse + resourcesFetcher Fetcher + datapointsPusher datapointsPusher + queue *Queue +} + +//nolint:revive +func NewResourcesMonitor(logger slog.Logger, clock quartz.Clock, config *proto.GetResourcesMonitoringConfigurationResponse, resourcesFetcher Fetcher, datapointsPusher datapointsPusher) *monitor { + return &monitor{ + logger: logger, + clock: clock, + config: config, + resourcesFetcher: resourcesFetcher, + datapointsPusher: datapointsPusher, + queue: NewQueue(int(config.Config.NumDatapoints)), + } +} + +type datapointsPusher interface { + PushResourcesMonitoringUsage(ctx context.Context, req *proto.PushResourcesMonitoringUsageRequest) (*proto.PushResourcesMonitoringUsageResponse, error) +} + +func (m *monitor) Start(ctx context.Context) error { + m.clock.TickerFunc(ctx, time.Duration(m.config.Config.CollectionIntervalSeconds)*time.Second, func() error { + datapoint := Datapoint{ + CollectedAt: m.clock.Now(), + Volumes: make([]*VolumeDatapoint, 0, len(m.config.Volumes)), + } + + if m.config.Memory != nil && m.config.Memory.Enabled { + memTotal, memUsed, err := m.resourcesFetcher.FetchMemory() + if err != nil { + m.logger.Error(ctx, "failed to fetch memory", slog.Error(err)) + } else { + datapoint.Memory = &MemoryDatapoint{ + Total: memTotal, + Used: memUsed, + } + } + } + + for _, volume := range m.config.Volumes { + if !volume.Enabled { + continue + } + + volTotal, volUsed, err := m.resourcesFetcher.FetchVolume(volume.Path) + if err != nil { + m.logger.Error(ctx, "failed to fetch volume", slog.Error(err)) + continue + } + + datapoint.Volumes = append(datapoint.Volumes, &VolumeDatapoint{ + Path: volume.Path, + Total: volTotal, + Used: volUsed, + }) + } + + m.queue.Push(datapoint) + + if m.queue.IsFull() { + _, err := m.datapointsPusher.PushResourcesMonitoringUsage(ctx, &proto.PushResourcesMonitoringUsageRequest{ + Datapoints: m.queue.ItemsAsProto(), + }) + if err != nil { + // We don't want to stop the monitoring if we fail to push the datapoints + // to the server. We just log the error and continue. + // The queue will anyway remove the oldest datapoint and add the new one. + m.logger.Error(ctx, "failed to push resources monitoring usage", slog.Error(err)) + return nil + } + } + + return nil + }, "resources_monitor") + + return nil +} diff --git a/agent/proto/resourcesmonitor/resources_monitor_test.go b/agent/proto/resourcesmonitor/resources_monitor_test.go new file mode 100644 index 0000000000..ddf3522ece --- /dev/null +++ b/agent/proto/resourcesmonitor/resources_monitor_test.go @@ -0,0 +1,235 @@ +package resourcesmonitor_test + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" + "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/agent/proto/resourcesmonitor" + "github.com/coder/quartz" +) + +type datapointsPusherMock struct { + PushResourcesMonitoringUsageFunc func(ctx context.Context, req *proto.PushResourcesMonitoringUsageRequest) (*proto.PushResourcesMonitoringUsageResponse, error) +} + +func (d *datapointsPusherMock) PushResourcesMonitoringUsage(ctx context.Context, req *proto.PushResourcesMonitoringUsageRequest) (*proto.PushResourcesMonitoringUsageResponse, error) { + return d.PushResourcesMonitoringUsageFunc(ctx, req) +} + +type fetcher struct { + totalMemory int64 + usedMemory int64 + totalVolume int64 + usedVolume int64 + + errMemory error + errVolume error +} + +func (r *fetcher) FetchMemory() (total int64, used int64, err error) { + return r.totalMemory, r.usedMemory, r.errMemory +} + +func (r *fetcher) FetchVolume(_ string) (total int64, used int64, err error) { + return r.totalVolume, r.usedVolume, r.errVolume +} + +func TestPushResourcesMonitoringWithConfig(t *testing.T) { + t.Parallel() + tests := []struct { + name string + config *proto.GetResourcesMonitoringConfigurationResponse + datapointsPusher func(ctx context.Context, req *proto.PushResourcesMonitoringUsageRequest) (*proto.PushResourcesMonitoringUsageResponse, error) + fetcher resourcesmonitor.Fetcher + numTicks int + }{ + { + name: "SuccessfulMonitoring", + config: &proto.GetResourcesMonitoringConfigurationResponse{ + Config: &proto.GetResourcesMonitoringConfigurationResponse_Config{ + NumDatapoints: 20, + CollectionIntervalSeconds: 1, + }, + Volumes: []*proto.GetResourcesMonitoringConfigurationResponse_Volume{ + { + Enabled: true, + Path: "/", + }, + }, + }, + datapointsPusher: func(_ context.Context, _ *proto.PushResourcesMonitoringUsageRequest) (*proto.PushResourcesMonitoringUsageResponse, error) { + return &proto.PushResourcesMonitoringUsageResponse{}, nil + }, + fetcher: &fetcher{ + totalMemory: 16000, + usedMemory: 8000, + totalVolume: 100000, + usedVolume: 50000, + }, + numTicks: 20, + }, + { + name: "SuccessfulMonitoringLongRun", + config: &proto.GetResourcesMonitoringConfigurationResponse{ + Config: &proto.GetResourcesMonitoringConfigurationResponse_Config{ + NumDatapoints: 20, + CollectionIntervalSeconds: 1, + }, + Volumes: []*proto.GetResourcesMonitoringConfigurationResponse_Volume{ + { + Enabled: true, + Path: "/", + }, + }, + }, + datapointsPusher: func(_ context.Context, _ *proto.PushResourcesMonitoringUsageRequest) (*proto.PushResourcesMonitoringUsageResponse, error) { + return &proto.PushResourcesMonitoringUsageResponse{}, nil + }, + fetcher: &fetcher{ + totalMemory: 16000, + usedMemory: 8000, + totalVolume: 100000, + usedVolume: 50000, + }, + numTicks: 60, + }, + { + // We want to make sure that even if the datapointsPusher fails, the monitoring continues. + name: "ErrorPushingDatapoints", + config: &proto.GetResourcesMonitoringConfigurationResponse{ + Config: &proto.GetResourcesMonitoringConfigurationResponse_Config{ + NumDatapoints: 20, + CollectionIntervalSeconds: 1, + }, + Volumes: []*proto.GetResourcesMonitoringConfigurationResponse_Volume{ + { + Enabled: true, + Path: "/", + }, + }, + }, + datapointsPusher: func(_ context.Context, _ *proto.PushResourcesMonitoringUsageRequest) (*proto.PushResourcesMonitoringUsageResponse, error) { + return nil, assert.AnError + }, + fetcher: &fetcher{ + totalMemory: 16000, + usedMemory: 8000, + totalVolume: 100000, + usedVolume: 50000, + }, + numTicks: 60, + }, + { + // If one of the resources fails to be fetched, the datapoints still should be pushed with the other resources. + name: "ErrorFetchingMemory", + config: &proto.GetResourcesMonitoringConfigurationResponse{ + Config: &proto.GetResourcesMonitoringConfigurationResponse_Config{ + NumDatapoints: 20, + CollectionIntervalSeconds: 1, + }, + Volumes: []*proto.GetResourcesMonitoringConfigurationResponse_Volume{ + { + Enabled: true, + Path: "/", + }, + }, + }, + datapointsPusher: func(_ context.Context, req *proto.PushResourcesMonitoringUsageRequest) (*proto.PushResourcesMonitoringUsageResponse, error) { + require.Len(t, req.Datapoints, 20) + require.Nil(t, req.Datapoints[0].Memory) + require.NotNil(t, req.Datapoints[0].Volumes) + require.Equal(t, &proto.PushResourcesMonitoringUsageRequest_Datapoint_VolumeUsage{ + Volume: "/", + Total: 100000, + Used: 50000, + }, req.Datapoints[0].Volumes[0]) + + return &proto.PushResourcesMonitoringUsageResponse{}, nil + }, + fetcher: &fetcher{ + totalMemory: 0, + usedMemory: 0, + errMemory: assert.AnError, + totalVolume: 100000, + usedVolume: 50000, + }, + numTicks: 20, + }, + { + // If one of the resources fails to be fetched, the datapoints still should be pushed with the other resources. + name: "ErrorFetchingVolume", + config: &proto.GetResourcesMonitoringConfigurationResponse{ + Config: &proto.GetResourcesMonitoringConfigurationResponse_Config{ + NumDatapoints: 20, + CollectionIntervalSeconds: 1, + }, + Volumes: []*proto.GetResourcesMonitoringConfigurationResponse_Volume{ + { + Enabled: true, + Path: "/", + }, + }, + }, + datapointsPusher: func(_ context.Context, req *proto.PushResourcesMonitoringUsageRequest) (*proto.PushResourcesMonitoringUsageResponse, error) { + require.Len(t, req.Datapoints, 20) + require.Len(t, req.Datapoints[0].Volumes, 0) + + return &proto.PushResourcesMonitoringUsageResponse{}, nil + }, + fetcher: &fetcher{ + totalMemory: 16000, + usedMemory: 8000, + totalVolume: 0, + usedVolume: 0, + errVolume: assert.AnError, + }, + numTicks: 20, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var ( + logger = slog.Make(sloghuman.Sink(os.Stdout)) + clk = quartz.NewMock(t) + counterCalls = 0 + ) + + datapointsPusher := func(ctx context.Context, req *proto.PushResourcesMonitoringUsageRequest) (*proto.PushResourcesMonitoringUsageResponse, error) { + counterCalls++ + return tt.datapointsPusher(ctx, req) + } + + pusher := &datapointsPusherMock{ + PushResourcesMonitoringUsageFunc: datapointsPusher, + } + + monitor := resourcesmonitor.NewResourcesMonitor(logger, clk, tt.config, tt.fetcher, pusher) + require.NoError(t, monitor.Start(ctx)) + + for i := 0; i < tt.numTicks; i++ { + _, waiter := clk.AdvanceNext() + require.NoError(t, waiter.Wait(ctx)) + } + + // expectedCalls is computed with the following logic : + // We have one call per tick, once reached the ${config.NumDatapoints}. + expectedCalls := tt.numTicks - int(tt.config.Config.NumDatapoints) + 1 + require.Equal(t, expectedCalls, counterCalls) + cancel() + }) + } +} diff --git a/cli/testdata/coder_provisioner_list_--output_json.golden b/cli/testdata/coder_provisioner_list_--output_json.golden index d6983d11e5..168e690f0b 100644 --- a/cli/testdata/coder_provisioner_list_--output_json.golden +++ b/cli/testdata/coder_provisioner_list_--output_json.golden @@ -21,7 +21,7 @@ "previous_job": { "id": "======[workspace build job ID]======", "status": "succeeded", - "template_name": "", + "template_name": "test-template", "template_icon": "", "template_display_name": "" }, diff --git a/coderd/agentapi/api.go b/coderd/agentapi/api.go index c48fd17f8a..a4f02de82b 100644 --- a/coderd/agentapi/api.go +++ b/coderd/agentapi/api.go @@ -42,6 +42,7 @@ type API struct { *LifecycleAPI *AppsAPI *MetadataAPI + *ResourcesMonitoringAPI *LogsAPI *ScriptsAPI *tailnet.DRPCService @@ -104,6 +105,12 @@ func New(opts Options) *API { appearanceFetcher: opts.AppearanceFetcher, } + api.ResourcesMonitoringAPI = &ResourcesMonitoringAPI{ + Log: opts.Log, + AgentID: opts.AgentID, + Database: opts.Database, + } + api.StatsAPI = &StatsAPI{ AgentFn: api.agent, Database: opts.Database, diff --git a/coderd/agentapi/resources_monitoring.go b/coderd/agentapi/resources_monitoring.go new file mode 100644 index 0000000000..0bce9b5104 --- /dev/null +++ b/coderd/agentapi/resources_monitoring.go @@ -0,0 +1,67 @@ +package agentapi + +import ( + "context" + "database/sql" + "errors" + + "golang.org/x/xerrors" + + "github.com/google/uuid" + + "cdr.dev/slog" + "github.com/coder/coder/v2/agent/proto" + "github.com/coder/coder/v2/coderd/database" +) + +type ResourcesMonitoringAPI struct { + AgentID uuid.UUID + Database database.Store + Log slog.Logger +} + +func (a *ResourcesMonitoringAPI) GetResourcesMonitoringConfiguration(ctx context.Context, _ *proto.GetResourcesMonitoringConfigurationRequest) (*proto.GetResourcesMonitoringConfigurationResponse, error) { + memoryMonitor, memoryErr := a.Database.FetchMemoryResourceMonitorsByAgentID(ctx, a.AgentID) + if memoryErr != nil && !errors.Is(memoryErr, sql.ErrNoRows) { + return nil, xerrors.Errorf("failed to fetch memory resource monitor: %w", memoryErr) + } + + volumeMonitors, err := a.Database.FetchVolumesResourceMonitorsByAgentID(ctx, a.AgentID) + if err != nil { + return nil, xerrors.Errorf("failed to fetch volume resource monitors: %w", err) + } + + return &proto.GetResourcesMonitoringConfigurationResponse{ + Config: &proto.GetResourcesMonitoringConfigurationResponse_Config{ + CollectionIntervalSeconds: 10, + NumDatapoints: 20, + }, + Memory: func() *proto.GetResourcesMonitoringConfigurationResponse_Memory { + if memoryErr != nil { + return nil + } + + return &proto.GetResourcesMonitoringConfigurationResponse_Memory{ + Enabled: memoryMonitor.Enabled, + } + }(), + Volumes: func() []*proto.GetResourcesMonitoringConfigurationResponse_Volume { + volumes := make([]*proto.GetResourcesMonitoringConfigurationResponse_Volume, 0, len(volumeMonitors)) + for _, monitor := range volumeMonitors { + volumes = append(volumes, &proto.GetResourcesMonitoringConfigurationResponse_Volume{ + Enabled: monitor.Enabled, + Path: monitor.Path, + }) + } + + return volumes + }(), + }, nil +} + +func (a *ResourcesMonitoringAPI) PushResourcesMonitoringUsage(ctx context.Context, req *proto.PushResourcesMonitoringUsageRequest) (*proto.PushResourcesMonitoringUsageResponse, error) { + a.Log.Info(ctx, "resources monitoring usage received", + slog.F("request", req)) + + return &proto.PushResourcesMonitoringUsageResponse{}, nil +} diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 828904416b..7a54ee80dc 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -2976,6 +2976,43 @@ const docTemplate = `{ "in": "path", "required": true }, + { + "type": "integer", + "description": "Page limit", + "name": "limit", + "in": "query" + }, + { + "type": "array", + "format": "uuid", + "items": { + "type": "string" + }, + "description": "Filter results by job IDs", + "name": "ids", + "in": "query" + }, + { + "enum": [ + "pending", + "running", + "succeeded", + "canceling", + "canceled", + "failed", + "unknown", + "pending", + "running", + "succeeded", + "canceling", + "canceled", + "failed" + ], + "type": "string", + "description": "Filter results by status", + "name": "status", + "in": "query" + }, { "type": "object", "description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})", diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 64d8617736..aa24368d37 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -2610,6 +2610,43 @@ "in": "path", "required": true }, + { + "type": "integer", + "description": "Page limit", + "name": "limit", + "in": "query" + }, + { + "type": "array", + "format": "uuid", + "items": { + "type": "string" + }, + "description": "Filter results by job IDs", + "name": "ids", + "in": "query" + }, + { + "enum": [ + "pending", + "running", + "succeeded", + "canceling", + "canceled", + "failed", + "unknown", + "pending", + "running", + "succeeded", + "canceling", + "canceled", + "failed" + ], + "type": "string", + "description": "Filter results by status", + "name": "status", + "in": "query" + }, { "type": "object", "description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})", diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 23ff52f014..9a985a779b 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -184,6 +184,8 @@ var ( rbac.ResourceGroup.Type: {policy.ActionRead}, // Provisionerd creates notification messages rbac.ResourceNotificationMessage.Type: {policy.ActionCreate, policy.ActionRead}, + // Provisionerd creates workspaces resources monitor + rbac.ResourceWorkspaceAgentResourceMonitor.Type: {policy.ActionCreate}, }), Org: map[string][]rbac.Permission{}, User: []rbac.Permission{}, @@ -1401,7 +1403,13 @@ func (q *querier) FavoriteWorkspace(ctx context.Context, id uuid.UUID) error { } func (q *querier) FetchMemoryResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) (database.WorkspaceAgentMemoryResourceMonitor, error) { - if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceWorkspaceAgentResourceMonitor); err != nil { + workspace, err := q.db.GetWorkspaceByAgentID(ctx, agentID) + if err != nil { + return database.WorkspaceAgentMemoryResourceMonitor{}, err + } + + err = q.authorizeContext(ctx, policy.ActionRead, workspace) + if err != nil { return database.WorkspaceAgentMemoryResourceMonitor{}, err } @@ -1416,7 +1424,13 @@ func (q *querier) FetchNewMessageMetadata(ctx context.Context, arg database.Fetc } func (q *querier) FetchVolumesResourceMonitorsByAgentID(ctx context.Context, agentID uuid.UUID) ([]database.WorkspaceAgentVolumeResourceMonitor, error) { - if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceWorkspaceAgentResourceMonitor); err != nil { + workspace, err := q.db.GetWorkspaceByAgentID(ctx, agentID) + if err != nil { + return nil, err + } + + err = q.authorizeContext(ctx, policy.ActionRead, workspace) + if err != nil { return nil, err } diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 46aa96bf1f..1291c27236 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -4772,7 +4772,7 @@ func (s *MethodTestSuite) TestResourcesMonitor() { monitor, err := db.FetchMemoryResourceMonitorsByAgentID(context.Background(), agt.ID) require.NoError(s.T(), err) - check.Args(agt.ID).Asserts(rbac.ResourceWorkspaceAgentResourceMonitor, policy.ActionRead).Returns(monitor) + check.Args(agt.ID).Asserts(w, policy.ActionRead).Returns(monitor) })) s.Run("FetchVolumesResourceMonitorsByAgentID", s.Subtest(func(db database.Store, check *expects) { @@ -4813,6 +4813,6 @@ func (s *MethodTestSuite) TestResourcesMonitor() { monitors, err := db.FetchVolumesResourceMonitorsByAgentID(context.Background(), agt.ID) require.NoError(s.T(), err) - check.Args(agt.ID).Asserts(rbac.ResourceWorkspaceAgentResourceMonitor, policy.ActionRead).Returns(monitors) + check.Args(agt.ID).Asserts(w, policy.ActionRead).Returns(monitors) })) } diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index fc8708ce32..83ca261cd3 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -3935,7 +3935,7 @@ func (q *FakeQuerier) GetProvisionerDaemonsByOrganization(_ context.Context, arg return daemons, nil } -func (q *FakeQuerier) GetProvisionerDaemonsWithStatusByOrganization(_ context.Context, arg database.GetProvisionerDaemonsWithStatusByOrganizationParams) ([]database.GetProvisionerDaemonsWithStatusByOrganizationRow, error) { +func (q *FakeQuerier) GetProvisionerDaemonsWithStatusByOrganization(ctx context.Context, arg database.GetProvisionerDaemonsWithStatusByOrganizationParams) ([]database.GetProvisionerDaemonsWithStatusByOrganizationRow, error) { err := validateDatabaseType(arg) if err != nil { return nil, err @@ -3985,6 +3985,31 @@ func (q *FakeQuerier) GetProvisionerDaemonsWithStatusByOrganization(_ context.Co status = database.ProvisionerDaemonStatusIdle } } + var currentTemplate database.Template + if currentJob.ID != uuid.Nil { + var input codersdk.ProvisionerJobInput + err := json.Unmarshal(currentJob.Input, &input) + if err != nil { + return nil, err + } + if input.WorkspaceBuildID != nil { + b, err := q.getWorkspaceBuildByIDNoLock(ctx, *input.WorkspaceBuildID) + if err != nil { + return nil, err + } + input.TemplateVersionID = &b.TemplateVersionID + } + if input.TemplateVersionID != nil { + v, err := q.getTemplateVersionByIDNoLock(ctx, *input.TemplateVersionID) + if err != nil { + return nil, err + } + currentTemplate, err = q.getTemplateByIDNoLock(ctx, v.TemplateID.UUID) + if err != nil { + return nil, err + } + } + } var previousJob database.ProvisionerJob for _, job := range q.provisionerJobs { @@ -4001,6 +4026,31 @@ func (q *FakeQuerier) GetProvisionerDaemonsWithStatusByOrganization(_ context.Co } } } + var previousTemplate database.Template + if previousJob.ID != uuid.Nil { + var input codersdk.ProvisionerJobInput + err := json.Unmarshal(previousJob.Input, &input) + if err != nil { + return nil, err + } + if input.WorkspaceBuildID != nil { + b, err := q.getWorkspaceBuildByIDNoLock(ctx, *input.WorkspaceBuildID) + if err != nil { + return nil, err + } + input.TemplateVersionID = &b.TemplateVersionID + } + if input.TemplateVersionID != nil { + v, err := q.getTemplateVersionByIDNoLock(ctx, *input.TemplateVersionID) + if err != nil { + return nil, err + } + previousTemplate, err = q.getTemplateByIDNoLock(ctx, v.TemplateID.UUID) + if err != nil { + return nil, err + } + } + } // Get the provisioner key name var keyName string @@ -4012,13 +4062,19 @@ func (q *FakeQuerier) GetProvisionerDaemonsWithStatusByOrganization(_ context.Co } rows = append(rows, database.GetProvisionerDaemonsWithStatusByOrganizationRow{ - ProvisionerDaemon: daemon, - Status: status, - KeyName: keyName, - CurrentJobID: uuid.NullUUID{UUID: currentJob.ID, Valid: currentJob.ID != uuid.Nil}, - CurrentJobStatus: database.NullProvisionerJobStatus{ProvisionerJobStatus: currentJob.JobStatus, Valid: currentJob.ID != uuid.Nil}, - PreviousJobID: uuid.NullUUID{UUID: previousJob.ID, Valid: previousJob.ID != uuid.Nil}, - PreviousJobStatus: database.NullProvisionerJobStatus{ProvisionerJobStatus: previousJob.JobStatus, Valid: previousJob.ID != uuid.Nil}, + ProvisionerDaemon: daemon, + Status: status, + KeyName: keyName, + CurrentJobID: uuid.NullUUID{UUID: currentJob.ID, Valid: currentJob.ID != uuid.Nil}, + CurrentJobStatus: database.NullProvisionerJobStatus{ProvisionerJobStatus: currentJob.JobStatus, Valid: currentJob.ID != uuid.Nil}, + CurrentJobTemplateName: currentTemplate.Name, + CurrentJobTemplateDisplayName: currentTemplate.DisplayName, + CurrentJobTemplateIcon: currentTemplate.Icon, + PreviousJobID: uuid.NullUUID{UUID: previousJob.ID, Valid: previousJob.ID != uuid.Nil}, + PreviousJobStatus: database.NullProvisionerJobStatus{ProvisionerJobStatus: previousJob.JobStatus, Valid: previousJob.ID != uuid.Nil}, + PreviousJobTemplateName: previousTemplate.Name, + PreviousJobTemplateDisplayName: previousTemplate.DisplayName, + PreviousJobTemplateIcon: previousTemplate.Icon, }) } @@ -4026,6 +4082,10 @@ func (q *FakeQuerier) GetProvisionerDaemonsWithStatusByOrganization(_ context.Co return a.ProvisionerDaemon.CreatedAt.Compare(b.ProvisionerDaemon.CreatedAt) }) + if arg.Limit.Valid && arg.Limit.Int32 > 0 && len(rows) > int(arg.Limit.Int32) { + rows = rows[:arg.Limit.Int32] + } + return rows, nil } diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 2b6679a869..56e110f0f7 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -210,6 +210,8 @@ type sqlcQuerier interface { GetPreviousTemplateVersion(ctx context.Context, arg GetPreviousTemplateVersionParams) (TemplateVersion, error) GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, error) GetProvisionerDaemonsByOrganization(ctx context.Context, arg GetProvisionerDaemonsByOrganizationParams) ([]ProvisionerDaemon, error) + // Current job information. + // Previous job information. GetProvisionerDaemonsWithStatusByOrganization(ctx context.Context, arg GetProvisionerDaemonsWithStatusByOrganizationParams) ([]GetProvisionerDaemonsWithStatusByOrganizationRow, error) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (ProvisionerJob, error) GetProvisionerJobTimingsByJobID(ctx context.Context, jobID uuid.UUID) ([]ProvisionerJobTiming, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 49ecffbe51..4816e57142 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5960,9 +5960,12 @@ SELECT current_job.job_status AS current_job_status, previous_job.id AS previous_job_id, previous_job.job_status AS previous_job_status, - COALESCE(tmpl.name, ''::text) AS current_job_template_name, - COALESCE(tmpl.display_name, ''::text) AS current_job_template_display_name, - COALESCE(tmpl.icon, ''::text) AS current_job_template_icon + COALESCE(current_template.name, ''::text) AS current_job_template_name, + COALESCE(current_template.display_name, ''::text) AS current_job_template_display_name, + COALESCE(current_template.icon, ''::text) AS current_job_template_icon, + COALESCE(previous_template.name, ''::text) AS previous_job_template_name, + COALESCE(previous_template.display_name, ''::text) AS previous_job_template_display_name, + COALESCE(previous_template.icon, ''::text) AS previous_job_template_icon FROM provisioner_daemons pd JOIN @@ -5988,43 +5991,62 @@ LEFT JOIN ) ) LEFT JOIN - template_versions version ON version.id = (current_job.input->>'template_version_id')::uuid + workspace_builds current_build ON current_build.id = CASE WHEN current_job.input ? 'workspace_build_id' THEN (current_job.input->>'workspace_build_id')::uuid END LEFT JOIN - templates tmpl ON tmpl.id = version.template_id + -- We should always have a template version, either explicitly or implicitly via workspace build. + template_versions current_version ON current_version.id = CASE WHEN current_job.input ? 'template_version_id' THEN (current_job.input->>'template_version_id')::uuid ELSE current_build.template_version_id END +LEFT JOIN + templates current_template ON current_template.id = current_version.template_id +LEFT JOIN + workspace_builds previous_build ON previous_build.id = CASE WHEN previous_job.input ? 'workspace_build_id' THEN (previous_job.input->>'workspace_build_id')::uuid END +LEFT JOIN + -- We should always have a template version, either explicitly or implicitly via workspace build. + template_versions previous_version ON previous_version.id = CASE WHEN previous_job.input ? 'template_version_id' THEN (previous_job.input->>'template_version_id')::uuid ELSE previous_build.template_version_id END +LEFT JOIN + templates previous_template ON previous_template.id = previous_version.template_id WHERE pd.organization_id = $2::uuid AND (COALESCE(array_length($3::uuid[], 1), 0) = 0 OR pd.id = ANY($3::uuid[])) AND ($4::tagset = 'null'::tagset OR provisioner_tagset_contains(pd.tags::tagset, $4::tagset)) ORDER BY pd.created_at ASC +LIMIT + $5::int ` type GetProvisionerDaemonsWithStatusByOrganizationParams struct { - StaleIntervalMS int64 `db:"stale_interval_ms" json:"stale_interval_ms"` - OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` - IDs []uuid.UUID `db:"ids" json:"ids"` - Tags StringMap `db:"tags" json:"tags"` + StaleIntervalMS int64 `db:"stale_interval_ms" json:"stale_interval_ms"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + IDs []uuid.UUID `db:"ids" json:"ids"` + Tags StringMap `db:"tags" json:"tags"` + Limit sql.NullInt32 `db:"limit" json:"limit"` } type GetProvisionerDaemonsWithStatusByOrganizationRow struct { - ProvisionerDaemon ProvisionerDaemon `db:"provisioner_daemon" json:"provisioner_daemon"` - Status ProvisionerDaemonStatus `db:"status" json:"status"` - KeyName string `db:"key_name" json:"key_name"` - CurrentJobID uuid.NullUUID `db:"current_job_id" json:"current_job_id"` - CurrentJobStatus NullProvisionerJobStatus `db:"current_job_status" json:"current_job_status"` - PreviousJobID uuid.NullUUID `db:"previous_job_id" json:"previous_job_id"` - PreviousJobStatus NullProvisionerJobStatus `db:"previous_job_status" json:"previous_job_status"` - CurrentJobTemplateName string `db:"current_job_template_name" json:"current_job_template_name"` - CurrentJobTemplateDisplayName string `db:"current_job_template_display_name" json:"current_job_template_display_name"` - CurrentJobTemplateIcon string `db:"current_job_template_icon" json:"current_job_template_icon"` + ProvisionerDaemon ProvisionerDaemon `db:"provisioner_daemon" json:"provisioner_daemon"` + Status ProvisionerDaemonStatus `db:"status" json:"status"` + KeyName string `db:"key_name" json:"key_name"` + CurrentJobID uuid.NullUUID `db:"current_job_id" json:"current_job_id"` + CurrentJobStatus NullProvisionerJobStatus `db:"current_job_status" json:"current_job_status"` + PreviousJobID uuid.NullUUID `db:"previous_job_id" json:"previous_job_id"` + PreviousJobStatus NullProvisionerJobStatus `db:"previous_job_status" json:"previous_job_status"` + CurrentJobTemplateName string `db:"current_job_template_name" json:"current_job_template_name"` + CurrentJobTemplateDisplayName string `db:"current_job_template_display_name" json:"current_job_template_display_name"` + CurrentJobTemplateIcon string `db:"current_job_template_icon" json:"current_job_template_icon"` + PreviousJobTemplateName string `db:"previous_job_template_name" json:"previous_job_template_name"` + PreviousJobTemplateDisplayName string `db:"previous_job_template_display_name" json:"previous_job_template_display_name"` + PreviousJobTemplateIcon string `db:"previous_job_template_icon" json:"previous_job_template_icon"` } +// Current job information. +// Previous job information. func (q *sqlQuerier) GetProvisionerDaemonsWithStatusByOrganization(ctx context.Context, arg GetProvisionerDaemonsWithStatusByOrganizationParams) ([]GetProvisionerDaemonsWithStatusByOrganizationRow, error) { rows, err := q.db.QueryContext(ctx, getProvisionerDaemonsWithStatusByOrganization, arg.StaleIntervalMS, arg.OrganizationID, pq.Array(arg.IDs), arg.Tags, + arg.Limit, ) if err != nil { return nil, err @@ -6054,6 +6076,9 @@ func (q *sqlQuerier) GetProvisionerDaemonsWithStatusByOrganization(ctx context.C &i.CurrentJobTemplateName, &i.CurrentJobTemplateDisplayName, &i.CurrentJobTemplateIcon, + &i.PreviousJobTemplateName, + &i.PreviousJobTemplateDisplayName, + &i.PreviousJobTemplateIcon, ); err != nil { return nil, err } diff --git a/coderd/database/queries/provisionerdaemons.sql b/coderd/database/queries/provisionerdaemons.sql index b003153ee9..2aaf23ec0d 100644 --- a/coderd/database/queries/provisionerdaemons.sql +++ b/coderd/database/queries/provisionerdaemons.sql @@ -45,9 +45,12 @@ SELECT current_job.job_status AS current_job_status, previous_job.id AS previous_job_id, previous_job.job_status AS previous_job_status, - COALESCE(tmpl.name, ''::text) AS current_job_template_name, - COALESCE(tmpl.display_name, ''::text) AS current_job_template_display_name, - COALESCE(tmpl.icon, ''::text) AS current_job_template_icon + COALESCE(current_template.name, ''::text) AS current_job_template_name, + COALESCE(current_template.display_name, ''::text) AS current_job_template_display_name, + COALESCE(current_template.icon, ''::text) AS current_job_template_icon, + COALESCE(previous_template.name, ''::text) AS previous_job_template_name, + COALESCE(previous_template.display_name, ''::text) AS previous_job_template_display_name, + COALESCE(previous_template.icon, ''::text) AS previous_job_template_icon FROM provisioner_daemons pd JOIN @@ -72,16 +75,30 @@ LEFT JOIN LIMIT 1 ) ) +-- Current job information. LEFT JOIN - template_versions version ON version.id = (current_job.input->>'template_version_id')::uuid + workspace_builds current_build ON current_build.id = CASE WHEN current_job.input ? 'workspace_build_id' THEN (current_job.input->>'workspace_build_id')::uuid END LEFT JOIN - templates tmpl ON tmpl.id = version.template_id + -- We should always have a template version, either explicitly or implicitly via workspace build. + template_versions current_version ON current_version.id = CASE WHEN current_job.input ? 'template_version_id' THEN (current_job.input->>'template_version_id')::uuid ELSE current_build.template_version_id END +LEFT JOIN + templates current_template ON current_template.id = current_version.template_id +-- Previous job information. +LEFT JOIN + workspace_builds previous_build ON previous_build.id = CASE WHEN previous_job.input ? 'workspace_build_id' THEN (previous_job.input->>'workspace_build_id')::uuid END +LEFT JOIN + -- We should always have a template version, either explicitly or implicitly via workspace build. + template_versions previous_version ON previous_version.id = CASE WHEN previous_job.input ? 'template_version_id' THEN (previous_job.input->>'template_version_id')::uuid ELSE previous_build.template_version_id END +LEFT JOIN + templates previous_template ON previous_template.id = previous_version.template_id WHERE pd.organization_id = @organization_id::uuid AND (COALESCE(array_length(@ids::uuid[], 1), 0) = 0 OR pd.id = ANY(@ids::uuid[])) AND (@tags::tagset = 'null'::tagset OR provisioner_tagset_contains(pd.tags::tagset, @tags::tagset)) ORDER BY - pd.created_at ASC; + pd.created_at ASC +LIMIT + sqlc.narg('limit')::int; -- name: DeleteOldProvisionerDaemons :exec -- Delete provisioner daemons that have been created at least a week ago diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index bf4dfb6c4d..e701771770 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -1,6 +1,7 @@ package coderd import ( + "database/sql" "net/http" "github.com/coder/coder/v2/coderd/database" @@ -18,31 +19,50 @@ import ( // @Produce json // @Tags Provisioning // @Param organization path string true "Organization ID" format(uuid) +// @Param limit query int false "Page limit" +// @Param ids query []string false "Filter results by job IDs" format(uuid) +// @Param status query codersdk.ProvisionerJobStatus false "Filter results by status" enums(pending,running,succeeded,canceling,canceled,failed) // @Param tags query object false "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})" // @Success 200 {array} codersdk.ProvisionerDaemon // @Router /organizations/{organization}/provisionerdaemons [get] func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) { var ( - ctx = r.Context() - org = httpmw.OrganizationParam(r) - tagParam = r.URL.Query().Get("tags") - tags = database.StringMap{} - err = tags.Scan([]byte(tagParam)) + ctx = r.Context() + org = httpmw.OrganizationParam(r) ) - if tagParam != "" && err != nil { + qp := r.URL.Query() + p := httpapi.NewQueryParamParser() + limit := p.PositiveInt32(qp, 50, "limit") + ids := p.UUIDs(qp, nil, "ids") + tagsRaw := p.String(qp, "", "tags") + p.ErrorExcessParams(qp) + if len(p.Errors) > 0 { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Invalid tags query parameter", - Detail: err.Error(), + Message: "Invalid query parameters.", + Validations: p.Errors, }) return } + tags := database.StringMap{} + if tagsRaw != "" { + if err := tags.Scan([]byte(tagsRaw)); err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Invalid tags query parameter", + Detail: err.Error(), + }) + return + } + } + daemons, err := api.Database.GetProvisionerDaemonsWithStatusByOrganization( ctx, database.GetProvisionerDaemonsWithStatusByOrganizationParams{ OrganizationID: org.ID, StaleIntervalMS: provisionerdserver.StaleInterval.Milliseconds(), + Limit: sql.NullInt32{Int32: limit, Valid: limit > 0}, + IDs: ids, Tags: tags, }, ) @@ -68,8 +88,11 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) { } if dbDaemon.PreviousJobID.Valid { previousJob = &codersdk.ProvisionerDaemonJob{ - ID: dbDaemon.PreviousJobID.UUID, - Status: codersdk.ProvisionerJobStatus(dbDaemon.PreviousJobStatus.ProvisionerJobStatus), + ID: dbDaemon.PreviousJobID.UUID, + Status: codersdk.ProvisionerJobStatus(dbDaemon.PreviousJobStatus.ProvisionerJobStatus), + TemplateName: dbDaemon.PreviousJobTemplateName, + TemplateIcon: dbDaemon.PreviousJobTemplateIcon, + TemplateDisplayName: dbDaemon.PreviousJobTemplateDisplayName, } } diff --git a/coderd/provisionerdaemons_test.go b/coderd/provisionerdaemons_test.go index 243a24add0..6496b4dd57 100644 --- a/coderd/provisionerdaemons_test.go +++ b/coderd/provisionerdaemons_test.go @@ -1,27 +1,251 @@ package coderd_test import ( + "database/sql" + "encoding/json" + "strconv" "testing" + "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbgen" + "github.com/coder/coder/v2/coderd/database/dbtestutil" + "github.com/coder/coder/v2/coderd/database/dbtime" + "github.com/coder/coder/v2/coderd/rbac" + "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/testutil" ) -func TestGetProvisionerDaemons(t *testing.T) { +func TestProvisionerDaemons(t *testing.T) { t.Parallel() - t.Run("OK", func(t *testing.T) { + db, ps := dbtestutil.NewDB(t, + dbtestutil.WithDumpOnFailure(), + //nolint:gocritic // Use UTC for consistent timestamp length in golden files. + dbtestutil.WithTimezone("UTC"), + ) + client, _, coderdAPI := coderdtest.NewWithAPI(t, &coderdtest.Options{ + IncludeProvisionerDaemon: false, + Database: db, + Pubsub: ps, + }) + owner := coderdtest.CreateFirstUser(t, client) + templateAdminClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgTemplateAdmin(owner.OrganizationID)) + memberClient, member := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) + + // Create initial resources with a running provisioner. + firstProvisioner := coderdtest.NewTaggedProvisionerDaemon(t, coderdAPI, "default-provisioner", map[string]string{"owner": "", "scope": "organization"}) + t.Cleanup(func() { _ = firstProvisioner.Close() }) + version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) + + workspace := coderdtest.CreateWorkspace(t, client, template.ID) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) + + // Stop the provisioner so it doesn't grab any more jobs. + firstProvisioner.Close() + + // Create a provisioner that's working on a job. + pd1 := dbgen.ProvisionerDaemon(t, coderdAPI.Database, database.ProvisionerDaemon{ + Name: "provisioner-1", + CreatedAt: dbtime.Now().Add(1 * time.Second), + LastSeenAt: sql.NullTime{Time: coderdAPI.Clock.Now().Add(time.Hour), Valid: true}, // Stale interval can't be adjusted, keep online. + KeyID: codersdk.ProvisionerKeyUUIDBuiltIn, + Tags: database.StringMap{"owner": "", "scope": "organization", "foo": "bar"}, + }) + w1 := dbgen.Workspace(t, coderdAPI.Database, database.WorkspaceTable{ + OwnerID: member.ID, + TemplateID: template.ID, + }) + wb1ID := uuid.MustParse("00000000-0000-0000-dddd-000000000001") + job1 := dbgen.ProvisionerJob(t, db, coderdAPI.Pubsub, database.ProvisionerJob{ + WorkerID: uuid.NullUUID{UUID: pd1.ID, Valid: true}, + Input: json.RawMessage(`{"workspace_build_id":"` + wb1ID.String() + `"}`), + CreatedAt: dbtime.Now().Add(2 * time.Second), + StartedAt: sql.NullTime{Time: coderdAPI.Clock.Now(), Valid: true}, + Tags: database.StringMap{"owner": "", "scope": "organization", "foo": "bar"}, + }) + dbgen.WorkspaceBuild(t, coderdAPI.Database, database.WorkspaceBuild{ + ID: wb1ID, + JobID: job1.ID, + WorkspaceID: w1.ID, + TemplateVersionID: version.ID, + }) + + // Create a provisioner that completed a job previously and is offline. + pd2 := dbgen.ProvisionerDaemon(t, coderdAPI.Database, database.ProvisionerDaemon{ + Name: "provisioner-2", + CreatedAt: dbtime.Now().Add(2 * time.Second), + LastSeenAt: sql.NullTime{Time: coderdAPI.Clock.Now().Add(-time.Hour), Valid: true}, + KeyID: codersdk.ProvisionerKeyUUIDBuiltIn, + Tags: database.StringMap{"owner": "", "scope": "organization"}, + }) + w2 := dbgen.Workspace(t, coderdAPI.Database, database.WorkspaceTable{ + OwnerID: member.ID, + TemplateID: template.ID, + }) + wb2ID := uuid.MustParse("00000000-0000-0000-dddd-000000000002") + job2 := dbgen.ProvisionerJob(t, db, coderdAPI.Pubsub, database.ProvisionerJob{ + WorkerID: uuid.NullUUID{UUID: pd2.ID, Valid: true}, + Input: json.RawMessage(`{"workspace_build_id":"` + wb2ID.String() + `"}`), + CreatedAt: dbtime.Now().Add(3 * time.Second), + StartedAt: sql.NullTime{Time: coderdAPI.Clock.Now().Add(-2 * time.Hour), Valid: true}, + CompletedAt: sql.NullTime{Time: coderdAPI.Clock.Now().Add(-time.Hour), Valid: true}, + Tags: database.StringMap{"owner": "", "scope": "organization"}, + }) + dbgen.WorkspaceBuild(t, coderdAPI.Database, database.WorkspaceBuild{ + ID: wb2ID, + JobID: job2.ID, + WorkspaceID: w2.ID, + TemplateVersionID: version.ID, + }) + + // Create a pending job. + w3 := dbgen.Workspace(t, coderdAPI.Database, database.WorkspaceTable{ + OwnerID: member.ID, + TemplateID: template.ID, + }) + wb3ID := uuid.MustParse("00000000-0000-0000-dddd-000000000003") + job3 := dbgen.ProvisionerJob(t, db, coderdAPI.Pubsub, database.ProvisionerJob{ + Input: json.RawMessage(`{"workspace_build_id":"` + wb3ID.String() + `"}`), + CreatedAt: dbtime.Now().Add(4 * time.Second), + Tags: database.StringMap{"owner": "", "scope": "organization"}, + }) + dbgen.WorkspaceBuild(t, coderdAPI.Database, database.WorkspaceBuild{ + ID: wb3ID, + JobID: job3.ID, + WorkspaceID: w3.ID, + TemplateVersionID: version.ID, + }) + + // Create a provisioner that is idle. + pd3 := dbgen.ProvisionerDaemon(t, coderdAPI.Database, database.ProvisionerDaemon{ + Name: "provisioner-3", + CreatedAt: dbtime.Now().Add(3 * time.Second), + LastSeenAt: sql.NullTime{Time: coderdAPI.Clock.Now().Add(time.Hour), Valid: true}, + KeyID: codersdk.ProvisionerKeyUUIDBuiltIn, + Tags: database.StringMap{"owner": "", "scope": "organization"}, + }) + + // Add more provisioners than the default limit. + var userDaemons []database.ProvisionerDaemon + for i := range 50 { + userDaemons = append(userDaemons, dbgen.ProvisionerDaemon(t, coderdAPI.Database, database.ProvisionerDaemon{ + Name: "user-provisioner-" + strconv.Itoa(i), + CreatedAt: dbtime.Now().Add(3 * time.Second), + KeyID: codersdk.ProvisionerKeyUUIDUserAuth, + Tags: database.StringMap{"count": strconv.Itoa(i)}, + })) + } + + t.Run("Default limit", func(t *testing.T) { t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) - owner := coderdtest.CreateFirstUser(t, client) - memberClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - ctx := testutil.Context(t, testutil.WaitMedium) + daemons, err := templateAdminClient.OrganizationProvisionerDaemons(ctx, owner.OrganizationID, nil) + require.NoError(t, err) + require.Len(t, daemons, 50) + }) - daemons, err := memberClient.ProvisionerDaemons(ctx) + t.Run("IDs", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + daemons, err := templateAdminClient.OrganizationProvisionerDaemons(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerDaemonsOptions{ + IDs: []uuid.UUID{pd1.ID, pd2.ID}, + }) + require.NoError(t, err) + require.Len(t, daemons, 2) + require.Equal(t, pd1.ID, daemons[0].ID) + require.Equal(t, pd2.ID, daemons[1].ID) + }) + + t.Run("Tags", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + daemons, err := templateAdminClient.OrganizationProvisionerDaemons(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerDaemonsOptions{ + Tags: map[string]string{"count": "1"}, + }) + require.NoError(t, err) + require.Len(t, daemons, 1) + require.Equal(t, userDaemons[1].ID, daemons[0].ID) + }) + + t.Run("Limit", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + daemons, err := templateAdminClient.OrganizationProvisionerDaemons(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerDaemonsOptions{ + Limit: 1, + }) require.NoError(t, err) require.Len(t, daemons, 1) }) + + t.Run("Busy", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + daemons, err := templateAdminClient.OrganizationProvisionerDaemons(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerDaemonsOptions{ + IDs: []uuid.UUID{pd1.ID}, + }) + require.NoError(t, err) + require.Len(t, daemons, 1) + // Verify status. + require.NotNil(t, daemons[0].Status) + require.Equal(t, codersdk.ProvisionerDaemonBusy, *daemons[0].Status) + require.NotNil(t, daemons[0].CurrentJob) + require.Nil(t, daemons[0].PreviousJob) + // Verify job. + require.Equal(t, job1.ID, daemons[0].CurrentJob.ID) + require.Equal(t, codersdk.ProvisionerJobRunning, daemons[0].CurrentJob.Status) + require.Equal(t, template.Name, daemons[0].CurrentJob.TemplateName) + require.Equal(t, template.DisplayName, daemons[0].CurrentJob.TemplateDisplayName) + require.Equal(t, template.Icon, daemons[0].CurrentJob.TemplateIcon) + }) + + t.Run("Offline", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + daemons, err := templateAdminClient.OrganizationProvisionerDaemons(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerDaemonsOptions{ + IDs: []uuid.UUID{pd2.ID}, + }) + require.NoError(t, err) + require.Len(t, daemons, 1) + // Verify status. + require.NotNil(t, daemons[0].Status) + require.Equal(t, codersdk.ProvisionerDaemonOffline, *daemons[0].Status) + require.Nil(t, daemons[0].CurrentJob) + require.NotNil(t, daemons[0].PreviousJob) + // Verify job. + require.Equal(t, job2.ID, daemons[0].PreviousJob.ID) + require.Equal(t, codersdk.ProvisionerJobSucceeded, daemons[0].PreviousJob.Status) + require.Equal(t, template.Name, daemons[0].PreviousJob.TemplateName) + require.Equal(t, template.DisplayName, daemons[0].PreviousJob.TemplateDisplayName) + require.Equal(t, template.Icon, daemons[0].PreviousJob.TemplateIcon) + }) + + t.Run("Idle", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + daemons, err := templateAdminClient.OrganizationProvisionerDaemons(ctx, owner.OrganizationID, &codersdk.OrganizationProvisionerDaemonsOptions{ + IDs: []uuid.UUID{pd3.ID}, + }) + require.NoError(t, err) + require.Len(t, daemons, 1) + // Verify status. + require.NotNil(t, daemons[0].Status) + require.Equal(t, codersdk.ProvisionerDaemonIdle, *daemons[0].Status) + require.Nil(t, daemons[0].CurrentJob) + require.Nil(t, daemons[0].PreviousJob) + }) + + t.Run("MemberAllowed", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + daemons, err := memberClient.OrganizationProvisionerDaemons(ctx, owner.OrganizationID, nil) + require.NoError(t, err) + require.Len(t, daemons, 50) + }) } diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index f10fa8e569..21ba8c6fad 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -1785,7 +1785,7 @@ func TestInsertWorkspacePresetsAndParameters(t *testing.T) { t.Run(c.name, func(t *testing.T) { t.Parallel() - ctx := context.Background() + ctx := testutil.Context(t, testutil.WaitLong) logger := testutil.Logger(t) db, ps := dbtestutil.NewDB(t) org := dbgen.Organization(t, db, database.Organization{}) diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 7a051ef233..cdb33e08a5 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -2234,7 +2234,7 @@ func requireGetManifest(ctx context.Context, t testing.TB, aAPI agentproto.DRPCA } func postStartup(ctx context.Context, t testing.TB, client agent.Client, startup *agentproto.Startup) error { - aAPI, _, err := client.ConnectRPC23(ctx) + aAPI, _, err := client.ConnectRPC24(ctx) require.NoError(t, err) defer func() { cErr := aAPI.DRPCConn().Close() diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index 13f2fff43d..b5f86c2220 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -231,9 +231,9 @@ func (c *Client) ConnectRPC23(ctx context.Context) ( } // ConnectRPC24 returns a dRPC client to the Agent API v2.4. It is useful when you want to be -// maximally compatible with Coderd Release Versions from 2.18+ // TODO update release +// maximally compatible with Coderd Release Versions from 2.xx+ // TODO @vincent: define version func (c *Client) ConnectRPC24(ctx context.Context) ( - proto.DRPCAgentClient23, tailnetproto.DRPCTailnetClient23, error, + proto.DRPCAgentClient24, tailnetproto.DRPCTailnetClient24, error, ) { conn, err := c.connectRPCVersion(ctx, apiversion.New(2, 4)) if err != nil { diff --git a/codersdk/organizations.go b/codersdk/organizations.go index 59d2d6822f..3fafc72b80 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -318,21 +318,34 @@ func (c *Client) ProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, e return daemons, json.NewDecoder(res.Body).Decode(&daemons) } -func (c *Client) OrganizationProvisionerDaemons(ctx context.Context, organizationID uuid.UUID, tags map[string]string) ([]ProvisionerDaemon, error) { - baseURL := fmt.Sprintf("/api/v2/organizations/%s/provisionerdaemons", organizationID.String()) +type OrganizationProvisionerDaemonsOptions struct { + Limit int + IDs []uuid.UUID + Tags map[string]string +} - queryParams := url.Values{} - tagsJSON, err := json.Marshal(tags) - if err != nil { - return nil, xerrors.Errorf("marshal tags: %w", err) +func (c *Client) OrganizationProvisionerDaemons(ctx context.Context, organizationID uuid.UUID, opts *OrganizationProvisionerDaemonsOptions) ([]ProvisionerDaemon, error) { + qp := url.Values{} + if opts != nil { + if opts.Limit > 0 { + qp.Add("limit", strconv.Itoa(opts.Limit)) + } + if len(opts.IDs) > 0 { + qp.Add("ids", joinSliceStringer(opts.IDs)) + } + if len(opts.Tags) > 0 { + tagsRaw, err := json.Marshal(opts.Tags) + if err != nil { + return nil, xerrors.Errorf("marshal tags: %w", err) + } + qp.Add("tags", string(tagsRaw)) + } } - queryParams.Add("tags", string(tagsJSON)) - if len(queryParams) > 0 { - baseURL = fmt.Sprintf("%s?%s", baseURL, queryParams.Encode()) - } - - res, err := c.Request(ctx, http.MethodGet, baseURL, nil) + res, err := c.Request(ctx, http.MethodGet, + fmt.Sprintf("/api/v2/organizations/%s/provisionerdaemons?%s", organizationID.String(), qp.Encode()), + nil, + ) if err != nil { return nil, xerrors.Errorf("execute request: %w", err) } diff --git a/codersdk/richparameters.go b/codersdk/richparameters.go index a0848d3cdf..6fd082d5fa 100644 --- a/codersdk/richparameters.go +++ b/codersdk/richparameters.go @@ -5,7 +5,7 @@ import ( "golang.org/x/xerrors" - "github.com/coder/terraform-provider-coder/provider" + "github.com/coder/terraform-provider-coder/v2/provider" ) func ValidateNewWorkspaceParameters(richParameters []TemplateVersionParameter, buildParameters []WorkspaceBuildParameter) error { diff --git a/docs/admin/users/sessions-tokens.md b/docs/admin/users/sessions-tokens.md index dbbcfb82df..6332b8182f 100644 --- a/docs/admin/users/sessions-tokens.md +++ b/docs/admin/users/sessions-tokens.md @@ -51,10 +51,28 @@ See the help docs for ### Generate a long-lived API token on behalf of another user -Today, you must use the REST API to generate a token on behalf of another user. -You must have the `Owner` role to do this. Use our API reference for more -information: -[Create token API key](https://coder.com/docs/reference/api/users#create-token-api-key) +You must have the `Owner` role to generate a token for another user. + +As of Coder v2.17+, you can use the CLI or API to create long-lived tokens on +behalf of other users. Use the API for earlier versions of Coder. + +