Files
coder/enterprise/cli/provisionerdaemons.go
T
Ammar Bandukwala dd97fe2bce chore(cli): replace lipgloss with coder/pretty (#9564)
This change will improve over CLI performance and "snappiness" as well as
substantially reduce our test times. Preliminary benchmarks show
`coder server --help` times cut from 300ms to 120ms on my dogfood
instance.

The inefficiency of lipgloss disproportionately impacts our system, as all help
text for every command is generated whenever any command is invoked.

The `pretty` API could clean up a lot of the code (e.g., by replacing
complex string concatenations with Printf), but this commit is too
expansive as is so that work will be done in a follow up.
2023-09-07 16:28:22 -05:00

212 lines
5.8 KiB
Go

//go:build !slim
package cli
import (
"context"
"fmt"
"os"
"os/signal"
"time"
"golang.org/x/xerrors"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
agpl "github.com/coder/coder/v2/cli"
"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/provisionerdserver"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/provisioner/terraform"
"github.com/coder/coder/v2/provisionerd"
provisionerdproto "github.com/coder/coder/v2/provisionerd/proto"
"github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/provisionersdk/proto"
)
func (r *RootCmd) provisionerDaemons() *clibase.Cmd {
cmd := &clibase.Cmd{
Use: "provisionerd",
Short: "Manage provisioner daemons",
Handler: func(inv *clibase.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Children: []*clibase.Cmd{
r.provisionerDaemonStart(),
},
}
return cmd
}
func (r *RootCmd) provisionerDaemonStart() *clibase.Cmd {
var (
cacheDir string
rawTags []string
pollInterval time.Duration
pollJitter time.Duration
preSharedKey string
)
client := new(codersdk.Client)
cmd := &clibase.Cmd{
Use: "start",
Short: "Run a provisioner daemon",
Middleware: clibase.Chain(
r.InitClientMissingTokenOK(client),
),
Handler: func(inv *clibase.Invocation) error {
ctx, cancel := context.WithCancel(inv.Context())
defer cancel()
notifyCtx, notifyStop := signal.NotifyContext(ctx, agpl.InterruptSignals...)
defer notifyStop()
tags, err := agpl.ParseProvisionerTags(rawTags)
if err != nil {
return err
}
logger := slog.Make(sloghuman.Sink(inv.Stderr))
if ok, _ := inv.ParsedFlags().GetBool("verbose"); ok {
logger = logger.Leveled(slog.LevelDebug)
}
if len(tags) != 0 {
logger.Info(ctx, "note: tagged provisioners can currently pick up jobs from untagged templates")
logger.Info(ctx, "see https://github.com/coder/coder/issues/6442 for details")
}
// When authorizing with a PSK, we automatically scope the provisionerd
// to organization. Scoping to user with PSK auth is not a valid configuration.
if preSharedKey != "" {
logger.Info(ctx, "psk auth automatically sets tag "+provisionerdserver.TagScope+"="+provisionerdserver.ScopeOrganization)
tags[provisionerdserver.TagScope] = provisionerdserver.ScopeOrganization
}
err = os.MkdirAll(cacheDir, 0o700)
if err != nil {
return xerrors.Errorf("mkdir %q: %w", cacheDir, err)
}
tempDir, err := os.MkdirTemp("", "provisionerd")
if err != nil {
return err
}
terraformClient, terraformServer := provisionersdk.MemTransportPipe()
go func() {
<-ctx.Done()
_ = terraformClient.Close()
_ = terraformServer.Close()
}()
errCh := make(chan error, 1)
go func() {
defer cancel()
err := terraform.Serve(ctx, &terraform.ServeOptions{
ServeOptions: &provisionersdk.ServeOptions{
Listener: terraformServer,
Logger: logger.Named("terraform"),
WorkDirectory: tempDir,
},
CachePath: cacheDir,
})
if err != nil && !xerrors.Is(err, context.Canceled) {
select {
case errCh <- err:
default:
}
}
}()
logger.Info(ctx, "starting provisioner daemon", slog.F("tags", tags))
provisioners := provisionerd.Provisioners{
string(database.ProvisionerTypeTerraform): proto.NewDRPCProvisionerClient(terraformClient),
}
srv := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) {
return client.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{
Provisioners: []codersdk.ProvisionerType{
codersdk.ProvisionerTypeTerraform,
},
Tags: tags,
PreSharedKey: preSharedKey,
})
}, &provisionerd.Options{
Logger: logger,
JobPollInterval: pollInterval,
JobPollJitter: pollJitter,
UpdateInterval: 500 * time.Millisecond,
Provisioners: provisioners,
})
var exitErr error
select {
case <-notifyCtx.Done():
exitErr = notifyCtx.Err()
_, _ = fmt.Fprintln(inv.Stdout, cliui.Bold(
"Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit",
))
case exitErr = <-errCh:
}
if exitErr != nil && !xerrors.Is(exitErr, context.Canceled) {
cliui.Errorf(inv.Stderr, "Unexpected error, shutting down server: %s\n", exitErr)
}
err = srv.Shutdown(ctx)
if err != nil {
return xerrors.Errorf("shutdown: %w", err)
}
cancel()
if xerrors.Is(exitErr, context.Canceled) {
return nil
}
return exitErr
},
}
cmd.Options = clibase.OptionSet{
{
Flag: "cache-dir",
FlagShorthand: "c",
Env: "CODER_CACHE_DIRECTORY",
Description: "Directory to store cached data.",
Default: codersdk.DefaultCacheDir(),
Value: clibase.StringOf(&cacheDir),
},
{
Flag: "tag",
FlagShorthand: "t",
Env: "CODER_PROVISIONERD_TAGS",
Description: "Tags to filter provisioner jobs by.",
Value: clibase.StringArrayOf(&rawTags),
},
{
Flag: "poll-interval",
Env: "CODER_PROVISIONERD_POLL_INTERVAL",
Default: time.Second.String(),
Description: "How often to poll for provisioner jobs.",
Value: clibase.DurationOf(&pollInterval),
},
{
Flag: "poll-jitter",
Env: "CODER_PROVISIONERD_POLL_JITTER",
Description: "How much to jitter the poll interval by.",
Default: (100 * time.Millisecond).String(),
Value: clibase.DurationOf(&pollJitter),
},
{
Flag: "psk",
Env: "CODER_PROVISIONER_DAEMON_PSK",
Description: "Pre-shared key to authenticate with Coder server.",
Value: clibase.StringOf(&preSharedKey),
},
}
return cmd
}