From 1279682a78e81978dd57f463a2db0e2f1b46df12 Mon Sep 17 00:00:00 2001 From: Amir Alavi Date: Thu, 3 Aug 2023 21:56:37 -0400 Subject: [PATCH] feat(provider/kubernetes): support for kubectl server-side-apply strategy kubernetes server-side apply (SSA) was released back in 1.14 and became GA In 1.22. This new strategy will use the new merging algorithm, as well as tracking field ownership at the kubernetes api-server Signed-off-by: Amir Alavi --- .../manifest/KubernetesManifestStrategy.java | 6 +++++- .../clouddriver/kubernetes/op/handler/CanDeploy.java | 3 +++ .../kubernetes/op/job/KubectlJobExecutor.java | 7 ++++++- .../kubernetes/security/KubernetesCredentials.java | 5 +++-- .../manifest/KubernetesManifestStrategyTest.java | 8 ++++++++ .../kubernetes/op/handler/CanDeployTest.java | 10 ++++++++++ 6 files changed, 35 insertions(+), 4 deletions(-) diff --git a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/description/manifest/KubernetesManifestStrategy.java b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/description/manifest/KubernetesManifestStrategy.java index 79809bbbdb1..327ca7bef34 100644 --- a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/description/manifest/KubernetesManifestStrategy.java +++ b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/description/manifest/KubernetesManifestStrategy.java @@ -107,7 +107,8 @@ ImmutableMap toAnnotations() { public enum DeployStrategy { APPLY(null), RECREATE(STRATEGY_ANNOTATION_PREFIX + "/recreate"), - REPLACE(STRATEGY_ANNOTATION_PREFIX + "/replace"); + REPLACE(STRATEGY_ANNOTATION_PREFIX + "/replace"), + SERVER_SIDE_APPLY(STRATEGY_ANNOTATION_PREFIX + "/server-side-apply"); @Nullable private final String annotation; @@ -122,6 +123,9 @@ static DeployStrategy fromAnnotations(Map annotations) { if (Boolean.parseBoolean(annotations.get(REPLACE.annotation))) { return REPLACE; } + if (Boolean.parseBoolean(annotations.get(SERVER_SIDE_APPLY.annotation))) { + return SERVER_SIDE_APPLY; + } return APPLY; } diff --git a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/CanDeploy.java b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/CanDeploy.java index 84f80769e31..4af67d6c8f6 100644 --- a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/CanDeploy.java +++ b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/CanDeploy.java @@ -59,6 +59,9 @@ default OperationResult deploy( case REPLACE: deployedManifest = credentials.createOrReplace(manifest, task, opName); break; + case SERVER_SIDE_APPLY: + deployedManifest = credentials.deploy(manifest, task, opName, "--server-side"); + break; case APPLY: deployedManifest = credentials.deploy(manifest, task, opName); break; diff --git a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/job/KubectlJobExecutor.java b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/job/KubectlJobExecutor.java index 3ffe7d9a32c..7032ca4cf35 100644 --- a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/job/KubectlJobExecutor.java +++ b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/job/KubectlJobExecutor.java @@ -587,12 +587,17 @@ public ImmutableList list( } public KubernetesManifest deploy( - KubernetesCredentials credentials, KubernetesManifest manifest, Task task, String opName) { + KubernetesCredentials credentials, + KubernetesManifest manifest, + Task task, + String opName, + String... cmdArgs) { log.info("Deploying manifest {}", manifest.getFullResourceName()); List command = kubectlAuthPrefix(credentials); // Read from stdin command.add("apply"); + command.addAll(List.of(cmdArgs)); command.add("-o"); command.add("json"); command.add("-f"); diff --git a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/security/KubernetesCredentials.java b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/security/KubernetesCredentials.java index 07e061521fa..ede2c1c299e 100644 --- a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/security/KubernetesCredentials.java +++ b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/security/KubernetesCredentials.java @@ -561,12 +561,13 @@ public Collection topPod(KubernetesCoordinates coords) { () -> jobExecutor.topPod(this, coords.getNamespace(), coords.getName())); } - public KubernetesManifest deploy(KubernetesManifest manifest, Task task, String opName) { + public KubernetesManifest deploy( + KubernetesManifest manifest, Task task, String opName, String... cmdArgs) { return runAndRecordMetrics( "deploy", manifest.getKind(), manifest.getNamespace(), - () -> jobExecutor.deploy(this, manifest, task, opName)); + () -> jobExecutor.deploy(this, manifest, task, opName, cmdArgs)); } private KubernetesManifest replace(KubernetesManifest manifest, Task task, String opName) { diff --git a/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/description/manifest/KubernetesManifestStrategyTest.java b/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/description/manifest/KubernetesManifestStrategyTest.java index 64bd00c6355..2c2dd99d215 100644 --- a/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/description/manifest/KubernetesManifestStrategyTest.java +++ b/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/description/manifest/KubernetesManifestStrategyTest.java @@ -64,6 +64,14 @@ void replaceStrategy() { assertThat(strategy).isEqualTo(DeployStrategy.REPLACE); } + @Test + void serverSideApplyStrategy() { + KubernetesManifestStrategy.DeployStrategy strategy = + KubernetesManifestStrategy.DeployStrategy.fromAnnotations( + ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "true")); + assertThat(strategy).isEqualTo(DeployStrategy.SERVER_SIDE_APPLY); + } + @Test void nonBooleanValue() { KubernetesManifestStrategy.DeployStrategy strategy = diff --git a/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/CanDeployTest.java b/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/CanDeployTest.java index f052311e562..c0d10b2dc38 100644 --- a/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/CanDeployTest.java +++ b/clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/CanDeployTest.java @@ -61,6 +61,16 @@ void applyReturnValue() { assertThat(result.getManifests()).containsExactlyInAnyOrder(manifest); } + @Test + void applyServerSideMutations() { + KubernetesCredentials credentials = mock(KubernetesCredentials.class); + KubernetesManifest manifest = ManifestFetcher.getManifest("candeploy/deployment.yml"); + when(credentials.deploy(manifest, task, OP_NAME, "--server-side")).thenReturn(manifest); + handler.deploy(credentials, manifest, DeployStrategy.SERVER_SIDE_APPLY, task, OP_NAME); + verify(credentials).deploy(manifest, task, OP_NAME, "--server-side"); + verifyNoMoreInteractions(credentials); + } + @Test void replaceMutations() { KubernetesCredentials credentials = mock(KubernetesCredentials.class);