Merge branch 'jjs/presets' of github.com:/coder/coder into dk/prebuilds

This commit is contained in:
Danny Kopping
2025-02-18 08:32:25 +00:00
88 changed files with 2785 additions and 550 deletions

View File

@ -15,6 +15,9 @@ on:
- "**.md" - "**.md"
- ".github/workflows/docs-ci.yaml" - ".github/workflows/docs-ci.yaml"
permissions:
contents: read
jobs: jobs:
docs: docs:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -41,8 +41,10 @@ import (
"github.com/coder/coder/v2/agent/agentscripts" "github.com/coder/coder/v2/agent/agentscripts"
"github.com/coder/coder/v2/agent/agentssh" "github.com/coder/coder/v2/agent/agentssh"
"github.com/coder/coder/v2/agent/proto" "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/agent/reconnectingpty"
"github.com/coder/coder/v2/buildinfo" "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/cli/gitauth"
"github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk"
@ -50,6 +52,7 @@ import (
"github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/coder/v2/tailnet" "github.com/coder/coder/v2/tailnet"
tailnetproto "github.com/coder/coder/v2/tailnet/proto" tailnetproto "github.com/coder/coder/v2/tailnet/proto"
"github.com/coder/quartz"
) )
const ( const (
@ -89,8 +92,8 @@ type Options struct {
} }
type Client interface { type Client interface {
ConnectRPC23(ctx context.Context) ( ConnectRPC24(ctx context.Context) (
proto.DRPCAgentClient23, tailnetproto.DRPCTailnetClient23, error, proto.DRPCAgentClient24, tailnetproto.DRPCTailnetClient24, error,
) )
RewriteDERPMap(derpMap *tailcfg.DERPMap) RewriteDERPMap(derpMap *tailcfg.DERPMap)
} }
@ -410,7 +413,7 @@ func (t *trySingleflight) Do(key string, fn func()) {
fn() 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{}) tickerDone := make(chan struct{})
collectDone := make(chan struct{}) collectDone := make(chan struct{})
ctx, cancel := context.WithCancel(ctx) 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 // reportLifecycle reports the current lifecycle state once. All state
// changes are reported in order. // 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 { for {
select { select {
case <-a.lifecycleUpdate: case <-a.lifecycleUpdate:
@ -708,7 +711,7 @@ func (a *agent) setLifecycle(state codersdk.WorkspaceAgentLifecycle) {
// fetchServiceBannerLoop fetches the service banner on an interval. It will // fetchServiceBannerLoop fetches the service banner on an interval. It will
// not be fetched immediately; the expectation is that it is primed elsewhere // not be fetched immediately; the expectation is that it is primed elsewhere
// (and must be done before the session actually starts). // (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) ticker := time.NewTicker(a.announcementBannersRefreshInterval)
defer ticker.Stop() defer ticker.Stop()
for { for {
@ -744,7 +747,7 @@ func (a *agent) run() (retErr error) {
a.sessionToken.Store(&sessionToken) a.sessionToken.Store(&sessionToken)
// ConnectRPC returns the dRPC connection we use for the Agent and Tailnet v2+ APIs // 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 { if err != nil {
return err return err
} }
@ -761,7 +764,7 @@ func (a *agent) run() (retErr error) {
connMan := newAPIConnRoutineManager(a.gracefulCtx, a.hardCtx, a.logger, aAPI, tAPI) connMan := newAPIConnRoutineManager(a.gracefulCtx, a.hardCtx, a.logger, aAPI, tAPI)
connMan.startAgentAPI("init notification banners", gracefulShutdownBehaviorStop, 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{}) bannersProto, err := aAPI.GetAnnouncementBanners(ctx, &proto.GetAnnouncementBannersRequest{})
if err != nil { if err != nil {
return xerrors.Errorf("fetch service banner: %w", err) 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 // sending logs gets gracefulShutdownBehaviorRemain because we want to send logs generated by
// shutdown scripts. // shutdown scripts.
connMan.startAgentAPI("send logs", gracefulShutdownBehaviorRemain, 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) err := a.logSender.SendLoop(ctx, aAPI)
if xerrors.Is(err, agentsdk.LogLimitExceededError) { if xerrors.Is(err, agentsdk.LogLimitExceededError) {
// we don't want this error to tear down the API connection and propagate to the // 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 // metadata reporting can cease as soon as we start gracefully shutting down
connMan.startAgentAPI("report metadata", gracefulShutdownBehaviorStop, a.reportMetadata) 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 // channels to sync goroutines below
// handle manifest // handle manifest
// | // |
@ -818,7 +840,7 @@ func (a *agent) run() (retErr error) {
connMan.startAgentAPI("handle manifest", gracefulShutdownBehaviorStop, a.handleManifest(manifestOK)) connMan.startAgentAPI("handle manifest", gracefulShutdownBehaviorStop, a.handleManifest(manifestOK))
connMan.startAgentAPI("app health reporter", gracefulShutdownBehaviorStop, 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 { if err := manifestOK.wait(ctx); err != nil {
return xerrors.Errorf("no manifest: %w", err) return xerrors.Errorf("no manifest: %w", err)
} }
@ -833,7 +855,7 @@ func (a *agent) run() (retErr error) {
a.createOrUpdateNetwork(manifestOK, networkOK)) a.createOrUpdateNetwork(manifestOK, networkOK))
connMan.startTailnetAPI("coordination", gracefulShutdownBehaviorStop, 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 { if err := networkOK.wait(ctx); err != nil {
return xerrors.Errorf("no network: %w", err) return xerrors.Errorf("no network: %w", err)
} }
@ -842,7 +864,7 @@ func (a *agent) run() (retErr error) {
) )
connMan.startTailnetAPI("derp map subscriber", gracefulShutdownBehaviorStop, 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 { if err := networkOK.wait(ctx); err != nil {
return xerrors.Errorf("no network: %w", err) 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("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 { if err := networkOK.wait(ctx); err != nil {
return xerrors.Errorf("no network: %w", err) 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 // handleManifest returns a function that fetches and processes the manifest
func (a *agent) handleManifest(manifestOK *checkpoint) 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.DRPCAgentClient23) error { return func(ctx context.Context, aAPI proto.DRPCAgentClient24) error {
var ( var (
sentResult = false sentResult = false
err error 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 // createOrUpdateNetwork waits for the manifest to be set using manifestOK, then creates or updates
// the tailnet using the information in the manifest // the tailnet using the information in the manifest
func (a *agent) createOrUpdateNetwork(manifestOK, networkOK *checkpoint) func(context.Context, proto.DRPCAgentClient23) error { func (a *agent) createOrUpdateNetwork(manifestOK, networkOK *checkpoint) func(context.Context, proto.DRPCAgentClient24) error {
return func(ctx context.Context, _ proto.DRPCAgentClient23) (retErr error) { return func(ctx context.Context, _ proto.DRPCAgentClient24) (retErr error) {
if err := manifestOK.wait(ctx); err != nil { if err := manifestOK.wait(ctx); err != nil {
return xerrors.Errorf("no manifest: %w", err) 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 // runCoordinator runs a coordinator and returns whether a reconnect
// should occur. // 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") 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 // we run the RPC on the hardCtx so that we have a chance to send the disconnect message if we
// gracefully shut down. // 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. // 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") defer a.logger.Debug(ctx, "disconnected from derp map RPC")
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
@ -1696,8 +1718,8 @@ const (
type apiConnRoutineManager struct { type apiConnRoutineManager struct {
logger slog.Logger logger slog.Logger
aAPI proto.DRPCAgentClient23 aAPI proto.DRPCAgentClient24
tAPI tailnetproto.DRPCTailnetClient23 tAPI tailnetproto.DRPCTailnetClient24
eg *errgroup.Group eg *errgroup.Group
stopCtx context.Context stopCtx context.Context
remainCtx context.Context remainCtx context.Context
@ -1705,7 +1727,7 @@ type apiConnRoutineManager struct {
func newAPIConnRoutineManager( func newAPIConnRoutineManager(
gracefulCtx, hardCtx context.Context, logger slog.Logger, gracefulCtx, hardCtx context.Context, logger slog.Logger,
aAPI proto.DRPCAgentClient23, tAPI tailnetproto.DRPCTailnetClient23, aAPI proto.DRPCAgentClient24, tAPI tailnetproto.DRPCTailnetClient24,
) *apiConnRoutineManager { ) *apiConnRoutineManager {
// routines that remain in operation during graceful shutdown use the remainCtx. They'll still // 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. // exit if the errgroup hits an error, which usually means a problem with the conn.
@ -1738,7 +1760,7 @@ func newAPIConnRoutineManager(
// but for Tailnet. // but for Tailnet.
func (a *apiConnRoutineManager) startAgentAPI( func (a *apiConnRoutineManager) startAgentAPI(
name string, behavior gracefulShutdownBehavior, 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)) logger := a.logger.With(slog.F("name", name))
var ctx context.Context var ctx context.Context
@ -1775,7 +1797,7 @@ func (a *apiConnRoutineManager) startAgentAPI(
// but for the Agent API. // but for the Agent API.
func (a *apiConnRoutineManager) startTailnetAPI( func (a *apiConnRoutineManager) startTailnetAPI(
name string, behavior gracefulShutdownBehavior, 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)) logger := a.logger.With(slog.F("name", name))
var ctx context.Context var ctx context.Context

View File

@ -97,8 +97,8 @@ func (c *Client) Close() {
c.derpMapOnce.Do(func() { close(c.derpMapUpdates) }) c.derpMapOnce.Do(func() { close(c.derpMapUpdates) })
} }
func (c *Client) ConnectRPC23(ctx context.Context) ( func (c *Client) ConnectRPC24(ctx context.Context) (
agentproto.DRPCAgentClient23, proto.DRPCTailnetClient23, error, agentproto.DRPCAgentClient24, proto.DRPCTailnetClient24, error,
) { ) {
conn, lis := drpcsdk.MemTransportPipe() conn, lis := drpcsdk.MemTransportPipe()
c.LastWorkspaceAgent = func() { c.LastWorkspaceAgent = func() {
@ -173,6 +173,8 @@ type FakeAgentAPI struct {
timings []*agentproto.Timing 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) { 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 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) { func (f *FakeAgentAPI) UpdateStats(ctx context.Context, req *agentproto.UpdateStatsRequest) (*agentproto.UpdateStatsResponse, error) {
f.logger.Debug(ctx, "update stats called", slog.F("req", req)) 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 // empty request is sent to get the interval; but our tests don't want empty stats requests

File diff suppressed because it is too large Load Diff

View File

@ -295,6 +295,51 @@ message Timing {
Status status = 6; 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 { service Agent {
rpc GetManifest(GetManifestRequest) returns (Manifest); rpc GetManifest(GetManifestRequest) returns (Manifest);
rpc GetServiceBanner(GetServiceBannerRequest) returns (ServiceBanner); rpc GetServiceBanner(GetServiceBannerRequest) returns (ServiceBanner);
@ -306,4 +351,6 @@ service Agent {
rpc BatchCreateLogs(BatchCreateLogsRequest) returns (BatchCreateLogsResponse); rpc BatchCreateLogs(BatchCreateLogsRequest) returns (BatchCreateLogsResponse);
rpc GetAnnouncementBanners(GetAnnouncementBannersRequest) returns (GetAnnouncementBannersResponse); rpc GetAnnouncementBanners(GetAnnouncementBannersRequest) returns (GetAnnouncementBannersResponse);
rpc ScriptCompleted(WorkspaceAgentScriptCompletedRequest) returns (WorkspaceAgentScriptCompletedResponse); rpc ScriptCompleted(WorkspaceAgentScriptCompletedRequest) returns (WorkspaceAgentScriptCompletedResponse);
rpc GetResourcesMonitoringConfiguration(GetResourcesMonitoringConfigurationRequest) returns (GetResourcesMonitoringConfigurationResponse);
rpc PushResourcesMonitoringUsage(PushResourcesMonitoringUsageRequest) returns (PushResourcesMonitoringUsageResponse);
} }

View File

@ -48,6 +48,8 @@ type DRPCAgentClient interface {
BatchCreateLogs(ctx context.Context, in *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error) BatchCreateLogs(ctx context.Context, in *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error)
GetAnnouncementBanners(ctx context.Context, in *GetAnnouncementBannersRequest) (*GetAnnouncementBannersResponse, error) GetAnnouncementBanners(ctx context.Context, in *GetAnnouncementBannersRequest) (*GetAnnouncementBannersResponse, error)
ScriptCompleted(ctx context.Context, in *WorkspaceAgentScriptCompletedRequest) (*WorkspaceAgentScriptCompletedResponse, 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 { type drpcAgentClient struct {
@ -150,6 +152,24 @@ func (c *drpcAgentClient) ScriptCompleted(ctx context.Context, in *WorkspaceAgen
return out, nil 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 { type DRPCAgentServer interface {
GetManifest(context.Context, *GetManifestRequest) (*Manifest, error) GetManifest(context.Context, *GetManifestRequest) (*Manifest, error)
GetServiceBanner(context.Context, *GetServiceBannerRequest) (*ServiceBanner, error) GetServiceBanner(context.Context, *GetServiceBannerRequest) (*ServiceBanner, error)
@ -161,6 +181,8 @@ type DRPCAgentServer interface {
BatchCreateLogs(context.Context, *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error) BatchCreateLogs(context.Context, *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error)
GetAnnouncementBanners(context.Context, *GetAnnouncementBannersRequest) (*GetAnnouncementBannersResponse, error) GetAnnouncementBanners(context.Context, *GetAnnouncementBannersRequest) (*GetAnnouncementBannersResponse, error)
ScriptCompleted(context.Context, *WorkspaceAgentScriptCompletedRequest) (*WorkspaceAgentScriptCompletedResponse, error) ScriptCompleted(context.Context, *WorkspaceAgentScriptCompletedRequest) (*WorkspaceAgentScriptCompletedResponse, error)
GetResourcesMonitoringConfiguration(context.Context, *GetResourcesMonitoringConfigurationRequest) (*GetResourcesMonitoringConfigurationResponse, error)
PushResourcesMonitoringUsage(context.Context, *PushResourcesMonitoringUsageRequest) (*PushResourcesMonitoringUsageResponse, error)
} }
type DRPCAgentUnimplementedServer struct{} type DRPCAgentUnimplementedServer struct{}
@ -205,9 +227,17 @@ func (s *DRPCAgentUnimplementedServer) ScriptCompleted(context.Context, *Workspa
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented) 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{} 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) { func (DRPCAgentDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) {
switch n { switch n {
@ -301,6 +331,24 @@ func (DRPCAgentDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver,
in1.(*WorkspaceAgentScriptCompletedRequest), in1.(*WorkspaceAgentScriptCompletedRequest),
) )
}, DRPCAgentServer.ScriptCompleted, true }, 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: default:
return "", nil, nil, nil, false return "", nil, nil, nil, false
} }
@ -469,3 +517,35 @@ func (x *drpcAgent_ScriptCompletedStream) SendAndClose(m *WorkspaceAgentScriptCo
} }
return x.CloseSend() 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()
}

View File

@ -40,3 +40,11 @@ type DRPCAgentClient23 interface {
DRPCAgentClient22 DRPCAgentClient22
ScriptCompleted(ctx context.Context, in *WorkspaceAgentScriptCompletedRequest) (*WorkspaceAgentScriptCompletedResponse, error) 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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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())
})
}
}

View File

@ -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
}

View File

@ -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()
})
}
}

View File

@ -21,7 +21,7 @@
"previous_job": { "previous_job": {
"id": "======[workspace build job ID]======", "id": "======[workspace build job ID]======",
"status": "succeeded", "status": "succeeded",
"template_name": "", "template_name": "test-template",
"template_icon": "", "template_icon": "",
"template_display_name": "" "template_display_name": ""
}, },

View File

@ -42,6 +42,7 @@ type API struct {
*LifecycleAPI *LifecycleAPI
*AppsAPI *AppsAPI
*MetadataAPI *MetadataAPI
*ResourcesMonitoringAPI
*LogsAPI *LogsAPI
*ScriptsAPI *ScriptsAPI
*tailnet.DRPCService *tailnet.DRPCService
@ -104,6 +105,12 @@ func New(opts Options) *API {
appearanceFetcher: opts.AppearanceFetcher, appearanceFetcher: opts.AppearanceFetcher,
} }
api.ResourcesMonitoringAPI = &ResourcesMonitoringAPI{
Log: opts.Log,
AgentID: opts.AgentID,
Database: opts.Database,
}
api.StatsAPI = &StatsAPI{ api.StatsAPI = &StatsAPI{
AgentFn: api.agent, AgentFn: api.agent,
Database: opts.Database, Database: opts.Database,

View File

@ -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
}

37
coderd/apidoc/docs.go generated
View File

@ -2976,6 +2976,43 @@ const docTemplate = `{
"in": "path", "in": "path",
"required": true "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", "type": "object",
"description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})", "description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})",

View File

@ -2610,6 +2610,43 @@
"in": "path", "in": "path",
"required": true "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", "type": "object",
"description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})", "description": "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})",

View File

@ -184,6 +184,8 @@ var (
rbac.ResourceGroup.Type: {policy.ActionRead}, rbac.ResourceGroup.Type: {policy.ActionRead},
// Provisionerd creates notification messages // Provisionerd creates notification messages
rbac.ResourceNotificationMessage.Type: {policy.ActionCreate, policy.ActionRead}, rbac.ResourceNotificationMessage.Type: {policy.ActionCreate, policy.ActionRead},
// Provisionerd creates workspaces resources monitor
rbac.ResourceWorkspaceAgentResourceMonitor.Type: {policy.ActionCreate},
}), }),
Org: map[string][]rbac.Permission{}, Org: map[string][]rbac.Permission{},
User: []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) { 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 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) { 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 return nil, err
} }

View File

@ -4772,7 +4772,7 @@ func (s *MethodTestSuite) TestResourcesMonitor() {
monitor, err := db.FetchMemoryResourceMonitorsByAgentID(context.Background(), agt.ID) monitor, err := db.FetchMemoryResourceMonitorsByAgentID(context.Background(), agt.ID)
require.NoError(s.T(), err) 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) { 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) monitors, err := db.FetchVolumesResourceMonitorsByAgentID(context.Background(), agt.ID)
require.NoError(s.T(), err) 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)
})) }))
} }

View File

@ -3935,7 +3935,7 @@ func (q *FakeQuerier) GetProvisionerDaemonsByOrganization(_ context.Context, arg
return daemons, nil 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) err := validateDatabaseType(arg)
if err != nil { if err != nil {
return nil, err return nil, err
@ -3985,6 +3985,31 @@ func (q *FakeQuerier) GetProvisionerDaemonsWithStatusByOrganization(_ context.Co
status = database.ProvisionerDaemonStatusIdle 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 var previousJob database.ProvisionerJob
for _, job := range q.provisionerJobs { 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 // Get the provisioner key name
var keyName string var keyName string
@ -4017,8 +4067,14 @@ func (q *FakeQuerier) GetProvisionerDaemonsWithStatusByOrganization(_ context.Co
KeyName: keyName, KeyName: keyName,
CurrentJobID: uuid.NullUUID{UUID: currentJob.ID, Valid: currentJob.ID != uuid.Nil}, CurrentJobID: uuid.NullUUID{UUID: currentJob.ID, Valid: currentJob.ID != uuid.Nil},
CurrentJobStatus: database.NullProvisionerJobStatus{ProvisionerJobStatus: currentJob.JobStatus, 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}, PreviousJobID: uuid.NullUUID{UUID: previousJob.ID, Valid: previousJob.ID != uuid.Nil},
PreviousJobStatus: database.NullProvisionerJobStatus{ProvisionerJobStatus: previousJob.JobStatus, 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) 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 return rows, nil
} }

View File

@ -210,6 +210,8 @@ type sqlcQuerier interface {
GetPreviousTemplateVersion(ctx context.Context, arg GetPreviousTemplateVersionParams) (TemplateVersion, error) GetPreviousTemplateVersion(ctx context.Context, arg GetPreviousTemplateVersionParams) (TemplateVersion, error)
GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, error) GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, error)
GetProvisionerDaemonsByOrganization(ctx context.Context, arg GetProvisionerDaemonsByOrganizationParams) ([]ProvisionerDaemon, error) GetProvisionerDaemonsByOrganization(ctx context.Context, arg GetProvisionerDaemonsByOrganizationParams) ([]ProvisionerDaemon, error)
// Current job information.
// Previous job information.
GetProvisionerDaemonsWithStatusByOrganization(ctx context.Context, arg GetProvisionerDaemonsWithStatusByOrganizationParams) ([]GetProvisionerDaemonsWithStatusByOrganizationRow, error) GetProvisionerDaemonsWithStatusByOrganization(ctx context.Context, arg GetProvisionerDaemonsWithStatusByOrganizationParams) ([]GetProvisionerDaemonsWithStatusByOrganizationRow, error)
GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (ProvisionerJob, error) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (ProvisionerJob, error)
GetProvisionerJobTimingsByJobID(ctx context.Context, jobID uuid.UUID) ([]ProvisionerJobTiming, error) GetProvisionerJobTimingsByJobID(ctx context.Context, jobID uuid.UUID) ([]ProvisionerJobTiming, error)

View File

@ -5960,9 +5960,12 @@ SELECT
current_job.job_status AS current_job_status, current_job.job_status AS current_job_status,
previous_job.id AS previous_job_id, previous_job.id AS previous_job_id,
previous_job.job_status AS previous_job_status, previous_job.job_status AS previous_job_status,
COALESCE(tmpl.name, ''::text) AS current_job_template_name, COALESCE(current_template.name, ''::text) AS current_job_template_name,
COALESCE(tmpl.display_name, ''::text) AS current_job_template_display_name, COALESCE(current_template.display_name, ''::text) AS current_job_template_display_name,
COALESCE(tmpl.icon, ''::text) AS current_job_template_icon 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 FROM
provisioner_daemons pd provisioner_daemons pd
JOIN JOIN
@ -5988,15 +5991,27 @@ LEFT JOIN
) )
) )
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 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 WHERE
pd.organization_id = $2::uuid pd.organization_id = $2::uuid
AND (COALESCE(array_length($3::uuid[], 1), 0) = 0 OR pd.id = ANY($3::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)) AND ($4::tagset = 'null'::tagset OR provisioner_tagset_contains(pd.tags::tagset, $4::tagset))
ORDER BY ORDER BY
pd.created_at ASC pd.created_at ASC
LIMIT
$5::int
` `
type GetProvisionerDaemonsWithStatusByOrganizationParams struct { type GetProvisionerDaemonsWithStatusByOrganizationParams struct {
@ -6004,6 +6019,7 @@ type GetProvisionerDaemonsWithStatusByOrganizationParams struct {
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
IDs []uuid.UUID `db:"ids" json:"ids"` IDs []uuid.UUID `db:"ids" json:"ids"`
Tags StringMap `db:"tags" json:"tags"` Tags StringMap `db:"tags" json:"tags"`
Limit sql.NullInt32 `db:"limit" json:"limit"`
} }
type GetProvisionerDaemonsWithStatusByOrganizationRow struct { type GetProvisionerDaemonsWithStatusByOrganizationRow struct {
@ -6017,14 +6033,20 @@ type GetProvisionerDaemonsWithStatusByOrganizationRow struct {
CurrentJobTemplateName string `db:"current_job_template_name" json:"current_job_template_name"` 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"` 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"` 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) { func (q *sqlQuerier) GetProvisionerDaemonsWithStatusByOrganization(ctx context.Context, arg GetProvisionerDaemonsWithStatusByOrganizationParams) ([]GetProvisionerDaemonsWithStatusByOrganizationRow, error) {
rows, err := q.db.QueryContext(ctx, getProvisionerDaemonsWithStatusByOrganization, rows, err := q.db.QueryContext(ctx, getProvisionerDaemonsWithStatusByOrganization,
arg.StaleIntervalMS, arg.StaleIntervalMS,
arg.OrganizationID, arg.OrganizationID,
pq.Array(arg.IDs), pq.Array(arg.IDs),
arg.Tags, arg.Tags,
arg.Limit,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -6054,6 +6076,9 @@ func (q *sqlQuerier) GetProvisionerDaemonsWithStatusByOrganization(ctx context.C
&i.CurrentJobTemplateName, &i.CurrentJobTemplateName,
&i.CurrentJobTemplateDisplayName, &i.CurrentJobTemplateDisplayName,
&i.CurrentJobTemplateIcon, &i.CurrentJobTemplateIcon,
&i.PreviousJobTemplateName,
&i.PreviousJobTemplateDisplayName,
&i.PreviousJobTemplateIcon,
); err != nil { ); err != nil {
return nil, err return nil, err
} }

View File

@ -45,9 +45,12 @@ SELECT
current_job.job_status AS current_job_status, current_job.job_status AS current_job_status,
previous_job.id AS previous_job_id, previous_job.id AS previous_job_id,
previous_job.job_status AS previous_job_status, previous_job.job_status AS previous_job_status,
COALESCE(tmpl.name, ''::text) AS current_job_template_name, COALESCE(current_template.name, ''::text) AS current_job_template_name,
COALESCE(tmpl.display_name, ''::text) AS current_job_template_display_name, COALESCE(current_template.display_name, ''::text) AS current_job_template_display_name,
COALESCE(tmpl.icon, ''::text) AS current_job_template_icon 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 FROM
provisioner_daemons pd provisioner_daemons pd
JOIN JOIN
@ -72,16 +75,30 @@ LEFT JOIN
LIMIT 1 LIMIT 1
) )
) )
-- Current job information.
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 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 WHERE
pd.organization_id = @organization_id::uuid pd.organization_id = @organization_id::uuid
AND (COALESCE(array_length(@ids::uuid[], 1), 0) = 0 OR pd.id = ANY(@ids::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)) AND (@tags::tagset = 'null'::tagset OR provisioner_tagset_contains(pd.tags::tagset, @tags::tagset))
ORDER BY ORDER BY
pd.created_at ASC; pd.created_at ASC
LIMIT
sqlc.narg('limit')::int;
-- name: DeleteOldProvisionerDaemons :exec -- name: DeleteOldProvisionerDaemons :exec
-- Delete provisioner daemons that have been created at least a week ago -- Delete provisioner daemons that have been created at least a week ago

View File

@ -1,6 +1,7 @@
package coderd package coderd
import ( import (
"database/sql"
"net/http" "net/http"
"github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database"
@ -18,6 +19,9 @@ import (
// @Produce json // @Produce json
// @Tags Provisioning // @Tags Provisioning
// @Param organization path string true "Organization ID" format(uuid) // @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'})" // @Param tags query object false "Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'})"
// @Success 200 {array} codersdk.ProvisionerDaemon // @Success 200 {array} codersdk.ProvisionerDaemon
// @Router /organizations/{organization}/provisionerdaemons [get] // @Router /organizations/{organization}/provisionerdaemons [get]
@ -25,24 +29,40 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
var ( var (
ctx = r.Context() ctx = r.Context()
org = httpmw.OrganizationParam(r) org = httpmw.OrganizationParam(r)
tagParam = r.URL.Query().Get("tags")
tags = database.StringMap{}
err = tags.Scan([]byte(tagParam))
) )
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 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{ httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid tags query parameter", Message: "Invalid tags query parameter",
Detail: err.Error(), Detail: err.Error(),
}) })
return return
} }
}
daemons, err := api.Database.GetProvisionerDaemonsWithStatusByOrganization( daemons, err := api.Database.GetProvisionerDaemonsWithStatusByOrganization(
ctx, ctx,
database.GetProvisionerDaemonsWithStatusByOrganizationParams{ database.GetProvisionerDaemonsWithStatusByOrganizationParams{
OrganizationID: org.ID, OrganizationID: org.ID,
StaleIntervalMS: provisionerdserver.StaleInterval.Milliseconds(), StaleIntervalMS: provisionerdserver.StaleInterval.Milliseconds(),
Limit: sql.NullInt32{Int32: limit, Valid: limit > 0},
IDs: ids,
Tags: tags, Tags: tags,
}, },
) )
@ -70,6 +90,9 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
previousJob = &codersdk.ProvisionerDaemonJob{ previousJob = &codersdk.ProvisionerDaemonJob{
ID: dbDaemon.PreviousJobID.UUID, ID: dbDaemon.PreviousJobID.UUID,
Status: codersdk.ProvisionerJobStatus(dbDaemon.PreviousJobStatus.ProvisionerJobStatus), Status: codersdk.ProvisionerJobStatus(dbDaemon.PreviousJobStatus.ProvisionerJobStatus),
TemplateName: dbDaemon.PreviousJobTemplateName,
TemplateIcon: dbDaemon.PreviousJobTemplateIcon,
TemplateDisplayName: dbDaemon.PreviousJobTemplateDisplayName,
} }
} }

View File

@ -1,27 +1,251 @@
package coderd_test package coderd_test
import ( import (
"database/sql"
"encoding/json"
"strconv"
"testing" "testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/coderdtest" "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" "github.com/coder/coder/v2/testutil"
) )
func TestGetProvisionerDaemons(t *testing.T) { func TestProvisionerDaemons(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("OK", func(t *testing.T) { db, ps := dbtestutil.NewDB(t,
t.Parallel() dbtestutil.WithDumpOnFailure(),
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) //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) owner := coderdtest.CreateFirstUser(t, client)
memberClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) 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()
ctx := testutil.Context(t, testutil.WaitMedium) 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.NoError(t, err)
require.Len(t, daemons, 1) 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)
})
} }

View File

@ -1785,7 +1785,7 @@ func TestInsertWorkspacePresetsAndParameters(t *testing.T) {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background() ctx := testutil.Context(t, testutil.WaitLong)
logger := testutil.Logger(t) logger := testutil.Logger(t)
db, ps := dbtestutil.NewDB(t) db, ps := dbtestutil.NewDB(t)
org := dbgen.Organization(t, db, database.Organization{}) org := dbgen.Organization(t, db, database.Organization{})

View File

@ -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 { 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) require.NoError(t, err)
defer func() { defer func() {
cErr := aAPI.DRPCConn().Close() cErr := aAPI.DRPCConn().Close()

View File

@ -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 // 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) ( 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)) conn, err := c.connectRPCVersion(ctx, apiversion.New(2, 4))
if err != nil { if err != nil {

View File

@ -318,21 +318,34 @@ func (c *Client) ProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, e
return daemons, json.NewDecoder(res.Body).Decode(&daemons) return daemons, json.NewDecoder(res.Body).Decode(&daemons)
} }
func (c *Client) OrganizationProvisionerDaemons(ctx context.Context, organizationID uuid.UUID, tags map[string]string) ([]ProvisionerDaemon, error) { type OrganizationProvisionerDaemonsOptions struct {
baseURL := fmt.Sprintf("/api/v2/organizations/%s/provisionerdaemons", organizationID.String()) Limit int
IDs []uuid.UUID
Tags map[string]string
}
queryParams := url.Values{} func (c *Client) OrganizationProvisionerDaemons(ctx context.Context, organizationID uuid.UUID, opts *OrganizationProvisionerDaemonsOptions) ([]ProvisionerDaemon, error) {
tagsJSON, err := json.Marshal(tags) 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 { if err != nil {
return nil, xerrors.Errorf("marshal tags: %w", err) 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 { if err != nil {
return nil, xerrors.Errorf("execute request: %w", err) return nil, xerrors.Errorf("execute request: %w", err)
} }

View File

@ -5,7 +5,7 @@ import (
"golang.org/x/xerrors" "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 { func ValidateNewWorkspaceParameters(richParameters []TemplateVersionParameter, buildParameters []WorkspaceBuildParameter) error {

View File

@ -51,10 +51,28 @@ See the help docs for
### Generate a long-lived API token on behalf of another user ### 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 generate a token for another user.
You must have the `Owner` role to do this. Use our API reference for more
information: As of Coder v2.17+, you can use the CLI or API to create long-lived tokens on
[Create token API key](https://coder.com/docs/reference/api/users#create-token-api-key) behalf of other users. Use the API for earlier versions of Coder.
<div class="tabs">
#### CLI
```sh
coder tokens create my-token --user <username>
```
See the full CLI reference for
[`coder tokens create`](../../reference/cli/tokens_create.md)
#### API
Use our API reference for more information on how to
[create token API key](../../reference/api/users.md#create-token-api-key)
</div>
### Set max token length ### Set max token length

View File

@ -18,8 +18,29 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi
| Name | In | Type | Required | Description | | Name | In | Type | Required | Description |
|----------------|-------|--------------|----------|------------------------------------------------------------------------------------| |----------------|-------|--------------|----------|------------------------------------------------------------------------------------|
| `organization` | path | string(uuid) | true | Organization ID | | `organization` | path | string(uuid) | true | Organization ID |
| `limit` | query | integer | false | Page limit |
| `ids` | query | array(uuid) | false | Filter results by job IDs |
| `status` | query | string | false | Filter results by status |
| `tags` | query | object | false | Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'}) | | `tags` | query | object | false | Provisioner tags to filter by (JSON of the form {'tag1':'value1','tag2':'value2'}) |
#### Enumerated Values
| Parameter | Value |
|-----------|-------------|
| `status` | `pending` |
| `status` | `running` |
| `status` | `succeeded` |
| `status` | `canceling` |
| `status` | `canceled` |
| `status` | `failed` |
| `status` | `unknown` |
| `status` | `pending` |
| `status` | `running` |
| `status` | `succeeded` |
| `status` | `canceling` |
| `status` | `canceled` |
| `status` | `failed` |
### Example responses ### Example responses
> 200 Response > 200 Response

View File

@ -990,7 +990,9 @@ func TestGetProvisionerDaemons(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, allDaemons, 1) require.Len(t, allDaemons, 1)
daemonsAsFound, err := orgAdmin.OrganizationProvisionerDaemons(ctx, org.ID, tt.tagsToFilterBy) daemonsAsFound, err := orgAdmin.OrganizationProvisionerDaemons(ctx, org.ID, &codersdk.OrganizationProvisionerDaemonsOptions{
Tags: tt.tagsToFilterBy,
})
if tt.expectToGetDaemon { if tt.expectToGetDaemon {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, daemonsAsFound, 1) require.Len(t, daemonsAsFound, 1)

6
go.mod
View File

@ -94,7 +94,7 @@ require (
github.com/coder/quartz v0.1.2 github.com/coder/quartz v0.1.2
github.com/coder/retry v1.5.1 github.com/coder/retry v1.5.1
github.com/coder/serpent v0.10.0 github.com/coder/serpent v0.10.0
github.com/coder/terraform-provider-coder v1.0.5-0.20250131073245-5b9a30ca496b github.com/coder/terraform-provider-coder/v2 v2.1.3
github.com/coder/websocket v1.8.12 github.com/coder/websocket v1.8.12
github.com/coder/wgtunnel v0.1.13-0.20240522110300-ade90dfb2da0 github.com/coder/wgtunnel v0.1.13-0.20240522110300-ade90dfb2da0
github.com/coreos/go-oidc/v3 v3.12.0 github.com/coreos/go-oidc/v3 v3.12.0
@ -468,6 +468,4 @@ require (
sigs.k8s.io/yaml v1.4.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect
) )
require github.com/coder/terraform-provider-coder/v2 v2.1.3 // indirect replace github.com/coder/terraform-provider-coder/v2 => github.com/coder/terraform-provider-coder/v2 v2.1.4-0.20250211100915-129c295afed8
replace github.com/coder/terraform-provider-coder => github.com/coder/terraform-provider-coder/v2 v2.1.4-0.20250211100915-129c295afed8

2
go.sum
View File

@ -240,8 +240,6 @@ github.com/coder/tailscale v1.1.1-0.20250129014916-8086c871eae6 h1:prDIwUcsSEKbs
github.com/coder/tailscale v1.1.1-0.20250129014916-8086c871eae6/go.mod h1:1ggFFdHTRjPRu9Yc1yA7nVHBYB50w9Ce7VIXNqcW6Ko= github.com/coder/tailscale v1.1.1-0.20250129014916-8086c871eae6/go.mod h1:1ggFFdHTRjPRu9Yc1yA7nVHBYB50w9Ce7VIXNqcW6Ko=
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e h1:JNLPDi2P73laR1oAclY6jWzAbucf70ASAvf5mh2cME0= github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e h1:JNLPDi2P73laR1oAclY6jWzAbucf70ASAvf5mh2cME0=
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e/go.mod h1:Gz/z9Hbn+4KSp8A2FBtNszfLSdT2Tn/uAKGuVqqWmDI= github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e/go.mod h1:Gz/z9Hbn+4KSp8A2FBtNszfLSdT2Tn/uAKGuVqqWmDI=
github.com/coder/terraform-provider-coder/v2 v2.1.3 h1:zB7ObGsiOGBHcJUUMmcSauEPlTWRIYmMYieF05LxHSc=
github.com/coder/terraform-provider-coder/v2 v2.1.3/go.mod h1:RHGyb+ghiy8UpDAMJM8duRFuzd+1VqA3AtkRLh2P3Ug=
github.com/coder/terraform-provider-coder/v2 v2.1.4-0.20250211100915-129c295afed8 h1:qslh7kQytybvJHlqTI3XKUuFRnZWgvEjzZKq6e1aQ2M= github.com/coder/terraform-provider-coder/v2 v2.1.4-0.20250211100915-129c295afed8 h1:qslh7kQytybvJHlqTI3XKUuFRnZWgvEjzZKq6e1aQ2M=
github.com/coder/terraform-provider-coder/v2 v2.1.4-0.20250211100915-129c295afed8/go.mod h1:RHGyb+ghiy8UpDAMJM8duRFuzd+1VqA3AtkRLh2P3Ug= github.com/coder/terraform-provider-coder/v2 v2.1.4-0.20250211100915-129c295afed8/go.mod h1:RHGyb+ghiy8UpDAMJM8duRFuzd+1VqA3AtkRLh2P3Ug=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=

View File

@ -15,7 +15,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/coder/terraform-provider-coder/provider" "github.com/coder/terraform-provider-coder/v2/provider"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
tfjson "github.com/hashicorp/terraform-json" tfjson "github.com/hashicorp/terraform-json"

View File

@ -16,7 +16,7 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
"cdr.dev/slog" "cdr.dev/slog"
"github.com/coder/terraform-provider-coder/provider" "github.com/coder/terraform-provider-coder/v2/provider"
"github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/tracing"
@ -274,7 +274,7 @@ func provisionEnv(
env = append(env, provider.ParameterEnvironmentVariable(param.Name)+"="+param.Value) env = append(env, provider.ParameterEnvironmentVariable(param.Name)+"="+param.Value)
} }
for _, extAuth := range externalAuth { for _, extAuth := range externalAuth {
// env = append(env, provider.GitAuthAccessTokenEnvironmentVariable(extAuth.Id)+"="+extAuth.AccessToken) // env = append(env, gitAuthAccessTokenEnvironmentVariable(extAuth.Id)+"="+extAuth.AccessToken)
env = append(env, provider.ExternalAuthAccessTokenEnvironmentVariable(extAuth.Id)+"="+extAuth.AccessToken) env = append(env, provider.ExternalAuthAccessTokenEnvironmentVariable(extAuth.Id)+"="+extAuth.AccessToken)
} }
@ -356,3 +356,12 @@ func tryGettingCoderProviderStacktrace(sess *provisionersdk.Session) string {
} }
return string(stacktraces) return string(stacktraces)
} }
// gitAuthAccessTokenEnvironmentVariable is copied from
// github.com/coder/terraform-provider-coder/provider.GitAuthAccessTokenEnvironmentVariable@v1.0.4.
// While removed in v2 of the provider, we keep this to support customers using older templates that
// depend on this environment variable. Once we are certain that no customers are still using v1 of
// the provider, we can remove this function.
func gitAuthAccessTokenEnvironmentVariable(id string) string {
return fmt.Sprintf("CODER_GIT_AUTH_ACCESS_TOKEN_%s", id)
}

View File

@ -12,7 +12,7 @@ import (
"cdr.dev/slog" "cdr.dev/slog"
"github.com/coder/terraform-provider-coder/provider" "github.com/coder/terraform-provider-coder/v2/provider"
tfaddr "github.com/hashicorp/go-terraform-address" tfaddr "github.com/hashicorp/go-terraform-address"
@ -824,7 +824,6 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
} }
if len(nonExistentParameters) > 0 { if len(nonExistentParameters) > 0 {
// TODO (sasswart): should this be an error? Or should we just log it?
logger.Warn( logger.Warn(
ctx, ctx,
"coder_workspace_preset defines preset values for at least one parameter that is not defined by the template", "coder_workspace_preset defines preset values for at least one parameter that is not defined by the template",

View File

@ -827,38 +827,9 @@ func TestConvertResources(t *testing.T) {
Parameters: []*proto.PresetParameter{{ Parameters: []*proto.PresetParameter{{
Name: "Sample", Name: "Sample",
Value: "A1B2C3", Value: "A1B2C3",
// Advice from Danny:
// This is Terraform functionality. We don't have to test it explicitly.
// Sas: We still at some point need to document it.
// TODO (sasswart): Decide how to support presetting coder parameters from external modules
// Options are:
// * Set outputs with the parameter names and refer to those in the preset
// * set presets in the child module (won't work because we don't support merging presets)
// * hard coder parameter names
// }, {
// Name: "First parameter from module",
// Value: "A1B2C3",
// }, {
// Name: "First parameter from child module",
// Value: "A1B2C3",
}}, }},
}}, }},
}, },
// TODO (sasswart): Decide how to test sad paths.
// Do we just introduce an expectedErr in the testcase?
// Methinks yes
// "presets-without-parameters": {
// resources: []*proto.Resource{{
// Name: "dev",
// Type: "null_resource",
// }},
// },
// "presets-with-invalid-parameters": {
// resources: []*proto.Resource{{
// Name: "dev",
// Type: "null_resource",
// }},
// },
} { } {
folderName := folderName folderName := folderName
expected := expected expected := expected

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -91,6 +93,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -101,12 +104,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -254,7 +259,7 @@
] ]
} }
], ],
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:47:46Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "64433843-18f6-4f77-8f4f-f4f0eeaa67e2", "id": "14f0eb08-1bdb-4d48-ab20-e06584ee5b68",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "d109220e-5f54-4971-90d6-0761a77c653e", "token": "454fffe5-3c59-4a9e-80a0-0d1644ce3b24",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -66,7 +68,7 @@
"outputs": { "outputs": {
"script": "" "script": ""
}, },
"random": "1425989887898282436" "random": "8389680299908922676"
}, },
"sensitive_values": { "sensitive_values": {
"inputs": {}, "inputs": {},
@ -81,7 +83,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "7833850527819665042", "id": "8124127383117450432",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -81,6 +83,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -91,12 +94,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -199,7 +204,7 @@
] ]
} }
}, },
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:47:48Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "8092bd1c-099b-4ca8-8dc1-03e1c8687300", "id": "038d5038-be85-4609-bde3-56b7452e4386",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "1492d9de-d682-4966-b98e-d3e387823458", "token": "e570d762-5584-4192-a474-be9e137b2f09",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -54,7 +56,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "8792052167088454384", "id": "690495753077748083",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},
@ -71,7 +73,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "8664046372417778228", "id": "3238567980725122951",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -81,6 +83,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -91,12 +94,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -199,7 +204,7 @@
] ]
} }
}, },
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:47:50Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "f2f20308-2d37-4f79-b773-26427b2d6b88", "id": "be15a1b3-f041-4471-9dec-9784c68edb26",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "7c634c45-9a0c-494c-9469-d78253487973", "token": "df2580ad-59cc-48fb-bb21-40a8be5a5a66",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -54,7 +56,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "5416219352181334882", "id": "9103672483967127580",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},
@ -70,7 +72,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "5511965030506979192", "id": "4372402015997897970",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -30,6 +30,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -40,6 +41,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -89,6 +91,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -101,6 +104,7 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
@ -109,6 +113,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -198,7 +203,7 @@
] ]
} }
}, },
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:47:53Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "5a27b716-6b53-46ad-94fb-6ff0a7fb5028", "id": "398e27d3-10cc-4522-9144-34658eedad0e",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "ce7268ac-92f8-4889-8341-64aa6c411844", "token": "33068dbe-54d7-45eb-bfe5-87a9756802e2",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -54,7 +56,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "682773674388598502", "id": "5682617535476100233",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -30,6 +30,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -40,6 +41,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -89,6 +91,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -101,6 +104,7 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
@ -109,6 +113,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -198,7 +203,7 @@
] ]
} }
}, },
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:47:52Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "52c95fc8-4d53-479c-8368-87f329b84bf7", "id": "810cdd01-a27d-442f-9e69-bdaecced8a59",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "326a8bd5-1673-4bdf-beee-66c644b45088", "token": "fade1b71-d52b-4ef2-bb05-961f7795bab9",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -54,7 +56,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "2940668912954669921", "id": "5174735461860530782",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -69,6 +71,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -79,12 +82,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -222,7 +227,7 @@
] ]
} }
}, },
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:47:55Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -54,16 +54,17 @@
} }
], ],
"env": null, "env": null,
"id": "0be614e7-c8c1-4821-97c2-1cc3dec6744b", "id": "7ead336b-d366-4991-b38d-bdb8b9333ae9",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "de9d4b55-2735-47b4-b091-260dcb1be4cb", "token": "a3d2c620-f065-4b29-ae58-370292e787d4",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -71,6 +72,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -82,7 +84,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "6479999379045576610", "id": "3060850815800759131",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -81,6 +83,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -91,12 +94,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -219,7 +224,7 @@
] ]
} }
], ],
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:47:57Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "c4dfe556-bda2-4c46-921d-877a9f6362a5", "id": "c6e99a38-f10b-4242-a7c6-bd9186008b9d",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "0cd5fb29-059b-490d-a8d9-d2b6daa86dfe", "token": "ecddacca-df83-4dd2-b6cb-71f439e9e5f5",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -54,8 +56,8 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"agent_id": "c4dfe556-bda2-4c46-921d-877a9f6362a5", "agent_id": "c6e99a38-f10b-4242-a7c6-bd9186008b9d",
"id": "6d9e9e65-c9ff-47de-a733-541a36d9d039", "id": "0ed215f9-07b0-455f-828d-faee5f63ea93",
"instance_id": "example" "instance_id": "example"
}, },
"sensitive_values": {}, "sensitive_values": {},
@ -71,7 +73,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "1151295831746427704", "id": "1340003819945612525",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -121,6 +123,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -131,12 +134,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -321,7 +326,7 @@
] ]
} }
], ],
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:47:59Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "81df047e-38db-4717-a08d-79822c7f9631", "id": "18098e15-2e8b-4c83-9362-0823834ae628",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "9aa50425-529d-4948-b301-47d00140bbf2", "token": "59691c9e-bf9e-4c93-9768-ba3582c68727",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -55,14 +57,14 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "81df047e-38db-4717-a08d-79822c7f9631", "agent_id": "18098e15-2e8b-4c83-9362-0823834ae628",
"command": null, "command": null,
"display_name": "app1", "display_name": "app1",
"external": false, "external": false,
"healthcheck": [], "healthcheck": [],
"hidden": false, "hidden": false,
"icon": null, "icon": null,
"id": "701538ed-7f91-4fa7-b55f-a2389bfc7b0b", "id": "8f031ab5-e051-4eff-9f7e-233f5825c3fd",
"open_in": "slim-window", "open_in": "slim-window",
"order": null, "order": null,
"share": "owner", "share": "owner",
@ -86,14 +88,14 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "81df047e-38db-4717-a08d-79822c7f9631", "agent_id": "18098e15-2e8b-4c83-9362-0823834ae628",
"command": null, "command": null,
"display_name": "app2", "display_name": "app2",
"external": false, "external": false,
"healthcheck": [], "healthcheck": [],
"hidden": false, "hidden": false,
"icon": null, "icon": null,
"id": "df262f25-b224-4598-8f2e-e7d1ddb252d4", "id": "5462894e-7fdc-4fd0-8715-7829e53efea2",
"open_in": "slim-window", "open_in": "slim-window",
"order": null, "order": null,
"share": "owner", "share": "owner",
@ -116,7 +118,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "3545091168208360820", "id": "2699316377754222096",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -49,6 +51,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -57,6 +60,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -192,6 +196,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -202,12 +207,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -233,6 +240,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -243,12 +251,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -563,19 +573,19 @@
}, },
"relevant_attributes": [ "relevant_attributes": [
{ {
"resource": "coder_agent.dev2", "resource": "coder_agent.dev1",
"attribute": [ "attribute": [
"id" "id"
] ]
}, },
{ {
"resource": "coder_agent.dev1", "resource": "coder_agent.dev2",
"attribute": [ "attribute": [
"id" "id"
] ]
} }
], ],
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:48:03Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "358f434c-9056-4c2a-96cf-dd273e62a2f4", "id": "00794e64-40d3-43df-885a-4b1cc5f5b965",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "c3f886c0-c942-46f2-807f-1e6c38524191", "token": "7c0a6e5e-dd2c-46e4-a5f5-f71aae7515c3",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -68,16 +70,17 @@
} }
], ],
"env": null, "env": null,
"id": "decbcebe-ce0f-46ed-81cd-366651275bdc", "id": "1b8ddc14-25c2-4eab-b282-71b12d45de73",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "51f8f7fc-ec89-4500-b292-29386cf85245", "token": "39497aa1-11a1-40c0-854d-554c2e27ef77",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -85,6 +88,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -96,14 +100,14 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "358f434c-9056-4c2a-96cf-dd273e62a2f4", "agent_id": "00794e64-40d3-43df-885a-4b1cc5f5b965",
"command": null, "command": null,
"display_name": null, "display_name": null,
"external": false, "external": false,
"healthcheck": [], "healthcheck": [],
"hidden": false, "hidden": false,
"icon": null, "icon": null,
"id": "07e9147a-8ef2-4160-afbc-f3f7d1c06fad", "id": "c9cf036f-5fd9-408a-8c28-90cde4c5b0cf",
"open_in": "slim-window", "open_in": "slim-window",
"order": null, "order": null,
"share": "owner", "share": "owner",
@ -126,7 +130,7 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "358f434c-9056-4c2a-96cf-dd273e62a2f4", "agent_id": "00794e64-40d3-43df-885a-4b1cc5f5b965",
"command": null, "command": null,
"display_name": null, "display_name": null,
"external": false, "external": false,
@ -139,7 +143,7 @@
], ],
"hidden": false, "hidden": false,
"icon": null, "icon": null,
"id": "47b6572e-4ce6-4b8c-b2bc-5b1bfaa6c6c3", "id": "e40999b2-8ceb-4e35-962b-c0b7b95c8bc8",
"open_in": "slim-window", "open_in": "slim-window",
"order": null, "order": null,
"share": "owner", "share": "owner",
@ -164,14 +168,14 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "decbcebe-ce0f-46ed-81cd-366651275bdc", "agent_id": "1b8ddc14-25c2-4eab-b282-71b12d45de73",
"command": null, "command": null,
"display_name": null, "display_name": null,
"external": false, "external": false,
"healthcheck": [], "healthcheck": [],
"hidden": false, "hidden": false,
"icon": null, "icon": null,
"id": "3218f83a-486c-4d32-9dff-bcc502a06057", "id": "4e61c245-271a-41e1-9a37-2badf68bf5cd",
"open_in": "slim-window", "open_in": "slim-window",
"order": null, "order": null,
"share": "owner", "share": "owner",
@ -194,7 +198,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "6789549112713389367", "id": "7796235346668423309",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},
@ -210,7 +214,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "4753479913925883652", "id": "8353198974918613541",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -49,6 +51,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -57,6 +60,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -148,6 +152,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -158,12 +163,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -189,6 +196,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -199,12 +207,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -472,7 +482,7 @@
] ]
} }
], ],
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:48:05Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "cd64c74f-5861-4733-b7dc-a01fbe892431", "id": "f1398cbc-4e67-4a0e-92b7-15dc33221872",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "98f0d5fc-182e-49c3-8e20-7fb228f63a3e", "token": "acbbabee-e370-4aba-b876-843fb10201e8",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -68,16 +70,17 @@
} }
], ],
"env": null, "env": null,
"id": "a3e3ec9b-0695-4b8b-a32b-fb55021ee96a", "id": "ea44429d-fc3c-4ea6-ba23-a997dc66cad8",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "6892aa3e-0411-4b59-9bee-785b905fdba2", "token": "51fea695-82dd-4ccd-bf25-2c55a82b4851",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -85,6 +88,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -96,8 +100,8 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "cd64c74f-5861-4733-b7dc-a01fbe892431", "agent_id": "f1398cbc-4e67-4a0e-92b7-15dc33221872",
"id": "911d7e12-7c8e-436f-abc3-ee53df6422b1", "id": "f8f7b3f7-5c4b-47b9-959e-32d2044329e3",
"name": "ENV_1", "name": "ENV_1",
"value": "Env 1" "value": "Env 1"
}, },
@ -114,8 +118,8 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "cd64c74f-5861-4733-b7dc-a01fbe892431", "agent_id": "f1398cbc-4e67-4a0e-92b7-15dc33221872",
"id": "68417a79-e829-4577-86a5-2a282a420fd9", "id": "b7171d98-09c9-4bc4-899d-4b7343cd86ca",
"name": "ENV_2", "name": "ENV_2",
"value": "Env 2" "value": "Env 2"
}, },
@ -132,8 +136,8 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "a3e3ec9b-0695-4b8b-a32b-fb55021ee96a", "agent_id": "ea44429d-fc3c-4ea6-ba23-a997dc66cad8",
"id": "32a80778-1c3c-444a-947a-6eef729de65e", "id": "84021f25-1736-4884-8e5c-553e9c1f6fa6",
"name": "ENV_3", "name": "ENV_3",
"value": "Env 3" "value": "Env 3"
}, },
@ -150,7 +154,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "7492908688673175566", "id": "4901314428677246063",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},
@ -166,7 +170,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "1959061390178382235", "id": "3203010350140581146",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -49,6 +51,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -57,6 +60,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -169,6 +173,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -179,12 +184,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -210,6 +217,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -220,12 +228,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -511,19 +521,19 @@
}, },
"relevant_attributes": [ "relevant_attributes": [
{ {
"resource": "coder_agent.dev1", "resource": "coder_agent.dev2",
"attribute": [ "attribute": [
"id" "id"
] ]
}, },
{ {
"resource": "coder_agent.dev2", "resource": "coder_agent.dev1",
"attribute": [ "attribute": [
"id" "id"
] ]
} }
], ],
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:48:08Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "ee4b0276-a97b-4b6b-8cd3-2819c6a67aa6", "id": "bd762939-8952-4ac7-a9e5-618ec420b518",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "6a41a636-d2d2-44cf-9c3e-548478f06e19", "token": "f86127e8-2852-4c02-9f07-c376ec04318f",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -68,16 +70,17 @@
} }
], ],
"env": null, "env": null,
"id": "bd327d5e-3632-4d42-af81-cadf99aad2b5", "id": "60244093-3c9d-4655-b34f-c4713f7001c1",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "4ce115b1-7bab-440a-9136-025321a4b0c8", "token": "cad61f70-873f-440c-ad1c-9d34be2e19c4",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -85,6 +88,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -96,11 +100,11 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "ee4b0276-a97b-4b6b-8cd3-2819c6a67aa6", "agent_id": "bd762939-8952-4ac7-a9e5-618ec420b518",
"cron": null, "cron": null,
"display_name": "Foobar Script 1", "display_name": "Foobar Script 1",
"icon": null, "icon": null,
"id": "de8c8a6c-e85f-41fb-afa2-d5ac623a4fae", "id": "b34b6cd5-e85d-41c8-ad92-eaaceb2404cb",
"log_path": null, "log_path": null,
"run_on_start": true, "run_on_start": true,
"run_on_stop": false, "run_on_stop": false,
@ -121,11 +125,11 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "ee4b0276-a97b-4b6b-8cd3-2819c6a67aa6", "agent_id": "bd762939-8952-4ac7-a9e5-618ec420b518",
"cron": null, "cron": null,
"display_name": "Foobar Script 2", "display_name": "Foobar Script 2",
"icon": null, "icon": null,
"id": "3cb68229-11fd-4828-951c-e18d3c20a81d", "id": "d6f4e24c-3023-417d-b9be-4c83dbdf4802",
"log_path": null, "log_path": null,
"run_on_start": true, "run_on_start": true,
"run_on_stop": false, "run_on_stop": false,
@ -146,11 +150,11 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "bd327d5e-3632-4d42-af81-cadf99aad2b5", "agent_id": "60244093-3c9d-4655-b34f-c4713f7001c1",
"cron": null, "cron": null,
"display_name": "Foobar Script 3", "display_name": "Foobar Script 3",
"icon": null, "icon": null,
"id": "54976735-7900-4be8-9cfa-4b1bbd2dd0b6", "id": "a19e9106-5eb5-4941-b6ae-72a7724efdf0",
"log_path": null, "log_path": null,
"run_on_start": true, "run_on_start": true,
"run_on_stop": false, "run_on_stop": false,
@ -171,7 +175,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "1999609760128298126", "id": "8576645433635584827",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},
@ -187,7 +191,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "1520359202312470236", "id": "1280398780322015606",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -49,6 +51,7 @@
"motd_file": "/etc/motd", "motd_file": "/etc/motd",
"order": null, "order": null,
"os": "darwin", "os": "darwin",
"resources_monitoring": [],
"shutdown_script": "echo bye bye", "shutdown_script": "echo bye bye",
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -57,6 +60,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -77,6 +81,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "blocking", "startup_script_behavior": "blocking",
@ -85,6 +90,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -105,6 +111,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -113,6 +120,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -153,6 +161,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -163,12 +172,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -194,6 +205,7 @@
"motd_file": "/etc/motd", "motd_file": "/etc/motd",
"order": null, "order": null,
"os": "darwin", "os": "darwin",
"resources_monitoring": [],
"shutdown_script": "echo bye bye", "shutdown_script": "echo bye bye",
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -204,12 +216,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -235,6 +249,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "blocking", "startup_script_behavior": "blocking",
@ -245,12 +260,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -276,6 +293,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -286,12 +304,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -431,7 +451,7 @@
] ]
} }
}, },
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:48:01Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "c063caac-e0f7-40eb-8b1e-e9b653d5753d", "id": "215a9369-35c9-4abe-b1c0-3eb3ab1c1922",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "3e420343-27a9-49da-9851-d434d34a6d53", "token": "3fdd733c-b02e-4d81-a032-7c8d7ee3dcd8",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -68,16 +70,17 @@
} }
], ],
"env": null, "env": null,
"id": "952eddec-f95e-4401-aee6-ba7b9f1f4f40", "id": "b79acfba-d148-4940-80aa-0c72c037a3ed",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": "/etc/motd", "motd_file": "/etc/motd",
"order": null, "order": null,
"os": "darwin", "os": "darwin",
"resources_monitoring": [],
"shutdown_script": "echo bye bye", "shutdown_script": "echo bye bye",
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "23253e29-9acc-4602-a8b9-e66b2a75af61", "token": "e841a152-a794-4b05-9818-95e7440d402d",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -85,6 +88,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -110,16 +114,17 @@
} }
], ],
"env": null, "env": null,
"id": "24e9cc27-7115-4ad5-a3e8-77b143be2d30", "id": "4e863395-523b-443a-83c2-ab27e42a06b2",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "blocking", "startup_script_behavior": "blocking",
"token": "13a353ce-da86-4015-a6d0-c5926cd0374c", "token": "ee0a5e1d-879e-4bff-888e-6cf94533f0bd",
"troubleshooting_url": "https://coder.com/troubleshoot" "troubleshooting_url": "https://coder.com/troubleshoot"
}, },
"sensitive_values": { "sensitive_values": {
@ -127,6 +132,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -152,16 +158,17 @@
} }
], ],
"env": null, "env": null,
"id": "3d83fed1-7aed-4240-9c0b-3afbbcfb6fa2", "id": "611c43f5-fa8f-4641-9b5c-a58a8945caa1",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "9a31e16d-9c15-4dba-accf-a037d17741be", "token": "2d2669c7-6385-4ce8-8948-e4b24db45132",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -169,6 +176,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -180,7 +188,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "1754313482916802938", "id": "5237006672454822031",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -152,6 +154,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -162,12 +165,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -440,7 +445,7 @@
] ]
} }
], ],
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:48:10Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,16 +26,17 @@
} }
], ],
"env": null, "env": null,
"id": "054b161e-afd2-4783-a5b3-e926149361f3", "id": "cae4d590-8332-45b6-9453-e0151ca4f219",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "4d658a7b-6bdb-4b8d-a689-cbe3d5a3d95e", "token": "6db086ba-440b-4e66-8803-80e021cda61a",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -43,6 +44,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -54,14 +56,14 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "054b161e-afd2-4783-a5b3-e926149361f3", "agent_id": "cae4d590-8332-45b6-9453-e0151ca4f219",
"command": null, "command": null,
"display_name": null, "display_name": null,
"external": false, "external": false,
"healthcheck": [], "healthcheck": [],
"hidden": false, "hidden": false,
"icon": null, "icon": null,
"id": "15a4a26b-a880-4bdd-aa1a-1c023dc699c3", "id": "64803468-4ec4-49fe-beb7-e65eaf8e01ca",
"open_in": "slim-window", "open_in": "slim-window",
"order": null, "order": null,
"share": "owner", "share": "owner",
@ -84,7 +86,7 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "054b161e-afd2-4783-a5b3-e926149361f3", "agent_id": "cae4d590-8332-45b6-9453-e0151ca4f219",
"command": null, "command": null,
"display_name": null, "display_name": null,
"external": false, "external": false,
@ -97,7 +99,7 @@
], ],
"hidden": false, "hidden": false,
"icon": null, "icon": null,
"id": "13804972-c155-47bf-9fc2-81421523eebf", "id": "df3f07ab-1796-41c9-8e7d-b957dca031d4",
"open_in": "slim-window", "open_in": "slim-window",
"order": null, "order": null,
"share": "owner", "share": "owner",
@ -122,14 +124,14 @@
"provider_name": "registry.terraform.io/coder/coder", "provider_name": "registry.terraform.io/coder/coder",
"schema_version": 1, "schema_version": 1,
"values": { "values": {
"agent_id": "054b161e-afd2-4783-a5b3-e926149361f3", "agent_id": "cae4d590-8332-45b6-9453-e0151ca4f219",
"command": null, "command": null,
"display_name": null, "display_name": null,
"external": false, "external": false,
"healthcheck": [], "healthcheck": [],
"hidden": false, "hidden": false,
"icon": null, "icon": null,
"id": "2744abcb-51db-43cf-a2e3-61dcbd8d896a", "id": "fdb06774-4140-42ef-989b-12b98254b27c",
"open_in": "slim-window", "open_in": "slim-window",
"order": null, "order": null,
"share": "owner", "share": "owner",
@ -152,7 +154,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "4930273725392327631", "id": "8206837964247342986",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -30,6 +30,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -40,6 +41,7 @@
"metadata": [ "metadata": [
{} {}
], ],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -145,6 +147,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -157,6 +160,7 @@
"metadata": [ "metadata": [
{} {}
], ],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
@ -165,6 +169,7 @@
"metadata": [ "metadata": [
{} {}
], ],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -426,7 +431,7 @@
] ]
} }
], ],
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:48:14Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,7 +26,7 @@
} }
], ],
"env": null, "env": null,
"id": "233e324a-4c1b-490b-9439-ed996b476cf5", "id": "b3257d67-247c-4fc6-92a8-fc997501a0e1",
"init_script": "", "init_script": "",
"metadata": [ "metadata": [
{ {
@ -41,10 +41,11 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "1f12022b-bcef-4bfd-b07d-d3ad488da0a2", "token": "ac3563fb-3069-4919-b076-6687c765772b",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -54,6 +55,7 @@
"metadata": [ "metadata": [
{} {}
], ],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -68,7 +70,7 @@
"daily_cost": 29, "daily_cost": 29,
"hide": true, "hide": true,
"icon": "/icon/server.svg", "icon": "/icon/server.svg",
"id": "63496838-bc47-449a-b1fe-0135ad8e1759", "id": "fcd81afa-64ad-45e3-b000-31d1b19df922",
"item": [ "item": [
{ {
"is_null": false, "is_null": false,
@ -83,7 +85,7 @@
"value": "" "value": ""
} }
], ],
"resource_id": "1166169950293623087" "resource_id": "8033209281634385030"
}, },
"sensitive_values": { "sensitive_values": {
"item": [ "item": [
@ -107,7 +109,7 @@
"daily_cost": 20, "daily_cost": 20,
"hide": true, "hide": true,
"icon": "/icon/server.svg", "icon": "/icon/server.svg",
"id": "fc15b16c-6a70-4875-8a21-cca3aa9ec21a", "id": "186819f3-a92f-4785-9ee4-d79f57711f63",
"item": [ "item": [
{ {
"is_null": false, "is_null": false,
@ -116,7 +118,7 @@
"value": "world" "value": "world"
} }
], ],
"resource_id": "1166169950293623087" "resource_id": "8033209281634385030"
}, },
"sensitive_values": { "sensitive_values": {
"item": [ "item": [
@ -136,7 +138,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "1166169950293623087", "id": "8033209281634385030",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -30,6 +30,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -40,6 +41,7 @@
"metadata": [ "metadata": [
{} {}
], ],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -132,6 +134,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -144,6 +147,7 @@
"metadata": [ "metadata": [
{} {}
], ],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
@ -152,6 +156,7 @@
"metadata": [ "metadata": [
{} {}
], ],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -378,7 +383,7 @@
] ]
} }
], ],
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:48:12Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -26,7 +26,7 @@
} }
], ],
"env": null, "env": null,
"id": "9ca8533d-2f72-497c-8ecc-b4ce7ed7c00e", "id": "066d91d2-860a-4a44-9443-9eaf9315729b",
"init_script": "", "init_script": "",
"metadata": [ "metadata": [
{ {
@ -41,10 +41,11 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "linux", "os": "linux",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "d86a54a7-bd70-4d8d-90b1-10fde02f5bcf", "token": "9b6cc6dd-0e02-489f-b651-7a01804c406f",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -54,6 +55,7 @@
"metadata": [ "metadata": [
{} {}
], ],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -68,7 +70,7 @@
"daily_cost": 29, "daily_cost": 29,
"hide": true, "hide": true,
"icon": "/icon/server.svg", "icon": "/icon/server.svg",
"id": "5dfed2fe-d39c-4bf1-9234-d153d43c205f", "id": "fa791d91-9718-420e-9fa8-7a02e7af1563",
"item": [ "item": [
{ {
"is_null": false, "is_null": false,
@ -95,7 +97,7 @@
"value": "squirrel" "value": "squirrel"
} }
], ],
"resource_id": "3677336873960413975" "resource_id": "2710066198333857753"
}, },
"sensitive_values": { "sensitive_values": {
"item": [ "item": [
@ -118,7 +120,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "3677336873960413975", "id": "2710066198333857753",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -69,6 +71,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -79,12 +82,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -130,7 +135,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "678e4104-cd99-40d4-86e0-5244028860de", "id": "e8485920-025a-4c2c-b018-722f61b64347",
"mutable": false, "mutable": false,
"name": "Example", "name": "Example",
"option": null, "option": null,
@ -157,7 +162,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "22b1d84d-7c1d-47d0-b789-6a8d07ea926d", "id": "6156655b-f893-4eba-914e-e87414f4bf7e",
"mutable": false, "mutable": false,
"name": "Sample", "name": "Sample",
"option": null, "option": null,
@ -263,7 +268,7 @@
] ]
} }
}, },
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:48:18Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -17,7 +17,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "b8460866-87f5-4e31-824e-6d0c48dbcc79", "id": "4b774ce8-1e9f-4721-8a14-05efd3eb2dab",
"mutable": false, "mutable": false,
"name": "Example", "name": "Example",
"option": null, "option": null,
@ -44,7 +44,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "61920fa1-8186-40a6-9e0f-8cbca91985a9", "id": "447ae720-c046-452e-8d2c-1b5d4060b798",
"mutable": false, "mutable": false,
"name": "Sample", "name": "Sample",
"option": null, "option": null,
@ -80,16 +80,17 @@
} }
], ],
"env": null, "env": null,
"id": "adb01e5b-ebc5-488a-ae46-79d99bd6310f", "id": "b8d637c2-a19c-479c-b3e2-374f15ce37c3",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "ca3400c8-5759-4ffe-b335-368737690d93", "token": "52ce8a0d-12c9-40b5-9f86-dc6240b98d5f",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -97,6 +98,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -108,7 +110,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "7469573159164897550", "id": "769369130050936586",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -69,6 +71,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -79,12 +82,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -130,7 +135,7 @@
"display_name": null, "display_name": null,
"ephemeral": true, "ephemeral": true,
"icon": null, "icon": null,
"id": "0db9ef7c-02fe-43cf-8654-1bedc26f9fcb", "id": "30116bcb-f109-4807-be06-666a60b6cbb2",
"mutable": true, "mutable": true,
"name": "number_example", "name": "number_example",
"option": null, "option": null,
@ -157,7 +162,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "ef93145f-722a-4c24-8e7f-f94fc8327188", "id": "755395f4-d163-4b90-a8f4-e7ae24e17dd0",
"mutable": false, "mutable": false,
"name": "number_example_max", "name": "number_example_max",
"option": null, "option": null,
@ -196,7 +201,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "4561c132-1925-4612-8456-7ddbc8d1a1a8", "id": "dec9fa47-a252-4eb7-868b-10d0fe7bad57",
"mutable": false, "mutable": false,
"name": "number_example_max_zero", "name": "number_example_max_zero",
"option": null, "option": null,
@ -235,7 +240,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "b102d78f-a86b-4457-a643-505a59710008", "id": "57107f82-107b-484d-8491-0787f051dca7",
"mutable": false, "mutable": false,
"name": "number_example_min", "name": "number_example_min",
"option": null, "option": null,
@ -274,7 +279,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "ad756fd0-ac71-4be8-87a3-ca7462d44a6b", "id": "c21a61f4-26e0-49bb-99c8-56240433c21b",
"mutable": false, "mutable": false,
"name": "number_example_min_max", "name": "number_example_min_max",
"option": null, "option": null,
@ -313,7 +318,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "918bb8b5-ef2f-4bf6-9c65-bee901131d5d", "id": "4894f5cc-f4e6-4a86-bdfa-36c9d3f8f1a3",
"mutable": false, "mutable": false,
"name": "number_example_min_zero", "name": "number_example_min_zero",
"option": null, "option": null,
@ -545,7 +550,7 @@
] ]
} }
}, },
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:48:20Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -17,7 +17,7 @@
"display_name": null, "display_name": null,
"ephemeral": true, "ephemeral": true,
"icon": null, "icon": null,
"id": "56a3b2a7-479b-41f5-a99b-f51a850ac8c2", "id": "9b5bb411-bfe5-471a-8f2d-9fcc8c17b616",
"mutable": true, "mutable": true,
"name": "number_example", "name": "number_example",
"option": null, "option": null,
@ -44,7 +44,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "bf19a33e-4b16-4b86-bbf8-cb76c952ce71", "id": "2ebaf3ec-9272-48f4-981d-09485ae7960e",
"mutable": false, "mutable": false,
"name": "number_example_max", "name": "number_example_max",
"option": null, "option": null,
@ -83,7 +83,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "d25e4b3a-f630-4da0-840f-1b823e336155", "id": "d05a833c-d0ca-4f22-8b80-40851c111b61",
"mutable": false, "mutable": false,
"name": "number_example_max_zero", "name": "number_example_max_zero",
"option": null, "option": null,
@ -122,7 +122,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "6fde8249-13f5-4c8e-b9cb-2a1db8d3ff11", "id": "de0cd614-72b3-4404-80a1-e3c780823fc9",
"mutable": false, "mutable": false,
"name": "number_example_min", "name": "number_example_min",
"option": null, "option": null,
@ -161,7 +161,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "8a21c092-2d4a-412a-a0bb-61f20b67b9b0", "id": "66eae3e1-9bb5-44f8-8f15-2b400628d0e7",
"mutable": false, "mutable": false,
"name": "number_example_min_max", "name": "number_example_min_max",
"option": null, "option": null,
@ -200,7 +200,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "916a9318-6082-4ace-a196-20d78621aa9a", "id": "d24d37f9-5a91-4c7f-9915-bfc10f6d353d",
"mutable": false, "mutable": false,
"name": "number_example_min_zero", "name": "number_example_min_zero",
"option": null, "option": null,
@ -248,16 +248,17 @@
} }
], ],
"env": null, "env": null,
"id": "327d3049-be5e-4a37-98f1-b6591fb86104", "id": "81170f06-8f49-43fb-998f-dc505a29632c",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "0de65cf4-bb12-4daa-b75b-422eb9c6f3b1", "token": "f8433068-1acc-4225-94c0-725f86cdc002",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -265,6 +266,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -276,7 +278,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "4847948730203176710", "id": "3641782836917385715",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},

