Skip to content

feat: Allow running standalone provisioner daemons #3563

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Prev Previous commit
Next Next commit
have provisionerd register its supported provisioners on connection
  • Loading branch information
dwahler committed Aug 30, 2022
commit 73f1e5cc95fa4ccf3263ae65f232c4b33e199a40
7 changes: 4 additions & 3 deletions cli/provisionercreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -42,5 +43,5 @@ coder provisioners run --token `+tokenArg)
return nil
},
}
return root
return cmd
}
5 changes: 3 additions & 2 deletions cli/provisionercreate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
23 changes: 14 additions & 9 deletions cli/provisionerrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not saying it has to happen here, but since the provisioner has a name, consider using filepath.Join(cacheDir, "provisionerd", name). Perhaps in newProvisionerDaemon.

This will allow multiple provisioners to run on the same machine without potentially breaking terraform init.

if err != nil {
return xerrors.Errorf("create provisioner daemon: %w", err)
}
Expand Down Expand Up @@ -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
}
50 changes: 49 additions & 1 deletion cli/provisionerrun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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())
Expand All @@ -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)
})
}
29 changes: 29 additions & 0 deletions coderd/provisionerdaemons.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Loading