diff --git a/agent/agent.go b/agent/agent.go
index f73d004ed65b7..356338b14bfa7 100644
--- a/agent/agent.go
+++ b/agent/agent.go
@@ -2,12 +2,14 @@ package agent
import (
"bufio"
+ "bytes"
"context"
"crypto/rand"
"crypto/rsa"
"encoding/binary"
"encoding/json"
"errors"
+ "flag"
"fmt"
"io"
"net"
@@ -33,6 +35,7 @@ import (
"go.uber.org/atomic"
gossh "golang.org/x/crypto/ssh"
"golang.org/x/exp/slices"
+ "golang.org/x/sync/singleflight"
"golang.org/x/xerrors"
"tailscale.com/net/speedtest"
"tailscale.com/tailcfg"
@@ -83,12 +86,13 @@ type Options struct {
}
type Client interface {
- Metadata(ctx context.Context) (agentsdk.Metadata, error)
+ Manifest(ctx context.Context) (agentsdk.Manifest, error)
Listen(ctx context.Context) (net.Conn, error)
ReportStats(ctx context.Context, log slog.Logger, statsChan <-chan *agentsdk.Stats, setInterval func(time.Duration)) (io.Closer, error)
PostLifecycle(ctx context.Context, state agentsdk.PostLifecycleRequest) error
PostAppHealth(ctx context.Context, req agentsdk.PostAppHealthsRequest) error
PostStartup(ctx context.Context, req agentsdk.PostStartupRequest) error
+ PostMetadata(ctx context.Context, key string, req agentsdk.PostMetadataRequest) error
PatchStartupLogs(ctx context.Context, req agentsdk.PatchStartupLogs) error
}
@@ -156,8 +160,8 @@ type agent struct {
closed chan struct{}
envVars map[string]string
- // metadata is atomic because values can change after reconnection.
- metadata atomic.Value
+ // manifest is atomic because values can change after reconnection.
+ manifest atomic.Pointer[agentsdk.Manifest]
sessionToken atomic.Pointer[string]
sshServer *ssh.Server
sshMaxTimeout time.Duration
@@ -183,6 +187,7 @@ type agent struct {
// failure, you'll want the agent to reconnect.
func (a *agent) runLoop(ctx context.Context) {
go a.reportLifecycleLoop(ctx)
+ go a.reportMetadataLoop(ctx)
for retrier := retry.New(100*time.Millisecond, 10*time.Second); retrier.Wait(ctx); {
a.logger.Info(ctx, "connecting to coderd")
@@ -205,6 +210,168 @@ func (a *agent) runLoop(ctx context.Context) {
}
}
+func (a *agent) collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentMetadataDescription) *codersdk.WorkspaceAgentMetadataResult {
+ var out bytes.Buffer
+ result := &codersdk.WorkspaceAgentMetadataResult{
+ // CollectedAt is set here for testing purposes and overrode by
+ // the server to the time the server received the result to protect
+ // against clock skew.
+ //
+ // In the future, the server may accept the timestamp from the agent
+ // if it is certain the clocks are in sync.
+ CollectedAt: time.Now(),
+ }
+ cmd, err := a.createCommand(ctx, md.Script, nil)
+ if err != nil {
+ result.Error = err.Error()
+ return result
+ }
+
+ cmd.Stdout = &out
+ cmd.Stderr = &out
+
+ // The error isn't mutually exclusive with useful output.
+ err = cmd.Run()
+
+ const bufLimit = 10 << 10
+ if out.Len() > bufLimit {
+ err = errors.Join(
+ err,
+ xerrors.Errorf("output truncated from %v to %v bytes", out.Len(), bufLimit),
+ )
+ out.Truncate(bufLimit)
+ }
+
+ if err != nil {
+ result.Error = err.Error()
+ }
+ result.Value = out.String()
+ return result
+}
+
+func adjustIntervalForTests(i int64) time.Duration {
+ // In tests we want to set shorter intervals because engineers are
+ // impatient.
+ base := time.Second
+ if flag.Lookup("test.v") != nil {
+ base = time.Millisecond * 100
+ }
+ return time.Duration(i) * base
+}
+
+type metadataResultAndKey struct {
+ result *codersdk.WorkspaceAgentMetadataResult
+ key string
+}
+
+func (a *agent) reportMetadataLoop(ctx context.Context) {
+ baseInterval := adjustIntervalForTests(1)
+
+ const metadataLimit = 128
+
+ var (
+ baseTicker = time.NewTicker(baseInterval)
+ lastCollectedAts = make(map[string]time.Time)
+ metadataResults = make(chan metadataResultAndKey, metadataLimit)
+ )
+ defer baseTicker.Stop()
+
+ var flight singleflight.Group
+
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case mr := <-metadataResults:
+ lastCollectedAts[mr.key] = mr.result.CollectedAt
+ err := a.client.PostMetadata(ctx, mr.key, *mr.result)
+ if err != nil {
+ a.logger.Error(ctx, "report metadata", slog.Error(err))
+ }
+ case <-baseTicker.C:
+ }
+
+ if len(metadataResults) > 0 {
+ // The inner collection loop expects the channel is empty before spinning up
+ // all the collection goroutines.
+ a.logger.Debug(
+ ctx, "metadata collection backpressured",
+ slog.F("queue_len", len(metadataResults)),
+ )
+ continue
+ }
+
+ manifest := a.manifest.Load()
+ if manifest == nil {
+ continue
+ }
+
+ if len(manifest.Metadata) > metadataLimit {
+ a.logger.Error(
+ ctx, "metadata limit exceeded",
+ slog.F("limit", metadataLimit), slog.F("got", len(manifest.Metadata)),
+ )
+ continue
+ }
+
+ // If the manifest changes (e.g. on agent reconnect) we need to
+ // purge old cache values to prevent lastCollectedAt from growing
+ // boundlessly.
+ for key := range lastCollectedAts {
+ if slices.IndexFunc(manifest.Metadata, func(md codersdk.WorkspaceAgentMetadataDescription) bool {
+ return md.Key == key
+ }) < 0 {
+ delete(lastCollectedAts, key)
+ }
+ }
+
+ // Spawn a goroutine for each metadata collection, and use a
+ // channel to synchronize the results and avoid both messy
+ // mutex logic and overloading the API.
+ for _, md := range manifest.Metadata {
+ collectedAt, ok := lastCollectedAts[md.Key]
+ if ok {
+ // If the interval is zero, we assume the user just wants
+ // a single collection at startup, not a spinning loop.
+ if md.Interval == 0 {
+ continue
+ }
+ // The last collected value isn't quite stale yet, so we skip it.
+ if collectedAt.Add(
+ adjustIntervalForTests(md.Interval),
+ ).After(time.Now()) {
+ continue
+ }
+ }
+
+ md := md
+ // We send the result to the channel in the goroutine to avoid
+ // sending the same result multiple times. So, we don't care about
+ // the return values.
+ flight.DoChan(md.Key, func() (interface{}, error) {
+ timeout := md.Timeout
+ if timeout == 0 {
+ timeout = md.Interval
+ }
+ ctx, cancel := context.WithTimeout(ctx,
+ time.Duration(timeout)*time.Second,
+ )
+ defer cancel()
+
+ select {
+ case <-ctx.Done():
+ return 0, nil
+ case metadataResults <- metadataResultAndKey{
+ key: md.Key,
+ result: a.collectMetadata(ctx, md),
+ }:
+ }
+ return 0, nil
+ })
+ }
+ }
+}
+
// reportLifecycleLoop reports the current lifecycle state once.
// Only the latest state is reported, intermediate states may be
// lost if the agent can't communicate with the API.
@@ -279,40 +446,40 @@ func (a *agent) run(ctx context.Context) error {
}
a.sessionToken.Store(&sessionToken)
- metadata, err := a.client.Metadata(ctx)
+ manifest, err := a.client.Manifest(ctx)
if err != nil {
return xerrors.Errorf("fetch metadata: %w", err)
}
- a.logger.Info(ctx, "fetched metadata", slog.F("metadata", metadata))
+ a.logger.Info(ctx, "fetched manifest", slog.F("manifest", manifest))
// Expand the directory and send it back to coderd so external
// applications that rely on the directory can use it.
//
// An example is VS Code Remote, which must know the directory
// before initializing a connection.
- metadata.Directory, err = expandDirectory(metadata.Directory)
+ manifest.Directory, err = expandDirectory(manifest.Directory)
if err != nil {
return xerrors.Errorf("expand directory: %w", err)
}
err = a.client.PostStartup(ctx, agentsdk.PostStartupRequest{
Version: buildinfo.Version(),
- ExpandedDirectory: metadata.Directory,
+ ExpandedDirectory: manifest.Directory,
})
if err != nil {
return xerrors.Errorf("update workspace agent version: %w", err)
}
- oldMetadata := a.metadata.Swap(metadata)
+ oldManifest := a.manifest.Swap(&manifest)
// The startup script should only execute on the first run!
- if oldMetadata == nil {
+ if oldManifest == nil {
a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleStarting)
// Perform overrides early so that Git auth can work even if users
// connect to a workspace that is not yet ready. We don't run this
// concurrently with the startup script to avoid conflicts between
// them.
- if metadata.GitAuthConfigs > 0 {
+ if manifest.GitAuthConfigs > 0 {
// If this fails, we should consider surfacing the error in the
// startup log and setting the lifecycle state to be "start_error"
// (after startup script completion), but for now we'll just log it.
@@ -327,7 +494,7 @@ func (a *agent) run(ctx context.Context) error {
scriptStart := time.Now()
err = a.trackConnGoroutine(func() {
defer close(scriptDone)
- scriptDone <- a.runStartupScript(ctx, metadata.StartupScript)
+ scriptDone <- a.runStartupScript(ctx, manifest.StartupScript)
})
if err != nil {
return xerrors.Errorf("track startup script: %w", err)
@@ -336,8 +503,8 @@ func (a *agent) run(ctx context.Context) error {
var timeout <-chan time.Time
// If timeout is zero, an older version of the coder
// provider was used. Otherwise a timeout is always > 0.
- if metadata.StartupScriptTimeout > 0 {
- t := time.NewTimer(metadata.StartupScriptTimeout)
+ if manifest.StartupScriptTimeout > 0 {
+ t := time.NewTimer(manifest.StartupScriptTimeout)
defer t.Stop()
timeout = t.C
}
@@ -354,7 +521,7 @@ func (a *agent) run(ctx context.Context) error {
return
}
// Only log if there was a startup script.
- if metadata.StartupScript != "" {
+ if manifest.StartupScript != "" {
execTime := time.Since(scriptStart)
if err != nil {
a.logger.Warn(ctx, "startup script failed", slog.F("execution_time", execTime), slog.Error(err))
@@ -371,13 +538,13 @@ func (a *agent) run(ctx context.Context) error {
appReporterCtx, appReporterCtxCancel := context.WithCancel(ctx)
defer appReporterCtxCancel()
go NewWorkspaceAppHealthReporter(
- a.logger, metadata.Apps, a.client.PostAppHealth)(appReporterCtx)
+ a.logger, manifest.Apps, a.client.PostAppHealth)(appReporterCtx)
a.closeMutex.Lock()
network := a.network
a.closeMutex.Unlock()
if network == nil {
- network, err = a.createTailnet(ctx, metadata.DERPMap)
+ network, err = a.createTailnet(ctx, manifest.DERPMap)
if err != nil {
return xerrors.Errorf("create tailnet: %w", err)
}
@@ -396,7 +563,7 @@ func (a *agent) run(ctx context.Context) error {
a.startReportingConnectionStats(ctx)
} else {
// Update the DERP map!
- network.SetDERPMap(metadata.DERPMap)
+ network.SetDERPMap(manifest.DERPMap)
}
a.logger.Debug(ctx, "running tailnet connection coordinator")
@@ -926,9 +1093,9 @@ func (a *agent) init(ctx context.Context) {
}
// createCommand processes raw command input with OpenSSH-like behavior.
-// If the rawCommand provided is empty, it will default to the users shell.
+// If the script provided is empty, it will default to the users shell.
// This injects environment variables specified by the user at launch too.
-func (a *agent) createCommand(ctx context.Context, rawCommand string, env []string) (*exec.Cmd, error) {
+func (a *agent) createCommand(ctx context.Context, script string, env []string) (*exec.Cmd, error) {
currentUser, err := user.Current()
if err != nil {
return nil, xerrors.Errorf("get current user: %w", err)
@@ -940,14 +1107,10 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
return nil, xerrors.Errorf("get user shell: %w", err)
}
- rawMetadata := a.metadata.Load()
- if rawMetadata == nil {
+ manifest := a.manifest.Load()
+ if manifest == nil {
return nil, xerrors.Errorf("no metadata was provided")
}
- metadata, valid := rawMetadata.(agentsdk.Metadata)
- if !valid {
- return nil, xerrors.Errorf("metadata is the wrong type: %T", metadata)
- }
// OpenSSH executes all commands with the users current shell.
// We replicate that behavior for IDE support.
@@ -955,11 +1118,11 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
if runtime.GOOS == "windows" {
caller = "/c"
}
- args := []string{caller, rawCommand}
+ args := []string{caller, script}
// gliderlabs/ssh returns a command slice of zero
// when a shell is requested.
- if len(rawCommand) == 0 {
+ if len(script) == 0 {
args = []string{}
if runtime.GOOS != "windows" {
// On Linux and macOS, we should start a login
@@ -969,7 +1132,7 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
}
cmd := exec.CommandContext(ctx, shell, args...)
- cmd.Dir = metadata.Directory
+ cmd.Dir = manifest.Directory
// If the metadata directory doesn't exist, we run the command
// in the users home directory.
@@ -1010,14 +1173,14 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
// This adds the ports dialog to code-server that enables
// proxying a port dynamically.
- cmd.Env = append(cmd.Env, fmt.Sprintf("VSCODE_PROXY_URI=%s", metadata.VSCodePortProxyURI))
+ cmd.Env = append(cmd.Env, fmt.Sprintf("VSCODE_PROXY_URI=%s", manifest.VSCodePortProxyURI))
// Hide Coder message on code-server's "Getting Started" page
cmd.Env = append(cmd.Env, "CS_DISABLE_GETTING_STARTED_OVERRIDE=true")
// Load environment variables passed via the agent.
// These should override all variables we manually specify.
- for envKey, value := range metadata.EnvironmentVariables {
+ for envKey, value := range manifest.EnvironmentVariables {
// Expanding environment variables allows for customization
// of the $PATH, among other variables. Customers can prepend
// or append to the $PATH, so allowing expand is required!
@@ -1080,9 +1243,9 @@ func (a *agent) handleSSHSession(session ssh.Session) (retErr error) {
session.DisablePTYEmulation()
if !isQuietLogin(session.RawCommand()) {
- metadata, ok := a.metadata.Load().(agentsdk.Metadata)
- if ok {
- err = showMOTD(session, metadata.MOTDFile)
+ manifest := a.manifest.Load()
+ if manifest != nil {
+ err = showMOTD(session, manifest.MOTDFile)
if err != nil {
a.logger.Error(ctx, "show MOTD", slog.Error(err))
}
@@ -1512,19 +1675,19 @@ func (a *agent) Close() error {
a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleShuttingDown)
lifecycleState := codersdk.WorkspaceAgentLifecycleOff
- if metadata, ok := a.metadata.Load().(agentsdk.Metadata); ok && metadata.ShutdownScript != "" {
+ if manifest := a.manifest.Load(); manifest != nil && manifest.ShutdownScript != "" {
scriptDone := make(chan error, 1)
scriptStart := time.Now()
go func() {
defer close(scriptDone)
- scriptDone <- a.runShutdownScript(ctx, metadata.ShutdownScript)
+ scriptDone <- a.runShutdownScript(ctx, manifest.ShutdownScript)
}()
var timeout <-chan time.Time
// If timeout is zero, an older version of the coder
// provider was used. Otherwise a timeout is always > 0.
- if metadata.ShutdownScriptTimeout > 0 {
- t := time.NewTimer(metadata.ShutdownScriptTimeout)
+ if manifest.ShutdownScriptTimeout > 0 {
+ t := time.NewTimer(manifest.ShutdownScriptTimeout)
defer t.Stop()
timeout = t.C
}
diff --git a/agent/agent_test.go b/agent/agent_test.go
index a147d27815f6a..ec76aa1b0b6b9 100644
--- a/agent/agent_test.go
+++ b/agent/agent_test.go
@@ -33,6 +33,7 @@ import (
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"golang.org/x/crypto/ssh"
+ "golang.org/x/exp/maps"
"golang.org/x/xerrors"
"tailscale.com/net/speedtest"
"tailscale.com/tailcfg"
@@ -61,7 +62,7 @@ func TestAgent_Stats_SSH(t *testing.T) {
defer cancel()
//nolint:dogsled
- conn, _, stats, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
+ conn, _, stats, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
@@ -94,7 +95,7 @@ func TestAgent_Stats_ReconnectingPTY(t *testing.T) {
defer cancel()
//nolint:dogsled
- conn, _, stats, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
+ conn, _, stats, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
ptyConn, err := conn.ReconnectingPTY(ctx, uuid.New(), 128, 128, "/bin/bash")
require.NoError(t, err)
@@ -124,7 +125,7 @@ func TestAgent_Stats_Magic(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
//nolint:dogsled
- conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
+ conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
@@ -151,7 +152,7 @@ func TestAgent_Stats_Magic(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
//nolint:dogsled
- conn, _, stats, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
+ conn, _, stats, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
@@ -186,7 +187,7 @@ func TestAgent_Stats_Magic(t *testing.T) {
func TestAgent_SessionExec(t *testing.T) {
t.Parallel()
- session := setupSSHSession(t, agentsdk.Metadata{})
+ session := setupSSHSession(t, agentsdk.Manifest{})
command := "echo test"
if runtime.GOOS == "windows" {
@@ -199,7 +200,7 @@ func TestAgent_SessionExec(t *testing.T) {
func TestAgent_GitSSH(t *testing.T) {
t.Parallel()
- session := setupSSHSession(t, agentsdk.Metadata{})
+ session := setupSSHSession(t, agentsdk.Manifest{})
command := "sh -c 'echo $GIT_SSH_COMMAND'"
if runtime.GOOS == "windows" {
command = "cmd.exe /c echo %GIT_SSH_COMMAND%"
@@ -219,7 +220,7 @@ func TestAgent_SessionTTYShell(t *testing.T) {
// it seems like it could be either.
t.Skip("ConPTY appears to be inconsistent on Windows.")
}
- session := setupSSHSession(t, agentsdk.Metadata{})
+ session := setupSSHSession(t, agentsdk.Manifest{})
command := "sh"
if runtime.GOOS == "windows" {
command = "cmd.exe"
@@ -242,7 +243,7 @@ func TestAgent_SessionTTYShell(t *testing.T) {
func TestAgent_SessionTTYExitCode(t *testing.T) {
t.Parallel()
- session := setupSSHSession(t, agentsdk.Metadata{})
+ session := setupSSHSession(t, agentsdk.Manifest{})
command := "areallynotrealcommand"
err := session.RequestPty("xterm", 128, 128, ssh.TerminalModes{})
require.NoError(t, err)
@@ -281,7 +282,7 @@ func TestAgent_Session_TTY_MOTD(t *testing.T) {
// Set HOME so we can ensure no ~/.hushlogin is present.
t.Setenv("HOME", tmpdir)
- session := setupSSHSession(t, agentsdk.Metadata{
+ session := setupSSHSession(t, agentsdk.Manifest{
MOTDFile: name,
})
err = session.RequestPty("xterm", 128, 128, ssh.TerminalModes{})
@@ -327,7 +328,7 @@ func TestAgent_Session_TTY_Hushlogin(t *testing.T) {
// Set HOME so we can ensure ~/.hushlogin is present.
t.Setenv("HOME", tmpdir)
- session := setupSSHSession(t, agentsdk.Metadata{
+ session := setupSSHSession(t, agentsdk.Manifest{
MOTDFile: name,
})
err = session.RequestPty("xterm", 128, 128, ssh.TerminalModes{})
@@ -357,7 +358,7 @@ func TestAgent_Session_TTY_FastCommandHasOutput(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
//nolint:dogsled
- conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
+ conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
@@ -407,7 +408,7 @@ func TestAgent_Session_TTY_HugeOutputIsNotLost(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
//nolint:dogsled
- conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
+ conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
@@ -706,7 +707,7 @@ func TestAgent_SFTP(t *testing.T) {
home = "/" + strings.ReplaceAll(home, "\\", "/")
}
//nolint:dogsled
- conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
+ conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
@@ -738,7 +739,7 @@ func TestAgent_SCP(t *testing.T) {
defer cancel()
//nolint:dogsled
- conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
+ conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
@@ -757,7 +758,7 @@ func TestAgent_EnvironmentVariables(t *testing.T) {
t.Parallel()
key := "EXAMPLE"
value := "value"
- session := setupSSHSession(t, agentsdk.Metadata{
+ session := setupSSHSession(t, agentsdk.Manifest{
EnvironmentVariables: map[string]string{
key: value,
},
@@ -774,7 +775,7 @@ func TestAgent_EnvironmentVariables(t *testing.T) {
func TestAgent_EnvironmentVariableExpansion(t *testing.T) {
t.Parallel()
key := "EXAMPLE"
- session := setupSSHSession(t, agentsdk.Metadata{
+ session := setupSSHSession(t, agentsdk.Manifest{
EnvironmentVariables: map[string]string{
key: "$SOMETHINGNOTSET",
},
@@ -801,7 +802,7 @@ func TestAgent_CoderEnvVars(t *testing.T) {
t.Run(key, func(t *testing.T) {
t.Parallel()
- session := setupSSHSession(t, agentsdk.Metadata{})
+ session := setupSSHSession(t, agentsdk.Manifest{})
command := "sh -c 'echo $" + key + "'"
if runtime.GOOS == "windows" {
command = "cmd.exe /c echo %" + key + "%"
@@ -824,7 +825,7 @@ func TestAgent_SSHConnectionEnvVars(t *testing.T) {
t.Run(key, func(t *testing.T) {
t.Parallel()
- session := setupSSHSession(t, agentsdk.Metadata{})
+ session := setupSSHSession(t, agentsdk.Manifest{})
command := "sh -c 'echo $" + key + "'"
if runtime.GOOS == "windows" {
command = "cmd.exe /c echo %" + key + "%"
@@ -848,7 +849,7 @@ func TestAgent_StartupScript(t *testing.T) {
client := &client{
t: t,
agentID: uuid.New(),
- metadata: agentsdk.Metadata{
+ manifest: agentsdk.Manifest{
StartupScript: command,
DERPMap: &tailcfg.DERPMap{},
},
@@ -879,7 +880,7 @@ func TestAgent_StartupScript(t *testing.T) {
client := &client{
t: t,
agentID: uuid.New(),
- metadata: agentsdk.Metadata{
+ manifest: agentsdk.Manifest{
StartupScript: command,
DERPMap: &tailcfg.DERPMap{},
},
@@ -912,13 +913,125 @@ func TestAgent_StartupScript(t *testing.T) {
})
}
+func TestAgent_Metadata(t *testing.T) {
+ t.Parallel()
+
+ t.Run("Once", func(t *testing.T) {
+ t.Parallel()
+ script := "echo -n hello"
+ if runtime.GOOS == "windows" {
+ script = "powershell " + script
+ }
+ //nolint:dogsled
+ _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{
+ Metadata: []codersdk.WorkspaceAgentMetadataDescription{
+ {
+ Key: "greeting",
+ Interval: 0,
+ Script: script,
+ },
+ },
+ }, 0)
+
+ var gotMd map[string]agentsdk.PostMetadataRequest
+ require.Eventually(t, func() bool {
+ gotMd = client.getMetadata()
+ return len(gotMd) == 1
+ }, testutil.WaitShort, testutil.IntervalMedium)
+
+ collectedAt := gotMd["greeting"].CollectedAt
+
+ require.Never(t, func() bool {
+ gotMd = client.getMetadata()
+ if len(gotMd) != 1 {
+ panic("unexpected number of metadata")
+ }
+ return !gotMd["greeting"].CollectedAt.Equal(collectedAt)
+ }, testutil.WaitShort, testutil.IntervalMedium)
+ })
+
+ t.Run("Many", func(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ // Shell scripting in Windows is a pain, and we have already tested
+ // that the OS logic works in the simpler "Once" test above.
+ t.Skip()
+ }
+ t.Parallel()
+
+ dir := t.TempDir()
+
+ const reportInterval = 2
+ const intervalUnit = 100 * time.Millisecond
+ var (
+ greetingPath = filepath.Join(dir, "greeting")
+ script = "echo hello | tee -a " + greetingPath
+ )
+ _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{
+ Metadata: []codersdk.WorkspaceAgentMetadataDescription{
+ {
+ Key: "greeting",
+ Interval: reportInterval,
+ Script: script,
+ },
+ {
+ Key: "bad",
+ Interval: reportInterval,
+ Script: "exit 1",
+ },
+ },
+ }, 0)
+
+ require.Eventually(t, func() bool {
+ return len(client.getMetadata()) == 2
+ }, testutil.WaitShort, testutil.IntervalMedium)
+
+ for start := time.Now(); time.Since(start) < testutil.WaitMedium; time.Sleep(testutil.IntervalMedium) {
+ md := client.getMetadata()
+ if len(md) != 2 {
+ panic("unexpected number of metadata entries")
+ }
+
+ require.Equal(t, "hello\n", md["greeting"].Value)
+ require.Equal(t, "exit status 1", md["bad"].Error)
+
+ greetingByt, err := os.ReadFile(greetingPath)
+ require.NoError(t, err)
+
+ var (
+ numGreetings = bytes.Count(greetingByt, []byte("hello"))
+ idealNumGreetings = time.Since(start) / (reportInterval * intervalUnit)
+ // We allow a 50% error margin because the report loop may backlog
+ // in CI and other toasters. In production, there is no hard
+ // guarantee on timing either, and the frontend gives similar
+ // wiggle room to the staleness of the value.
+ upperBound = int(idealNumGreetings) + 1
+ lowerBound = (int(idealNumGreetings) / 2)
+ )
+
+ if idealNumGreetings < 50 {
+ // There is an insufficient sample size.
+ continue
+ }
+
+ t.Logf("numGreetings: %d, idealNumGreetings: %d", numGreetings, idealNumGreetings)
+ // The report loop may slow down on load, but it should never, ever
+ // speed up.
+ if numGreetings > upperBound {
+ t.Fatalf("too many greetings: %d > %d in %v", numGreetings, upperBound, time.Since(start))
+ } else if numGreetings < lowerBound {
+ t.Fatalf("too few greetings: %d < %d", numGreetings, lowerBound)
+ }
+ }
+ })
+}
+
func TestAgent_Lifecycle(t *testing.T) {
t.Parallel()
t.Run("StartTimeout", func(t *testing.T) {
t.Parallel()
- _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
+ _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{
StartupScript: "sleep 5",
StartupScriptTimeout: time.Nanosecond,
}, 0)
@@ -947,7 +1060,7 @@ func TestAgent_Lifecycle(t *testing.T) {
t.Run("StartError", func(t *testing.T) {
t.Parallel()
- _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
+ _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{
StartupScript: "false",
StartupScriptTimeout: 30 * time.Second,
}, 0)
@@ -976,7 +1089,7 @@ func TestAgent_Lifecycle(t *testing.T) {
t.Run("Ready", func(t *testing.T) {
t.Parallel()
- _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
+ _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{
StartupScript: "true",
StartupScriptTimeout: 30 * time.Second,
}, 0)
@@ -1005,7 +1118,7 @@ func TestAgent_Lifecycle(t *testing.T) {
t.Run("ShuttingDown", func(t *testing.T) {
t.Parallel()
- _, client, _, _, closer := setupAgent(t, agentsdk.Metadata{
+ _, client, _, _, closer := setupAgent(t, agentsdk.Manifest{
ShutdownScript: "sleep 5",
StartupScriptTimeout: 30 * time.Second,
}, 0)
@@ -1043,7 +1156,7 @@ func TestAgent_Lifecycle(t *testing.T) {
t.Run("ShutdownTimeout", func(t *testing.T) {
t.Parallel()
- _, client, _, _, closer := setupAgent(t, agentsdk.Metadata{
+ _, client, _, _, closer := setupAgent(t, agentsdk.Manifest{
ShutdownScript: "sleep 5",
ShutdownScriptTimeout: time.Nanosecond,
}, 0)
@@ -1090,7 +1203,7 @@ func TestAgent_Lifecycle(t *testing.T) {
t.Run("ShutdownError", func(t *testing.T) {
t.Parallel()
- _, client, _, _, closer := setupAgent(t, agentsdk.Metadata{
+ _, client, _, _, closer := setupAgent(t, agentsdk.Manifest{
ShutdownScript: "false",
ShutdownScriptTimeout: 30 * time.Second,
}, 0)
@@ -1141,7 +1254,7 @@ func TestAgent_Lifecycle(t *testing.T) {
client := &client{
t: t,
agentID: uuid.New(),
- metadata: agentsdk.Metadata{
+ manifest: agentsdk.Manifest{
DERPMap: tailnettest.RunDERPAndSTUN(t),
StartupScript: "echo 1",
ShutdownScript: "echo " + expected,
@@ -1194,7 +1307,7 @@ func TestAgent_Startup(t *testing.T) {
t.Run("EmptyDirectory", func(t *testing.T) {
t.Parallel()
- _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
+ _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{
StartupScript: "true",
StartupScriptTimeout: 30 * time.Second,
Directory: "",
@@ -1208,7 +1321,7 @@ func TestAgent_Startup(t *testing.T) {
t.Run("HomeDirectory", func(t *testing.T) {
t.Parallel()
- _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
+ _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{
StartupScript: "true",
StartupScriptTimeout: 30 * time.Second,
Directory: "~",
@@ -1224,7 +1337,7 @@ func TestAgent_Startup(t *testing.T) {
t.Run("HomeEnvironmentVariable", func(t *testing.T) {
t.Parallel()
- _, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
+ _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{
StartupScript: "true",
StartupScriptTimeout: 30 * time.Second,
Directory: "$HOME",
@@ -1251,7 +1364,7 @@ func TestAgent_ReconnectingPTY(t *testing.T) {
defer cancel()
//nolint:dogsled
- conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
+ conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
id := uuid.New()
netConn, err := conn.ReconnectingPTY(ctx, id, 100, 100, "/bin/bash")
require.NoError(t, err)
@@ -1353,7 +1466,7 @@ func TestAgent_Dial(t *testing.T) {
}()
//nolint:dogsled
- conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
+ conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
require.True(t, conn.AwaitReachable(context.Background()))
conn1, err := conn.DialContext(context.Background(), l.Addr().Network(), l.Addr().String())
require.NoError(t, err)
@@ -1375,7 +1488,7 @@ func TestAgent_Speedtest(t *testing.T) {
defer cancel()
derpMap := tailnettest.RunDERPAndSTUN(t)
//nolint:dogsled
- conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{
+ conn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{
DERPMap: derpMap,
}, 0)
defer conn.Close()
@@ -1397,7 +1510,7 @@ func TestAgent_Reconnect(t *testing.T) {
client := &client{
t: t,
agentID: agentID,
- metadata: agentsdk.Metadata{
+ manifest: agentsdk.Manifest{
DERPMap: derpMap,
},
statsChan: statsCh,
@@ -1432,7 +1545,7 @@ func TestAgent_WriteVSCodeConfigs(t *testing.T) {
client := &client{
t: t,
agentID: uuid.New(),
- metadata: agentsdk.Metadata{
+ manifest: agentsdk.Manifest{
GitAuthConfigs: 1,
DERPMap: &tailcfg.DERPMap{},
},
@@ -1461,7 +1574,7 @@ func TestAgent_WriteVSCodeConfigs(t *testing.T) {
func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exec.Cmd {
//nolint:dogsled
- agentConn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
+ agentConn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
waitGroup := sync.WaitGroup{}
@@ -1504,7 +1617,7 @@ func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exe
return exec.Command("ssh", args...)
}
-func setupSSHSession(t *testing.T, options agentsdk.Metadata) *ssh.Session {
+func setupSSHSession(t *testing.T, options agentsdk.Manifest) *ssh.Session {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
//nolint:dogsled
@@ -1528,7 +1641,7 @@ func (c closeFunc) Close() error {
return c()
}
-func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Duration) (
+func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Duration) (
*codersdk.WorkspaceAgentConn,
*client,
<-chan *agentsdk.Stats,
@@ -1548,7 +1661,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Durati
c := &client{
t: t,
agentID: agentID,
- metadata: metadata,
+ manifest: metadata,
statsChan: statsCh,
coordinator: coordinator,
}
@@ -1631,7 +1744,8 @@ func assertWritePayload(t *testing.T, w io.Writer, payload []byte) {
type client struct {
t *testing.T
agentID uuid.UUID
- metadata agentsdk.Metadata
+ manifest agentsdk.Manifest
+ metadata map[string]agentsdk.PostMetadataRequest
statsChan chan *agentsdk.Stats
coordinator tailnet.Coordinator
lastWorkspaceAgent func()
@@ -1643,8 +1757,8 @@ type client struct {
logs []agentsdk.StartupLog
}
-func (c *client) Metadata(_ context.Context) (agentsdk.Metadata, error) {
- return c.metadata, nil
+func (c *client) Manifest(_ context.Context) (agentsdk.Manifest, error) {
+ return c.manifest, nil
}
func (c *client) Listen(_ context.Context) (net.Conn, error) {
@@ -1718,6 +1832,22 @@ func (c *client) getStartup() agentsdk.PostStartupRequest {
return c.startup
}
+func (c *client) getMetadata() map[string]agentsdk.PostMetadataRequest {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ return maps.Clone(c.metadata)
+}
+
+func (c *client) PostMetadata(_ context.Context, key string, req agentsdk.PostMetadataRequest) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if c.metadata == nil {
+ c.metadata = make(map[string]agentsdk.PostMetadataRequest)
+ }
+ c.metadata[key] = req
+ return nil
+}
+
func (c *client) PostStartup(_ context.Context, startup agentsdk.PostStartupRequest) error {
c.mu.Lock()
defer c.mu.Unlock()
diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go
index 9561e0b511a6d..e18f37d5348d4 100644
--- a/coderd/apidoc/docs.go
+++ b/coderd/apidoc/docs.go
@@ -4263,7 +4263,7 @@ const docTemplate = `{
}
}
},
- "/workspaceagents/me/metadata": {
+ "/workspaceagents/me/manifest": {
"get": {
"security": [
{
@@ -4276,18 +4276,62 @@ const docTemplate = `{
"tags": [
"Agents"
],
- "summary": "Get authorized workspace agent metadata",
- "operationId": "get-authorized-workspace-agent-metadata",
+ "summary": "Get authorized workspace agent manifest",
+ "operationId": "get-authorized-workspace-agent-manifest",
"responses": {
"200": {
"description": "OK",
"schema": {
- "$ref": "#/definitions/agentsdk.Metadata"
+ "$ref": "#/definitions/agentsdk.Manifest"
}
}
}
}
},
+ "/workspaceagents/me/metadata/{key}": {
+ "post": {
+ "security": [
+ {
+ "CoderSessionToken": []
+ }
+ ],
+ "consumes": [
+ "application/json"
+ ],
+ "tags": [
+ "Agents"
+ ],
+ "summary": "Submit workspace agent metadata",
+ "operationId": "submit-workspace-agent-metadata",
+ "parameters": [
+ {
+ "description": "Workspace agent metadata request",
+ "name": "request",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/agentsdk.PostMetadataRequest"
+ }
+ },
+ {
+ "type": "string",
+ "format": "string",
+ "description": "metadata key",
+ "name": "key",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Success"
+ }
+ },
+ "x-apidocgen": {
+ "skip": true
+ }
+ }
+ },
"/workspaceagents/me/report-lifecycle": {
"post": {
"security": [
@@ -4663,6 +4707,38 @@ const docTemplate = `{
}
}
},
+ "/workspaceagents/{workspaceagent}/watch-metadata": {
+ "get": {
+ "security": [
+ {
+ "CoderSessionToken": []
+ }
+ ],
+ "tags": [
+ "Agents"
+ ],
+ "summary": "Watch for workspace agent metadata updates",
+ "operationId": "watch-for-workspace-agent-metadata-updates",
+ "parameters": [
+ {
+ "type": "string",
+ "format": "uuid",
+ "description": "Workspace agent ID",
+ "name": "workspaceagent",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success"
+ }
+ },
+ "x-apidocgen": {
+ "skip": true
+ }
+ }
+ },
"/workspacebuilds/{workspacebuild}": {
"get": {
"security": [
@@ -5397,7 +5473,7 @@ const docTemplate = `{
}
}
},
- "agentsdk.Metadata": {
+ "agentsdk.Manifest": {
"type": "object",
"properties": {
"apps": {
@@ -5422,6 +5498,12 @@ const docTemplate = `{
"description": "GitAuthConfigs stores the number of Git configurations\nthe Coder deployment has. If this number is \u003e0, we\nset up special configuration in the workspace.",
"type": "integer"
},
+ "metadata": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/codersdk.WorkspaceAgentMetadataDescription"
+ }
+ },
"motd_file": {
"type": "string"
},
@@ -5473,6 +5555,25 @@ const docTemplate = `{
}
}
},
+ "agentsdk.PostMetadataRequest": {
+ "type": "object",
+ "properties": {
+ "age": {
+ "description": "Age is the number of seconds since the metadata was collected.\nIt is provided in addition to CollectedAt to protect against clock skew.",
+ "type": "integer"
+ },
+ "collected_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "error": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ }
+ },
"agentsdk.PostStartupRequest": {
"type": "object",
"properties": {
@@ -8915,6 +9016,26 @@ const docTemplate = `{
}
}
},
+ "codersdk.WorkspaceAgentMetadataDescription": {
+ "type": "object",
+ "properties": {
+ "display_name": {
+ "type": "string"
+ },
+ "interval": {
+ "type": "integer"
+ },
+ "key": {
+ "type": "string"
+ },
+ "script": {
+ "type": "string"
+ },
+ "timeout": {
+ "type": "integer"
+ }
+ }
+ },
"codersdk.WorkspaceAgentStartupLog": {
"type": "object",
"properties": {
diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json
index fd9c8a7e8a3ff..da7128998340f 100644
--- a/coderd/apidoc/swagger.json
+++ b/coderd/apidoc/swagger.json
@@ -3747,7 +3747,7 @@
}
}
},
- "/workspaceagents/me/metadata": {
+ "/workspaceagents/me/manifest": {
"get": {
"security": [
{
@@ -3756,18 +3756,58 @@
],
"produces": ["application/json"],
"tags": ["Agents"],
- "summary": "Get authorized workspace agent metadata",
- "operationId": "get-authorized-workspace-agent-metadata",
+ "summary": "Get authorized workspace agent manifest",
+ "operationId": "get-authorized-workspace-agent-manifest",
"responses": {
"200": {
"description": "OK",
"schema": {
- "$ref": "#/definitions/agentsdk.Metadata"
+ "$ref": "#/definitions/agentsdk.Manifest"
}
}
}
}
},
+ "/workspaceagents/me/metadata/{key}": {
+ "post": {
+ "security": [
+ {
+ "CoderSessionToken": []
+ }
+ ],
+ "consumes": ["application/json"],
+ "tags": ["Agents"],
+ "summary": "Submit workspace agent metadata",
+ "operationId": "submit-workspace-agent-metadata",
+ "parameters": [
+ {
+ "description": "Workspace agent metadata request",
+ "name": "request",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/agentsdk.PostMetadataRequest"
+ }
+ },
+ {
+ "type": "string",
+ "format": "string",
+ "description": "metadata key",
+ "name": "key",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Success"
+ }
+ },
+ "x-apidocgen": {
+ "skip": true
+ }
+ }
+ },
"/workspaceagents/me/report-lifecycle": {
"post": {
"security": [
@@ -4101,6 +4141,36 @@
}
}
},
+ "/workspaceagents/{workspaceagent}/watch-metadata": {
+ "get": {
+ "security": [
+ {
+ "CoderSessionToken": []
+ }
+ ],
+ "tags": ["Agents"],
+ "summary": "Watch for workspace agent metadata updates",
+ "operationId": "watch-for-workspace-agent-metadata-updates",
+ "parameters": [
+ {
+ "type": "string",
+ "format": "uuid",
+ "description": "Workspace agent ID",
+ "name": "workspaceagent",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success"
+ }
+ },
+ "x-apidocgen": {
+ "skip": true
+ }
+ }
+ },
"/workspacebuilds/{workspacebuild}": {
"get": {
"security": [
@@ -4758,7 +4828,7 @@
}
}
},
- "agentsdk.Metadata": {
+ "agentsdk.Manifest": {
"type": "object",
"properties": {
"apps": {
@@ -4783,6 +4853,12 @@
"description": "GitAuthConfigs stores the number of Git configurations\nthe Coder deployment has. If this number is \u003e0, we\nset up special configuration in the workspace.",
"type": "integer"
},
+ "metadata": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/codersdk.WorkspaceAgentMetadataDescription"
+ }
+ },
"motd_file": {
"type": "string"
},
@@ -4834,6 +4910,25 @@
}
}
},
+ "agentsdk.PostMetadataRequest": {
+ "type": "object",
+ "properties": {
+ "age": {
+ "description": "Age is the number of seconds since the metadata was collected.\nIt is provided in addition to CollectedAt to protect against clock skew.",
+ "type": "integer"
+ },
+ "collected_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "error": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ }
+ }
+ },
"agentsdk.PostStartupRequest": {
"type": "object",
"properties": {
@@ -8043,6 +8138,26 @@
}
}
},
+ "codersdk.WorkspaceAgentMetadataDescription": {
+ "type": "object",
+ "properties": {
+ "display_name": {
+ "type": "string"
+ },
+ "interval": {
+ "type": "integer"
+ },
+ "key": {
+ "type": "string"
+ },
+ "script": {
+ "type": "string"
+ },
+ "timeout": {
+ "type": "integer"
+ }
+ }
+ },
"codersdk.WorkspaceAgentStartupLog": {
"type": "object",
"properties": {
diff --git a/coderd/coderd.go b/coderd/coderd.go
index 8fd68ba47e82a..9573d9b3cf889 100644
--- a/coderd/coderd.go
+++ b/coderd/coderd.go
@@ -608,7 +608,10 @@ func New(options *Options) *API {
r.Post("/google-instance-identity", api.postWorkspaceAuthGoogleInstanceIdentity)
r.Route("/me", func(r chi.Router) {
r.Use(httpmw.ExtractWorkspaceAgent(options.Database))
- r.Get("/metadata", api.workspaceAgentMetadata)
+ r.Get("/manifest", api.workspaceAgentManifest)
+ // This route is deprecated and will be removed in a future release.
+ // New agents will use /me/manifest instead.
+ r.Get("/metadata", api.workspaceAgentManifest)
r.Post("/startup", api.postWorkspaceAgentStartup)
r.Patch("/startup-logs", api.patchWorkspaceAgentStartupLogs)
r.Post("/app-health", api.postWorkspaceAppHealth)
@@ -617,6 +620,7 @@ func New(options *Options) *API {
r.Get("/coordinate", api.workspaceAgentCoordinate)
r.Post("/report-stats", api.workspaceAgentReportStats)
r.Post("/report-lifecycle", api.workspaceAgentReportLifecycle)
+ r.Post("/metadata/{key}", api.workspaceAgentPostMetadata)
})
// No middleware on the PTY endpoint since it uses workspace
// application auth and tickets.
@@ -628,6 +632,8 @@ func New(options *Options) *API {
httpmw.ExtractWorkspaceParam(options.Database),
)
r.Get("/", api.workspaceAgent)
+ r.Get("/watch-metadata", api.watchWorkspaceAgentMetadata)
+ r.Get("/pty", api.workspaceAgentPTY)
r.Get("/startup-logs", api.workspaceAgentStartupLogs)
r.Get("/listening-ports", api.workspaceAgentListeningPorts)
r.Get("/connection", api.workspaceAgentConnection)
diff --git a/coderd/coderdtest/swaggerparser.go b/coderd/coderdtest/swaggerparser.go
index dda80d2f40800..be70b0379d6b0 100644
--- a/coderd/coderdtest/swaggerparser.go
+++ b/coderd/coderdtest/swaggerparser.go
@@ -160,6 +160,11 @@ func VerifySwaggerDefinitions(t *testing.T, router chi.Router, swaggerComments [
t.Run(method+" "+route, func(t *testing.T) {
t.Parallel()
+ // This route is for compatibility purposes and is not documented.
+ if route == "/workspaceagents/me/metadata" {
+ return
+ }
+
c := findSwaggerCommentByMethodAndRoute(swaggerComments, method, route)
assert.NotNil(t, c, "Missing @Router annotation")
if c == nil {
diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go
index a55ed6ba95d8f..13699e28f7cd8 100644
--- a/coderd/database/dbauthz/querier.go
+++ b/coderd/database/dbauthz/querier.go
@@ -1564,6 +1564,44 @@ func (q *querier) InsertWorkspaceAgentStat(ctx context.Context, arg database.Ins
return q.db.InsertWorkspaceAgentStat(ctx, arg)
}
+func (q *querier) InsertWorkspaceAgentMetadata(ctx context.Context, arg database.InsertWorkspaceAgentMetadataParams) error {
+ // We don't check for workspace ownership here since the agent metadata may
+ // be associated with an orphaned agent used by a dry run build.
+ if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil {
+ return err
+ }
+
+ return q.db.InsertWorkspaceAgentMetadata(ctx, arg)
+}
+
+func (q *querier) UpdateWorkspaceAgentMetadata(ctx context.Context, arg database.UpdateWorkspaceAgentMetadataParams) error {
+ workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.WorkspaceAgentID)
+ if err != nil {
+ return err
+ }
+
+ err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace)
+ if err != nil {
+ return err
+ }
+
+ return q.db.UpdateWorkspaceAgentMetadata(ctx, arg)
+}
+
+func (q *querier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) {
+ workspace, err := q.db.GetWorkspaceByAgentID(ctx, workspaceAgentID)
+ if err != nil {
+ return nil, err
+ }
+
+ err = q.authorizeContext(ctx, rbac.ActionRead, workspace)
+ if err != nil {
+ return nil, err
+ }
+
+ return q.db.GetWorkspaceAgentMetadata(ctx, workspaceAgentID)
+}
+
func (q *querier) UpdateWorkspaceAppHealthByID(ctx context.Context, arg database.UpdateWorkspaceAppHealthByIDParams) error {
// TODO: This is a workspace agent operation. Should users be able to query this?
workspace, err := q.db.GetWorkspaceByWorkspaceAppID(ctx, arg.ID)
diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go
index 351707748b3c9..5822b15a727eb 100644
--- a/coderd/database/dbfake/databasefake.go
+++ b/coderd/database/dbfake/databasefake.go
@@ -124,6 +124,7 @@ type data struct {
templateVersionVariables []database.TemplateVersionVariable
templates []database.Template
workspaceAgents []database.WorkspaceAgent
+ workspaceAgentMetadata []database.WorkspaceAgentMetadatum
workspaceAgentLogs []database.WorkspaceAgentStartupLog
workspaceApps []database.WorkspaceApp
workspaceBuilds []database.WorkspaceBuild
@@ -2741,6 +2742,60 @@ func (q *fakeQuerier) InsertAPIKey(_ context.Context, arg database.InsertAPIKeyP
return key, nil
}
+func (q *fakeQuerier) UpdateWorkspaceAgentMetadata(_ context.Context, arg database.UpdateWorkspaceAgentMetadataParams) error {
+ q.mutex.Lock()
+ defer q.mutex.Unlock()
+
+ //nolint:gosimple
+ updated := database.WorkspaceAgentMetadatum{
+ WorkspaceAgentID: arg.WorkspaceAgentID,
+ Key: arg.Key,
+ Value: arg.Value,
+ Error: arg.Error,
+ CollectedAt: arg.CollectedAt,
+ }
+
+ for i, m := range q.workspaceAgentMetadata {
+ if m.WorkspaceAgentID == arg.WorkspaceAgentID && m.Key == arg.Key {
+ q.workspaceAgentMetadata[i] = updated
+ return nil
+ }
+ }
+
+ return nil
+}
+
+func (q *fakeQuerier) InsertWorkspaceAgentMetadata(_ context.Context, arg database.InsertWorkspaceAgentMetadataParams) error {
+ q.mutex.Lock()
+ defer q.mutex.Unlock()
+
+ //nolint:gosimple
+ metadatum := database.WorkspaceAgentMetadatum{
+ WorkspaceAgentID: arg.WorkspaceAgentID,
+ Script: arg.Script,
+ DisplayName: arg.DisplayName,
+ Key: arg.Key,
+ Timeout: arg.Timeout,
+ Interval: arg.Interval,
+ }
+
+ q.workspaceAgentMetadata = append(q.workspaceAgentMetadata, metadatum)
+ return nil
+}
+
+func (q *fakeQuerier) GetWorkspaceAgentMetadata(_ context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) {
+ q.mutex.RLock()
+ defer q.mutex.RUnlock()
+
+ metadata := make([]database.WorkspaceAgentMetadatum, 0)
+ for _, m := range q.workspaceAgentMetadata {
+ if m.WorkspaceAgentID == workspaceAgentID {
+ metadata = append(metadata, m)
+ }
+ }
+ return metadata, nil
+}
+
func (q *fakeQuerier) InsertFile(_ context.Context, arg database.InsertFileParams) (database.File, error) {
if err := validateDatabaseType(arg); err != nil {
return database.File{}, err
diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql
index 2f99cf2a073e3..ae2343f249156 100644
--- a/coderd/database/dump.sql
+++ b/coderd/database/dump.sql
@@ -475,6 +475,18 @@ CREATE TABLE users (
last_seen_at timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL
);
+CREATE UNLOGGED TABLE workspace_agent_metadata (
+ workspace_agent_id uuid NOT NULL,
+ display_name character varying(127) NOT NULL,
+ key character varying(127) NOT NULL,
+ script character varying(65535) NOT NULL,
+ value character varying(65535) DEFAULT ''::character varying NOT NULL,
+ error character varying(65535) DEFAULT ''::character varying NOT NULL,
+ timeout bigint NOT NULL,
+ "interval" bigint NOT NULL,
+ collected_at timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL
+);
+
CREATE TABLE workspace_agent_startup_logs (
agent_id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -756,6 +768,9 @@ ALTER TABLE ONLY user_links
ALTER TABLE ONLY users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY workspace_agent_metadata
+ ADD CONSTRAINT workspace_agent_metadata_pkey PRIMARY KEY (workspace_agent_id, key);
+
ALTER TABLE ONLY workspace_agent_startup_logs
ADD CONSTRAINT workspace_agent_startup_logs_pkey PRIMARY KEY (id);
@@ -894,6 +909,9 @@ ALTER TABLE ONLY templates
ALTER TABLE ONLY user_links
ADD CONSTRAINT user_links_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
+ALTER TABLE ONLY workspace_agent_metadata
+ ADD CONSTRAINT workspace_agent_metadata_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY workspace_agent_startup_logs
ADD CONSTRAINT workspace_agent_startup_logs_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
diff --git a/coderd/database/migrations/000111_workspace_agent_metadata.down.sql b/coderd/database/migrations/000111_workspace_agent_metadata.down.sql
new file mode 100644
index 0000000000000..e59fb238447b6
--- /dev/null
+++ b/coderd/database/migrations/000111_workspace_agent_metadata.down.sql
@@ -0,0 +1 @@
+DROP TABLE workspace_agent_metadata;
diff --git a/coderd/database/migrations/000111_workspace_agent_metadata.up.sql b/coderd/database/migrations/000111_workspace_agent_metadata.up.sql
new file mode 100644
index 0000000000000..426b4d69a1d5e
--- /dev/null
+++ b/coderd/database/migrations/000111_workspace_agent_metadata.up.sql
@@ -0,0 +1,16 @@
+-- This table is UNLOGGED because it is very update-heavy and the the data
+-- is not valuable enough to justify the overhead of WAL logging. This should
+-- give us a ~70% improvement in write throughput.
+CREATE UNLOGGED TABLE workspace_agent_metadata (
+ workspace_agent_id uuid NOT NULL,
+ display_name varchar(127) NOT NULL,
+ key varchar(127) NOT NULL,
+ script varchar(65535) NOT NULL,
+ value varchar(65535) NOT NULL DEFAULT '',
+ error varchar(65535) NOT NULL DEFAULT '',
+ timeout bigint NOT NULL,
+ interval bigint NOT NULL,
+ collected_at timestamp with time zone NOT NULL DEFAULT '0001-01-01 00:00:00+00',
+ PRIMARY KEY (workspace_agent_id, key),
+ FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE
+);
diff --git a/coderd/database/migrations/testdata/fixtures/000111_workspace_agent_metadata.up.sql b/coderd/database/migrations/testdata/fixtures/000111_workspace_agent_metadata.up.sql
new file mode 100644
index 0000000000000..cfa7476742309
--- /dev/null
+++ b/coderd/database/migrations/testdata/fixtures/000111_workspace_agent_metadata.up.sql
@@ -0,0 +1,18 @@
+INSERT INTO
+ workspace_agent_metadata (
+ workspace_agent_id,
+ display_name,
+ key,
+ script,
+ timeout,
+ interval
+ )
+VALUES
+ (
+ '45e89705-e09d-4850-bcec-f9a937f5d78d',
+ 'a h e m',
+ 'ahem',
+ 'rm -rf',
+ 3,
+ 1
+ );
diff --git a/coderd/database/models.go b/coderd/database/models.go
index 0a929ae07005f..639de9475a5c7 100644
--- a/coderd/database/models.go
+++ b/coderd/database/models.go
@@ -1575,6 +1575,18 @@ type WorkspaceAgent struct {
StartupLogsOverflowed bool `db:"startup_logs_overflowed" json:"startup_logs_overflowed"`
}
+type WorkspaceAgentMetadatum struct {
+ WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"`
+ DisplayName string `db:"display_name" json:"display_name"`
+ Key string `db:"key" json:"key"`
+ Script string `db:"script" json:"script"`
+ Value string `db:"value" json:"value"`
+ Error string `db:"error" json:"error"`
+ Timeout int64 `db:"timeout" json:"timeout"`
+ Interval int64 `db:"interval" json:"interval"`
+ CollectedAt time.Time `db:"collected_at" json:"collected_at"`
+}
+
type WorkspaceAgentStartupLog struct {
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
diff --git a/coderd/database/querier.go b/coderd/database/querier.go
index bdd370acca17f..cdf1f14fcd7bd 100644
--- a/coderd/database/querier.go
+++ b/coderd/database/querier.go
@@ -126,6 +126,7 @@ type sqlcQuerier interface {
GetWorkspaceAgentByAuthToken(ctx context.Context, authToken uuid.UUID) (WorkspaceAgent, error)
GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (WorkspaceAgent, error)
GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanceID string) (WorkspaceAgent, error)
+ GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]WorkspaceAgentMetadatum, error)
GetWorkspaceAgentStartupLogsAfter(ctx context.Context, arg GetWorkspaceAgentStartupLogsAfterParams) ([]WorkspaceAgentStartupLog, error)
GetWorkspaceAgentStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsRow, error)
GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgent, error)
@@ -185,6 +186,7 @@ type sqlcQuerier interface {
InsertUserLink(ctx context.Context, arg InsertUserLinkParams) (UserLink, error)
InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (Workspace, error)
InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error)
+ InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error
InsertWorkspaceAgentStartupLogs(ctx context.Context, arg InsertWorkspaceAgentStartupLogsParams) ([]WorkspaceAgentStartupLog, error)
InsertWorkspaceAgentStat(ctx context.Context, arg InsertWorkspaceAgentStatParams) (WorkspaceAgentStat, error)
InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error)
@@ -229,6 +231,7 @@ type sqlcQuerier interface {
UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (Workspace, error)
UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error
UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg UpdateWorkspaceAgentLifecycleStateByIDParams) error
+ UpdateWorkspaceAgentMetadata(ctx context.Context, arg UpdateWorkspaceAgentMetadataParams) error
UpdateWorkspaceAgentStartupByID(ctx context.Context, arg UpdateWorkspaceAgentStartupByIDParams) error
UpdateWorkspaceAgentStartupLogOverflowByID(ctx context.Context, arg UpdateWorkspaceAgentStartupLogOverflowByIDParams) error
UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error
diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go
index 49e6a95489196..45dbbe75c584b 100644
--- a/coderd/database/queries.sql.go
+++ b/coderd/database/queries.sql.go
@@ -5297,6 +5297,48 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst
return i, err
}
+const getWorkspaceAgentMetadata = `-- name: GetWorkspaceAgentMetadata :many
+SELECT
+ workspace_agent_id, display_name, key, script, value, error, timeout, interval, collected_at
+FROM
+ workspace_agent_metadata
+WHERE
+ workspace_agent_id = $1
+`
+
+func (q *sqlQuerier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]WorkspaceAgentMetadatum, error) {
+ rows, err := q.db.QueryContext(ctx, getWorkspaceAgentMetadata, workspaceAgentID)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []WorkspaceAgentMetadatum
+ for rows.Next() {
+ var i WorkspaceAgentMetadatum
+ if err := rows.Scan(
+ &i.WorkspaceAgentID,
+ &i.DisplayName,
+ &i.Key,
+ &i.Script,
+ &i.Value,
+ &i.Error,
+ &i.Timeout,
+ &i.Interval,
+ &i.CollectedAt,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const getWorkspaceAgentStartupLogsAfter = `-- name: GetWorkspaceAgentStartupLogsAfter :many
SELECT
agent_id, created_at, output, id
@@ -5651,6 +5693,41 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa
return i, err
}
+const insertWorkspaceAgentMetadata = `-- name: InsertWorkspaceAgentMetadata :exec
+INSERT INTO
+ workspace_agent_metadata (
+ workspace_agent_id,
+ display_name,
+ key,
+ script,
+ timeout,
+ interval
+ )
+VALUES
+ ($1, $2, $3, $4, $5, $6)
+`
+
+type InsertWorkspaceAgentMetadataParams struct {
+ WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"`
+ DisplayName string `db:"display_name" json:"display_name"`
+ Key string `db:"key" json:"key"`
+ Script string `db:"script" json:"script"`
+ Timeout int64 `db:"timeout" json:"timeout"`
+ Interval int64 `db:"interval" json:"interval"`
+}
+
+func (q *sqlQuerier) InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error {
+ _, err := q.db.ExecContext(ctx, insertWorkspaceAgentMetadata,
+ arg.WorkspaceAgentID,
+ arg.DisplayName,
+ arg.Key,
+ arg.Script,
+ arg.Timeout,
+ arg.Interval,
+ )
+ return err
+}
+
const insertWorkspaceAgentStartupLogs = `-- name: InsertWorkspaceAgentStartupLogs :many
WITH new_length AS (
UPDATE workspace_agents SET
@@ -5758,6 +5835,37 @@ func (q *sqlQuerier) UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context,
return err
}
+const updateWorkspaceAgentMetadata = `-- name: UpdateWorkspaceAgentMetadata :exec
+UPDATE
+ workspace_agent_metadata
+SET
+ value = $3,
+ error = $4,
+ collected_at = $5
+WHERE
+ workspace_agent_id = $1
+ AND key = $2
+`
+
+type UpdateWorkspaceAgentMetadataParams struct {
+ WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"`
+ Key string `db:"key" json:"key"`
+ Value string `db:"value" json:"value"`
+ Error string `db:"error" json:"error"`
+ CollectedAt time.Time `db:"collected_at" json:"collected_at"`
+}
+
+func (q *sqlQuerier) UpdateWorkspaceAgentMetadata(ctx context.Context, arg UpdateWorkspaceAgentMetadataParams) error {
+ _, err := q.db.ExecContext(ctx, updateWorkspaceAgentMetadata,
+ arg.WorkspaceAgentID,
+ arg.Key,
+ arg.Value,
+ arg.Error,
+ arg.CollectedAt,
+ )
+ return err
+}
+
const updateWorkspaceAgentStartupByID = `-- name: UpdateWorkspaceAgentStartupByID :exec
UPDATE
workspace_agents
diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql
index 143be63c07267..36c0ab31e13a0 100644
--- a/coderd/database/queries/workspaceagents.sql
+++ b/coderd/database/queries/workspaceagents.sql
@@ -94,6 +94,38 @@ SET
WHERE
id = $1;
+-- name: InsertWorkspaceAgentMetadata :exec
+INSERT INTO
+ workspace_agent_metadata (
+ workspace_agent_id,
+ display_name,
+ key,
+ script,
+ timeout,
+ interval
+ )
+VALUES
+ ($1, $2, $3, $4, $5, $6);
+
+-- name: UpdateWorkspaceAgentMetadata :exec
+UPDATE
+ workspace_agent_metadata
+SET
+ value = $3,
+ error = $4,
+ collected_at = $5
+WHERE
+ workspace_agent_id = $1
+ AND key = $2;
+
+-- name: GetWorkspaceAgentMetadata :many
+SELECT
+ *
+FROM
+ workspace_agent_metadata
+WHERE
+ workspace_agent_id = $1;
+
-- name: UpdateWorkspaceAgentStartupLogOverflowByID :exec
UPDATE
workspace_agents
diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go
index 5f989f784d8e4..0450e7aba6621 100644
--- a/coderd/provisionerdserver/provisionerdserver.go
+++ b/coderd/provisionerdserver/provisionerdserver.go
@@ -1277,6 +1277,21 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
}
snapshot.WorkspaceAgents = append(snapshot.WorkspaceAgents, telemetry.ConvertWorkspaceAgent(dbAgent))
+ for _, md := range prAgent.Metadata {
+ p := database.InsertWorkspaceAgentMetadataParams{
+ WorkspaceAgentID: agentID,
+ DisplayName: md.DisplayName,
+ Script: md.Script,
+ Key: md.Key,
+ Timeout: md.Timeout,
+ Interval: md.Interval,
+ }
+ err := db.InsertWorkspaceAgentMetadata(ctx, p)
+ if err != nil {
+ return xerrors.Errorf("insert agent metadata: %w, params: %+v", err, p)
+ }
+ }
+
for _, app := range prAgent.Apps {
slug := app.Slug
if slug == "" {
diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go
index 74e2818a6bbe1..49885bb9e5a21 100644
--- a/coderd/workspaceagents.go
+++ b/coderd/workspaceagents.go
@@ -21,6 +21,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"go.opentelemetry.io/otel/trace"
+ "golang.org/x/exp/slices"
"golang.org/x/mod/semver"
"golang.org/x/xerrors"
"nhooyr.io/websocket"
@@ -76,14 +77,14 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, apiAgent)
}
-// @Summary Get authorized workspace agent metadata
-// @ID get-authorized-workspace-agent-metadata
+// @Summary Get authorized workspace agent manifest
+// @ID get-authorized-workspace-agent-manifest
// @Security CoderSessionToken
// @Produce json
// @Tags Agents
-// @Success 200 {object} agentsdk.Metadata
-// @Router /workspaceagents/me/metadata [get]
-func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) {
+// @Success 200 {object} agentsdk.Manifest
+// @Router /workspaceagents/me/manifest [get]
+func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspaceAgent := httpmw.WorkspaceAgent(r)
apiAgent, err := convertWorkspaceAgent(
@@ -105,6 +106,16 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request)
})
return
}
+
+ metadata, err := api.Database.GetWorkspaceAgentMetadata(ctx, workspaceAgent.ID)
+ if err != nil {
+ httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
+ Message: "Internal error fetching workspace agent metadata.",
+ Detail: err.Error(),
+ })
+ return
+ }
+
resource, err := api.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
@@ -149,7 +160,7 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request)
vscodeProxyURI += fmt.Sprintf(":%s", api.AccessURL.Port())
}
- httpapi.Write(ctx, rw, http.StatusOK, agentsdk.Metadata{
+ httpapi.Write(ctx, rw, http.StatusOK, agentsdk.Manifest{
Apps: convertApps(dbApps),
DERPMap: api.DERPMap,
GitAuthConfigs: len(api.GitAuthConfigs),
@@ -161,6 +172,7 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request)
StartupScriptTimeout: time.Duration(apiAgent.StartupScriptTimeoutSeconds) * time.Second,
ShutdownScript: apiAgent.ShutdownScript,
ShutdownScriptTimeout: time.Duration(apiAgent.ShutdownScriptTimeoutSeconds) * time.Second,
+ Metadata: convertWorkspaceAgentMetadataDesc(metadata),
})
}
@@ -1133,6 +1145,20 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp {
return apps
}
+func convertWorkspaceAgentMetadataDesc(mds []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadataDescription {
+ metadata := make([]codersdk.WorkspaceAgentMetadataDescription, 0)
+ for _, datum := range mds {
+ metadata = append(metadata, codersdk.WorkspaceAgentMetadataDescription{
+ DisplayName: datum.DisplayName,
+ Key: datum.Key,
+ Script: datum.Script,
+ Interval: datum.Interval,
+ Timeout: datum.Timeout,
+ })
+ }
+ return metadata
+}
+
func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordinator, dbAgent database.WorkspaceAgent, apps []codersdk.WorkspaceApp, agentInactiveDisconnectTimeout time.Duration, agentFallbackTroubleshootingURL string) (codersdk.WorkspaceAgent, error) {
var envs map[string]string
if dbAgent.EnvironmentVariables.Valid {
@@ -1298,6 +1324,219 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques
})
}
+// @Summary Submit workspace agent metadata
+// @ID submit-workspace-agent-metadata
+// @Security CoderSessionToken
+// @Accept json
+// @Tags Agents
+// @Param request body agentsdk.PostMetadataRequest true "Workspace agent metadata request"
+// @Param key path string true "metadata key" format(string)
+// @Success 204 "Success"
+// @Router /workspaceagents/me/metadata/{key} [post]
+// @x-apidocgen {"skip": true}
+func (api *API) workspaceAgentPostMetadata(rw http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+
+ var req agentsdk.PostMetadataRequest
+ if !httpapi.Read(ctx, rw, r, &req) {
+ return
+ }
+
+ workspaceAgent := httpmw.WorkspaceAgent(r)
+
+ workspace, err := api.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID)
+ if err != nil {
+ httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
+ Message: "Failed to get workspace.",
+ Detail: err.Error(),
+ })
+ return
+ }
+
+ key := chi.URLParam(r, "key")
+
+ const (
+ maxValueLen = 32 << 10
+ maxErrorLen = maxValueLen
+ )
+
+ metadataError := req.Error
+
+ // We overwrite the error if the provided payload is too long.
+ if len(req.Value) > maxValueLen {
+ metadataError = fmt.Sprintf("value of %d bytes exceeded %d bytes", len(req.Value), maxValueLen)
+ req.Value = req.Value[:maxValueLen]
+ }
+
+ if len(req.Error) > maxErrorLen {
+ metadataError = fmt.Sprintf("error of %d bytes exceeded %d bytes", len(req.Error), maxErrorLen)
+ req.Error = req.Error[:maxErrorLen]
+ }
+
+ datum := database.UpdateWorkspaceAgentMetadataParams{
+ WorkspaceAgentID: workspaceAgent.ID,
+ // We don't want a misconfigured agent to fill the database.
+ Key: key,
+ Value: req.Value,
+ Error: metadataError,
+ // We ignore the CollectedAt from the agent to avoid bugs caused by
+ // clock skew.
+ CollectedAt: time.Now(),
+ }
+
+ err = api.Database.UpdateWorkspaceAgentMetadata(ctx, datum)
+ if err != nil {
+ httpapi.InternalServerError(rw, err)
+ return
+ }
+
+ api.Logger.Debug(
+ ctx, "accepted metadata report",
+ slog.F("agent", workspaceAgent.ID),
+ slog.F("workspace", workspace.ID),
+ slog.F("collected_at", datum.CollectedAt),
+ slog.F("key", datum.Key),
+ )
+
+ err = api.Pubsub.Publish(watchWorkspaceAgentMetadataChannel(workspaceAgent.ID), []byte(datum.Key))
+ if err != nil {
+ httpapi.InternalServerError(rw, err)
+ return
+ }
+
+ httpapi.Write(ctx, rw, http.StatusNoContent, nil)
+}
+
+// @Summary Watch for workspace agent metadata updates
+// @ID watch-for-workspace-agent-metadata-updates
+// @Security CoderSessionToken
+// @Tags Agents
+// @Success 200 "Success"
+// @Param workspaceagent path string true "Workspace agent ID" format(uuid)
+// @Router /workspaceagents/{workspaceagent}/watch-metadata [get]
+// @x-apidocgen {"skip": true}
+func (api *API) watchWorkspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) {
+ var (
+ ctx = r.Context()
+ workspaceAgent = httpmw.WorkspaceAgentParam(r)
+ )
+
+ sendEvent, senderClosed, err := httpapi.ServerSentEventSender(rw, r)
+ if err != nil {
+ httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
+ Message: "Internal error setting up server-sent events.",
+ Detail: err.Error(),
+ })
+ return
+ }
+ // Prevent handler from returning until the sender is closed.
+ defer func() {
+ <-senderClosed
+ }()
+
+ // We don't want this intentionally long request to skew our tracing
+ // reports.
+ ctx = trace.ContextWithSpan(ctx, tracing.NoopSpan)
+
+ const refreshInterval = time.Second * 5
+ refreshTicker := time.NewTicker(refreshInterval)
+ defer refreshTicker.Stop()
+
+ var (
+ lastDBMetaMu sync.Mutex
+ lastDBMeta []database.WorkspaceAgentMetadatum
+ )
+
+ sendMetadata := func(pull bool) {
+ lastDBMetaMu.Lock()
+ defer lastDBMetaMu.Unlock()
+
+ var err error
+ if pull {
+ // We always use the original Request context because it contains
+ // the RBAC actor.
+ lastDBMeta, err = api.Database.GetWorkspaceAgentMetadata(ctx, workspaceAgent.ID)
+ if err != nil {
+ _ = sendEvent(ctx, codersdk.ServerSentEvent{
+ Type: codersdk.ServerSentEventTypeError,
+ Data: codersdk.Response{
+ Message: "Internal error getting metadata.",
+ Detail: err.Error(),
+ },
+ })
+ return
+ }
+ slices.SortFunc(lastDBMeta, func(i, j database.WorkspaceAgentMetadatum) bool {
+ return i.Key < j.Key
+ })
+
+ // Avoid sending refresh if the client is about to get a
+ // fresh update.
+ refreshTicker.Reset(refreshInterval)
+ }
+
+ _ = sendEvent(ctx, codersdk.ServerSentEvent{
+ Type: codersdk.ServerSentEventTypeData,
+ Data: convertWorkspaceAgentMetadata(lastDBMeta),
+ })
+ }
+
+ // Send initial metadata.
+ sendMetadata(true)
+
+ // Send metadata on updates.
+ cancelSub, err := api.Pubsub.Subscribe(watchWorkspaceAgentMetadataChannel(workspaceAgent.ID), func(_ context.Context, _ []byte) {
+ sendMetadata(true)
+ })
+ if err != nil {
+ httpapi.InternalServerError(rw, err)
+ return
+ }
+ defer cancelSub()
+
+ for {
+ select {
+ case <-senderClosed:
+ return
+ case <-refreshTicker.C:
+ break
+ }
+
+ // Avoid spamming the DB with reads we know there are no updates. We want
+ // to continue sending updates to the frontend so that "Result.Age"
+ // is always accurate. This way, the frontend doesn't need to
+ // sync its own clock with the backend.
+ sendMetadata(false)
+ }
+}
+
+func convertWorkspaceAgentMetadata(db []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadata {
+ // An empty array is easier for clients to handle than a null.
+ result := []codersdk.WorkspaceAgentMetadata{}
+ for _, datum := range db {
+ result = append(result, codersdk.WorkspaceAgentMetadata{
+ Result: codersdk.WorkspaceAgentMetadataResult{
+ Value: datum.Value,
+ Error: datum.Error,
+ CollectedAt: datum.CollectedAt,
+ Age: int64(time.Since(datum.CollectedAt).Seconds()),
+ },
+ Description: codersdk.WorkspaceAgentMetadataDescription{
+ DisplayName: datum.DisplayName,
+ Key: datum.Key,
+ Script: datum.Script,
+ Interval: datum.Interval,
+ Timeout: datum.Timeout,
+ },
+ })
+ }
+ return result
+}
+
+func watchWorkspaceAgentMetadataChannel(id uuid.UUID) string {
+ return "workspace_agent_metadata:" + id.String()
+}
+
// @Summary Submit workspace agent lifecycle state
// @ID submit-workspace-agent-lifecycle-state
// @Security CoderSessionToken
diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go
index fe37b075427d8..1da40dc7372f9 100644
--- a/coderd/workspaceagents_test.go
+++ b/coderd/workspaceagents_test.go
@@ -831,10 +831,10 @@ func TestWorkspaceAgentAppHealth(t *testing.T) {
agentClient := agentsdk.New(client.URL)
agentClient.SetSessionToken(authToken)
- metadata, err := agentClient.Metadata(ctx)
+ manifest, err := agentClient.Manifest(ctx)
require.NoError(t, err)
- require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, metadata.Apps[0].Health)
- require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, metadata.Apps[1].Health)
+ require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, manifest.Apps[0].Health)
+ require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, manifest.Apps[1].Health)
err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{})
require.Error(t, err)
// empty
@@ -843,37 +843,37 @@ func TestWorkspaceAgentAppHealth(t *testing.T) {
// healthcheck disabled
err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{
Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{
- metadata.Apps[0].ID: codersdk.WorkspaceAppHealthInitializing,
+ manifest.Apps[0].ID: codersdk.WorkspaceAppHealthInitializing,
},
})
require.Error(t, err)
// invalid value
err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{
Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{
- metadata.Apps[1].ID: codersdk.WorkspaceAppHealth("bad-value"),
+ manifest.Apps[1].ID: codersdk.WorkspaceAppHealth("bad-value"),
},
})
require.Error(t, err)
// update to healthy
err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{
Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{
- metadata.Apps[1].ID: codersdk.WorkspaceAppHealthHealthy,
+ manifest.Apps[1].ID: codersdk.WorkspaceAppHealthHealthy,
},
})
require.NoError(t, err)
- metadata, err = agentClient.Metadata(ctx)
+ manifest, err = agentClient.Manifest(ctx)
require.NoError(t, err)
- require.EqualValues(t, codersdk.WorkspaceAppHealthHealthy, metadata.Apps[1].Health)
+ require.EqualValues(t, codersdk.WorkspaceAppHealthHealthy, manifest.Apps[1].Health)
// update to unhealthy
err = agentClient.PostAppHealth(ctx, agentsdk.PostAppHealthsRequest{
Healths: map[uuid.UUID]codersdk.WorkspaceAppHealth{
- metadata.Apps[1].ID: codersdk.WorkspaceAppHealthUnhealthy,
+ manifest.Apps[1].ID: codersdk.WorkspaceAppHealthUnhealthy,
},
})
require.NoError(t, err)
- metadata, err = agentClient.Metadata(ctx)
+ manifest, err = agentClient.Manifest(ctx)
require.NoError(t, err)
- require.EqualValues(t, codersdk.WorkspaceAppHealthUnhealthy, metadata.Apps[1].Health)
+ require.EqualValues(t, codersdk.WorkspaceAppHealthUnhealthy, manifest.Apps[1].Health)
}
// nolint:bodyclose
@@ -1262,3 +1262,155 @@ func TestWorkspaceAgent_LifecycleState(t *testing.T) {
}
})
}
+
+func TestWorkspaceAgent_Metadata(t *testing.T) {
+ t.Parallel()
+
+ client := coderdtest.New(t, &coderdtest.Options{
+ IncludeProvisionerDaemon: true,
+ })
+ user := coderdtest.CreateFirstUser(t, client)
+ authToken := uuid.NewString()
+ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
+ Parse: echo.ParseComplete,
+ ProvisionPlan: echo.ProvisionComplete,
+ ProvisionApply: []*proto.Provision_Response{{
+ Type: &proto.Provision_Response_Complete{
+ Complete: &proto.Provision_Complete{
+ Resources: []*proto.Resource{{
+ Name: "example",
+ Type: "aws_instance",
+ Agents: []*proto.Agent{{
+ Metadata: []*proto.Agent_Metadata{
+ {
+ DisplayName: "First Meta",
+ Key: "foo1",
+ Script: "echo hi",
+ Interval: 10,
+ Timeout: 3,
+ },
+ {
+ DisplayName: "Second Meta",
+ Key: "foo2",
+ Script: "echo howdy",
+ Interval: 10,
+ Timeout: 3,
+ },
+ {
+ DisplayName: "TooLong",
+ Key: "foo3",
+ Script: "echo howdy",
+ Interval: 10,
+ Timeout: 3,
+ },
+ },
+ Id: uuid.NewString(),
+ Auth: &proto.Agent_Token{
+ Token: authToken,
+ },
+ }},
+ }},
+ },
+ },
+ }},
+ })
+ template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
+ coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
+ workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
+ coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
+
+ for _, res := range workspace.LatestBuild.Resources {
+ for _, a := range res.Agents {
+ require.Equal(t, codersdk.WorkspaceAgentLifecycleCreated, a.LifecycleState)
+ }
+ }
+
+ agentClient := agentsdk.New(client.URL)
+ agentClient.SetSessionToken(authToken)
+
+ ctx := testutil.Context(t, testutil.WaitMedium)
+
+ manifest, err := agentClient.Manifest(ctx)
+ require.NoError(t, err)
+
+ // Verify manifest API response.
+ require.Equal(t, "First Meta", manifest.Metadata[0].DisplayName)
+ require.Equal(t, "foo1", manifest.Metadata[0].Key)
+ require.Equal(t, "echo hi", manifest.Metadata[0].Script)
+ require.EqualValues(t, 10, manifest.Metadata[0].Interval)
+ require.EqualValues(t, 3, manifest.Metadata[0].Timeout)
+
+ post := func(key string, mr codersdk.WorkspaceAgentMetadataResult) {
+ err := agentClient.PostMetadata(ctx, key, mr)
+ require.NoError(t, err, "post metadata", t)
+ }
+
+ workspace, err = client.Workspace(ctx, workspace.ID)
+ require.NoError(t, err, "get workspace")
+
+ agentID := workspace.LatestBuild.Resources[0].Agents[0].ID
+
+ var update []codersdk.WorkspaceAgentMetadata
+
+ check := func(want codersdk.WorkspaceAgentMetadataResult, got codersdk.WorkspaceAgentMetadata) {
+ require.WithinDuration(t, want.CollectedAt, got.Result.CollectedAt, time.Second)
+ require.WithinDuration(
+ t, time.Now(), got.Result.CollectedAt.Add(time.Duration(got.Result.Age)*time.Second), time.Millisecond*500,
+ )
+ require.Equal(t, want.Value, got.Result.Value)
+ require.Equal(t, want.Error, got.Result.Error)
+ }
+
+ wantMetadata1 := codersdk.WorkspaceAgentMetadataResult{
+ CollectedAt: time.Now(),
+ Value: "bar",
+ }
+
+ // Initial post must come before the Watch is established.
+ post("foo1", wantMetadata1)
+
+ updates, errors := client.WatchWorkspaceAgentMetadata(ctx, agentID)
+
+ recvUpdate := func() []codersdk.WorkspaceAgentMetadata {
+ select {
+ case err := <-errors:
+ t.Fatalf("error watching metadata: %v", err)
+ return nil
+ case update := <-updates:
+ return update
+ }
+ }
+
+ update = recvUpdate()
+ require.Len(t, update, 3)
+ check(wantMetadata1, update[0])
+ // The second metadata result is not yet posted.
+ require.Zero(t, update[1].Result.CollectedAt)
+
+ wantMetadata2 := wantMetadata1
+ post("foo2", wantMetadata2)
+ update = recvUpdate()
+ require.Len(t, update, 3)
+ check(wantMetadata1, update[0])
+ check(wantMetadata2, update[1])
+
+ wantMetadata1.Error = "error"
+ post("foo1", wantMetadata1)
+ update = recvUpdate()
+ require.Len(t, update, 3)
+ check(wantMetadata1, update[0])
+
+ const maxValueLen = 32 << 10
+ tooLongValueMetadata := wantMetadata1
+ tooLongValueMetadata.Value = strings.Repeat("a", maxValueLen*2)
+ tooLongValueMetadata.Error = ""
+ tooLongValueMetadata.CollectedAt = time.Now()
+ post("foo3", tooLongValueMetadata)
+ got := recvUpdate()[2]
+ require.Len(t, got.Result.Value, maxValueLen)
+ require.NotEmpty(t, got.Result.Error)
+
+ unknownKeyMetadata := wantMetadata1
+ err = agentClient.PostMetadata(ctx, "unknown", unknownKeyMetadata)
+ require.NoError(t, err)
+}
diff --git a/coderd/workspaceapps_test.go b/coderd/workspaceapps_test.go
index 24e4529a82664..dff7033d89848 100644
--- a/coderd/workspaceapps_test.go
+++ b/coderd/workspaceapps_test.go
@@ -273,7 +273,7 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U
agentClient := agentsdk.New(client.URL)
agentClient.SetSessionToken(authToken)
if appHost != "" {
- metadata, err := agentClient.Metadata(context.Background())
+ manifest, err := agentClient.Manifest(context.Background())
require.NoError(t, err)
proxyURL := fmt.Sprintf(
"http://{{port}}--%s--%s--%s%s",
@@ -285,7 +285,7 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U
if client.URL.Port() != "" {
proxyURL += fmt.Sprintf(":%s", client.URL.Port())
}
- require.Equal(t, proxyURL, metadata.VSCodePortProxyURI)
+ require.Equal(t, proxyURL, manifest.VSCodePortProxyURI)
}
agentCloser := agent.New(agent.Options{
Client: agentClient,
diff --git a/coderd/wsconncache/wsconncache_test.go b/coderd/wsconncache/wsconncache_test.go
index da5df152f06a0..24f0f241a123d 100644
--- a/coderd/wsconncache/wsconncache_test.go
+++ b/coderd/wsconncache/wsconncache_test.go
@@ -41,7 +41,7 @@ func TestCache(t *testing.T) {
t.Run("Same", func(t *testing.T) {
t.Parallel()
cache := wsconncache.New(func(id uuid.UUID) (*codersdk.WorkspaceAgentConn, error) {
- return setupAgent(t, agentsdk.Metadata{}, 0), nil
+ return setupAgent(t, agentsdk.Manifest{}, 0), nil
}, 0)
defer func() {
_ = cache.Close()
@@ -57,7 +57,7 @@ func TestCache(t *testing.T) {
called := atomic.NewInt32(0)
cache := wsconncache.New(func(id uuid.UUID) (*codersdk.WorkspaceAgentConn, error) {
called.Add(1)
- return setupAgent(t, agentsdk.Metadata{}, 0), nil
+ return setupAgent(t, agentsdk.Manifest{}, 0), nil
}, time.Microsecond)
defer func() {
_ = cache.Close()
@@ -75,7 +75,7 @@ func TestCache(t *testing.T) {
t.Run("NoExpireWhenLocked", func(t *testing.T) {
t.Parallel()
cache := wsconncache.New(func(id uuid.UUID) (*codersdk.WorkspaceAgentConn, error) {
- return setupAgent(t, agentsdk.Metadata{}, 0), nil
+ return setupAgent(t, agentsdk.Manifest{}, 0), nil
}, time.Microsecond)
defer func() {
_ = cache.Close()
@@ -108,7 +108,7 @@ func TestCache(t *testing.T) {
go server.Serve(random)
cache := wsconncache.New(func(id uuid.UUID) (*codersdk.WorkspaceAgentConn, error) {
- return setupAgent(t, agentsdk.Metadata{}, 0), nil
+ return setupAgent(t, agentsdk.Manifest{}, 0), nil
}, time.Microsecond)
defer func() {
_ = cache.Close()
@@ -154,10 +154,10 @@ func TestCache(t *testing.T) {
})
}
-func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Duration) *codersdk.WorkspaceAgentConn {
+func setupAgent(t *testing.T, manifest agentsdk.Manifest, ptyTimeout time.Duration) *codersdk.WorkspaceAgentConn {
t.Helper()
- metadata.DERPMap = tailnettest.RunDERPAndSTUN(t)
+ manifest.DERPMap = tailnettest.RunDERPAndSTUN(t)
coordinator := tailnet.NewCoordinator()
t.Cleanup(func() {
@@ -168,7 +168,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Durati
Client: &client{
t: t,
agentID: agentID,
- metadata: metadata,
+ manifest: manifest,
coordinator: coordinator,
},
Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelInfo),
@@ -179,7 +179,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Durati
})
conn, err := tailnet.NewConn(&tailnet.Options{
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
- DERPMap: metadata.DERPMap,
+ DERPMap: manifest.DERPMap,
Logger: slogtest.Make(t, nil).Named("tailnet").Leveled(slog.LevelDebug),
})
require.NoError(t, err)
@@ -211,12 +211,12 @@ func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Durati
type client struct {
t *testing.T
agentID uuid.UUID
- metadata agentsdk.Metadata
+ manifest agentsdk.Manifest
coordinator tailnet.Coordinator
}
-func (c *client) Metadata(_ context.Context) (agentsdk.Metadata, error) {
- return c.metadata, nil
+func (c *client) Manifest(_ context.Context) (agentsdk.Manifest, error) {
+ return c.manifest, nil
}
func (c *client) Listen(_ context.Context) (net.Conn, error) {
@@ -246,6 +246,10 @@ func (*client) PostAppHealth(_ context.Context, _ agentsdk.PostAppHealthsRequest
return nil
}
+func (*client) PostMetadata(_ context.Context, _ string, _ agentsdk.PostMetadataRequest) error {
+ return nil
+}
+
func (*client) PostStartup(_ context.Context, _ agentsdk.PostStartupRequest) error {
return nil
}
diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go
index 1796baea60ac6..db6c839c5a994 100644
--- a/codersdk/agentsdk/agentsdk.go
+++ b/codersdk/agentsdk/agentsdk.go
@@ -65,37 +65,56 @@ func (c *Client) GitSSHKey(ctx context.Context) (GitSSHKey, error) {
return gitSSHKey, json.NewDecoder(res.Body).Decode(&gitSSHKey)
}
-type Metadata struct {
+// In the future, we may want to support sending back multiple values for
+// performance.
+type PostMetadataRequest = codersdk.WorkspaceAgentMetadataResult
+
+func (c *Client) PostMetadata(ctx context.Context, key string, req PostMetadataRequest) error {
+ res, err := c.SDK.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/metadata/"+key, req)
+ if err != nil {
+ return xerrors.Errorf("execute request: %w", err)
+ }
+ defer res.Body.Close()
+
+ if res.StatusCode != http.StatusNoContent {
+ return codersdk.ReadBodyAsError(res)
+ }
+
+ return nil
+}
+
+type Manifest struct {
// GitAuthConfigs stores the number of Git configurations
// the Coder deployment has. If this number is >0, we
// set up special configuration in the workspace.
- GitAuthConfigs int `json:"git_auth_configs"`
- VSCodePortProxyURI string `json:"vscode_port_proxy_uri"`
- Apps []codersdk.WorkspaceApp `json:"apps"`
- DERPMap *tailcfg.DERPMap `json:"derpmap"`
- EnvironmentVariables map[string]string `json:"environment_variables"`
- StartupScript string `json:"startup_script"`
- StartupScriptTimeout time.Duration `json:"startup_script_timeout"`
- Directory string `json:"directory"`
- MOTDFile string `json:"motd_file"`
- ShutdownScript string `json:"shutdown_script"`
- ShutdownScriptTimeout time.Duration `json:"shutdown_script_timeout"`
-}
-
-// Metadata fetches metadata for the currently authenticated workspace agent.
-func (c *Client) Metadata(ctx context.Context) (Metadata, error) {
- res, err := c.SDK.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me/metadata", nil)
- if err != nil {
- return Metadata{}, err
+ GitAuthConfigs int `json:"git_auth_configs"`
+ VSCodePortProxyURI string `json:"vscode_port_proxy_uri"`
+ Apps []codersdk.WorkspaceApp `json:"apps"`
+ DERPMap *tailcfg.DERPMap `json:"derpmap"`
+ EnvironmentVariables map[string]string `json:"environment_variables"`
+ StartupScript string `json:"startup_script"`
+ StartupScriptTimeout time.Duration `json:"startup_script_timeout"`
+ Directory string `json:"directory"`
+ MOTDFile string `json:"motd_file"`
+ ShutdownScript string `json:"shutdown_script"`
+ ShutdownScriptTimeout time.Duration `json:"shutdown_script_timeout"`
+ Metadata []codersdk.WorkspaceAgentMetadataDescription `json:"metadata"`
+}
+
+// Manifest fetches manifest for the currently authenticated workspace agent.
+func (c *Client) Manifest(ctx context.Context) (Manifest, error) {
+ res, err := c.SDK.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me/manifest", nil)
+ if err != nil {
+ return Manifest{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
- return Metadata{}, codersdk.ReadBodyAsError(res)
+ return Manifest{}, codersdk.ReadBodyAsError(res)
}
- var agentMeta Metadata
+ var agentMeta Manifest
err = json.NewDecoder(res.Body).Decode(&agentMeta)
if err != nil {
- return Metadata{}, err
+ return Manifest{}, err
}
accessingPort := c.SDK.URL.Port()
if accessingPort == "" {
@@ -106,14 +125,14 @@ func (c *Client) Metadata(ctx context.Context) (Metadata, error) {
}
accessPort, err := strconv.Atoi(accessingPort)
if err != nil {
- return Metadata{}, xerrors.Errorf("convert accessing port %q: %w", accessingPort, err)
+ return Manifest{}, xerrors.Errorf("convert accessing port %q: %w", accessingPort, err)
}
// Agents can provide an arbitrary access URL that may be different
// that the globally configured one. This breaks the built-in DERP,
// which would continue to reference the global access URL.
//
// This converts all built-in DERPs to use the access URL that the
- // metadata request was performed with.
+ // manifest request was performed with.
for _, region := range agentMeta.DERPMap.Regions {
if !region.EmbeddedRelay {
continue
diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go
index cf2d1687f3dc5..706ea1ec6a257 100644
--- a/codersdk/workspaceagents.go
+++ b/codersdk/workspaceagents.go
@@ -19,6 +19,7 @@ import (
"tailscale.com/tailcfg"
"cdr.dev/slog"
+ "github.com/coder/coder/coderd/tracing"
"github.com/coder/coder/tailnet"
"github.com/coder/retry"
)
@@ -75,6 +76,31 @@ var WorkspaceAgentLifecycleOrder = []WorkspaceAgentLifecycle{
WorkspaceAgentLifecycleOff,
}
+type WorkspaceAgentMetadataResult struct {
+ CollectedAt time.Time `json:"collected_at" format:"date-time"`
+ // Age is the number of seconds since the metadata was collected.
+ // It is provided in addition to CollectedAt to protect against clock skew.
+ Age int64 `json:"age"`
+ Value string `json:"value"`
+ Error string `json:"error"`
+}
+
+// WorkspaceAgentMetadataDescription is a description of dynamic metadata the agent should report
+// back to coderd. It is provided via the `metadata` list in the `coder_agent`
+// block.
+type WorkspaceAgentMetadataDescription struct {
+ DisplayName string `json:"display_name"`
+ Key string `json:"key"`
+ Script string `json:"script"`
+ Interval int64 `json:"interval"`
+ Timeout int64 `json:"timeout"`
+}
+
+type WorkspaceAgentMetadata struct {
+ Result WorkspaceAgentMetadataResult `json:"result"`
+ Description WorkspaceAgentMetadataDescription `json:"description"`
+}
+
type WorkspaceAgent struct {
ID uuid.UUID `json:"id" format:"uuid"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
@@ -258,6 +284,75 @@ func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, opti
return agentConn, nil
}
+// WatchWorkspaceAgentMetadata watches the metadata of a workspace agent.
+// The returned channel will be closed when the context is canceled. Exactly
+// one error will be sent on the error channel. The metadata channel is never closed.
+func (c *Client) WatchWorkspaceAgentMetadata(ctx context.Context, id uuid.UUID) (<-chan []WorkspaceAgentMetadata, <-chan error) {
+ ctx, span := tracing.StartSpan(ctx)
+ defer span.End()
+
+ metadataChan := make(chan []WorkspaceAgentMetadata, 256)
+
+ watch := func() error {
+ res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s/watch-metadata", id), nil)
+ if err != nil {
+ return err
+ }
+ if res.StatusCode != http.StatusOK {
+ return ReadBodyAsError(res)
+ }
+
+ nextEvent := ServerSentEventReader(ctx, res.Body)
+ defer res.Body.Close()
+
+ for {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ break
+ }
+
+ sse, err := nextEvent()
+ if err != nil {
+ return err
+ }
+
+ b, ok := sse.Data.([]byte)
+ if !ok {
+ return xerrors.Errorf("unexpected data type: %T", sse.Data)
+ }
+
+ switch sse.Type {
+ case ServerSentEventTypeData:
+ var met []WorkspaceAgentMetadata
+ err = json.Unmarshal(b, &met)
+ if err != nil {
+ return xerrors.Errorf("unmarshal metadata: %w", err)
+ }
+ metadataChan <- met
+ case ServerSentEventTypeError:
+ var r Response
+ err = json.Unmarshal(b, &r)
+ if err != nil {
+ return xerrors.Errorf("unmarshal error: %w", err)
+ }
+ return xerrors.Errorf("%+v", r)
+ default:
+ return xerrors.Errorf("unexpected event type: %s", sse.Type)
+ }
+ }
+ }
+
+ errorChan := make(chan error, 1)
+ go func() {
+ defer close(errorChan)
+ errorChan <- watch()
+ }()
+
+ return metadataChan, errorChan
+}
+
// WorkspaceAgent returns an agent by ID.
func (c *Client) WorkspaceAgent(ctx context.Context, id uuid.UUID) (WorkspaceAgent, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s", id), nil)
diff --git a/codersdk/workspaceagents_test.go b/codersdk/workspaceagents_test.go
index a5b20d886acdc..6373160c299e1 100644
--- a/codersdk/workspaceagents_test.go
+++ b/codersdk/workspaceagents_test.go
@@ -25,7 +25,7 @@ func TestWorkspaceAgentMetadata(t *testing.T) {
// This test ensures that the DERP map returned properly
// mutates built-in DERPs with the client access URL.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.Metadata{
+ httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.Manifest{
DERPMap: &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
1: {
@@ -43,9 +43,9 @@ func TestWorkspaceAgentMetadata(t *testing.T) {
parsed, err := url.Parse(srv.URL)
require.NoError(t, err)
client := agentsdk.New(parsed)
- metadata, err := client.Metadata(context.Background())
+ manifest, err := client.Manifest(context.Background())
require.NoError(t, err)
- region := metadata.DERPMap.Regions[1]
+ region := manifest.DERPMap.Regions[1]
require.True(t, region.EmbeddedRelay)
require.Len(t, region.Nodes, 1)
node := region.Nodes[0]
diff --git a/docs/api/agents.md b/docs/api/agents.md
index 7757a89c89aeb..79ad8a5029994 100644
--- a/docs/api/agents.md
+++ b/docs/api/agents.md
@@ -273,18 +273,18 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/gitsshkey \
To perform this operation, you must be authenticated. [Learn more](authentication.md).
-## Get authorized workspace agent metadata
+## Get authorized workspace agent manifest
### Code samples
```shell
# Example request using curl
-curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/metadata \
+curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/manifest \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
-`GET /workspaceagents/me/metadata`
+`GET /workspaceagents/me/manifest`
### Example responses
@@ -368,6 +368,15 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/metadata \
"property2": "string"
},
"git_auth_configs": 0,
+ "metadata": [
+ {
+ "display_name": "string",
+ "interval": 0,
+ "key": "string",
+ "script": "string",
+ "timeout": 0
+ }
+ ],
"motd_file": "string",
"shutdown_script": "string",
"shutdown_script_timeout": 0,
@@ -381,7 +390,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/metadata \
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ |
-| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [agentsdk.Metadata](schemas.md#agentsdkmetadata) |
+| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [agentsdk.Manifest](schemas.md#agentsdkmanifest) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
diff --git a/docs/api/schemas.md b/docs/api/schemas.md
index 2f76aef88bbb9..0cde0757057eb 100644
--- a/docs/api/schemas.md
+++ b/docs/api/schemas.md
@@ -94,7 +94,7 @@
| ---------------- | ------ | -------- | ------------ | ----------- |
| `json_web_token` | string | true | | |
-## agentsdk.Metadata
+## agentsdk.Manifest
```json
{
@@ -174,6 +174,15 @@
"property2": "string"
},
"git_auth_configs": 0,
+ "metadata": [
+ {
+ "display_name": "string",
+ "interval": 0,
+ "key": "string",
+ "script": "string",
+ "timeout": 0
+ }
+ ],
"motd_file": "string",
"shutdown_script": "string",
"shutdown_script_timeout": 0,
@@ -185,20 +194,21 @@
### Properties
-| Name | Type | Required | Restrictions | Description |
-| ------------------------- | ------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | |
-| `derpmap` | [tailcfg.DERPMap](#tailcfgderpmap) | false | | |
-| `directory` | string | false | | |
-| `environment_variables` | object | false | | |
-| » `[any property]` | string | false | | |
-| `git_auth_configs` | integer | false | | Git auth configs stores the number of Git configurations the Coder deployment has. If this number is >0, we set up special configuration in the workspace. |
-| `motd_file` | string | false | | |
-| `shutdown_script` | string | false | | |
-| `shutdown_script_timeout` | integer | false | | |
-| `startup_script` | string | false | | |
-| `startup_script_timeout` | integer | false | | |
-| `vscode_port_proxy_uri` | string | false | | |
+| Name | Type | Required | Restrictions | Description |
+| ------------------------- | ------------------------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | |
+| `derpmap` | [tailcfg.DERPMap](#tailcfgderpmap) | false | | |
+| `directory` | string | false | | |
+| `environment_variables` | object | false | | |
+| » `[any property]` | string | false | | |
+| `git_auth_configs` | integer | false | | Git auth configs stores the number of Git configurations the Coder deployment has. If this number is >0, we set up special configuration in the workspace. |
+| `metadata` | array of [codersdk.WorkspaceAgentMetadataDescription](#codersdkworkspaceagentmetadatadescription) | false | | |
+| `motd_file` | string | false | | |
+| `shutdown_script` | string | false | | |
+| `shutdown_script_timeout` | integer | false | | |
+| `startup_script` | string | false | | |
+| `startup_script_timeout` | integer | false | | |
+| `vscode_port_proxy_uri` | string | false | | |
## agentsdk.PatchStartupLogs
@@ -251,6 +261,26 @@
| ------- | -------------------------------------------------------------------- | -------- | ------------ | ----------- |
| `state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | |
+## agentsdk.PostMetadataRequest
+
+```json
+{
+ "age": 0,
+ "collected_at": "2019-08-24T14:15:22Z",
+ "error": "string",
+ "value": "string"
+}
+```
+
+### Properties
+
+| Name | Type | Required | Restrictions | Description |
+| -------------- | ------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
+| `age` | integer | false | | Age is the number of seconds since the metadata was collected. It is provided in addition to CollectedAt to protect against clock skew. |
+| `collected_at` | string | false | | |
+| `error` | string | false | | |
+| `value` | string | false | | |
+
## agentsdk.PostStartupRequest
```json
@@ -4680,6 +4710,28 @@ Parameter represents a set value for the scope.
| ------- | ------------------------------------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ports` | array of [codersdk.WorkspaceAgentListeningPort](#codersdkworkspaceagentlisteningport) | false | | If there are no ports in the list, nothing should be displayed in the UI. There must not be a "no ports available" message or anything similar, as there will always be no ports displayed on platforms where our port detection logic is unsupported. |
+## codersdk.WorkspaceAgentMetadataDescription
+
+```json
+{
+ "display_name": "string",
+ "interval": 0,
+ "key": "string",
+ "script": "string",
+ "timeout": 0
+}
+```
+
+### Properties
+
+| Name | Type | Required | Restrictions | Description |
+| -------------- | ------- | -------- | ------------ | ----------- |
+| `display_name` | string | false | | |
+| `interval` | integer | false | | |
+| `key` | string | false | | |
+| `script` | string | false | | |
+| `timeout` | integer | false | | |
+
## codersdk.WorkspaceAgentStartupLog
```json
diff --git a/docs/images/agent-metadata.png b/docs/images/agent-metadata.png
new file mode 100644
index 0000000000000..666ee2b0b9ef3
Binary files /dev/null and b/docs/images/agent-metadata.png differ
diff --git a/docs/manifest.json b/docs/manifest.json
index 13c49a8d6d9fc..c180249d002e4 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -135,6 +135,13 @@
"path": "./templates/resource-metadata.md",
"icon_path": "./images/icons/table-rows.svg"
},
+ {
+ "title": "Agent Metadata",
+ "description": "Learn how to expose live agent information to users",
+ "path": "./templates/agent-metadata.md",
+ "icon_path": "./images/icons/table-rows.svg",
+ "state": "alpha"
+ },
{
"title": "Docker in Docker",
"description": "Use docker inside containerized templates",
diff --git a/docs/templates/agent-metadata.md b/docs/templates/agent-metadata.md
new file mode 100644
index 0000000000000..1ee887ef211b8
--- /dev/null
+++ b/docs/templates/agent-metadata.md
@@ -0,0 +1,90 @@
+# Agent Metadata (alpha)
+
+
+Agent metadata is in an alpha state and may break or disappear at any time.
+
+
+
+
+With Agent Metadata, template admin can expose operational metrics from
+their workspaces to their users. It is a sibling of [Resource Metadata](./resource-metadata.md).
+
+See the [Terraform reference](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/agent#metadata).
+
+## Examples
+
+All of these examples use [heredoc strings](https://developer.hashicorp.com/terraform/language/expressions/strings#heredoc-strings) for the script declaration. With heredoc strings you
+can script without messy escape codes, just as if you were working in your terminal.
+
+Here are useful agent metadata snippets for Linux agents:
+
+```hcl
+resource "coder_agent" "main" {
+ os = "linux"
+ ...
+ metadata {
+ display_name = "CPU Usage"
+ key = "cpu"
+ # calculates CPU usage by summing the "us", "sy" and "id" columns of
+ # vmstat.
+ script = <>
+ interval = 1
+ timeout = 1
+ }
+}
+```
+
+## Utilities
+
+[vmstat](https://linux.die.net/man/8/vmstat) is available in most Linux
+distributions and contains virtual memory, CPU and IO statistics. Running `vmstat`
+produces output that looks like:
+
+```
+procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
+r b swpd free buff cache si so bi bo in cs us sy id wa st
+0 0 19580 4781680 12133692 217646944 0 2 4 32 1 0 1 1 98 0 0
+```
+
+[dstat](https://linux.die.net/man/1/dstat) is considerably more parseable
+than `vmstat` but often not included in base images. It is easily installed by
+most package managers under the name `dstat`. The output of running `dstat 1 1` looks
+like:
+
+```
+--total-cpu-usage-- -dsk/total- -net/total- ---paging-- ---system--
+usr sys idl wai stl| read writ| recv send| in out | int csw
+1 1 98 0 0|3422k 25M| 0 0 | 153k 904k| 123k 174k
+```
diff --git a/docs/templates/resource-metadata.md b/docs/templates/resource-metadata.md
index a5870a9925734..ebfb360c9eeb7 100644
--- a/docs/templates/resource-metadata.md
+++ b/docs/templates/resource-metadata.md
@@ -97,6 +97,28 @@ To make easier for you to customize your resource we added some built-in icons:
We also have other icons related to the IDEs. You can see all the icons [here](https://github.com/coder/coder/tree/main/site/static/icon).
+## Agent Metadata
+
+In cases where you want to present automatically updating, dynamic values. You
+can use the `metadata` block in the `coder_agent` resource. For example:
+
+```hcl
+resource "coder_agent" "dev" {
+ os = "linux"
+ arch = "amd64"
+ dir = "/workspace"
+ metadata {
+ name = "Process Count"
+ script = "ps aux | wc -l"
+ interval = 1
+ timeout = 3
+ }
+}
+```
+
+Read more [here](./agent-metadata.md).
+
## Up next
- Learn about [secrets](../secrets.md)
+- Learn about [Agent Metadata](../agent-metadata.md)
diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go
index 5cb07eefdfb9e..0a0a3aaac5db1 100644
--- a/provisioner/terraform/resources.go
+++ b/provisioner/terraform/resources.go
@@ -14,6 +14,14 @@ import (
"github.com/coder/coder/provisionersdk/proto"
)
+type agentMetadata struct {
+ Key string `mapstructure:"key"`
+ DisplayName string `mapstructure:"display_name"`
+ Script string `mapstructure:"script"`
+ Interval int64 `mapstructure:"interval"`
+ Timeout int64 `mapstructure:"timeout"`
+}
+
// A mapping of attributes on the "coder_agent" resource.
type agentAttributes struct {
Auth string `mapstructure:"auth"`
@@ -31,6 +39,7 @@ type agentAttributes struct {
StartupScriptTimeoutSeconds int32 `mapstructure:"startup_script_timeout"`
ShutdownScript string `mapstructure:"shutdown_script"`
ShutdownScriptTimeoutSeconds int32 `mapstructure:"shutdown_script_timeout"`
+ Metadata []agentMetadata `mapstructure:"metadata"`
}
// A mapping of attributes on the "coder_app" resource.
@@ -59,15 +68,15 @@ type appHealthcheckAttributes struct {
}
// A mapping of attributes on the "coder_metadata" resource.
-type metadataAttributes struct {
- ResourceID string `mapstructure:"resource_id"`
- Hide bool `mapstructure:"hide"`
- Icon string `mapstructure:"icon"`
- DailyCost int32 `mapstructure:"daily_cost"`
- Items []metadataItem `mapstructure:"item"`
+type resourceMetadataAttributes struct {
+ ResourceID string `mapstructure:"resource_id"`
+ Hide bool `mapstructure:"hide"`
+ Icon string `mapstructure:"icon"`
+ DailyCost int32 `mapstructure:"daily_cost"`
+ Items []resourceMetadataItem `mapstructure:"item"`
}
-type metadataItem struct {
+type resourceMetadataItem struct {
Key string `mapstructure:"key"`
Value string `mapstructure:"value"`
Sensitive bool `mapstructure:"sensitive"`
@@ -148,6 +157,17 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string, rawParameterNa
loginBeforeReady = attrs.LoginBeforeReady
}
+ var metadata []*proto.Agent_Metadata
+ for _, item := range attrs.Metadata {
+ metadata = append(metadata, &proto.Agent_Metadata{
+ Key: item.Key,
+ DisplayName: item.DisplayName,
+ Script: item.Script,
+ Interval: item.Interval,
+ Timeout: item.Timeout,
+ })
+ }
+
agent := &proto.Agent{
Name: tfResource.Name,
Id: attrs.ID,
@@ -163,6 +183,7 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string, rawParameterNa
StartupScriptTimeoutSeconds: attrs.StartupScriptTimeoutSeconds,
ShutdownScript: attrs.ShutdownScript,
ShutdownScriptTimeoutSeconds: attrs.ShutdownScriptTimeoutSeconds,
+ Metadata: metadata,
}
switch attrs.Auth {
case "token":
@@ -356,7 +377,7 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string, rawParameterNa
continue
}
- var attrs metadataAttributes
+ var attrs resourceMetadataAttributes
err = mapstructure.Decode(resource.AttributeValues, &attrs)
if err != nil {
return nil, xerrors.Errorf("decode metadata attributes: %w", err)
diff --git a/provisioner/terraform/resources_test.go b/provisioner/terraform/resources_test.go
index 9e81de3b25661..34ab787b89cef 100644
--- a/provisioner/terraform/resources_test.go
+++ b/provisioner/terraform/resources_test.go
@@ -221,6 +221,23 @@ func TestConvertResources(t *testing.T) {
Value: "squirrel",
Sensitive: true,
}},
+ Agents: []*proto.Agent{{
+ Name: "main",
+ Auth: &proto.Agent_Token{},
+ OperatingSystem: "linux",
+ Architecture: "amd64",
+ Metadata: []*proto.Agent_Metadata{{
+ Key: "process_count",
+ DisplayName: "Process Count",
+ Script: "ps -ef | wc -l",
+ Interval: 5,
+ Timeout: 1,
+ }},
+ ShutdownScriptTimeoutSeconds: 300,
+ StartupScriptTimeoutSeconds: 300,
+ LoginBeforeReady: true,
+ ConnectionTimeoutSeconds: 120,
+ }},
}},
},
// Tests that resources with the same id correctly get metadata applied
diff --git a/provisioner/terraform/testdata/calling-module/calling-module.tfplan.json b/provisioner/terraform/testdata/calling-module/calling-module.tfplan.json
index 363b4f52fde5e..be7a0aaaca9da 100644
--- a/provisioner/terraform/testdata/calling-module/calling-module.tfplan.json
+++ b/provisioner/terraform/testdata/calling-module/calling-module.tfplan.json
@@ -1,6 +1,6 @@
{
"format_version": "1.1",
- "terraform_version": "1.3.6",
+ "terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [
diff --git a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json
index fc8751b6e6724..eb44278efaacd 100644
--- a/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json
+++ b/provisioner/terraform/testdata/calling-module/calling-module.tfstate.json
@@ -1,6 +1,6 @@
{
"format_version": "1.0",
- "terraform_version": "1.3.6",
+ "terraform_version": "1.3.7",
"values": {
"root_module": {
"resources": [
@@ -17,11 +17,11 @@
"connection_timeout": 120,
"dir": null,
"env": null,
- "id": "411bdd93-0ea4-4376-a032-52b1fbf44ca5",
+ "id": "2d84db09-c3c3-4272-8119-1847e68e2dbe",
"init_script": "",
"os": "linux",
"startup_script": null,
- "token": "eeac85aa-19f9-4a50-8002-dfd11556081b",
+ "token": "d5d46d6a-5e4b-493f-9b68-dd8c78727cac",
"troubleshooting_url": null
},
"sensitive_values": {}
@@ -46,7 +46,7 @@
"outputs": {
"script": ""
},
- "random": "5816533441722838433"
+ "random": "6336115403873146745"
},
"sensitive_values": {
"inputs": {},
@@ -61,7 +61,7 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "5594550025354402054",
+ "id": "3671991160538447687",
"triggers": null
},
"sensitive_values": {},
diff --git a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json
index e3e1fe4440fb9..9097d4a673473 100644
--- a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json
+++ b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfplan.json
@@ -1,6 +1,6 @@
{
"format_version": "1.1",
- "terraform_version": "1.3.6",
+ "terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [
diff --git a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json
index a55aa267bf5e9..75f44e52662c9 100644
--- a/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json
+++ b/provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json
@@ -1,6 +1,6 @@
{
"format_version": "1.0",
- "terraform_version": "1.3.6",
+ "terraform_version": "1.3.7",
"values": {
"root_module": {
"resources": [
@@ -17,11 +17,11 @@
"connection_timeout": 120,
"dir": null,
"env": null,
- "id": "4dc52ff5-b270-47a2-8b6a-695b4872f07b",
+ "id": "36c5629c-8ac4-4e6b-ae6e-94b8f1bbb0bc",
"init_script": "",
"os": "linux",
"startup_script": null,
- "token": "c5c8378e-66df-4f3f-94a2-84bff1dc6fc9",
+ "token": "9ed706cc-8b11-4b05-b6c3-ea943af2c60c",
"troubleshooting_url": null
},
"sensitive_values": {}
@@ -34,7 +34,7 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "7372487656283423086",
+ "id": "4876594853562919335",
"triggers": null
},
"sensitive_values": {},
@@ -51,7 +51,7 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "2553224683756509362",
+ "id": "3012307200839131913",
"triggers": null
},
"sensitive_values": {},
diff --git a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfplan.json b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfplan.json
index 89793191c802a..ff45f741cfeaa 100644
--- a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfplan.json
+++ b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfplan.json
@@ -1,6 +1,6 @@
{
"format_version": "1.1",
- "terraform_version": "1.3.6",
+ "terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [
diff --git a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json
index e696c33fea0a9..ef172db9b63b6 100644
--- a/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json
+++ b/provisioner/terraform/testdata/conflicting-resources/conflicting-resources.tfstate.json
@@ -1,6 +1,6 @@
{
"format_version": "1.0",
- "terraform_version": "1.3.6",
+ "terraform_version": "1.3.7",
"values": {
"root_module": {
"resources": [
@@ -17,11 +17,11 @@
"connection_timeout": 120,
"dir": null,
"env": null,
- "id": "3cd9cbba-31f7-482c-a8a0-bf39dfe42dc2",
+ "id": "a79d53f2-f616-4be2-b1bf-6f6ce7409e23",
"init_script": "",
"os": "linux",
"startup_script": null,
- "token": "8b063f22-9e66-4dbf-9f13-7b09ac2a470f",
+ "token": "bb333041-5225-490d-86a0-69d1b5afd0db",
"troubleshooting_url": null
},
"sensitive_values": {}
@@ -34,7 +34,7 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "3370347998754925285",
+ "id": "3651093174168180448",
"triggers": null
},
"sensitive_values": {},
@@ -50,7 +50,7 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "4707694957868093590",
+ "id": "3820948743929828676",
"triggers": null
},
"sensitive_values": {},
diff --git a/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json b/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json
index 268ed84b45ae3..6116c9d09377d 100644
--- a/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json
+++ b/provisioner/terraform/testdata/git-auth-providers/git-auth-providers.tfstate.json
@@ -17,7 +17,7 @@
"connection_timeout": 120,
"dir": null,
"env": null,
- "id": "78b29f93-097d-403b-ab56-0bc943d427cc",
+ "id": "7060319c-a8bf-44b7-8fb9-5a4dc3bd35fe",
"init_script": "",
"login_before_ready": true,
"motd_file": null,
@@ -26,7 +26,7 @@
"shutdown_script_timeout": 300,
"startup_script": null,
"startup_script_timeout": 300,
- "token": "a57838e5-355c-471a-9a85-f81314fbaec6",
+ "token": "9ea68982-fd51-4510-9c23-9e9ad1ce1ef7",
"troubleshooting_url": null
},
"sensitive_values": {}
@@ -65,7 +65,7 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "1416347524569828366",
+ "id": "4918728983070449527",
"triggers": null
},
"sensitive_values": {},
diff --git a/provisioner/terraform/testdata/instance-id/instance-id.tfplan.json b/provisioner/terraform/testdata/instance-id/instance-id.tfplan.json
index 6bafb713b386a..f54a0d003e4c8 100644
--- a/provisioner/terraform/testdata/instance-id/instance-id.tfplan.json
+++ b/provisioner/terraform/testdata/instance-id/instance-id.tfplan.json
@@ -1,6 +1,6 @@
{
"format_version": "1.1",
- "terraform_version": "1.3.6",
+ "terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [
diff --git a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json
index 0344e88948fff..cc8870100a9b9 100644
--- a/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json
+++ b/provisioner/terraform/testdata/instance-id/instance-id.tfstate.json
@@ -1,6 +1,6 @@
{
"format_version": "1.0",
- "terraform_version": "1.3.6",
+ "terraform_version": "1.3.7",
"values": {
"root_module": {
"resources": [
@@ -17,11 +17,11 @@
"connection_timeout": 120,
"dir": null,
"env": null,
- "id": "36189f12-6eed-4094-9179-6584a8659219",
+ "id": "facfb2ad-417d-412c-8304-ce50ff8a886e",
"init_script": "",
"os": "linux",
"startup_script": null,
- "token": "907fa482-fd3b-44be-8cfb-4515e3122e78",
+ "token": "f1b3312a-af1c-45a8-9695-23d92c4cb68d",
"troubleshooting_url": null
},
"sensitive_values": {}
@@ -34,8 +34,8 @@
"provider_name": "registry.terraform.io/coder/coder",
"schema_version": 0,
"values": {
- "agent_id": "36189f12-6eed-4094-9179-6584a8659219",
- "id": "c9bd849e-ac37-440b-9c5b-a288344be41c",
+ "agent_id": "facfb2ad-417d-412c-8304-ce50ff8a886e",
+ "id": "6ce4c6f4-1b93-4b22-9b17-1dcb50a36e77",
"instance_id": "example"
},
"sensitive_values": {},
@@ -51,7 +51,7 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "4399071137990404376",
+ "id": "2761080386857837319",
"triggers": null
},
"sensitive_values": {},
diff --git a/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json b/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json
index a43e7923880f4..91f08d1e250e4 100644
--- a/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json
+++ b/provisioner/terraform/testdata/mapped-apps/mapped-apps.tfstate.json
@@ -17,11 +17,11 @@
"connection_timeout": 120,
"dir": null,
"env": null,
- "id": "5cb3c105-1a70-4c60-bd32-b75d9a60a98d",
+ "id": "441cfb02-3044-4d2b-ac2c-1ac150ec8c58",
"init_script": "",
"os": "linux",
"startup_script": null,
- "token": "b477690f-0a2d-4d9b-818e-7b60c845c44f",
+ "token": "75ee44b6-4cbb-4615-acb2-19c53f82dc58",
"troubleshooting_url": null
},
"sensitive_values": {}
@@ -35,12 +35,12 @@
"provider_name": "registry.terraform.io/coder/coder",
"schema_version": 0,
"values": {
- "agent_id": "5cb3c105-1a70-4c60-bd32-b75d9a60a98d",
+ "agent_id": "441cfb02-3044-4d2b-ac2c-1ac150ec8c58",
"command": null,
"display_name": "app1",
"healthcheck": [],
"icon": null,
- "id": "72d41f36-d775-424c-9a05-b633da43cd58",
+ "id": "abbc4449-5dd1-4ee0-ac58-3055a6da206b",
"name": null,
"relative_path": null,
"share": "owner",
@@ -64,12 +64,12 @@
"provider_name": "registry.terraform.io/coder/coder",
"schema_version": 0,
"values": {
- "agent_id": "5cb3c105-1a70-4c60-bd32-b75d9a60a98d",
+ "agent_id": "441cfb02-3044-4d2b-ac2c-1ac150ec8c58",
"command": null,
"display_name": "app2",
"healthcheck": [],
"icon": null,
- "id": "810dbeda-3041-403d-86e8-146354ad2657",
+ "id": "ff87411e-4edb-48ce-b62f-6f792d02b25c",
"name": null,
"relative_path": null,
"share": "owner",
@@ -92,7 +92,7 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "1955786418433284816",
+ "id": "3464468981645058362",
"triggers": null
},
"sensitive_values": {},
diff --git a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json
index f4b5ff036e511..ad6eb7118c57b 100644
--- a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json
+++ b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json
@@ -17,7 +17,7 @@
"connection_timeout": 120,
"dir": null,
"env": null,
- "id": "6b912abe-50d4-48b2-be7c-1464ca69b5b9",
+ "id": "7fd8bb3f-704e-4d85-aaaf-1928a9a4df83",
"init_script": "",
"login_before_ready": true,
"motd_file": null,
@@ -26,7 +26,7 @@
"shutdown_script_timeout": 300,
"startup_script": null,
"startup_script_timeout": 300,
- "token": "d296a9cd-6f7c-4c6b-b2f3-7a647512efe8",
+ "token": "ebdd904c-a277-49ec-97cc-e29c7326b475",
"troubleshooting_url": null
},
"sensitive_values": {}
@@ -44,7 +44,7 @@
"connection_timeout": 1,
"dir": null,
"env": null,
- "id": "8a2956f7-d37b-441e-bf62-bd9a45316f6a",
+ "id": "cd35b6c2-3f81-4857-ac8c-9cc0d1d0f0ee",
"init_script": "",
"login_before_ready": true,
"motd_file": "/etc/motd",
@@ -53,7 +53,7 @@
"shutdown_script_timeout": 30,
"startup_script": null,
"startup_script_timeout": 30,
- "token": "b1e0fba4-5bba-439f-b3ea-3f6a8ba4d301",
+ "token": "bbfc3bb1-31c8-42a2-bc5a-3f4ee95eea7b",
"troubleshooting_url": null
},
"sensitive_values": {}
@@ -71,7 +71,7 @@
"connection_timeout": 120,
"dir": null,
"env": null,
- "id": "819b1b19-a709-463e-9aeb-5e1321b7af23",
+ "id": "7407c159-30e7-4c7c-8187-3bf0f6805515",
"init_script": "",
"login_before_ready": false,
"motd_file": null,
@@ -80,7 +80,7 @@
"shutdown_script_timeout": 300,
"startup_script": null,
"startup_script_timeout": 300,
- "token": "238ff017-12ae-403f-b3f8-4dea4dc87a7d",
+ "token": "080070d7-cb08-4634-aa05-7ee07a193441",
"troubleshooting_url": "https://coder.com/troubleshoot"
},
"sensitive_values": {}
@@ -93,7 +93,7 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "5288433022262248914",
+ "id": "235602507221507275",
"triggers": null
},
"sensitive_values": {},
diff --git a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json
index 04c77276e3669..9b10a6a2d50d7 100644
--- a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json
+++ b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfplan.json
@@ -1,6 +1,6 @@
{
"format_version": "1.1",
- "terraform_version": "1.3.3",
+ "terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [
diff --git a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json
index 07c2f8ce17eed..1f27d054930ec 100644
--- a/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json
+++ b/provisioner/terraform/testdata/multiple-apps/multiple-apps.tfstate.json
@@ -1,6 +1,6 @@
{
"format_version": "1.0",
- "terraform_version": "1.3.3",
+ "terraform_version": "1.3.7",
"values": {
"root_module": {
"resources": [
@@ -17,11 +17,11 @@
"connection_timeout": 120,
"dir": null,
"env": null,
- "id": "f911bd98-54fc-476a-aec1-df6e525630a9",
+ "id": "54519a12-e34b-4c4f-aef9-7dfac5f4949b",
"init_script": "",
"os": "linux",
"startup_script": null,
- "token": "fa05ad9c-2062-4707-a27f-12364c89641e",
+ "token": "bf339e89-0594-4f44-83f0-fc7cde9ceb0c",
"troubleshooting_url": null
},
"sensitive_values": {}
@@ -34,12 +34,12 @@
"provider_name": "registry.terraform.io/coder/coder",
"schema_version": 0,
"values": {
- "agent_id": "f911bd98-54fc-476a-aec1-df6e525630a9",
+ "agent_id": "54519a12-e34b-4c4f-aef9-7dfac5f4949b",
"command": null,
"display_name": null,
"healthcheck": [],
"icon": null,
- "id": "038d0f6c-90b7-465b-915a-8a9f0cf21757",
+ "id": "13101247-bdf1-409e-81e2-51a4ff45576b",
"name": null,
"relative_path": null,
"share": "owner",
@@ -62,7 +62,7 @@
"provider_name": "registry.terraform.io/coder/coder",
"schema_version": 0,
"values": {
- "agent_id": "f911bd98-54fc-476a-aec1-df6e525630a9",
+ "agent_id": "54519a12-e34b-4c4f-aef9-7dfac5f4949b",
"command": null,
"display_name": null,
"healthcheck": [
@@ -73,7 +73,7 @@
}
],
"icon": null,
- "id": "c00ec121-a167-4418-8c4e-2ccae0a0cd6e",
+ "id": "ef508497-0437-43eb-b773-c0622582ab5d",
"name": null,
"relative_path": null,
"share": "owner",
@@ -98,12 +98,12 @@
"provider_name": "registry.terraform.io/coder/coder",
"schema_version": 0,
"values": {
- "agent_id": "f911bd98-54fc-476a-aec1-df6e525630a9",
+ "agent_id": "54519a12-e34b-4c4f-aef9-7dfac5f4949b",
"command": null,
"display_name": null,
"healthcheck": [],
"icon": null,
- "id": "e9226aa6-a1a6-42a7-8557-64620cbf3dc2",
+ "id": "2c187306-80cc-46ba-a75c-42d4648ff94a",
"name": null,
"relative_path": null,
"share": "owner",
@@ -126,7 +126,7 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "5577006791947779410",
+ "id": "1264552698255765246",
"triggers": null
},
"sensitive_values": {},
diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf
index 07dcdffdac150..1b8a4abea68ff 100644
--- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf
+++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tf
@@ -2,7 +2,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
- version = "0.6.3"
+ version = "0.7.0"
}
}
}
@@ -10,9 +10,20 @@ terraform {
resource "coder_agent" "main" {
os = "linux"
arch = "amd64"
+ metadata {
+ key = "process_count"
+ display_name = "Process Count"
+ script = "ps -ef | wc -l"
+ interval = 5
+ timeout = 1
+ }
}
-resource "null_resource" "about" {}
+resource "null_resource" "about" {
+ depends_on = [
+ coder_agent.main,
+ ]
+}
resource "coder_metadata" "about_info" {
resource_id = null_resource.about.id
diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.dot b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.dot
index 4bac498b3516c..041734ac4bbc4 100644
--- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.dot
+++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.dot
@@ -9,9 +9,8 @@ digraph {
"[root] provider[\"registry.terraform.io/hashicorp/null\"]" [label = "provider[\"registry.terraform.io/hashicorp/null\"]", shape = "diamond"]
"[root] coder_agent.main (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]"
"[root] coder_metadata.about_info (expand)" -> "[root] null_resource.about (expand)"
- "[root] coder_metadata.about_info (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]"
+ "[root] null_resource.about (expand)" -> "[root] coder_agent.main (expand)"
"[root] null_resource.about (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/null\"]"
- "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_agent.main (expand)"
"[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_metadata.about_info (expand)"
"[root] provider[\"registry.terraform.io/hashicorp/null\"] (close)" -> "[root] null_resource.about (expand)"
"[root] root" -> "[root] provider[\"registry.terraform.io/coder/coder\"] (close)"
diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json
index 6fc6591b820a6..9fae1dcf31951 100644
--- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json
+++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfplan.json
@@ -1,6 +1,6 @@
{
"format_version": "1.1",
- "terraform_version": "1.3.3",
+ "terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [
@@ -17,11 +17,29 @@
"connection_timeout": 120,
"dir": null,
"env": null,
+ "login_before_ready": true,
+ "metadata": [
+ {
+ "display_name": "Process Count",
+ "interval": 5,
+ "key": "process_count",
+ "script": "ps -ef | wc -l",
+ "timeout": 1
+ }
+ ],
+ "motd_file": null,
"os": "linux",
+ "shutdown_script": null,
+ "shutdown_script_timeout": 300,
"startup_script": null,
+ "startup_script_timeout": 300,
"troubleshooting_url": null
},
- "sensitive_values": {}
+ "sensitive_values": {
+ "metadata": [
+ {}
+ ]
+ }
},
{
"address": "coder_metadata.about_info",
@@ -99,17 +117,37 @@
"connection_timeout": 120,
"dir": null,
"env": null,
+ "login_before_ready": true,
+ "metadata": [
+ {
+ "display_name": "Process Count",
+ "interval": 5,
+ "key": "process_count",
+ "script": "ps -ef | wc -l",
+ "timeout": 1
+ }
+ ],
+ "motd_file": null,
"os": "linux",
+ "shutdown_script": null,
+ "shutdown_script_timeout": 300,
"startup_script": null,
+ "startup_script_timeout": 300,
"troubleshooting_url": null
},
"after_unknown": {
"id": true,
"init_script": true,
+ "metadata": [
+ {}
+ ],
"token": true
},
"before_sensitive": false,
"after_sensitive": {
+ "metadata": [
+ {}
+ ],
"token": true
}
}
@@ -208,7 +246,7 @@
"coder": {
"name": "coder",
"full_name": "registry.terraform.io/coder/coder",
- "version_constraint": "0.6.3"
+ "version_constraint": "0.7.0-rc0"
},
"null": {
"name": "null",
@@ -227,6 +265,25 @@
"arch": {
"constant_value": "amd64"
},
+ "metadata": [
+ {
+ "display_name": {
+ "constant_value": "Process Count"
+ },
+ "interval": {
+ "constant_value": 5
+ },
+ "key": {
+ "constant_value": "process_count"
+ },
+ "script": {
+ "constant_value": "ps -ef | wc -l"
+ },
+ "timeout": {
+ "constant_value": 1
+ }
+ }
+ ],
"os": {
"constant_value": "linux"
}
@@ -298,7 +355,10 @@
"type": "null_resource",
"name": "about",
"provider_config_key": "null",
- "schema_version": 0
+ "schema_version": 0,
+ "depends_on": [
+ "coder_agent.main"
+ ]
}
]
}
diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.dot b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.dot
index 4bac498b3516c..041734ac4bbc4 100644
--- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.dot
+++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.dot
@@ -9,9 +9,8 @@ digraph {
"[root] provider[\"registry.terraform.io/hashicorp/null\"]" [label = "provider[\"registry.terraform.io/hashicorp/null\"]", shape = "diamond"]
"[root] coder_agent.main (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]"
"[root] coder_metadata.about_info (expand)" -> "[root] null_resource.about (expand)"
- "[root] coder_metadata.about_info (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]"
+ "[root] null_resource.about (expand)" -> "[root] coder_agent.main (expand)"
"[root] null_resource.about (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/null\"]"
- "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_agent.main (expand)"
"[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_metadata.about_info (expand)"
"[root] provider[\"registry.terraform.io/hashicorp/null\"] (close)" -> "[root] null_resource.about (expand)"
"[root] root" -> "[root] provider[\"registry.terraform.io/coder/coder\"] (close)"
diff --git a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json
index 0b0e51e5286a4..efcf3b98e13ed 100644
--- a/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json
+++ b/provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json
@@ -1,6 +1,6 @@
{
"format_version": "1.0",
- "terraform_version": "1.3.3",
+ "terraform_version": "1.3.7",
"values": {
"root_module": {
"resources": [
@@ -17,14 +17,32 @@
"connection_timeout": 120,
"dir": null,
"env": null,
- "id": "7766b2a9-c00f-4cde-9acc-1fc05651dbdf",
+ "id": "2090d6c6-c4c1-4219-b8f8-9df6db6d5864",
"init_script": "",
+ "login_before_ready": true,
+ "metadata": [
+ {
+ "display_name": "Process Count",
+ "interval": 5,
+ "key": "process_count",
+ "script": "ps -ef | wc -l",
+ "timeout": 1
+ }
+ ],
+ "motd_file": null,
"os": "linux",
+ "shutdown_script": null,
+ "shutdown_script_timeout": 300,
"startup_script": null,
- "token": "5e54c173-a813-4df0-b87d-0617082769dc",
+ "startup_script_timeout": 300,
+ "token": "a984d85d-eff6-4366-9658-9719fb3dd82e",
"troubleshooting_url": null
},
- "sensitive_values": {}
+ "sensitive_values": {
+ "metadata": [
+ {}
+ ]
+ }
},
{
"address": "coder_metadata.about_info",
@@ -37,7 +55,7 @@
"daily_cost": 29,
"hide": true,
"icon": "/icon/server.svg",
- "id": "e43f1cd6-5dbb-4d6b-8942-37f914b37be5",
+ "id": "9e239cb2-e381-423a-bf16-74ece8254eff",
"item": [
{
"is_null": false,
@@ -64,7 +82,7 @@
"value": "squirrel"
}
],
- "resource_id": "5577006791947779410"
+ "resource_id": "3104684855633455084"
},
"sensitive_values": {
"item": [
@@ -75,6 +93,7 @@
]
},
"depends_on": [
+ "coder_agent.main",
"null_resource.about"
]
},
@@ -86,10 +105,13 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "5577006791947779410",
+ "id": "3104684855633455084",
"triggers": null
},
- "sensitive_values": {}
+ "sensitive_values": {},
+ "depends_on": [
+ "coder_agent.main"
+ ]
}
]
}
diff --git a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json
index 899e1617f7726..71b701abde02d 100644
--- a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json
+++ b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfplan.json
@@ -1,6 +1,6 @@
{
"format_version": "1.1",
- "terraform_version": "1.4.0",
+ "terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [
@@ -105,7 +105,7 @@
],
"prior_state": {
"format_version": "1.0",
- "terraform_version": "1.4.0",
+ "terraform_version": "1.3.7",
"values": {
"root_module": {
"resources": [
@@ -120,7 +120,7 @@
"default": null,
"description": null,
"icon": null,
- "id": "5820e575-2637-4830-b6a3-75f4c664b447",
+ "id": "857b1591-ee42-4ded-9804-783ffd1eb180",
"legacy_variable": null,
"legacy_variable_name": null,
"mutable": false,
@@ -162,7 +162,7 @@
"default": "ok",
"description": "blah blah",
"icon": null,
- "id": "9cac2300-0618-45f6-97d9-2f0395b1a0b4",
+ "id": "1477c44d-b36a-48cd-9942-0b532f1791db",
"legacy_variable": null,
"legacy_variable_name": null,
"mutable": false,
diff --git a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json
index 38a1aed88ffcb..268a678601ac4 100644
--- a/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json
+++ b/provisioner/terraform/testdata/rich-parameters/rich-parameters.tfstate.json
@@ -1,9 +1,36 @@
{
"format_version": "1.0",
- "terraform_version": "1.4.0",
+ "terraform_version": "1.3.7",
"values": {
"root_module": {
"resources": [
+ {
+ "address": "coder_agent.dev",
+ "mode": "managed",
+ "type": "coder_agent",
+ "name": "dev",
+ "provider_name": "registry.terraform.io/coder/coder",
+ "schema_version": 0,
+ "values": {
+ "arch": "arm64",
+ "auth": "token",
+ "connection_timeout": 120,
+ "dir": null,
+ "env": null,
+ "id": "c2221717-e813-49f0-a655-4cb7aa5265e2",
+ "init_script": "",
+ "login_before_ready": true,
+ "motd_file": null,
+ "os": "windows",
+ "shutdown_script": null,
+ "shutdown_script_timeout": 300,
+ "startup_script": null,
+ "startup_script_timeout": 300,
+ "token": "fdb94db8-fca1-4a13-bbcb-73bfaec95b77",
+ "troubleshooting_url": null
+ },
+ "sensitive_values": {}
+ },
{
"address": "data.coder_parameter.example",
"mode": "data",
@@ -15,7 +42,7 @@
"default": null,
"description": null,
"icon": null,
- "id": "30b8b963-684d-4c11-9230-a06b81473f6f",
+ "id": "f5f644c9-cb0c-47b1-8e02-d9f6fa99b935",
"legacy_variable": null,
"legacy_variable_name": null,
"mutable": false,
@@ -57,7 +84,7 @@
"default": "ok",
"description": "blah blah",
"icon": null,
- "id": "c40e87d2-7694-40f7-8b7d-30dbf14dd0c0",
+ "id": "e2944252-1c30-43c8-9ce3-53a9755030dc",
"legacy_variable": null,
"legacy_variable_name": null,
"mutable": false,
@@ -70,33 +97,6 @@
},
"sensitive_values": {}
},
- {
- "address": "coder_agent.dev",
- "mode": "managed",
- "type": "coder_agent",
- "name": "dev",
- "provider_name": "registry.terraform.io/coder/coder",
- "schema_version": 0,
- "values": {
- "arch": "arm64",
- "auth": "token",
- "connection_timeout": 120,
- "dir": null,
- "env": null,
- "id": "775b9977-421e-4d4d-8c02-dc38958259e3",
- "init_script": "",
- "login_before_ready": true,
- "motd_file": null,
- "os": "windows",
- "shutdown_script": null,
- "shutdown_script_timeout": 300,
- "startup_script": null,
- "startup_script_timeout": 300,
- "token": "927e1872-90d0-43a2-9a55-a8438ead0ad3",
- "troubleshooting_url": null
- },
- "sensitive_values": {}
- },
{
"address": "null_resource.dev",
"mode": "managed",
@@ -105,7 +105,7 @@
"provider_name": "registry.terraform.io/hashicorp/null",
"schema_version": 0,
"values": {
- "id": "3727779938861599093",
+ "id": "5032149403215603103",
"triggers": null
},
"sensitive_values": {},
diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go
index 013f5fa054ed5..e8061dc426f30 100644
--- a/provisionersdk/proto/provisioner.pb.go
+++ b/provisionersdk/proto/provisioner.pb.go
@@ -1252,14 +1252,15 @@ type Agent struct {
//
// *Agent_Token
// *Agent_InstanceId
- Auth isAgent_Auth `protobuf_oneof:"auth"`
- ConnectionTimeoutSeconds int32 `protobuf:"varint,11,opt,name=connection_timeout_seconds,json=connectionTimeoutSeconds,proto3" json:"connection_timeout_seconds,omitempty"`
- TroubleshootingUrl string `protobuf:"bytes,12,opt,name=troubleshooting_url,json=troubleshootingUrl,proto3" json:"troubleshooting_url,omitempty"`
- MotdFile string `protobuf:"bytes,13,opt,name=motd_file,json=motdFile,proto3" json:"motd_file,omitempty"`
- LoginBeforeReady bool `protobuf:"varint,14,opt,name=login_before_ready,json=loginBeforeReady,proto3" json:"login_before_ready,omitempty"`
- StartupScriptTimeoutSeconds int32 `protobuf:"varint,15,opt,name=startup_script_timeout_seconds,json=startupScriptTimeoutSeconds,proto3" json:"startup_script_timeout_seconds,omitempty"`
- ShutdownScript string `protobuf:"bytes,16,opt,name=shutdown_script,json=shutdownScript,proto3" json:"shutdown_script,omitempty"`
- ShutdownScriptTimeoutSeconds int32 `protobuf:"varint,17,opt,name=shutdown_script_timeout_seconds,json=shutdownScriptTimeoutSeconds,proto3" json:"shutdown_script_timeout_seconds,omitempty"`
+ Auth isAgent_Auth `protobuf_oneof:"auth"`
+ ConnectionTimeoutSeconds int32 `protobuf:"varint,11,opt,name=connection_timeout_seconds,json=connectionTimeoutSeconds,proto3" json:"connection_timeout_seconds,omitempty"`
+ TroubleshootingUrl string `protobuf:"bytes,12,opt,name=troubleshooting_url,json=troubleshootingUrl,proto3" json:"troubleshooting_url,omitempty"`
+ MotdFile string `protobuf:"bytes,13,opt,name=motd_file,json=motdFile,proto3" json:"motd_file,omitempty"`
+ LoginBeforeReady bool `protobuf:"varint,14,opt,name=login_before_ready,json=loginBeforeReady,proto3" json:"login_before_ready,omitempty"`
+ StartupScriptTimeoutSeconds int32 `protobuf:"varint,15,opt,name=startup_script_timeout_seconds,json=startupScriptTimeoutSeconds,proto3" json:"startup_script_timeout_seconds,omitempty"`
+ ShutdownScript string `protobuf:"bytes,16,opt,name=shutdown_script,json=shutdownScript,proto3" json:"shutdown_script,omitempty"`
+ ShutdownScriptTimeoutSeconds int32 `protobuf:"varint,17,opt,name=shutdown_script_timeout_seconds,json=shutdownScriptTimeoutSeconds,proto3" json:"shutdown_script_timeout_seconds,omitempty"`
+ Metadata []*Agent_Metadata `protobuf:"bytes,18,rep,name=metadata,proto3" json:"metadata,omitempty"`
}
func (x *Agent) Reset() {
@@ -1420,6 +1421,13 @@ func (x *Agent) GetShutdownScriptTimeoutSeconds() int32 {
return 0
}
+func (x *Agent) GetMetadata() []*Agent_Metadata {
+ if x != nil {
+ return x.Metadata
+ }
+ return nil
+}
+
type isAgent_Auth interface {
isAgent_Auth()
}
@@ -1797,6 +1805,85 @@ func (*Provision) Descriptor() ([]byte, []int) {
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{18}
}
+type Agent_Metadata struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+ DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"`
+ Script string `protobuf:"bytes,3,opt,name=script,proto3" json:"script,omitempty"`
+ Interval int64 `protobuf:"varint,4,opt,name=interval,proto3" json:"interval,omitempty"`
+ Timeout int64 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"`
+}
+
+func (x *Agent_Metadata) Reset() {
+ *x = Agent_Metadata{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Agent_Metadata) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Agent_Metadata) ProtoMessage() {}
+
+func (x *Agent_Metadata) ProtoReflect() protoreflect.Message {
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Agent_Metadata.ProtoReflect.Descriptor instead.
+func (*Agent_Metadata) Descriptor() ([]byte, []int) {
+ return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{13, 0}
+}
+
+func (x *Agent_Metadata) GetKey() string {
+ if x != nil {
+ return x.Key
+ }
+ return ""
+}
+
+func (x *Agent_Metadata) GetDisplayName() string {
+ if x != nil {
+ return x.DisplayName
+ }
+ return ""
+}
+
+func (x *Agent_Metadata) GetScript() string {
+ if x != nil {
+ return x.Script
+ }
+ return ""
+}
+
+func (x *Agent_Metadata) GetInterval() int64 {
+ if x != nil {
+ return x.Interval
+ }
+ return 0
+}
+
+func (x *Agent_Metadata) GetTimeout() int64 {
+ if x != nil {
+ return x.Timeout
+ }
+ return 0
+}
+
type Resource_Metadata struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1811,7 +1898,7 @@ type Resource_Metadata struct {
func (x *Resource_Metadata) Reset() {
*x = Resource_Metadata{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1824,7 +1911,7 @@ func (x *Resource_Metadata) String() string {
func (*Resource_Metadata) ProtoMessage() {}
func (x *Resource_Metadata) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1879,7 +1966,7 @@ type Parse_Request struct {
func (x *Parse_Request) Reset() {
*x = Parse_Request{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1892,7 +1979,7 @@ func (x *Parse_Request) String() string {
func (*Parse_Request) ProtoMessage() {}
func (x *Parse_Request) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1927,7 +2014,7 @@ type Parse_Complete struct {
func (x *Parse_Complete) Reset() {
*x = Parse_Complete{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1940,7 +2027,7 @@ func (x *Parse_Complete) String() string {
func (*Parse_Complete) ProtoMessage() {}
func (x *Parse_Complete) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1985,7 +2072,7 @@ type Parse_Response struct {
func (x *Parse_Response) Reset() {
*x = Parse_Response{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1998,7 +2085,7 @@ func (x *Parse_Response) String() string {
func (*Parse_Response) ProtoMessage() {}
func (x *Parse_Response) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2071,7 +2158,7 @@ type Provision_Metadata struct {
func (x *Provision_Metadata) Reset() {
*x = Provision_Metadata{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2084,7 +2171,7 @@ func (x *Provision_Metadata) String() string {
func (*Provision_Metadata) ProtoMessage() {}
func (x *Provision_Metadata) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2186,7 +2273,7 @@ type Provision_Config struct {
func (x *Provision_Config) Reset() {
*x = Provision_Config{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2199,7 +2286,7 @@ func (x *Provision_Config) String() string {
func (*Provision_Config) ProtoMessage() {}
func (x *Provision_Config) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2258,7 +2345,7 @@ type Provision_Plan struct {
func (x *Provision_Plan) Reset() {
*x = Provision_Plan{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2271,7 +2358,7 @@ func (x *Provision_Plan) String() string {
func (*Provision_Plan) ProtoMessage() {}
func (x *Provision_Plan) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2334,7 +2421,7 @@ type Provision_Apply struct {
func (x *Provision_Apply) Reset() {
*x = Provision_Apply{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2347,7 +2434,7 @@ func (x *Provision_Apply) String() string {
func (*Provision_Apply) ProtoMessage() {}
func (x *Provision_Apply) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2386,7 +2473,7 @@ type Provision_Cancel struct {
func (x *Provision_Cancel) Reset() {
*x = Provision_Cancel{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2399,7 +2486,7 @@ func (x *Provision_Cancel) String() string {
func (*Provision_Cancel) ProtoMessage() {}
func (x *Provision_Cancel) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[29]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2431,7 +2518,7 @@ type Provision_Request struct {
func (x *Provision_Request) Reset() {
*x = Provision_Request{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[29]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2444,7 +2531,7 @@ func (x *Provision_Request) String() string {
func (*Provision_Request) ProtoMessage() {}
func (x *Provision_Request) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[29]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[30]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2526,7 +2613,7 @@ type Provision_Complete struct {
func (x *Provision_Complete) Reset() {
*x = Provision_Complete{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[30]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2539,7 +2626,7 @@ func (x *Provision_Complete) String() string {
func (*Provision_Complete) ProtoMessage() {}
func (x *Provision_Complete) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[30]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[31]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2612,7 +2699,7 @@ type Provision_Response struct {
func (x *Provision_Response) Reset() {
*x = Provision_Response{}
if protoimpl.UnsafeEnabled {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[31]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2625,7 +2712,7 @@ func (x *Provision_Response) String() string {
func (*Provision_Response) ProtoMessage() {}
func (x *Provision_Response) ProtoReflect() protoreflect.Message {
- mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[31]
+ mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[32]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2827,7 +2914,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a,
0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
- 0x22, 0xfe, 0x05, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
+ 0x22, 0xc7, 0x07, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d,
0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72,
@@ -2871,210 +2958,223 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x11, 0x20,
0x01, 0x28, 0x05, 0x52, 0x1c, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x53, 0x63, 0x72,
0x69, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64,
- 0x73, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
- 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
- 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
- 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74,
- 0x68, 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75,
- 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a,
- 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65,
- 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
- 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72,
- 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04,
- 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e,
- 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20,
- 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a,
- 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20,
- 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
- 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68,
- 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68,
- 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28,
- 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
- 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52,
- 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a,
- 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52,
- 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61,
- 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18,
- 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e,
- 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e,
- 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68,
- 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73,
- 0x68, 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
- 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
- 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65,
- 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76,
- 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61,
- 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
- 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
- 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d,
- 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
- 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
- 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73,
- 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d,
- 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01,
- 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x1a, 0x69, 0x0a,
- 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
- 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
- 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
- 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03,
- 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12,
- 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08,
- 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xcb, 0x02, 0x0a, 0x05, 0x50, 0x61, 0x72,
- 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a,
- 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0xa3, 0x01, 0x0a, 0x08,
- 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70,
- 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x01,
- 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
- 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61,
- 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72,
- 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65,
- 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
- 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
- 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52,
- 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61,
- 0x73, 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a,
- 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f,
- 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03,
- 0x6c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18,
- 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
- 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65,
- 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06,
- 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x90, 0x0d, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69,
- 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xeb, 0x03, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
- 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01,
- 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53,
- 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e,
- 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70,
- 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73,
- 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13,
- 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74,
- 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
- 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72,
- 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f,
- 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77,
- 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
- 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73,
- 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70,
- 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01,
- 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e,
- 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
- 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77,
- 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70,
- 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52,
- 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a,
- 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
- 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
- 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, 0x6f, 0x72, 0x6b,
- 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x69, 0x64, 0x63,
- 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77,
- 0x6e, 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b,
- 0x65, 0x6e, 0x1a, 0xad, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a,
- 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73,
- 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74,
- 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20,
- 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
- 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61,
- 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x32,
- 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f,
- 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70,
- 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76,
- 0x65, 0x6c, 0x1a, 0xeb, 0x02, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x35, 0x0a, 0x06, 0x63,
- 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72,
- 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73,
- 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66,
- 0x69, 0x67, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f,
- 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70,
- 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d,
- 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d,
- 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69,
- 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c,
- 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76,
- 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61,
- 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68,
- 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12,
- 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75,
- 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
- 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56,
- 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61,
- 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68,
- 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b,
- 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x47,
- 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x10,
- 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73,
- 0x1a, 0x52, 0x0a, 0x05, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e,
- 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76,
- 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
- 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
- 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04,
- 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0xb3,
- 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x04, 0x70, 0x6c,
- 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
- 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
- 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x34, 0x0a,
- 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70,
+ 0x73, 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x12, 0x20,
+ 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
+ 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+ 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x8d, 0x01, 0x0a, 0x08, 0x4d,
+ 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73,
+ 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06,
+ 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63,
+ 0x72, 0x69, 0x70, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
+ 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
+ 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28,
+ 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e,
+ 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
+ 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
+ 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41,
+ 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61,
+ 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69,
+ 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d,
+ 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d,
+ 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62,
+ 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75,
+ 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74,
+ 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70,
+ 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74,
+ 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68,
+ 0x65, 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c,
+ 0x65, 0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f,
+ 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72,
+ 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e,
+ 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e,
+ 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e,
+ 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63,
+ 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
+ 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18,
+ 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12,
+ 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01,
+ 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02,
+ 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
+ 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12,
+ 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79,
+ 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03,
+ 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
+ 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a,
+ 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b,
+ 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52,
+ 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+ 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69,
+ 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12,
+ 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63,
+ 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74,
+ 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61,
+ 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79,
+ 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69,
+ 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
+ 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65,
+ 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73,
+ 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e,
+ 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c,
+ 0x6c, 0x22, 0xcb, 0x02, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52,
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
+ 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63,
+ 0x74, 0x6f, 0x72, 0x79, 0x1a, 0xa3, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74,
+ 0x65, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61,
+ 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e,
+ 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70,
+ 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65,
+ 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12,
+ 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68,
+ 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f,
+ 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74,
+ 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65,
+ 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65,
+ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
+ 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08,
+ 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b,
+ 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72,
+ 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63,
+ 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22,
+ 0x90, 0x0d, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xeb, 0x03,
+ 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f,
+ 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63,
+ 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73,
+ 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18,
+ 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
+ 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61,
+ 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
+ 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e,
+ 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e,
+ 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
+ 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f,
+ 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c,
+ 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12,
+ 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e,
+ 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72,
+ 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a,
+ 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72,
+ 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f,
+ 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69,
+ 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61,
+ 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61,
+ 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61,
+ 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
+ 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f,
+ 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73,
+ 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f,
+ 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63,
+ 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0xad, 0x01, 0x0a, 0x06,
+ 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
+ 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63,
+ 0x74, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20,
+ 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65,
+ 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70,
+ 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69,
+ 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d,
+ 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69,
+ 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c,
+ 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
+ 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x1a, 0xeb, 0x02, 0x0a, 0x04,
+ 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
+ 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e,
+ 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x46, 0x0a, 0x10, 0x70,
+ 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18,
+ 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
+ 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c,
+ 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c,
+ 0x75, 0x65, 0x73, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61,
+ 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03,
+ 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
+ 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61,
+ 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74,
+ 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69,
+ 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
+ 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
+ 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76,
+ 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4a, 0x0a,
+ 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64,
+ 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76,
+ 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50,
+ 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68,
+ 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x52, 0x0a, 0x05, 0x41, 0x70, 0x70,
+ 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
+ 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+ 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61,
+ 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x08, 0x0a,
+ 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0xb3, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75,
+ 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
+ 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x48, 0x00,
+ 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x34, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18,
+ 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
+ 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x70,
+ 0x70, 0x6c, 0x79, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06,
+ 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69,
- 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70,
- 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x03, 0x20,
- 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
- 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x6e, 0x63,
- 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04,
- 0x74, 0x79, 0x70, 0x65, 0x1a, 0xe9, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74,
- 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
- 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
- 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a,
- 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
- 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52,
- 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
- 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73,
- 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
- 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74,
- 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2c,
- 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69,
- 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41,
- 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04,
- 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e,
- 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03,
- 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76,
- 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c,
- 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02,
- 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
- 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d,
- 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74,
- 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67,
- 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00,
- 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49,
- 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12,
- 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70,
- 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a,
- 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48,
- 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50,
- 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73,
- 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09,
- 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f,
- 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02,
- 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
- 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76,
- 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65,
- 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
- 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
- 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
- 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
- 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
- 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
- 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
- 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
- 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72,
- 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f,
- 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63,
+ 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0xe9, 0x01,
+ 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74,
+ 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65,
+ 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
+ 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76,
+ 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
+ 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70,
+ 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
+ 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69,
+ 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72,
+ 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61,
+ 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20,
+ 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76,
+ 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x06, 0x20,
+ 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
+ 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63,
+ 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e,
+ 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76,
+ 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00,
+ 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79,
+ 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09,
+ 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42,
+ 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08,
+ 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f,
+ 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e,
+ 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10,
+ 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54,
+ 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02,
+ 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61,
+ 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54,
+ 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07,
+ 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72,
+ 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72,
+ 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
+ 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b,
+ 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72,
+ 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a,
+ 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f,
+ 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
+ 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f,
+ 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
+ 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42,
+ 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f,
+ 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
+ 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -3090,7 +3190,7 @@ func file_provisionersdk_proto_provisioner_proto_rawDescGZIP() []byte {
}
var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 6)
-var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 32)
+var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 33)
var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{
(LogLevel)(0), // 0: provisioner.LogLevel
(AppSharingLevel)(0), // 1: provisioner.AppSharingLevel
@@ -3117,19 +3217,20 @@ var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{
(*Resource)(nil), // 22: provisioner.Resource
(*Parse)(nil), // 23: provisioner.Parse
(*Provision)(nil), // 24: provisioner.Provision
- nil, // 25: provisioner.Agent.EnvEntry
- (*Resource_Metadata)(nil), // 26: provisioner.Resource.Metadata
- (*Parse_Request)(nil), // 27: provisioner.Parse.Request
- (*Parse_Complete)(nil), // 28: provisioner.Parse.Complete
- (*Parse_Response)(nil), // 29: provisioner.Parse.Response
- (*Provision_Metadata)(nil), // 30: provisioner.Provision.Metadata
- (*Provision_Config)(nil), // 31: provisioner.Provision.Config
- (*Provision_Plan)(nil), // 32: provisioner.Provision.Plan
- (*Provision_Apply)(nil), // 33: provisioner.Provision.Apply
- (*Provision_Cancel)(nil), // 34: provisioner.Provision.Cancel
- (*Provision_Request)(nil), // 35: provisioner.Provision.Request
- (*Provision_Complete)(nil), // 36: provisioner.Provision.Complete
- (*Provision_Response)(nil), // 37: provisioner.Provision.Response
+ (*Agent_Metadata)(nil), // 25: provisioner.Agent.Metadata
+ nil, // 26: provisioner.Agent.EnvEntry
+ (*Resource_Metadata)(nil), // 27: provisioner.Resource.Metadata
+ (*Parse_Request)(nil), // 28: provisioner.Parse.Request
+ (*Parse_Complete)(nil), // 29: provisioner.Parse.Complete
+ (*Parse_Response)(nil), // 30: provisioner.Parse.Response
+ (*Provision_Metadata)(nil), // 31: provisioner.Provision.Metadata
+ (*Provision_Config)(nil), // 32: provisioner.Provision.Config
+ (*Provision_Plan)(nil), // 33: provisioner.Provision.Plan
+ (*Provision_Apply)(nil), // 34: provisioner.Provision.Apply
+ (*Provision_Cancel)(nil), // 35: provisioner.Provision.Cancel
+ (*Provision_Request)(nil), // 36: provisioner.Provision.Request
+ (*Provision_Complete)(nil), // 37: provisioner.Provision.Complete
+ (*Provision_Response)(nil), // 38: provisioner.Provision.Response
}
var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{
3, // 0: provisioner.ParameterSource.scheme:type_name -> provisioner.ParameterSource.Scheme
@@ -3140,40 +3241,41 @@ var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{
5, // 5: provisioner.ParameterSchema.validation_type_system:type_name -> provisioner.ParameterSchema.TypeSystem
12, // 6: provisioner.RichParameter.options:type_name -> provisioner.RichParameterOption
0, // 7: provisioner.Log.level:type_name -> provisioner.LogLevel
- 25, // 8: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry
+ 26, // 8: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry
20, // 9: provisioner.Agent.apps:type_name -> provisioner.App
- 21, // 10: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck
- 1, // 11: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel
- 19, // 12: provisioner.Resource.agents:type_name -> provisioner.Agent
- 26, // 13: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata
- 11, // 14: provisioner.Parse.Complete.template_variables:type_name -> provisioner.TemplateVariable
- 10, // 15: provisioner.Parse.Complete.parameter_schemas:type_name -> provisioner.ParameterSchema
- 16, // 16: provisioner.Parse.Response.log:type_name -> provisioner.Log
- 28, // 17: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete
- 2, // 18: provisioner.Provision.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition
- 30, // 19: provisioner.Provision.Config.metadata:type_name -> provisioner.Provision.Metadata
- 31, // 20: provisioner.Provision.Plan.config:type_name -> provisioner.Provision.Config
- 9, // 21: provisioner.Provision.Plan.parameter_values:type_name -> provisioner.ParameterValue
- 14, // 22: provisioner.Provision.Plan.rich_parameter_values:type_name -> provisioner.RichParameterValue
- 15, // 23: provisioner.Provision.Plan.variable_values:type_name -> provisioner.VariableValue
- 18, // 24: provisioner.Provision.Plan.git_auth_providers:type_name -> provisioner.GitAuthProvider
- 31, // 25: provisioner.Provision.Apply.config:type_name -> provisioner.Provision.Config
- 32, // 26: provisioner.Provision.Request.plan:type_name -> provisioner.Provision.Plan
- 33, // 27: provisioner.Provision.Request.apply:type_name -> provisioner.Provision.Apply
- 34, // 28: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel
- 22, // 29: provisioner.Provision.Complete.resources:type_name -> provisioner.Resource
- 13, // 30: provisioner.Provision.Complete.parameters:type_name -> provisioner.RichParameter
- 16, // 31: provisioner.Provision.Response.log:type_name -> provisioner.Log
- 36, // 32: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete
- 27, // 33: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request
- 35, // 34: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request
- 29, // 35: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response
- 37, // 36: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response
- 35, // [35:37] is the sub-list for method output_type
- 33, // [33:35] is the sub-list for method input_type
- 33, // [33:33] is the sub-list for extension type_name
- 33, // [33:33] is the sub-list for extension extendee
- 0, // [0:33] is the sub-list for field type_name
+ 25, // 10: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata
+ 21, // 11: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck
+ 1, // 12: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel
+ 19, // 13: provisioner.Resource.agents:type_name -> provisioner.Agent
+ 27, // 14: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata
+ 11, // 15: provisioner.Parse.Complete.template_variables:type_name -> provisioner.TemplateVariable
+ 10, // 16: provisioner.Parse.Complete.parameter_schemas:type_name -> provisioner.ParameterSchema
+ 16, // 17: provisioner.Parse.Response.log:type_name -> provisioner.Log
+ 29, // 18: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete
+ 2, // 19: provisioner.Provision.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition
+ 31, // 20: provisioner.Provision.Config.metadata:type_name -> provisioner.Provision.Metadata
+ 32, // 21: provisioner.Provision.Plan.config:type_name -> provisioner.Provision.Config
+ 9, // 22: provisioner.Provision.Plan.parameter_values:type_name -> provisioner.ParameterValue
+ 14, // 23: provisioner.Provision.Plan.rich_parameter_values:type_name -> provisioner.RichParameterValue
+ 15, // 24: provisioner.Provision.Plan.variable_values:type_name -> provisioner.VariableValue
+ 18, // 25: provisioner.Provision.Plan.git_auth_providers:type_name -> provisioner.GitAuthProvider
+ 32, // 26: provisioner.Provision.Apply.config:type_name -> provisioner.Provision.Config
+ 33, // 27: provisioner.Provision.Request.plan:type_name -> provisioner.Provision.Plan
+ 34, // 28: provisioner.Provision.Request.apply:type_name -> provisioner.Provision.Apply
+ 35, // 29: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel
+ 22, // 30: provisioner.Provision.Complete.resources:type_name -> provisioner.Resource
+ 13, // 31: provisioner.Provision.Complete.parameters:type_name -> provisioner.RichParameter
+ 16, // 32: provisioner.Provision.Response.log:type_name -> provisioner.Log
+ 37, // 33: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete
+ 28, // 34: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request
+ 36, // 35: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request
+ 30, // 36: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response
+ 38, // 37: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response
+ 36, // [36:38] is the sub-list for method output_type
+ 34, // [34:36] is the sub-list for method input_type
+ 34, // [34:34] is the sub-list for extension type_name
+ 34, // [34:34] is the sub-list for extension extendee
+ 0, // [0:34] is the sub-list for field type_name
}
func init() { file_provisionersdk_proto_provisioner_proto_init() }
@@ -3410,8 +3512,8 @@ func file_provisionersdk_proto_provisioner_proto_init() {
return nil
}
}
- file_provisionersdk_proto_provisioner_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*Resource_Metadata); i {
+ file_provisionersdk_proto_provisioner_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Agent_Metadata); i {
case 0:
return &v.state
case 1:
@@ -3423,7 +3525,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
}
}
file_provisionersdk_proto_provisioner_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*Parse_Request); i {
+ switch v := v.(*Resource_Metadata); i {
case 0:
return &v.state
case 1:
@@ -3435,7 +3537,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
}
}
file_provisionersdk_proto_provisioner_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*Parse_Complete); i {
+ switch v := v.(*Parse_Request); i {
case 0:
return &v.state
case 1:
@@ -3447,7 +3549,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
}
}
file_provisionersdk_proto_provisioner_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*Parse_Response); i {
+ switch v := v.(*Parse_Complete); i {
case 0:
return &v.state
case 1:
@@ -3459,7 +3561,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
}
}
file_provisionersdk_proto_provisioner_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*Provision_Metadata); i {
+ switch v := v.(*Parse_Response); i {
case 0:
return &v.state
case 1:
@@ -3471,7 +3573,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
}
}
file_provisionersdk_proto_provisioner_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*Provision_Config); i {
+ switch v := v.(*Provision_Metadata); i {
case 0:
return &v.state
case 1:
@@ -3483,7 +3585,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
}
}
file_provisionersdk_proto_provisioner_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*Provision_Plan); i {
+ switch v := v.(*Provision_Config); i {
case 0:
return &v.state
case 1:
@@ -3495,7 +3597,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
}
}
file_provisionersdk_proto_provisioner_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*Provision_Apply); i {
+ switch v := v.(*Provision_Plan); i {
case 0:
return &v.state
case 1:
@@ -3507,7 +3609,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
}
}
file_provisionersdk_proto_provisioner_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*Provision_Cancel); i {
+ switch v := v.(*Provision_Apply); i {
case 0:
return &v.state
case 1:
@@ -3519,7 +3621,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
}
}
file_provisionersdk_proto_provisioner_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*Provision_Request); i {
+ switch v := v.(*Provision_Cancel); i {
case 0:
return &v.state
case 1:
@@ -3531,7 +3633,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
}
}
file_provisionersdk_proto_provisioner_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*Provision_Complete); i {
+ switch v := v.(*Provision_Request); i {
case 0:
return &v.state
case 1:
@@ -3543,6 +3645,18 @@ func file_provisionersdk_proto_provisioner_proto_init() {
}
}
file_provisionersdk_proto_provisioner_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Provision_Complete); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_provisionersdk_proto_provisioner_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Provision_Response); i {
case 0:
return &v.state
@@ -3559,16 +3673,16 @@ func file_provisionersdk_proto_provisioner_proto_init() {
(*Agent_Token)(nil),
(*Agent_InstanceId)(nil),
}
- file_provisionersdk_proto_provisioner_proto_msgTypes[23].OneofWrappers = []interface{}{
+ file_provisionersdk_proto_provisioner_proto_msgTypes[24].OneofWrappers = []interface{}{
(*Parse_Response_Log)(nil),
(*Parse_Response_Complete)(nil),
}
- file_provisionersdk_proto_provisioner_proto_msgTypes[29].OneofWrappers = []interface{}{
+ file_provisionersdk_proto_provisioner_proto_msgTypes[30].OneofWrappers = []interface{}{
(*Provision_Request_Plan)(nil),
(*Provision_Request_Apply)(nil),
(*Provision_Request_Cancel)(nil),
}
- file_provisionersdk_proto_provisioner_proto_msgTypes[31].OneofWrappers = []interface{}{
+ file_provisionersdk_proto_provisioner_proto_msgTypes[32].OneofWrappers = []interface{}{
(*Provision_Response_Log)(nil),
(*Provision_Response_Complete)(nil),
}
@@ -3578,7 +3692,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_provisionersdk_proto_provisioner_proto_rawDesc,
NumEnums: 6,
- NumMessages: 32,
+ NumMessages: 33,
NumExtensions: 0,
NumServices: 1,
},
diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto
index 135c616d35f14..5737ade95b7fe 100644
--- a/provisionersdk/proto/provisioner.proto
+++ b/provisionersdk/proto/provisioner.proto
@@ -127,6 +127,13 @@ message GitAuthProvider {
// Agent represents a running agent on the workspace.
message Agent {
+ message Metadata {
+ string key = 1;
+ string display_name = 2;
+ string script = 3;
+ int64 interval = 4;
+ int64 timeout = 5;
+ }
string id = 1;
string name = 2;
map env = 3;
@@ -146,6 +153,7 @@ message Agent {
int32 startup_script_timeout_seconds = 15;
string shutdown_script = 16;
int32 shutdown_script_timeout_seconds = 17;
+ repeated Metadata metadata = 18;
}
enum AppSharingLevel {
diff --git a/site/package.json b/site/package.json
index 3e9e4b7df7044..a47bd32efad13 100644
--- a/site/package.json
+++ b/site/package.json
@@ -138,6 +138,8 @@
"prettier": "2.8.1",
"resize-observer": "1.0.4",
"semver": "7.3.7",
+ "storybook-addon-mock": "^3.2.0",
+ "storybook-react-context": "^0.6.0",
"typescript": "4.8.2"
},
"browserslist": [
diff --git a/site/src/api/api.ts b/site/src/api/api.ts
index 190ec4baae23b..b686b2936594e 100644
--- a/site/src/api/api.ts
+++ b/site/src/api/api.ts
@@ -1046,6 +1046,18 @@ const getMissingParameters = (
return missingParameters
}
+/**
+ *
+ * @param agentId
+ * @returns An EventSource that emits agent metadata event objects (ServerSentEvent)
+ */
+export const watchAgentMetadata = (agentId: string): EventSource => {
+ return new EventSource(
+ `${location.protocol}//${location.host}/api/v2/workspaceagents/${agentId}/watch-metadata`,
+ { withCredentials: true },
+ )
+}
+
export const watchBuildLogs = (
versionId: string,
onMessage: (log: TypesGen.ProvisionerJobLog) => void,
diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts
index f1b40dddc60cd..1bb3a823548b4 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -1086,6 +1086,29 @@ export interface WorkspaceAgentListeningPortsResponse {
readonly ports: WorkspaceAgentListeningPort[]
}
+// From codersdk/workspaceagents.go
+export interface WorkspaceAgentMetadata {
+ readonly result: WorkspaceAgentMetadataResult
+ readonly description: WorkspaceAgentMetadataDescription
+}
+
+// From codersdk/workspaceagents.go
+export interface WorkspaceAgentMetadataDescription {
+ readonly display_name: string
+ readonly key: string
+ readonly script: string
+ readonly interval: number
+ readonly timeout: number
+}
+
+// From codersdk/workspaceagents.go
+export interface WorkspaceAgentMetadataResult {
+ readonly collected_at: string
+ readonly age: number
+ readonly value: string
+ readonly error: string
+}
+
// From codersdk/workspaceagents.go
export interface WorkspaceAgentStartupLog {
readonly id: number
diff --git a/site/src/components/Resources/AgentMetadata.stories.tsx b/site/src/components/Resources/AgentMetadata.stories.tsx
new file mode 100644
index 0000000000000..8b6fc80a9f683
--- /dev/null
+++ b/site/src/components/Resources/AgentMetadata.stories.tsx
@@ -0,0 +1,107 @@
+import { Story } from "@storybook/react"
+import {
+ WorkspaceAgentMetadataDescription,
+ WorkspaceAgentMetadataResult,
+} from "api/typesGenerated"
+import { AgentMetadataView, AgentMetadataViewProps } from "./AgentMetadata"
+
+export default {
+ title: "components/AgentMetadata",
+ component: AgentMetadataView,
+}
+
+const Template: Story = (args) => (
+
+)
+
+const resultDefaults: WorkspaceAgentMetadataResult = {
+ collected_at: "2021-05-05T00:00:00Z",
+ error: "",
+ value: "defvalue",
+ age: 5,
+}
+
+const descriptionDefaults: WorkspaceAgentMetadataDescription = {
+ display_name: "DisPlay",
+ key: "defkey",
+ interval: 10,
+ timeout: 10,
+ script: "some command",
+}
+
+export const Example = Template.bind({})
+Example.args = {
+ metadata: [
+ {
+ result: {
+ ...resultDefaults,
+ value: "110%",
+ },
+ description: {
+ ...descriptionDefaults,
+ display_name: "CPU",
+ key: "CPU",
+ },
+ },
+ {
+ result: {
+ ...resultDefaults,
+ value: "50GB",
+ },
+ description: {
+ ...descriptionDefaults,
+ display_name: "Memory",
+ key: "Memory",
+ },
+ },
+ {
+ result: {
+ ...resultDefaults,
+ value: "cant see it",
+ age: 300,
+ },
+ description: {
+ ...descriptionDefaults,
+ interval: 5,
+ display_name: "Stale",
+ key: "stale",
+ },
+ },
+ {
+ result: {
+ ...resultDefaults,
+ value: "oops",
+ error: "fatal error",
+ },
+ description: {
+ ...descriptionDefaults,
+ display_name: "Error",
+ },
+ },
+ {
+ result: {
+ ...resultDefaults,
+ value: "oops",
+ error: "fatal error",
+ },
+ description: {
+ ...descriptionDefaults,
+ display_name: "Error",
+ key: "stale",
+ },
+ },
+ {
+ result: {
+ ...resultDefaults,
+ value: "",
+ collected_at: "0001-01-01T00:00:00Z",
+ age: 1000000,
+ },
+ description: {
+ ...descriptionDefaults,
+ display_name: "Never loads",
+ key: "nloads",
+ },
+ },
+ ],
+}
diff --git a/site/src/components/Resources/AgentMetadata.tsx b/site/src/components/Resources/AgentMetadata.tsx
new file mode 100644
index 0000000000000..df70543a30351
--- /dev/null
+++ b/site/src/components/Resources/AgentMetadata.tsx
@@ -0,0 +1,319 @@
+import Popover from "@material-ui/core/Popover"
+import CircularProgress from "@material-ui/core/CircularProgress"
+import makeStyles from "@material-ui/core/styles/makeStyles"
+import { watchAgentMetadata } from "api/api"
+import { WorkspaceAgent, WorkspaceAgentMetadata } from "api/typesGenerated"
+import { CodeExample } from "components/CodeExample/CodeExample"
+import { Stack } from "components/Stack/Stack"
+import {
+ HelpTooltipText,
+ HelpTooltipTitle,
+} from "components/Tooltips/HelpTooltip"
+import dayjs from "dayjs"
+import {
+ createContext,
+ FC,
+ PropsWithChildren,
+ useContext,
+ useEffect,
+ useRef,
+ useState,
+} from "react"
+
+export const WatchAgentMetadataContext = createContext(watchAgentMetadata)
+
+const MetadataItemValue: FC<
+ PropsWithChildren<{ item: WorkspaceAgentMetadata }>
+> = ({ item, children }) => {
+ const [isOpen, setIsOpen] = useState(false)
+ const anchorRef = useRef(null)
+ const styles = useStyles()
+ return (
+ <>
+ setIsOpen(true)}
+ role="presentation"
+ >
+ {children}
+
+ setIsOpen(false)}
+ PaperProps={{
+ onMouseEnter: () => setIsOpen(true),
+ onMouseLeave: () => setIsOpen(false),
+ }}
+ classes={{ paper: styles.metadataPopover }}
+ >
+ {item.description.display_name}
+ {item.result.value.length > 0 && (
+ <>
+ Last result:
+
+
+
+ >
+ )}
+ {item.result.error.length > 0 && (
+ <>
+ Last error:
+
+
+
+ >
+ )}
+
+ >
+ )
+}
+
+const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
+ const styles = useStyles()
+
+ const [isOpen, setIsOpen] = useState(false)
+
+ const labelAnchorRef = useRef(null)
+
+ if (item.result === undefined) {
+ throw new Error("Metadata item result is undefined")
+ }
+ if (item.description === undefined) {
+ throw new Error("Metadata item description is undefined")
+ }
+
+ const staleThreshold = Math.max(
+ item.description.interval + item.description.timeout * 2,
+ 5,
+ )
+
+ const status: "stale" | "valid" | "loading" = (() => {
+ const year = dayjs(item.result.collected_at).year()
+ if (year <= 1970 || isNaN(year)) {
+ return "loading"
+ }
+ if (item.result.age > staleThreshold) {
+ return "stale"
+ }
+ return "valid"
+ })()
+
+ // Stale data is as good as no data. Plus, we want to build confidence in our
+ // users that what's shown is real. If times aren't correctly synced this
+ // could be buggy. But, how common is that anyways?
+ const value =
+ status === "stale" || status === "loading" ? (
+
+ ) : (
+
+ {item.result.value}
+
+ )
+
+ const updatesInSeconds = -(item.description.interval - item.result.age)
+
+ return (
+ <>
+
+
setIsOpen(true)}
+ // onMouseLeave={() => setIsOpen(false)}
+ role="presentation"
+ ref={labelAnchorRef}
+ >
+ {item.description.display_name}
+
+
{value}
+
+ setIsOpen(false)}
+ PaperProps={{
+ onMouseEnter: () => setIsOpen(true),
+ onMouseLeave: () => setIsOpen(false),
+ }}
+ classes={{ paper: styles.metadataPopover }}
+ >
+ {item.description.display_name}
+ {status === "stale" ? (
+
+ This item is now stale because the agent hasn{"'"}t reported a new
+ value in {dayjs.duration(item.result.age, "s").humanize()}.
+
+ ) : (
+ <>>
+ )}
+ {status === "valid" ? (
+
+ The agent collected this value{" "}
+ {dayjs.duration(item.result.age, "s").humanize()} ago and will
+ update it in{" "}
+ {dayjs.duration(Math.min(updatesInSeconds, 0), "s").humanize()}.
+
+ ) : (
+ <>>
+ )}
+ {status === "loading" ? (
+
+ This value is loading for the first time...
+
+ ) : (
+ <>>
+ )}
+
+ This value is produced by the following script:
+
+
+
+
+
+ >
+ )
+}
+
+export interface AgentMetadataViewProps {
+ metadata: WorkspaceAgentMetadata[]
+}
+
+export const AgentMetadataView: FC = ({ metadata }) => {
+ const styles = useStyles()
+ if (metadata.length === 0) {
+ return <>>
+ }
+ return (
+
+
+ {metadata.map((m) => {
+ if (m.description === undefined) {
+ throw new Error("Metadata item description is undefined")
+ }
+ return
+ })}
+
+
+ )
+}
+
+export const AgentMetadata: FC<{
+ agent: WorkspaceAgent
+}> = ({ agent }) => {
+ const [metadata, setMetadata] = useState<
+ WorkspaceAgentMetadata[] | undefined
+ >(undefined)
+
+ const watchAgentMetadata = useContext(WatchAgentMetadataContext)
+
+ useEffect(() => {
+ const source = watchAgentMetadata(agent.id)
+
+ source.onerror = (e) => {
+ console.error("received error in watch stream", e)
+ }
+ source.addEventListener("data", (e) => {
+ const data = JSON.parse(e.data)
+ setMetadata(data)
+ })
+ return () => {
+ source.close()
+ }
+ }, [agent.id, watchAgentMetadata])
+
+ if (metadata === undefined) {
+ return
+ }
+
+ return
+}
+
+// These are more or less copied from
+// site/src/components/Resources/ResourceCard.tsx
+const useStyles = makeStyles((theme) => ({
+ metadataStack: {
+ border: `2px dashed ${theme.palette.divider}`,
+ borderRadius: theme.shape.borderRadius,
+ width: "100%",
+ },
+ metadataHeader: {
+ padding: "8px",
+ display: "grid",
+ gridTemplateColumns: "repeat(4, minmax(0, 1fr))",
+ gap: theme.spacing(5),
+ rowGap: theme.spacing(3),
+ },
+
+ metadata: {
+ fontSize: 16,
+ },
+
+ metadataLabel: {
+ fontSize: 12,
+ color: theme.palette.text.secondary,
+ textOverflow: "ellipsis",
+ overflow: "hidden",
+ whiteSpace: "nowrap",
+ fontWeight: "bold",
+ },
+
+ metadataValue: {
+ textOverflow: "ellipsis",
+ overflow: "hidden",
+ whiteSpace: "nowrap",
+ },
+
+ metadataValueSuccess: {
+ color: theme.palette.success.light,
+ },
+ metadataValueError: {
+ color: theme.palette.error.main,
+ },
+
+ metadataPopover: {
+ marginTop: theme.spacing(0.5),
+ padding: theme.spacing(2.5),
+ color: theme.palette.text.secondary,
+ pointerEvents: "auto",
+ maxWidth: "480px",
+
+ "& .MuiButton-root": {
+ padding: theme.spacing(1, 2),
+ borderRadius: 0,
+ border: 0,
+
+ "&:hover": {
+ background: theme.palette.action.hover,
+ },
+ },
+ },
+}))
diff --git a/site/src/components/Resources/AgentRow.tsx b/site/src/components/Resources/AgentRow.tsx
index 00d84793fb2d4..c00ca2a1793a0 100644
--- a/site/src/components/Resources/AgentRow.tsx
+++ b/site/src/components/Resources/AgentRow.tsx
@@ -35,6 +35,7 @@ import { SSHButton } from "../SSHButton/SSHButton"
import { Stack } from "../Stack/Stack"
import { TerminalLink } from "../TerminalLink/TerminalLink"
import { AgentLatency } from "./AgentLatency"
+import { AgentMetadata } from "./AgentMetadata"
import { AgentStatus } from "./AgentStatus"
import { AgentVersion } from "./AgentVersion"
@@ -169,178 +170,202 @@ export const AgentRow: FC = ({
-
-
-
-
{agent.name}
-
- {agent.operating_system}
+
+
+
+
+
+
{agent.name}
+
+
+ {agent.operating_system}
+
-
-
-
+
+
+
-
+
-
-
-
-
+
+
+
+
-
- {t("unableToConnect")}
-
+
+ {t("unableToConnect")}
+
+
+
- {hasStartupFeatures && (
-
+ {showApps && agent.status === "connected" && (
+ <>
+ {agent.apps.map((app) => (
+
+ ))}
+
+
+ {!hideSSHButton && (
+
+ )}
+ {!hideVSCodeDesktopButton && (
+
+ )}
+ {applicationsHost !== undefined &&
+ applicationsHost !== "" && (
+
+ )}
+ >
+ )}
+ {showApps && agent.status === "connecting" && (
+ <>
+
+
+ >
+ )}
+
+
+
+ {hasStartupFeatures && (
+
+ {
+ setShowStartupLogs(!showStartupLogs)
+ }}
>
+ {showStartupLogs ? (
+
+ ) : (
+
+ )}
+ {showStartupLogs ? "Hide" : "Show"} Startup Logs
+
+
+ {agent.startup_script && (
{
- setShowStartupLogs(!showStartupLogs)
+ setStartupScriptOpen(!startupScriptOpen)
}}
>
- {showStartupLogs ? (
-
- ) : (
-
- )}
- {showStartupLogs ? "Hide" : "Show"} Startup Logs
+
+ View Startup Script
+ )}
- {agent.startup_script && (
- {
- setStartupScriptOpen(!startupScriptOpen)
+ setStartupScriptOpen(false)}
+ anchorEl={startupScriptAnchorRef.current}
+ anchorOrigin={{
+ vertical: "bottom",
+ horizontal: "left",
+ }}
+ transformOrigin={{
+ vertical: "top",
+ horizontal: "left",
+ }}
+ >
+
+
-
- View Startup Script
-
- )}
-
- setStartupScriptOpen(false)}
- anchorEl={startupScriptAnchorRef.current}
- anchorOrigin={{
- vertical: "bottom",
- horizontal: "left",
- }}
- transformOrigin={{
- vertical: "top",
- horizontal: "left",
- }}
- >
-
-
- {agent.startup_script || ""}
-
-
-
-
- )}
-
-
-
-
- {showApps && agent.status === "connected" && (
- <>
- {agent.apps.map((app) => (
-
- ))}
-
-
- {!hideSSHButton && (
-
- )}
- {!hideVSCodeDesktopButton && (
-
- )}
- {applicationsHost !== undefined && applicationsHost !== "" && (
-
- )}
- >
- )}
- {showApps && agent.status === "connecting" && (
- <>
-
-
- >
+ {agent.startup_script || ""}
+
+
+
+
)}
-
{showStartupLogs && (
{({ width }) => (
diff --git a/site/src/components/Workspace/Workspace.stories.tsx b/site/src/components/Workspace/Workspace.stories.tsx
index a6fb225a20a7d..30bf79507f3e5 100644
--- a/site/src/components/Workspace/Workspace.stories.tsx
+++ b/site/src/components/Workspace/Workspace.stories.tsx
@@ -1,13 +1,25 @@
import { action } from "@storybook/addon-actions"
import { Story } from "@storybook/react"
+import { WatchAgentMetadataContext } from "components/Resources/AgentMetadata"
import { ProvisionerJobLog } from "api/typesGenerated"
import * as Mocks from "../../testHelpers/entities"
import { Workspace, WorkspaceErrors, WorkspaceProps } from "./Workspace"
+import { withReactContext } from "storybook-react-context"
+import EventSource from "eventsourcemock"
export default {
title: "components/Workspace",
component: Workspace,
argTypes: {},
+ decorators: [
+ withReactContext({
+ Context: WatchAgentMetadataContext,
+ initialState: (_: string): EventSource => {
+ // Need Bruno's help here.
+ return new EventSource()
+ },
+ }),
+ ],
}
const Template: Story = (args) =>
diff --git a/site/yarn.lock b/site/yarn.lock
index 6e81f10bab216..58e74b6699818 100644
--- a/site/yarn.lock
+++ b/site/yarn.lock
@@ -2219,6 +2219,23 @@
global "^4.4.0"
regenerator-runtime "^0.13.7"
+"@storybook/addons@^6.3.6":
+ version "6.5.16"
+ resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.5.16.tgz#07e8f2205f86fa4c9dada719e3e096cb468e3cdd"
+ integrity sha512-p3DqQi+8QRL5k7jXhXmJZLsE/GqHqyY6PcoA1oNTJr0try48uhTGUOYkgzmqtDaa/qPFO5LP+xCPzZXckGtquQ==
+ dependencies:
+ "@storybook/api" "6.5.16"
+ "@storybook/channels" "6.5.16"
+ "@storybook/client-logger" "6.5.16"
+ "@storybook/core-events" "6.5.16"
+ "@storybook/csf" "0.0.2--canary.4566f4d.1"
+ "@storybook/router" "6.5.16"
+ "@storybook/theming" "6.5.16"
+ "@types/webpack-env" "^1.16.0"
+ core-js "^3.8.2"
+ global "^4.4.0"
+ regenerator-runtime "^0.13.7"
+
"@storybook/api@6.5.12":
version "6.5.12"
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.5.12.tgz#7cc82087fc9298be03f15bf4ab9c4aab294b3bac"
@@ -2242,6 +2259,29 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
+"@storybook/api@6.5.16":
+ version "6.5.16"
+ resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.5.16.tgz#897915b76de05587fd702951d5d836f708043662"
+ integrity sha512-HOsuT8iomqeTMQJrRx5U8nsC7lJTwRr1DhdD0SzlqL4c80S/7uuCy4IZvOt4sYQjOzW5fOo/kamcoBXyLproTA==
+ dependencies:
+ "@storybook/channels" "6.5.16"
+ "@storybook/client-logger" "6.5.16"
+ "@storybook/core-events" "6.5.16"
+ "@storybook/csf" "0.0.2--canary.4566f4d.1"
+ "@storybook/router" "6.5.16"
+ "@storybook/semver" "^7.3.2"
+ "@storybook/theming" "6.5.16"
+ core-js "^3.8.2"
+ fast-deep-equal "^3.1.3"
+ global "^4.4.0"
+ lodash "^4.17.21"
+ memoizerific "^1.11.3"
+ regenerator-runtime "^0.13.7"
+ store2 "^2.12.0"
+ telejson "^6.0.8"
+ ts-dedent "^2.0.0"
+ util-deprecate "^1.0.2"
+
"@storybook/api@6.5.9":
version "6.5.9"
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.5.9.tgz#303733214c9de0422d162f7c54ae05d088b89bf9"
@@ -2351,6 +2391,15 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
+"@storybook/channels@6.5.16":
+ version "6.5.16"
+ resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.5.16.tgz#3fb9a3b5666ecb951a2d0cf8b0699b084ef2d3c6"
+ integrity sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==
+ dependencies:
+ core-js "^3.8.2"
+ ts-dedent "^2.0.0"
+ util-deprecate "^1.0.2"
+
"@storybook/channels@6.5.9":
version "6.5.9"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.5.9.tgz#abfab89a6587a2688e9926d4aafeb11c9d8b2e79"
@@ -2394,6 +2443,14 @@
core-js "^3.8.2"
global "^4.4.0"
+"@storybook/client-logger@6.5.16":
+ version "6.5.16"
+ resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.5.16.tgz#955cc46b389e7151c9eb1585a75e6a0605af61a1"
+ integrity sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==
+ dependencies:
+ core-js "^3.8.2"
+ global "^4.4.0"
+
"@storybook/client-logger@6.5.9":
version "6.5.9"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.5.9.tgz#dc1669abe8c45af1cc38f74c6f4b15ff33e63014"
@@ -2521,6 +2578,13 @@
dependencies:
core-js "^3.8.2"
+"@storybook/core-events@6.5.16":
+ version "6.5.16"
+ resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.5.16.tgz#b1c265dac755007dae172d9d4b72656c9e5d7bb3"
+ integrity sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==
+ dependencies:
+ core-js "^3.8.2"
+
"@storybook/core-events@6.5.9":
version "6.5.9"
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.5.9.tgz#5b0783c7d22a586c0f5e927a61fe1b1223e19637"
@@ -2790,6 +2854,17 @@
qs "^6.10.0"
regenerator-runtime "^0.13.7"
+"@storybook/router@6.5.16":
+ version "6.5.16"
+ resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.5.16.tgz#28fb4d34e8219351a40bee1fc94dcacda6e1bd8b"
+ integrity sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==
+ dependencies:
+ "@storybook/client-logger" "6.5.16"
+ core-js "^3.8.2"
+ memoizerific "^1.11.3"
+ qs "^6.10.0"
+ regenerator-runtime "^0.13.7"
+
"@storybook/router@6.5.9":
version "6.5.9"
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.5.9.tgz#4740248f8517425b2056273fb366ace8a17c65e8"
@@ -2874,6 +2949,16 @@
memoizerific "^1.11.3"
regenerator-runtime "^0.13.7"
+"@storybook/theming@6.5.16":
+ version "6.5.16"
+ resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.5.16.tgz#b999bdb98945b605b93b9dfdf7408535b701e2aa"
+ integrity sha512-hNLctkjaYLRdk1+xYTkC1mg4dYz2wSv6SqbLpcKMbkPHTE0ElhddGPHQqB362md/w9emYXNkt1LSMD8Xk9JzVQ==
+ dependencies:
+ "@storybook/client-logger" "6.5.16"
+ core-js "^3.8.2"
+ memoizerific "^1.11.3"
+ regenerator-runtime "^0.13.7"
+
"@storybook/theming@6.5.9":
version "6.5.9"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.5.9.tgz#13f60a3a3cd73ceb5caf9f188e1627e79f1891aa"
@@ -8853,7 +8938,7 @@ is-plain-obj@^4.0.0:
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
-is-plain-object@5.0.0:
+is-plain-object@5.0.0, is-plain-object@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
@@ -11043,6 +11128,11 @@ mock-socket@^9.1.0:
resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.2.1.tgz#cc9c0810aa4d0afe02d721dcb2b7e657c00e2282"
integrity sha512-aw9F9T9G2zpGipLLhSNh6ZpgUyUl4frcVmRN08uE1NWPWg43Wx6+sGPDbQ7E5iFZZDJW5b5bypMeAEHqTbIFag==
+mock-xmlhttprequest@^7.0.3:
+ version "7.0.4"
+ resolved "https://registry.yarnpkg.com/mock-xmlhttprequest/-/mock-xmlhttprequest-7.0.4.tgz#5e188da009cf46900e522f690cbea8d26274a872"
+ integrity sha512-hA0fIHy/74p5DE0rdmrpU0sV1U+gnWTcgShWequGRLy0L1eT+zY0ozFukawpLaxMwIA+orRcqFRElYwT+5p81A==
+
monaco-editor@0.34.1:
version "0.34.1"
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.34.1.tgz#1b75c4ad6bc4c1f9da656d740d98e0b850a22f87"
@@ -12590,6 +12680,14 @@ react@18.2.0:
dependencies:
loose-envify "^1.1.0"
+react@^17.0.2:
+ version "17.0.2"
+ resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
+ integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
+ dependencies:
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+
reactcss@^1.2.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"
@@ -13638,6 +13736,24 @@ store2@^2.12.0:
resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.2.tgz#56138d200f9fe5f582ad63bc2704dbc0e4a45068"
integrity sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==
+storybook-addon-mock@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/storybook-addon-mock/-/storybook-addon-mock-3.2.0.tgz#5832b1e49ff39ffab7a0ae8ec7de8bfdb8ddea45"
+ integrity sha512-LaggsF/6Lt0AyHiotIEVQpwKfIiZ3KsNqtdXKVnIdOetjaD7GaOQeX0jIZiZUFX/i6QLmMuNoXFngqqkdVtfSg==
+ dependencies:
+ mock-xmlhttprequest "^7.0.3"
+ path-to-regexp "^6.2.0"
+ polished "^4.2.2"
+
+storybook-react-context@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/storybook-react-context/-/storybook-react-context-0.6.0.tgz#06c7b48dc95f4619cf12e59429305fbd6f2b1373"
+ integrity sha512-6IOUbSoC1WW68x8zQBEh8tZsVXjEvOBSJSOhkaD9o8IF9caIg/o1jnwuGibdyAd47ARN6g95O0N0vFBjXcB7pA==
+ dependencies:
+ "@storybook/addons" "^6.3.6"
+ is-plain-object "^5.0.0"
+ react "^17.0.2"
+
stream-browserify@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"