io.fabric8.kubernetes.api.KubernetesHelper Java Examples

The following examples show how to use io.fabric8.kubernetes.api.KubernetesHelper. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: GitUserHelper.java    From fabric8-forge with Apache License 2.0 6 votes vote down vote up
protected String getGogsURL(boolean external) {
    StopWatch watch = new StopWatch();

    String namespace = kubernetesClient.getNamespace();
    if (Strings.isNullOrEmpty(namespace)) {
        namespace = KubernetesHelper.defaultNamespace();
    }
    String serviceName = GOGS;
    String answer = KubernetesHelper.getServiceURL(kubernetesClient, serviceName, namespace, "http", external);
    if (Strings.isNullOrEmpty(answer)) {
        String kind = external ? "external" : "internal";
        throw new IllegalStateException("Could not find external URL for " + kind + " service: gogs!");
    }
    if (!external) {
        // lets stick with the service name instead as its easier to grok
        return "http://" + serviceName + "/";
    }

    LOG.info("getGogsURL took " + watch.taken());
    return answer;
}
 
Example #2
Source File: ServicesList.java    From fabric8-forge with Apache License 2.0 6 votes vote down vote up
private void printServices(ServiceList services, PrintStream out) {
    TablePrinter table = new TablePrinter();
    table.columns("id", "labels", "selector", "port");
    List<Service> items = services.getItems();
    if (items == null) {
        items = Collections.EMPTY_LIST;
    }
    Filter<Service> filter = KubernetesHelper.createServiceFilter(filterText.getValue());
    for (Service service : items) {
        if (filter.matches(service)) {
            String labels = KubernetesHelper.toLabelsString(service.getMetadata().getLabels());
            String selector = KubernetesHelper.toLabelsString(getSelector(service));
            Set<Integer> ports = getPorts(service);
            List<Integer> portList = new ArrayList<>(ports);
            String portText;
            if (portList.size() == 1) {
                portText = portList.get(0).toString();

            } else {
                portText = Strings.join(portList, ", ");
            }
            table.row(KubernetesHelper.getName(service), labels, selector, portText);
        }
    }
    table.print();
}
 
Example #3
Source File: ApplyTest.java    From kubernetes-pipeline-plugin with Apache License 2.0 6 votes vote down vote up
private void testAddRegistryToImageName(String json, String registry, String expectedImageName) throws Exception {
    Set<HasMetadata> entities = new TreeSet<>(new HasMetadataComparator());
    Object dto = KubernetesHelper.loadJson(json);

    entities.addAll(KubernetesHelper.toItemList(dto));

    ApplyStepExecution execution = new ApplyStepExecution();
    execution.addRegistryToImageNameIfNotPresent(entities, registry);

    boolean matched = false;
    for (HasMetadata entity : entities) {
        if (entity instanceof ReplicationController){
            assertEquals(expectedImageName, ((ReplicationController) entity).getSpec().getTemplate().getSpec().getContainers().get(0).getImage());
            matched = true;
        }
    }
    assertTrue(matched);
}
 
Example #4
Source File: ServiceDelete.java    From fabric8-forge with Apache License 2.0 6 votes vote down vote up
@Override
public void initializeUI(UIBuilder builder) throws Exception {
    super.initializeUI(builder);

    // populate autocompletion options
    serviceId.setCompleter(new UICompleter<String>() {
        @Override
        public Iterable<String> getCompletionProposals(UIContext context, InputComponent<?, String> input, String value) {
            List<String> list = new ArrayList<String>();
            ServiceList services = getKubernetes().services().inNamespace(getNamespace()).list();
            if (services != null) {
                List<Service> items = services.getItems();
                if (items != null) {
                    for (Service item : items) {
                        String id = KubernetesHelper.getName(item);
                        list.add(id);
                    }
                }
            }
            Collections.sort(list);
            return list;
        }
    });

    builder.add(serviceId);
}
 
