kubernetes/kops · error

not a directory: %q

Error message

not a directory: %q

What it means

mkdirAll verifies that each path component exists and is a directory before writing a file over SFTP. When Lstat succeeds for `dir` but the entry is a regular file, symlink-to-file, or other non-directory, the function aborts with this error instead of silently proceeding. It exists to give a clear message rather than confusing Mkdir/Create failures downstream.

Source

Thrown at util/pkg/vfs/sshfs.go:153

}

func (p *SSHPath) Join(relativePath ...string) Path {
	args := []string{p.path}
	args = append(args, relativePath...)
	joined := path.Join(args...)
	return NewSSHPath(p.client, p.server, joined, p.sudo)
}

func mkdirAll(sftpClient *sftp.Client, dir string) error {
	if dir == "/" {
		// Must always exist
		return nil
	}

	stat, err := sftpClient.Lstat(dir)
	if err == nil {
		if !stat.IsDir() {
			return fmt.Errorf("not a directory: %q", dir)
		}
		return nil
	}

	parent := path.Dir(dir)
	err = mkdirAll(sftpClient, parent)
	if err != nil {
		return err
	}

	err = sftpClient.Mkdir(dir)
	if err != nil {
		return fmt.Errorf("error creating directory %q over sftp: %w", dir, err)
	}
	return nil
}

func (p *SSHPath) WriteFile(ctx context.Context, data io.ReadSeeker, acl ACL) error {

View on GitHub (pinned to 4c8573c808)

Solutions

  1. SSH to the remote host and inspect the offending path (`ls -la <dir>`); remove or rename the file that occupies the directory name (`mv <dir> <dir>.bak` or `rm <dir>`).
  2. Recreate the path as a directory: `mkdir -p <dir>` on the remote host, then retry the kOps operation.
  3. Fix the SSHPath construction in your tooling so the target parent path points at an actual directory.
  4. If a symlink is involved, repoint it (`ln -sfn <realdir> <dir>`) so it resolves to a directory.

Example fix

// before (remote host has a file where a directory is needed)
$ ls -la /etc/kubernetes
-rw-r--r-- 1 root root 12 config
// after
$ rm /etc/kubernetes/config && mkdir -p /etc/kubernetes/config
Defensive patterns

Strategy: validation

Validate before calling

// check remote parent path is a directory before writing
sftp, err := newSFTPClient(ctx)
if err != nil { return err }
if stat, err := sftp.Lstat(dir); err == nil && !stat.IsDir() {
    return fmt.Errorf("precheck: %q exists and is not a directory", dir)
}

Prevention

When it happens

Trigger: Calling WriteFile (via CreateFile) on an SSHPath whose parent path component is an existing non-directory, e.g. writing to /etc/kubernetes/config where /etc/kubernetes is a regular file on the remote host.

Common situations: A previously created config file now collides with a directory kOps needs (e.g. state store path conflicts); a stray file was created where a directory was expected; symlink pointing at a file; wrong SSHPath prefix built by the caller.

Related errors


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