kubernetes/kops · error

image has no sources: %v

Error message

image has no sources: %v

What it means

Run() iterates nodeupConfig.Images[architecture] and creates a LoadImageTask for each container image to sideload. If an image entry has an empty Sources list, nodeup rejects it outright because a LoadImageTask with no source cannot pull or sideload anything. This indicates malformed image config in the NodeupConfig.

Source

Thrown at upup/pkg/fi/nodeup/command.go:346

	loader.Builders = append(loader.Builders, &model.AzureBuilder{NodeupModelContext: modelContext})
	loader.Builders = append(loader.Builders, &model.LinodeBuilder{NodeupModelContext: modelContext})

	loader.Builders = append(loader.Builders, &networking.CommonBuilder{NodeupModelContext: modelContext})
	loader.Builders = append(loader.Builders, &networking.CalicoBuilder{NodeupModelContext: modelContext})
	loader.Builders = append(loader.Builders, &networking.CiliumBuilder{NodeupModelContext: modelContext})
	loader.Builders = append(loader.Builders, &networking.KindnetBuilder{NodeupModelContext: modelContext})
	loader.Builders = append(loader.Builders, &networking.AmazonVPCRoutedENIBuilder{NodeupModelContext: modelContext})
	loader.Builders = append(loader.Builders, &networking.KuberouterBuilder{NodeupModelContext: modelContext})

	loader.Builders = append(loader.Builders, &model.BootstrapClientBuilder{NodeupModelContext: modelContext})
	taskMap, err := loader.Build()
	if err != nil {
		return fmt.Errorf("error building loader: %v", err)
	}

	for _, image := range nodeupConfig.Images[architecture] {
		if len(image.Sources) == 0 {
			return fmt.Errorf("image has no sources: %v", image)
		}
		u, err := url.Parse(image.Sources[0])
		if err != nil {
			return fmt.Errorf("invalid image source URL %q: %w", image.Sources[0], err)
		}
		key := "SideloadImage/" + path.Base(u.Path)
		if _, ok := taskMap[key]; ok {
			return fmt.Errorf("duplicate image task %q", key)
		}
		taskMap[key] = &nodetasks.LoadImageTask{
			Sources: image.Sources,
			Hash:    image.Hash,
		}
	}

	var target fi.NodeupTarget

	switch c.Target {

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Inspect the NodeupConfig Images map for the node's architecture and fix/remove entries with empty Sources
  2. Regenerate the cluster config with `kops update cluster` so image lists are repopulated from the kops version defaults
  3. Ensure any containerd_config/images overrides in the cluster spec include full source URLs
  4. Align nodeup binary version with the kops version that rendered the config

Example fix

// before: image entry in NodeupConfig
// {"name":"pause","sources":[]}
// after
// {"name":"pause","sources":["https://registry.k8s.io/pause:3.9"]}
Defensive patterns

Strategy: validation

Validate before calling

for arch, imgs := range nodeupConfig.Images {
    for i, img := range imgs {
        if len(img.Sources) == 0 {
            return fmt.Errorf("nodeupConfig.Images[%s][%d] has no sources", arch, i)
        }
    }
}

Type guard

func imageHasSources(img NodeupImage) bool { return len(img.Sources) > 0 }

Try / catch

if err := nodeupCmd.Run(ctx); err != nil {
    if strings.Contains(err.Error(), "image has no sources") {
        log.Fatalf("bad image config: %v — regenerate NodeupConfig", err)
    }
    return err
}

Prevention

When it happens

Trigger: An entry in nodeupConfig.Images[architecture] has len(image.Sources) == 0 — i.e. the images map for the node's architecture contains an image with no source URLs.

Common situations: Hand-edited or template-generated nodeup config where the image source list was dropped; cluster spec with custom images configured incorrectly; arch mismatch so the wrong architecture bucket is populated; older kops versions producing configs consumed by newer nodeup.

Related errors


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