Example #5
Source File: ApplyStepExecution.java    From kubernetes-pipeline-plugin with Apache License 2.0 6 votes vote down vote up
private List<HasMetadata> loadImageStreams() throws IOException, InterruptedException {
    if (kubernetes.isAdaptable(OpenShiftClient.class)) {
        FilePath child = workspace.child("target");
        if (child.exists() && child.isDirectory()) {
            List<FilePath> paths = child.list();
            if (paths != null) {
                for (FilePath path : paths) {
                    String name = path.getName();
                    if (path.exists() && !path.isDirectory() && name.endsWith("-is.yml")) {
                        try (InputStream is = path.read()) {
                            listener.getLogger().println("Loading OpenShift ImageStreams file: " + name);
                            KubernetesResource dto = KubernetesHelper.loadYaml(is, KubernetesResource.class);
                            return KubernetesHelper.toItemList(dto);
                        }
                    }
                }
            }
        }
    }
    return Collections.emptyList();
}
 
Example #6
Source File: ReplicationControllerDelete.java    From fabric8-forge with Apache License 2.0 6 votes vote down vote up
@Override
public void initializeUI(UIBuilder builder) throws Exception {
    super.initializeUI(builder);

    // populate autocompletion options
    replicationControllerId.setCompleter(new UICompleter<String>() {
        @Override
        public Iterable<String> getCompletionProposals(UIContext context, InputComponent<?, String> input, String value) {
            List<String> list = new ArrayList<String>();
            ReplicationControllerList replicationControllers = getKubernetes().replicationControllers().inNamespace(getNamespace()).list();
            if (replicationControllers != null) {
                List<ReplicationController> items = replicationControllers.getItems();
                if (items != null) {
                    for (ReplicationController item : items) {
                        String id = KubernetesHelper.getName(item);
                        list.add(id);
                    }
                }
            }
            Collections.sort(list);
            return list;
        }
    });

    builder.add(replicationControllerId);
}
 
Example #7
Source File: Agent.java    From funktion-connectors with Apache License 2.0 5 votes vote down vote up
public SubscribeResponse subscribe(SubscribeRequest request) throws InternalException {
    String namespace = request.getNamespace();
    Objects.notNull(namespace, "namespace");

    ConfigMap configMap = createSubscriptionResource(request, namespace);
    kubernetesClient.configMaps().inNamespace(namespace).create(configMap);
    return new SubscribeResponse(namespace, KubernetesHelper.getName(configMap));
}
 
Example #8
Source File: ApplyStepExecution.java    From kubernetes-pipeline-plugin with Apache License 2.0 5 votes vote down vote up
protected void addEnvironmentAnnotations(HasMetadata resource) throws AbortException {
    Map<String, String> mapEnvVarToAnnotation = new HashMap<>();
    String resourceName = "environmentAnnotations.properties";
    URL url = getClass().getResource(resourceName);
    if (url == null) {
        throw new AbortException("Cannot find resource `" + resourceName + "` on the classpath!");
    }
    addPropertiesFileToMap(url, mapEnvVarToAnnotation);
    //TODO add this in and support for non java projects
    //addPropertiesFileToMap(this.environmentVariableToAnnotationsFile, mapEnvVarToAnnotation);
    addPropertiesFileToMap(new File("./src/main/fabric8/environmentToAnnotations.properties"), mapEnvVarToAnnotation);
    Map<String, String> annotations = KubernetesHelper.getOrCreateAnnotations(resource);
    Set<Map.Entry<String, String>> entries = mapEnvVarToAnnotation.entrySet();
    for (Map.Entry<String, String> entry : entries) {
        String envVar = entry.getKey();
        String annotation = entry.getValue();
        if (Strings.isNotBlank(envVar) && Strings.isNotBlank(annotation)) {
            String value = Systems.getEnvVarOrSystemProperty(envVar);
            if (Strings.isNullOrBlank(value)) {
                value = tryDefaultAnnotationEnvVar(envVar);
            }
            if (Strings.isNotBlank(value)) {
                String oldValue = annotations.get(annotation);
                if (Strings.isNotBlank(oldValue)) {
                    listener.getLogger().println("Not adding annotation `" + annotation + "` to " + KubernetesHelper.getKind(resource) + " " + KubernetesHelper.getName(resource) + " with value `" + value + "` as there is already an annotation value of `" + oldValue + "`");
                } else {
                    annotations.put(annotation, value);
                }
            }
        }
    }
}
 