View File

@ -21,6 +21,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -29,6 +30,7 @@
"sensitive_values": { "sensitive_values": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -69,6 +71,7 @@
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
@ -79,12 +82,14 @@
"id": true, "id": true,
"init_script": true, "init_script": true,
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
}, },
"before_sensitive": false, "before_sensitive": false,
"after_sensitive": { "after_sensitive": {
"display_apps": [], "display_apps": [],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
} }
@ -130,7 +135,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "18a679bf-c1f9-4056-b5ec-1401587efcaf", "id": "72f11f9b-8c7f-4e4a-a207-f080b114862b",
"mutable": false, "mutable": false,
"name": "Example", "name": "Example",
"option": [ "option": [
@ -174,7 +179,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "a15920af-2f38-4cba-a26f-97492b58d853", "id": "b154b8a7-d31f-46f7-b876-e5bfdf50950c",
"mutable": false, "mutable": false,
"name": "number_example", "name": "number_example",
"option": null, "option": null,
@ -201,7 +206,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "7fdb781f-ad42-4cbf-88f2-b8014a4f1b9e", "id": "8199f88e-8b73-4385-bbb2-315182f753ef",
"mutable": false, "mutable": false,
"name": "number_example_max_zero", "name": "number_example_max_zero",
"option": null, "option": null,
@ -240,7 +245,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "b1284628-4b51-4fb5-9bd6-d4b46895e73a", "id": "110c995d-46d7-4277-8f57-a3d3d42733c3",
"mutable": false, "mutable": false,
"name": "number_example_min_max", "name": "number_example_min_max",
"option": null, "option": null,
@ -279,7 +284,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "24062c56-f326-4bab-893f-eb6bd99d9d0e", "id": "e7a1f991-48a8-44c5-8a5c-597db8539cb7",
"mutable": false, "mutable": false,
"name": "number_example_min_zero", "name": "number_example_min_zero",
"option": null, "option": null,
@ -318,7 +323,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "ea1e26d1-dd66-41af-b1f9-b50220db3dc6", "id": "27d12cdf-da7e-466b-907a-4824920305da",
"mutable": false, "mutable": false,
"name": "Sample", "name": "Sample",
"option": null, "option": null,
@ -349,7 +354,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "91236b71-4966-4cdc-8d3c-39d706180779", "id": "1242389a-5061-482a-8274-410174fb3fc0",
"mutable": true, "mutable": true,
"name": "First parameter from module", "name": "First parameter from module",
"option": null, "option": null,
@ -376,7 +381,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "6ae5570d-ef16-4bdf-a46b-0025b197f2fa", "id": "72418f70-4e3c-400f-9a7d-bf3467598deb",
"mutable": true, "mutable": true,
"name": "Second parameter from module", "name": "Second parameter from module",
"option": null, "option": null,
@ -408,7 +413,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "7b0cbc7c-c11b-4902-98d4-4b4354978e05", "id": "9b4b60d8-21bb-4d52-910a-536355e9a85f",
"mutable": true, "mutable": true,
"name": "First parameter from child module", "name": "First parameter from child module",
"option": null, "option": null,
@ -435,7 +440,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "2b4e1132-77ef-4871-9ffe-f51d188d9821", "id": "4edca123-07bf-4409-ad40-ed26f93beb5f",
"mutable": true, "mutable": true,
"name": "Second parameter from child module", "name": "Second parameter from child module",
"option": null, "option": null,
@ -788,7 +793,7 @@
} }
} }
}, },
"timestamp": "2025-02-06T07:28:26Z", "timestamp": "2025-01-29T22:48:16Z",
"applyable": true, "applyable": true,
"complete": true, "complete": true,
"errored": false "errored": false

