0

I am running automation tests which apply kubernetes yamls to a kubernetes cluster using the fabric8 kubernetes client.

One of the features I like about kubectl apply -f is that if I attempt to apply the same yaml twice then the environment simply ignores the action and leaves my deployments, pods etc running as they were. This means that each of my tests can apply the same yaml and lazily create resources on the kubernetes cluster

I would like this same functionality with the fabric8 KubernetesClient but the createOrReplace() method seems to behave differently to kubectl apply -f

lance-java
  • 25,497
  • 4
  • 59
  • 101
  • Why not use `serverSideApply()` , see [example](https://github.com/rohanKanojia/kubernetes-client-demo/blob/master/src/main/java/io/fabric8/ServerSideApplyDemo.java#L25) – Rohan Kumar Mar 31 '23 at 09:18
  • With `serverSideApply()` I get errors like `code=409 field=.spec.template.spec.containers[name="xyz"].resources.limits.cpu message=Apply failed with 1 conflict: ... .spec.template.spec.containers[name="xyz"].resources.limits.cpu` – lance-java Mar 31 '23 at 10:16

2 Answers2

0

I ended up solving this by:

  1. Storing the last applied configuration in an annotation
  2. Checking if the resource already exists
  3. Calling create() if the resource does not exist
  4. Calling patch() on the previous configuration (the value stored in the annotation) if the resource previously exists

Code:

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.NamespaceableResource;
import io.fabric8.kubernetes.client.utils.Serialization;

public class KubernetesHelper {
    private final KubernetesClient client;

    // constructor
    
    @Override
    public Collection<HasMetadata> apply(InputStream yamlStream) {
        Object unmarshalled = Serialization.unmarshal(yamlStream);
        Collection<HasMetadata> entities;
        if (unmarshalled instanceof Collection) {
            entities = (Collection) unmarshalled;
        } else if (unmarshalled instanceof KubernetesResourceList) {
            entities = ((KubernetesResourceList) unmarshalled).getItems();
        } else {
            entities = List.of((HasMetadata) unmarshalled);
        }       
        return entities.stream().map(this::createOrPatch).collect(toList());
    }
    
    protected <T extends HasMetadata> T createOrPatch(T inEntity) {
        String annotationName = "kubectl.kubernetes.io/last-applied-configuration";
        if (inEntity.getMetadata().getAnnotations() == null) {
            inEntity.getMetadata().setAnnotations(new LinkedHashMap<>());
        }
        inEntity.getMetadata().getAnnotations().put(annotationName, Serialization.asJson(inEntity));
        NamespaceableResource<T> resource = client.resource(inEntity);
        T serverEntity = resource.get();
        T outEntity;
        if (serverEntity == null) {
            outEntity = resource.create();
        } else {
            String lastAppliedConfiguration = serverEntity.getMetadata().getAnnotations().get(annotationName);
            if (lastAppliedConfiguration == null) {
                String msg = String.format("Could not find annotation '%s' for entity '%s'", annotationName, inEntity.getMetadata().getName());
                throw new RuntimeException(msg);
            }
            T lastAppliedEntity = Serialization.unmarshal(lastAppliedConfiguration);
            outEntity = client.resource(lastAppliedEntity).patch(inEntity);
        }
        return outEntity;
    }
}

It seems that other people want this functionality too. See related discussions

lance-java
  • 25,497
  • 4
  • 59
  • 101
  • This is not a good mechanism, you're basically recreating (with nuances) the createOrReplace logic yourself. serverSideApply should work, or even just sending a plain patch request to the API server. If that's failing, then we should treat it as a bug – Marc Nuri Mar 31 '23 at 12:23
  • `serversideApply()` does not work. It fails for fields such as `resources.limits.cpu` even though the value has not changed since last apply. – lance-java Mar 31 '23 at 12:51
  • If that fails, then there's probably something that we can do to resolve it. The whole point of deprecating the createOrReplace method is to avoid doing this kind of process client-side that can eventually fail in case of high concurrency. I'd really recommend to report a bug and if possible include a reproducer – Marc Nuri Mar 31 '23 at 13:03
0

You could still use Fabric8 client with the following command:

resource.forceConflicts().serverSideApply();