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
3 changes: 3 additions & 0 deletions plugins/kubernetesapi-report/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ 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:
- liveness
- readiness
```

The possible selectors can be found in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
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 @@ -21,24 +22,28 @@ 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 KUBE_CONFIG_FILE_PROPERTY = "kubeConfigFile";

private APIClientFactory apiClientFactory = new APIClientFactory();
private KubernetesLabelAnnotationReader kubernetesLabelAnnotationReader = new KubernetesLabelAnnotationReader();
private KubernetesEnvironmentVariablesReader kubernetesEnvironmentVariablesReader = new KubernetesEnvironmentVariablesReader();
private KubernetesPropertiesFilesReader kubernetesPropertiesFilesReader = new KubernetesPropertiesFilesReader();
private KubernetesContainersReader kubernetesContainersReader = new KubernetesContainersReader();


public KubernetesApiReport(
final APIClientFactory apiClientFactory,
final KubernetesLabelAnnotationReader kubernetesLabelAnnotationReader,
final KubernetesEnvironmentVariablesReader kubernetesEnvironmentVariablesReader,
final KubernetesPropertiesFilesReader kubernetesPropertiesFilesReader)
final KubernetesPropertiesFilesReader kubernetesPropertiesFilesReader,
final KubernetesContainersReader kubernetesContainersReader)
{
this.apiClientFactory = apiClientFactory;
this.kubernetesLabelAnnotationReader = kubernetesLabelAnnotationReader;
this.kubernetesEnvironmentVariablesReader = kubernetesEnvironmentVariablesReader;
this.kubernetesPropertiesFilesReader = kubernetesPropertiesFilesReader;
this.kubernetesContainersReader = kubernetesContainersReader;
}


Expand All @@ -62,6 +67,11 @@ public DataSet apply(PluginsConfigurationProperties properties, DataSet input)
.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)
.map(Map.class::cast)
.map(Map::values)
.ifPresent(containersCheck -> kubernetesContainersReader.read(input, serviceLabel, containersCheck, apiClient));
});
return input;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.freenow.sauron.plugins.commands;

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 java.util.Comparator;
import java.util.Optional;

import static com.freenow.sauron.plugins.utils.KubernetesConstants.K8S_API_TIMEOUT_SECONDS;
import static com.freenow.sauron.plugins.utils.KubernetesConstants.K8S_DEFAULT_NAMESPACE;
import static com.freenow.sauron.plugins.utils.KubernetesConstants.K8S_PRETTY_OUTPUT;

@Slf4j
public class KubernetesGetDeploymentSpecCommand
{
//defined to let us override it in the tests and inject a mock
protected AppsV1Api createAppsV1Api(ApiClient client)
{
return new AppsV1Api(client);
}


public Optional<V1DeploymentSpec> getDeploymentSpec(String serviceLabel, KubernetesResources resource, String service, ApiClient client)
{
try
{
String labelSelector = String.format("%s=%s", serviceLabel, service);
log.debug("Filtering deployment {} using selector {}", resource, labelSelector);
return createAppsV1Api(client).listNamespacedDeployment(
K8S_DEFAULT_NAMESPACE,
K8S_PRETTY_OUTPUT,
false,
null,
null,
labelSelector,
null,
null,
null,
K8S_API_TIMEOUT_SECONDS,
false
)
.getItems().stream()
.max(Comparator.comparing(
d -> {
if (d.getMetadata() == null)
{
return null;
}
return d.getMetadata().getCreationTimestamp();
}, Comparator.nullsLast(Comparator.naturalOrder())
))
.map(V1Deployment::getSpec);
}
catch (ApiException ex)
{
log.error(ex.getMessage(), ex);
}

return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.freenow.sauron.plugins.readers;

import com.freenow.sauron.model.DataSet;
import com.freenow.sauron.plugins.commands.KubernetesGetDeploymentSpecCommand;
import com.freenow.sauron.plugins.utils.ContainerCheckStrategy;
import com.freenow.sauron.plugins.utils.LivenessCheckStrategy;
import com.freenow.sauron.plugins.utils.ReadinessCheckStrategy;
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 java.util.Collection;
import java.util.List;
import java.util.Map;
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
@RequiredArgsConstructor
public class KubernetesContainersReader
{
private final KubernetesGetDeploymentSpecCommand kubernetesGetDeploymentSpecCommand;
private final RetryConfig retryConfig;
public static final String LIVENESS = "liveness";
public static final String READINESS = "readiness";

public static final Map<String, ContainerCheckStrategy> strategies = Map.of(
LIVENESS, new LivenessCheckStrategy(),
READINESS, new ReadinessCheckStrategy()
);


public KubernetesContainersReader()
{
this.kubernetesGetDeploymentSpecCommand = new KubernetesGetDeploymentSpecCommand();
this.retryConfig = new RetryConfig();
}


public void read(DataSet input, String serviceLabel, Collection<String> containersCheck, ApiClient apiClient)
{
List<ContainerCheckStrategy> strategiesToApply = containersCheck.stream()
.map(strategies::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());

new RetryCommand<Void>(retryConfig).run(() ->
{
Optional<V1DeploymentSpec> deploymentSpecOpt = kubernetesGetDeploymentSpecCommand.getDeploymentSpec(
String.valueOf(serviceLabel),
DEPLOYMENT,
input.getServiceName(),
apiClient
);

if (deploymentSpecOpt.isPresent())
{
final String deploymentName = Objects.requireNonNull(deploymentSpecOpt.get().getTemplate().getMetadata()).getName();
try
{
deploymentSpecOpt.ifPresent(deploymentSpec -> {
V1PodSpec podSpec = deploymentSpec.getTemplate().getSpec();
if (podSpec == null)
{
log.warn("deployment by name: {} doesn't have spec", deploymentName);
return;
}
for (V1Container container : podSpec.getContainers())
{
for (ContainerCheckStrategy strategy : strategiesToApply)
{
strategy.check(container, input);
}
}
});
}
catch (Exception e)
{
log.warn("Failed to fetch deployment by name: {}", deploymentName, e);
}
}
else
{
log.warn("Deployment not found for service: {}", input.getServiceName());
}
return null;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.freenow.sauron.plugins.utils;

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

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


Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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;

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

@Override
public void check(V1Container container, DataSet input) {
boolean hasLiveness = container.getLivenessProbe() != null;
input.setAdditionalInformation(getName(), String.valueOf(hasLiveness));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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;

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

@Override
public void check(V1Container container, DataSet input) {
boolean hasReadiness = container.getReadinessProbe() != null;
input.setAdditionalInformation(getName(), String.valueOf(hasReadiness));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ private PluginsConfigurationProperties pluginConfig()
props.put("serviceLabel", SERVICE_LABEL);
props.put("selectors", dummySelectors());
props.put("environmentVariablesCheck", Map.of("0", "ENV_ENABLED", "1", "ENV_VERSION"));
props.put("apiClientConfig", Map.of(
"default", "",
"cluster-a", "https://kubernetes.cluster-a.com",
"cluster-b", "https://kubernetes.cluster-b.com"
));
props.put(
"apiClientConfig", Map.of(
"default", "",
"cluster-a", "https://kubernetes.cluster-a.com",
"cluster-b", "https://kubernetes.cluster-b.com"
));

properties.put(PLUGIN_ID, props);
return properties;
Expand Down
Loading