elastic/elasticsearch · error · TestClustersException

Can't create roles.yml config file from

Error message

Can't create roles.yml config file from {} for {} as it does not exist

What it means

Thrown by configureSecurity() during start() when a File added via rolesFile(from) does not exist (Files.exists false). Symmetric to the extra-config-file check, this guards the roles.yml append step so that a missing source file fails loudly instead of silently producing an incomplete roles file.

Solutions

  1. Verify the path in the message exists at start time.
  2. If generated, pass the producing task's output as a Provider so Gradle builds it first.
  3. If the failing source is the bundled /roles.yml, ensure the build-tools jar providing that resource is on the test classpath (not stripped by shading).
  4. Use an absolute or project-relative path resolved via project.layout.projectDirectory.

Example fix

// before: relative file that may not resolve
rolesFile(new File('roles/custom.yml'))
// after: project-relative, generated task output wired in
rolesFile(generateRolesTask.flatMap { it.outputFile.asFile })
Defensive patterns

Strategy: validation

Validate before calling

node.getRoleFiles().forEach(f -> {
    if (!Files.exists(f.toPath())) {
        throw new IllegalStateException("rolesFile source missing: " + f);
    }
});
// If generated, wire Provider<File> from the producing task.

Prevention

When it happens

Trigger: Calling node.rolesFile(file) with a File that is wrong, generated-too-late, outside the project tree, or missing on the CI checkout. Note configureSecurity() also auto-adds getBuildPluginFile('/roles.yml') when credentials are present; if that bundled resource is unreachable (classpath issue) the same throw fires.

Common situations: Custom roles file generated by a task not wired as a dependency. Path relative to wrong subproject. Bundled /roles.yml resource missing from the test classpath after a refactor of build-tools resources.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/03c816bd426a0ba7. Report an issue: GitHub.

Appendix: source

Thrown at build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java:678

            logToProcessStdout("Setting up " + credentials.size() + " users");

            credentials.forEach(
                paramMap -> runElasticsearchBinScript(
                    getVersion().onOrAfter("6.3.0") ? "elasticsearch-users" : "x-pack/users",
                    paramMap.entrySet().stream().flatMap(entry -> Stream.of(entry.getKey(), entry.getValue())).toArray(String[]::new)
                )
            );

            // If we added users, then also add the standard test roles
            rolesFile(getBuildPluginFile("/roles.yml"));
        }
        if (roleFiles.isEmpty() == false) {
            logToProcessStdout("Setting up roles.yml");

            Path dst = configFile.getParent().resolve("roles.yml");
            roleFiles.forEach(from -> {
                if (Files.exists(from.toPath()) == false) {
                    throw new TestClustersException(
                        "Can't create roles.yml config file from " + from + " for " + this + " as it does not exist"
                    );
                }
                try {
                    final Path source = from.toPath();
                    final String content = Files.readString(source, StandardCharsets.UTF_8);
                    Files.writeString(dst, content + System.lineSeparator(), StandardCharsets.UTF_8, StandardOpenOption.APPEND);
                    LOGGER.info("Appended roles file {} to {}", source, dst);
                } catch (IOException e) {
                    throw new UncheckedIOException("Can't append roles file " + from + " to " + dst, e);
                }
            });
        }
    }

    private void installModules() {
        logToProcessStdout("Installing " + modules.size() + " modules");
        for (Provider<File> module : modules) {

View on GitHub (pinned to db6a809a66)