Example #9
Source File: ApplyStepExecution.java    From kubernetes-pipeline-plugin with Apache License 2.0 5 votes vote down vote up
private void addDeploymentVersion(Map<String, String> deploymentVersions, HasMetadata resource) {
    String name = KubernetesHelper.getName(resource);
    String version = KubernetesHelper.getLabels(resource).get("version");
    // TODO if there is no version label could we find it from somewhere else?
    if (Strings.isNotBlank(version)) {
        deploymentVersions.put(name, version);
    } else {
        listener.getLogger().println("No version label for  " + KubernetesHelper.getKind(resource) + " "
                + KubernetesHelper.getName(resource) + " in namespace "
                + KubernetesHelper.getNamespace(resource));
    }
}
 
Example #10
Source File: ApplyStepExecution.java    From kubernetes-pipeline-plugin with Apache License 2.0 5 votes vote down vote up
private String loadKubernetesExposeControllerCommandLine(KubernetesClient kubernetes, String commands) throws Exception {
    String namespace = System.getenv("KUBERNETES_NAMESPACE");
    if (Strings.isNullOrBlank(namespace)) {
        namespace = KubernetesHelper.getNamespace(kubernetes);
    }
    ConfigMap configMap = kubernetes.configMaps().inNamespace(namespace).withName("exposecontroller").get();
    if (configMap == null) {
        listener.getLogger().println("WARNING: no ConfigMap in namespace " + namespace + " called: exposecontroller so cannot run exposecontroller to expose Service URLs");
        return null;
    }
    String configYaml = null;
    Map<String, String> data = configMap.getData();
    if (data != null) {
        configYaml = data.get("config.yml");
    }
    if (Strings.isNullOrBlank(configYaml)) {
        throw new Exception("ConfigMap " + namespace + "/exposecontroller does not have a `config.yml` data entry");
    }
    Map map;
    try {
        map = KubernetesHelper.loadYaml(configYaml, Map.class);
    } catch (IOException e) {
        throw new Exception("Could not parse YAML in ConfigMap " + namespace + "/exposecontroller entry `config.yml`: " + e, e);
    }
    StringBuilder builder = new StringBuilder(commands);
    appendCliArgument(builder, "--http", map.get("http"));
    appendCliArgument(builder, "--exposer", map.get("exposer"));
    appendCliArgument(builder, "--domain", map.get("domain"));
    return builder.toString();
}
 
Example #11
Source File: ZipkinMinimalKubernetesTest.java    From kubernetes-zipkin with Apache License 2.0 5 votes vote down vote up
@Test
@RunAsClient
public void testConnectionToZipkinQuery() throws Exception {
    String url = URLUtils.join(KubernetesHelper.getServiceURL(service), "/api/v1/services");
    OkHttpClient httpClient = new OkHttpClient();
    try {
        Request request = new Request.Builder().get().url(url).build();
        Response response = httpClient.newCall(request).execute();
        Assert.assertNotNull(response);
        Assert.assertEquals(200, response.code());
    } finally {
        close(httpClient);
    }
}
 
Example #12
Source File: Agent.java    From funktion-connectors with Apache License 2.0 5 votes vote down vote up
public String getCurrentNamespace() {
    String answer = kubernetesClient.getNamespace();
    if (Strings.isNullOrBlank(answer)) {
        answer = KubernetesHelper.defaultNamespace();
    }
    if (Strings.isNullOrBlank(answer)) {
        answer = System.getenv("KUBERNETES_NAMESPACE");
    }
    if (Strings.isNullOrBlank(answer)) {
        answer = "default";
    }
    return answer;
}
 
Example #13
Source File: ForgeClient.java    From fabric8-forge with Apache License 2.0 5 votes vote down vote up
public String getNamespace() {
    if (Strings.isNullOrBlank(namespace)) {
        namespace = kubernetesClient.getNamespace();
    }
    if (Strings.isNullOrBlank(namespace)) {
        namespace = KubernetesHelper.defaultNamespace();
    }
    if (Strings.isNullOrBlank(namespace)) {
        namespace = "default";
    }
    return namespace;
}
 
