hashicorp/nomad · error

cannot upload management tokens

Error message

cannot upload management tokens

What it means

Create with topologies/capacity requires a plugin with a CSI controller (ControllerRequired), but the referenced plugin is node-only. Controller-less plugins cannot provision volumes, so Nomad rejects the create with this message.

Source

Thrown at api/acl.go:153

	var resp ACLToken
	wm, err := a.client.put("/v1/acl/token", token, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Upload is used to create a client token with pre-specified AccessorID and
// SecretID. Management tokens cannot be uploaded and must be created with Create.
func (a *ACLTokens) Upload(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	if token.AccessorID == "" {
		return nil, nil, errors.New("missing accessor ID")
	}
	if token.SecretID == "" {
		return nil, nil, errors.New("missing secret ID")
	}
	if token.Type == "management" {
		return nil, nil, errors.New("cannot upload management tokens")
	}
	var resp ACLToken
	wm, err := a.client.put("/v1/acl/token/"+token.AccessorID, token, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Update is used to update an existing token
func (a *ACLTokens) Update(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	if token.AccessorID == "" {
		return nil, nil, errors.New("missing accessor ID")
	}
	var resp ACLToken
	wm, err := a.client.put("/v1/acl/token/"+token.AccessorID,
		token, &resp, q)
	if err != nil {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Add a controller plugin task to the plugin job and re-register (nomad job stop/start) so ControllerRequired is satisfied
  2. Use `nomad volume register` (static registration) instead of dynamic create for controller-less drivers
  3. Verify with nomad plugin status <id> that Controllers > 0

Example fix

// before: node-only CSI plugin
plugin "hostpath" {
  type = "node"
}
// after: dynamic provisioning needs a controller too
plugin "hostpath" {
  type = "controller"
}
plugin "hostpath" {
  type = "node"
}
Defensive patterns

Strategy: validation

Validate before calling

plug, _, _ := client.CSIPlugins().Get(pluginID, nil)
if plug == nil || plug.ControllersExpected == 0 {
    // controller-less driver: use static volume register instead of create
}

Try / catch

if err := create(req); err != nil && strings.Contains(err.Error(), "plugin has no controller") {
    // fall back to static registration or deploy a controller plugin
}

Prevention

When it happens

Trigger: `nomad volume create` (Create RPC) whose volume's plugin_id points to a plugin registered only as type="node" (no controller task) — e.g. hostpath or node-only CSI drivers.

Common situations: Registering a CSI plugin job with only node plugins, then trying dynamic provisioning; driver (e.g. some local/path provisioners) simply has no controller; typo'd plugin ID resolving to a node-only plugin.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/128a91ce1c9aa669. Report an issue: GitHub.