From 97052e0ccd32af56ee9da1b7269cf3132f0eb378 Mon Sep 17 00:00:00 2001 From: David Wahler Date: Thu, 18 Aug 2022 16:50:44 +0000 Subject: [PATCH 1/7] Revert "feature: disable provisionerd listen endpoint (#1612)" This reverts commit a03615a01fe2eabec334072a5b52a170e7ed3502. --- coderd/coderd.go | 3 ++ coderd/coderd_test.go | 3 ++ coderd/provisionerdaemons.go | 74 ++++++++++++++++++++++++++++++++++ codersdk/provisionerdaemons.go | 34 ++++++++++++++++ 4 files changed, 114 insertions(+) diff --git a/coderd/coderd.go b/coderd/coderd.go index cf29aa986fc22..dad570c3abf46 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -182,6 +182,9 @@ func New(options *Options) *API { apiKeyMiddleware, ) r.Get("/", api.provisionerDaemons) + r.Route("/me", func(r chi.Router) { + r.Get("/listen", api.provisionerDaemonsListen) + }) }) r.Route("/organizations", func(r chi.Router) { r.Use( diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index c042855687cd3..d01ae92cc6dcd 100644 --- a/coderd/coderd_test.go +++ b/coderd/coderd_test.go @@ -468,6 +468,9 @@ func TestAuthorizeAllEndpoints(t *testing.T) { "PUT:/api/v2/organizations/{organization}/members/{user}/roles": {NoAuthorize: true}, "POST:/api/v2/workspaces/{workspace}/builds": {StatusCode: http.StatusBadRequest, NoAuthorize: true}, "POST:/api/v2/organizations/{organization}/templateversions": {StatusCode: http.StatusBadRequest, NoAuthorize: true}, + + // TODO needs authorization + "GET:/api/v2/provisionerdaemons/me/listen": {NoAuthorize: true}, } for k, v := range assertRoute { diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 56af2eb68c0b1..db3388c5042f6 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -13,10 +13,12 @@ import ( "time" "github.com/google/uuid" + "github.com/hashicorp/yamux" "github.com/moby/moby/pkg/namesgenerator" "github.com/tabbed/pqtype" "golang.org/x/xerrors" protobuf "google.golang.org/protobuf/proto" + "nhooyr.io/websocket" "storj.io/drpc/drpcmux" "storj.io/drpc/drpcserver" @@ -62,6 +64,78 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) { httpapi.Write(rw, http.StatusOK, daemons) } +// Serves the provisioner daemon protobuf API over a WebSocket. +func (api *API) provisionerDaemonsListen(rw http.ResponseWriter, r *http.Request) { + api.websocketWaitMutex.Lock() + api.websocketWaitGroup.Add(1) + api.websocketWaitMutex.Unlock() + defer api.websocketWaitGroup.Done() + + conn, err := websocket.Accept(rw, r, &websocket.AcceptOptions{ + // Need to disable compression to avoid a data-race. + CompressionMode: websocket.CompressionDisabled, + }) + if err != nil { + httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + Message: "Internal error accepting websocket connection.", + Detail: err.Error(), + }) + return + } + // Align with the frame size of yamux. + conn.SetReadLimit(256 * 1024) + + daemon, err := api.Database.InsertProvisionerDaemon(r.Context(), database.InsertProvisionerDaemonParams{ + ID: uuid.New(), + CreatedAt: database.Now(), + Name: namesgenerator.GetRandomName(1), + Provisioners: []database.ProvisionerType{database.ProvisionerTypeEcho, database.ProvisionerTypeTerraform}, + }) + if err != nil { + _ = conn.Close(websocket.StatusInternalError, httpapi.WebsocketCloseSprintf("insert provisioner daemon: %s", err)) + return + } + + // Multiplexes the incoming connection using yamux. + // This allows multiple function calls to occur over + // the same connection. + config := yamux.DefaultConfig() + config.LogOutput = io.Discard + session, err := yamux.Server(websocket.NetConn(r.Context(), conn, websocket.MessageBinary), config) + if err != nil { + _ = conn.Close(websocket.StatusInternalError, httpapi.WebsocketCloseSprintf("multiplex server: %s", err)) + return + } + mux := drpcmux.New() + err = proto.DRPCRegisterProvisionerDaemon(mux, &provisionerdServer{ + AccessURL: api.AccessURL, + ID: daemon.ID, + Database: api.Database, + Pubsub: api.Pubsub, + Provisioners: daemon.Provisioners, + Logger: api.Logger.Named(fmt.Sprintf("provisionerd-%s", daemon.Name)), + }) + if err != nil { + _ = conn.Close(websocket.StatusInternalError, httpapi.WebsocketCloseSprintf("drpc register provisioner daemon: %s", err)) + return + } + server := drpcserver.NewWithOptions(mux, drpcserver.Options{ + Log: func(err error) { + if xerrors.Is(err, io.EOF) { + return + } + api.Logger.Debug(r.Context(), "drpc server error", slog.Error(err)) + }, + }) + err = server.Serve(r.Context(), session) + if err != nil && !xerrors.Is(err, io.EOF) { + api.Logger.Debug(r.Context(), "provisioner daemon disconnected", slog.Error(err)) + _ = conn.Close(websocket.StatusInternalError, httpapi.WebsocketCloseSprintf("serve: %s", err)) + return + } + _ = conn.Close(websocket.StatusGoingAway, "") +} + // ListenProvisionerDaemon is an in-memory connection to a provisionerd. Useful when starting coderd and provisionerd // in the same process. func (api *API) ListenProvisionerDaemon(ctx context.Context) (client proto.DRPCProvisionerDaemonClient, err error) { diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index e71fbec7fbee5..5f86aa2aa613f 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/json" "fmt" + "io" "net/http" "net/http/cookiejar" "net/url" @@ -12,8 +13,12 @@ import ( "time" "github.com/google/uuid" + "github.com/hashicorp/yamux" "golang.org/x/xerrors" "nhooyr.io/websocket" + + "github.com/coder/coder/provisionerd/proto" + "github.com/coder/coder/provisionersdk" ) type LogSource string @@ -82,6 +87,35 @@ type ProvisionerJobLog struct { Output string `json:"output"` } +// ListenProvisionerDaemon returns the gRPC service for a provisioner daemon implementation. +func (c *Client) ListenProvisionerDaemon(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) { + serverURL, err := c.URL.Parse("/api/v2/provisionerdaemons/me/listen") + if err != nil { + return nil, xerrors.Errorf("parse url: %w", err) + } + conn, res, err := websocket.Dial(ctx, serverURL.String(), &websocket.DialOptions{ + HTTPClient: c.HTTPClient, + // Need to disable compression to avoid a data-race. + CompressionMode: websocket.CompressionDisabled, + }) + if err != nil { + if res == nil { + return nil, err + } + return nil, readBodyAsError(res) + } + // Align with the frame size of yamux. + conn.SetReadLimit(256 * 1024) + + config := yamux.DefaultConfig() + config.LogOutput = io.Discard + session, err := yamux.Client(websocket.NetConn(ctx, conn, websocket.MessageBinary), config) + if err != nil { + return nil, xerrors.Errorf("multiplex client: %w", err) + } + return proto.NewDRPCProvisionerDaemonClient(provisionersdk.Conn(session)), nil +} + // provisionerJobLogsBefore provides log output that occurred before a time. // This is abstracted from a specific job type to provide consistency between // APIs. Logs is the only shared route between jobs. From 48fc97aa8a8a46b43ba690fe1b2db6fcbcef9225 Mon Sep 17 00:00:00 2001 From: David Wahler Date: Thu, 18 Aug 2022 19:55:59 +0000 Subject: [PATCH 2/7] Add support for standalone provisioner daemons with token authentication --- cli/provisionercreate.go | 46 ++++++++++++ cli/provisionerrun.go | 73 +++++++++++++++++++ cli/provisioners.go | 19 +++++ cli/root.go | 1 + cli/server.go | 6 +- coderd/coderd.go | 10 ++- coderd/database/databasefake/databasefake.go | 21 ++++++ coderd/database/dump.sql | 3 +- .../000036_provisioner_daemon_auth.down.sql | 1 + .../000036_provisioner_daemon_auth.up.sql | 1 + coderd/database/models.go | 1 + coderd/database/querier.go | 1 + coderd/database/queries.sql.go | 37 +++++++++- .../database/queries/provisionerdaemons.sql | 13 +++- coderd/httpmw/provisionerdaemon.go | 69 ++++++++++++++++++ coderd/provisionerdaemons.go | 68 ++++++++++++++--- coderd/rbac/builtin.go | 2 - codersdk/provisionerdaemons.go | 34 ++++++++- scripts/develop.sh | 2 +- site/src/api/typesGenerated.ts | 6 ++ 20 files changed, 384 insertions(+), 30 deletions(-) create mode 100644 cli/provisionercreate.go create mode 100644 cli/provisionerrun.go create mode 100644 cli/provisioners.go create mode 100644 coderd/database/migrations/000036_provisioner_daemon_auth.down.sql create mode 100644 coderd/database/migrations/000036_provisioner_daemon_auth.up.sql create mode 100644 coderd/httpmw/provisionerdaemon.go diff --git a/cli/provisionercreate.go b/cli/provisionercreate.go new file mode 100644 index 0000000000000..229500f47cb94 --- /dev/null +++ b/cli/provisionercreate.go @@ -0,0 +1,46 @@ +package cli + +import ( + "fmt" + + "github.com/coder/coder/codersdk" + "github.com/spf13/cobra" + "golang.org/x/xerrors" +) + +func provisionerCreate() *cobra.Command { + root := &cobra.Command{ + Use: "create [name]", + Short: "Create a provisioner daemon instance", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + client, err := createClient(cmd) + if err != nil { + return err + } + + provisionerName := args[0] + + provisionerDaemon, err := client.CreateProvisionerDaemon(cmd.Context(), codersdk.CreateProvisionerDaemonRequest{ + Name: provisionerName, + }) + if err != nil { + return err + } + + if provisionerDaemon.AuthToken == nil { + return xerrors.New("provisioner daemon was created without an auth token") + } + tokenArg := provisionerDaemon.AuthToken.String() + + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), `A new provisioner daemon has been registered. + + Start the provisioner daemon with the following command: + + coder provisioners run --token `+tokenArg) + + return nil + }, + } + return root +} diff --git a/cli/provisionerrun.go b/cli/provisionerrun.go new file mode 100644 index 0000000000000..d64122357e86e --- /dev/null +++ b/cli/provisionerrun.go @@ -0,0 +1,73 @@ +package cli + +import ( + "fmt" + "os" + "os/signal" + "path/filepath" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" + "github.com/coder/coder/cli/cliflag" + "github.com/coder/coder/cli/cliui" + "github.com/spf13/cobra" + "golang.org/x/xerrors" +) + +func provisionerRun() *cobra.Command { + var ( + cacheDir string + verbose bool + ) + root := &cobra.Command{ + Use: "run", + Short: "Run a standalone Coder provisioner", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + notifyCtx, notifyStop := signal.NotifyContext(ctx, interruptSignals...) + defer notifyStop() + + logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr())) + if verbose { + logger = logger.Leveled(slog.LevelDebug) + } + + client, err := createClient(cmd) + if err != nil { + return err + } + + errCh := make(chan error, 1) + provisionerDaemon, err := newProvisionerDaemon(ctx, client.ListenProvisionerDaemon, logger, cacheDir, errCh, false) + if err != nil { + return xerrors.Errorf("create provisioner daemon: %w", err) + } + + var exitErr error + select { + case <-notifyCtx.Done(): + exitErr = notifyCtx.Err() + _, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Bold.Render( + "Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit", + )) + case exitErr = <-errCh: + } + + err = provisionerDaemon.Close() + if err != nil { + cmd.PrintErrf("Close provisioner daemon: %s\n", err) + return err + } + + return exitErr + }, + } + defaultCacheDir := filepath.Join(os.TempDir(), "coder-cache") + if dir := os.Getenv("CACHE_DIRECTORY"); dir != "" { + // For compatibility with systemd. + defaultCacheDir = dir + } + cliflag.StringVarP(root.Flags(), &cacheDir, "cache-dir", "", "CODER_CACHE_DIRECTORY", defaultCacheDir, "Specifies a directory to cache binaries for provision operations. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd.") + cliflag.BoolVarP(root.Flags(), &verbose, "verbose", "v", "CODER_VERBOSE", false, "Enables verbose logging.") + return root +} diff --git a/cli/provisioners.go b/cli/provisioners.go new file mode 100644 index 0000000000000..d43e2f523c8e9 --- /dev/null +++ b/cli/provisioners.go @@ -0,0 +1,19 @@ +package cli + +import ( + "github.com/spf13/cobra" +) + +func provisioners() *cobra.Command { + cmd := &cobra.Command{ + Use: "provisioners", + Short: "Create, manage and run standalone provisioner daemons", + Aliases: []string{"provisioner"}, + } + cmd.AddCommand( + provisionerRun(), + provisionerCreate(), + ) + + return cmd +} diff --git a/cli/root.go b/cli/root.go index 74a37ac4b6d8b..365db2ff23061 100644 --- a/cli/root.go +++ b/cli/root.go @@ -120,6 +120,7 @@ func Root() *cobra.Command { logout(), parameters(), portForward(), + provisioners(), publickey(), resetPassword(), schedules(), diff --git a/cli/server.go b/cli/server.go index 126ea0bc8204b..a51232550c632 100644 --- a/cli/server.go +++ b/cli/server.go @@ -463,7 +463,7 @@ func server() *cobra.Command { } }() for i := 0; uint8(i) < provisionerDaemonCount; i++ { - daemon, err := newProvisionerDaemon(ctx, coderAPI, logger, cacheDir, errCh, false) + daemon, err := newProvisionerDaemon(ctx, coderAPI.ListenProvisionerDaemon, logger, cacheDir, errCh, false) if err != nil { return xerrors.Errorf("create provisioner daemon: %w", err) } @@ -805,7 +805,7 @@ func shutdownWithTimeout(s interface{ Shutdown(context.Context) error }, timeout } // nolint:revive -func newProvisionerDaemon(ctx context.Context, coderAPI *coderd.API, +func newProvisionerDaemon(ctx context.Context, dialer provisionerd.Dialer, logger slog.Logger, cacheDir string, errCh chan error, dev bool, ) (srv *provisionerd.Server, err error) { ctx, cancel := context.WithCancel(ctx) @@ -873,7 +873,7 @@ func newProvisionerDaemon(ctx context.Context, coderAPI *coderd.API, }() provisioners[string(database.ProvisionerTypeEcho)] = proto.NewDRPCProvisionerClient(provisionersdk.Conn(echoClient)) } - return provisionerd.New(coderAPI.ListenProvisionerDaemon, &provisionerd.Options{ + return provisionerd.New(dialer, &provisionerd.Options{ Logger: logger, PollInterval: 500 * time.Millisecond, UpdateInterval: 500 * time.Millisecond, diff --git a/coderd/coderd.go b/coderd/coderd.go index dad570c3abf46..abc79da61930c 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -178,11 +178,13 @@ func New(options *Options) *API { r.Post("/", api.postFile) }) r.Route("/provisionerdaemons", func(r chi.Router) { - r.Use( - apiKeyMiddleware, - ) - r.Get("/", api.provisionerDaemons) + r.Group(func(r chi.Router) { + r.Use(apiKeyMiddleware) + r.Get("/", api.provisionerDaemons) + r.Post("/", api.postProvisionerDaemon) + }) r.Route("/me", func(r chi.Router) { + r.Use(httpmw.ExtractProvisionerDaemon(options.Database)) r.Get("/listen", api.provisionerDaemonsListen) }) }) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index bf88994256452..49f9804f9bb4f 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -1306,6 +1306,26 @@ func (q *fakeQuerier) GetProvisionerDaemonByID(_ context.Context, id uuid.UUID) return database.ProvisionerDaemon{}, sql.ErrNoRows } +func (q *fakeQuerier) GetProvisionerDaemonByAuthToken(_ context.Context, token uuid.NullUUID) (database.ProvisionerDaemon, error) { + if !token.Valid { + return database.ProvisionerDaemon{}, sql.ErrNoRows + } + + q.mutex.RLock() + defer q.mutex.RUnlock() + + for _, provisionerDaemon := range q.provisionerDaemons { + if !provisionerDaemon.AuthToken.Valid { + continue + } + if provisionerDaemon.AuthToken.UUID.String() != token.UUID.String() { + continue + } + return provisionerDaemon, nil + } + return database.ProvisionerDaemon{}, sql.ErrNoRows +} + func (q *fakeQuerier) GetProvisionerJobByID(_ context.Context, id uuid.UUID) (database.ProvisionerJob, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -1663,6 +1683,7 @@ func (q *fakeQuerier) InsertProvisionerDaemon(_ context.Context, arg database.In CreatedAt: arg.CreatedAt, Name: arg.Name, Provisioners: arg.Provisioners, + AuthToken: arg.AuthToken, } q.provisionerDaemons = append(q.provisionerDaemons, daemon) return daemon, nil diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 1db9442d93543..142dfd0bcf53b 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -200,7 +200,8 @@ CREATE TABLE provisioner_daemons ( created_at timestamp with time zone NOT NULL, updated_at timestamp with time zone, name character varying(64) NOT NULL, - provisioners provisioner_type[] NOT NULL + provisioners provisioner_type[] NOT NULL, + auth_token uuid ); CREATE TABLE provisioner_job_logs ( diff --git a/coderd/database/migrations/000036_provisioner_daemon_auth.down.sql b/coderd/database/migrations/000036_provisioner_daemon_auth.down.sql new file mode 100644 index 0000000000000..fd5c4ad9ea6ee --- /dev/null +++ b/coderd/database/migrations/000036_provisioner_daemon_auth.down.sql @@ -0,0 +1 @@ +ALTER TABLE provisioner_daemons DROP COLUMN IF EXISTS auth_token; diff --git a/coderd/database/migrations/000036_provisioner_daemon_auth.up.sql b/coderd/database/migrations/000036_provisioner_daemon_auth.up.sql new file mode 100644 index 0000000000000..69c00e5375bfe --- /dev/null +++ b/coderd/database/migrations/000036_provisioner_daemon_auth.up.sql @@ -0,0 +1 @@ +ALTER TABLE provisioner_daemons ADD COLUMN IF NOT EXISTS auth_token uuid; diff --git a/coderd/database/models.go b/coderd/database/models.go index 25ed8eedf3190..1a0b5d66b7124 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -416,6 +416,7 @@ type ProvisionerDaemon struct { UpdatedAt sql.NullTime `db:"updated_at" json:"updated_at"` Name string `db:"name" json:"name"` Provisioners []ProvisionerType `db:"provisioners" json:"provisioners"` + AuthToken uuid.NullUUID `db:"auth_token" json:"auth_token"` } type ProvisionerJob struct { diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 2cc1a2b084ad5..ce0533a66f47c 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -46,6 +46,7 @@ type querier interface { GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUID) ([]ParameterSchema, error) GetParameterSchemasCreatedAfter(ctx context.Context, createdAt time.Time) ([]ParameterSchema, error) GetParameterValueByScopeAndName(ctx context.Context, arg GetParameterValueByScopeAndNameParams) (ParameterValue, error) + GetProvisionerDaemonByAuthToken(ctx context.Context, authToken uuid.NullUUID) (ProvisionerDaemon, error) GetProvisionerDaemonByID(ctx context.Context, id uuid.UUID) (ProvisionerDaemon, error) GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, error) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (ProvisionerJob, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index b4fc10691f4fe..e1dab71d8da0e 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1218,9 +1218,32 @@ func (q *sqlQuerier) ParameterValues(ctx context.Context, arg ParameterValuesPar return items, nil } +const getProvisionerDaemonByAuthToken = `-- name: GetProvisionerDaemonByAuthToken :one +SELECT + id, created_at, updated_at, name, provisioners, auth_token +FROM + provisioner_daemons +WHERE + auth_token = $1 +` + +func (q *sqlQuerier) GetProvisionerDaemonByAuthToken(ctx context.Context, authToken uuid.NullUUID) (ProvisionerDaemon, error) { + row := q.db.QueryRowContext(ctx, getProvisionerDaemonByAuthToken, authToken) + var i ProvisionerDaemon + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.UpdatedAt, + &i.Name, + pq.Array(&i.Provisioners), + &i.AuthToken, + ) + return i, err +} + const getProvisionerDaemonByID = `-- name: GetProvisionerDaemonByID :one SELECT - id, created_at, updated_at, name, provisioners + id, created_at, updated_at, name, provisioners, auth_token FROM provisioner_daemons WHERE @@ -1236,13 +1259,14 @@ func (q *sqlQuerier) GetProvisionerDaemonByID(ctx context.Context, id uuid.UUID) &i.UpdatedAt, &i.Name, pq.Array(&i.Provisioners), + &i.AuthToken, ) return i, err } const getProvisionerDaemons = `-- name: GetProvisionerDaemons :many SELECT - id, created_at, updated_at, name, provisioners + id, created_at, updated_at, name, provisioners, auth_token FROM provisioner_daemons ` @@ -1262,6 +1286,7 @@ func (q *sqlQuerier) GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDa &i.UpdatedAt, &i.Name, pq.Array(&i.Provisioners), + &i.AuthToken, ); err != nil { return nil, err } @@ -1282,10 +1307,11 @@ INSERT INTO id, created_at, "name", - provisioners + provisioners, + auth_token ) VALUES - ($1, $2, $3, $4) RETURNING id, created_at, updated_at, name, provisioners + ($1, $2, $3, $4, $5) RETURNING id, created_at, updated_at, name, provisioners, auth_token ` type InsertProvisionerDaemonParams struct { @@ -1293,6 +1319,7 @@ type InsertProvisionerDaemonParams struct { CreatedAt time.Time `db:"created_at" json:"created_at"` Name string `db:"name" json:"name"` Provisioners []ProvisionerType `db:"provisioners" json:"provisioners"` + AuthToken uuid.NullUUID `db:"auth_token" json:"auth_token"` } func (q *sqlQuerier) InsertProvisionerDaemon(ctx context.Context, arg InsertProvisionerDaemonParams) (ProvisionerDaemon, error) { @@ -1301,6 +1328,7 @@ func (q *sqlQuerier) InsertProvisionerDaemon(ctx context.Context, arg InsertProv arg.CreatedAt, arg.Name, pq.Array(arg.Provisioners), + arg.AuthToken, ) var i ProvisionerDaemon err := row.Scan( @@ -1309,6 +1337,7 @@ func (q *sqlQuerier) InsertProvisionerDaemon(ctx context.Context, arg InsertProv &i.UpdatedAt, &i.Name, pq.Array(&i.Provisioners), + &i.AuthToken, ) return i, err } diff --git a/coderd/database/queries/provisionerdaemons.sql b/coderd/database/queries/provisionerdaemons.sql index 30ff6d9d43eda..e5f2e1b173a86 100644 --- a/coderd/database/queries/provisionerdaemons.sql +++ b/coderd/database/queries/provisionerdaemons.sql @@ -18,10 +18,11 @@ INSERT INTO id, created_at, "name", - provisioners + provisioners, + auth_token ) VALUES - ($1, $2, $3, $4) RETURNING *; + ($1, $2, $3, $4, $5) RETURNING *; -- name: UpdateProvisionerDaemonByID :exec UPDATE @@ -31,3 +32,11 @@ SET provisioners = $3 WHERE id = $1; + +-- name: GetProvisionerDaemonByAuthToken :one +SELECT + * +FROM + provisioner_daemons +WHERE + auth_token = $1; diff --git a/coderd/httpmw/provisionerdaemon.go b/coderd/httpmw/provisionerdaemon.go new file mode 100644 index 0000000000000..9580b7e15d221 --- /dev/null +++ b/coderd/httpmw/provisionerdaemon.go @@ -0,0 +1,69 @@ +package httpmw + +import ( + "context" + "database/sql" + "errors" + "fmt" + "net/http" + + "github.com/google/uuid" + + "github.com/coder/coder/coderd/database" + "github.com/coder/coder/coderd/httpapi" + "github.com/coder/coder/codersdk" +) + +type provisionerDaemonContextKey struct{} + +// ProvisionerDaemon returns the daemon from the ExtractProvisionerDaemon handler. +func ProvisionerDaemon(r *http.Request) database.ProvisionerDaemon { + user, ok := r.Context().Value(provisionerDaemonContextKey{}).(database.ProvisionerDaemon) + if !ok { + panic("developer error: provisioner daemon middleware not provided") + } + return user +} + +// ExtractWorkspaceAgent requires authentication using a valid provisioner token. +func ExtractProvisionerDaemon(db database.Store) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie(codersdk.SessionTokenKey) + if err != nil { + httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + Message: fmt.Sprintf("Cookie %q must be provided.", codersdk.SessionTokenKey), + }) + return + } + token, err := uuid.Parse(cookie.Value) + if err != nil { + httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + Message: "Provisioner token is invalid.", + }) + return + } + provisioner, err := db.GetProvisionerDaemonByAuthToken(r.Context(), uuid.NullUUID{ + Valid: true, + UUID: token, + }) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + Message: "Provisioner token is invalid.", + }) + return + } + + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching provisioner daemon.", + Detail: err.Error(), + }) + return + } + + ctx := context.WithValue(r.Context(), provisionerDaemonContextKey{}, provisioner) + next.ServeHTTP(rw, r.WithContext(ctx)) + }) + } +} diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index db3388c5042f6..5d85250483702 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -27,6 +27,7 @@ import ( "github.com/coder/coder/coderd/database" "github.com/coder/coder/coderd/database/dbtypes" "github.com/coder/coder/coderd/httpapi" + "github.com/coder/coder/coderd/httpmw" "github.com/coder/coder/coderd/parameter" "github.com/coder/coder/coderd/rbac" "github.com/coder/coder/coderd/telemetry" @@ -61,11 +62,18 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, daemons) + var results []codersdk.ProvisionerDaemon + for _, daemon := range daemons { + results = append(results, convertProvisionerDaemon(daemon)) + } + httpapi.Write(rw, http.StatusOK, results) } // Serves the provisioner daemon protobuf API over a WebSocket. func (api *API) provisionerDaemonsListen(rw http.ResponseWriter, r *http.Request) { + daemon := httpmw.ProvisionerDaemon(r) + api.Logger.Warn(r.Context(), "daemon connected", slog.F("daemon", daemon.Name)) + api.websocketWaitMutex.Lock() api.websocketWaitGroup.Add(1) api.websocketWaitMutex.Unlock() @@ -85,17 +93,6 @@ func (api *API) provisionerDaemonsListen(rw http.ResponseWriter, r *http.Request // Align with the frame size of yamux. conn.SetReadLimit(256 * 1024) - daemon, err := api.Database.InsertProvisionerDaemon(r.Context(), database.InsertProvisionerDaemonParams{ - ID: uuid.New(), - CreatedAt: database.Now(), - Name: namesgenerator.GetRandomName(1), - Provisioners: []database.ProvisionerType{database.ProvisionerTypeEcho, database.ProvisionerTypeTerraform}, - }) - if err != nil { - _ = conn.Close(websocket.StatusInternalError, httpapi.WebsocketCloseSprintf("insert provisioner daemon: %s", err)) - return - } - // Multiplexes the incoming connection using yamux. // This allows multiple function calls to occur over // the same connection. @@ -113,6 +110,7 @@ func (api *API) provisionerDaemonsListen(rw http.ResponseWriter, r *http.Request Database: api.Database, Pubsub: api.Pubsub, Provisioners: daemon.Provisioners, + Telemetry: api.Telemetry, Logger: api.Logger.Named(fmt.Sprintf("provisionerd-%s", daemon.Name)), }) if err != nil { @@ -136,6 +134,36 @@ func (api *API) provisionerDaemonsListen(rw http.ResponseWriter, r *http.Request _ = conn.Close(websocket.StatusGoingAway, "") } +func (api *API) postProvisionerDaemon(rw http.ResponseWriter, r *http.Request) { + // Create the user on the site. + if !api.Authorize(r, rbac.ActionCreate, rbac.ResourceProvisionerDaemon) { + httpapi.Forbidden(rw) + return + } + + var req codersdk.CreateProvisionerDaemonRequest + if !httpapi.Read(rw, r, &req) { + return + } + + provisioner, err := api.Database.InsertProvisionerDaemon(r.Context(), database.InsertProvisionerDaemonParams{ + ID: uuid.New(), + CreatedAt: database.Now(), + Name: req.Name, + Provisioners: []database.ProvisionerType{database.ProvisionerTypeTerraform}, + AuthToken: uuid.NullUUID{Valid: true, UUID: uuid.New()}, + }) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Error inserting provisioner daemon.", + Detail: err.Error(), + }) + return + } + + httpapi.Write(rw, http.StatusCreated, convertProvisionerDaemon(provisioner)) +} + // ListenProvisionerDaemon is an in-memory connection to a provisionerd. Useful when starting coderd and provisionerd // in the same process. func (api *API) ListenProvisionerDaemon(ctx context.Context) (client proto.DRPCProvisionerDaemonClient, err error) { @@ -1034,3 +1062,19 @@ func convertWorkspaceTransition(transition database.WorkspaceTransition) (sdkpro return 0, xerrors.Errorf("unrecognized transition: %q", transition) } } + +func convertProvisionerDaemon(daemon database.ProvisionerDaemon) codersdk.ProvisionerDaemon { + result := codersdk.ProvisionerDaemon{ + ID: daemon.ID, + CreatedAt: daemon.CreatedAt, + UpdatedAt: daemon.UpdatedAt, + Name: daemon.Name, + } + for _, provisionerType := range daemon.Provisioners { + result.Provisioners = append(result.Provisioners, codersdk.ProvisionerType(provisionerType)) + } + if daemon.AuthToken.Valid { + result.AuthToken = &daemon.AuthToken.UUID + } + return result +} diff --git a/coderd/rbac/builtin.go b/coderd/rbac/builtin.go index bfa6ded0a5250..d941ba5db7254 100644 --- a/coderd/rbac/builtin.go +++ b/coderd/rbac/builtin.go @@ -78,8 +78,6 @@ var ( // All users can read all other users and know they exist. ResourceUser: {ActionRead}, ResourceRoleAssignment: {ActionRead}, - // All users can see the provisioner daemons. - ResourceProvisionerDaemon: {ActionRead}, }), User: permissions(map[Object][]Action{ ResourceWildcard: {WildcardSymbol}, diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index 5f86aa2aa613f..154835e9d60a8 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -44,6 +44,7 @@ type ProvisionerDaemon struct { UpdatedAt sql.NullTime `json:"updated_at"` Name string `json:"name"` Provisioners []ProvisionerType `json:"provisioners"` + AuthToken *uuid.UUID `json:"auth_token,omitempty"` } // ProvisionerJobStatus represents the at-time state of a job. @@ -87,14 +88,29 @@ type ProvisionerJobLog struct { Output string `json:"output"` } +type CreateProvisionerDaemonRequest struct { + Name string `json:"name" validate:"required"` +} + // ListenProvisionerDaemon returns the gRPC service for a provisioner daemon implementation. func (c *Client) ListenProvisionerDaemon(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) { serverURL, err := c.URL.Parse("/api/v2/provisionerdaemons/me/listen") if err != nil { return nil, xerrors.Errorf("parse url: %w", err) } + jar, err := cookiejar.New(nil) + if err != nil { + return nil, xerrors.Errorf("create cookie jar: %w", err) + } + jar.SetCookies(serverURL, []*http.Cookie{{ + Name: SessionTokenKey, + Value: c.SessionToken, + }}) + httpClient := &http.Client{ + Jar: jar, + } conn, res, err := websocket.Dial(ctx, serverURL.String(), &websocket.DialOptions{ - HTTPClient: c.HTTPClient, + HTTPClient: httpClient, // Need to disable compression to avoid a data-race. CompressionMode: websocket.CompressionDisabled, }) @@ -116,6 +132,22 @@ func (c *Client) ListenProvisionerDaemon(ctx context.Context) (proto.DRPCProvisi return proto.NewDRPCProvisionerDaemonClient(provisionersdk.Conn(session)), nil } +// CreateProvisionerDaemon creates a new standalone provisioner instance and generates an auth token. +func (c *Client) CreateProvisionerDaemon(ctx context.Context, req CreateProvisionerDaemonRequest) (ProvisionerDaemon, error) { + res, err := c.Request(ctx, http.MethodPost, "/api/v2/provisionerdaemons/", req) + if err != nil { + return ProvisionerDaemon{}, xerrors.Errorf("execute request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusCreated { + return ProvisionerDaemon{}, readBodyAsError(res) + } + + var provisionerDaemon ProvisionerDaemon + return provisionerDaemon, json.NewDecoder(res.Body).Decode(&provisionerDaemon) +} + // provisionerJobLogsBefore provides log output that occurred before a time. // This is abstracted from a specific job type to provide consistency between // APIs. Logs is the only shared route between jobs. diff --git a/scripts/develop.sh b/scripts/develop.sh index df0cafd2bae65..37f30084bde23 100755 --- a/scripts/develop.sh +++ b/scripts/develop.sh @@ -73,7 +73,7 @@ CODER_DEV_SHIM="${PROJECT_ROOT}/scripts/coder-dev.sh" # rather than leaving things in an inconsistent state. trap 'kill -TERM -$$' ERR cdroot - "${CODER_DEV_SHIM}" server --address 127.0.0.1:3000 --in-memory --tunnel || kill -INT -$$ & + "${CODER_DEV_SHIM}" server --address 127.0.0.1:3000 --in-memory --tunnel "$@" || kill -INT -$$ & echo '== Waiting for Coder to become ready' timeout 60s bash -c 'until curl -s --fail http://localhost:3000 > /dev/null 2>&1; do sleep 0.5; done' diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 377f2d9157969..272f571ed403f 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -83,6 +83,11 @@ export interface CreateParameterRequest { readonly destination_scheme: ParameterDestinationScheme } +// From codersdk/provisionerdaemons.go +export interface CreateProvisionerDaemonRequest { + readonly name: string +} + // From codersdk/organizations.go export interface CreateTemplateRequest { readonly name: string @@ -242,6 +247,7 @@ export interface ProvisionerDaemon { readonly updated_at?: string readonly name: string readonly provisioners: ProvisionerType[] + readonly auth_token?: string } // From codersdk/provisionerdaemons.go From 6067403786c00b4881c491d6dbc34a5dbac0587c Mon Sep 17 00:00:00 2001 From: David Wahler Date: Thu, 18 Aug 2022 20:07:14 +0000 Subject: [PATCH 3/7] fix indentation --- cli/provisionercreate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/provisionercreate.go b/cli/provisionercreate.go index 229500f47cb94..73347f786824c 100644 --- a/cli/provisionercreate.go +++ b/cli/provisionercreate.go @@ -35,9 +35,9 @@ func provisionerCreate() *cobra.Command { _, _ = fmt.Fprintln(cmd.ErrOrStderr(), `A new provisioner daemon has been registered. - Start the provisioner daemon with the following command: +Start the provisioner daemon with the following command: - coder provisioners run --token `+tokenArg) +coder provisioners run --token `+tokenArg) return nil }, From a56220a60c86b2ce961f16e710a5f350861089aa Mon Sep 17 00:00:00 2001 From: David Wahler Date: Fri, 26 Aug 2022 02:18:19 +0000 Subject: [PATCH 4/7] Add tests for standalone provisionerd --- cli/provisionercreate.go | 2 +- cli/provisionercreate_test.go | 63 +++++++++++++++++++++++++++++++++++ cli/provisionerrun_test.go | 47 ++++++++++++++++++++++++++ coderd/coderdtest/authtest.go | 11 ++++-- 4 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 cli/provisionercreate_test.go create mode 100644 cli/provisionerrun_test.go diff --git a/cli/provisionercreate.go b/cli/provisionercreate.go index 352d4c4037225..ee4d3b0763a32 100644 --- a/cli/provisionercreate.go +++ b/cli/provisionercreate.go @@ -33,7 +33,7 @@ func provisionerCreate() *cobra.Command { } tokenArg := provisionerDaemon.AuthToken.String() - _, _ = fmt.Fprintln(cmd.ErrOrStderr(), `A new provisioner daemon has been registered. + _, _ = fmt.Fprintln(cmd.OutOrStderr(), `A new provisioner daemon has been registered. Start the provisioner daemon with the following command: diff --git a/cli/provisionercreate_test.go b/cli/provisionercreate_test.go new file mode 100644 index 0000000000000..6be5f7ae5017e --- /dev/null +++ b/cli/provisionercreate_test.go @@ -0,0 +1,63 @@ +package cli_test + +import ( + "bufio" + "bytes" + "context" + "strings" + "testing" + + "github.com/coder/coder/cli/clitest" + "github.com/coder/coder/coderd/coderdtest" + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +func TestProvisionerCreate(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + _ = coderdtest.CreateFirstUser(t, client) + cmd, root := clitest.New(t, "provisioners", "create", "foobar") + clitest.SetupConfig(t, client, root) + buf := new(bytes.Buffer) + cmd.SetOut(buf) + err := cmd.Execute() + require.NoError(t, err) + + var token *uuid.UUID + const tokenPrefix = "coder provisioners run --token " + s := bufio.NewScanner(buf) + for s.Scan() { + line := s.Text() + if strings.HasPrefix(line, tokenPrefix) { + tokenString := strings.TrimPrefix(line, tokenPrefix) + parsedToken, err := uuid.Parse(tokenString) + require.NoError(t, err, "provisioner token has invalid format") + token = &parsedToken + } + } + require.NotNil(t, token, "provisioner token not generated in output") + + provisioners, err := client.ProvisionerDaemons(context.Background()) + require.NoError(t, err) + tokensByName := make(map[string]*uuid.UUID) + for _, p := range provisioners { + tokensByName[p.Name] = p.AuthToken + } + require.Equal(t, token, tokensByName["foobar"]) + }) + + t.Run("Unprivileged", func(t *testing.T) { + t.Parallel() + adminClient := coderdtest.New(t, nil) + admin := coderdtest.CreateFirstUser(t, adminClient) + otherClient := coderdtest.CreateAnotherUser(t, adminClient, admin.OrganizationID) + cmd, root := clitest.New(t, "provisioners", "create", "foobar") + clitest.SetupConfig(t, otherClient, root) + err := cmd.Execute() + require.Error(t, err, "unprivileged user was allowed to create provisioner") + }) +} diff --git a/cli/provisionerrun_test.go b/cli/provisionerrun_test.go new file mode 100644 index 0000000000000..1c8a619c144a4 --- /dev/null +++ b/cli/provisionerrun_test.go @@ -0,0 +1,47 @@ +package cli_test + +import ( + "context" + "testing" + + "github.com/coder/coder/cli/clitest" + "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/codersdk" + "github.com/stretchr/testify/require" +) + +func TestProvisionerRun(t *testing.T) { + t.Parallel() + t.Run("Provisioner", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + _ = coderdtest.CreateFirstUser(t, client) + provisionerResponse, err := client.CreateProvisionerDaemon(context.Background(), + codersdk.CreateProvisionerDaemonRequest{ + Name: "foobar", + }, + ) + require.NoError(t, err) + token := provisionerResponse.AuthToken + require.NotNil(t, token) + + doneCh := make(chan error) + defer func() { + err := <-doneCh + require.ErrorIs(t, err, context.Canceled, "provisioner command terminated with error") + }() + + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + cmd, root := clitest.New(t, "provisioners", "run", "--token", token.String()) + // command should only have access to provisioner auth token, not user credentials + err = root.URL().Write(client.URL.String()) + require.NoError(t, err) + + go func() { + defer close(doneCh) + doneCh <- cmd.ExecuteContext(ctx) + }() + }) +} diff --git a/coderd/coderdtest/authtest.go b/coderd/coderdtest/authtest.go index 02d168f3f72e8..6cdacc69f651d 100644 --- a/coderd/coderdtest/authtest.go +++ b/coderd/coderdtest/authtest.go @@ -178,9 +178,6 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { "POST:/api/v2/csp/reports": {NoAuthorize: true}, "GET:/api/v2/entitlements": {NoAuthorize: true}, - // TODO needs authorization test - "GET:/api/v2/provisionerdaemons/me/listen": {NoAuthorize: true}, - "GET:/%40{user}/{workspacename}/apps/{workspaceapp}/*": { AssertAction: rbac.ActionCreate, AssertObject: workspaceExecObj, @@ -209,6 +206,9 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { "GET:/api/v2/workspaceagents/{workspaceagent}/iceservers": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/{workspaceagent}/derp": {NoAuthorize: true}, + // Provisioner daemon endpoint does not use RBAC + "GET:/api/v2/provisionerdaemons/me/listen": {NoAuthorize: true}, + // These endpoints have more assertions. This is good, add more endpoints to assert if you can! "GET:/api/v2/organizations/{organization}": {AssertObject: rbac.ResourceOrganization.InOrg(a.Admin.OrganizationID)}, "GET:/api/v2/users/{user}/organizations": {StatusCode: http.StatusOK, AssertObject: rbac.ResourceOrganization}, @@ -366,6 +366,11 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { }, "GET:/api/v2/provisionerdaemons": { StatusCode: http.StatusOK, + AssertAction: rbac.ActionRead, + AssertObject: rbac.ResourceProvisionerDaemon, + }, + "POST:/api/v2/provisionerdaemons": { + AssertAction: rbac.ActionCreate, AssertObject: rbac.ResourceProvisionerDaemon, }, From 57926bb85d86c948b147244cc5e8cb2d30690186 Mon Sep 17 00:00:00 2001 From: David Wahler Date: Fri, 26 Aug 2022 02:46:31 +0000 Subject: [PATCH 5/7] Check for successful provisioner connection in test --- cli/provisionerrun_test.go | 10 +++++++++- provisionerd/provisionerd.go | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cli/provisionerrun_test.go b/cli/provisionerrun_test.go index 1c8a619c144a4..c389543c75e5f 100644 --- a/cli/provisionerrun_test.go +++ b/cli/provisionerrun_test.go @@ -7,6 +7,7 @@ import ( "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/codersdk" + "github.com/coder/coder/pty/ptytest" "github.com/stretchr/testify/require" ) @@ -34,7 +35,12 @@ func TestProvisionerRun(t *testing.T) { ctx, cancelFunc := context.WithCancel(context.Background()) defer cancelFunc() - cmd, root := clitest.New(t, "provisioners", "run", "--token", token.String()) + cmd, root := clitest.New(t, "provisioners", "run", + "--token", token.String(), + "--verbose", // to test debug-level logs + ) + pty := ptytest.New(t) + cmd.SetErr(pty.Output()) // command should only have access to provisioner auth token, not user credentials err = root.URL().Write(client.URL.String()) require.NoError(t, err) @@ -43,5 +49,7 @@ func TestProvisionerRun(t *testing.T) { defer close(doneCh) doneCh <- cmd.ExecuteContext(ctx) }() + + pty.ExpectMatch("\tprovisioner client connected") }) } diff --git a/provisionerd/provisionerd.go b/provisionerd/provisionerd.go index 429627e33de82..64663e93a6c34 100644 --- a/provisionerd/provisionerd.go +++ b/provisionerd/provisionerd.go @@ -108,7 +108,7 @@ func (p *Server) connect(ctx context.Context) { continue } p.clientValue.Store(client) - p.opts.Logger.Debug(context.Background(), "connected") + p.opts.Logger.Debug(context.Background(), "provisioner client connected") break } select { From 097ebc9dc3aeabc88f72eecd9d148fc61a5612d4 Mon Sep 17 00:00:00 2001 From: David Wahler Date: Fri, 26 Aug 2022 02:54:57 +0000 Subject: [PATCH 6/7] fix protoc version --- peerbroker/proto/peerbroker.pb.go | 2 +- provisionerd/proto/provisionerd.pb.go | 2 +- provisionersdk/proto/provisioner.pb.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/peerbroker/proto/peerbroker.pb.go b/peerbroker/proto/peerbroker.pb.go index eb54630466b66..498d250a14149 100644 --- a/peerbroker/proto/peerbroker.pb.go +++ b/peerbroker/proto/peerbroker.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.6.1 +// protoc v3.20.0 // source: peerbroker/proto/peerbroker.proto package proto diff --git a/provisionerd/proto/provisionerd.pb.go b/provisionerd/proto/provisionerd.pb.go index 337650b7fa5ce..85aeaf79b38f8 100644 --- a/provisionerd/proto/provisionerd.pb.go +++ b/provisionerd/proto/provisionerd.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.6.1 +// protoc v3.20.0 // source: provisionerd/proto/provisionerd.proto package proto diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index bce3f6e7927ad..fb2c9024a06c4 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 -// protoc v3.6.1 +// protoc v3.20.0 // source: provisionersdk/proto/provisioner.proto package proto From 73f1e5cc95fa4ccf3263ae65f232c4b33e199a40 Mon Sep 17 00:00:00 2001 From: David Wahler Date: Tue, 30 Aug 2022 00:40:09 +0000 Subject: [PATCH 7/7] have provisionerd register its supported provisioners on connection --- cli/provisionercreate.go | 7 +- cli/provisionercreate_test.go | 5 +- cli/provisionerrun.go | 23 +- cli/provisionerrun_test.go | 50 +++- coderd/provisionerdaemons.go | 29 +++ provisionerd/proto/provisionerd.pb.go | 259 +++++++++++++-------- provisionerd/proto/provisionerd.proto | 8 + provisionerd/proto/provisionerd_drpc.pb.go | 42 +++- provisionerd/provisionerd.go | 34 +++ provisionerd/provisionerd_test.go | 36 +++ 10 files changed, 383 insertions(+), 110 deletions(-) diff --git a/cli/provisionercreate.go b/cli/provisionercreate.go index ee4d3b0763a32..ce124137bb01e 100644 --- a/cli/provisionercreate.go +++ b/cli/provisionercreate.go @@ -3,13 +3,14 @@ package cli import ( "fmt" - "github.com/coder/coder/codersdk" "github.com/spf13/cobra" "golang.org/x/xerrors" + + "github.com/coder/coder/codersdk" ) func provisionerCreate() *cobra.Command { - root := &cobra.Command{ + cmd := &cobra.Command{ Use: "create [name]", Short: "Create a provisioner daemon instance", Args: cobra.ExactArgs(1), @@ -42,5 +43,5 @@ coder provisioners run --token `+tokenArg) return nil }, } - return root + return cmd } diff --git a/cli/provisionercreate_test.go b/cli/provisionercreate_test.go index 6be5f7ae5017e..dd65587430233 100644 --- a/cli/provisionercreate_test.go +++ b/cli/provisionercreate_test.go @@ -7,10 +7,11 @@ import ( "strings" "testing" - "github.com/coder/coder/cli/clitest" - "github.com/coder/coder/coderd/coderdtest" "github.com/google/uuid" "github.com/stretchr/testify/require" + + "github.com/coder/coder/cli/clitest" + "github.com/coder/coder/coderd/coderdtest" ) func TestProvisionerCreate(t *testing.T) { diff --git a/cli/provisionerrun.go b/cli/provisionerrun.go index 70e5b32784013..d6eca866ed41c 100644 --- a/cli/provisionerrun.go +++ b/cli/provisionerrun.go @@ -6,20 +6,22 @@ import ( "os/signal" "path/filepath" + "github.com/spf13/cobra" + "golang.org/x/xerrors" + "cdr.dev/slog" "cdr.dev/slog/sloggers/sloghuman" "github.com/coder/coder/cli/cliflag" "github.com/coder/coder/cli/cliui" - "github.com/spf13/cobra" - "golang.org/x/xerrors" ) func provisionerRun() *cobra.Command { var ( - cacheDir string - verbose bool + cacheDir string + verbose bool + useEchoProvisioner bool ) - root := &cobra.Command{ + cmd := &cobra.Command{ Use: "run", Short: "Run a standalone Coder provisioner", RunE: func(cmd *cobra.Command, args []string) error { @@ -38,7 +40,7 @@ func provisionerRun() *cobra.Command { } errCh := make(chan error, 1) - provisionerDaemon, err := newProvisionerDaemon(ctx, client.ListenProvisionerDaemon, logger, cacheDir, errCh, false) + provisionerDaemon, err := newProvisionerDaemon(ctx, client.ListenProvisionerDaemon, logger, cacheDir, errCh, useEchoProvisioner) if err != nil { return xerrors.Errorf("create provisioner daemon: %w", err) } @@ -67,7 +69,10 @@ func provisionerRun() *cobra.Command { // For compatibility with systemd. defaultCacheDir = dir } - cliflag.StringVarP(root.Flags(), &cacheDir, "cache-dir", "", "CODER_CACHE_DIRECTORY", defaultCacheDir, "Specifies a directory to cache binaries for provision operations. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd.") - cliflag.BoolVarP(root.Flags(), &verbose, "verbose", "v", "CODER_VERBOSE", false, "Enables verbose logging.") - return root + cliflag.StringVarP(cmd.Flags(), &cacheDir, "cache-dir", "", "CODER_CACHE_DIRECTORY", defaultCacheDir, "Specifies a directory to cache binaries for provision operations. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd.") + cliflag.BoolVarP(cmd.Flags(), &verbose, "verbose", "v", "CODER_VERBOSE", false, "Enables verbose logging.") + // flags for testing only + cmd.Flags().BoolVarP(&useEchoProvisioner, "test.use-echo-provisioner", "", false, "Enable the echo provisioner") + _ = cmd.Flags().MarkHidden("test.use-echo-provisioner") + return cmd } diff --git a/cli/provisionerrun_test.go b/cli/provisionerrun_test.go index c389543c75e5f..2214003dc405b 100644 --- a/cli/provisionerrun_test.go +++ b/cli/provisionerrun_test.go @@ -4,11 +4,14 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/coderd/database" "github.com/coder/coder/codersdk" + "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/pty/ptytest" - "github.com/stretchr/testify/require" ) func TestProvisionerRun(t *testing.T) { @@ -38,8 +41,10 @@ func TestProvisionerRun(t *testing.T) { cmd, root := clitest.New(t, "provisioners", "run", "--token", token.String(), "--verbose", // to test debug-level logs + "--test.use-echo-provisioner", ) pty := ptytest.New(t) + defer pty.Close() cmd.SetErr(pty.Output()) // command should only have access to provisioner auth token, not user credentials err = root.URL().Write(client.URL.String()) @@ -51,5 +56,48 @@ func TestProvisionerRun(t *testing.T) { }() pty.ExpectMatch("\tprovisioner client connected") + + source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: provisionCompleteWithAgent, + }) + args := []string{ + "templates", + "create", + "my-template", + "--directory", source, + "--test.provisioner", string(database.ProvisionerTypeEcho), + "--max-ttl", "24h", + "--min-autostart-interval", "2h", + } + createCmd, root := clitest.New(t, args...) + clitest.SetupConfig(t, client, root) + pty = ptytest.New(t) + defer pty.Close() + createCmd.SetIn(pty.Input()) + createCmd.SetOut(pty.Output()) + + execDone := make(chan error) + go func() { + execDone <- createCmd.Execute() + }() + + matches := []struct { + match string + write string + }{ + {match: "Create and upload", write: "yes"}, + {match: "compute.main"}, + {match: "smith (linux, i386)"}, + {match: "Confirm create?", write: "yes"}, + } + for _, m := range matches { + pty.ExpectMatch(m.match) + if len(m.write) > 0 { + pty.WriteLine(m.write) + } + } + + require.NoError(t, <-execDone) }) } diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 8802772d3d9f7..e4086b250cabd 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -848,6 +848,35 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr return &proto.Empty{}, nil } +func (server *provisionerdServer) Connect(ctx context.Context, req *proto.ConnectRequest) (*proto.Empty, error) { + if len(req.SupportedProvisioners) == 0 { + return nil, xerrors.New("supported provisioners not specified") + } + + var provisionerTypes []database.ProvisionerType + for _, name := range req.SupportedProvisioners { + provisionerType := database.ProvisionerType(name) + if provisionerType != database.ProvisionerTypeEcho && provisionerType != database.ProvisionerTypeTerraform { + return nil, xerrors.Errorf("unknown provisioner name: %v", name) + } + provisionerTypes = append(provisionerTypes, provisionerType) + } + + err := server.Database.UpdateProvisionerDaemonByID(ctx, database.UpdateProvisionerDaemonByIDParams{ + ID: server.ID, + UpdatedAt: sql.NullTime{Time: time.Now(), Valid: true}, + Provisioners: provisionerTypes, + }) + if err != nil { + server.Logger.Error(ctx, "error during provisioner daemon connection", slog.Error(err)) + return nil, xerrors.Errorf("connect: %w", err) + } + server.Provisioners = provisionerTypes + + server.Logger.Debug(ctx, "Connect done", slog.F("provisioner_id", server.ID), slog.F("provisioners", provisionerTypes)) + return &proto.Empty{}, nil +} + func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.UUID, transition database.WorkspaceTransition, protoResource *sdkproto.Resource, snapshot *telemetry.Snapshot) error { resource, err := db.InsertWorkspaceResource(ctx, database.InsertWorkspaceResourceParams{ ID: uuid.New(), diff --git a/provisionerd/proto/provisionerd.pb.go b/provisionerd/proto/provisionerd.pb.go index 85aeaf79b38f8..40aee643442ab 100644 --- a/provisionerd/proto/provisionerd.pb.go +++ b/provisionerd/proto/provisionerd.pb.go @@ -667,6 +667,53 @@ func (x *UpdateJobResponse) GetParameterValues() []*proto.ParameterValue { return nil } +type ConnectRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SupportedProvisioners []string `protobuf:"bytes,1,rep,name=supported_provisioners,json=supportedProvisioners,proto3" json:"supported_provisioners,omitempty"` +} + +func (x *ConnectRequest) Reset() { + *x = ConnectRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConnectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectRequest) ProtoMessage() {} + +func (x *ConnectRequest) ProtoReflect() protoreflect.Message { + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] + 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 ConnectRequest.ProtoReflect.Descriptor instead. +func (*ConnectRequest) Descriptor() ([]byte, []int) { + return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{7} +} + +func (x *ConnectRequest) GetSupportedProvisioners() []string { + if x != nil { + return x.SupportedProvisioners + } + return nil +} + type AcquiredJob_WorkspaceBuild struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -682,7 +729,7 @@ type AcquiredJob_WorkspaceBuild struct { func (x *AcquiredJob_WorkspaceBuild) Reset() { *x = AcquiredJob_WorkspaceBuild{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -695,7 +742,7 @@ func (x *AcquiredJob_WorkspaceBuild) String() string { func (*AcquiredJob_WorkspaceBuild) ProtoMessage() {} func (x *AcquiredJob_WorkspaceBuild) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -757,7 +804,7 @@ type AcquiredJob_TemplateImport struct { func (x *AcquiredJob_TemplateImport) Reset() { *x = AcquiredJob_TemplateImport{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -770,7 +817,7 @@ func (x *AcquiredJob_TemplateImport) String() string { func (*AcquiredJob_TemplateImport) ProtoMessage() {} func (x *AcquiredJob_TemplateImport) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -805,7 +852,7 @@ type AcquiredJob_TemplateDryRun struct { func (x *AcquiredJob_TemplateDryRun) Reset() { *x = AcquiredJob_TemplateDryRun{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -818,7 +865,7 @@ func (x *AcquiredJob_TemplateDryRun) String() string { func (*AcquiredJob_TemplateDryRun) ProtoMessage() {} func (x *AcquiredJob_TemplateDryRun) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -859,7 +906,7 @@ type FailedJob_WorkspaceBuild struct { func (x *FailedJob_WorkspaceBuild) Reset() { *x = FailedJob_WorkspaceBuild{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -872,7 +919,7 @@ func (x *FailedJob_WorkspaceBuild) String() string { func (*FailedJob_WorkspaceBuild) ProtoMessage() {} func (x *FailedJob_WorkspaceBuild) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -904,7 +951,7 @@ type FailedJob_TemplateImport struct { func (x *FailedJob_TemplateImport) Reset() { *x = FailedJob_TemplateImport{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -917,7 +964,7 @@ func (x *FailedJob_TemplateImport) String() string { func (*FailedJob_TemplateImport) ProtoMessage() {} func (x *FailedJob_TemplateImport) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -942,7 +989,7 @@ type FailedJob_TemplateDryRun struct { func (x *FailedJob_TemplateDryRun) Reset() { *x = FailedJob_TemplateDryRun{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[12] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -955,7 +1002,7 @@ func (x *FailedJob_TemplateDryRun) String() string { func (*FailedJob_TemplateDryRun) ProtoMessage() {} func (x *FailedJob_TemplateDryRun) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[12] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -983,7 +1030,7 @@ type CompletedJob_WorkspaceBuild struct { func (x *CompletedJob_WorkspaceBuild) Reset() { *x = CompletedJob_WorkspaceBuild{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[13] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -996,7 +1043,7 @@ func (x *CompletedJob_WorkspaceBuild) String() string { func (*CompletedJob_WorkspaceBuild) ProtoMessage() {} func (x *CompletedJob_WorkspaceBuild) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[13] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1038,7 +1085,7 @@ type CompletedJob_TemplateImport struct { func (x *CompletedJob_TemplateImport) Reset() { *x = CompletedJob_TemplateImport{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[14] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1051,7 +1098,7 @@ func (x *CompletedJob_TemplateImport) String() string { func (*CompletedJob_TemplateImport) ProtoMessage() {} func (x *CompletedJob_TemplateImport) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[14] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1092,7 +1139,7 @@ type CompletedJob_TemplateDryRun struct { func (x *CompletedJob_TemplateDryRun) Reset() { *x = CompletedJob_TemplateDryRun{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[15] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1105,7 +1152,7 @@ func (x *CompletedJob_TemplateDryRun) String() string { func (*CompletedJob_TemplateDryRun) ProtoMessage() {} func (x *CompletedJob_TemplateDryRun) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[15] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1289,31 +1336,40 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{ 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, 0x2a, 0x34, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x53, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, - 0x4f, 0x4e, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x45, 0x4d, 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x0f, 0x0a, - 0x0b, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x10, 0x01, 0x32, 0x98, - 0x02, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x61, - 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x0a, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, - 0x6f, 0x62, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, - 0x6f, 0x62, 0x12, 0x4c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, - 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x17, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, - 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x2b, 0x5a, 0x29, 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, 0x64, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x47, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x73, 0x75, + 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x73, 0x75, 0x70, 0x70, + 0x6f, 0x72, 0x74, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x73, 0x2a, 0x34, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, + 0x0a, 0x12, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x5f, 0x44, 0x41, + 0x45, 0x4d, 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, + 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x10, 0x01, 0x32, 0xd6, 0x02, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, + 0x0a, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x13, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x4c, 0x0a, 0x09, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, + 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, + 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, + 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, + 0x62, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, + 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x3c, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, 0x1c, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x42, 0x2b, 0x5a, 0x29, 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, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1329,7 +1385,7 @@ func file_provisionerd_proto_provisionerd_proto_rawDescGZIP() []byte { } var file_provisionerd_proto_provisionerd_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_provisionerd_proto_provisionerd_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_provisionerd_proto_provisionerd_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{ (LogSource)(0), // 0: provisionerd.LogSource (*Empty)(nil), // 1: provisionerd.Empty @@ -1339,55 +1395,58 @@ var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{ (*Log)(nil), // 5: provisionerd.Log (*UpdateJobRequest)(nil), // 6: provisionerd.UpdateJobRequest (*UpdateJobResponse)(nil), // 7: provisionerd.UpdateJobResponse - (*AcquiredJob_WorkspaceBuild)(nil), // 8: provisionerd.AcquiredJob.WorkspaceBuild - (*AcquiredJob_TemplateImport)(nil), // 9: provisionerd.AcquiredJob.TemplateImport - (*AcquiredJob_TemplateDryRun)(nil), // 10: provisionerd.AcquiredJob.TemplateDryRun - (*FailedJob_WorkspaceBuild)(nil), // 11: provisionerd.FailedJob.WorkspaceBuild - (*FailedJob_TemplateImport)(nil), // 12: provisionerd.FailedJob.TemplateImport - (*FailedJob_TemplateDryRun)(nil), // 13: provisionerd.FailedJob.TemplateDryRun - (*CompletedJob_WorkspaceBuild)(nil), // 14: provisionerd.CompletedJob.WorkspaceBuild - (*CompletedJob_TemplateImport)(nil), // 15: provisionerd.CompletedJob.TemplateImport - (*CompletedJob_TemplateDryRun)(nil), // 16: provisionerd.CompletedJob.TemplateDryRun - (proto.LogLevel)(0), // 17: provisioner.LogLevel - (*proto.ParameterSchema)(nil), // 18: provisioner.ParameterSchema - (*proto.ParameterValue)(nil), // 19: provisioner.ParameterValue - (*proto.Provision_Metadata)(nil), // 20: provisioner.Provision.Metadata - (*proto.Resource)(nil), // 21: provisioner.Resource + (*ConnectRequest)(nil), // 8: provisionerd.ConnectRequest + (*AcquiredJob_WorkspaceBuild)(nil), // 9: provisionerd.AcquiredJob.WorkspaceBuild + (*AcquiredJob_TemplateImport)(nil), // 10: provisionerd.AcquiredJob.TemplateImport + (*AcquiredJob_TemplateDryRun)(nil), // 11: provisionerd.AcquiredJob.TemplateDryRun + (*FailedJob_WorkspaceBuild)(nil), // 12: provisionerd.FailedJob.WorkspaceBuild + (*FailedJob_TemplateImport)(nil), // 13: provisionerd.FailedJob.TemplateImport + (*FailedJob_TemplateDryRun)(nil), // 14: provisionerd.FailedJob.TemplateDryRun + (*CompletedJob_WorkspaceBuild)(nil), // 15: provisionerd.CompletedJob.WorkspaceBuild + (*CompletedJob_TemplateImport)(nil), // 16: provisionerd.CompletedJob.TemplateImport + (*CompletedJob_TemplateDryRun)(nil), // 17: provisionerd.CompletedJob.TemplateDryRun + (proto.LogLevel)(0), // 18: provisioner.LogLevel + (*proto.ParameterSchema)(nil), // 19: provisioner.ParameterSchema + (*proto.ParameterValue)(nil), // 20: provisioner.ParameterValue + (*proto.Provision_Metadata)(nil), // 21: provisioner.Provision.Metadata + (*proto.Resource)(nil), // 22: provisioner.Resource } var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{ - 8, // 0: provisionerd.AcquiredJob.workspace_build:type_name -> provisionerd.AcquiredJob.WorkspaceBuild - 9, // 1: provisionerd.AcquiredJob.template_import:type_name -> provisionerd.AcquiredJob.TemplateImport - 10, // 2: provisionerd.AcquiredJob.template_dry_run:type_name -> provisionerd.AcquiredJob.TemplateDryRun - 11, // 3: provisionerd.FailedJob.workspace_build:type_name -> provisionerd.FailedJob.WorkspaceBuild - 12, // 4: provisionerd.FailedJob.template_import:type_name -> provisionerd.FailedJob.TemplateImport - 13, // 5: provisionerd.FailedJob.template_dry_run:type_name -> provisionerd.FailedJob.TemplateDryRun - 14, // 6: provisionerd.CompletedJob.workspace_build:type_name -> provisionerd.CompletedJob.WorkspaceBuild - 15, // 7: provisionerd.CompletedJob.template_import:type_name -> provisionerd.CompletedJob.TemplateImport - 16, // 8: provisionerd.CompletedJob.template_dry_run:type_name -> provisionerd.CompletedJob.TemplateDryRun + 9, // 0: provisionerd.AcquiredJob.workspace_build:type_name -> provisionerd.AcquiredJob.WorkspaceBuild + 10, // 1: provisionerd.AcquiredJob.template_import:type_name -> provisionerd.AcquiredJob.TemplateImport + 11, // 2: provisionerd.AcquiredJob.template_dry_run:type_name -> provisionerd.AcquiredJob.TemplateDryRun + 12, // 3: provisionerd.FailedJob.workspace_build:type_name -> provisionerd.FailedJob.WorkspaceBuild + 13, // 4: provisionerd.FailedJob.template_import:type_name -> provisionerd.FailedJob.TemplateImport + 14, // 5: provisionerd.FailedJob.template_dry_run:type_name -> provisionerd.FailedJob.TemplateDryRun + 15, // 6: provisionerd.CompletedJob.workspace_build:type_name -> provisionerd.CompletedJob.WorkspaceBuild + 16, // 7: provisionerd.CompletedJob.template_import:type_name -> provisionerd.CompletedJob.TemplateImport + 17, // 8: provisionerd.CompletedJob.template_dry_run:type_name -> provisionerd.CompletedJob.TemplateDryRun 0, // 9: provisionerd.Log.source:type_name -> provisionerd.LogSource - 17, // 10: provisionerd.Log.level:type_name -> provisioner.LogLevel + 18, // 10: provisionerd.Log.level:type_name -> provisioner.LogLevel 5, // 11: provisionerd.UpdateJobRequest.logs:type_name -> provisionerd.Log - 18, // 12: provisionerd.UpdateJobRequest.parameter_schemas:type_name -> provisioner.ParameterSchema - 19, // 13: provisionerd.UpdateJobResponse.parameter_values:type_name -> provisioner.ParameterValue - 19, // 14: provisionerd.AcquiredJob.WorkspaceBuild.parameter_values:type_name -> provisioner.ParameterValue - 20, // 15: provisionerd.AcquiredJob.WorkspaceBuild.metadata:type_name -> provisioner.Provision.Metadata - 20, // 16: provisionerd.AcquiredJob.TemplateImport.metadata:type_name -> provisioner.Provision.Metadata - 19, // 17: provisionerd.AcquiredJob.TemplateDryRun.parameter_values:type_name -> provisioner.ParameterValue - 20, // 18: provisionerd.AcquiredJob.TemplateDryRun.metadata:type_name -> provisioner.Provision.Metadata - 21, // 19: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource - 21, // 20: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource - 21, // 21: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource - 21, // 22: provisionerd.CompletedJob.TemplateDryRun.resources:type_name -> provisioner.Resource + 19, // 12: provisionerd.UpdateJobRequest.parameter_schemas:type_name -> provisioner.ParameterSchema + 20, // 13: provisionerd.UpdateJobResponse.parameter_values:type_name -> provisioner.ParameterValue + 20, // 14: provisionerd.AcquiredJob.WorkspaceBuild.parameter_values:type_name -> provisioner.ParameterValue + 21, // 15: provisionerd.AcquiredJob.WorkspaceBuild.metadata:type_name -> provisioner.Provision.Metadata + 21, // 16: provisionerd.AcquiredJob.TemplateImport.metadata:type_name -> provisioner.Provision.Metadata + 20, // 17: provisionerd.AcquiredJob.TemplateDryRun.parameter_values:type_name -> provisioner.ParameterValue + 21, // 18: provisionerd.AcquiredJob.TemplateDryRun.metadata:type_name -> provisioner.Provision.Metadata + 22, // 19: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource + 22, // 20: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource + 22, // 21: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource + 22, // 22: provisionerd.CompletedJob.TemplateDryRun.resources:type_name -> provisioner.Resource 1, // 23: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty 6, // 24: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest 3, // 25: provisionerd.ProvisionerDaemon.FailJob:input_type -> provisionerd.FailedJob 4, // 26: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob - 2, // 27: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob - 7, // 28: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse - 1, // 29: provisionerd.ProvisionerDaemon.FailJob:output_type -> provisionerd.Empty - 1, // 30: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty - 27, // [27:31] is the sub-list for method output_type - 23, // [23:27] is the sub-list for method input_type + 8, // 27: provisionerd.ProvisionerDaemon.Connect:input_type -> provisionerd.ConnectRequest + 2, // 28: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob + 7, // 29: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse + 1, // 30: provisionerd.ProvisionerDaemon.FailJob:output_type -> provisionerd.Empty + 1, // 31: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty + 1, // 32: provisionerd.ProvisionerDaemon.Connect:output_type -> provisionerd.Empty + 28, // [28:33] is the sub-list for method output_type + 23, // [23:28] 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 @@ -1484,7 +1543,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AcquiredJob_WorkspaceBuild); i { + switch v := v.(*ConnectRequest); i { case 0: return &v.state case 1: @@ -1496,7 +1555,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AcquiredJob_TemplateImport); i { + switch v := v.(*AcquiredJob_WorkspaceBuild); i { case 0: return &v.state case 1: @@ -1508,7 +1567,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AcquiredJob_TemplateDryRun); i { + switch v := v.(*AcquiredJob_TemplateImport); i { case 0: return &v.state case 1: @@ -1520,7 +1579,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FailedJob_WorkspaceBuild); i { + switch v := v.(*AcquiredJob_TemplateDryRun); i { case 0: return &v.state case 1: @@ -1532,7 +1591,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FailedJob_TemplateImport); i { + switch v := v.(*FailedJob_WorkspaceBuild); i { case 0: return &v.state case 1: @@ -1544,7 +1603,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FailedJob_TemplateDryRun); i { + switch v := v.(*FailedJob_TemplateImport); i { case 0: return &v.state case 1: @@ -1556,7 +1615,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CompletedJob_WorkspaceBuild); i { + switch v := v.(*FailedJob_TemplateDryRun); i { case 0: return &v.state case 1: @@ -1568,7 +1627,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CompletedJob_TemplateImport); i { + switch v := v.(*CompletedJob_WorkspaceBuild); i { case 0: return &v.state case 1: @@ -1580,6 +1639,18 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CompletedJob_TemplateImport); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_provisionerd_proto_provisionerd_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CompletedJob_TemplateDryRun); i { case 0: return &v.state @@ -1613,7 +1684,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_provisionerd_proto_provisionerd_proto_rawDesc, NumEnums: 1, - NumMessages: 16, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, diff --git a/provisionerd/proto/provisionerd.proto b/provisionerd/proto/provisionerd.proto index 57f768ad68ada..1828e55b4f075 100644 --- a/provisionerd/proto/provisionerd.proto +++ b/provisionerd/proto/provisionerd.proto @@ -106,6 +106,10 @@ message UpdateJobResponse { repeated provisioner.ParameterValue parameter_values = 2; } +message ConnectRequest { + repeated string supported_provisioners = 1; +} + service ProvisionerDaemon { // AcquireJob requests a job. Implementations should // hold a lock on the job until CompleteJob() is @@ -122,4 +126,8 @@ service ProvisionerDaemon { // CompleteJob indicates a job has been completed. rpc CompleteJob(CompletedJob) returns (Empty); + + // Connect provides metadata about the provisioner. Implementations + // should call this method as the first thing they do after connecting. + rpc Connect(ConnectRequest) returns (Empty); } diff --git a/provisionerd/proto/provisionerd_drpc.pb.go b/provisionerd/proto/provisionerd_drpc.pb.go index 646f855eabc70..472084be38b7e 100644 --- a/provisionerd/proto/provisionerd_drpc.pb.go +++ b/provisionerd/proto/provisionerd_drpc.pb.go @@ -42,6 +42,7 @@ type DRPCProvisionerDaemonClient interface { UpdateJob(ctx context.Context, in *UpdateJobRequest) (*UpdateJobResponse, error) FailJob(ctx context.Context, in *FailedJob) (*Empty, error) CompleteJob(ctx context.Context, in *CompletedJob) (*Empty, error) + Connect(ctx context.Context, in *ConnectRequest) (*Empty, error) } type drpcProvisionerDaemonClient struct { @@ -90,11 +91,21 @@ func (c *drpcProvisionerDaemonClient) CompleteJob(ctx context.Context, in *Compl return out, nil } +func (c *drpcProvisionerDaemonClient) Connect(ctx context.Context, in *ConnectRequest) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/provisionerd.ProvisionerDaemon/Connect", drpcEncoding_File_provisionerd_proto_provisionerd_proto{}, in, out) + if err != nil { + return nil, err + } + return out, nil +} + type DRPCProvisionerDaemonServer interface { AcquireJob(context.Context, *Empty) (*AcquiredJob, error) UpdateJob(context.Context, *UpdateJobRequest) (*UpdateJobResponse, error) FailJob(context.Context, *FailedJob) (*Empty, error) CompleteJob(context.Context, *CompletedJob) (*Empty, error) + Connect(context.Context, *ConnectRequest) (*Empty, error) } type DRPCProvisionerDaemonUnimplementedServer struct{} @@ -115,9 +126,13 @@ func (s *DRPCProvisionerDaemonUnimplementedServer) CompleteJob(context.Context, return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented) } +func (s *DRPCProvisionerDaemonUnimplementedServer) Connect(context.Context, *ConnectRequest) (*Empty, error) { + return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented) +} + type DRPCProvisionerDaemonDescription struct{} -func (DRPCProvisionerDaemonDescription) NumMethods() int { return 4 } +func (DRPCProvisionerDaemonDescription) NumMethods() int { return 5 } func (DRPCProvisionerDaemonDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) { switch n { @@ -157,6 +172,15 @@ func (DRPCProvisionerDaemonDescription) Method(n int) (string, drpc.Encoding, dr in1.(*CompletedJob), ) }, DRPCProvisionerDaemonServer.CompleteJob, true + case 4: + return "/provisionerd.ProvisionerDaemon/Connect", drpcEncoding_File_provisionerd_proto_provisionerd_proto{}, + func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { + return srv.(DRPCProvisionerDaemonServer). + Connect( + ctx, + in1.(*ConnectRequest), + ) + }, DRPCProvisionerDaemonServer.Connect, true default: return "", nil, nil, nil, false } @@ -229,3 +253,19 @@ func (x *drpcProvisionerDaemon_CompleteJobStream) SendAndClose(m *Empty) error { } return x.CloseSend() } + +type DRPCProvisionerDaemon_ConnectStream interface { + drpc.Stream + SendAndClose(*Empty) error +} + +type drpcProvisionerDaemon_ConnectStream struct { + drpc.Stream +} + +func (x *drpcProvisionerDaemon_ConnectStream) SendAndClose(m *Empty) error { + if err := x.MsgSend(m, drpcEncoding_File_provisionerd_proto_provisionerd_proto{}); err != nil { + return err + } + return x.CloseSend() +} diff --git a/provisionerd/provisionerd.go b/provisionerd/provisionerd.go index 64663e93a6c34..d596dce1e01a4 100644 --- a/provisionerd/provisionerd.go +++ b/provisionerd/provisionerd.go @@ -141,6 +141,9 @@ func (p *Server) connect(ctx context.Context) { if p.isClosed() { return } + + p.sendConnectRequest(ctx) + ticker := time.NewTicker(p.opts.PollInterval) defer ticker.Stop() for { @@ -182,6 +185,37 @@ func (p *Server) isRunningJob() bool { } } +// sendConnectRequest is called as the first RPC on each connection, and +// informs the server which provisioners this daemon supports +func (p *Server) sendConnectRequest(ctx context.Context) { + p.mutex.Lock() + defer p.mutex.Unlock() + if p.isClosed() { + return + } + client, ok := p.client() + if !ok { + return + } + + var provisionerNames []string + for key := range p.opts.Provisioners { + provisionerNames = append(provisionerNames, key) + } + _, err := client.Connect(ctx, &proto.ConnectRequest{ + SupportedProvisioners: provisionerNames, + }) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + if errors.Is(err, yamux.ErrSessionShutdown) { + return + } + p.opts.Logger.Warn(context.Background(), "initial connect request", slog.Error(err)) + } +} + // Locks a job in the database, and runs it! func (p *Server) acquireJob(ctx context.Context) { p.mutex.Lock() diff --git a/provisionerd/provisionerd_test.go b/provisionerd/provisionerd_test.go index 05becb863ebd7..5fa26c121232a 100644 --- a/provisionerd/provisionerd_test.go +++ b/provisionerd/provisionerd_test.go @@ -51,6 +51,9 @@ func closedWithin(c chan struct{}, d time.Duration) func() bool { func TestProvisionerd(t *testing.T) { t.Parallel() + noopAcquireJob := func(ctx context.Context, _ *proto.Empty) (*proto.AcquiredJob, error) { + return &proto.AcquiredJob{}, nil + } noopUpdateJob := func(ctx context.Context, update *proto.UpdateJobRequest) (*proto.UpdateJobResponse, error) { return &proto.UpdateJobResponse{}, nil } @@ -74,6 +77,28 @@ func TestProvisionerd(t *testing.T) { require.NoError(t, closer.Close()) }) + t.Run("InitialConnectRequest", func(t *testing.T) { + t.Parallel() + completeChan := make(chan struct{}) + var provisioners []string + closer := createProvisionerd(t, func(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) { + return createProvisionerDaemonClient(t, provisionerDaemonTestServer{ + acquireJob: noopAcquireJob, + updateJob: noopUpdateJob, + connect: func(ctx context.Context, request *proto.ConnectRequest) (*proto.Empty, error) { + defer close(completeChan) + provisioners = request.SupportedProvisioners + return &proto.Empty{}, nil + }, + }), nil + }, provisionerd.Provisioners{ + "someprovisioner": createProvisionerClient(t, provisionerTestServer{}), + }) + require.Condition(t, closedWithin(completeChan, testutil.WaitShort)) + require.Equal(t, []string{"someprovisioner"}, provisioners) + require.NoError(t, closer.Close()) + }) + t.Run("AcquireEmptyJob", func(t *testing.T) { // The provisioner daemon is supposed to skip the job acquire if // the job provided is empty. This is to show it successfully @@ -972,6 +997,12 @@ func createProvisionerDaemonClient(t *testing.T, server provisionerDaemonTestSer return &proto.Empty{}, nil } } + if server.connect == nil { + // Default to no-op + server.connect = func(ctx context.Context, request *proto.ConnectRequest) (*proto.Empty, error) { + return &proto.Empty{}, nil + } + } clientPipe, serverPipe := provisionersdk.TransportPipe() t.Cleanup(func() { _ = clientPipe.Close() @@ -1030,6 +1061,7 @@ type provisionerDaemonTestServer struct { updateJob func(ctx context.Context, update *proto.UpdateJobRequest) (*proto.UpdateJobResponse, error) failJob func(ctx context.Context, job *proto.FailedJob) (*proto.Empty, error) completeJob func(ctx context.Context, job *proto.CompletedJob) (*proto.Empty, error) + connect func(ctx context.Context, request *proto.ConnectRequest) (*proto.Empty, error) } func (p *provisionerDaemonTestServer) AcquireJob(ctx context.Context, empty *proto.Empty) (*proto.AcquiredJob, error) { @@ -1047,3 +1079,7 @@ func (p *provisionerDaemonTestServer) FailJob(ctx context.Context, job *proto.Fa func (p *provisionerDaemonTestServer) CompleteJob(ctx context.Context, job *proto.CompletedJob) (*proto.Empty, error) { return p.completeJob(ctx, job) } + +func (p *provisionerDaemonTestServer) Connect(ctx context.Context, request *proto.ConnectRequest) (*proto.Empty, error) { + return p.connect(ctx, request) +}