View File

@ -17,7 +17,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "4d2ee311-d55d-4222-b78c-8573531f141a", "id": "7298c15e-11c8-4a9e-a2ef-044dbc44d519",
"mutable": false, "mutable": false,
"name": "Example", "name": "Example",
"option": [ "option": [
@ -61,7 +61,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "558025fd-1456-4f1f-b876-f4466e1df6a6", "id": "a0dda000-20cb-42a7-9f83-1a1de0876e48",
"mutable": false, "mutable": false,
"name": "number_example", "name": "number_example",
"option": null, "option": null,
@ -88,7 +88,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "0b65aa73-27c7-47d9-9281-7604545c3f6d", "id": "82a297b9-bbcb-4807-9de3-7217953dc6b0",
"mutable": false, "mutable": false,
"name": "number_example_max_zero", "name": "number_example_max_zero",
"option": null, "option": null,
@ -127,7 +127,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "4d817bbd-d8d7-413b-b7d9-5bffdb3b6f15", "id": "ae1c376b-e28b-456a-b36e-125b3bc6d938",
"mutable": false, "mutable": false,
"name": "number_example_min_max", "name": "number_example_min_max",
"option": null, "option": null,
@ -166,7 +166,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "654efe81-8678-4425-b19e-0436cc4a460e", "id": "57573ac3-5610-4887-b269-376071867eb5",
"mutable": false, "mutable": false,
"name": "number_example_min_zero", "name": "number_example_min_zero",
"option": null, "option": null,
@ -205,7 +205,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "8e6d963f-186c-4eea-863e-e6f492d09b98", "id": "0e08645d-0105-49ef-b278-26cdc30a826c",
"mutable": false, "mutable": false,
"name": "Sample", "name": "Sample",
"option": null, "option": null,
@ -241,16 +241,17 @@
} }
], ],
"env": null, "env": null,
"id": "0832e1d0-6ee5-4f00-89c2-ead6d781734d", "id": "c5c402bd-215b-487f-862f-eca25fe88a72",
"init_script": "", "init_script": "",
"metadata": [], "metadata": [],
"motd_file": null, "motd_file": null,
"order": null, "order": null,
"os": "windows", "os": "windows",
"resources_monitoring": [],
"shutdown_script": null, "shutdown_script": null,
"startup_script": null, "startup_script": null,
"startup_script_behavior": "non-blocking", "startup_script_behavior": "non-blocking",
"token": "e51c05f1-e69c-4489-951a-b18cc28dfc8e", "token": "b70d10f3-90bc-4abd-8cd9-b11da843954a",
"troubleshooting_url": null "troubleshooting_url": null
}, },
"sensitive_values": { "sensitive_values": {
@ -258,6 +259,7 @@
{} {}
], ],
"metadata": [], "metadata": [],
"resources_monitoring": [],
"token": true "token": true
} }
}, },
@ -269,7 +271,7 @@
"provider_name": "registry.terraform.io/hashicorp/null", "provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0, "schema_version": 0,
"values": { "values": {
"id": "7215235512764323226", "id": "8544034527967282476",
"triggers": null "triggers": null
}, },
"sensitive_values": {}, "sensitive_values": {},
@ -294,7 +296,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "c3ae0239-9c8d-4d71-b7cf-858f7f93da00", "id": "68ae438d-7194-4f5b-adeb-9c74059d9888",
"mutable": true, "mutable": true,
"name": "First parameter from module", "name": "First parameter from module",
"option": null, "option": null,
@ -321,7 +323,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "5fdf8d8d-b4b3-4703-9d37-e235a337b0f6", "id": "32f0f7f3-26a5-4023-a4e6-d9436cfe8cb4",
"mutable": true, "mutable": true,
"name": "Second parameter from module", "name": "Second parameter from module",
"option": null, "option": null,
@ -353,7 +355,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "3903f4a3-89e8-47be-ad6d-7d1953f73e9d", "id": "5235636a-3319-47ae-8879-b62f9ee9c5aa",
"mutable": true, "mutable": true,
"name": "First parameter from child module", "name": "First parameter from child module",
"option": null, "option": null,
@ -380,7 +382,7 @@
"display_name": null, "display_name": null,
"ephemeral": false, "ephemeral": false,
"icon": null, "icon": null,
"id": "06e55c81-1797-4240-9887-a0ad9fee1ee3", "id": "54fa94ff-3048-457d-8de2-c182f6287c8d",
"mutable": true, "mutable": true,
"name": "Second parameter from child module", "name": "Second parameter from child module",
"option": null, "option": null,