Example #14
Source File: ReplicationControllersList.java    From fabric8-forge with Apache License 2.0 5 votes vote down vote up
private void printReplicationControllers(ReplicationControllerList replicationControllers, PrintStream out) {
    TablePrinter table = new TablePrinter();
    table.columns("id", "labels", "replicas", "replica selector");
    List<ReplicationController> items = replicationControllers.getItems();
    if (items == null) {
        items = Collections.EMPTY_LIST;
    }
    Filter<ReplicationController> filter = KubernetesHelper.createReplicationControllerFilter(filterText.getValue());
    for (ReplicationController item : items) {
        if (filter.matches(item)) {
            String id = KubernetesHelper.getName(item);
            String labels = KubernetesHelper.toLabelsString(item.getMetadata().getLabels());
            Integer replicas = null;
            ReplicationControllerSpec desiredState = item.getSpec();
            ReplicationControllerStatus currentState = item.getStatus();
            String selector = null;
            if (desiredState != null) {
                selector = KubernetesHelper.toLabelsString(desiredState.getSelector());
            }
            if (currentState != null) {
                replicas = currentState.getReplicas();
            }
            table.row(id, labels, toPositiveNonZeroText(replicas), selector);
        }
    }
    table.print();
}
 
Example #15
Source File: PodsList.java    From fabric8-forge with Apache License 2.0 5 votes vote down vote up
protected TablePrinter podsAsTable(PodList pods) {
    TablePrinter table = new TablePrinter();
    table.columns("id", "image(s)", "host", "labels", "status");
    List<Pod> items = pods.getItems();
    if (items == null) {
        items = Collections.EMPTY_LIST;
    }
    Filter<Pod> filter = KubernetesHelper.createPodFilter(filterText.getValue());
    for (Pod item : items) {
        if (filter.matches(item)) {
            String id = KubernetesHelper.getName(item);
            PodStatus podStatus = item.getStatus();
            String status = "";
            String host = "";
            if (podStatus != null) {
                status = KubernetesHelper.getStatusText(podStatus);
                host = podStatus.getHostIP();
            }
            Map<String, String> labelMap = item.getMetadata().getLabels();
            String labels = KubernetesHelper.toLabelsString(labelMap);
            PodSpec spec = item.getSpec();
            if (spec != null) {
                List<Container> containerList = spec.getContainers();
                for (Container container : containerList) {
                    String image = container.getImage();
                    table.row(id, image, host, labels, status);

                    id = "";
                    host = "";
                    status = "";
                    labels = "";
                }
            }
        }
    }
    return table;
}
 
Example #16
Source File: PodsList.java    From fabric8-forge with Apache License 2.0 5 votes vote down vote up
@Override
public Result execute(UIExecutionContext uiExecutionContext) throws Exception {
    PodList pods = getKubernetes().pods().inNamespace(getNamespace()).list();
    KubernetesHelper.removeEmptyPods(pods);
    TablePrinter table = podsAsTable(pods);
    return tableResults(table);
}
 
Example #17
Source File: AbstractPodCommand.java    From fabric8-forge with Apache License 2.0 5 votes vote down vote up
@Override
public void initializeUI(UIBuilder builder) throws Exception {
    super.initializeUI(builder);

    // populate autocompletion options
    podId.setCompleter(new UICompleter<String>() {
        @Override
        public Iterable<String> getCompletionProposals(UIContext context, InputComponent<?, String> input, String value) {
            List<String> list = new ArrayList<String>();
            PodList pods = getKubernetes().pods().list();
            if (pods != null) {
                List<Pod> items = pods.getItems();
                if (items != null) {
                    for (Pod item : items) {
                        String id = KubernetesHelper.getName(item);
                        list.add(id);
                    }
                }
            }
            Collections.sort(list);
            System.out.println("Completion list is " + list);
            return list;
        }
    });

    builder.add(podId);
}
 
