- Notifications
You must be signed in to change notification settings - Fork924
feat(cli): allow specifying name of provisioner daemon#11077
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
4c4ef62
9415b66
95970f9
80e49ff
6fdcec2
80f71eb
b9e7141
30e8e59
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package cliutil | ||
import ( | ||
"os" | ||
"strings" | ||
"sync" | ||
) | ||
var ( | ||
hostname string | ||
hostnameOnce sync.Once | ||
) | ||
// Hostname returns the hostname of the machine, lowercased, | ||
// with any trailing domain suffix stripped. | ||
// It is cached after the first call. | ||
// If the hostname cannot be determined, for any reason, | ||
// localhost will be returned instead. | ||
func Hostname() string { | ||
hostnameOnce.Do(func() { hostname = getHostname() }) | ||
return hostname | ||
} | ||
func getHostname() string { | ||
h, err := os.Hostname() | ||
if err != nil { | ||
// Something must be very wrong if this fails. | ||
// We'll just return localhost and hope for the best. | ||
return "localhost" | ||
} | ||
// On some platforms, the hostname can be an FQDN. We only want the hostname. | ||
if idx := strings.Index(h, "."); idx != -1 { | ||
johnstcn marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
h = h[:idx] | ||
} | ||
// For the sake of consistency, we also want to lowercase the hostname. | ||
// Per RFC 4343, DNS lookups must be case-insensitive. | ||
return strings.ToLower(h) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -62,6 +62,7 @@ import ( | ||
"github.com/coder/coder/v2/buildinfo" | ||
"github.com/coder/coder/v2/cli/clibase" | ||
"github.com/coder/coder/v2/cli/cliui" | ||
"github.com/coder/coder/v2/cli/cliutil" | ||
"github.com/coder/coder/v2/cli/config" | ||
"github.com/coder/coder/v2/coderd" | ||
"github.com/coder/coder/v2/coderd/autobuild" | ||
@@ -86,6 +87,7 @@ import ( | ||
"github.com/coder/coder/v2/coderd/unhanger" | ||
"github.com/coder/coder/v2/coderd/updatecheck" | ||
"github.com/coder/coder/v2/coderd/util/slice" | ||
stringutil "github.com/coder/coder/v2/coderd/util/strings" | ||
"github.com/coder/coder/v2/coderd/workspaceapps" | ||
"github.com/coder/coder/v2/codersdk" | ||
"github.com/coder/coder/v2/cryptorand" | ||
@@ -875,9 +877,14 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. | ||
defer provisionerdWaitGroup.Wait() | ||
provisionerdMetrics := provisionerd.NewMetrics(options.PrometheusRegistry) | ||
for i := int64(0); i < vals.Provisioner.Daemons.Value(); i++ { | ||
suffix := fmt.Sprintf("%d", i) | ||
// The suffix is added to the hostname, so we may need to trim to fit into | ||
// the 64 character limit. | ||
hostname := stringutil.Truncate(cliutil.Hostname(), 63-len(suffix)) | ||
name := fmt.Sprintf("%s-%s", hostname, suffix) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. A hostname could include e.g. MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Itshould not, but it turns out that you can write whatever you want to If we trim, we run the risk of collisions between machines named | ||
daemonCacheDir := filepath.Join(cacheDir, fmt.Sprintf("provisioner-%d", i)) | ||
daemon, err := newProvisionerDaemon( | ||
ctx, coderAPI, provisionerdMetrics, logger, vals, daemonCacheDir, errCh, &provisionerdWaitGroup, name, | ||
) | ||
if err != nil { | ||
return xerrors.Errorf("create provisioner daemon: %w", err) | ||
@@ -1243,6 +1250,7 @@ func newProvisionerDaemon( | ||
cacheDir string, | ||
errCh chan error, | ||
wg *sync.WaitGroup, | ||
name string, | ||
) (srv *provisionerd.Server, err error) { | ||
ctx, cancel := context.WithCancel(ctx) | ||
defer func() { | ||
@@ -1334,9 +1342,9 @@ func newProvisionerDaemon( | ||
return provisionerd.New(func(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) { | ||
// This debounces calls to listen every second. Read the comment | ||
// in provisionerdserver.go to learn more! | ||
return coderAPI.CreateInMemoryProvisionerDaemon(ctx, name) | ||
}, &provisionerd.Options{ | ||
Logger: logger.Named(fmt.Sprintf("provisionerd-%s", name)), | ||
UpdateInterval: time.Second, | ||
ForceCancelInterval: cfg.Provisioner.ForceCancelInterval.Value(), | ||
Connector: connector, | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -177,6 +177,8 @@ func (c *Client) provisionerJobLogsAfter(ctx context.Context, path string, after | ||
type ServeProvisionerDaemonRequest struct { | ||
// ID is a unique ID for a provisioner daemon. | ||
ID uuid.UUID `json:"id" format:"uuid"` | ||
// Name is the human-readable unique identifier for the daemon. | ||
Name string `json:"name" example:"my-cool-provisioner-daemon"` | ||
// Organization is the organization for the URL. At present provisioner daemons ARE NOT scoped to organizations | ||
// and so the organization ID is optional. | ||
Organization uuid.UUID `json:"organization" format:"uuid"` | ||
@@ -198,6 +200,7 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione | ||
} | ||
query := serverURL.Query() | ||
query.Add("id", req.ID.String()) | ||
query.Add("name", req.Name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. curious: is it required to pass ID and name now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I think I'm going to end up ignoring the ID parameter and just upserting based on name in a follow-up PR. | ||
for _, provisioner := range req.Provisioners { | ||
query.Add("provisioner", string(provisioner)) | ||
} | ||
Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.
Uh oh!
There was an error while loading.Please reload this page.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -6,6 +6,7 @@ import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"regexp" | ||
"time" | ||
"github.com/google/uuid" | ||
@@ -16,6 +17,7 @@ import ( | ||
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/cli/cliutil" | ||
"github.com/coder/coder/v2/coderd/database" | ||
"github.com/coder/coder/v2/coderd/provisionerdserver" | ||
"github.com/coder/coder/v2/codersdk" | ||
@@ -41,13 +43,24 @@ func (r *RootCmd) provisionerDaemons() *clibase.Cmd { | ||
return cmd | ||
} | ||
func validateProvisionerDaemonName(name string) error { | ||
if len(name) > 64 { | ||
return xerrors.Errorf("name cannot be greater than 64 characters in length") | ||
} | ||
if ok, err := regexp.MatchString(`^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$`, name); err != nil || !ok { | ||
return xerrors.Errorf("name %q is not a valid hostname", name) | ||
} | ||
return nil | ||
} | ||
func (r *RootCmd) provisionerDaemonStart() *clibase.Cmd { | ||
var ( | ||
cacheDir string | ||
rawTags []string | ||
pollInterval time.Duration | ||
pollJitter time.Duration | ||
preSharedKey string | ||
name string | ||
) | ||
client := new(codersdk.Client) | ||
cmd := &clibase.Cmd{ | ||
@@ -68,6 +81,14 @@ func (r *RootCmd) provisionerDaemonStart() *clibase.Cmd { | ||
return err | ||
} | ||
if name == "" { | ||
name = cliutil.Hostname() | ||
johnstcn marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
} | ||
if err := validateProvisionerDaemonName(name); err != nil { | ||
return err | ||
} | ||
logger := slog.Make(sloghuman.Sink(inv.Stderr)) | ||
if ok, _ := inv.ParsedFlags().GetBool("verbose"); ok { | ||
logger = logger.Leveled(slog.LevelDebug) | ||
@@ -122,15 +143,16 @@ func (r *RootCmd) provisionerDaemonStart() *clibase.Cmd { | ||
} | ||
}() | ||
logger.Info(ctx, "starting provisioner daemon", slog.F("tags", tags), slog.F("name", name)) | ||
connector := provisionerd.LocalProvisioners{ | ||
string(database.ProvisionerTypeTerraform): proto.NewDRPCProvisionerClient(terraformClient), | ||
} | ||
id := uuid.New() | ||
srv := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) { | ||
return client.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ | ||
ID: id, | ||
Name: name, | ||
Provisioners: []codersdk.ProvisionerType{ | ||
codersdk.ProvisionerTypeTerraform, | ||
}, | ||
@@ -205,6 +227,13 @@ func (r *RootCmd) provisionerDaemonStart() *clibase.Cmd { | ||
Description: "Pre-shared key to authenticate with Coder server.", | ||
Value: clibase.StringOf(&preSharedKey), | ||
}, | ||
{ | ||
Flag: "name", | ||
Env: "CODER_PROVISIONER_DAEMON_NAME", | ||
Description: "Name of this provisioner daemon. Defaults to the current hostname without FQDN.", | ||
Value: clibase.StringOf(&name), | ||
Default: "", | ||
}, | ||
} | ||
return cmd | ||
Uh oh!
There was an error while loading.Please reload this page.