mirror of https://github.com/coder/coder.git
feat: add health check monitoring to workspace apps (#4114)
This commit is contained in:
parent
f160830226
commit
4c8be34d81
|
@ -33,6 +33,7 @@ import (
|
|||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/agent/usershell"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/pty"
|
||||
"github.com/coder/coder/tailnet"
|
||||
"github.com/coder/retry"
|
||||
|
@ -49,39 +50,23 @@ const (
|
|||
MagicSessionErrorCode = 229
|
||||
)
|
||||
|
||||
var (
|
||||
// tailnetIP is a static IPv6 address with the Tailscale prefix that is used to route
|
||||
// connections from clients to this node. A dynamic address is not required because a Tailnet
|
||||
// client only dials a single agent at a time.
|
||||
tailnetIP = netip.MustParseAddr("fd7a:115c:a1e0:49d6:b259:b7ac:b1b2:48f4")
|
||||
tailnetSSHPort = 1
|
||||
tailnetReconnectingPTYPort = 2
|
||||
tailnetSpeedtestPort = 3
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
CoordinatorDialer CoordinatorDialer
|
||||
FetchMetadata FetchMetadata
|
||||
|
||||
StatsReporter StatsReporter
|
||||
ReconnectingPTYTimeout time.Duration
|
||||
EnvironmentVariables map[string]string
|
||||
Logger slog.Logger
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
DERPMap *tailcfg.DERPMap `json:"derpmap"`
|
||||
EnvironmentVariables map[string]string `json:"environment_variables"`
|
||||
StartupScript string `json:"startup_script"`
|
||||
Directory string `json:"directory"`
|
||||
CoordinatorDialer CoordinatorDialer
|
||||
FetchMetadata FetchMetadata
|
||||
StatsReporter StatsReporter
|
||||
WorkspaceAgentApps WorkspaceAgentApps
|
||||
PostWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth
|
||||
ReconnectingPTYTimeout time.Duration
|
||||
EnvironmentVariables map[string]string
|
||||
Logger slog.Logger
|
||||
}
|
||||
|
||||
// CoordinatorDialer is a function that constructs a new broker.
|
||||
// A dialer must be passed in to allow for reconnects.
|
||||
type CoordinatorDialer func(ctx context.Context) (net.Conn, error)
|
||||
type CoordinatorDialer func(context.Context) (net.Conn, error)
|
||||
|
||||
// FetchMetadata is a function to obtain metadata for the agent.
|
||||
type FetchMetadata func(ctx context.Context) (Metadata, error)
|
||||
type FetchMetadata func(context.Context) (codersdk.WorkspaceAgentMetadata, error)
|
||||
|
||||
func New(options Options) io.Closer {
|
||||
if options.ReconnectingPTYTimeout == 0 {
|
||||
|
@ -89,15 +74,17 @@ func New(options Options) io.Closer {
|
|||
}
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
server := &agent{
|
||||
reconnectingPTYTimeout: options.ReconnectingPTYTimeout,
|
||||
logger: options.Logger,
|
||||
closeCancel: cancelFunc,
|
||||
closed: make(chan struct{}),
|
||||
envVars: options.EnvironmentVariables,
|
||||
coordinatorDialer: options.CoordinatorDialer,
|
||||
fetchMetadata: options.FetchMetadata,
|
||||
stats: &Stats{},
|
||||
statsReporter: options.StatsReporter,
|
||||
reconnectingPTYTimeout: options.ReconnectingPTYTimeout,
|
||||
logger: options.Logger,
|
||||
closeCancel: cancelFunc,
|
||||
closed: make(chan struct{}),
|
||||
envVars: options.EnvironmentVariables,
|
||||
coordinatorDialer: options.CoordinatorDialer,
|
||||
fetchMetadata: options.FetchMetadata,
|
||||
stats: &Stats{},
|
||||
statsReporter: options.StatsReporter,
|
||||
workspaceAgentApps: options.WorkspaceAgentApps,
|
||||
postWorkspaceAgentAppHealth: options.PostWorkspaceAgentAppHealth,
|
||||
}
|
||||
server.init(ctx)
|
||||
return server
|
||||
|
@ -120,14 +107,16 @@ type agent struct {
|
|||
fetchMetadata FetchMetadata
|
||||
sshServer *ssh.Server
|
||||
|
||||
network *tailnet.Conn
|
||||
coordinatorDialer CoordinatorDialer
|
||||
stats *Stats
|
||||
statsReporter StatsReporter
|
||||
network *tailnet.Conn
|
||||
coordinatorDialer CoordinatorDialer
|
||||
stats *Stats
|
||||
statsReporter StatsReporter
|
||||
workspaceAgentApps WorkspaceAgentApps
|
||||
postWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth
|
||||
}
|
||||
|
||||
func (a *agent) run(ctx context.Context) {
|
||||
var metadata Metadata
|
||||
var metadata codersdk.WorkspaceAgentMetadata
|
||||
var err error
|
||||
// An exponential back-off occurs when the connection is failing to dial.
|
||||
// This is to prevent server spam in case of a coderd outage.
|
||||
|
@ -168,6 +157,10 @@ func (a *agent) run(ctx context.Context) {
|
|||
if metadata.DERPMap != nil {
|
||||
go a.runTailnet(ctx, metadata.DERPMap)
|
||||
}
|
||||
|
||||
if a.workspaceAgentApps != nil && a.postWorkspaceAgentAppHealth != nil {
|
||||
go NewWorkspaceAppHealthReporter(a.logger, a.workspaceAgentApps, a.postWorkspaceAgentAppHealth)(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) {
|
||||
|
@ -182,7 +175,7 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) {
|
|||
}
|
||||
var err error
|
||||
a.network, err = tailnet.NewConn(&tailnet.Options{
|
||||
Addresses: []netip.Prefix{netip.PrefixFrom(tailnetIP, 128)},
|
||||
Addresses: []netip.Prefix{netip.PrefixFrom(codersdk.TailnetIP, 128)},
|
||||
DERPMap: derpMap,
|
||||
Logger: a.logger.Named("tailnet"),
|
||||
})
|
||||
|
@ -199,7 +192,7 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) {
|
|||
})
|
||||
go a.runCoordinator(ctx)
|
||||
|
||||
sshListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(tailnetSSHPort))
|
||||
sshListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(codersdk.TailnetSSHPort))
|
||||
if err != nil {
|
||||
a.logger.Critical(ctx, "listen for ssh", slog.Error(err))
|
||||
return
|
||||
|
@ -213,7 +206,7 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) {
|
|||
go a.sshServer.HandleConn(a.stats.wrapConn(conn))
|
||||
}
|
||||
}()
|
||||
reconnectingPTYListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(tailnetReconnectingPTYPort))
|
||||
reconnectingPTYListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(codersdk.TailnetReconnectingPTYPort))
|
||||
if err != nil {
|
||||
a.logger.Critical(ctx, "listen for reconnecting pty", slog.Error(err))
|
||||
return
|
||||
|
@ -239,7 +232,7 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) {
|
|||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var msg reconnectingPTYInit
|
||||
var msg codersdk.ReconnectingPTYInit
|
||||
err = json.Unmarshal(data, &msg)
|
||||
if err != nil {
|
||||
continue
|
||||
|
@ -247,7 +240,7 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) {
|
|||
go a.handleReconnectingPTY(ctx, msg, conn)
|
||||
}
|
||||
}()
|
||||
speedtestListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(tailnetSpeedtestPort))
|
||||
speedtestListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(codersdk.TailnetSpeedtestPort))
|
||||
if err != nil {
|
||||
a.logger.Critical(ctx, "listen for speedtest", slog.Error(err))
|
||||
return
|
||||
|
@ -443,7 +436,7 @@ func (a *agent) init(ctx context.Context) {
|
|||
|
||||
go a.run(ctx)
|
||||
if a.statsReporter != nil {
|
||||
cl, err := a.statsReporter(ctx, a.logger, func() *Stats {
|
||||
cl, err := a.statsReporter(ctx, a.logger, func() *codersdk.AgentStats {
|
||||
return a.stats.Copy()
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -478,7 +471,7 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
|
|||
if rawMetadata == nil {
|
||||
return nil, xerrors.Errorf("no metadata was provided: %w", err)
|
||||
}
|
||||
metadata, valid := rawMetadata.(Metadata)
|
||||
metadata, valid := rawMetadata.(codersdk.WorkspaceAgentMetadata)
|
||||
if !valid {
|
||||
return nil, xerrors.Errorf("metadata is the wrong type: %T", metadata)
|
||||
}
|
||||
|
@ -634,7 +627,7 @@ func (a *agent) handleSSHSession(session ssh.Session) (retErr error) {
|
|||
return cmd.Wait()
|
||||
}
|
||||
|
||||
func (a *agent) handleReconnectingPTY(ctx context.Context, msg reconnectingPTYInit, conn net.Conn) {
|
||||
func (a *agent) handleReconnectingPTY(ctx context.Context, msg codersdk.ReconnectingPTYInit, conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
var rpty *reconnectingPTY
|
||||
|
@ -775,7 +768,7 @@ func (a *agent) handleReconnectingPTY(ctx context.Context, msg reconnectingPTYIn
|
|||
rpty.activeConnsMutex.Unlock()
|
||||
}()
|
||||
decoder := json.NewDecoder(conn)
|
||||
var req ReconnectingPTYRequest
|
||||
var req codersdk.ReconnectingPTYRequest
|
||||
for {
|
||||
err = decoder.Decode(&req)
|
||||
if xerrors.Is(err, io.EOF) {
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
"github.com/coder/coder/agent"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/pty/ptytest"
|
||||
"github.com/coder/coder/tailnet"
|
||||
"github.com/coder/coder/tailnet/tailnettest"
|
||||
|
@ -52,7 +53,7 @@ func TestAgent(t *testing.T) {
|
|||
|
||||
t.Run("SSH", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, stats := setupAgent(t, agent.Metadata{}, 0)
|
||||
conn, stats := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
|
||||
|
||||
sshClient, err := conn.SSHClient()
|
||||
require.NoError(t, err)
|
||||
|
@ -69,20 +70,20 @@ func TestAgent(t *testing.T) {
|
|||
t.Run("ReconnectingPTY", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conn, stats := setupAgent(t, agent.Metadata{}, 0)
|
||||
conn, stats := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
|
||||
|
||||
ptyConn, err := conn.ReconnectingPTY(uuid.NewString(), 128, 128, "/bin/bash")
|
||||
require.NoError(t, err)
|
||||
defer ptyConn.Close()
|
||||
|
||||
data, err := json.Marshal(agent.ReconnectingPTYRequest{
|
||||
data, err := json.Marshal(codersdk.ReconnectingPTYRequest{
|
||||
Data: "echo test\r\n",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = ptyConn.Write(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
var s *agent.Stats
|
||||
var s *codersdk.AgentStats
|
||||
require.Eventuallyf(t, func() bool {
|
||||
var ok bool
|
||||
s, ok = (<-stats)
|
||||
|
@ -95,7 +96,7 @@ func TestAgent(t *testing.T) {
|
|||
|
||||
t.Run("SessionExec", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
session := setupSSHSession(t, agent.Metadata{})
|
||||
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
|
||||
|
||||
command := "echo test"
|
||||
if runtime.GOOS == "windows" {
|
||||
|
@ -108,7 +109,7 @@ func TestAgent(t *testing.T) {
|
|||
|
||||
t.Run("GitSSH", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
session := setupSSHSession(t, agent.Metadata{})
|
||||
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
|
||||
command := "sh -c 'echo $GIT_SSH_COMMAND'"
|
||||
if runtime.GOOS == "windows" {
|
||||
command = "cmd.exe /c echo %GIT_SSH_COMMAND%"
|
||||
|
@ -126,7 +127,7 @@ func TestAgent(t *testing.T) {
|
|||
// it seems like it could be either.
|
||||
t.Skip("ConPTY appears to be inconsistent on Windows.")
|
||||
}
|
||||
session := setupSSHSession(t, agent.Metadata{})
|
||||
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
|
||||
command := "bash"
|
||||
if runtime.GOOS == "windows" {
|
||||
command = "cmd.exe"
|
||||
|
@ -154,7 +155,7 @@ func TestAgent(t *testing.T) {
|
|||
|
||||
t.Run("SessionTTYExitCode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
session := setupSSHSession(t, agent.Metadata{})
|
||||
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
|
||||
command := "areallynotrealcommand"
|
||||
err := session.RequestPty("xterm", 128, 128, ssh.TerminalModes{})
|
||||
require.NoError(t, err)
|
||||
|
@ -211,7 +212,7 @@ func TestAgent(t *testing.T) {
|
|||
|
||||
t.Run("SFTP", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := setupAgent(t, agent.Metadata{}, 0)
|
||||
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
|
||||
sshClient, err := conn.SSHClient()
|
||||
require.NoError(t, err)
|
||||
defer sshClient.Close()
|
||||
|
@ -229,7 +230,7 @@ func TestAgent(t *testing.T) {
|
|||
t.Run("SCP", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conn, _ := setupAgent(t, agent.Metadata{}, 0)
|
||||
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
|
||||
sshClient, err := conn.SSHClient()
|
||||
require.NoError(t, err)
|
||||
defer sshClient.Close()
|
||||
|
@ -247,7 +248,7 @@ func TestAgent(t *testing.T) {
|
|||
t.Parallel()
|
||||
key := "EXAMPLE"
|
||||
value := "value"
|
||||
session := setupSSHSession(t, agent.Metadata{
|
||||
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{
|
||||
EnvironmentVariables: map[string]string{
|
||||
key: value,
|
||||
},
|
||||
|
@ -264,7 +265,7 @@ func TestAgent(t *testing.T) {
|
|||
t.Run("EnvironmentVariableExpansion", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
key := "EXAMPLE"
|
||||
session := setupSSHSession(t, agent.Metadata{
|
||||
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{
|
||||
EnvironmentVariables: map[string]string{
|
||||
key: "$SOMETHINGNOTSET",
|
||||
},
|
||||
|
@ -291,7 +292,7 @@ func TestAgent(t *testing.T) {
|
|||
t.Run(key, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
session := setupSSHSession(t, agent.Metadata{})
|
||||
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
|
||||
command := "sh -c 'echo $" + key + "'"
|
||||
if runtime.GOOS == "windows" {
|
||||
command = "cmd.exe /c echo %" + key + "%"
|
||||
|
@ -314,7 +315,7 @@ func TestAgent(t *testing.T) {
|
|||
t.Run(key, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
session := setupSSHSession(t, agent.Metadata{})
|
||||
session := setupSSHSession(t, codersdk.WorkspaceAgentMetadata{})
|
||||
command := "sh -c 'echo $" + key + "'"
|
||||
if runtime.GOOS == "windows" {
|
||||
command = "cmd.exe /c echo %" + key + "%"
|
||||
|
@ -330,7 +331,7 @@ func TestAgent(t *testing.T) {
|
|||
t.Parallel()
|
||||
tempPath := filepath.Join(t.TempDir(), "content.txt")
|
||||
content := "somethingnice"
|
||||
setupAgent(t, agent.Metadata{
|
||||
setupAgent(t, codersdk.WorkspaceAgentMetadata{
|
||||
StartupScript: fmt.Sprintf("echo %s > %s", content, tempPath),
|
||||
}, 0)
|
||||
|
||||
|
@ -365,7 +366,7 @@ func TestAgent(t *testing.T) {
|
|||
t.Skip("ConPTY appears to be inconsistent on Windows.")
|
||||
}
|
||||
|
||||
conn, _ := setupAgent(t, agent.Metadata{}, 0)
|
||||
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
|
||||
id := uuid.NewString()
|
||||
netConn, err := conn.ReconnectingPTY(id, 100, 100, "/bin/bash")
|
||||
require.NoError(t, err)
|
||||
|
@ -375,7 +376,7 @@ func TestAgent(t *testing.T) {
|
|||
// the shell is simultaneously sending a prompt.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
data, err := json.Marshal(agent.ReconnectingPTYRequest{
|
||||
data, err := json.Marshal(codersdk.ReconnectingPTYRequest{
|
||||
Data: "echo test\r\n",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
@ -462,7 +463,7 @@ func TestAgent(t *testing.T) {
|
|||
}
|
||||
}()
|
||||
|
||||
conn, _ := setupAgent(t, agent.Metadata{}, 0)
|
||||
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
|
||||
require.Eventually(t, func() bool {
|
||||
_, err := conn.Ping()
|
||||
return err == nil
|
||||
|
@ -483,7 +484,7 @@ func TestAgent(t *testing.T) {
|
|||
t.Run("Tailnet", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
derpMap := tailnettest.RunDERPAndSTUN(t)
|
||||
conn, _ := setupAgent(t, agent.Metadata{
|
||||
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{
|
||||
DERPMap: derpMap,
|
||||
}, 0)
|
||||
defer conn.Close()
|
||||
|
@ -499,7 +500,7 @@ func TestAgent(t *testing.T) {
|
|||
t.Skip("The minimum duration for a speedtest is hardcoded in Tailscale to 5s!")
|
||||
}
|
||||
derpMap := tailnettest.RunDERPAndSTUN(t)
|
||||
conn, _ := setupAgent(t, agent.Metadata{
|
||||
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{
|
||||
DERPMap: derpMap,
|
||||
}, 0)
|
||||
defer conn.Close()
|
||||
|
@ -510,7 +511,7 @@ func TestAgent(t *testing.T) {
|
|||
}
|
||||
|
||||
func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exec.Cmd {
|
||||
agentConn, _ := setupAgent(t, agent.Metadata{}, 0)
|
||||
agentConn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
waitGroup := sync.WaitGroup{}
|
||||
|
@ -547,7 +548,7 @@ func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exe
|
|||
return exec.Command("ssh", args...)
|
||||
}
|
||||
|
||||
func setupSSHSession(t *testing.T, options agent.Metadata) *ssh.Session {
|
||||
func setupSSHSession(t *testing.T, options codersdk.WorkspaceAgentMetadata) *ssh.Session {
|
||||
conn, _ := setupAgent(t, options, 0)
|
||||
sshClient, err := conn.SSHClient()
|
||||
require.NoError(t, err)
|
||||
|
@ -565,18 +566,18 @@ func (c closeFunc) Close() error {
|
|||
return c()
|
||||
}
|
||||
|
||||
func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) (
|
||||
*agent.Conn,
|
||||
<-chan *agent.Stats,
|
||||
func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeout time.Duration) (
|
||||
*codersdk.AgentConn,
|
||||
<-chan *codersdk.AgentStats,
|
||||
) {
|
||||
if metadata.DERPMap == nil {
|
||||
metadata.DERPMap = tailnettest.RunDERPAndSTUN(t)
|
||||
}
|
||||
coordinator := tailnet.NewCoordinator()
|
||||
agentID := uuid.New()
|
||||
statsCh := make(chan *agent.Stats)
|
||||
statsCh := make(chan *codersdk.AgentStats)
|
||||
closer := agent.New(agent.Options{
|
||||
FetchMetadata: func(ctx context.Context) (agent.Metadata, error) {
|
||||
FetchMetadata: func(ctx context.Context) (codersdk.WorkspaceAgentMetadata, error) {
|
||||
return metadata, nil
|
||||
},
|
||||
CoordinatorDialer: func(ctx context.Context) (net.Conn, error) {
|
||||
|
@ -595,7 +596,7 @@ func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration)
|
|||
},
|
||||
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
||||
ReconnectingPTYTimeout: ptyTimeout,
|
||||
StatsReporter: func(ctx context.Context, log slog.Logger, statsFn func() *agent.Stats) (io.Closer, error) {
|
||||
StatsReporter: func(ctx context.Context, log slog.Logger, statsFn func() *codersdk.AgentStats) (io.Closer, error) {
|
||||
doneCh := make(chan struct{})
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
|
@ -648,7 +649,7 @@ func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration)
|
|||
return conn.UpdateNodes(node)
|
||||
})
|
||||
conn.SetNodeCallback(sendNode)
|
||||
return &agent.Conn{
|
||||
return &codersdk.AgentConn{
|
||||
Conn: conn,
|
||||
}, statsCh
|
||||
}
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/retry"
|
||||
)
|
||||
|
||||
// WorkspaceAgentApps fetches the workspace apps.
|
||||
type WorkspaceAgentApps func(context.Context) ([]codersdk.WorkspaceApp, error)
|
||||
|
||||
// PostWorkspaceAgentAppHealth updates the workspace app health.
|
||||
type PostWorkspaceAgentAppHealth func(context.Context, codersdk.PostWorkspaceAppHealthsRequest) error
|
||||
|
||||
// WorkspaceAppHealthReporter is a function that checks and reports the health of the workspace apps until the passed context is canceled.
|
||||
type WorkspaceAppHealthReporter func(ctx context.Context)
|
||||
|
||||
// NewWorkspaceAppHealthReporter creates a WorkspaceAppHealthReporter that reports app health to coderd.
|
||||
func NewWorkspaceAppHealthReporter(logger slog.Logger, workspaceAgentApps WorkspaceAgentApps, postWorkspaceAgentAppHealth PostWorkspaceAgentAppHealth) WorkspaceAppHealthReporter {
|
||||
runHealthcheckLoop := func(ctx context.Context) error {
|
||||
apps, err := workspaceAgentApps(ctx)
|
||||
if err != nil {
|
||||
if xerrors.Is(err, context.Canceled) {
|
||||
return nil
|
||||
}
|
||||
return xerrors.Errorf("getting workspace apps: %w", err)
|
||||
}
|
||||
|
||||
// no need to run this loop if no apps for this workspace.
|
||||
if len(apps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
hasHealthchecksEnabled := false
|
||||
health := make(map[string]codersdk.WorkspaceAppHealth, 0)
|
||||
for _, app := range apps {
|
||||
health[app.Name] = app.Health
|
||||
if !hasHealthchecksEnabled && app.Health != codersdk.WorkspaceAppHealthDisabled {
|
||||
hasHealthchecksEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// no need to run this loop if no health checks are configured.
|
||||
if !hasHealthchecksEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// run a ticker for each app health check.
|
||||
var mu sync.RWMutex
|
||||
failures := make(map[string]int, 0)
|
||||
for _, nextApp := range apps {
|
||||
if !shouldStartTicker(nextApp) {
|
||||
continue
|
||||
}
|
||||
app := nextApp
|
||||
t := time.NewTicker(time.Duration(app.Healthcheck.Interval) * time.Second)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
// we set the http timeout to the healthcheck interval to prevent getting too backed up.
|
||||
client := &http.Client{
|
||||
Timeout: time.Duration(app.Healthcheck.Interval) * time.Second,
|
||||
}
|
||||
err := func() error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, app.Healthcheck.URL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// successful healthcheck is a non-5XX status code
|
||||
res.Body.Close()
|
||||
if res.StatusCode >= http.StatusInternalServerError {
|
||||
return xerrors.Errorf("error status code: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
if failures[app.Name] < int(app.Healthcheck.Threshold) {
|
||||
// increment the failure count and keep status the same.
|
||||
// we will change it when we hit the threshold.
|
||||
failures[app.Name]++
|
||||
} else {
|
||||
// set to unhealthy if we hit the failure threshold.
|
||||
// we stop incrementing at the threshold to prevent the failure value from increasing forever.
|
||||
health[app.Name] = codersdk.WorkspaceAppHealthUnhealthy
|
||||
}
|
||||
mu.Unlock()
|
||||
} else {
|
||||
mu.Lock()
|
||||
// we only need one successful health check to be considered healthy.
|
||||
health[app.Name] = codersdk.WorkspaceAppHealthHealthy
|
||||
failures[app.Name] = 0
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
t.Reset(time.Duration(app.Healthcheck.Interval))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
lastHealth := copyHealth(health)
|
||||
mu.Unlock()
|
||||
reportTicker := time.NewTicker(time.Second)
|
||||
// every second we check if the health values of the apps have changed
|
||||
// and if there is a change we will report the new values.
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-reportTicker.C:
|
||||
mu.RLock()
|
||||
changed := healthChanged(lastHealth, health)
|
||||
mu.RUnlock()
|
||||
if !changed {
|
||||
continue
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
lastHealth = copyHealth(health)
|
||||
mu.Unlock()
|
||||
err := postWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{
|
||||
Healths: lastHealth,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error(ctx, "failed to report workspace app stat", slog.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return func(ctx context.Context) {
|
||||
for r := retry.New(time.Second, 30*time.Second); r.Wait(ctx); {
|
||||
err := runHealthcheckLoop(ctx)
|
||||
if err == nil || xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) {
|
||||
return
|
||||
}
|
||||
logger.Error(ctx, "failed running workspace app reporter", slog.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func shouldStartTicker(app codersdk.WorkspaceApp) bool {
|
||||
return app.Healthcheck.URL != "" && app.Healthcheck.Interval > 0 && app.Healthcheck.Threshold > 0
|
||||
}
|
||||
|
||||
func healthChanged(old map[string]codersdk.WorkspaceAppHealth, new map[string]codersdk.WorkspaceAppHealth) bool {
|
||||
for name, newValue := range new {
|
||||
oldValue, found := old[name]
|
||||
if !found {
|
||||
return true
|
||||
}
|
||||
if newValue != oldValue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func copyHealth(h1 map[string]codersdk.WorkspaceAppHealth) map[string]codersdk.WorkspaceAppHealth {
|
||||
h2 := make(map[string]codersdk.WorkspaceAppHealth, 0)
|
||||
for k, v := range h1 {
|
||||
h2[k] = v
|
||||
}
|
||||
|
||||
return h2
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
package agent_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
"github.com/coder/coder/agent"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/testutil"
|
||||
)
|
||||
|
||||
func TestAppHealth(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Healthy", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
apps := []codersdk.WorkspaceApp{
|
||||
{
|
||||
Name: "app1",
|
||||
Healthcheck: codersdk.Healthcheck{},
|
||||
Health: codersdk.WorkspaceAppHealthDisabled,
|
||||
},
|
||||
{
|
||||
Name: "app2",
|
||||
Healthcheck: codersdk.Healthcheck{
|
||||
// URL: We don't set the URL for this test because the setup will
|
||||
// create a httptest server for us and set it for us.
|
||||
Interval: 1,
|
||||
Threshold: 1,
|
||||
},
|
||||
Health: codersdk.WorkspaceAppHealthInitializing,
|
||||
},
|
||||
}
|
||||
handlers := []http.Handler{
|
||||
nil,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(r.Context(), w, http.StatusOK, nil)
|
||||
}),
|
||||
}
|
||||
getApps, closeFn := setupAppReporter(ctx, t, apps, handlers)
|
||||
defer closeFn()
|
||||
apps, err := getApps(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, apps[0].Health)
|
||||
require.Eventually(t, func() bool {
|
||||
apps, err := getApps(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return apps[1].Health == codersdk.WorkspaceAppHealthHealthy
|
||||
}, testutil.WaitLong, testutil.IntervalSlow)
|
||||
})
|
||||
|
||||
t.Run("500", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
apps := []codersdk.WorkspaceApp{
|
||||
{
|
||||
Name: "app2",
|
||||
Healthcheck: codersdk.Healthcheck{
|
||||
// URL: We don't set the URL for this test because the setup will
|
||||
// create a httptest server for us and set it for us.
|
||||
Interval: 1,
|
||||
Threshold: 1,
|
||||
},
|
||||
Health: codersdk.WorkspaceAppHealthInitializing,
|
||||
},
|
||||
}
|
||||
handlers := []http.Handler{
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(r.Context(), w, http.StatusInternalServerError, nil)
|
||||
}),
|
||||
}
|
||||
getApps, closeFn := setupAppReporter(ctx, t, apps, handlers)
|
||||
defer closeFn()
|
||||
require.Eventually(t, func() bool {
|
||||
apps, err := getApps(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return apps[0].Health == codersdk.WorkspaceAppHealthUnhealthy
|
||||
}, testutil.WaitLong, testutil.IntervalSlow)
|
||||
})
|
||||
|
||||
t.Run("Timeout", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
apps := []codersdk.WorkspaceApp{
|
||||
{
|
||||
Name: "app2",
|
||||
Healthcheck: codersdk.Healthcheck{
|
||||
// URL: We don't set the URL for this test because the setup will
|
||||
// create a httptest server for us and set it for us.
|
||||
Interval: 1,
|
||||
Threshold: 1,
|
||||
},
|
||||
Health: codersdk.WorkspaceAppHealthInitializing,
|
||||
},
|
||||
}
|
||||
handlers := []http.Handler{
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// sleep longer than the interval to cause the health check to time out
|
||||
time.Sleep(2 * time.Second)
|
||||
httpapi.Write(r.Context(), w, http.StatusOK, nil)
|
||||
}),
|
||||
}
|
||||
getApps, closeFn := setupAppReporter(ctx, t, apps, handlers)
|
||||
defer closeFn()
|
||||
require.Eventually(t, func() bool {
|
||||
apps, err := getApps(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return apps[0].Health == codersdk.WorkspaceAppHealthUnhealthy
|
||||
}, testutil.WaitLong, testutil.IntervalSlow)
|
||||
})
|
||||
}
|
||||
|
||||
func setupAppReporter(ctx context.Context, t *testing.T, apps []codersdk.WorkspaceApp, handlers []http.Handler) (agent.WorkspaceAgentApps, func()) {
|
||||
closers := []func(){}
|
||||
for i, handler := range handlers {
|
||||
if handler == nil {
|
||||
continue
|
||||
}
|
||||
ts := httptest.NewServer(handler)
|
||||
app := apps[i]
|
||||
app.Healthcheck.URL = ts.URL
|
||||
apps[i] = app
|
||||
closers = append(closers, ts.Close)
|
||||
}
|
||||
|
||||
var mu sync.Mutex
|
||||
workspaceAgentApps := func(context.Context) ([]codersdk.WorkspaceApp, error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
var newApps []codersdk.WorkspaceApp
|
||||
return append(newApps, apps...), nil
|
||||
}
|
||||
postWorkspaceAgentAppHealth := func(_ context.Context, req codersdk.PostWorkspaceAppHealthsRequest) error {
|
||||
mu.Lock()
|
||||
for name, health := range req.Healths {
|
||||
for i, app := range apps {
|
||||
if app.Name != name {
|
||||
continue
|
||||
}
|
||||
app.Health = health
|
||||
apps[i] = app
|
||||
}
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
go agent.NewWorkspaceAppHealthReporter(slogtest.Make(t, nil).Leveled(slog.LevelDebug), workspaceAgentApps, postWorkspaceAgentAppHealth)(ctx)
|
||||
|
||||
return workspaceAgentApps, func() {
|
||||
for _, closeFn := range closers {
|
||||
closeFn()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||
"sync/atomic"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
// statsConn wraps a net.Conn with statistics.
|
||||
|
@ -40,8 +41,8 @@ type Stats struct {
|
|||
TxBytes int64 `json:"tx_bytes"`
|
||||
}
|
||||
|
||||
func (s *Stats) Copy() *Stats {
|
||||
return &Stats{
|
||||
func (s *Stats) Copy() *codersdk.AgentStats {
|
||||
return &codersdk.AgentStats{
|
||||
NumConns: atomic.LoadInt64(&s.NumConns),
|
||||
RxBytes: atomic.LoadInt64(&s.RxBytes),
|
||||
TxBytes: atomic.LoadInt64(&s.TxBytes),
|
||||
|
@ -63,5 +64,5 @@ func (s *Stats) wrapConn(conn net.Conn) net.Conn {
|
|||
type StatsReporter func(
|
||||
ctx context.Context,
|
||||
log slog.Logger,
|
||||
stats func() *Stats,
|
||||
stats func() *codersdk.AgentStats,
|
||||
) (io.Closer, error)
|
||||
|
|
|
@ -189,8 +189,10 @@ func workspaceAgent() *cobra.Command {
|
|||
// shells so "gitssh" works!
|
||||
"CODER_AGENT_TOKEN": client.SessionToken,
|
||||
},
|
||||
CoordinatorDialer: client.ListenWorkspaceAgentTailnet,
|
||||
StatsReporter: client.AgentReportStats,
|
||||
CoordinatorDialer: client.ListenWorkspaceAgentTailnet,
|
||||
StatsReporter: client.AgentReportStats,
|
||||
WorkspaceAgentApps: client.WorkspaceAgentApps,
|
||||
PostWorkspaceAgentAppHealth: client.PostWorkspaceAgentAppHealth,
|
||||
})
|
||||
<-cmd.Context().Done()
|
||||
return closer.Close()
|
||||
|
|
|
@ -169,7 +169,7 @@ func portForward() *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func listenAndPortForward(ctx context.Context, cmd *cobra.Command, conn *agent.Conn, wg *sync.WaitGroup, spec portForwardSpec) (net.Listener, error) {
|
||||
func listenAndPortForward(ctx context.Context, cmd *cobra.Command, conn *codersdk.AgentConn, wg *sync.WaitGroup, spec portForwardSpec) (net.Listener, error) {
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStderr(), "Forwarding '%v://%v' locally to '%v://%v' in the workspace\n", spec.listenNetwork, spec.listenAddress, spec.dialNetwork, spec.dialAddress)
|
||||
|
||||
var (
|
||||
|
|
|
@ -414,8 +414,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("/apps", api.workspaceAgentApps)
|
||||
r.Get("/metadata", api.workspaceAgentMetadata)
|
||||
r.Post("/version", api.postWorkspaceAgentVersion)
|
||||
r.Post("/app-health", api.postWorkspaceAppHealth)
|
||||
r.Get("/gitsshkey", api.agentGitSSHKey)
|
||||
r.Get("/coordinate", api.workspaceAgentCoordinate)
|
||||
r.Get("/report-stats", api.workspaceAgentReportStats)
|
||||
|
|
|
@ -57,10 +57,12 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
|
|||
"POST:/api/v2/workspaceagents/aws-instance-identity": {NoAuthorize: true},
|
||||
"POST:/api/v2/workspaceagents/azure-instance-identity": {NoAuthorize: true},
|
||||
"POST:/api/v2/workspaceagents/google-instance-identity": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/me/apps": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/me/gitsshkey": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/me/metadata": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/me/coordinate": {NoAuthorize: true},
|
||||
"POST:/api/v2/workspaceagents/me/version": {NoAuthorize: true},
|
||||
"POST:/api/v2/workspaceagents/me/app-health": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/me/report-stats": {NoAuthorize: true},
|
||||
|
||||
// These endpoints have more assertions. This is good, add more endpoints to assert if you can!
|
||||
|
|
|
@ -2019,19 +2019,38 @@ func (q *fakeQuerier) InsertWorkspaceApp(_ context.Context, arg database.InsertW
|
|||
|
||||
// nolint:gosimple
|
||||
workspaceApp := database.WorkspaceApp{
|
||||
ID: arg.ID,
|
||||
AgentID: arg.AgentID,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
Name: arg.Name,
|
||||
Icon: arg.Icon,
|
||||
Command: arg.Command,
|
||||
Url: arg.Url,
|
||||
RelativePath: arg.RelativePath,
|
||||
ID: arg.ID,
|
||||
AgentID: arg.AgentID,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
Name: arg.Name,
|
||||
Icon: arg.Icon,
|
||||
Command: arg.Command,
|
||||
Url: arg.Url,
|
||||
RelativePath: arg.RelativePath,
|
||||
HealthcheckUrl: arg.HealthcheckUrl,
|
||||
HealthcheckInterval: arg.HealthcheckInterval,
|
||||
HealthcheckThreshold: arg.HealthcheckThreshold,
|
||||
Health: arg.Health,
|
||||
}
|
||||
q.workspaceApps = append(q.workspaceApps, workspaceApp)
|
||||
return workspaceApp, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpdateWorkspaceAppHealthByID(_ context.Context, arg database.UpdateWorkspaceAppHealthByIDParams) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for index, app := range q.workspaceApps {
|
||||
if app.ID != arg.ID {
|
||||
continue
|
||||
}
|
||||
app.Health = arg.Health
|
||||
q.workspaceApps[index] = app
|
||||
return nil
|
||||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpdateAPIKeyByID(_ context.Context, arg database.UpdateAPIKeyByIDParams) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
|
|
@ -88,6 +88,13 @@ CREATE TYPE user_status AS ENUM (
|
|||
'suspended'
|
||||
);
|
||||
|
||||
CREATE TYPE workspace_app_health AS ENUM (
|
||||
'disabled',
|
||||
'initializing',
|
||||
'healthy',
|
||||
'unhealthy'
|
||||
);
|
||||
|
||||
CREATE TYPE workspace_transition AS ENUM (
|
||||
'start',
|
||||
'stop',
|
||||
|
@ -344,7 +351,11 @@ CREATE TABLE workspace_apps (
|
|||
icon character varying(256) NOT NULL,
|
||||
command character varying(65534),
|
||||
url character varying(65534),
|
||||
relative_path boolean DEFAULT false NOT NULL
|
||||
relative_path boolean DEFAULT false NOT NULL,
|
||||
healthcheck_url text DEFAULT ''::text NOT NULL,
|
||||
healthcheck_interval integer DEFAULT 0 NOT NULL,
|
||||
healthcheck_threshold integer DEFAULT 0 NOT NULL,
|
||||
health workspace_app_health DEFAULT 'disabled'::public.workspace_app_health NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE workspace_builds (
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
ALTER TABLE ONLY workspace_apps
|
||||
DROP COLUMN IF EXISTS healthcheck_url,
|
||||
DROP COLUMN IF EXISTS healthcheck_interval,
|
||||
DROP COLUMN IF EXISTS healthcheck_threshold,
|
||||
DROP COLUMN IF EXISTS health;
|
||||
|
||||
DROP TYPE workspace_app_health;
|
|
@ -0,0 +1,7 @@
|
|||
CREATE TYPE workspace_app_health AS ENUM ('disabled', 'initializing', 'healthy', 'unhealthy');
|
||||
|
||||
ALTER TABLE ONLY workspace_apps
|
||||
ADD COLUMN IF NOT EXISTS healthcheck_url text NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS healthcheck_interval int NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS healthcheck_threshold int NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS health workspace_app_health NOT NULL DEFAULT 'disabled';
|
|
@ -312,6 +312,27 @@ func (e *UserStatus) Scan(src interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type WorkspaceAppHealth string
|
||||
|
||||
const (
|
||||
WorkspaceAppHealthDisabled WorkspaceAppHealth = "disabled"
|
||||
WorkspaceAppHealthInitializing WorkspaceAppHealth = "initializing"
|
||||
WorkspaceAppHealthHealthy WorkspaceAppHealth = "healthy"
|
||||
WorkspaceAppHealthUnhealthy WorkspaceAppHealth = "unhealthy"
|
||||
)
|
||||
|
||||
func (e *WorkspaceAppHealth) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = WorkspaceAppHealth(s)
|
||||
case string:
|
||||
*e = WorkspaceAppHealth(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for WorkspaceAppHealth: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type WorkspaceTransition string
|
||||
|
||||
const (
|
||||
|
@ -576,14 +597,18 @@ type WorkspaceAgent struct {
|
|||
}
|
||||
|
||||
type WorkspaceApp struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
Command sql.NullString `db:"command" json:"command"`
|
||||
Url sql.NullString `db:"url" json:"url"`
|
||||
RelativePath bool `db:"relative_path" json:"relative_path"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
Command sql.NullString `db:"command" json:"command"`
|
||||
Url sql.NullString `db:"url" json:"url"`
|
||||
RelativePath bool `db:"relative_path" json:"relative_path"`
|
||||
HealthcheckUrl string `db:"healthcheck_url" json:"healthcheck_url"`
|
||||
HealthcheckInterval int32 `db:"healthcheck_interval" json:"healthcheck_interval"`
|
||||
HealthcheckThreshold int32 `db:"healthcheck_threshold" json:"healthcheck_threshold"`
|
||||
Health WorkspaceAppHealth `db:"health" json:"health"`
|
||||
}
|
||||
|
||||
type WorkspaceBuild struct {
|
||||
|
|
|
@ -149,6 +149,7 @@ type querier interface {
|
|||
UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (Workspace, error)
|
||||
UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error
|
||||
UpdateWorkspaceAgentVersionByID(ctx context.Context, arg UpdateWorkspaceAgentVersionByIDParams) error
|
||||
UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error
|
||||
UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error
|
||||
UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) error
|
||||
UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error
|
||||
|
|
|
@ -3849,7 +3849,7 @@ func (q *sqlQuerier) UpdateWorkspaceAgentVersionByID(ctx context.Context, arg Up
|
|||
}
|
||||
|
||||
const getWorkspaceAppByAgentIDAndName = `-- name: GetWorkspaceAppByAgentIDAndName :one
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE agent_id = $1 AND name = $2
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 AND name = $2
|
||||
`
|
||||
|
||||
type GetWorkspaceAppByAgentIDAndNameParams struct {
|
||||
|
@ -3869,12 +3869,16 @@ func (q *sqlQuerier) GetWorkspaceAppByAgentIDAndName(ctx context.Context, arg Ge
|
|||
&i.Command,
|
||||
&i.Url,
|
||||
&i.RelativePath,
|
||||
&i.HealthcheckUrl,
|
||||
&i.HealthcheckInterval,
|
||||
&i.HealthcheckThreshold,
|
||||
&i.Health,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceAppsByAgentID = `-- name: GetWorkspaceAppsByAgentID :many
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = $1 ORDER BY name ASC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid.UUID) ([]WorkspaceApp, error) {
|
||||
|
@ -3895,6 +3899,10 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
|
|||
&i.Command,
|
||||
&i.Url,
|
||||
&i.RelativePath,
|
||||
&i.HealthcheckUrl,
|
||||
&i.HealthcheckInterval,
|
||||
&i.HealthcheckThreshold,
|
||||
&i.Health,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3910,7 +3918,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentID(ctx context.Context, agentID uuid
|
|||
}
|
||||
|
||||
const getWorkspaceAppsByAgentIDs = `-- name: GetWorkspaceAppsByAgentIDs :many
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE agent_id = ANY($1 :: uuid [ ]) ORDER BY name ASC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceApp, error) {
|
||||
|
@ -3931,6 +3939,10 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
|
|||
&i.Command,
|
||||
&i.Url,
|
||||
&i.RelativePath,
|
||||
&i.HealthcheckUrl,
|
||||
&i.HealthcheckInterval,
|
||||
&i.HealthcheckThreshold,
|
||||
&i.Health,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3946,7 +3958,7 @@ func (q *sqlQuerier) GetWorkspaceAppsByAgentIDs(ctx context.Context, ids []uuid.
|
|||
}
|
||||
|
||||
const getWorkspaceAppsCreatedAfter = `-- name: GetWorkspaceAppsCreatedAfter :many
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, relative_path FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC
|
||||
SELECT id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_url, healthcheck_interval, healthcheck_threshold, health FROM workspace_apps WHERE created_at > $1 ORDER BY name ASC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceApp, error) {
|
||||
|
@ -3967,6 +3979,10 @@ func (q *sqlQuerier) GetWorkspaceAppsCreatedAfter(ctx context.Context, createdAt
|
|||
&i.Command,
|
||||
&i.Url,
|
||||
&i.RelativePath,
|
||||
&i.HealthcheckUrl,
|
||||
&i.HealthcheckInterval,
|
||||
&i.HealthcheckThreshold,
|
||||
&i.Health,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3991,21 +4007,29 @@ INSERT INTO
|
|||
icon,
|
||||
command,
|
||||
url,
|
||||
relative_path
|
||||
relative_path,
|
||||
healthcheck_url,
|
||||
healthcheck_interval,
|
||||
healthcheck_threshold,
|
||||
health
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, created_at, agent_id, name, icon, command, url, relative_path, healthcheck_url, healthcheck_interval, healthcheck_threshold, health
|
||||
`
|
||||
|
||||
type InsertWorkspaceAppParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
Command sql.NullString `db:"command" json:"command"`
|
||||
Url sql.NullString `db:"url" json:"url"`
|
||||
RelativePath bool `db:"relative_path" json:"relative_path"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
Command sql.NullString `db:"command" json:"command"`
|
||||
Url sql.NullString `db:"url" json:"url"`
|
||||
RelativePath bool `db:"relative_path" json:"relative_path"`
|
||||
HealthcheckUrl string `db:"healthcheck_url" json:"healthcheck_url"`
|
||||
HealthcheckInterval int32 `db:"healthcheck_interval" json:"healthcheck_interval"`
|
||||
HealthcheckThreshold int32 `db:"healthcheck_threshold" json:"healthcheck_threshold"`
|
||||
Health WorkspaceAppHealth `db:"health" json:"health"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) {
|
||||
|
@ -4018,6 +4042,10 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
|
|||
arg.Command,
|
||||
arg.Url,
|
||||
arg.RelativePath,
|
||||
arg.HealthcheckUrl,
|
||||
arg.HealthcheckInterval,
|
||||
arg.HealthcheckThreshold,
|
||||
arg.Health,
|
||||
)
|
||||
var i WorkspaceApp
|
||||
err := row.Scan(
|
||||
|
@ -4029,10 +4057,33 @@ func (q *sqlQuerier) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspace
|
|||
&i.Command,
|
||||
&i.Url,
|
||||
&i.RelativePath,
|
||||
&i.HealthcheckUrl,
|
||||
&i.HealthcheckInterval,
|
||||
&i.HealthcheckThreshold,
|
||||
&i.Health,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateWorkspaceAppHealthByID = `-- name: UpdateWorkspaceAppHealthByID :exec
|
||||
UPDATE
|
||||
workspace_apps
|
||||
SET
|
||||
health = $2
|
||||
WHERE
|
||||
id = $1
|
||||
`
|
||||
|
||||
type UpdateWorkspaceAppHealthByIDParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Health WorkspaceAppHealth `db:"health" json:"health"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateWorkspaceAppHealthByID, arg.ID, arg.Health)
|
||||
return err
|
||||
}
|
||||
|
||||
const getLatestWorkspaceBuildByWorkspaceID = `-- name: GetLatestWorkspaceBuildByWorkspaceID :one
|
||||
SELECT
|
||||
id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason
|
||||
|
|
|
@ -20,7 +20,19 @@ INSERT INTO
|
|||
icon,
|
||||
command,
|
||||
url,
|
||||
relative_path
|
||||
relative_path,
|
||||
healthcheck_url,
|
||||
healthcheck_interval,
|
||||
healthcheck_threshold,
|
||||
health
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *;
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *;
|
||||
|
||||
-- name: UpdateWorkspaceAppHealthByID :exec
|
||||
UPDATE
|
||||
workspace_apps
|
||||
SET
|
||||
health = $2
|
||||
WHERE
|
||||
id = $1;
|
||||
|
|
|
@ -812,6 +812,14 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
|||
snapshot.WorkspaceAgents = append(snapshot.WorkspaceAgents, telemetry.ConvertWorkspaceAgent(dbAgent))
|
||||
|
||||
for _, app := range prAgent.Apps {
|
||||
health := database.WorkspaceAppHealthDisabled
|
||||
if app.Healthcheck == nil {
|
||||
app.Healthcheck = &sdkproto.Healthcheck{}
|
||||
}
|
||||
if app.Healthcheck.Url != "" {
|
||||
health = database.WorkspaceAppHealthInitializing
|
||||
}
|
||||
|
||||
dbApp, err := db.InsertWorkspaceApp(ctx, database.InsertWorkspaceAppParams{
|
||||
ID: uuid.New(),
|
||||
CreatedAt: database.Now(),
|
||||
|
@ -826,7 +834,11 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
|||
String: app.Url,
|
||||
Valid: app.Url != "",
|
||||
},
|
||||
RelativePath: app.RelativePath,
|
||||
RelativePath: app.RelativePath,
|
||||
HealthcheckUrl: app.Healthcheck.Url,
|
||||
HealthcheckInterval: app.Healthcheck.Interval,
|
||||
HealthcheckThreshold: app.Healthcheck.Threshold,
|
||||
Health: health,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert app: %w", err)
|
||||
|
|
|
@ -23,7 +23,6 @@ import (
|
|||
"tailscale.com/tailcfg"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/agent"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/coderd/httpmw"
|
||||
|
@ -61,6 +60,20 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) {
|
|||
httpapi.Write(ctx, rw, http.StatusOK, apiAgent)
|
||||
}
|
||||
|
||||
func (api *API) workspaceAgentApps(rw http.ResponseWriter, r *http.Request) {
|
||||
workspaceAgent := httpmw.WorkspaceAgent(r)
|
||||
dbApps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID)
|
||||
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching workspace agent applications.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(r.Context(), rw, http.StatusOK, convertApps(dbApps))
|
||||
}
|
||||
|
||||
func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
workspaceAgent := httpmw.WorkspaceAgent(r)
|
||||
|
@ -73,7 +86,7 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request)
|
|||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, agent.Metadata{
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentMetadata{
|
||||
DERPMap: api.DERPMap,
|
||||
EnvironmentVariables: apiAgent.EnvironmentVariables,
|
||||
StartupScript: apiAgent.StartupScript,
|
||||
|
@ -205,7 +218,7 @@ func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) {
|
|||
_, _ = io.Copy(ptNetConn, wsNetConn)
|
||||
}
|
||||
|
||||
func (api *API) dialWorkspaceAgentTailnet(r *http.Request, agentID uuid.UUID) (*agent.Conn, error) {
|
||||
func (api *API) dialWorkspaceAgentTailnet(r *http.Request, agentID uuid.UUID) (*codersdk.AgentConn, error) {
|
||||
clientConn, serverConn := net.Pipe()
|
||||
go func() {
|
||||
<-r.Context().Done()
|
||||
|
@ -232,7 +245,7 @@ func (api *API) dialWorkspaceAgentTailnet(r *http.Request, agentID uuid.UUID) (*
|
|||
_ = conn.Close()
|
||||
}
|
||||
}()
|
||||
return &agent.Conn{
|
||||
return &codersdk.AgentConn{
|
||||
Conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
@ -442,6 +455,12 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp {
|
|||
Name: dbApp.Name,
|
||||
Command: dbApp.Command.String,
|
||||
Icon: dbApp.Icon,
|
||||
Healthcheck: codersdk.Healthcheck{
|
||||
URL: dbApp.HealthcheckUrl,
|
||||
Interval: dbApp.HealthcheckInterval,
|
||||
Threshold: dbApp.HealthcheckThreshold,
|
||||
},
|
||||
Health: codersdk.WorkspaceAppHealth(dbApp.Health),
|
||||
})
|
||||
}
|
||||
return apps
|
||||
|
@ -667,6 +686,94 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
}
|
||||
|
||||
func (api *API) postWorkspaceAppHealth(rw http.ResponseWriter, r *http.Request) {
|
||||
workspaceAgent := httpmw.WorkspaceAgent(r)
|
||||
var req codersdk.PostWorkspaceAppHealthsRequest
|
||||
if !httpapi.Read(r.Context(), rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Healths == nil || len(req.Healths) == 0 {
|
||||
httpapi.Write(r.Context(), rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Health field is empty",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
apps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID)
|
||||
if err != nil {
|
||||
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Error getting agent apps",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var newApps []database.WorkspaceApp
|
||||
for name, newHealth := range req.Healths {
|
||||
old := func() *database.WorkspaceApp {
|
||||
for _, app := range apps {
|
||||
if app.Name == name {
|
||||
return &app
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if old == nil {
|
||||
httpapi.Write(r.Context(), rw, http.StatusNotFound, codersdk.Response{
|
||||
Message: "Error setting workspace app health",
|
||||
Detail: xerrors.Errorf("workspace app name %s not found", name).Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if old.HealthcheckUrl == "" {
|
||||
httpapi.Write(r.Context(), rw, http.StatusNotFound, codersdk.Response{
|
||||
Message: "Error setting workspace app health",
|
||||
Detail: xerrors.Errorf("health checking is disabled for workspace app %s", name).Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
switch newHealth {
|
||||
case codersdk.WorkspaceAppHealthInitializing:
|
||||
case codersdk.WorkspaceAppHealthHealthy:
|
||||
case codersdk.WorkspaceAppHealthUnhealthy:
|
||||
default:
|
||||
httpapi.Write(r.Context(), rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Error setting workspace app health",
|
||||
Detail: xerrors.Errorf("workspace app health %s is not a valid value", newHealth).Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// don't save if the value hasn't changed
|
||||
if old.Health == database.WorkspaceAppHealth(newHealth) {
|
||||
continue
|
||||
}
|
||||
old.Health = database.WorkspaceAppHealth(newHealth)
|
||||
|
||||
newApps = append(newApps, *old)
|
||||
}
|
||||
|
||||
for _, app := range newApps {
|
||||
err = api.Database.UpdateWorkspaceAppHealthByID(r.Context(), database.UpdateWorkspaceAppHealthByIDParams{
|
||||
ID: app.ID,
|
||||
Health: app.Health,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Error setting workspace app health",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
httpapi.Write(r.Context(), rw, http.StatusOK, nil)
|
||||
}
|
||||
|
||||
// wsNetConn wraps net.Conn created by websocket.NetConn(). Cancel func
|
||||
// is called if a read or write error is encountered.
|
||||
type wsNetConn struct {
|
||||
|
|
|
@ -324,7 +324,7 @@ func TestWorkspaceAgentPTY(t *testing.T) {
|
|||
|
||||
// First attempt to resize the TTY.
|
||||
// The websocket will close if it fails!
|
||||
data, err := json.Marshal(agent.ReconnectingPTYRequest{
|
||||
data, err := json.Marshal(codersdk.ReconnectingPTYRequest{
|
||||
Height: 250,
|
||||
Width: 250,
|
||||
})
|
||||
|
@ -337,7 +337,7 @@ func TestWorkspaceAgentPTY(t *testing.T) {
|
|||
// the shell is simultaneously sending a prompt.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
data, err = json.Marshal(agent.ReconnectingPTYRequest{
|
||||
data, err = json.Marshal(codersdk.ReconnectingPTYRequest{
|
||||
Data: "echo test\r\n",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
@ -363,3 +363,112 @@ func TestWorkspaceAgentPTY(t *testing.T) {
|
|||
expectLine(matchEchoCommand)
|
||||
expectLine(matchEchoOutput)
|
||||
}
|
||||
|
||||
func TestWorkspaceAgentAppHealth(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
IncludeProvisionerDaemon: true,
|
||||
})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
authToken := uuid.NewString()
|
||||
apps := []*proto.App{
|
||||
{
|
||||
Name: "code-server",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
},
|
||||
{
|
||||
Name: "code-server-2",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
Healthcheck: &proto.Healthcheck{
|
||||
Url: "http://localhost:3000",
|
||||
Interval: 5,
|
||||
Threshold: 6,
|
||||
},
|
||||
},
|
||||
}
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Provision: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "aws_instance",
|
||||
Agents: []*proto.Agent{{
|
||||
Id: uuid.NewString(),
|
||||
Auth: &proto.Agent_Token{
|
||||
Token: authToken,
|
||||
},
|
||||
Apps: apps,
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
agentClient := codersdk.New(client.URL)
|
||||
agentClient.SessionToken = authToken
|
||||
|
||||
apiApps, err := agentClient.WorkspaceAgentApps(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, apiApps[0].Health)
|
||||
require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, apiApps[1].Health)
|
||||
err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{})
|
||||
require.Error(t, err)
|
||||
// empty
|
||||
err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{})
|
||||
require.Error(t, err)
|
||||
// invalid name
|
||||
err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{
|
||||
Healths: map[string]codersdk.WorkspaceAppHealth{
|
||||
"bad-name": codersdk.WorkspaceAppHealthDisabled,
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
// healcheck disabled
|
||||
err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{
|
||||
Healths: map[string]codersdk.WorkspaceAppHealth{
|
||||
"code-server": codersdk.WorkspaceAppHealthInitializing,
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
// invalid value
|
||||
err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{
|
||||
Healths: map[string]codersdk.WorkspaceAppHealth{
|
||||
"code-server-2": codersdk.WorkspaceAppHealth("bad-value"),
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
// update to healthy
|
||||
err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{
|
||||
Healths: map[string]codersdk.WorkspaceAppHealth{
|
||||
"code-server-2": codersdk.WorkspaceAppHealthHealthy,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
apiApps, err = agentClient.WorkspaceAgentApps(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, codersdk.WorkspaceAppHealthHealthy, apiApps[1].Health)
|
||||
// update to unhealthy
|
||||
err = agentClient.PostWorkspaceAgentAppHealth(ctx, codersdk.PostWorkspaceAppHealthsRequest{
|
||||
Healths: map[string]codersdk.WorkspaceAppHealth{
|
||||
"code-server-2": codersdk.WorkspaceAppHealthUnhealthy,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
apiApps, err = agentClient.WorkspaceAgentApps(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, codersdk.WorkspaceAppHealthUnhealthy, apiApps[1].Health)
|
||||
}
|
||||
|
|
|
@ -74,11 +74,24 @@ func TestWorkspaceResource(t *testing.T) {
|
|||
IncludeProvisionerDaemon: true,
|
||||
})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
app := &proto.App{
|
||||
Name: "code-server",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
apps := []*proto.App{
|
||||
{
|
||||
Name: "code-server",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
},
|
||||
{
|
||||
Name: "code-server-2",
|
||||
Command: "some-command",
|
||||
Url: "http://localhost:3000",
|
||||
Icon: "/code.svg",
|
||||
Healthcheck: &proto.Healthcheck{
|
||||
Url: "http://localhost:3000",
|
||||
Interval: 5,
|
||||
Threshold: 6,
|
||||
},
|
||||
},
|
||||
}
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
|
@ -91,7 +104,7 @@ func TestWorkspaceResource(t *testing.T) {
|
|||
Agents: []*proto.Agent{{
|
||||
Id: "something",
|
||||
Auth: &proto.Agent_Token{},
|
||||
Apps: []*proto.App{app},
|
||||
Apps: apps,
|
||||
}},
|
||||
}},
|
||||
},
|
||||
|
@ -112,11 +125,25 @@ func TestWorkspaceResource(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Len(t, resource.Agents, 1)
|
||||
agent := resource.Agents[0]
|
||||
require.Len(t, agent.Apps, 1)
|
||||
require.Len(t, agent.Apps, 2)
|
||||
got := agent.Apps[0]
|
||||
require.Equal(t, app.Command, got.Command)
|
||||
require.Equal(t, app.Icon, got.Icon)
|
||||
require.Equal(t, app.Name, got.Name)
|
||||
app := apps[0]
|
||||
require.EqualValues(t, app.Command, got.Command)
|
||||
require.EqualValues(t, app.Icon, got.Icon)
|
||||
require.EqualValues(t, app.Name, got.Name)
|
||||
require.EqualValues(t, codersdk.WorkspaceAppHealthDisabled, got.Health)
|
||||
require.EqualValues(t, "", got.Healthcheck.URL)
|
||||
require.EqualValues(t, 0, got.Healthcheck.Interval)
|
||||
require.EqualValues(t, 0, got.Healthcheck.Threshold)
|
||||
got = agent.Apps[1]
|
||||
app = apps[1]
|
||||
require.EqualValues(t, app.Command, got.Command)
|
||||
require.EqualValues(t, app.Icon, got.Icon)
|
||||
require.EqualValues(t, app.Name, got.Name)
|
||||
require.EqualValues(t, codersdk.WorkspaceAppHealthInitializing, got.Health)
|
||||
require.EqualValues(t, app.Healthcheck.Url, got.Healthcheck.URL)
|
||||
require.EqualValues(t, app.Healthcheck.Interval, got.Healthcheck.Interval)
|
||||
require.EqualValues(t, app.Healthcheck.Threshold, got.Healthcheck.Threshold)
|
||||
})
|
||||
|
||||
t.Run("Metadata", func(t *testing.T) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"golang.org/x/sync/singleflight"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/agent"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
// New creates a new workspace connection cache that closes
|
||||
|
@ -32,11 +32,11 @@ func New(dialer Dialer, inactiveTimeout time.Duration) *Cache {
|
|||
}
|
||||
|
||||
// Dialer creates a new agent connection by ID.
|
||||
type Dialer func(r *http.Request, id uuid.UUID) (*agent.Conn, error)
|
||||
type Dialer func(r *http.Request, id uuid.UUID) (*codersdk.AgentConn, error)
|
||||
|
||||
// Conn wraps an agent connection with a reusable HTTP transport.
|
||||
type Conn struct {
|
||||
*agent.Conn
|
||||
*codersdk.AgentConn
|
||||
|
||||
locks atomic.Uint64
|
||||
timeoutMutex sync.Mutex
|
||||
|
@ -59,7 +59,7 @@ func (c *Conn) CloseWithError(err error) error {
|
|||
if c.timeout != nil {
|
||||
c.timeout.Stop()
|
||||
}
|
||||
return c.Conn.CloseWithError(err)
|
||||
return c.AgentConn.CloseWithError(err)
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
|
@ -98,7 +98,7 @@ func (c *Cache) Acquire(r *http.Request, id uuid.UUID) (*Conn, func(), error) {
|
|||
transport := defaultTransport.Clone()
|
||||
transport.DialContext = agentConn.DialContext
|
||||
conn := &Conn{
|
||||
Conn: agentConn,
|
||||
AgentConn: agentConn,
|
||||
timeoutCancel: timeoutCancelFunc,
|
||||
transport: transport,
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"cdr.dev/slog/sloggers/slogtest"
|
||||
"github.com/coder/coder/agent"
|
||||
"github.com/coder/coder/coderd/wsconncache"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/tailnet"
|
||||
"github.com/coder/coder/tailnet/tailnettest"
|
||||
)
|
||||
|
@ -35,8 +36,8 @@ func TestCache(t *testing.T) {
|
|||
t.Parallel()
|
||||
t.Run("Same", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) {
|
||||
return setupAgent(t, agent.Metadata{}, 0), nil
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.AgentConn, error) {
|
||||
return setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0), nil
|
||||
}, 0)
|
||||
defer func() {
|
||||
_ = cache.Close()
|
||||
|
@ -50,9 +51,9 @@ func TestCache(t *testing.T) {
|
|||
t.Run("Expire", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
called := atomic.NewInt32(0)
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) {
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.AgentConn, error) {
|
||||
called.Add(1)
|
||||
return setupAgent(t, agent.Metadata{}, 0), nil
|
||||
return setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0), nil
|
||||
}, time.Microsecond)
|
||||
defer func() {
|
||||
_ = cache.Close()
|
||||
|
@ -69,8 +70,8 @@ func TestCache(t *testing.T) {
|
|||
})
|
||||
t.Run("NoExpireWhenLocked", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) {
|
||||
return setupAgent(t, agent.Metadata{}, 0), nil
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.AgentConn, error) {
|
||||
return setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0), nil
|
||||
}, time.Microsecond)
|
||||
defer func() {
|
||||
_ = cache.Close()
|
||||
|
@ -102,8 +103,8 @@ func TestCache(t *testing.T) {
|
|||
}()
|
||||
go server.Serve(random)
|
||||
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*agent.Conn, error) {
|
||||
return setupAgent(t, agent.Metadata{}, 0), nil
|
||||
cache := wsconncache.New(func(r *http.Request, id uuid.UUID) (*codersdk.AgentConn, error) {
|
||||
return setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0), nil
|
||||
}, time.Microsecond)
|
||||
defer func() {
|
||||
_ = cache.Close()
|
||||
|
@ -139,13 +140,13 @@ func TestCache(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration) *agent.Conn {
|
||||
func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeout time.Duration) *codersdk.AgentConn {
|
||||
metadata.DERPMap = tailnettest.RunDERPAndSTUN(t)
|
||||
|
||||
coordinator := tailnet.NewCoordinator()
|
||||
agentID := uuid.New()
|
||||
closer := agent.New(agent.Options{
|
||||
FetchMetadata: func(ctx context.Context) (agent.Metadata, error) {
|
||||
FetchMetadata: func(ctx context.Context) (codersdk.WorkspaceAgentMetadata, error) {
|
||||
return metadata, nil
|
||||
},
|
||||
CoordinatorDialer: func(ctx context.Context) (net.Conn, error) {
|
||||
|
@ -180,7 +181,7 @@ func setupAgent(t *testing.T, metadata agent.Metadata, ptyTimeout time.Duration)
|
|||
return conn.UpdateNodes(node)
|
||||
})
|
||||
conn.SetNodeCallback(sendNode)
|
||||
return &agent.Conn{
|
||||
return &codersdk.AgentConn{
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package agent
|
||||
package codersdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -18,23 +18,35 @@ import (
|
|||
"github.com/coder/coder/tailnet"
|
||||
)
|
||||
|
||||
var (
|
||||
// TailnetIP is a static IPv6 address with the Tailscale prefix that is used to route
|
||||
// connections from clients to this node. A dynamic address is not required because a Tailnet
|
||||
// client only dials a single agent at a time.
|
||||
TailnetIP = netip.MustParseAddr("fd7a:115c:a1e0:49d6:b259:b7ac:b1b2:48f4")
|
||||
TailnetSSHPort = 1
|
||||
TailnetReconnectingPTYPort = 2
|
||||
TailnetSpeedtestPort = 3
|
||||
)
|
||||
|
||||
// ReconnectingPTYRequest is sent from the client to the server
|
||||
// to pipe data to a PTY.
|
||||
// @typescript-ignore ReconnectingPTYRequest
|
||||
type ReconnectingPTYRequest struct {
|
||||
Data string `json:"data"`
|
||||
Height uint16 `json:"height"`
|
||||
Width uint16 `json:"width"`
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
// @typescript-ignore AgentConn
|
||||
type AgentConn struct {
|
||||
*tailnet.Conn
|
||||
CloseFunc func()
|
||||
}
|
||||
|
||||
func (c *Conn) Ping() (time.Duration, error) {
|
||||
func (c *AgentConn) Ping() (time.Duration, error) {
|
||||
errCh := make(chan error, 1)
|
||||
durCh := make(chan time.Duration, 1)
|
||||
c.Conn.Ping(tailnetIP, tailcfg.PingICMP, func(pr *ipnstate.PingResult) {
|
||||
c.Conn.Ping(TailnetIP, tailcfg.PingICMP, func(pr *ipnstate.PingResult) {
|
||||
if pr.Err != "" {
|
||||
errCh <- xerrors.New(pr.Err)
|
||||
return
|
||||
|
@ -49,30 +61,31 @@ func (c *Conn) Ping() (time.Duration, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Conn) CloseWithError(_ error) error {
|
||||
func (c *AgentConn) CloseWithError(_ error) error {
|
||||
return c.Close()
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
func (c *AgentConn) Close() error {
|
||||
if c.CloseFunc != nil {
|
||||
c.CloseFunc()
|
||||
}
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
type reconnectingPTYInit struct {
|
||||
// @typescript-ignore ReconnectingPTYInit
|
||||
type ReconnectingPTYInit struct {
|
||||
ID string
|
||||
Height uint16
|
||||
Width uint16
|
||||
Command string
|
||||
}
|
||||
|
||||
func (c *Conn) ReconnectingPTY(id string, height, width uint16, command string) (net.Conn, error) {
|
||||
conn, err := c.DialContextTCP(context.Background(), netip.AddrPortFrom(tailnetIP, uint16(tailnetReconnectingPTYPort)))
|
||||
func (c *AgentConn) ReconnectingPTY(id string, height, width uint16, command string) (net.Conn, error) {
|
||||
conn, err := c.DialContextTCP(context.Background(), netip.AddrPortFrom(TailnetIP, uint16(TailnetReconnectingPTYPort)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := json.Marshal(reconnectingPTYInit{
|
||||
data, err := json.Marshal(ReconnectingPTYInit{
|
||||
ID: id,
|
||||
Height: height,
|
||||
Width: width,
|
||||
|
@ -93,13 +106,13 @@ func (c *Conn) ReconnectingPTY(id string, height, width uint16, command string)
|
|||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Conn) SSH() (net.Conn, error) {
|
||||
return c.DialContextTCP(context.Background(), netip.AddrPortFrom(tailnetIP, uint16(tailnetSSHPort)))
|
||||
func (c *AgentConn) SSH() (net.Conn, error) {
|
||||
return c.DialContextTCP(context.Background(), netip.AddrPortFrom(TailnetIP, uint16(TailnetSSHPort)))
|
||||
}
|
||||
|
||||
// SSHClient calls SSH to create a client that uses a weak cipher
|
||||
// for high throughput.
|
||||
func (c *Conn) SSHClient() (*ssh.Client, error) {
|
||||
func (c *AgentConn) SSHClient() (*ssh.Client, error) {
|
||||
netConn, err := c.SSH()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("ssh: %w", err)
|
||||
|
@ -116,8 +129,8 @@ func (c *Conn) SSHClient() (*ssh.Client, error) {
|
|||
return ssh.NewClient(sshConn, channels, requests), nil
|
||||
}
|
||||
|
||||
func (c *Conn) Speedtest(direction speedtest.Direction, duration time.Duration) ([]speedtest.Result, error) {
|
||||
speedConn, err := c.DialContextTCP(context.Background(), netip.AddrPortFrom(tailnetIP, uint16(tailnetSpeedtestPort)))
|
||||
func (c *AgentConn) Speedtest(direction speedtest.Direction, duration time.Duration) ([]speedtest.Result, error) {
|
||||
speedConn, err := c.DialContextTCP(context.Background(), netip.AddrPortFrom(TailnetIP, uint16(TailnetSpeedtestPort)))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("dial speedtest: %w", err)
|
||||
}
|
||||
|
@ -128,13 +141,13 @@ func (c *Conn) Speedtest(direction speedtest.Direction, duration time.Duration)
|
|||
return results, err
|
||||
}
|
||||
|
||||
func (c *Conn) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
|
||||
func (c *AgentConn) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
|
||||
if network == "unix" {
|
||||
return nil, xerrors.New("network must be tcp or udp")
|
||||
}
|
||||
_, rawPort, _ := net.SplitHostPort(addr)
|
||||
port, _ := strconv.Atoi(rawPort)
|
||||
ipp := netip.AddrPortFrom(tailnetIP, uint16(port))
|
||||
ipp := netip.AddrPortFrom(TailnetIP, uint16(port))
|
||||
if network == "udp" {
|
||||
return c.Conn.DialContextUDP(ctx, ipp)
|
||||
}
|
|
@ -21,20 +21,22 @@ import (
|
|||
|
||||
"cdr.dev/slog"
|
||||
|
||||
"github.com/coder/coder/agent"
|
||||
"github.com/coder/coder/tailnet"
|
||||
"github.com/coder/retry"
|
||||
)
|
||||
|
||||
// @typescript-ignore GoogleInstanceIdentityToken
|
||||
type GoogleInstanceIdentityToken struct {
|
||||
JSONWebToken string `json:"json_web_token" validate:"required"`
|
||||
}
|
||||
|
||||
// @typescript-ignore AWSInstanceIdentityToken
|
||||
type AWSInstanceIdentityToken struct {
|
||||
Signature string `json:"signature" validate:"required"`
|
||||
Document string `json:"document" validate:"required"`
|
||||
}
|
||||
|
||||
// @typescript-ignore ReconnectingPTYRequest
|
||||
type AzureInstanceIdentityToken struct {
|
||||
Signature string `json:"signature" validate:"required"`
|
||||
Encoding string `json:"encoding" validate:"required"`
|
||||
|
@ -42,20 +44,31 @@ type AzureInstanceIdentityToken struct {
|
|||
|
||||
// WorkspaceAgentAuthenticateResponse is returned when an instance ID
|
||||
// has been exchanged for a session token.
|
||||
// @typescript-ignore WorkspaceAgentAuthenticateResponse
|
||||
type WorkspaceAgentAuthenticateResponse struct {
|
||||
SessionToken string `json:"session_token"`
|
||||
}
|
||||
|
||||
// WorkspaceAgentConnectionInfo returns required information for establishing
|
||||
// a connection with a workspace.
|
||||
// @typescript-ignore WorkspaceAgentConnectionInfo
|
||||
type WorkspaceAgentConnectionInfo struct {
|
||||
DERPMap *tailcfg.DERPMap `json:"derp_map"`
|
||||
}
|
||||
|
||||
// @typescript-ignore PostWorkspaceAgentVersionRequest
|
||||
type PostWorkspaceAgentVersionRequest struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// @typescript-ignore WorkspaceAgentMetadata
|
||||
type WorkspaceAgentMetadata struct {
|
||||
DERPMap *tailcfg.DERPMap `json:"derpmap"`
|
||||
EnvironmentVariables map[string]string `json:"environment_variables"`
|
||||
StartupScript string `json:"startup_script"`
|
||||
Directory string `json:"directory"`
|
||||
}
|
||||
|
||||
// AuthWorkspaceGoogleInstanceIdentity uses the Google Compute Engine Metadata API to
|
||||
// fetch a signed JWT, and exchange it for a session token for a workspace agent.
|
||||
//
|
||||
|
@ -185,16 +198,16 @@ func (c *Client) AuthWorkspaceAzureInstanceIdentity(ctx context.Context) (Worksp
|
|||
}
|
||||
|
||||
// WorkspaceAgentMetadata fetches metadata for the currently authenticated workspace agent.
|
||||
func (c *Client) WorkspaceAgentMetadata(ctx context.Context) (agent.Metadata, error) {
|
||||
func (c *Client) WorkspaceAgentMetadata(ctx context.Context) (WorkspaceAgentMetadata, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me/metadata", nil)
|
||||
if err != nil {
|
||||
return agent.Metadata{}, err
|
||||
return WorkspaceAgentMetadata{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return agent.Metadata{}, readBodyAsError(res)
|
||||
return WorkspaceAgentMetadata{}, readBodyAsError(res)
|
||||
}
|
||||
var agentMetadata agent.Metadata
|
||||
var agentMetadata WorkspaceAgentMetadata
|
||||
return agentMetadata, json.NewDecoder(res.Body).Decode(&agentMetadata)
|
||||
}
|
||||
|
||||
|
@ -228,7 +241,7 @@ func (c *Client) ListenWorkspaceAgentTailnet(ctx context.Context) (net.Conn, err
|
|||
return websocket.NetConn(ctx, conn, websocket.MessageBinary), nil
|
||||
}
|
||||
|
||||
func (c *Client) DialWorkspaceAgentTailnet(ctx context.Context, logger slog.Logger, agentID uuid.UUID) (*agent.Conn, error) {
|
||||
func (c *Client) DialWorkspaceAgentTailnet(ctx context.Context, logger slog.Logger, agentID uuid.UUID) (*AgentConn, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s/connection", agentID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -325,7 +338,7 @@ func (c *Client) DialWorkspaceAgentTailnet(ctx context.Context, logger slog.Logg
|
|||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &agent.Conn{
|
||||
return &AgentConn{
|
||||
Conn: conn,
|
||||
CloseFunc: func() {
|
||||
cancelFunc()
|
||||
|
@ -348,6 +361,34 @@ func (c *Client) WorkspaceAgent(ctx context.Context, id uuid.UUID) (WorkspaceAge
|
|||
return workspaceAgent, json.NewDecoder(res.Body).Decode(&workspaceAgent)
|
||||
}
|
||||
|
||||
// MyWorkspaceAgent returns the requesting agent.
|
||||
func (c *Client) WorkspaceAgentApps(ctx context.Context) ([]WorkspaceApp, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/me/apps", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, readBodyAsError(res)
|
||||
}
|
||||
var workspaceApps []WorkspaceApp
|
||||
return workspaceApps, json.NewDecoder(res.Body).Decode(&workspaceApps)
|
||||
}
|
||||
|
||||
// PostWorkspaceAgentAppHealth updates the workspace agent app health status.
|
||||
func (c *Client) PostWorkspaceAgentAppHealth(ctx context.Context, req PostWorkspaceAppHealthsRequest) error {
|
||||
res, err := c.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/app-health", req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return readBodyAsError(res)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) PostWorkspaceAgentVersion(ctx context.Context, version string) error {
|
||||
// Phone home and tell the mothership what version we're on.
|
||||
versionReq := PostWorkspaceAgentVersionRequest{Version: version}
|
||||
|
@ -392,12 +433,22 @@ func (c *Client) WorkspaceAgentReconnectingPTY(ctx context.Context, agentID, rec
|
|||
return websocket.NetConn(ctx, conn, websocket.MessageBinary), nil
|
||||
}
|
||||
|
||||
// Stats records the Agent's network connection statistics for use in
|
||||
// user-facing metrics and debugging.
|
||||
// Each member value must be written and read with atomic.
|
||||
// @typescript-ignore AgentStats
|
||||
type AgentStats struct {
|
||||
NumConns int64 `json:"num_comms"`
|
||||
RxBytes int64 `json:"rx_bytes"`
|
||||
TxBytes int64 `json:"tx_bytes"`
|
||||
}
|
||||
|
||||
// AgentReportStats begins a stat streaming connection with the Coder server.
|
||||
// It is resilient to network failures and intermittent coderd issues.
|
||||
func (c *Client) AgentReportStats(
|
||||
ctx context.Context,
|
||||
log slog.Logger,
|
||||
stats func() *agent.Stats,
|
||||
stats func() *AgentStats,
|
||||
) (io.Closer, error) {
|
||||
serverURL, err := c.URL.Parse("/api/v2/workspaceagents/me/report-stats")
|
||||
if err != nil {
|
||||
|
|
|
@ -4,6 +4,15 @@ import (
|
|||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type WorkspaceAppHealth string
|
||||
|
||||
const (
|
||||
WorkspaceAppHealthDisabled WorkspaceAppHealth = "disabled"
|
||||
WorkspaceAppHealthInitializing WorkspaceAppHealth = "initializing"
|
||||
WorkspaceAppHealthHealthy WorkspaceAppHealth = "healthy"
|
||||
WorkspaceAppHealthUnhealthy WorkspaceAppHealth = "unhealthy"
|
||||
)
|
||||
|
||||
type WorkspaceApp struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
// Name is a unique identifier attached to an agent.
|
||||
|
@ -12,4 +21,22 @@ type WorkspaceApp struct {
|
|||
// Icon is a relative path or external URL that specifies
|
||||
// an icon to be displayed in the dashboard.
|
||||
Icon string `json:"icon,omitempty"`
|
||||
// Healthcheck specifies the configuration for checking app health.
|
||||
Healthcheck Healthcheck `json:"healthcheck"`
|
||||
Health WorkspaceAppHealth `json:"health"`
|
||||
}
|
||||
|
||||
type Healthcheck struct {
|
||||
// URL specifies the url to check for the app health.
|
||||
URL string `json:"url"`
|
||||
// Interval specifies the seconds between each health check.
|
||||
Interval int32 `json:"interval"`
|
||||
// Threshold specifies the number of consecutive failed health checks before returning "unhealthy".
|
||||
Threshold int32 `json:"threshold"`
|
||||
}
|
||||
|
||||
// @typescript-ignore PostWorkspaceAppHealthsRequest
|
||||
type PostWorkspaceAppHealthsRequest struct {
|
||||
// Healths is a map of the workspace app name and the health of the app.
|
||||
Healths map[string]WorkspaceAppHealth
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.5"
|
||||
version = "0.4.15"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
|
|
|
@ -6,7 +6,7 @@ terraform {
|
|||
}
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
digitalocean = {
|
||||
source = "digitalocean/digitalocean"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
|
|
|
@ -3,7 +3,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
|
|
|
@ -9,7 +9,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
|
@ -47,6 +47,11 @@ resource "coder_app" "code-server" {
|
|||
name = "code-server"
|
||||
url = "http://localhost:13337/?folder=/home/coder"
|
||||
icon = "/icon/code.svg"
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
interval = 5
|
||||
threshold = 6
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
kubernetes = {
|
||||
source = "hashicorp/kubernetes"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/awalterschulze/gographviz"
|
||||
|
@ -25,12 +27,20 @@ type agentAttributes struct {
|
|||
|
||||
// A mapping of attributes on the "coder_app" resource.
|
||||
type agentAppAttributes struct {
|
||||
AgentID string `mapstructure:"agent_id"`
|
||||
Name string `mapstructure:"name"`
|
||||
Icon string `mapstructure:"icon"`
|
||||
URL string `mapstructure:"url"`
|
||||
Command string `mapstructure:"command"`
|
||||
RelativePath bool `mapstructure:"relative_path"`
|
||||
AgentID string `mapstructure:"agent_id"`
|
||||
Name string `mapstructure:"name"`
|
||||
Icon string `mapstructure:"icon"`
|
||||
URL string `mapstructure:"url"`
|
||||
Command string `mapstructure:"command"`
|
||||
RelativePath bool `mapstructure:"relative_path"`
|
||||
Healthcheck []appHealthcheckAttributes `mapstructure:"healthcheck"`
|
||||
}
|
||||
|
||||
// A mapping of attributes on the "healthcheck" resource.
|
||||
type appHealthcheckAttributes struct {
|
||||
URL string `mapstructure:"url"`
|
||||
Interval int32 `mapstructure:"interval"`
|
||||
Threshold int32 `mapstructure:"threshold"`
|
||||
}
|
||||
|
||||
// A mapping of attributes on the "coder_metadata" resource.
|
||||
|
@ -212,12 +222,22 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
|
|||
var attrs agentAppAttributes
|
||||
err = mapstructure.Decode(resource.AttributeValues, &attrs)
|
||||
if err != nil {
|
||||
d, _ := json.MarshalIndent(resource.AttributeValues, "", " ")
|
||||
fmt.Print(string(d))
|
||||
return nil, xerrors.Errorf("decode app attributes: %w", err)
|
||||
}
|
||||
if attrs.Name == "" {
|
||||
// Default to the resource name if none is set!
|
||||
attrs.Name = resource.Name
|
||||
}
|
||||
var healthcheck *proto.Healthcheck
|
||||
if len(attrs.Healthcheck) != 0 {
|
||||
healthcheck = &proto.Healthcheck{
|
||||
Url: attrs.Healthcheck[0].URL,
|
||||
Interval: attrs.Healthcheck[0].Interval,
|
||||
Threshold: attrs.Healthcheck[0].Threshold,
|
||||
}
|
||||
}
|
||||
for _, agents := range resourceAgents {
|
||||
for _, agent := range agents {
|
||||
// Find agents with the matching ID and associate them!
|
||||
|
@ -230,6 +250,7 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
|
|||
Url: attrs.URL,
|
||||
Icon: attrs.Icon,
|
||||
RelativePath: attrs.RelativePath,
|
||||
Healthcheck: healthcheck,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,11 @@ func TestConvertResources(t *testing.T) {
|
|||
Name: "app1",
|
||||
}, {
|
||||
Name: "app2",
|
||||
Healthcheck: &proto.Healthcheck{
|
||||
Url: "http://localhost:13337/healthz",
|
||||
Interval: 5,
|
||||
Threshold: 6,
|
||||
},
|
||||
}},
|
||||
Auth: &proto.Agent_Token{},
|
||||
}},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "f05ddf9e-106a-4669-bba8-5e2289bd891d",
|
||||
"id": "f5435556-71b4-4e9c-a961-474ef4c70836",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "ed4655b9-e917-44af-8706-a1215384a35f"
|
||||
"token": "cbe1cec2-8c52-4411-ab1b-c7e9aa4e93ea"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@
|
|||
"outputs": {
|
||||
"script": ""
|
||||
},
|
||||
"random": "7640853885488752810"
|
||||
"random": "2977741887145450154"
|
||||
},
|
||||
"sensitive_values": {
|
||||
"inputs": {},
|
||||
|
@ -59,7 +59,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "6481148597794195898",
|
||||
"id": "3098344175322958112",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
8
provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json
generated
vendored
8
provisioner/terraform/testdata/chaining-resources/chaining-resources.tfstate.json
generated
vendored
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "fcd8018c-7e4a-4e92-855b-e02319ab051e",
|
||||
"id": "846b2cd1-1dcc-4b26-ad71-8508c8d71738",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "ad906408-0eb0-4844-83f7-0f5070427e1c"
|
||||
"token": "3a3e4e25-6be2-4b51-a369-957fdb243a4f"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -32,7 +32,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "2672857180605476162",
|
||||
"id": "8441562949971496089",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
@ -49,7 +49,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "264584188140644760",
|
||||
"id": "4737933879128730392",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "e3df7d56-17ce-4d8a-9d4e-30ea41cc8a93",
|
||||
"id": "2efd4acf-bb30-4713-98b5-21fef293c995",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "1717f79d-2c72-440e-a5c6-e4b8c3fef084"
|
||||
"token": "7db84d6e-c079-4b4a-99e0-e2414a70df84"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -32,7 +32,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "2957375211969224115",
|
||||
"id": "6618109150570768254",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
@ -48,7 +48,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "6924176854496195292",
|
||||
"id": "4505836003282545145",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"auth": "google-instance-identity",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "9a37096a-7f01-42cd-93d8-9f4572c94489",
|
||||
"id": "e2d2e12e-1975-4bca-8a96-67d6b303b25b",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "7784ea1f-7fe5-463f-af8d-255c32d12992"
|
||||
"token": "87ba2736-3519-4368-b9ee-4132bd042fe3"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -32,8 +32,8 @@
|
|||
"provider_name": "registry.terraform.io/coder/coder",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"agent_id": "9a37096a-7f01-42cd-93d8-9f4572c94489",
|
||||
"id": "8ed448e2-51d7-4cc7-9e26-a3a77f252b1d",
|
||||
"agent_id": "e2d2e12e-1975-4bca-8a96-67d6b303b25b",
|
||||
"id": "979121e7-2a41-432a-aa90-8b0d2d802b50",
|
||||
"instance_id": "example"
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
@ -49,7 +49,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "771742387122791362",
|
||||
"id": "3316746911978433294",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "0c3c20d8-8a1d-4fc9-bc73-ed45ddad9a9d",
|
||||
"id": "032d71aa-570d-4d0a-bce8-57b9d884b694",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "48b3f4c4-4bb9-477c-8d32-d1e14188e5f8"
|
||||
"token": "7a0df6bf-313d-4f73-ba2c-6532d72cb808"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -36,11 +36,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "08e8ebc8-4660-47f0-acb5-6ca46747919d",
|
||||
"id": "019ae4b9-ae5c-4837-be16-dae99b911acf",
|
||||
"init_script": "",
|
||||
"os": "darwin",
|
||||
"startup_script": null,
|
||||
"token": "827a1f01-a2d7-4794-ab73-8fd8442010d5"
|
||||
"token": "9f4adbf4-9113-42f4-bb84-d1621262b1e2"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -56,11 +56,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "50f52bd4-a52b-4c73-bf99-fe956913bca4",
|
||||
"id": "8f2c3b12-e112-405e-9fbf-fe540ed3fe21",
|
||||
"init_script": "",
|
||||
"os": "windows",
|
||||
"startup_script": null,
|
||||
"token": "159d6407-a913-4e05-8ba7-786d47a7e34b"
|
||||
"token": "1a6ddbc7-77a9-43c2-9e60-c84d3ecf512a"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -72,7 +72,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "2529387636030139440",
|
||||
"id": "6351611769218065391",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,11 @@ resource "coder_app" "app1" {
|
|||
|
||||
resource "coder_app" "app2" {
|
||||
agent_id = coder_agent.dev1.id
|
||||
healthcheck {
|
||||
url = "http://localhost:13337/healthz"
|
||||
interval = 5
|
||||
threshold = 6
|
||||
}
|
||||
}
|
||||
|
||||
resource "null_resource" "dev" {
|
||||
|
|
|
@ -30,12 +30,15 @@
|
|||
"schema_version": 0,
|
||||
"values": {
|
||||
"command": null,
|
||||
"healthcheck": [],
|
||||
"icon": null,
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"url": null
|
||||
},
|
||||
"sensitive_values": {}
|
||||
"sensitive_values": {
|
||||
"healthcheck": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "coder_app.app2",
|
||||
|
@ -46,12 +49,23 @@
|
|||
"schema_version": 0,
|
||||
"values": {
|
||||
"command": null,
|
||||
"healthcheck": [
|
||||
{
|
||||
"interval": 5,
|
||||
"threshold": 6,
|
||||
"url": "http://localhost:13337/healthz"
|
||||
}
|
||||
],
|
||||
"icon": null,
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"url": null
|
||||
},
|
||||
"sensitive_values": {}
|
||||
"sensitive_values": {
|
||||
"healthcheck": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"address": "null_resource.dev",
|
||||
|
@ -94,7 +108,9 @@
|
|||
"token": true
|
||||
},
|
||||
"before_sensitive": false,
|
||||
"after_sensitive": {}
|
||||
"after_sensitive": {
|
||||
"token": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -110,6 +126,7 @@
|
|||
"before": null,
|
||||
"after": {
|
||||
"command": null,
|
||||
"healthcheck": [],
|
||||
"icon": null,
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
|
@ -117,10 +134,13 @@
|
|||
},
|
||||
"after_unknown": {
|
||||
"agent_id": true,
|
||||
"healthcheck": [],
|
||||
"id": true
|
||||
},
|
||||
"before_sensitive": false,
|
||||
"after_sensitive": {}
|
||||
"after_sensitive": {
|
||||
"healthcheck": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -136,6 +156,13 @@
|
|||
"before": null,
|
||||
"after": {
|
||||
"command": null,
|
||||
"healthcheck": [
|
||||
{
|
||||
"interval": 5,
|
||||
"threshold": 6,
|
||||
"url": "http://localhost:13337/healthz"
|
||||
}
|
||||
],
|
||||
"icon": null,
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
|
@ -143,10 +170,17 @@
|
|||
},
|
||||
"after_unknown": {
|
||||
"agent_id": true,
|
||||
"healthcheck": [
|
||||
{}
|
||||
],
|
||||
"id": true
|
||||
},
|
||||
"before_sensitive": false,
|
||||
"after_sensitive": {}
|
||||
"after_sensitive": {
|
||||
"healthcheck": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -176,7 +210,7 @@
|
|||
"coder": {
|
||||
"name": "coder",
|
||||
"full_name": "registry.terraform.io/coder/coder",
|
||||
"version_constraint": "0.4.11"
|
||||
"version_constraint": "0.4.14"
|
||||
},
|
||||
"null": {
|
||||
"name": "null",
|
||||
|
@ -229,7 +263,20 @@
|
|||
"coder_agent.dev1.id",
|
||||
"coder_agent.dev1"
|
||||
]
|
||||
}
|
||||
},
|
||||
"healthcheck": [
|
||||
{
|
||||
"interval": {
|
||||
"constant_value": 5
|
||||
},
|
||||
"threshold": {
|
||||
"constant_value": 6
|
||||
},
|
||||
"url": {
|
||||
"constant_value": "http://localhost:13337/healthz"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema_version": 0
|
||||
},
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "3d4ee1d5-6413-4dc7-baec-2fa9dbd870ba",
|
||||
"id": "685dba1f-09de-40c0-8fc0-4d8ca00ef946",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "32e082d7-af02-42f1-a5bd-f6adc34220a1"
|
||||
"token": "2c73d680-ef4c-4bc1-80f0-f6916e4e5255"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -32,15 +32,18 @@
|
|||
"provider_name": "registry.terraform.io/coder/coder",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"agent_id": "3d4ee1d5-6413-4dc7-baec-2fa9dbd870ba",
|
||||
"agent_id": "685dba1f-09de-40c0-8fc0-4d8ca00ef946",
|
||||
"command": null,
|
||||
"healthcheck": [],
|
||||
"icon": null,
|
||||
"id": "90e045f9-19f1-4d8a-8021-be61c44ee54f",
|
||||
"id": "46f8d3cd-bcf7-4792-8d54-66e01e63018a",
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"url": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
"sensitive_values": {
|
||||
"healthcheck": []
|
||||
},
|
||||
"depends_on": [
|
||||
"coder_agent.dev1"
|
||||
]
|
||||
|
@ -53,15 +56,26 @@
|
|||
"provider_name": "registry.terraform.io/coder/coder",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"agent_id": "3d4ee1d5-6413-4dc7-baec-2fa9dbd870ba",
|
||||
"agent_id": "685dba1f-09de-40c0-8fc0-4d8ca00ef946",
|
||||
"command": null,
|
||||
"healthcheck": [
|
||||
{
|
||||
"interval": 5,
|
||||
"threshold": 6,
|
||||
"url": "http://localhost:13337/healthz"
|
||||
}
|
||||
],
|
||||
"icon": null,
|
||||
"id": "873026f8-3050-4b0b-bebf-41e13e5949bb",
|
||||
"id": "e4556c74-2f67-4266-b1e8-7ee61d754583",
|
||||
"name": null,
|
||||
"relative_path": null,
|
||||
"url": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
"sensitive_values": {
|
||||
"healthcheck": [
|
||||
{}
|
||||
]
|
||||
},
|
||||
"depends_on": [
|
||||
"coder_agent.dev1"
|
||||
]
|
||||
|
@ -74,7 +88,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "4447693752005094678",
|
||||
"id": "2997000197756647168",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {},
|
||||
|
|
|
@ -2,7 +2,7 @@ terraform {
|
|||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "0.4.11"
|
||||
version = "0.4.15"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json
generated
vendored
10
provisioner/terraform/testdata/resource-metadata/resource-metadata.tfstate.json
generated
vendored
|
@ -16,11 +16,11 @@
|
|||
"auth": "token",
|
||||
"dir": null,
|
||||
"env": null,
|
||||
"id": "09aac2a4-9d8e-43ef-83cb-34657db199f4",
|
||||
"id": "50a0466c-d983-422f-8bed-9dd0bf705a9a",
|
||||
"init_script": "",
|
||||
"os": "linux",
|
||||
"startup_script": null,
|
||||
"token": "a0f6b8af-8edc-447f-b6d2-67a60ecd2a77"
|
||||
"token": "aa714059-3579-49d1-a0e2-3519dbe43688"
|
||||
},
|
||||
"sensitive_values": {}
|
||||
},
|
||||
|
@ -34,7 +34,7 @@
|
|||
"values": {
|
||||
"hide": true,
|
||||
"icon": "/icon/server.svg",
|
||||
"id": "a7f9cf03-de78-4d17-bcbb-21dc34c2d86a",
|
||||
"id": "64a47d31-28d0-4a50-8e09-a3e705278305",
|
||||
"item": [
|
||||
{
|
||||
"is_null": false,
|
||||
|
@ -61,7 +61,7 @@
|
|||
"value": "squirrel"
|
||||
}
|
||||
],
|
||||
"resource_id": "6209384655473556868"
|
||||
"resource_id": "4887255791781048166"
|
||||
},
|
||||
"sensitive_values": {
|
||||
"item": [
|
||||
|
@ -83,7 +83,7 @@
|
|||
"provider_name": "registry.terraform.io/hashicorp/null",
|
||||
"schema_version": 0,
|
||||
"values": {
|
||||
"id": "6209384655473556868",
|
||||
"id": "4887255791781048166",
|
||||
"triggers": null
|
||||
},
|
||||
"sensitive_values": {}
|
||||
|
|
|
@ -850,11 +850,12 @@ type App struct {
|
|||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"`
|
||||
Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"`
|
||||
Icon string `protobuf:"bytes,4,opt,name=icon,proto3" json:"icon,omitempty"`
|
||||
RelativePath bool `protobuf:"varint,5,opt,name=relative_path,json=relativePath,proto3" json:"relative_path,omitempty"`
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"`
|
||||
Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"`
|
||||
Icon string `protobuf:"bytes,4,opt,name=icon,proto3" json:"icon,omitempty"`
|
||||
RelativePath bool `protobuf:"varint,5,opt,name=relative_path,json=relativePath,proto3" json:"relative_path,omitempty"`
|
||||
Healthcheck *Healthcheck `protobuf:"bytes,6,opt,name=healthcheck,proto3" json:"healthcheck,omitempty"`
|
||||
}
|
||||
|
||||
func (x *App) Reset() {
|
||||
|
@ -924,6 +925,77 @@ func (x *App) GetRelativePath() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (x *App) GetHealthcheck() *Healthcheck {
|
||||
if x != nil {
|
||||
return x.Healthcheck
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Healthcheck represents configuration for checking for app readiness.
|
||||
type Healthcheck struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
|
||||
Interval int32 `protobuf:"varint,2,opt,name=interval,proto3" json:"interval,omitempty"`
|
||||
Threshold int32 `protobuf:"varint,3,opt,name=threshold,proto3" json:"threshold,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Healthcheck) Reset() {
|
||||
*x = Healthcheck{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Healthcheck) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Healthcheck) ProtoMessage() {}
|
||||
|
||||
func (x *Healthcheck) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[9]
|
||||
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 Healthcheck.ProtoReflect.Descriptor instead.
|
||||
func (*Healthcheck) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *Healthcheck) GetUrl() string {
|
||||
if x != nil {
|
||||
return x.Url
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Healthcheck) GetInterval() int32 {
|
||||
if x != nil {
|
||||
return x.Interval
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Healthcheck) GetThreshold() int32 {
|
||||
if x != nil {
|
||||
return x.Threshold
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Resource represents created infrastructure.
|
||||
type Resource struct {
|
||||
state protoimpl.MessageState
|
||||
|
@ -941,7 +1013,7 @@ type Resource struct {
|
|||
func (x *Resource) Reset() {
|
||||
*x = Resource{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[9]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -954,7 +1026,7 @@ func (x *Resource) String() string {
|
|||
func (*Resource) ProtoMessage() {}
|
||||
|
||||
func (x *Resource) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[9]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[10]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -967,7 +1039,7 @@ func (x *Resource) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Resource.ProtoReflect.Descriptor instead.
|
||||
func (*Resource) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{9}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *Resource) GetName() string {
|
||||
|
@ -1022,7 +1094,7 @@ type Parse struct {
|
|||
func (x *Parse) Reset() {
|
||||
*x = Parse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[10]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1035,7 +1107,7 @@ func (x *Parse) String() string {
|
|||
func (*Parse) ProtoMessage() {}
|
||||
|
||||
func (x *Parse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[10]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1048,7 +1120,7 @@ func (x *Parse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Parse.ProtoReflect.Descriptor instead.
|
||||
func (*Parse) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
// Provision consumes source-code from a directory to produce resources.
|
||||
|
@ -1061,7 +1133,7 @@ type Provision struct {
|
|||
func (x *Provision) Reset() {
|
||||
*x = Provision{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1074,7 +1146,7 @@ func (x *Provision) String() string {
|
|||
func (*Provision) ProtoMessage() {}
|
||||
|
||||
func (x *Provision) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[12]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1087,7 +1159,7 @@ func (x *Provision) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Provision.ProtoReflect.Descriptor instead.
|
||||
func (*Provision) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12}
|
||||
}
|
||||
|
||||
type Resource_Metadata struct {
|
||||
|
@ -1104,7 +1176,7 @@ type Resource_Metadata struct {
|
|||
func (x *Resource_Metadata) Reset() {
|
||||
*x = Resource_Metadata{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[13]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[14]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1117,7 +1189,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[13]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[14]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1130,7 +1202,7 @@ func (x *Resource_Metadata) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Resource_Metadata.ProtoReflect.Descriptor instead.
|
||||
func (*Resource_Metadata) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{9, 0}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10, 0}
|
||||
}
|
||||
|
||||
func (x *Resource_Metadata) GetKey() string {
|
||||
|
@ -1172,7 +1244,7 @@ type Parse_Request struct {
|
|||
func (x *Parse_Request) Reset() {
|
||||
*x = Parse_Request{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[14]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[15]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1185,7 +1257,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[14]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[15]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1198,7 +1270,7 @@ func (x *Parse_Request) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Parse_Request.ProtoReflect.Descriptor instead.
|
||||
func (*Parse_Request) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10, 0}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 0}
|
||||
}
|
||||
|
||||
func (x *Parse_Request) GetDirectory() string {
|
||||
|
@ -1219,7 +1291,7 @@ type Parse_Complete struct {
|
|||
func (x *Parse_Complete) Reset() {
|
||||
*x = Parse_Complete{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[15]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[16]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1232,7 +1304,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[15]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[16]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1245,7 +1317,7 @@ func (x *Parse_Complete) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Parse_Complete.ProtoReflect.Descriptor instead.
|
||||
func (*Parse_Complete) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10, 1}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 1}
|
||||
}
|
||||
|
||||
func (x *Parse_Complete) GetParameterSchemas() []*ParameterSchema {
|
||||
|
@ -1270,7 +1342,7 @@ type Parse_Response struct {
|
|||
func (x *Parse_Response) Reset() {
|
||||
*x = Parse_Response{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[16]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[17]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1283,7 +1355,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[16]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[17]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1296,7 +1368,7 @@ func (x *Parse_Response) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Parse_Response.ProtoReflect.Descriptor instead.
|
||||
func (*Parse_Response) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{10, 2}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 2}
|
||||
}
|
||||
|
||||
func (m *Parse_Response) GetType() isParse_Response_Type {
|
||||
|
@ -1353,7 +1425,7 @@ type Provision_Metadata struct {
|
|||
func (x *Provision_Metadata) Reset() {
|
||||
*x = Provision_Metadata{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[17]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1366,7 +1438,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[17]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1379,7 +1451,7 @@ func (x *Provision_Metadata) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Provision_Metadata.ProtoReflect.Descriptor instead.
|
||||
func (*Provision_Metadata) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 0}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 0}
|
||||
}
|
||||
|
||||
func (x *Provision_Metadata) GetCoderUrl() string {
|
||||
|
@ -1446,7 +1518,7 @@ type Provision_Start struct {
|
|||
func (x *Provision_Start) Reset() {
|
||||
*x = Provision_Start{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1459,7 +1531,7 @@ func (x *Provision_Start) String() string {
|
|||
func (*Provision_Start) ProtoMessage() {}
|
||||
|
||||
func (x *Provision_Start) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1472,7 +1544,7 @@ func (x *Provision_Start) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Provision_Start.ProtoReflect.Descriptor instead.
|
||||
func (*Provision_Start) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 1}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 1}
|
||||
}
|
||||
|
||||
func (x *Provision_Start) GetDirectory() string {
|
||||
|
@ -1519,7 +1591,7 @@ type Provision_Cancel struct {
|
|||
func (x *Provision_Cancel) Reset() {
|
||||
*x = Provision_Cancel{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1532,7 +1604,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[19]
|
||||
mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1545,7 +1617,7 @@ func (x *Provision_Cancel) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Provision_Cancel.ProtoReflect.Descriptor instead.
|
||||
func (*Provision_Cancel) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 2}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 2}
|
||||
}
|
||||
|
||||
type Provision_Request struct {
|
||||
|
@ -1563,7 +1635,7 @@ type Provision_Request struct {
|
|||
func (x *Provision_Request) Reset() {
|
||||
*x = Provision_Request{}
|
||||
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)
|
||||
}
|
||||
|
@ -1576,7 +1648,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[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 {
|
||||
|
@ -1589,7 +1661,7 @@ func (x *Provision_Request) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Provision_Request.ProtoReflect.Descriptor instead.
|
||||
func (*Provision_Request) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 3}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 3}
|
||||
}
|
||||
|
||||
func (m *Provision_Request) GetType() isProvision_Request_Type {
|
||||
|
@ -1642,7 +1714,7 @@ type Provision_Complete struct {
|
|||
func (x *Provision_Complete) Reset() {
|
||||
*x = Provision_Complete{}
|
||||
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)
|
||||
}
|
||||
|
@ -1655,7 +1727,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[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 {
|
||||
|
@ -1668,7 +1740,7 @@ func (x *Provision_Complete) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Provision_Complete.ProtoReflect.Descriptor instead.
|
||||
func (*Provision_Complete) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 4}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 4}
|
||||
}
|
||||
|
||||
func (x *Provision_Complete) GetState() []byte {
|
||||
|
@ -1707,7 +1779,7 @@ type Provision_Response struct {
|
|||
func (x *Provision_Response) Reset() {
|
||||
*x = Provision_Response{}
|
||||
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)
|
||||
}
|
||||
|
@ -1720,7 +1792,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[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 {
|
||||
|
@ -1733,7 +1805,7 @@ func (x *Provision_Response) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Provision_Response.ProtoReflect.Descriptor instead.
|
||||
func (*Provision_Response) Descriptor() ([]byte, []int) {
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11, 5}
|
||||
return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12, 5}
|
||||
}
|
||||
|
||||
func (m *Provision_Response) GetType() isProvision_Response_Type {
|
||||
|
@ -1880,131 +1952,140 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
|
|||
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, 0x7e,
|
||||
0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d,
|
||||
0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d,
|
||||
0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x6c,
|
||||
0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x22, 0xad,
|
||||
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, 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, 0xfc,
|
||||
0x01, 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, 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 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, 0xae, 0x07,
|
||||
0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xd1, 0x02, 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, 0x1a,
|
||||
0xd9, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 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, 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,
|
||||
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, 0x14, 0x0a, 0x05,
|
||||
0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61,
|
||||
0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43,
|
||||
0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0x80, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xba,
|
||||
0x01, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f,
|
||||
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d,
|
||||
0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65,
|
||||
0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28,
|
||||
0x08, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12,
|
||||
0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x06,
|
||||
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, 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, 0xad, 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, 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, 0xfc, 0x01, 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, 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d,
|
||||
0x70, 0x6c, 0x65, 0x74, 0x65, 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,
|
||||
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x48, 0x00,
|
||||
0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65,
|
||||
0x6c, 0x18, 0x02, 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, 0x6b, 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, 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,
|
||||
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, 0xae, 0x07, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x1a, 0xd1, 0x02, 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, 0x1a, 0xd9, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72,
|
||||
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, 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, 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, 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,
|
||||
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,
|
||||
0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61,
|
||||
0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72,
|
||||
0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79,
|
||||
0x52, 0x75, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, 0x80, 0x01,
|
||||
0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x73, 0x74, 0x61,
|
||||
0x72, 0x74, 0x18, 0x01, 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, 0x53, 0x74, 0x61, 0x72, 0x74, 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12,
|
||||
0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x02, 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, 0x6b, 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, 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, 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 (
|
||||
|
@ -2020,7 +2101,7 @@ func file_provisionersdk_proto_provisioner_proto_rawDescGZIP() []byte {
|
|||
}
|
||||
|
||||
var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
|
||||
var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
|
||||
var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 24)
|
||||
var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{
|
||||
(LogLevel)(0), // 0: provisioner.LogLevel
|
||||
(WorkspaceTransition)(0), // 1: provisioner.WorkspaceTransition
|
||||
|
@ -2036,20 +2117,21 @@ var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{
|
|||
(*InstanceIdentityAuth)(nil), // 11: provisioner.InstanceIdentityAuth
|
||||
(*Agent)(nil), // 12: provisioner.Agent
|
||||
(*App)(nil), // 13: provisioner.App
|
||||
(*Resource)(nil), // 14: provisioner.Resource
|
||||
(*Parse)(nil), // 15: provisioner.Parse
|
||||
(*Provision)(nil), // 16: provisioner.Provision
|
||||
nil, // 17: provisioner.Agent.EnvEntry
|
||||
(*Resource_Metadata)(nil), // 18: provisioner.Resource.Metadata
|
||||
(*Parse_Request)(nil), // 19: provisioner.Parse.Request
|
||||
(*Parse_Complete)(nil), // 20: provisioner.Parse.Complete
|
||||
(*Parse_Response)(nil), // 21: provisioner.Parse.Response
|
||||
(*Provision_Metadata)(nil), // 22: provisioner.Provision.Metadata
|
||||
(*Provision_Start)(nil), // 23: provisioner.Provision.Start
|
||||
(*Provision_Cancel)(nil), // 24: provisioner.Provision.Cancel
|
||||
(*Provision_Request)(nil), // 25: provisioner.Provision.Request
|
||||
(*Provision_Complete)(nil), // 26: provisioner.Provision.Complete
|
||||
(*Provision_Response)(nil), // 27: provisioner.Provision.Response
|
||||
(*Healthcheck)(nil), // 14: provisioner.Healthcheck
|
||||
(*Resource)(nil), // 15: provisioner.Resource
|
||||
(*Parse)(nil), // 16: provisioner.Parse
|
||||
(*Provision)(nil), // 17: provisioner.Provision
|
||||
nil, // 18: provisioner.Agent.EnvEntry
|
||||
(*Resource_Metadata)(nil), // 19: provisioner.Resource.Metadata
|
||||
(*Parse_Request)(nil), // 20: provisioner.Parse.Request
|
||||
(*Parse_Complete)(nil), // 21: provisioner.Parse.Complete
|
||||
(*Parse_Response)(nil), // 22: provisioner.Parse.Response
|
||||
(*Provision_Metadata)(nil), // 23: provisioner.Provision.Metadata
|
||||
(*Provision_Start)(nil), // 24: provisioner.Provision.Start
|
||||
(*Provision_Cancel)(nil), // 25: provisioner.Provision.Cancel
|
||||
(*Provision_Request)(nil), // 26: provisioner.Provision.Request
|
||||
(*Provision_Complete)(nil), // 27: provisioner.Provision.Complete
|
||||
(*Provision_Response)(nil), // 28: provisioner.Provision.Response
|
||||
}
|
||||
var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{
|
||||
2, // 0: provisioner.ParameterSource.scheme:type_name -> provisioner.ParameterSource.Scheme
|
||||
|
@ -2059,30 +2141,31 @@ var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{
|
|||
7, // 4: provisioner.ParameterSchema.default_destination:type_name -> provisioner.ParameterDestination
|
||||
4, // 5: provisioner.ParameterSchema.validation_type_system:type_name -> provisioner.ParameterSchema.TypeSystem
|
||||
0, // 6: provisioner.Log.level:type_name -> provisioner.LogLevel
|
||||
17, // 7: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry
|
||||
18, // 7: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry
|
||||
13, // 8: provisioner.Agent.apps:type_name -> provisioner.App
|
||||
12, // 9: provisioner.Resource.agents:type_name -> provisioner.Agent
|
||||
18, // 10: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata
|
||||
9, // 11: provisioner.Parse.Complete.parameter_schemas:type_name -> provisioner.ParameterSchema
|
||||
10, // 12: provisioner.Parse.Response.log:type_name -> provisioner.Log
|
||||
20, // 13: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete
|
||||
1, // 14: provisioner.Provision.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition
|
||||
8, // 15: provisioner.Provision.Start.parameter_values:type_name -> provisioner.ParameterValue
|
||||
22, // 16: provisioner.Provision.Start.metadata:type_name -> provisioner.Provision.Metadata
|
||||
23, // 17: provisioner.Provision.Request.start:type_name -> provisioner.Provision.Start
|
||||
24, // 18: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel
|
||||
14, // 19: provisioner.Provision.Complete.resources:type_name -> provisioner.Resource
|
||||
10, // 20: provisioner.Provision.Response.log:type_name -> provisioner.Log
|
||||
26, // 21: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete
|
||||
19, // 22: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request
|
||||
25, // 23: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request
|
||||
21, // 24: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response
|
||||
27, // 25: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response
|
||||
24, // [24:26] is the sub-list for method output_type
|
||||
22, // [22:24] is the sub-list for method input_type
|
||||
22, // [22:22] is the sub-list for extension type_name
|
||||
22, // [22:22] is the sub-list for extension extendee
|
||||
0, // [0:22] is the sub-list for field type_name
|
||||
14, // 9: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck
|
||||
12, // 10: provisioner.Resource.agents:type_name -> provisioner.Agent
|
||||
19, // 11: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata
|
||||
9, // 12: provisioner.Parse.Complete.parameter_schemas:type_name -> provisioner.ParameterSchema
|
||||
10, // 13: provisioner.Parse.Response.log:type_name -> provisioner.Log
|
||||
21, // 14: provisioner.Parse.Response.complete:type_name -> provisioner.Parse.Complete
|
||||
1, // 15: provisioner.Provision.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition
|
||||
8, // 16: provisioner.Provision.Start.parameter_values:type_name -> provisioner.ParameterValue
|
||||
23, // 17: provisioner.Provision.Start.metadata:type_name -> provisioner.Provision.Metadata
|
||||
24, // 18: provisioner.Provision.Request.start:type_name -> provisioner.Provision.Start
|
||||
25, // 19: provisioner.Provision.Request.cancel:type_name -> provisioner.Provision.Cancel
|
||||
15, // 20: provisioner.Provision.Complete.resources:type_name -> provisioner.Resource
|
||||
10, // 21: provisioner.Provision.Response.log:type_name -> provisioner.Log
|
||||
27, // 22: provisioner.Provision.Response.complete:type_name -> provisioner.Provision.Complete
|
||||
20, // 23: provisioner.Provisioner.Parse:input_type -> provisioner.Parse.Request
|
||||
26, // 24: provisioner.Provisioner.Provision:input_type -> provisioner.Provision.Request
|
||||
22, // 25: provisioner.Provisioner.Parse:output_type -> provisioner.Parse.Response
|
||||
28, // 26: provisioner.Provisioner.Provision:output_type -> provisioner.Provision.Response
|
||||
25, // [25:27] is the sub-list for method output_type
|
||||
23, // [23:25] is the sub-list for method input_type
|
||||
23, // [23:23] is the sub-list for extension type_name
|
||||
23, // [23:23] is the sub-list for extension extendee
|
||||
0, // [0:23] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_provisionersdk_proto_provisioner_proto_init() }
|
||||
|
@ -2200,7 +2283,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Resource); i {
|
||||
switch v := v.(*Healthcheck); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -2212,7 +2295,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Parse); i {
|
||||
switch v := v.(*Resource); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -2224,6 +2307,18 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Parse); 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[12].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Provision); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2235,7 +2330,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Resource_Metadata); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2247,7 +2342,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Parse_Request); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2259,7 +2354,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Parse_Complete); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2271,7 +2366,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Parse_Response); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2283,7 +2378,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Provision_Metadata); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2295,7 +2390,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Provision_Start); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2307,7 +2402,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Provision_Cancel); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2319,7 +2414,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Provision_Request); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2331,7 +2426,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Provision_Complete); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2343,7 +2438,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Provision_Response); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -2360,15 +2455,15 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
(*Agent_Token)(nil),
|
||||
(*Agent_InstanceId)(nil),
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[16].OneofWrappers = []interface{}{
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[17].OneofWrappers = []interface{}{
|
||||
(*Parse_Response_Log)(nil),
|
||||
(*Parse_Response_Complete)(nil),
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[20].OneofWrappers = []interface{}{
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[21].OneofWrappers = []interface{}{
|
||||
(*Provision_Request_Start)(nil),
|
||||
(*Provision_Request_Cancel)(nil),
|
||||
}
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[22].OneofWrappers = []interface{}{
|
||||
file_provisionersdk_proto_provisioner_proto_msgTypes[23].OneofWrappers = []interface{}{
|
||||
(*Provision_Response_Log)(nil),
|
||||
(*Provision_Response_Complete)(nil),
|
||||
}
|
||||
|
@ -2378,7 +2473,7 @@ func file_provisionersdk_proto_provisioner_proto_init() {
|
|||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_provisionersdk_proto_provisioner_proto_rawDesc,
|
||||
NumEnums: 5,
|
||||
NumMessages: 23,
|
||||
NumMessages: 24,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
|
|
@ -94,6 +94,14 @@ message App {
|
|||
string url = 3;
|
||||
string icon = 4;
|
||||
bool relative_path = 5;
|
||||
Healthcheck healthcheck = 6;
|
||||
}
|
||||
|
||||
// Healthcheck represents configuration for checking for app readiness.
|
||||
message Healthcheck {
|
||||
string url = 1;
|
||||
int32 interval = 2;
|
||||
int32 threshold = 3;
|
||||
}
|
||||
|
||||
// Resource represents created infrastructure.
|
||||
|
|
|
@ -12,12 +12,6 @@ export interface APIKey {
|
|||
readonly lifetime_seconds: number
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go
|
||||
export interface AWSInstanceIdentityToken {
|
||||
readonly signature: string
|
||||
readonly document: string
|
||||
}
|
||||
|
||||
// From codersdk/licenses.go
|
||||
export interface AddLicenseRequest {
|
||||
readonly license: string
|
||||
|
@ -277,9 +271,11 @@ export interface GitSSHKey {
|
|||
readonly public_key: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go
|
||||
export interface GoogleInstanceIdentityToken {
|
||||
readonly json_web_token: string
|
||||
// From codersdk/workspaceapps.go
|
||||
export interface Healthcheck {
|
||||
readonly url: string
|
||||
readonly interval: number
|
||||
readonly threshold: number
|
||||
}
|
||||
|
||||
// From codersdk/licenses.go
|
||||
|
@ -358,11 +354,6 @@ export interface ParameterSchema {
|
|||
readonly validation_contains?: string[]
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go
|
||||
export interface PostWorkspaceAgentVersionRequest {
|
||||
readonly version: string
|
||||
}
|
||||
|
||||
// From codersdk/provisionerdaemons.go
|
||||
export interface ProvisionerDaemon {
|
||||
readonly id: string
|
||||
|
@ -581,18 +572,6 @@ export interface WorkspaceAgent {
|
|||
readonly latency?: Record<string, DERPRegion>
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go
|
||||
export interface WorkspaceAgentAuthenticateResponse {
|
||||
readonly session_token: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceagents.go
|
||||
export interface WorkspaceAgentConnectionInfo {
|
||||
// Named type "tailscale.com/tailcfg.DERPMap" unknown, using "any"
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
readonly derp_map?: any
|
||||
}
|
||||
|
||||
// From codersdk/workspaceresources.go
|
||||
export interface WorkspaceAgentInstanceMetadata {
|
||||
readonly jail_orchestrator: string
|
||||
|
@ -621,6 +600,8 @@ export interface WorkspaceApp {
|
|||
readonly name: string
|
||||
readonly command?: string
|
||||
readonly icon?: string
|
||||
readonly healthcheck: Healthcheck
|
||||
readonly health: WorkspaceAppHealth
|
||||
}
|
||||
|
||||
// From codersdk/workspacebuilds.go
|
||||
|
@ -743,5 +724,8 @@ export type UserStatus = "active" | "suspended"
|
|||
// From codersdk/workspaceresources.go
|
||||
export type WorkspaceAgentStatus = "connected" | "connecting" | "disconnected"
|
||||
|
||||
// From codersdk/workspaceapps.go
|
||||
export type WorkspaceAppHealth = "disabled" | "healthy" | "initializing" | "unhealthy"
|
||||
|
||||
// From codersdk/workspacebuilds.go
|
||||
export type WorkspaceTransition = "delete" | "start" | "stop"
|
||||
|
|
|
@ -15,6 +15,7 @@ WithIcon.args = {
|
|||
workspaceName: MockWorkspace.name,
|
||||
appName: "code-server",
|
||||
appIcon: "/icon/code.svg",
|
||||
health: "healthy",
|
||||
}
|
||||
|
||||
export const WithoutIcon = Template.bind({})
|
||||
|
@ -22,4 +23,29 @@ WithoutIcon.args = {
|
|||
userName: "developer",
|
||||
workspaceName: MockWorkspace.name,
|
||||
appName: "code-server",
|
||||
health: "healthy",
|
||||
}
|
||||
|
||||
export const HealthDisabled = Template.bind({})
|
||||
HealthDisabled.args = {
|
||||
userName: "developer",
|
||||
workspaceName: MockWorkspace.name,
|
||||
appName: "code-server",
|
||||
health: "disabled",
|
||||
}
|
||||
|
||||
export const HealthInitializing = Template.bind({})
|
||||
HealthInitializing.args = {
|
||||
userName: "developer",
|
||||
workspaceName: MockWorkspace.name,
|
||||
appName: "code-server",
|
||||
health: "initializing",
|
||||
}
|
||||
|
||||
export const HealthUnhealthy = Template.bind({})
|
||||
HealthUnhealthy.args = {
|
||||
userName: "developer",
|
||||
workspaceName: MockWorkspace.name,
|
||||
appName: "code-server",
|
||||
health: "unhealthy",
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import Button from "@material-ui/core/Button"
|
||||
import CircularProgress from "@material-ui/core/CircularProgress"
|
||||
import Link from "@material-ui/core/Link"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import ComputerIcon from "@material-ui/icons/Computer"
|
||||
import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline"
|
||||
import { FC, PropsWithChildren } from "react"
|
||||
import * as TypesGen from "../../api/typesGenerated"
|
||||
import { generateRandomString } from "../../util/random"
|
||||
|
@ -17,6 +19,7 @@ export interface AppLinkProps {
|
|||
appName: TypesGen.WorkspaceApp["name"]
|
||||
appIcon?: TypesGen.WorkspaceApp["icon"]
|
||||
appCommand?: TypesGen.WorkspaceApp["command"]
|
||||
health: TypesGen.WorkspaceApp["health"]
|
||||
}
|
||||
|
||||
export const AppLink: FC<PropsWithChildren<AppLinkProps>> = ({
|
||||
|
@ -26,6 +29,7 @@ export const AppLink: FC<PropsWithChildren<AppLinkProps>> = ({
|
|||
appName,
|
||||
appIcon,
|
||||
appCommand,
|
||||
health,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
|
||||
|
@ -38,37 +42,57 @@ export const AppLink: FC<PropsWithChildren<AppLinkProps>> = ({
|
|||
)}`
|
||||
}
|
||||
|
||||
let canClick = true
|
||||
let icon = appIcon ? <img alt={`${appName} Icon`} src={appIcon} /> : <ComputerIcon />
|
||||
if (health === "initializing") {
|
||||
canClick = false
|
||||
icon = <CircularProgress size={16} />
|
||||
}
|
||||
if (health === "unhealthy") {
|
||||
canClick = false
|
||||
icon = <ErrorOutlineIcon className={styles.unhealthyIcon} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
target="_blank"
|
||||
className={styles.link}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
window.open(
|
||||
href,
|
||||
Language.appTitle(appName, generateRandomString(12)),
|
||||
"width=900,height=600",
|
||||
)
|
||||
}}
|
||||
className={canClick ? styles.link : styles.disabledLink}
|
||||
onClick={
|
||||
canClick
|
||||
? (event) => {
|
||||
event.preventDefault()
|
||||
window.open(
|
||||
href,
|
||||
Language.appTitle(appName, generateRandomString(12)),
|
||||
"width=900,height=600",
|
||||
)
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
startIcon={appIcon ? <img alt={`${appName} Icon`} src={appIcon} /> : <ComputerIcon />}
|
||||
className={styles.button}
|
||||
>
|
||||
<Button size="small" startIcon={icon} className={styles.button} disabled={!canClick}>
|
||||
{appName}
|
||||
</Button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
link: {
|
||||
textDecoration: "none !important",
|
||||
},
|
||||
|
||||
disabledLink: {
|
||||
pointerEvents: "none",
|
||||
textDecoration: "none !important",
|
||||
},
|
||||
|
||||
button: {
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
|
||||
unhealthyIcon: {
|
||||
color: theme.palette.warning.light,
|
||||
},
|
||||
}))
|
||||
|
|
|
@ -171,6 +171,7 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
|
|||
userName={workspace.owner_name}
|
||||
workspaceName={workspace.name}
|
||||
agentName={agent.name}
|
||||
health={app.health}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
|
|
@ -324,6 +324,12 @@ export const MockWorkspaceApp: TypesGen.WorkspaceApp = {
|
|||
id: "test-app",
|
||||
name: "test-app",
|
||||
icon: "",
|
||||
health: "disabled",
|
||||
healthcheck: {
|
||||
url: "",
|
||||
interval: 0,
|
||||
threshold: 0,
|
||||
},
|
||||
}
|
||||
|
||||
export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = {
|
||||
|
|
Loading…
Reference in New Issue