kubernetes/kops · error

building menu for %q: %w

Error message

building menu for %q: %w

What it means

RunApplyChannel builds an addon menu for each channel location argument. When buildMenu fails for a location, the error is wrapped as 'building menu for %q' and accumulated into a multierr, so remaining locations are still processed. The wrapped cause is typically URL parsing failure, the legacy-addon rejection, or LoadAddons/GetCurrent failure.

Source

Thrown at channels/pkg/cmd/apply_channel.go:209

	}

	kubernetesVersion, err := semver.ParseTolerant(kubernetesVersionInfo.GitVersion)
	if err != nil {
		return fmt.Errorf("cannot parse kubernetes version %q", kubernetesVersionInfo.GitVersion)
	}

	// Remove Pre and Patch, as they make semver comparisons impractical
	kubernetesVersion.Pre = nil

	if len(args) == 0 {
		return fmt.Errorf("at least one channel URL is required")
	}

	var merr error
	for _, channelLocation := range args {
		menu, err := buildMenu(f.VFSContext(), kubernetesVersion, channelLocation)
		if err != nil {
			merr = multierr.Append(merr, fmt.Errorf("building menu for %q: %w", channelLocation, err))
			continue
		}
		if err := applyMenu(ctx, menu, f.VFSContext(), k8sClient, cmClient, dynamicClient, restMapper, options.Yes); err != nil {
			merr = multierr.Append(merr, fmt.Errorf("applying %q: %w", channelLocation, err))
		}
	}
	return merr
}

func applyMenu(ctx context.Context, menu *channels.AddonMenu, vfsContext *vfs.VFSContext, k8sClient kubernetes.Interface, cmClient certmanager.Interface, dynamicClient dynamic.Interface, restMapper *restmapper.DeferredDiscoveryRESTMapper, apply bool) error {
	// channelVersions is the list of installed addons in the cluster.
	// It is keyed by <namespace>:<addon name>.
	channelVersions, err := getChannelVersions(ctx, k8sClient)
	if err != nil {
		return fmt.Errorf("cannot fetch channel versions from namespaces: %w", err)
	}

	updates, needUpdates, err := getUpdates(ctx, menu, k8sClient, cmClient, channelVersions)

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Read the wrapped cause; verify each location is a valid absolute URL before running
  2. Replace legacy bare addon names with kOps managed addons in the cluster spec
  3. Fix network/VFS access if the underlying cause is a fetch failure
  4. Re-run; multierr reports each failing location independently

Example fix

// before
err := RunApplyChannel(ctx, f, out, []string{"networking.flannel"}, options)
// after
loc := "https://raw.githubusercontent.com/example/addons/networking.addons.k8s.io/v1.28.0/addon.yaml"
if u, uerr := url.Parse(loc); uerr != nil || !u.IsAbs() {
	return fmt.Errorf("invalid channel location %q", loc)
}
err := RunApplyChannel(ctx, f, out, []string{loc}, options)
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(channelLocation)
if err != nil {
	return fmt.Errorf("invalid channel location %q: %v", channelLocation, err)
}
if !u.IsAbs() {
	return fmt.Errorf("channel location %q must be an absolute URL (legacy addons are deprecated)", channelLocation)
}

Type guard

func isValidChannelLocation(loc string) bool {
	u, err := url.Parse(loc)
	return err == nil && u.IsAbs()
}

Try / catch

if err := RunApplyChannel(ctx, f, out, args, options); err != nil {
	if merrs, ok := err.(multierr.Error); ok {
		for _, e := range merrs.Errors() {
			log.Printf("channel apply failed: %v", e)
		}
	}
}

Prevention

When it happens

Trigger: Calling `kops apply channel <location>` where url.Parse fails, the location is relative (legacy-addon path), or the addon.yaml cannot be loaded/processed.

Common situations: Malformed channel URL (bad characters), using a deprecated bare addon name like networking.flannel, unreachable or invalid channel manifest.

Related errors


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