Skip to content

Commit

Permalink
java rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
Mehdi BEN ABDALLAH authored and mbenabda committed Jan 15, 2018
1 parent aec811f commit 7fd04f7
Show file tree
Hide file tree
Showing 35 changed files with 1,221 additions and 676 deletions.
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,38 @@ typings/
# dotenv environment variables file
.env


# IntelliJ

.idea/
*.iws
*.iml
*.ipr
/out/
.idea_modules/

.settings/

### Java ###
*.class

# Package Files #
*.jar
*.war
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
17 changes: 13 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
FROM node:boron-alpine
FROM openjdk:8-jre-alpine

ADD src/*.js* ./
ARG GIT_REPOSITORY=
ARG GIT_BRANCH_NAME=
ARG GIT_COMMIT_ID=
ARG ARTIFACT_VERSION=1.0-SNAPSHOT

RUN npm install
ADD dashboards-controller/target/dashboards-controller-$ARTIFACT_VERSION.jar ./app.jar

ENTRYPOINT [ "node", "index.js" ]
ENTRYPOINT [ "java", "-jar", "app.jar" ]

LABEL GIT_REPOSITORY=$GIT_REPOSITORY \
GIT_BRANCH_NAME=$GIT_BRANCH_NAME \
GIT_COMMIT_ID=$GIT_COMMIT_ID \
ARTIFACT_VERSION=$ARTIFACT_VERSION

30 changes: 8 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ Kubernetes controller that watches dashboard configurations defined as configmap

Kubernetes
=======
- Access to the API is expected to be granted by the `ServiceAccount` attached to the pod running the controller's container.
- You can use kube-api compatible [labelSelector](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors)s to select the configmaps to watch for dashboard descriptions.
- Access to the Kubernetes API is expected to be granted by the local kubeconfig file.
- You can use equality-based [labelSelector](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#equality-based-requirement)s to select the configmaps to watch for dashboard descriptions.

Grafana
=======
- The controller supports either `api key` or `basic auth`
- The controller requires access to the Grafana API, either using an `api key` or `basic auth`


Configuration
=======

Configuration can be specified in 2 ways:
- using command line arguments (use `--help` to see the complete usage instructions)
- using environment variables:

Env Variable | | Description | Default | Example
--- | --- | --- | --- | ---
`CONFIGMAP_SELECTOR` | `optional` | kube-api compatible [labelSelector](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) | `""` | `"role=grafana-dashboard,app=awesome-app"`
Expand All @@ -31,25 +36,6 @@ To update a managed dashboard's title:
- change the title in the configmap's dashboard json desciption
- apply the configmap manifest


TODO
=======
- fix dashboard update
- better logs (timestamp, levels, no password)
- parse args
- k8s:
`--kubeconfig`,
`--cluster`,
`--context`,
`--selector`,
`--namespace`,
`--all-namespaces`
- grafana (api url + auth)
- unit tests
- garbage collection
- rewrite in Go ?


Contributing
========
Contributions are welcome !
96 changes: 96 additions & 0 deletions dashboards-controller/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>grafana-dashboards-controller</artifactId>
<groupId>com.mbenabda.kubernetes</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<groupId>com.mbenabda.kubernetes.controllers.grafana.dashboards</groupId>
<artifactId>dashboards-controller</artifactId>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>3.0.3</version>
</dependency>

<dependency>
<groupId>com.mbenabda.grafana</groupId>
<artifactId>api-client</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>1.72</version>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.13.0</version>
</dependency>

</dependencies>


<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>com.mbenabda.kubernetes.controllers.grafana.dashboards.Main</Main-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.mbenabda.kubernetes.controllers.grafana.dashboards;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.mbenabda.grafana.client.GrafanaClient;
import com.mbenabda.grafana.client.exceptions.GrafanaException;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watcher;

import java.io.IOException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.lang.String.format;

class ConfigMapsWatcher implements Watcher<ConfigMap> {
private static final Joiner KEY_PARTS_JOINER = Joiner.on("/");
private final GrafanaClient grafana;
private final ObjectMapper mapper;
private static final Logger LOGGER = Logger.getLogger(ConfigMapsWatcher.class.getSimpleName());

public ConfigMapsWatcher(GrafanaClient grafana) {
this.grafana = grafana;
this.mapper =
new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}

@Override
public void eventReceived(Action action, ConfigMap configMap) {
try {
switch (action) {
case ADDED: {
JsonNode dashboard = asDashboard(configMap);
grafana.importDashboard(dashboard);
LOGGER.info(format("ConfigMap %s was created. Dashboard %s has been added to Grafana", key(configMap), title(dashboard)));
}
break;

case MODIFIED: {
JsonNode dashboard = asDashboard(configMap);
String title = title(dashboard);
String slug = grafana.slug(title);
grafana.deleteDashboard(slug);
grafana.importDashboard(dashboard);
LOGGER.info(format("ConfigMap %s was modified. Dashboard %s updated accordingly", key(configMap), title));
}
break;

case DELETED: {
JsonNode dashboard = asDashboard(configMap);
String title = title(dashboard);
String slug = grafana.slug(title);
grafana.deleteDashboard(slug);
LOGGER.info(format("ConfigMap %s was deleted. Dashboard %s removed from Grafana", key(configMap), title));
}
break;
}
} catch (GrafanaException | IOException e) {
LOGGER.warning(
format("Unable to handle %s event on ConfigMap %s because %s", action, key(configMap), e.getMessage())
);
}
}

private String key(ConfigMap configMap) {
ObjectMeta metadata = configMap.getMetadata();

return KEY_PARTS_JOINER.join(
metadata.getNamespace(),
metadata.getName()
);
}

@Override
public void onClose(KubernetesClientException e) {
if (e != null) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
}

private JsonNode asDashboard(ConfigMap configMap) throws IOException {
return mapper.readTree(dashboardJson(configMap));
}

private String dashboardJson(ConfigMap configMap) {
return valueOfFirstKey(configMap.getData());
}

private static <K, V> V valueOfFirstKey(Map<K, V> data) {
return data.entrySet().iterator().next().getValue();
}

private static String title(JsonNode dashboard) {
return dashboard.get("dashboard").get("title").asText();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.mbenabda.kubernetes.controllers.grafana.dashboards;

import com.mbenabda.grafana.client.GrafanaClient;
import com.mbenabda.kubernetes.controllers.grafana.dashboards.configuration.DashboardsWatchOptions;
import io.fabric8.kubernetes.client.KubernetesClient;

import java.util.logging.Logger;

public class Controller {
private static final Logger LOGGER = Logger.getLogger(Controller.class.getSimpleName());

private final DashboardsWatchOptions configMapsFiler;
private final GrafanaClient grafana;
private final KubernetesClient k8s;

public Controller(KubernetesClient k8s, DashboardsWatchOptions configMapsFiler, GrafanaClient grafana) {
this.k8s = k8s;
this.configMapsFiler = configMapsFiler;
this.grafana = grafana;
}

public void run() {
configMapsFiler
.with(k8s)
.watch(
new ConfigMapsWatcher(
grafana
)
);
}
}
Loading

0 comments on commit 7fd04f7

Please sign in to comment.