feat: add agent metadata (#6614)

This commit is contained in:
Ammar Bandukwala 2023-03-31 15:26:19 -05:00 committed by GitHub
parent c191692751
commit ca4fa81570
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 3139 additions and 727 deletions

View File

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

View File

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

131
coderd/apidoc/docs.go generated
View File

@ -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": {

View File

@ -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": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
DROP TABLE workspace_agent_metadata;

View File

@ -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
);

View File

@ -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
);

View File

@ -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"`

View File

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

View File

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

View File

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

View File

@ -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 == "" {

View File

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

View File

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

View File

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

View File

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

View File

@ -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"`
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"`
}
// 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)
// 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 Metadata{}, err
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

View File

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

View File

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

View File

@ -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).

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -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",

90
docs/templates/agent-metadata.md vendored Normal file
View File

@ -0,0 +1,90 @@
# Agent Metadata (alpha)
<blockquote class="warning">
Agent metadata is in an alpha state and may break or disappear at any time.
</blockquote>
![agent-metadata](../images/agent-metadata.png)
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 = <<EOT
vmstat | awk 'FNR==3 {printf "%2.0f%%", $13+$14+$16}'
EOT
interval = 1
timeout = 1
}
metadata {
display_name = "Disk Usage"
key = "cpu"
script = <<EOT
df -h | awk -v mount="/" '$6 == mount { print $5 }'
EOT
interval = 1
timeout = 1
}
metadata {
display_name = "Memory Usage"
key = "mem"
script = <<EOT
free | awk '/^Mem/ { printf("%.0f%%", $4/$2 * 100.0) }'
EOT
interval = 1
timeout = 1
}
metadata {
display_name = "Load Average"
key = "load"
script = <<EOT
awk '{print $1,$2,$3}' /proc/loadavg
>>
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
```

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"format_version": "1.1",
"terraform_version": "1.3.6",
"terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [

View File

@ -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": {},

View File

@ -1,6 +1,6 @@
{
"format_version": "1.1",
"terraform_version": "1.3.6",
"terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [

View File

@ -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": {},

View File

@ -1,6 +1,6 @@
{
"format_version": "1.1",
"terraform_version": "1.3.6",
"terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [

View File

@ -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": {},

View File

@ -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": {},

View File

@ -1,6 +1,6 @@
{
"format_version": "1.1",
"terraform_version": "1.3.6",
"terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [

View File

@ -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": {},

View File

@ -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": {},

View File

@ -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": {},

View File

@ -1,6 +1,6 @@
{
"format_version": "1.1",
"terraform_version": "1.3.3",
"terraform_version": "1.3.7",
"planned_values": {
"root_module": {
"resources": [

View File

@ -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": {},

View File

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

View File

@ -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)"

View File

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

View File

@ -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)"

View File

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

View File

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

View File

@ -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": {},

View File

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

View File

@ -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<string, string> 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 {

View File

@ -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": [

View File

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

View File

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

View File

@ -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<AgentMetadataViewProps> = (args) => (
<AgentMetadataView {...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",
},
},
],
}

View File

@ -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<HTMLDivElement>(null)
const styles = useStyles()
return (
<>
<div
ref={anchorRef}
onMouseEnter={() => setIsOpen(true)}
role="presentation"
>
{children}
</div>
<Popover
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
open={isOpen}
anchorEl={anchorRef.current}
onClose={() => setIsOpen(false)}
PaperProps={{
onMouseEnter: () => setIsOpen(true),
onMouseLeave: () => setIsOpen(false),
}}
classes={{ paper: styles.metadataPopover }}
>
<HelpTooltipTitle>{item.description.display_name}</HelpTooltipTitle>
{item.result.value.length > 0 && (
<>
<HelpTooltipText>Last result:</HelpTooltipText>
<HelpTooltipText>
<CodeExample code={item.result.value} />
</HelpTooltipText>
</>
)}
{item.result.error.length > 0 && (
<>
<HelpTooltipText>Last error:</HelpTooltipText>
<HelpTooltipText>
<CodeExample code={item.result.error} />
</HelpTooltipText>
</>
)}
</Popover>
</>
)
}
const MetadataItem: FC<{ item: WorkspaceAgentMetadata }> = ({ item }) => {
const styles = useStyles()
const [isOpen, setIsOpen] = useState(false)
const labelAnchorRef = useRef<HTMLDivElement>(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" ? (
<CircularProgress size={12} />
) : (
<div
className={
styles.metadataValue +
" " +
(item.result.error.length === 0
? styles.metadataValueSuccess
: styles.metadataValueError)
}
>
{item.result.value}
</div>
)
const updatesInSeconds = -(item.description.interval - item.result.age)
return (
<>
<div className={styles.metadata}>
<div
className={styles.metadataLabel}
onMouseEnter={() => setIsOpen(true)}
// onMouseLeave={() => setIsOpen(false)}
role="presentation"
ref={labelAnchorRef}
>
{item.description.display_name}
</div>
<MetadataItemValue item={item}>{value}</MetadataItemValue>
</div>
<Popover
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
open={isOpen}
anchorEl={labelAnchorRef.current}
onClose={() => setIsOpen(false)}
PaperProps={{
onMouseEnter: () => setIsOpen(true),
onMouseLeave: () => setIsOpen(false),
}}
classes={{ paper: styles.metadataPopover }}
>
<HelpTooltipTitle>{item.description.display_name}</HelpTooltipTitle>
{status === "stale" ? (
<HelpTooltipText>
This item is now stale because the agent hasn{"'"}t reported a new
value in {dayjs.duration(item.result.age, "s").humanize()}.
</HelpTooltipText>
) : (
<></>
)}
{status === "valid" ? (
<HelpTooltipText>
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()}.
</HelpTooltipText>
) : (
<></>
)}
{status === "loading" ? (
<HelpTooltipText>
This value is loading for the first time...
</HelpTooltipText>
) : (
<></>
)}
<HelpTooltipText>
This value is produced by the following script:
</HelpTooltipText>
<HelpTooltipText>
<CodeExample code={item.description.script}></CodeExample>
</HelpTooltipText>
</Popover>
</>
)
}
export interface AgentMetadataViewProps {
metadata: WorkspaceAgentMetadata[]
}
export const AgentMetadataView: FC<AgentMetadataViewProps> = ({ metadata }) => {
const styles = useStyles()
if (metadata.length === 0) {
return <></>
}
return (
<Stack
alignItems="flex-start"
direction="row"
spacing={5}
className={styles.metadataStack}
>
<div className={styles.metadataHeader}>
{metadata.map((m) => {
if (m.description === undefined) {
throw new Error("Metadata item description is undefined")
}
return <MetadataItem key={m.description.key} item={m} />
})}
</div>
</Stack>
)
}
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 <CircularProgress size={16} />
}
return <AgentMetadataView metadata={metadata} />
}
// 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,
},
},
},
}))

View File

@ -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<AgentRowProps> = ({
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
className={styles.agentRow}
spacing={4}
style={{
justifyContent: "none",
alignItems: "center",
}}
>
<Stack direction="row" alignItems="baseline">
<div className={styles.agentStatusWrapper}>
<AgentStatus agent={agent} />
</div>
<div>
<div className={styles.agentName}>{agent.name}</div>
<div className={styles.agentStatusWrapper}>
<AgentStatus agent={agent} />
</div>
<Stack
direction="column"
alignItems="flex-start"
key={agent.id}
spacing={2}
style={{
flex: 1,
}}
>
<Stack
direction="row"
alignItems="center"
style={{
width: "100%",
justifyContent: "space-between",
}}
>
<Stack direction="row" alignItems="baseline">
<div>
<div className={styles.agentName}>{agent.name}</div>
<Stack
direction="row"
alignItems="baseline"
className={styles.agentData}
spacing={1}
>
<span className={styles.agentOS}>
{agent.operating_system}
</span>
<Maybe condition={agent.status === "connected"}>
<AgentVersion
agent={agent}
serverVersion={serverVersion}
onUpdate={onUpdateAgent}
/>
</Maybe>
<AgentLatency agent={agent} />
<Maybe condition={agent.status === "connecting"}>
<Skeleton width={160} variant="text" />
<Skeleton width={36} variant="text" />
</Maybe>
<Maybe condition={agent.status === "timeout"}>
{t("unableToConnect")}
</Maybe>
</Stack>
</div>
</Stack>
<Stack
direction="row"
alignItems="center"
spacing={0.5}
wrap="wrap"
maxWidth="750px"
>
{showApps && agent.status === "connected" && (
<>
{agent.apps.map((app) => (
<AppLink
key={app.slug}
appsHost={applicationsHost}
app={app}
agent={agent}
workspace={workspace}
/>
))}
<TerminalLink
workspaceName={workspace.name}
agentName={agent.name}
userName={workspace.owner_name}
/>
{!hideSSHButton && (
<SSHButton
workspaceName={workspace.name}
agentName={agent.name}
sshPrefix={sshPrefix}
/>
)}
{!hideVSCodeDesktopButton && (
<VSCodeDesktopButton
userName={workspace.owner_name}
workspaceName={workspace.name}
agentName={agent.name}
folderPath={agent.expanded_directory}
/>
)}
{applicationsHost !== undefined &&
applicationsHost !== "" && (
<PortForwardButton
host={applicationsHost}
workspaceName={workspace.name}
agentId={agent.id}
agentName={agent.name}
username={workspace.owner_name}
/>
)}
</>
)}
{showApps && agent.status === "connecting" && (
<>
<AppLinkSkeleton width={84} />
<AppLinkSkeleton width={112} />
</>
)}
</Stack>
</Stack>
<AgentMetadata agent={agent} />
{hasStartupFeatures && (
<Stack
direction="row"
alignItems="baseline"
className={styles.agentData}
spacing={1}
className={styles.startupLinks}
>
<span className={styles.agentOS}>{agent.operating_system}</span>
<Maybe condition={agent.status === "connected"}>
<AgentVersion
agent={agent}
serverVersion={serverVersion}
onUpdate={onUpdateAgent}
/>
</Maybe>
<AgentLatency agent={agent} />
<Maybe condition={agent.status === "connecting"}>
<Skeleton width={160} variant="text" />
<Skeleton width={36} variant="text" />
</Maybe>
<Maybe condition={agent.status === "timeout"}>
{t("unableToConnect")}
</Maybe>
</Stack>
{hasStartupFeatures && (
<Stack
direction="row"
alignItems="baseline"
spacing={1}
className={styles.startupLinks}
<Link
className={styles.startupLink}
variant="body2"
onClick={() => {
setShowStartupLogs(!showStartupLogs)
}}
>
{showStartupLogs ? (
<VisibilityOffOutlined />
) : (
<VisibilityOutlined />
)}
{showStartupLogs ? "Hide" : "Show"} Startup Logs
</Link>
{agent.startup_script && (
<Link
className={styles.startupLink}
variant="body2"
ref={startupScriptAnchorRef}
onClick={() => {
setShowStartupLogs(!showStartupLogs)
setStartupScriptOpen(!startupScriptOpen)
}}
>
{showStartupLogs ? (
<VisibilityOffOutlined />
) : (
<VisibilityOutlined />
)}
{showStartupLogs ? "Hide" : "Show"} Startup Logs
<PlayCircleOutlined />
View Startup Script
</Link>
)}
{agent.startup_script && (
<Link
className={styles.startupLink}
variant="body2"
ref={startupScriptAnchorRef}
onClick={() => {
setStartupScriptOpen(!startupScriptOpen)
<Popover
classes={{
paper: styles.startupScriptPopover,
}}
open={startupScriptOpen}
onClose={() => setStartupScriptOpen(false)}
anchorEl={startupScriptAnchorRef.current}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
>
<div>
<SyntaxHighlighter
style={darcula}
language="shell"
showLineNumbers
// Use inline styles does not work correctly
// https://github.com/react-syntax-highlighter/react-syntax-highlighter/issues/329
codeTagProps={{ style: {} }}
customStyle={{
background: theme.palette.background.default,
maxWidth: 600,
margin: 0,
}}
>
<PlayCircleOutlined />
View Startup Script
</Link>
)}
<Popover
classes={{
paper: styles.startupScriptPopover,
}}
open={startupScriptOpen}
onClose={() => setStartupScriptOpen(false)}
anchorEl={startupScriptAnchorRef.current}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
>
<div>
<SyntaxHighlighter
style={darcula}
language="shell"
showLineNumbers
// Use inline styles does not work correctly
// https://github.com/react-syntax-highlighter/react-syntax-highlighter/issues/329
codeTagProps={{ style: {} }}
customStyle={{
background: theme.palette.background.default,
maxWidth: 600,
margin: 0,
}}
>
{agent.startup_script || ""}
</SyntaxHighlighter>
</div>
</Popover>
</Stack>
)}
</div>
</Stack>
<Stack
direction="row"
alignItems="center"
spacing={0.5}
wrap="wrap"
maxWidth="750px"
>
{showApps && agent.status === "connected" && (
<>
{agent.apps.map((app) => (
<AppLink
key={app.slug}
appsHost={applicationsHost}
app={app}
agent={agent}
workspace={workspace}
/>
))}
<TerminalLink
workspaceName={workspace.name}
agentName={agent.name}
userName={workspace.owner_name}
/>
{!hideSSHButton && (
<SSHButton
workspaceName={workspace.name}
agentName={agent.name}
sshPrefix={sshPrefix}
/>
)}
{!hideVSCodeDesktopButton && (
<VSCodeDesktopButton
userName={workspace.owner_name}
workspaceName={workspace.name}
agentName={agent.name}
folderPath={agent.expanded_directory}
/>
)}
{applicationsHost !== undefined && applicationsHost !== "" && (
<PortForwardButton
host={applicationsHost}
workspaceName={workspace.name}
agentId={agent.id}
agentName={agent.name}
username={workspace.owner_name}
/>
)}
</>
)}
{showApps && agent.status === "connecting" && (
<>
<AppLinkSkeleton width={84} />
<AppLinkSkeleton width={112} />
</>
{agent.startup_script || ""}
</SyntaxHighlighter>
</div>
</Popover>
</Stack>
)}
</Stack>
</Stack>
{showStartupLogs && (
<AutoSizer disableHeight>
{({ width }) => (

View File

@ -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<WorkspaceProps> = (args) => <Workspace {...args} />

View File

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