Example #18
Source File: AgentKT.java    From funktion-connectors with Apache License 2.0 4 votes vote down vote up
@Test
public void testAgentSubscribe() throws Throwable {
    String elasticSearchPath = "/testagent/funktion/1";
    String namespace = agent.getCurrentNamespace();

    String elasticsearchURL = getHttpServiceURL(namespace, "elasticsearch");
    String elasticSearchResource = pathJoin(elasticsearchURL, elasticSearchPath);
    deleteResource(elasticSearchResource);

    LOG.info("Creating a subscription in namespace: " + namespace);

    String expectedName = getClass().getName();
    SamplePayload payload = new SamplePayload(expectedName, new Date());
    String body = KubernetesHelper.toJson(payload);
    LOG.info("Sending JSON: " + body);

    SubscribeResponse response = null;
    try {
        Funktion funktion = new Funktion();

        funktion.createFlow().trace(true).logResult(true).singleMessageMode(true).
                endpoint("timer://cheese?fixedRate=true&period=5000").
                setBody(body).
                endpoint("http://elasticsearch:9200" + elasticSearchPath);


        Map<String, String> applicationProperties = new HashMap<>();
        applicationProperties.put("foo", "bar");

        SubscribeRequest request = new SubscribeRequest(namespace, funktion, applicationProperties);

        LOG.info("Creating a flow");
        response = agent.subscribe(request);

        LOG.info("Created a Subscription at " + response.getNamespace() + "/" + response.getName());

        assertThat(response.getNamespace()).describedAs("namespace").isEqualTo(namespace);

        assertWaitForResults(namespace, elasticSearchResource, payload);

    } finally {
        if (response != null) {
            LOG.info("Deleting the Subscription at  " + response.getNamespace() + "/" + response.getName());
            agent.unsubscribe(response.getNamespace(), response.getName());
            LOG.info("Deleted the Subscription at  " + response.getNamespace() + "/" + response.getName());
        }
    }

}
 
Example #19
Source File: AgentKT.java    From funktion-connectors with Apache License 2.0 4 votes vote down vote up
protected String getHttpServiceURL(String namespace, String serviceName) {
    String answer = KubernetesHelper.getServiceURL(agent.getKubernetesClient(), serviceName, namespace, "http", true);
    assertThat(answer).describedAs("Could not find service URL " + namespace + "/" + serviceName).isNotEmpty();
    return answer;

}
 
Example #20
Source File: ReplicationControllerDelete.java    From fabric8-forge with Apache License 2.0 4 votes vote down vote up
protected void executeReplicationController(ReplicationController replicationController) throws Exception {
    getKubernetes().replicationControllers().inNamespace(getNamespace()).withName(KubernetesHelper.getName(replicationController)).delete();
}
 
Example #21
Source File: ServiceDelete.java    From fabric8-forge with Apache License 2.0 4 votes vote down vote up
protected void executeService(Service service) throws Exception {
    getKubernetes().services().inNamespace(getNamespace()).withName(KubernetesHelper.getName(service)).delete();
}
 
Example #22
Source File: ApplyStepExecution.java    From kubernetes-pipeline-plugin with Apache License 2.0 4 votes vote down vote up
/**
 * Tries to default some environment variables if they are not already defined.
 * <p>
 * This can happen if using Jenkins Workflow which doesn't seem to define BUILD_URL or GIT_URL for example
 *
 * @return the value of the environment variable name if it can be found or calculated
 */