View File

@ -1441,6 +1441,13 @@ export interface OrganizationMemberWithUserData extends OrganizationMember {
readonly global_roles: readonly SlimRole[]; readonly global_roles: readonly SlimRole[];
} }
// From codersdk/organizations.go
export interface OrganizationProvisionerDaemonsOptions {
readonly Limit: number;
readonly IDs: readonly string[];
readonly Tags: Record<string, string>;
}
// From codersdk/organizations.go // From codersdk/organizations.go
export interface OrganizationProvisionerJobsOptions { export interface OrganizationProvisionerJobsOptions {
readonly Limit: number; readonly Limit: number;

View File

@ -0,0 +1,22 @@
import { includeOrigin } from "./MobileMenu";
const mockOrigin = "https://example.com";
describe("support link", () => {
it("should include origin if target starts with '/'", () => {
(window as unknown as { location: Partial<Location> }).location = {
origin: mockOrigin,
}; // Mock the location origin
expect(includeOrigin("/test")).toBe(`${mockOrigin}/test`);
expect(includeOrigin("/path/to/resource")).toBe(
`${mockOrigin}/path/to/resource`,
);
});
it("should return the target unchanged if it does not start with '/'", () => {
expect(includeOrigin(`${mockOrigin}/page`)).toBe(`${mockOrigin}/page`);
expect(includeOrigin("../relative/path")).toBe("../relative/path");
expect(includeOrigin("relative/path")).toBe("relative/path");
});
});

View File

@ -307,7 +307,11 @@ const UserSettingsSub: FC<UserSettingsSubProps> = ({
asChild asChild
className={cn(itemStyles.default, itemStyles.sub)} className={cn(itemStyles.default, itemStyles.sub)}
> >
<a href={l.target} target="_blank" rel="noreferrer"> <a
href={includeOrigin(l.target)}
target="_blank"
rel="noreferrer"
>
{l.name} {l.name}
</a> </a>
</DropdownMenuItem> </DropdownMenuItem>
@ -318,3 +322,11 @@ const UserSettingsSub: FC<UserSettingsSubProps> = ({
</Collapsible> </Collapsible>
); );
}; };
export const includeOrigin = (target: string): string => {
if (target.startsWith("/")) {
const baseUrl = window.location.origin;
return `${baseUrl}${target}`;
}
return target;
};

View File

@ -6,12 +6,12 @@ const meta: Meta<typeof UserEngagementChart> = {
component: UserEngagementChart, component: UserEngagementChart,
args: { args: {
data: [ data: [
{ date: "1/1/2024", users: 150 }, { date: "1/1/2024", users: 140 },
{ date: "1/2/2024", users: 165 }, { date: "1/2/2024", users: 175 },
{ date: "1/3/2024", users: 180 }, { date: "1/3/2024", users: 120 },
{ date: "1/4/2024", users: 155 }, { date: "1/4/2024", users: 195 },
{ date: "1/5/2024", users: 190 }, { date: "1/5/2024", users: 230 },
{ date: "1/6/2024", users: 200 }, { date: "1/6/2024", users: 130 },
{ date: "1/7/2024", users: 210 }, { date: "1/7/2024", users: 210 },
], ],
}, },

View File

@ -157,7 +157,7 @@ export const UserEngagementChart: FC<UserEngagementChartProps> = ({ data }) => {
<Area <Area
dataKey="users" dataKey="users"
type="natural" type="linear"
fill="url(#fillUsers)" fill="url(#fillUsers)"
fillOpacity={0.4} fillOpacity={0.4}
stroke="var(--color-users)" stroke="var(--color-users)"

View File

@ -126,14 +126,18 @@ export const OrganizationSettingsPageView: FC<
</HorizontalForm> </HorizontalForm>
{!organization.is_default && ( {!organization.is_default && (
<HorizontalContainer css={{ marginTop: 48 }}> <HorizontalContainer className="mt-12">
<HorizontalSection <HorizontalSection
title="Settings" title="Settings"
description="Change or delete your organization." description="Change or delete your organization."
> >
<div css={styles.dangerSettings}> <div className="flex bg-surface-orange items-center justify-between border border-solid border-orange-600 rounded-md p-3 pl-4 gap-2 flex-grow">
<span>Deleting an organization is irreversible.</span> <span>Deleting an organization is irreversible.</span>
<Button variant="destructive" onClick={() => setIsDeleting(true)}> <Button
variant="destructive"
onClick={() => setIsDeleting(true)}
className="min-w-fit"
>
Delete this organization Delete this organization
</Button> </Button>
</div> </div>
@ -151,45 +155,3 @@ export const OrganizationSettingsPageView: FC<
</div> </div>
); );
}; };
const styles = {
dangerSettings: (theme) => ({
display: "flex",
backgroundColor: theme.roles.danger.background,
alignItems: "center",
justifyContent: "space-between",
border: `1px solid ${theme.roles.danger.outline}`,
borderRadius: 8,
padding: 12,
paddingLeft: 18,
gap: 8,
lineHeight: "18px",
flexGrow: 1,
"& .option": {
color: theme.roles.danger.fill.solid,
"&.Mui-checked": {
color: theme.roles.danger.fill.solid,
},
},
"& .info": {
fontSize: 14,
fontWeight: 600,
color: theme.roles.danger.text,
},
}),
dangerButton: (theme) => ({
borderColor: theme.roles.danger.outline,
color: theme.roles.danger.text,
"&.MuiLoadingButton-loading": {
color: theme.roles.danger.disabled.text,
},
"&:hover:not(:disabled)": {
backgroundColor: theme.roles.danger.hover.background,
borderColor: theme.roles.danger.hover.fill.outline,
},
}),
} satisfies Record<string, Interpolation<Theme>>;

View File

@ -246,6 +246,11 @@ export const MockSupportLinks: TypesGen.LinkConfig[] = [
"https://github.com/coder/coder/issues/new?labels=needs+grooming&body={CODER_BUILD_INFO}", "https://github.com/coder/coder/issues/new?labels=needs+grooming&body={CODER_BUILD_INFO}",
icon: "", icon: "",
}, },
{
name: "Fourth link",
target: "/icons",
icon: "",
},
]; ];
export const MockUpdateCheck: TypesGen.UpdateCheckResponse = { export const MockUpdateCheck: TypesGen.UpdateCheckResponse = {

View File

@ -59,6 +59,7 @@
"jupyter.svg", "jupyter.svg",
"k8s.png", "k8s.png",
"kasmvnc.svg", "kasmvnc.svg",
"keycloak.svg",
"kotlin.svg", "kotlin.svg",
"lxc.svg", "lxc.svg",
"matlab.svg", "matlab.svg",

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="167.117" height="150.658" viewBox="0 0 44.216 39.861"><path d="m88.61 138.456 5.716-9.865 23.018-.004 5.686 9.965.007 19.932-5.691 9.957-23.012.008-5.782-9.965z" style="display:inline;fill:#4d4d4d;fill-opacity:1;stroke-width:.264583" transform="translate(-82.815 -128.588)"/><path d="M88.552 158.481h10.375l-5.699-10.041 4.634-9.982-9.252-.002-5.795 10.065" style="fill:#ededed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="M102.073 158.481h7.582l6.706-9.773-6.589-10.156h-8.921l-5.373 9.814z" style="fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="m82.815 148.52 5.738 9.964h10.374l-5.636-9.93z" style="fill:#acacac;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="m95.589 148.522 6.484 9.963h7.582l6.601-9.959z" style="fill:#9e9e9e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="m98.157 148.529-1.958.569-1.877-.572 7.667-13.288 1.918 3.316" style="fill:#00b8e3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="m103.9 158.482-1.909 3.332-5.093-5.487-2.58-7.797v-.004h3.838" style="fill:#33c6e9;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="M94.322 148.526h-.003v.003l-1.918 3.322-1.925-3.307 1.952-3.386 5.728-9.92h3.834" style="fill:#008aaa;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="M115.42 158.481h11.611l-.007-19.93h-11.605z" style="fill:#d4d4d4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="M115.42 148.554v9.93h11.59v-9.93z" style="fill:#919191;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="M101.992 161.817h-3.836l-5.755-9.966 1.918-3.321z" style="fill:#00b8e3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="m117.333 148.526-7.669 13.289c-.705-1.036-1.913-3.331-1.913-3.331l5.753-9.959z" style="fill:#008aaa;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="m113.495 161.815-3.831-.001 7.67-13.288 1.917-3.317 1.921 3.34m-3.839-.023h-3.828l-5.755-9.973 1.905-3.314 4.658 5.922z" style="fill:#00b8e3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/><path d="M119.25 145.205v.003l-1.917 3.318-7.677-13.286 3.841.002z" style="fill:#33c6e9;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.330729" transform="translate(-82.815 -128.588)"/></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -34,3 +34,9 @@ type DRPCTailnetClient23 interface {
RefreshResumeToken(ctx context.Context, in *RefreshResumeTokenRequest) (*RefreshResumeTokenResponse, error) RefreshResumeToken(ctx context.Context, in *RefreshResumeTokenRequest) (*RefreshResumeTokenResponse, error)
WorkspaceUpdates(ctx context.Context, in *WorkspaceUpdatesRequest) (DRPCTailnet_WorkspaceUpdatesClient, error) WorkspaceUpdates(ctx context.Context, in *WorkspaceUpdatesRequest) (DRPCTailnet_WorkspaceUpdatesClient, error)
} }
// DRPCTailnetClient24 is the Tailnet API at v2.4. It is functionally identical to 2.3, because the
// change was to the Agent API (ResourcesMonitoring methods).
type DRPCTailnetClient24 interface {
DRPCTailnetClient23
}

View File

@ -38,6 +38,11 @@ import (
// shipped in Coder v2.16.0, but we forgot to increment the API version. If // shipped in Coder v2.16.0, but we forgot to increment the API version. If
// you dial for API v2.2, you MAY be connected to a server that supports // you dial for API v2.2, you MAY be connected to a server that supports
// ScriptCompleted, but be prepared to process "unsupported" errors.) // ScriptCompleted, but be prepared to process "unsupported" errors.)
//
// API v2.4:
// - Shipped in Coder v2.{{placeholder}} // TODO Vincent: Replace with the correct version
// - Added support for GetResourcesMonitoringConfiguration and
// PushResourcesMonitoringUsage RPCs on the Agent API.
const ( const (
CurrentMajor = 2 CurrentMajor = 2
CurrentMinor = 4 CurrentMinor = 4