diff --git a/deploy/examples/sample-aiprovider-ollama.yaml b/deploy/examples/sample-aiprovider-ollama.yaml
new file mode 100644
index 0000000..85887af
--- /dev/null
+++ b/deploy/examples/sample-aiprovider-ollama.yaml
@@ -0,0 +1,18 @@
+---
+apiVersion: podmortem.redhat.com/v1alpha1
+kind: AIProvider
+metadata:
+ name: ollama-local
+ namespace: podmortem-system
+spec:
+ providerId: "ollama"
+ apiUrl: "http://ollama-service.ai-models.svc.cluster.local:11434"
+ modelId: "mistral:7b"
+ timeoutSeconds: 60
+ maxRetries: 2
+ cachingEnabled: true
+ temperature: 0.3
+ maxTokens: 500
+ additionalConfig:
+ stream: "false"
+ num_predict: "500"
diff --git a/deploy/examples/sample-podmortem.yaml b/deploy/examples/sample-podmortem.yaml
new file mode 100644
index 0000000..946017c
--- /dev/null
+++ b/deploy/examples/sample-podmortem.yaml
@@ -0,0 +1,14 @@
+---
+apiVersion: podmortem.redhat.com/v1alpha1
+kind: Podmortem
+metadata:
+ name: monitor-my-apps
+ namespace: default
+spec:
+ podSelector:
+ matchLabels:
+ app: "my-application"
+ aiProviderRef:
+ name: "ollama-local"
+ namespace: "podmortem-system"
+ aiAnalysisEnabled: true
diff --git a/pom.xml b/pom.xml
index de43291..c185f4d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,7 +16,7 @@
true
3.5.3
- 1.0-f094e1c-SNAPSHOT
+ 1.0-1db4e13-SNAPSHOT
@@ -68,6 +68,14 @@
io.quarkus
quarkus-kubernetes-client
+
+ io.quarkus
+ quarkus-rest-client
+
+
+ io.quarkus
+ quarkus-rest-client-jackson
+
com.redhat.podmortem
common
diff --git a/src/main/java/com/redhat/podmortem/operator/reconcile/PodmortemReconciler.java b/src/main/java/com/redhat/podmortem/operator/reconcile/PodmortemReconciler.java
deleted file mode 100644
index 558835d..0000000
--- a/src/main/java/com/redhat/podmortem/operator/reconcile/PodmortemReconciler.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.redhat.podmortem.operator.reconcile;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.redhat.podmortem.common.model.kube.podmortem.PodFailureData;
-import com.redhat.podmortem.common.model.kube.podmortem.Podmortem;
-import io.fabric8.kubernetes.api.model.Pod;
-import io.fabric8.kubernetes.api.model.events.v1.Event;
-import io.fabric8.kubernetes.client.KubernetesClient;
-import io.javaoperatorsdk.operator.api.reconciler.Context;
-import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
-import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
-import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
-import java.util.List;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@ControllerConfiguration
-public class PodmortemReconciler implements Reconciler {
-
- private static final Logger log = LoggerFactory.getLogger(PodmortemReconciler.class);
- private final KubernetesClient client;
- private final ObjectMapper objectMapper;
-
- public PodmortemReconciler(KubernetesClient client) {
- this.client = client;
- this.objectMapper = new ObjectMapper();
- this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
- }
-
- @Override
- public UpdateControl reconcile(Podmortem resource, Context context) {
- log.info("Reconciling PodmortemConfig: {}", resource.getMetadata().getName());
-
- List pods =
- client.pods()
- .inAnyNamespace()
- .withLabelSelector(resource.getSpec().getPodSelector())
- .list()
- .getItems();
-
- for (Pod pod : pods) {
- pod.getStatus()
- .getContainerStatuses()
- .forEach(
- containerStatus -> {
- if (containerStatus.getState().getTerminated() != null
- && containerStatus.getState().getTerminated().getExitCode()
- != 0) {
-
- log.info(
- "Pod has a container with an ungraceful exit",
- "pod",
- pod.getMetadata().getName(),
- "container",
- containerStatus.getName(),
- "exitCode",
- containerStatus
- .getState()
- .getTerminated()
- .getExitCode());
-
- // get Logs
- String podLogs =
- client.pods()
- .inNamespace(pod.getMetadata().getNamespace())
- .withName(pod.getMetadata().getName())
- .getLog();
-
- // get Events for the Pod
- List events =
- client.events()
- .v1()
- .events()
- .inNamespace(pod.getMetadata().getNamespace())
- .withField(
- "involvedObject.name",
- pod.getMetadata().getName())
- .list()
- .getItems();
-
- // assemble the failure data
- PodFailureData failureData =
- new PodFailureData(pod, podLogs, events);
-
- // marshal data to JSON and log it
- try {
- String jsonData =
- objectMapper.writeValueAsString(failureData);
- log.info("Collected pod failure data:\n{}", jsonData);
- } catch (JsonProcessingException e) {
- log.error(
- "Failed to marshal failure data to JSON for pod {}",
- pod.getMetadata().getName(),
- e);
- }
- }
- });
- }
- return UpdateControl.noUpdate();
- }
-}
diff --git a/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceClient.java b/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceClient.java
new file mode 100644
index 0000000..56fd24d
--- /dev/null
+++ b/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceClient.java
@@ -0,0 +1,30 @@
+package com.redhat.podmortem.operator.service;
+
+import com.redhat.podmortem.common.model.analysis.AnalysisResult;
+import com.redhat.podmortem.common.model.kube.aiprovider.AIProvider;
+import com.redhat.podmortem.common.model.provider.AIResponse;
+import io.smallrye.mutiny.Uni;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ApplicationScoped
+public class AIInterfaceClient {
+
+ private static final Logger log = LoggerFactory.getLogger(AIInterfaceClient.class);
+
+ @Inject @RestClient AIInterfaceRestClient restClient;
+
+ public Uni generateExplanation(
+ AnalysisResult analysisResult, AIProvider aiProvider) {
+ log.debug("Sending AI explanation request for analysis");
+ return restClient
+ .generateExplanation(analysisResult, aiProvider)
+ .onItem()
+ .invoke(response -> log.debug("Received AI explanation response"))
+ .onFailure()
+ .invoke(throwable -> log.error("AI explanation generation failed", throwable));
+ }
+}
diff --git a/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceRestClient.java b/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceRestClient.java
new file mode 100644
index 0000000..8e277d7
--- /dev/null
+++ b/src/main/java/com/redhat/podmortem/operator/service/AIInterfaceRestClient.java
@@ -0,0 +1,25 @@
+package com.redhat.podmortem.operator.service;
+
+import com.redhat.podmortem.common.model.analysis.AnalysisResult;
+import com.redhat.podmortem.common.model.kube.aiprovider.AIProvider;
+import com.redhat.podmortem.common.model.provider.AIResponse;
+import io.smallrye.mutiny.Uni;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@ApplicationScoped
+@RegisterRestClient(configKey = "ai-interface")
+@Path("/api/v1")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface AIInterfaceRestClient {
+
+ @POST
+ @Path("/analyze")
+ Uni generateExplanation(AnalysisResult analysisResult, AIProvider aiProvider);
+}
diff --git a/src/main/java/com/redhat/podmortem/operator/service/LogParserClient.java b/src/main/java/com/redhat/podmortem/operator/service/LogParserClient.java
new file mode 100644
index 0000000..291513f
--- /dev/null
+++ b/src/main/java/com/redhat/podmortem/operator/service/LogParserClient.java
@@ -0,0 +1,39 @@
+package com.redhat.podmortem.operator.service;
+
+import com.redhat.podmortem.common.model.analysis.AnalysisResult;
+import com.redhat.podmortem.common.model.kube.podmortem.PodFailureData;
+import io.smallrye.mutiny.Uni;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ApplicationScoped
+public class LogParserClient {
+
+ private static final Logger log = LoggerFactory.getLogger(LogParserClient.class);
+
+ @Inject @RestClient LogParserRestClient restClient;
+
+ public Uni analyzeLog(PodFailureData failureData) {
+ log.debug(
+ "Sending log analysis request for pod: {}",
+ failureData.getPod().getMetadata().getName());
+ return restClient
+ .parseLogs(failureData)
+ .onItem()
+ .invoke(
+ result ->
+ log.debug(
+ "Received analysis result for pod: {}",
+ failureData.getPod().getMetadata().getName()))
+ .onFailure()
+ .invoke(
+ throwable ->
+ log.error(
+ "Log analysis failed for pod: {}",
+ failureData.getPod().getMetadata().getName(),
+ throwable));
+ }
+}
diff --git a/src/main/java/com/redhat/podmortem/operator/service/LogParserRestClient.java b/src/main/java/com/redhat/podmortem/operator/service/LogParserRestClient.java
new file mode 100644
index 0000000..c4174bb
--- /dev/null
+++ b/src/main/java/com/redhat/podmortem/operator/service/LogParserRestClient.java
@@ -0,0 +1,24 @@
+package com.redhat.podmortem.operator.service;
+
+import com.redhat.podmortem.common.model.analysis.AnalysisResult;
+import com.redhat.podmortem.common.model.kube.podmortem.PodFailureData;
+import io.smallrye.mutiny.Uni;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+@ApplicationScoped
+@RegisterRestClient(configKey = "log-parser")
+@Path("/")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface LogParserRestClient {
+
+ @POST
+ @Path("/parse")
+ Uni parseLogs(PodFailureData failureData);
+}
diff --git a/src/main/java/com/redhat/podmortem/reconcile/config/PodmortemReconciler.java b/src/main/java/com/redhat/podmortem/reconcile/config/PodmortemReconciler.java
new file mode 100644
index 0000000..a12e947
--- /dev/null
+++ b/src/main/java/com/redhat/podmortem/reconcile/config/PodmortemReconciler.java
@@ -0,0 +1,247 @@
+package com.redhat.podmortem.reconcile.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.redhat.podmortem.common.model.analysis.AnalysisResult;
+import com.redhat.podmortem.common.model.kube.aiprovider.AIProvider;
+import com.redhat.podmortem.common.model.kube.podmortem.PodFailureData;
+import com.redhat.podmortem.common.model.kube.podmortem.Podmortem;
+import com.redhat.podmortem.common.model.kube.podmortem.PodmortemStatus;
+import com.redhat.podmortem.operator.service.AIInterfaceClient;
+import com.redhat.podmortem.operator.service.LogParserClient;
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.events.v1.Event;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
+import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import java.time.Instant;
+import java.util.List;
+import java.util.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ControllerConfiguration
+@ApplicationScoped
+public class PodmortemReconciler implements Reconciler {
+
+ private static final Logger log = LoggerFactory.getLogger(PodmortemReconciler.class);
+
+ @Inject KubernetesClient client;
+
+ @Inject LogParserClient logParserClient;
+
+ @Inject AIInterfaceClient aiInterfaceClient;
+
+ private final ObjectMapper objectMapper;
+
+ public PodmortemReconciler() {
+ this.objectMapper = new ObjectMapper();
+ this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
+ }
+
+ @Override
+ public UpdateControl reconcile(Podmortem resource, Context context) {
+ log.info("Reconciling Podmortem: {}", resource.getMetadata().getName());
+
+ // Initialize status if not present
+ if (resource.getStatus() == null) {
+ resource.setStatus(new PodmortemStatus());
+ }
+
+ try {
+ // Find pods matching the selector
+ List pods = findMatchingPods(resource);
+
+ // Process each pod for failures
+ for (Pod pod : pods) {
+ if (hasPodFailed(pod)) {
+ processPodFailure(resource, pod);
+ }
+ }
+
+ // Update status
+ updatePodmortemStatus(resource, "Ready", "Monitoring pods for failures");
+
+ return UpdateControl.patchStatus(resource);
+
+ } catch (Exception e) {
+ log.error("Error reconciling Podmortem: {}", resource.getMetadata().getName(), e);
+ updatePodmortemStatus(resource, "Error", "Failed to reconcile: " + e.getMessage());
+ return UpdateControl.patchStatus(resource);
+ }
+ }
+
+ private List findMatchingPods(Podmortem resource) {
+ return client.pods()
+ .inAnyNamespace()
+ .withLabelSelector(resource.getSpec().getPodSelector())
+ .list()
+ .getItems();
+ }
+
+ private boolean hasPodFailed(Pod pod) {
+ return pod.getStatus().getContainerStatuses().stream()
+ .anyMatch(
+ containerStatus ->
+ containerStatus.getState().getTerminated() != null
+ && containerStatus.getState().getTerminated().getExitCode()
+ != 0);
+ }
+
+ private void processPodFailure(Podmortem resource, Pod pod) {
+ log.info("Processing pod failure for pod: {}", pod.getMetadata().getName());
+
+ try {
+ // Collect failure data
+ PodFailureData failureData = collectPodFailureData(pod);
+
+ // Send to log parser for analysis
+ logParserClient
+ .analyzeLog(failureData)
+ .subscribe()
+ .with(
+ analysisResult -> {
+ log.info(
+ "Log analysis completed for pod: {}",
+ pod.getMetadata().getName());
+ handleAnalysisResult(resource, pod, analysisResult);
+ },
+ failure -> {
+ log.error(
+ "Log analysis failed for pod: {}",
+ pod.getMetadata().getName(),
+ failure);
+ updatePodFailureStatus(
+ resource, pod, "Analysis failed: " + failure.getMessage());
+ });
+
+ } catch (Exception e) {
+ log.error("Error processing pod failure for pod: {}", pod.getMetadata().getName(), e);
+ updatePodFailureStatus(resource, pod, "Processing failed: " + e.getMessage());
+ }
+ }
+
+ private PodFailureData collectPodFailureData(Pod pod) {
+ // Get pod logs
+ String podLogs =
+ client.pods()
+ .inNamespace(pod.getMetadata().getNamespace())
+ .withName(pod.getMetadata().getName())
+ .getLog();
+
+ // Get events for the pod
+ List events =
+ client.events()
+ .v1()
+ .events()
+ .inNamespace(pod.getMetadata().getNamespace())
+ .withField("involvedObject.name", pod.getMetadata().getName())
+ .list()
+ .getItems();
+
+ return new PodFailureData(pod, podLogs, events);
+ }
+
+ private void handleAnalysisResult(Podmortem resource, Pod pod, AnalysisResult analysisResult) {
+ log.info("Handling analysis result for pod: {}", pod.getMetadata().getName());
+
+ // Check if AI analysis is enabled and AI provider is configured
+ if (Boolean.TRUE.equals(resource.getSpec().getAiAnalysisEnabled())
+ && resource.getSpec().getAiProviderRef() != null) {
+
+ // Get AI provider
+ Optional aiProvider = getAIProvider(resource);
+
+ if (aiProvider.isPresent()) {
+ // Send to AI interface for explanation
+ aiInterfaceClient
+ .generateExplanation(analysisResult, aiProvider.get())
+ .subscribe()
+ .with(
+ aiResponse -> {
+ log.info(
+ "AI analysis completed for pod: {}",
+ pod.getMetadata().getName());
+ updatePodFailureStatus(
+ resource,
+ pod,
+ "Analysis completed with AI explanation: "
+ + aiResponse.getExplanation());
+ },
+ failure -> {
+ log.error(
+ "AI analysis failed for pod: {}",
+ pod.getMetadata().getName(),
+ failure);
+ updatePodFailureStatus(
+ resource,
+ pod,
+ "Pattern analysis completed, AI analysis failed: "
+ + failure.getMessage());
+ });
+ } else {
+ log.warn(
+ "AI provider not found for Podmortem: {}",
+ resource.getMetadata().getName());
+ updatePodFailureStatus(
+ resource, pod, "Pattern analysis completed, AI provider not found");
+ }
+ } else {
+ // Only pattern analysis, no AI
+ updatePodFailureStatus(resource, pod, "Pattern analysis completed");
+ }
+ }
+
+ private Optional getAIProvider(Podmortem resource) {
+ if (resource.getSpec().getAiProviderRef() == null) {
+ return Optional.empty();
+ }
+
+ String providerName = resource.getSpec().getAiProviderRef().getName();
+ String providerNamespace = resource.getSpec().getAiProviderRef().getNamespace();
+
+ // Default to same namespace if not specified
+ if (providerNamespace == null) {
+ providerNamespace = resource.getMetadata().getNamespace();
+ }
+
+ try {
+ AIProvider aiProvider =
+ client.resources(AIProvider.class)
+ .inNamespace(providerNamespace)
+ .withName(providerName)
+ .get();
+
+ return Optional.ofNullable(aiProvider);
+ } catch (Exception e) {
+ log.error("Error fetching AI provider: {}/{}", providerNamespace, providerName, e);
+ return Optional.empty();
+ }
+ }
+
+ private void updatePodmortemStatus(Podmortem resource, String phase, String message) {
+ PodmortemStatus status = resource.getStatus();
+ if (status == null) {
+ status = new PodmortemStatus();
+ resource.setStatus(status);
+ }
+
+ status.setPhase(phase);
+ status.setMessage(message);
+ status.setLastUpdate(Instant.now());
+ }
+
+ private void updatePodFailureStatus(Podmortem resource, Pod pod, String message) {
+ log.info("Updated status for pod {}: {}", pod.getMetadata().getName(), message);
+ // In a real implementation, you might want to store per-pod failure status
+ // For now, we'll just update the overall status
+ updatePodmortemStatus(
+ resource,
+ "Processing",
+ String.format("Pod %s: %s", pod.getMetadata().getName(), message));
+ }
+}
diff --git a/src/main/kubernetes/ai-interface-deployment.yaml b/src/main/kubernetes/ai-interface-deployment.yaml
new file mode 100644
index 0000000..1061f6e
--- /dev/null
+++ b/src/main/kubernetes/ai-interface-deployment.yaml
@@ -0,0 +1,23 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: podmortem-ai-interface-service
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: podmortem-ai-interface-service
+ template:
+ metadata:
+ labels:
+ app: podmortem-ai-interface-service
+ spec:
+ containers:
+ - name: ai-interface
+ image: ghcr.io/podmortem/podmortem-ai-interface:latest
+ ports:
+ - containerPort: 8080
+ env:
+ - name: QUARKUS_LOG_LEVEL
+ value: "INFO"
diff --git a/src/main/kubernetes/ai-interface-service.yaml b/src/main/kubernetes/ai-interface-service.yaml
new file mode 100644
index 0000000..15e5138
--- /dev/null
+++ b/src/main/kubernetes/ai-interface-service.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: podmortem-ai-interface-service
+spec:
+ selector:
+ app: podmortem-ai-interface-service
+ ports:
+ - protocol: TCP
+ port: 8080
+ targetPort: 8080
+ type: ClusterIP
diff --git a/src/main/kubernetes/aiprovider-crd.yaml b/src/main/kubernetes/aiprovider-crd.yaml
new file mode 100644
index 0000000..08f9562
--- /dev/null
+++ b/src/main/kubernetes/aiprovider-crd.yaml
@@ -0,0 +1,86 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: aiproviders.podmortem.redhat.com
+spec:
+ group: podmortem.redhat.com
+ versions:
+ - name: v1alpha1
+ served: true
+ storage: true
+ schema:
+ openAPIV3Schema:
+ type: object
+ properties:
+ spec:
+ type: object
+ properties:
+ providerId:
+ type: string
+ description: "Unique identifier for the AI provider (e.g., 'openai', 'ollama')"
+ apiUrl:
+ type: string
+ description: "Base URL for the AI provider API"
+ modelId:
+ type: string
+ description: "Model identifier to use for inference"
+ authenticationRef:
+ type: object
+ properties:
+ secretName:
+ type: string
+ secretKey:
+ type: string
+ description: "Reference to Kubernetes Secret containing authentication credentials"
+ timeoutSeconds:
+ type: integer
+ default: 30
+ description: "Request timeout in seconds"
+ maxRetries:
+ type: integer
+ default: 3
+ description: "Maximum number of retry attempts"
+ cachingEnabled:
+ type: boolean
+ default: true
+ description: "Enable response caching for similar requests"
+ promptTemplate:
+ type: string
+ description: "Custom prompt template to use"
+ maxTokens:
+ type: integer
+ default: 500
+ description: "Maximum number of tokens to generate"
+ temperature:
+ type: number
+ default: 0.3
+ description: "Temperature for AI generation"
+ additionalConfig:
+ type: object
+ additionalProperties:
+ type: string
+ description: "Provider-specific additional configuration"
+ status:
+ type: object
+ properties:
+ phase:
+ type: string
+ enum: ["Pending", "Ready", "Failed"]
+ message:
+ type: string
+ lastValidated:
+ type: string
+ format: date-time
+ observedGeneration:
+ type: integer
+ format: int64
+ subresources:
+ status: {}
+ scope: Namespaced
+ names:
+ plural: aiproviders
+ singular: aiprovider
+ kind: AIProvider
+ shortNames:
+ - aip
diff --git a/src/main/kubernetes/log-parser-manifest.yaml b/src/main/kubernetes/log-parser-deployment.yaml
similarity index 65%
rename from src/main/kubernetes/log-parser-manifest.yaml
rename to src/main/kubernetes/log-parser-deployment.yaml
index b029049..6036229 100644
--- a/src/main/kubernetes/log-parser-manifest.yaml
+++ b/src/main/kubernetes/log-parser-deployment.yaml
@@ -18,16 +18,3 @@ spec:
image: ghcr.io/podmortem/podmortem-log-parser:latest
ports:
- containerPort: 8080
----
-apiVersion: v1
-kind: Service
-metadata:
- name: podmortem-log-parser-service
-spec:
- selector:
- app: podmortem-log-parser-service
- ports:
- - protocol: TCP
- port: 8080
- targetPort: 8080
- type: ClusterIP
\ No newline at end of file
diff --git a/src/main/kubernetes/log-parser-service.yaml b/src/main/kubernetes/log-parser-service.yaml
new file mode 100644
index 0000000..fd963e1
--- /dev/null
+++ b/src/main/kubernetes/log-parser-service.yaml
@@ -0,0 +1,13 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: podmortem-log-parser-service
+spec:
+ selector:
+ app: podmortem-log-parser-service
+ ports:
+ - protocol: TCP
+ port: 8080
+ targetPort: 8080
+ type: ClusterIP
diff --git a/src/main/kubernetes/podmortem-crd.yaml b/src/main/kubernetes/podmortem-crd.yaml
new file mode 100644
index 0000000..73c046c
--- /dev/null
+++ b/src/main/kubernetes/podmortem-crd.yaml
@@ -0,0 +1,92 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: podmortems.podmortem.redhat.com
+spec:
+ group: podmortem.redhat.com
+ versions:
+ - name: v1alpha1
+ served: true
+ storage: true
+ schema:
+ openAPIV3Schema:
+ type: object
+ properties:
+ spec:
+ type: object
+ properties:
+ podSelector:
+ type: object
+ properties:
+ matchLabels:
+ type: object
+ additionalProperties:
+ type: string
+ matchExpressions:
+ type: array
+ items:
+ type: object
+ properties:
+ key:
+ type: string
+ operator:
+ type: string
+ values:
+ type: array
+ items:
+ type: string
+ description: "Label selector for pods to monitor"
+ aiProviderRef:
+ type: object
+ properties:
+ name:
+ type: string
+ description: "Name of the AIProvider resource to use for analysis"
+ namespace:
+ type: string
+ description: "Namespace of the AIProvider resource (optional, defaults to same namespace)"
+ description: "Reference to an AI provider for generating explanations"
+ aiAnalysisEnabled:
+ type: boolean
+ default: true
+ description: "Enable AI-powered analysis for this pod failure"
+ status:
+ type: object
+ properties:
+ phase:
+ type: string
+ enum: ["Pending", "Ready", "Processing", "Error"]
+ message:
+ type: string
+ lastUpdate:
+ type: string
+ format: date-time
+ observedGeneration:
+ type: integer
+ format: int64
+ recentFailures:
+ type: array
+ items:
+ type: object
+ properties:
+ podName:
+ type: string
+ podNamespace:
+ type: string
+ failureTime:
+ type: string
+ format: date-time
+ analysisStatus:
+ type: string
+ explanation:
+ type: string
+ subresources:
+ status: {}
+ scope: Namespaced
+ names:
+ plural: podmortems
+ singular: podmortem
+ kind: Podmortem
+ shortNames:
+ - pm
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index b9c83b5..8b8d62c 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -8,3 +8,10 @@ quarkus.kubernetes.deployment-target=openshift
quarkus.container-image.build=true
quarkus.container-image.group=com.redhat.podmortem
quarkus.container-image.name=podmortem-operator
+
+## REST Client Configuration ##
+# Log Parser Service
+quarkus.rest-client.log-parser.url=http://podmortem-log-parser-service:8080
+
+# AI Interface Service
+quarkus.rest-client.ai-interface.url=http://podmortem-ai-interface-service:8080