Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion plugins/kubernetesapi-report/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ sauron.plugins:
"THE_OUTPUT_KEY": "the.prop.key.in.the.file"
"[/path/to/file_b.env]":
"ANOTHER_OUTPUT_KEY": "the.prop.key.in.the.file"
containersCheck:
# Verify that each Kubernetes container has a valid health check path defined for at least one of the specified probes.
containerHealthCheck:
- liveness
- readiness
```
Expand All @@ -57,3 +58,5 @@ The possible selectors can be found in
- All the selector's value that can be found assigned to the specified resources
- All the environment variables and its values that were found in the running pod
- All the values found in the property files for the running pod
- All the checks performed for the container health check probes
- The `healthCheckPath` as `true` if any of the verified probes has a valid health check path defined
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.freenow.sauron.plugins;

import com.freenow.sauron.model.DataSet;
import com.freenow.sauron.plugins.readers.KubernetesContainersReader;
import com.freenow.sauron.plugins.readers.KubernetesEnvironmentVariablesReader;
import com.freenow.sauron.plugins.readers.KubernetesLabelAnnotationReader;
import com.freenow.sauron.plugins.readers.KubernetesPropertiesFilesReader;
import com.freenow.sauron.plugins.readers.KubernetesContainersReader;
import com.freenow.sauron.properties.PluginsConfigurationProperties;
import java.util.Map;
import lombok.NoArgsConstructor;
Expand All @@ -22,7 +22,7 @@ public class KubernetesApiReport implements SauronExtension
static final String SELECTORS_PROPERTY = "selectors";
static final String ENV_VARS_PROPERTY = "environmentVariablesCheck";
static final String PROPERTIES_FILES_CHECK = "propertiesFilesCheck";
static final String CONTAINERS_CHECK = "containersCheck";
static final String CONTAINER_HEALTH_CHECK = "containerHealthCheck";
static final String KUBE_CONFIG_FILE_PROPERTY = "kubeConfigFile";

private APIClientFactory apiClientFactory = new APIClientFactory();
Expand Down Expand Up @@ -64,14 +64,13 @@ public DataSet apply(PluginsConfigurationProperties properties, DataSet input)
.ifPresent(envVarsProperty -> kubernetesEnvironmentVariablesReader.read(input, serviceLabel, envVarsProperty, apiClient));

properties.getPluginConfigurationProperty(PLUGIN_ID, PROPERTIES_FILES_CHECK).filter(Map.class::isInstance)
.map(Map.class::cast)
.map(Map.class::cast)
.ifPresent(propFilesCheck -> kubernetesPropertiesFilesReader.read(input, serviceLabel, propFilesCheck, apiClient));

properties.getPluginConfigurationProperty(PLUGIN_ID, CONTAINERS_CHECK).filter(Map.class::isInstance)
properties.getPluginConfigurationProperty(PLUGIN_ID, CONTAINER_HEALTH_CHECK).filter(Map.class::isInstance)
.map(Map.class::cast)
.map(Map::values)
.ifPresent(containersCheck -> kubernetesContainersReader.read(input, serviceLabel, containersCheck, apiClient));
.ifPresent(containerHealthCheck -> kubernetesContainersReader.read(input, serviceLabel, containerHealthCheck, apiClient));
});
return input;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import com.freenow.sauron.plugins.utils.KubernetesResources;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.*;
import io.kubernetes.client.openapi.models.*;
import lombok.extern.slf4j.Slf4j;

import io.kubernetes.client.openapi.apis.AppsV1Api;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1DeploymentSpec;
import java.util.Comparator;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;

import static com.freenow.sauron.plugins.utils.KubernetesConstants.K8S_API_TIMEOUT_SECONDS;
import static com.freenow.sauron.plugins.utils.KubernetesConstants.K8S_DEFAULT_NAMESPACE;
Expand All @@ -30,7 +30,7 @@ public Optional<V1DeploymentSpec> getDeploymentSpec(String serviceLabel, Kuberne
{
String labelSelector = String.format("%s=%s", serviceLabel, service);
log.debug("Filtering deployment {} using selector {}", resource, labelSelector);
return createAppsV1Api(client).listNamespacedDeployment(
var deployments = createAppsV1Api(client).listNamespacedDeployment(
K8S_DEFAULT_NAMESPACE,
K8S_PRETTY_OUTPUT,
false,
Expand All @@ -43,7 +43,15 @@ public Optional<V1DeploymentSpec> getDeploymentSpec(String serviceLabel, Kuberne
K8S_API_TIMEOUT_SECONDS,
false
)
.getItems().stream()
.getItems();

if (deployments.isEmpty())
{
log.warn("No deployment found for service '{}' with selector '{}'", service, labelSelector);
return Optional.empty();
}

return deployments.stream()
.max(Comparator.comparing(
d -> {
if (d.getMetadata() == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@
import com.freenow.sauron.plugins.utils.RetryCommand;
import com.freenow.sauron.plugins.utils.RetryConfig;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.models.*;
import io.kubernetes.client.openapi.models.V1Container;
import io.kubernetes.client.openapi.models.V1DeploymentSpec;
import io.kubernetes.client.openapi.models.V1PodSpec;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.Objects;
import java.util.Optional;

import static com.freenow.sauron.plugins.utils.KubernetesResources.DEPLOYMENT;

@Slf4j
Expand All @@ -29,6 +30,7 @@ public class KubernetesContainersReader
private final RetryConfig retryConfig;
public static final String LIVENESS = "liveness";
public static final String READINESS = "readiness";
public static final String HAS_HEALTH_CHECK = "hasHealthCheck";

public static final Map<String, ContainerCheckStrategy> strategies = Map.of(
LIVENESS, new LivenessCheckStrategy(),
Expand All @@ -46,14 +48,20 @@ public KubernetesContainersReader()
public void read(DataSet input, String serviceLabel, Collection<String> containersCheck, ApiClient apiClient)
{
List<ContainerCheckStrategy> strategiesToApply = containersCheck.stream()
.peek(check -> {
if (!strategies.containsKey(check))
{
log.error("Missing container check implementation for: {} - proceeding with available checks...", check);
}
})
.map(strategies::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());

new RetryCommand<Void>(retryConfig).run(() ->
{
Optional<V1DeploymentSpec> deploymentSpecOpt = kubernetesGetDeploymentSpecCommand.getDeploymentSpec(
String.valueOf(serviceLabel),
serviceLabel,
DEPLOYMENT,
input.getServiceName(),
apiClient
Expand All @@ -68,7 +76,7 @@ public void read(DataSet input, String serviceLabel, Collection<String> containe
V1PodSpec podSpec = deploymentSpec.getTemplate().getSpec();
if (podSpec == null)
{
log.warn("deployment by name: {} doesn't have spec", deploymentName);
log.warn("Deployment by name: {} doesn't have spec", deploymentName);
return;
}
for (V1Container container : podSpec.getContainers())
Expand All @@ -89,7 +97,23 @@ public void read(DataSet input, String serviceLabel, Collection<String> containe
{
log.warn("Deployment not found for service: {}", input.getServiceName());
}

setHasHealthCheckPath(input, containersCheck);
return null;
});
}


/*
* Sets the hasHealthCheck flag in the dataset if any of the specified health check probes are present, giving priority to the liveness probe.
*/
private void setHasHealthCheckPath(DataSet input, final Collection<String> containersCheck)
{
boolean hasHealthCheck =
input.getBooleanAdditionalInformation(LIVENESS).orElse(false) ||
containersCheck.stream()
.anyMatch(check -> input.getBooleanAdditionalInformation(check).orElse(false));

input.setAdditionalInformation(HAS_HEALTH_CHECK, hasHealthCheck);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import com.freenow.sauron.model.DataSet;
import io.kubernetes.client.openapi.models.V1Container;

public interface ContainerCheckStrategy {
public interface ContainerCheckStrategy
{
String getName();
void check(V1Container container, DataSet input);
}


void check(V1Container container, DataSet input);
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package com.freenow.sauron.plugins.utils;

import com.freenow.sauron.model.DataSet;
import com.freenow.sauron.plugins.readers.KubernetesContainersReader;
import io.kubernetes.client.openapi.models.V1Container;

import static com.freenow.sauron.plugins.readers.KubernetesContainersReader.LIVENESS;

public class LivenessCheckStrategy implements ContainerCheckStrategy
{
@Override
public String getName() { return KubernetesContainersReader.LIVENESS; }
public String getName()
{
return LIVENESS;
}


@Override
public void check(V1Container container, DataSet input) {
public void check(V1Container container, DataSet input)
{
boolean hasLiveness = container.getLivenessProbe() != null;
input.setAdditionalInformation(getName(), String.valueOf(hasLiveness));
input.setAdditionalInformation(getName(), hasLiveness);
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
package com.freenow.sauron.plugins.utils;

import com.freenow.sauron.model.DataSet;
import com.freenow.sauron.plugins.readers.KubernetesContainersReader;
import io.kubernetes.client.openapi.models.V1Container;

import static com.freenow.sauron.plugins.readers.KubernetesContainersReader.READINESS;

public class ReadinessCheckStrategy implements ContainerCheckStrategy
{

@Override
public String getName() { return KubernetesContainersReader.READINESS; }
public String getName()
{
return READINESS;
}


@Override
public void check(V1Container container, DataSet input) {
public void check(V1Container container, DataSet input)
{
boolean hasReadiness = container.getReadinessProbe() != null;
input.setAdditionalInformation(getName(), String.valueOf(hasReadiness));
input.setAdditionalInformation(getName(), hasReadiness);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import com.freenow.sauron.plugins.readers.KubernetesContainersReader;
import com.freenow.sauron.plugins.utils.RetryConfig;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.models.V1Container;
import io.kubernetes.client.openapi.models.V1DeploymentSpec;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Container;
import io.kubernetes.client.openapi.models.V1PodSpec;
import io.kubernetes.client.openapi.models.V1PodTemplateSpec;
import io.kubernetes.client.openapi.models.V1Probe;
Expand All @@ -21,10 +21,11 @@
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import static com.freenow.sauron.plugins.readers.KubernetesContainersReader.HAS_HEALTH_CHECK;
import static com.freenow.sauron.plugins.utils.KubernetesResources.DEPLOYMENT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
Expand Down Expand Up @@ -56,8 +57,10 @@ public void deploymentIsReadyOnly()
when(kubernetesGetDeploymentSpecCommand.getDeploymentSpec(SERVICE_LABEL, DEPLOYMENT, SERVICE_NAME, apiClient)).thenReturn(deploymentData);
kubernetesContainersReader.read(input, SERVICE_LABEL, containersCheck(), apiClient);
assertNotNull(input);
assertEquals("true", input.getStringAdditionalInformation(KubernetesContainersReader.READINESS).get());
assertEquals("false", input.getStringAdditionalInformation(KubernetesContainersReader.LIVENESS).get());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.READINESS).isPresent());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.LIVENESS).isPresent());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.READINESS).get());
assertFalse(input.getBooleanAdditionalInformation(KubernetesContainersReader.LIVENESS).get());
}


Expand All @@ -69,8 +72,10 @@ public void deploymentIsLiveOnly()
when(kubernetesGetDeploymentSpecCommand.getDeploymentSpec(SERVICE_LABEL, DEPLOYMENT, SERVICE_NAME, apiClient)).thenReturn(deploymentData);
kubernetesContainersReader.read(input, SERVICE_LABEL, containersCheck(), apiClient);
assertNotNull(input);
assertEquals("false", input.getStringAdditionalInformation(KubernetesContainersReader.READINESS).get());
assertEquals("true", input.getStringAdditionalInformation(KubernetesContainersReader.LIVENESS).get());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.READINESS).isPresent());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.LIVENESS).isPresent());
assertFalse(input.getBooleanAdditionalInformation(KubernetesContainersReader.READINESS).get());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.LIVENESS).get());
}


Expand All @@ -83,8 +88,10 @@ public void deploymentIsLiveAndReady()

kubernetesContainersReader.read(input, SERVICE_LABEL, containersCheck(), apiClient);
assertNotNull(input);
assertEquals("true", input.getStringAdditionalInformation(KubernetesContainersReader.READINESS).get());
assertEquals("true", input.getStringAdditionalInformation(KubernetesContainersReader.LIVENESS).get());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.READINESS).isPresent());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.LIVENESS).isPresent());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.READINESS).get());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.LIVENESS).get());
}


Expand All @@ -96,8 +103,10 @@ public void deploymentIsNotReady()
when(kubernetesGetDeploymentSpecCommand.getDeploymentSpec(SERVICE_LABEL, DEPLOYMENT, SERVICE_NAME, apiClient)).thenReturn(deploymentData);
kubernetesContainersReader.read(input, SERVICE_LABEL, containersCheck(), apiClient);
assertNotNull(input);
assertEquals("false", input.getStringAdditionalInformation(KubernetesContainersReader.READINESS).get());
assertEquals("false", input.getStringAdditionalInformation(KubernetesContainersReader.LIVENESS).get());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.READINESS).isPresent());
assertTrue(input.getBooleanAdditionalInformation(KubernetesContainersReader.LIVENESS).isPresent());
assertFalse(input.getBooleanAdditionalInformation(KubernetesContainersReader.READINESS).get());
assertFalse(input.getBooleanAdditionalInformation(KubernetesContainersReader.LIVENESS).get());
}


Expand Down Expand Up @@ -136,6 +145,33 @@ public void containersReadinessIsNotRequested()
}


@Test
public void hasContainerHealthCheck()
{
final var input = dummyDataSet();
final var deploymentData = createDeploymentData(true, true, false);
when(kubernetesGetDeploymentSpecCommand.getDeploymentSpec(SERVICE_LABEL, DEPLOYMENT, SERVICE_NAME, apiClient)).thenReturn(deploymentData);

kubernetesContainersReader.read(input, SERVICE_LABEL, List.of("liveness", "readiness"), apiClient);

assertTrue(input.getBooleanAdditionalInformation(HAS_HEALTH_CHECK).isPresent());
assertTrue(input.getBooleanAdditionalInformation(HAS_HEALTH_CHECK).get());
}


@Test
public void hasNoContainerHealthCheck()
{
final var input = dummyDataSet();
final var deploymentData = createDeploymentData(false, false, false);
when(kubernetesGetDeploymentSpecCommand.getDeploymentSpec(SERVICE_LABEL, DEPLOYMENT, SERVICE_NAME, apiClient)).thenReturn(deploymentData);

kubernetesContainersReader.read(input, SERVICE_LABEL, List.of("liveness", "readiness", "Startup"), apiClient);
assertTrue(input.getBooleanAdditionalInformation(HAS_HEALTH_CHECK).isPresent());
assertFalse(input.getBooleanAdditionalInformation(HAS_HEALTH_CHECK).get());
}


private Optional<V1DeploymentSpec> createDeploymentData(boolean readinessProbe, boolean livenessProbe, boolean isNullSpec)
{
V1Container container = new V1Container().name("test-container")
Expand Down