fix(kubernetes): add support for json patching
MODIFY
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/description/manifest/KubernetesPatchManifestDescription.java
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/description/manifest/KubernetesPatchManifestDescription.java
@@ -36,7 +36,7 @@ public class KubernetesPatchManifestDescription extends KubernetesAtomicOperatio
// This will only be a portion of a full manifest so calls to some required fields can fail.
// Using the KubernetesManifest type makes it simpler to reuse the ArtifactReplacement logic.
// TODO: change Orca to only send a single manifest.
- private KubernetesManifest patchBody;
+ private Object patchBody;
private List<Artifact> requiredArtifacts;
private List<Artifact> allArtifacts;
private Artifact manifestArtifact;

MODIFY
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/CanPatch.java
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/CanPatch.java
package com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.JsonPatch;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesPatchOptions;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesKind;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesManifest;
@@ -23,14 +24,24 @@ import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.OperationResult;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.security.KubernetesV2Credentials;
import java.util.HashMap;
+import java.util.List;
public interface CanPatch {
KubernetesKind kind();
- default OperationResult patch(KubernetesV2Credentials credentials, String namespace, String name,
- KubernetesPatchOptions options, KubernetesManifest manifest) {
+ default OperationResult patchWithManifest(KubernetesV2Credentials credentials, String namespace,
+ String name, KubernetesPatchOptions options, KubernetesManifest manifest) {
credentials.patch(kind(), namespace, name, options, manifest);
+ return patch(namespace, name);
+ }
+
+ default OperationResult patchWithJson(KubernetesV2Credentials credentials, String namespace,
+ String name, KubernetesPatchOptions options, List<JsonPatch> patches) {
+ credentials.patch(kind(), namespace, name, options, patches);
+ return patch(namespace, name);
+ }
+ default OperationResult patch(String namespace, String name) {
KubernetesManifest patchedManifest = new KubernetesManifest();
patchedManifest.putIfAbsent("metadata", new HashMap<String, Object>()); // Hack: Set mandatory field
patchedManifest.setNamespace(namespace);

MODIFY
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/manifest/KubernetesPatchManifestOperation.java
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/manifest/KubernetesPatchManifestOperation.java
package com.netflix.spinnaker.clouddriver.kubernetes.v2.op.manifest;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;
import com.netflix.spinnaker.clouddriver.data.task.Task;
import com.netflix.spinnaker.clouddriver.data.task.TaskRepository;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.artifact.ArtifactReplacer.ReplaceResult;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.JsonPatch;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesCoordinates;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesPatchOptions.MergeStrategy;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesResourceProperties;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesResourcePropertyRegistry;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesManifest;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesPatchManifestDescription;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.OperationResult;
import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler;
@@ -30,6 +35,7 @@ import com.netflix.spinnaker.clouddriver.kubernetes.v2.security.KubernetesV2Cred
import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.HashSet;
@@ -44,6 +50,9 @@ public class KubernetesPatchManifestOperation implements AtomicOperation<Operati
private final String accountName;
private static final String OP_NAME = "PATCH_KUBERNETES_MANIFEST";
+ @Autowired
+ private ObjectMapper objectMapper;
+
public KubernetesPatchManifestOperation(KubernetesPatchManifestDescription description,
KubernetesResourcePropertyRegistry registry) {
this.description = description;
@@ -64,15 +73,26 @@ public class KubernetesPatchManifestOperation implements AtomicOperation<Operati
updateStatus("Finding patch handler for " + objToPatch + "...");
KubernetesHandler patchHandler = findPatchHandler(objToPatch);
+ OperationResult result = new OperationResult();
+
+ MergeStrategy mergeStrategy = description.getOptions().getMergeStrategy();
+
+ if (mergeStrategy == MergeStrategy.json) {
+ // Skip artifact replacement for json patches
+ updateStatus("Submitting manifest " + description.getManifestName() + " to Kubernetes master...");
+ List<JsonPatch> jsonPatches = objectMapper.convertValue(description.getPatchBody(), new TypeReference<List<JsonPatch>>(){});
+ result.merge(patchHandler.patchWithJson(credentials, objToPatch.getNamespace(),
+ objToPatch.getName(), description.getOptions(), jsonPatches));
+ } else {
updateStatus("Swapping out artifacts in " + objToPatch + " from context...");
ReplaceResult replaceResult = replaceArtifacts(objToPatch, patchHandler);
updateStatus("Submitting manifest " + description.getManifestName() + " to Kubernetes master...");
- OperationResult result = new OperationResult();
- result.merge(patchHandler.patch(credentials, objToPatch.getNamespace(), objToPatch.getName(),
- description.getOptions(), replaceResult.getManifest()));
-
+ result.merge(patchHandler.patchWithManifest(credentials, objToPatch.getNamespace(),
+ objToPatch.getName(), description.getOptions(), replaceResult.getManifest()));
result.getBoundArtifacts().addAll(replaceResult.getBoundArtifacts());
+ }
+
result.removeSensitiveKeys(registry, accountName);
return result;
}
@@ -86,7 +106,8 @@ public class KubernetesPatchManifestOperation implements AtomicOperation<Operati
List<Artifact> allArtifacts = description.getAllArtifacts() == null ? new ArrayList<>() :
description.getAllArtifacts();
- ReplaceResult replaceResult = patchHandler.replaceArtifacts(description.getPatchBody(),
+ KubernetesManifest manifest = objectMapper.convertValue(description.getPatchBody(), KubernetesManifest.class);
+ ReplaceResult replaceResult = patchHandler.replaceArtifacts(manifest,
allArtifacts, objToPatch.getNamespace(), description.getAccount());
if (description.getRequiredArtifacts() != null) {

fix(kubernetes): add support for json patching (#3047)