Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAMEL-21855: camel-jbang - k8s delete may not work any more #17419

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,41 +25,36 @@
import io.fabric8.kubernetes.api.model.StatusDetails;
import io.fabric8.openshift.client.OpenShiftClient;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait;
import org.apache.camel.util.StringHelper;
import org.codehaus.plexus.util.ExceptionUtils;
import picocli.CommandLine;

import static org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper.getKubernetesClient;
import static org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait.KUBERNETES_LABEL_NAME;

@CommandLine.Command(name = "delete",
description = "Delete Camel application from Kubernetes. This operation will delete all resources associated to this app, such as: Deployment, Routes, Services, etc. filtering by labels \"app.kubernetes.io/managed-by=camel-jbang\" and \"app=<app name>\".",
description = "Delete Camel application from Kubernetes. This operation will delete all resources associated to this app, such as: Deployment, Routes, Services, etc. filtering by label \"app.kubernetes.io/name=<name>\".",
sortOptions = false)
public class KubernetesDelete extends KubernetesBaseCommand {

@CommandLine.Parameters(description = "The deployed application name", arity = "1", paramLabel = "<app name>")
String appName;

public KubernetesDelete(CamelJBangMain main) {
super(main);
}

public Integer doCall() throws Exception {
printer().printf("Deleting all resources from app: %s%n", appName);
printer().printf("Deleting all resources from app: %s%n", name);
Map<String, String> labels = new HashMap<>();
// this label is set in KubernetesRun command
labels.put(BaseTrait.KUBERNETES_LABEL_MANAGED_BY, "camel-jbang");
labels.put("app", appName);
labels.put(KUBERNETES_LABEL_NAME, name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The KUBERNETES_LABEL_MANAGED_BY is there, as the run command sets this label.
Also, the manage-by=camel-jbang label is to make sure these custom resources are set by camel-jbang and not to delete any other deployment by a deployment that sets the same name.

Copy link
Contributor Author

@tdiesler tdiesler Mar 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run is an extension of export + package + deploy, for which manage-by is not camel-jbang, but jkube. There is a hack in the code that silently overrides manage-by=jkube with manage-by=camel-jbang, but only for the run case.

We could filter by manage-by, but only it works for all cases. Accidentally deleting (other) resources with the given name in the same namespace is a non-issue IMHO - we delete deployment + service (and some openshift specific stuff).

Do you have an actual use case where not filtering by manage-by would be a problem?

List<StatusDetails> deleteStatuses = new ArrayList<>();
try {
// delete the most common resources
// delete Deployment cascades to pod
deleteStatuses.addAll(getKubernetesClient().apps().deployments().withLabels(labels).delete());
// delete service
deleteStatuses.addAll(getKubernetesClient().services().withLabels(labels).delete());
ClusterType clusterType = KubernetesHelper.discoverClusterType();
if (ClusterType.OPENSHIFT == clusterType) {
// openshift specific: BuildConfig, ImageStreams, Route - BuildConfig casacade delete to Build and ConfigMap
// openshift specific: BuildConfig, ImageStreams, Route - BuildConfig cascade delete to Build and ConfigMap
OpenShiftClient ocpClient = getKubernetesClient().adapt(OpenShiftClient.class);
// BuildConfig
deleteStatuses.addAll(ocpClient.buildConfigs().withLabels(labels).delete());
Expand All @@ -72,7 +67,7 @@ public Integer doCall() throws Exception {
deleteStatuses.forEach(
s -> printer().printf("Deleted: %s '%s'%n", StringHelper.capitalize(s.getKind()), s.getName()));
} else {
printer().println("No deployment found with name: " + appName);
printer().println("No deployment found with name: " + name);
}
} catch (Exception ex) {
// there could be various chained exceptions, so we want to get the root cause
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void setup() {
public void shouldDeleteNonExistentApp() throws Exception {
KubernetesDelete command = new KubernetesDelete(new CamelJBangMain().withPrinter(printer));
command.withClient(client);
command.appName = "does-not-exist";
command.name = "does-not-exist";
int exit = command.doCall();
boolean errorOutput = printer.getOutput().contains("Error trying to delete the app");
if (errorOutput) {
Expand Down Expand Up @@ -88,7 +88,7 @@ public void shouldDeleteNonExistentAppInMinikube() throws Exception {

KubernetesDelete command = new KubernetesDelete(new CamelJBangMain().withPrinter(printer));
command.withClient(client);
command.appName = "does-not-exist";
command.name = "does-not-exist";
int exit = command.doCall();
boolean errorOutput = printer.getOutput().contains("Error trying to delete the app");
if (errorOutput) {
Expand All @@ -114,7 +114,7 @@ public void shouldDeleteNonExistentAppInOpenshift() throws Exception {

KubernetesDelete command = new KubernetesDelete(new CamelJBangMain().withPrinter(printer));
command.withClient(client);
command.appName = "does-not-exist";
command.name = "does-not-exist";
int exit = command.doCall();
boolean errorOutput = printer.getOutput().contains("Error trying to delete the app");
if (errorOutput) {
Expand All @@ -127,27 +127,27 @@ public void shouldDeleteNonExistentAppInOpenshift() throws Exception {

@Test
public void shouldDeleteOnKubernetes() throws Exception {
String appName = "my-route";
String name = "my-route";

// Mock the delete request expectation
serverDeleteExpect(
"/apis/apps/v1/namespaces/test/deployments?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang");
"/apis/apps/v1/namespaces/test/deployments?labelSelector=app.kubernetes.io%2Fname%3Dmy-route");
serverDeleteExpect(
"/api/v1/namespaces/test/services?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang");
"/api/v1/namespaces/test/services?labelSelector=app.kubernetes.io%2Fname%3Dmy-route");

// Execute delete command
KubernetesDelete command = new KubernetesDelete(new CamelJBangMain().withPrinter(printer));
command.withClient(client);
command.namespace = "test";
command.appName = appName;
command.name = name;
int exit = command.doCall();

// Verify command execution
Assertions.assertEquals(0, exit, printer.getOutput());

// Verify deployment was deleted
Assertions.assertNull(client.apps().deployments().withName(appName).get());
Assertions.assertNull(client.services().withName(appName).get());
Assertions.assertNull(client.apps().deployments().withName(name).get());
Assertions.assertNull(client.services().withName(name).get());
}

@Test
Expand All @@ -163,25 +163,25 @@ public void shouldDeleteOnOpenShift() throws Exception {

serverExpect("/apis/config.openshift.io/v1/clusterversions/version", versionCR);

String appName = "my-route";
String name = "my-route";

// Mock the delete request expectations for openshift resources
serverDeleteExpect(
"/apis/apps/v1/namespaces/test/deployments?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang");
"/apis/apps/v1/namespaces/test/deployments?labelSelector=app.kubernetes.io%2Fname%3Dmy-route");
serverDeleteExpect(
"/api/v1/namespaces/test/services?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang");
"/api/v1/namespaces/test/services?labelSelector=app.kubernetes.io%2Fname%3Dmy-route");
serverDeleteExpect(
"/apis/build.openshift.io/v1/namespaces/test/buildconfigs?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang");
"/apis/build.openshift.io/v1/namespaces/test/buildconfigs?labelSelector=app.kubernetes.io%2Fname%3Dmy-route");
serverDeleteExpect(
"/apis/image.openshift.io/v1/namespaces/test/imagestreams?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang");
"/apis/image.openshift.io/v1/namespaces/test/imagestreams?labelSelector=app.kubernetes.io%2Fname%3Dmy-route");
serverDeleteExpect(
"/apis/route.openshift.io/v1/namespaces/test/routes?labelSelector=app%3Dmy-route%2Capp.kubernetes.io%2Fmanaged-by%3Dcamel-jbang");
"/apis/route.openshift.io/v1/namespaces/test/routes?labelSelector=app.kubernetes.io%2Fname%3Dmy-route");

// Execute delete command
KubernetesDelete command = new KubernetesDelete(new CamelJBangMain().withPrinter(printer));
command.withClient(client);
command.namespace = "test";
command.appName = appName;
command.name = name;
int exit = command.doCall();

// Verify command execution
Expand All @@ -190,11 +190,11 @@ public void shouldDeleteOnOpenShift() throws Exception {
OpenShiftClient ocpClient = client.adapt(OpenShiftClient.class);

// Verify deployment was deleted
Assertions.assertNull(client.apps().deployments().withName(appName).get());
Assertions.assertNull(client.services().withName(appName).get());
Assertions.assertNull(ocpClient.buildConfigs().withName(appName).get());
Assertions.assertNull(ocpClient.imageStreams().withName(appName).get());
Assertions.assertNull(ocpClient.routes().withName(appName).get());
Assertions.assertNull(client.apps().deployments().withName(name).get());
Assertions.assertNull(client.services().withName(name).get());
Assertions.assertNull(ocpClient.buildConfigs().withName(name).get());
Assertions.assertNull(ocpClient.imageStreams().withName(name).get());
Assertions.assertNull(ocpClient.routes().withName(name).get());
}

private void serverDeleteExpect(String path) {
Expand Down