kubernetes/kops · error

server-side addons client does not support Addons::Replace

Error message

server-side addons client does not support Addons::Replace

What it means

The controller-side addons clientset is a read-only, server-side view of addons rendered from local files. Replace (mutating an addons list) is intentionally unimplemented and always returns this sentinel error. It signals 'this operation is not supported by this client', not a transient failure.

Source

Thrown at cmd/kops-controller/pkg/controllerclientset/addons.go:53

}

var _ simple.AddonsClient = &addonsClient{}

func newAddonsClient(basePath vfs.Path, cluster *kops.Cluster) *addonsClient {
	if cluster == nil || cluster.Name == "" {
		klog.Fatalf("cluster / cluster.Name is required")
	}

	r := &addonsClient{
		basePath: basePath,
		cluster:  cluster,
	}

	return r
}

func (c *addonsClient) Replace(addons kubemanifest.ObjectList) error {
	return fmt.Errorf("server-side addons client does not support Addons::Replace")
}

func (c *addonsClient) List(ctx context.Context) (kubemanifest.ObjectList, error) {
	configPath := c.basePath.Join("default")

	b, err := configPath.ReadFile(ctx)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("error reading addons file %s: %v", configPath, err)
	}

	objects, err := kubemanifest.LoadObjectsFrom(b)
	if err != nil {
		return nil, err
	}

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Refactor the calling code to skip addon replacement when running controller-side, or use the full kops CLI clientset
  2. Guard the call site: only invoke Replace from tooling that uses the file-backed admin clientset
  3. If addon mutation is needed from the controller, implement Replace in addonsClient against the API server

Example fix

// before
if err := addonsClient.Replace(addons); err != nil {
	return err
}
// after
if err := addonsClient.Replace(addons); err != nil {
	if err.Error() == "server-side addons client does not support Addons::Replace" {
		return nil // unsupported controller-side; managed via kops CLI
	}
	return err
}
Defensive patterns

Strategy: type-guard

Validate before calling

// detect the unsupported client before mutating
type replacer interface{ Replace(kubemanifest.ObjectList) error }
if _, ok := addonsClient.(replacer); !ok {
	// this clientset cannot Replace; route via kops CLI instead
}

Type guard

func supportsReplace(c interface{}) bool {
	r, ok := c.(interface{ Replace(kubemanifest.ObjectList) error })
	_ = r
	return ok
}

Try / catch

if err := addonsClient.Replace(addons); err != nil {
	if err.Error() == "server-side addons client does not support Addons::Replace" {
		return nil // expected on controller-side clientset
	}
	return err
}

Prevention

When it happens

Trigger: Any code path calls addonsClient.Replace(addons) — e.g. RunCreate or CreateClusterConfig invoking the addons clientset from within kops-controller, where only List/Get-style reads are supported.

Common situations: Reusing kops bootstrup/create code (which does addons replace) inside the controller; a new feature tried to apply addon changes via the controller clientset instead of `kops replace`/the admin CLI.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/eb1ea8a8ef6cf4c2. Report an issue: GitHub.