protected String tryDefaultAnnotationEnvVar(String envVarName) throws AbortException {

    ProjectConfig projectConfig = getProjectConfig();
    if (projectConfig != null){
        GitConfig gitConfig = getGitConfig();
        String repoName = projectConfig.getBuildName();
        String userEnvVar = "JENKINS_GOGS_USER";
        String username = env.get(userEnvVar);

        if (io.fabric8.utils.Objects.equal("BUILD_URL", envVarName)) {
            String jobUrl = projectConfig.getLink("Job");
            if (Strings.isNullOrBlank(jobUrl)) {
                String name = projectConfig.getBuildName();
                if (Strings.isNullOrBlank(name)) {
                    // lets try deduce the jenkins build name we'll generate
                    if (Strings.isNotBlank(repoName)) {
                        name = repoName;
                        if (Strings.isNotBlank(username)) {
                            name = ProjectRepositories.createBuildName(username, repoName);
                        } else {
                            listener.getLogger().println("Cannot auto-default BUILD_URL as there is no environment variable `" + userEnvVar + "` defined so we can't guess the Jenkins build URL");
                        }
                    }
                }
                if (Strings.isNotBlank(name)) {
                    String jenkinsUrl = KubernetesHelper.getServiceURLInCurrentNamespace(getKubernetes(), ServiceNames.JENKINS, "http", null, true);
                    jobUrl = URLUtils.pathJoin(jenkinsUrl, "/job", name);

                }
            }
            if (Strings.isNotBlank(jobUrl)) {
                String buildId = env.get("BUILD_ID");
                if (Strings.isNotBlank(buildId)) {
                    jobUrl = URLUtils.pathJoin(jobUrl, buildId);
                } else {
                    listener.getLogger().println("Cannot find BUILD_ID to create a specific jenkins build URL. So using: " + jobUrl);
                }
            }
            return jobUrl;
        } else if (io.fabric8.utils.Objects.equal("GIT_URL", envVarName)) {
            String gitUrl = projectConfig.getLinks().get("Git");
            if (Strings.isNullOrBlank(gitUrl)) {
                listener.getLogger().println("No Job link found in fabric8.yml so we cannot set the GIT_URL");
            } else {
                if (gitUrl.endsWith(".git")) {
                    gitUrl = gitUrl.substring(0, gitUrl.length() - 4);
                }
                String gitCommitId = gitConfig.getCommit();
                if (Strings.isNotBlank(gitCommitId)) {
                    gitUrl = URLUtils.pathJoin(gitUrl, "commit", gitCommitId);
                }
                return gitUrl;
            }

        } else if (io.fabric8.utils.Objects.equal("GIT_COMMIT", envVarName)) {
            String gitCommit = gitConfig.getCommit();
            if (Strings.isNullOrBlank(gitCommit)) {
                listener.getLogger().println("No git commit found in git.yml so we cannot set the GIT_COMMIT");
            }
            return gitCommit;

        } else if (io.fabric8.utils.Objects.equal("GIT_BRANCH", envVarName)) {
            String gitBranch = gitConfig.getBranch();
            if (Strings.isNullOrBlank(gitBranch)) {
                listener.getLogger().println("No git branch found in git.yml so we cannot set the GIT_BRANCH");
            }
            return gitBranch;

        }
    } else {
        listener.getLogger().println("No fabric8.yml so unable to add environment pod annotations");
    }
    return null;
}
 
Example #23
Source File: ArchetypeTest.java    From ipaas-quickstarts with Apache License 2.0 4 votes vote down vote up
@AfterClass
public static void afterAll() throws Exception {
    if (failed) {
        return;
    }
    // now let invoke the projects
    final int[] resultPointer = new int[1];
    List<String> failedProjects = new ArrayList<>();
    List<String> successfulProjects = new ArrayList<>();

    for (final String outDir : outDirs) {
        // thread locals are evil (I'm talking to you - org.codehaus.plexus.DefaultPlexusContainer#lookupRealm!)
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                LOG.info("Invoking projects in " + outDir);
                System.setProperty("maven.multiModuleProjectDirectory", "$M2_HOME");
                // Dmaven.multiModuleProjectDirectory
                String[] args = {"-U", "clean", "package"};
                boolean useArq = Objects.equals(arqTesting, "true");
                if (useArq) {
                    args = new String[]{"clean", "install", "-U"};
                    String fabric8Mode = System.getProperty("fabric8.mode", "");
                    if (Strings.isNotBlank(fabric8Mode) && KubernetesHelper.isOpenShift(new DefaultKubernetesClient())) {
                        // lets add a workaround for a lack of discovery OOTB with fabric8-maven-plugin
                        args = new String[]{"clean", "install", "-U", "-Dfabric8.mode=" + fabric8Mode};
                    }
                }
                File logFile = new File(new File(outDir), "output.log");
                logFile.delete();
                resultPointer[0] = invokeMaven(args, outDir, logFile);
                LOG.info("result: " + resultPointer[0]);

                if (useArq && resultPointer[0] == 0) {
                    args = new String[]{"failsafe:integration-test", "failsafe:verify"};
                    LOG.info("Now trying to run the integration tests via: mvn " + Strings.join(" ", args));
                    resultPointer[0] = invokeMaven(args, outDir, logFile);
                    LOG.info("result: " + resultPointer[0]);
                }
            }
        });
        t.start();
        t.join();
        String projectName = new File(outDir).getName();
        if (resultPointer[0] != 0) {
            failedProjects.add(projectName);
            LOG.error("Failed project: " + projectName);
        } else {
            successfulProjects.add(projectName);
            LOG.info("Successful project: " + projectName);
        }
    }

    for (String successful : successfulProjects) {
        LOG.info("Successful project: " + successful);
    }
    for (String failedProject : failedProjects) {
        LOG.error("Failed project: " + failedProject);
    }
    assertThat(failedProjects).describedAs("Projects failed: " + failedProjects).isEmpty();
}