From 8374a281c01f5e52602913217375ec77f4f050fa Mon Sep 17 00:00:00 2001 From: Raul Ciurescu <24556350+ciurescuraul@users.noreply.github.com> Date: Mon, 5 Dec 2022 19:25:05 +0200 Subject: [PATCH 01/10] feat(k8s): Add Deployment Kind support for Blue/Green deployments --- .../handler/KubernetesDeploymentHandler.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/KubernetesDeploymentHandler.java b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/KubernetesDeploymentHandler.java index 997f6a69558..b11d161696f 100644 --- a/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/KubernetesDeploymentHandler.java +++ b/clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/KubernetesDeploymentHandler.java @@ -30,7 +30,9 @@ import com.netflix.spinnaker.clouddriver.kubernetes.description.manifest.KubernetesApiVersion; import com.netflix.spinnaker.clouddriver.kubernetes.description.manifest.KubernetesKind; import com.netflix.spinnaker.clouddriver.kubernetes.description.manifest.KubernetesManifest; +import com.netflix.spinnaker.clouddriver.kubernetes.description.manifest.KubernetesManifestSelector; import com.netflix.spinnaker.clouddriver.kubernetes.model.Manifest.Status; +import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesCredentials; import com.netflix.spinnaker.kork.annotations.NonnullByDefault; import io.kubernetes.client.openapi.models.V1Deployment; import io.kubernetes.client.openapi.models.V1DeploymentCondition; @@ -39,6 +41,7 @@ import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.springframework.stereotype.Component; @@ -47,6 +50,7 @@ public class KubernetesDeploymentHandler extends KubernetesHandler implements CanResize, CanScale, + HasPods, CanPauseRollout, CanResumeRollout, CanUndoRollout, @@ -82,7 +86,7 @@ public KubernetesKind kind() { @Override public boolean versioned() { - return false; + return true; } @Override @@ -192,4 +196,18 @@ private static Optional checkReplicaCounts( return Optional.empty(); } + + @Override + public List pods( + KubernetesCredentials credentials, KubernetesManifest object) { + KubernetesManifestSelector selector = object.getManifestSelector(); + return credentials + .list(KubernetesKind.POD, object.getNamespace(), selector.toSelectorList()) + .stream() + .filter( + p -> + p.getOwnerReferences().stream() + .anyMatch(or -> or.getName().equals(object.getName()))) + .collect(Collectors.toList()); + } } From 976f368cd0c0a51a69d985718036c547bdf9ccf0 Mon Sep 17 00:00:00 2001 From: Raul Ciurescu <24556350+ciurescuraul@users.noreply.github.com> Date: Tue, 13 Dec 2022 14:30:55 +0200 Subject: [PATCH 02/10] test(integration): test red/black deployment --- .../kubernetes/it/DeployManifestIT.java | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java b/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java index d6a343e7704..2a017d7936c 100644 --- a/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java +++ b/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java @@ -35,11 +35,9 @@ import org.junit.jupiter.api.Test; public class DeployManifestIT extends BaseTest { - private static final String DEPLOYMENT_1_NAME = "deployment1"; private static final String REPLICASET_1_NAME = "rs1"; private static final String SERVICE_1_NAME = "service1"; - private static final String SERVICE_2_NAME = "service2"; private static String account1Ns; @@ -1351,6 +1349,80 @@ public void shouldDeployBlueGreenReplicaSet() throws IOException, InterruptedExc 1, podNames.size(), "Only one pod expected to have the label for traffic selection"); } + @DisplayName( + ".\n===\n" + + "Given a deployment yaml with red/black deployment traffic strategy\n" + + " and an existing service\n" + + "When sending deploy manifest request two times\n" + + " And sending disable manifest one time\n" + + "Then there are two deployments with only the last one receiving traffic\n===") + @Test + public void shouldDeployRedBlackDeploymentKind() throws IOException, InterruptedException { + // ------------------------- given -------------------------- + String appName = "red-black"; + System.out.println("> Using namespace: " + account1Ns + ", appName: " + appName); + String selectorValue = appName + "traffichere"; + + Map service = + KubeTestUtils.loadYaml("classpath:manifests/service.yml") + .withValue("metadata.namespace", account1Ns) + .withValue("metadata.name", SERVICE_2_NAME) + .withValue("spec.selector", ImmutableMap.of("pointer", selectorValue)) + .withValue("spec.type", "NodePort") + .asMap(); + kubeCluster.execKubectl("-n " + account1Ns + " apply -f -", service); + + List> manifest = + KubeTestUtils.loadYaml("classpath:manifests/deployment.yml") + .withValue("metadata.namespace", account1Ns) + .withValue("metadata.name", appName) + .withValue("spec.selector.matchLabels", ImmutableMap.of("label1", "value1")) + .withValue("spec.template.metadata.labels", ImmutableMap.of("label1", "value1")) + .asList(); + + // ------------------------- when -------------------------- + List> body = + KubeTestUtils.loadJson("classpath:requests/deploy_manifest.json") + .withValue("deployManifest.account", ACCOUNT1_NAME) + .withValue("deployManifest.moniker.app", appName) + .withValue("deployManifest.manifests", manifest) + .withValue( + "deployManifest.services", Collections.singleton("service " + SERVICE_2_NAME)) + .withValue("deployManifest.strategy", "RED_BLACK") + .withValue("deployManifest.trafficManagement.enabled", true) + .withValue("deployManifest.trafficManagement.options.strategy", "redblack") + .withValue("deployManifest.trafficManagement.options.enableTraffic", true) + .withValue("deployManifest.trafficManagement.options.namespace", account1Ns) + .withValue( + "deployManifest.trafficManagement.options.services", + Collections.singleton("service " + appName)) + .asList(); + KubeTestUtils.deployAndWaitStable( + baseUrl(), body, account1Ns, "deployment " + appName + "-v000"); + KubeTestUtils.deployAndWaitStable( + baseUrl(), body, account1Ns, "deployment " + appName + "-v001"); + body = + KubeTestUtils.loadJson("classpath:requests/disable_manifest.json") + .withValue("disableManifest.app", appName) + .withValue("disableManifest.manifestName", "deployment " + appName + "-v000") + .withValue("disableManifest.location", account1Ns) + .withValue("disableManifest.account", ACCOUNT1_NAME) + .asList(); + KubeTestUtils.disableManifest(baseUrl(), body, account1Ns, "deployment " + appName + "-v000"); + + // ------------------------- then -------------------------- + List podNames = + Splitter.on(" ") + .splitToList( + kubeCluster.execKubectl( + "-n " + + account1Ns + + " get pod -o=jsonpath='{.items[*].metadata.name}' -l=pointer=" + + selectorValue)); + assertEquals( + 1, podNames.size(), "Only one pod expected to have the label for traffic selection"); + } + @DisplayName( ".\n===\n" + "Given a cron job manifest without image tag\n" From 5f46f95e0b5d4694455d7e470b9f03dc32ac4fe9 Mon Sep 17 00:00:00 2001 From: Raul Ciurescu <24556350+ciurescuraul@users.noreply.github.com> Date: Tue, 13 Dec 2022 17:47:37 +0200 Subject: [PATCH 03/10] test(integration): test blue/green deployment --- .../kubernetes/it/DeployManifestIT.java | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java b/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java index 2a017d7936c..268885be9c0 100644 --- a/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java +++ b/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java @@ -1357,7 +1357,7 @@ public void shouldDeployBlueGreenReplicaSet() throws IOException, InterruptedExc + " And sending disable manifest one time\n" + "Then there are two deployments with only the last one receiving traffic\n===") @Test - public void shouldDeployRedBlackDeploymentKind() throws IOException, InterruptedException { + public void shouldDeployRedBlackDeployment() throws IOException, InterruptedException { // ------------------------- given -------------------------- String appName = "red-black"; System.out.println("> Using namespace: " + account1Ns + ", appName: " + appName); @@ -1423,6 +1423,80 @@ public void shouldDeployRedBlackDeploymentKind() throws IOException, Interrupted 1, podNames.size(), "Only one pod expected to have the label for traffic selection"); } + @DisplayName( + ".\n===\n" + + "Given a deployment yaml with blue/green deployment traffic strategy\n" + + " and an existing service\n" + + "When sending deploy manifest request two times\n" + + " And sending disable manifest one time\n" + + "Then there are two deployments with only the last one receiving traffic\n===") + @Test + public void shouldDeployBlueGreenDeployment() throws IOException, InterruptedException { + // ------------------------- given -------------------------- + String appName = "blue-green"; + System.out.println("> Using namespace: " + account1Ns + ", appName: " + appName); + String selectorValue = appName + "traffichere"; + + Map service = + KubeTestUtils.loadYaml("classpath:manifests/service.yml") + .withValue("metadata.namespace", account1Ns) + .withValue("metadata.name", SERVICE_2_NAME) + .withValue("spec.selector", ImmutableMap.of("pointer", selectorValue)) + .withValue("spec.type", "NodePort") + .asMap(); + kubeCluster.execKubectl("-n " + account1Ns + " apply -f -", service); + + List> manifest = + KubeTestUtils.loadYaml("classpath:manifests/deployment.yml") + .withValue("metadata.namespace", account1Ns) + .withValue("metadata.name", appName) + .withValue("spec.selector.matchLabels", ImmutableMap.of("label1", "value1")) + .withValue("spec.template.metadata.labels", ImmutableMap.of("label1", "value1")) + .asList(); + + // ------------------------- when -------------------------- + List> body = + KubeTestUtils.loadJson("classpath:requests/deploy_manifest.json") + .withValue("deployManifest.account", ACCOUNT1_NAME) + .withValue("deployManifest.moniker.app", appName) + .withValue("deployManifest.manifests", manifest) + .withValue( + "deployManifest.services", Collections.singleton("service " + SERVICE_2_NAME)) + .withValue("deployManifest.strategy", "BLUE_GREEN") + .withValue("deployManifest.trafficManagement.enabled", true) + .withValue("deployManifest.trafficManagement.options.strategy", "bluegreen") + .withValue("deployManifest.trafficManagement.options.enableTraffic", true) + .withValue("deployManifest.trafficManagement.options.namespace", account1Ns) + .withValue( + "deployManifest.trafficManagement.options.services", + Collections.singleton("service " + appName)) + .asList(); + KubeTestUtils.deployAndWaitStable( + baseUrl(), body, account1Ns, "deployment " + appName + "-v000"); + KubeTestUtils.deployAndWaitStable( + baseUrl(), body, account1Ns, "deployment " + appName + "-v001"); + body = + KubeTestUtils.loadJson("classpath:requests/disable_manifest.json") + .withValue("disableManifest.app", appName) + .withValue("disableManifest.manifestName", "deployment " + appName + "-v000") + .withValue("disableManifest.location", account1Ns) + .withValue("disableManifest.account", ACCOUNT1_NAME) + .asList(); + KubeTestUtils.disableManifest(baseUrl(), body, account1Ns, "deployment " + appName + "-v000"); + + // ------------------------- then -------------------------- + List podNames = + Splitter.on(" ") + .splitToList( + kubeCluster.execKubectl( + "-n " + + account1Ns + + " get pod -o=jsonpath='{.items[*].metadata.name}' -l=pointer=" + + selectorValue)); + assertEquals( + 1, podNames.size(), "Only one pod expected to have the label for traffic selection"); + } + @DisplayName( ".\n===\n" + "Given a cron job manifest without image tag\n" From ecddacefbdc576ddb0a846cf8f2f2b3b4763e781 Mon Sep 17 00:00:00 2001 From: Sandesh <30489233+j-sandy@users.noreply.github.com> Date: Wed, 7 Dec 2022 21:45:33 +0530 Subject: [PATCH 04/10] refactor(web): Clean up redundant spring property in gradle file (#5834) The property spring.config.additional-location is redundant in clouddriver-web.gradle file. This property is set by class com.netflix.spinnaker.kork.boot.DefaultPropertiesBuilder in com.netflix.spinnaker.clouddriver.Main. So removing it from gradle file. --- clouddriver-web/clouddriver-web.gradle | 7 ------- 1 file changed, 7 deletions(-) diff --git a/clouddriver-web/clouddriver-web.gradle b/clouddriver-web/clouddriver-web.gradle index 806c49727ca..b196dbefeee 100644 --- a/clouddriver-web/clouddriver-web.gradle +++ b/clouddriver-web/clouddriver-web.gradle @@ -1,13 +1,6 @@ apply plugin: 'io.spinnaker.package' -ext { - springConfigLocation = System.getProperty('spring.config.additional-location', "${System.getProperty('user.home')}/.spinnaker/".toString()) -} - mainClassName = 'com.netflix.spinnaker.clouddriver.Main' -run { - systemProperty('spring.config.additional-location', project.springConfigLocation) -} configurations.all { exclude group: 'javax.servlet', module: 'servlet-api' From 990170f8453dcedf013b19a62a17b1bb4e3506e4 Mon Sep 17 00:00:00 2001 From: David Byron <82477955+dbyron-sf@users.noreply.github.com> Date: Wed, 7 Dec 2022 13:56:34 -0800 Subject: [PATCH 05/10] feat(kubernetes): add endpoints to allow k8s tasks to be retried by orca (#5833) Co-authored-by: Apoorv Mahajan Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../core/test/TaskRepositoryTck.java | 19 ++++ .../DefaultOrchestrationProcessor.groovy | 3 + .../clouddriver/data/task/DefaultTask.java | 24 ++++- .../spinnaker/clouddriver/data/task/Task.java | 9 +- .../data/task/jedis/JedisTask.java | 25 +++++- .../data/task/jedis/JedisTaskTest.java | 3 +- .../spinnaker/clouddriver/sql/SqlTask.kt | 24 ++++- .../clouddriver/sql/SqlTaskRepository.kt | 11 +++ .../controllers/OperationsController.groovy | 87 ++++++++++++++++++- 9 files changed, 194 insertions(+), 11 deletions(-) diff --git a/clouddriver-core-tck/src/main/java/com/netflix/spinnaker/clouddriver/core/test/TaskRepositoryTck.java b/clouddriver-core-tck/src/main/java/com/netflix/spinnaker/clouddriver/core/test/TaskRepositoryTck.java index 64d2eb4a065..500a855b5d1 100644 --- a/clouddriver-core-tck/src/main/java/com/netflix/spinnaker/clouddriver/core/test/TaskRepositoryTck.java +++ b/clouddriver-core-tck/src/main/java/com/netflix/spinnaker/clouddriver/core/test/TaskRepositoryTck.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.fasterxml.jackson.annotation.JsonCreator; +import com.netflix.spinnaker.clouddriver.core.ClouddriverHostname; import com.netflix.spinnaker.clouddriver.data.task.Status; import com.netflix.spinnaker.clouddriver.data.task.Task; import com.netflix.spinnaker.clouddriver.data.task.TaskRepository; @@ -181,6 +182,24 @@ public void testClientRequestIdLookup() { assertThat(t1.getId()).isNotEqualTo(t3.getId()); } + @Test + public void testUpdateOwnerIdWithANewOwnerId() { + Task t1 = subject.create("Test", "Test Status", "the-key"); + String newOwnerId = "1234@spin-clouddriver-pod-new"; + assertThat(t1.getOwnerId()).isNotEqualTo(newOwnerId); + t1.updateOwnerId(newOwnerId, "ORCHESTRATION"); + assertThat(t1.getOwnerId()).isEqualTo(newOwnerId); + } + + @Test + public void testUpdateOwnerIdWithSameOwnerId() { + Task t1 = subject.create("Test", "Test Status", "the-key"); + String newOwnerId = ClouddriverHostname.ID; + assertThat(t1.getOwnerId()).isEqualTo(newOwnerId); + t1.updateOwnerId(newOwnerId, "ORCHESTRATION"); + assertThat(t1.getOwnerId()).isEqualTo(newOwnerId); + } + public class TestObject { public String name; public String value; diff --git a/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/orchestration/DefaultOrchestrationProcessor.groovy b/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/orchestration/DefaultOrchestrationProcessor.groovy index a82e29bd471..73a23416755 100644 --- a/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/orchestration/DefaultOrchestrationProcessor.groovy +++ b/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/orchestration/DefaultOrchestrationProcessor.groovy @@ -19,6 +19,7 @@ package com.netflix.spinnaker.clouddriver.orchestration import com.fasterxml.jackson.databind.ObjectMapper import com.google.common.util.concurrent.ThreadFactoryBuilder import com.netflix.spectator.api.Registry +import com.netflix.spinnaker.clouddriver.core.ClouddriverHostname import com.netflix.spinnaker.clouddriver.data.task.Task import com.netflix.spinnaker.clouddriver.data.task.TaskRepository import com.netflix.spinnaker.clouddriver.event.exceptions.DuplicateEventAggregateException @@ -99,6 +100,7 @@ class DefaultOrchestrationProcessor implements OrchestrationProcessor { def result = getTask(clientRequestId) def task = result.task if (!result.shouldExecute) { + log.debug("task with id {} has the shouldExecute flag set to false - not executing the task", task.getId()) return task } @@ -244,6 +246,7 @@ class DefaultOrchestrationProcessor implements OrchestrationProcessor { } existingTask.updateStatus(TASK_PHASE, "Re-initializing Orchestration Task (failure is retryable)") existingTask.retry() + existingTask.updateOwnerId(ClouddriverHostname.ID, TASK_PHASE) return new GetTaskResult(existingTask, true) } return new GetTaskResult( diff --git a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/DefaultTask.java b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/DefaultTask.java index 2ef3f0237c3..864e0161642 100644 --- a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/DefaultTask.java +++ b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/DefaultTask.java @@ -12,7 +12,7 @@ public class DefaultTask implements Task { private static final Logger log = Logger.getLogger(DefaultTask.class.getName()); private final String id; - private final String ownerId = ClouddriverHostname.ID; + private String ownerId = ClouddriverHostname.ID; private final String requestId = null; private final Deque statusHistory = new ConcurrentLinkedDeque(); private final Deque resultObjects = new ConcurrentLinkedDeque(); @@ -100,6 +100,28 @@ public void retry() { statusHistory.addLast(currentStatus().update(TaskState.STARTED)); } + @Override + public void updateOwnerId(String ownerId, String phase) { + if (ownerId == null) { + log.info("new owner id not provided. No update necessary."); + return; + } + + String previousCloudDriverHostname = this.getOwnerId().split("@")[1]; + String currentCloudDriverHostname = ownerId.split("@")[1]; + + if (previousCloudDriverHostname.equals(currentCloudDriverHostname)) { + log.info("new owner id is the same as the previous owner Id. No update necessary."); + return; + } + + String previousOwnerId = this.ownerId; + updateStatus(phase, "Re-assigning task from: " + previousOwnerId + " to: " + ownerId); + this.ownerId = ownerId; + log.info( + "Updated ownerId for task id: " + id + " from: " + previousOwnerId + " to: " + ownerId); + } + public final String getId() { return id; } diff --git a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/Task.java b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/Task.java index 480bfa92268..6ec618e21dd 100644 --- a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/Task.java +++ b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/Task.java @@ -58,7 +58,8 @@ public interface Task { * This method will fail the task and will represent completed = true and failed = true from the * Task's {@link #getStatus()} method. * - * @param retryable If true, the failed state will be marked as retryable (sagas only) + * @param retryable If true, the failed state will be marked as retryable (only for sagas and + * kubernetes tasks) */ void fail(boolean retryable); @@ -88,12 +89,12 @@ public interface Task { /** Returns true if the Task is retryable (in the case of a failure) */ default boolean isRetryable() { - if (!hasSagaIds()) { - return false; - } return getStatus().isFailed() && getStatus().isRetryable(); } /** Updates the status of a failed Task to running in response to a retry operation. */ void retry(); + + // updates the owner id in case the task was picked up by another clouddriver pod + void updateOwnerId(String ownerId, String phase); } diff --git a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/jedis/JedisTask.java b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/jedis/JedisTask.java index a1b987937fa..7b9a7af2a4c 100644 --- a/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/jedis/JedisTask.java +++ b/clouddriver-core/src/main/java/com/netflix/spinnaker/clouddriver/data/task/jedis/JedisTask.java @@ -39,7 +39,7 @@ public class JedisTask implements Task { @JsonIgnore private RedisTaskRepository repository; private final String id; private final long startTimeMs; - private final String ownerId; + private String ownerId; private final String requestId; private final Set sagaIds; @JsonIgnore private final boolean previousRedis; @@ -139,6 +139,29 @@ public void retry() { repository.addToHistory(repository.currentState(this).update(TaskState.STARTED), this); } + @Override + public void updateOwnerId(String ownerId, String phase) { + checkMutable(); + if (ownerId == null) { + log.debug("new owner id not provided. No update necessary."); + return; + } + + String previousCloudDriverHostname = this.getOwnerId().split("@")[1]; + String currentCloudDriverHostname = ownerId.split("@")[1]; + + if (previousCloudDriverHostname.equals(currentCloudDriverHostname)) { + log.debug("new owner id is the same as the previous owner Id. No update necessary."); + return; + } + + String previousOwnerId = this.ownerId; + updateStatus(phase, "Re-assigning task from: " + previousOwnerId + " to: " + ownerId); + this.ownerId = ownerId; + repository.set(this.id, this); + log.debug("Updated ownerId for task id={} from {} to {}", id, previousOwnerId, ownerId); + } + private void checkMutable() { if (previousRedis) { throw new IllegalStateException("Read-only task"); diff --git a/clouddriver-core/src/test/java/com/netflix/spinnaker/clouddriver/data/task/jedis/JedisTaskTest.java b/clouddriver-core/src/test/java/com/netflix/spinnaker/clouddriver/data/task/jedis/JedisTaskTest.java index 65ee365617d..249b4762353 100644 --- a/clouddriver-core/src/test/java/com/netflix/spinnaker/clouddriver/data/task/jedis/JedisTaskTest.java +++ b/clouddriver-core/src/test/java/com/netflix/spinnaker/clouddriver/data/task/jedis/JedisTaskTest.java @@ -67,9 +67,10 @@ void serializationTest() throws Exception { @Test void statusComputedFirst() throws Exception { RedisTaskRepository taskRepository = mock(RedisTaskRepository.class); + JedisTask task = new JedisTask("123", 100, taskRepository, "owner", "requestId", ImmutableSet.of(), false); - + when(taskRepository.currentState(task)).thenReturn(new DefaultTaskStatus(TaskState.STARTED)); objectMapper.writeValueAsString(task); InOrder inOrder = Mockito.inOrder(taskRepository); diff --git a/clouddriver-sql/src/main/kotlin/com/netflix/spinnaker/clouddriver/sql/SqlTask.kt b/clouddriver-sql/src/main/kotlin/com/netflix/spinnaker/clouddriver/sql/SqlTask.kt index fe59e699432..82f2468514f 100644 --- a/clouddriver-sql/src/main/kotlin/com/netflix/spinnaker/clouddriver/sql/SqlTask.kt +++ b/clouddriver-sql/src/main/kotlin/com/netflix/spinnaker/clouddriver/sql/SqlTask.kt @@ -29,7 +29,7 @@ import org.slf4j.LoggerFactory */ class SqlTask( private val id: String, - @JsonIgnore internal val ownerId: String, + @JsonIgnore internal var ownerId: String, @JsonIgnore internal val requestId: String, @JsonIgnore internal val startTimeMs: Long, private val sagaIds: MutableSet, @@ -139,4 +139,26 @@ class SqlTask( } } } + + override fun updateOwnerId(ownerId: String?, phase: String) { + this.dirty.set(true) + if (ownerId == null ) { + log.debug("new owner id not provided. No update necessary.") + return + } + + val previousCloudDriverHostname = this.getOwnerId().split("@")[1] + val currentCloudDriverHostname = ownerId.split("@")[1] + + if (previousCloudDriverHostname == currentCloudDriverHostname) { + log.debug("new owner id is the same as the previous owner Id. No update necessary.") + return + } + + val previousOwnerId = this.ownerId + updateStatus(phase, "Re-assigning task from: $previousOwnerId to: $ownerId") + this.ownerId = ownerId + repository.updateOwnerId(this) + log.debug("Updated ownerId for task id={} from {} to {}", id, previousOwnerId, ownerId) + } } diff --git a/clouddriver-sql/src/main/kotlin/com/netflix/spinnaker/clouddriver/sql/SqlTaskRepository.kt b/clouddriver-sql/src/main/kotlin/com/netflix/spinnaker/clouddriver/sql/SqlTaskRepository.kt index cb61c63a833..badd2fe4cbe 100644 --- a/clouddriver-sql/src/main/kotlin/com/netflix/spinnaker/clouddriver/sql/SqlTaskRepository.kt +++ b/clouddriver-sql/src/main/kotlin/com/netflix/spinnaker/clouddriver/sql/SqlTaskRepository.kt @@ -192,6 +192,17 @@ class SqlTaskRepository( } } + fun updateOwnerId(task: Task) { + return withPool(poolName) { + jooq.transactional { ctx -> + ctx.update(tasksTable) + .set(field("owner_id"), task.ownerId) + .where(field("id").eq(task.id)) + .execute() + } + } + } + internal fun retrieveInternal(taskId: String): Task? { return retrieveInternal(field("id").eq(taskId), field("task_id").eq(taskId)).firstOrNull() } diff --git a/clouddriver-web/src/main/groovy/com/netflix/spinnaker/clouddriver/controllers/OperationsController.groovy b/clouddriver-web/src/main/groovy/com/netflix/spinnaker/clouddriver/controllers/OperationsController.groovy index ec381053379..7d98adfbfd7 100644 --- a/clouddriver-web/src/main/groovy/com/netflix/spinnaker/clouddriver/controllers/OperationsController.groovy +++ b/clouddriver-web/src/main/groovy/com/netflix/spinnaker/clouddriver/controllers/OperationsController.groovy @@ -17,6 +17,7 @@ package com.netflix.spinnaker.clouddriver.controllers import com.fasterxml.jackson.annotation.JsonProperty +import com.google.common.collect.ImmutableList import com.netflix.spinnaker.clouddriver.data.task.Task import com.netflix.spinnaker.clouddriver.data.task.TaskRepository import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation @@ -27,6 +28,7 @@ import com.netflix.spinnaker.kork.web.exceptions.NotFoundException import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Value import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PatchMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -36,6 +38,7 @@ import org.springframework.web.bind.annotation.RestController import javax.annotation.Nonnull import javax.annotation.Nullable import javax.annotation.PreDestroy +import javax.naming.OperationNotSupportedException import java.util.concurrent.TimeUnit import static java.lang.String.format @@ -60,8 +63,8 @@ class OperationsController { this.shutdownWaitSeconds = shutdownWaitSeconds } /** - * @deprecated Use /{cloudProvider}/ops instead - */ + * @deprecated Use /{cloudProvider}/ops instead + */ @Deprecated @PostMapping("/ops") StartOperationResult operations( @@ -103,6 +106,67 @@ class OperationsController { return start(cloudProvider, atomicOperations, clientRequestId) } + @PatchMapping("/{cloudProvider}/task/{id}") + StartOperationResult updateTask(@PathVariable("cloudProvider") String cloudProvider, + @PathVariable("id") String id, + @RequestBody Map requestBody) { + validateCloudProvider(cloudProvider, ImmutableList.of("kubernetes")) + + Optional doRetry = requestBody.entrySet() + .stream() + .filter({ e -> e.getKey().equals("retry") }) + .map({ e -> (Boolean)e.getValue() }) + .findFirst(); + + if (doRetry.isEmpty()) { + throw new OperationNotSupportedException("Patching task id: ${id} with the provided inputs is not supported") + } + + Task t = taskRepository.get(id) + if (!t) { + throw new NotFoundException("Task not found (id: ${id})" + ) + } + + log.debug("updating task: ${t.id} state to retry: ${doRetry.get()}") + t.fail(doRetry.get()) + return new StartOperationResult(t.id) + } + + @GetMapping("/{cloudProvider}/task/{id}/owner") + TaskOwnerResult getOwnerName(@PathVariable("cloudProvider") String cloudProvider, + @PathVariable("id") String id) { + validateCloudProvider(cloudProvider, ImmutableList.of("kubernetes")) + + Task t = taskRepository.get(id) + if (!t) { + throw new NotFoundException("Task not found (id: ${id})") + } + return new TaskOwnerResult(t.getOwnerId().split("@")[1]) + } + + @PostMapping("/{cloudProvider}/task/{id}/restart") + StartOperationResult restartCloudProviderTask( + @PathVariable("cloudProvider") String cloudProvider, + @PathVariable("id") String id, + @RequestBody List> requestBody) { + validateCloudProvider(cloudProvider, ImmutableList.of("kubernetes")) + Task t = taskRepository.get(id) + if (t == null) { + throw new NotFoundException("Task not found (id: $id)") + } + + if (!t.status.retryable) { + throw new ConstraintViolationException("Task id: $id is not retryable").with { + setRetryable(false) + it + } + } + log.debug("restarting task: ${t.id}") + List atomicOperations = operationsService.collectAtomicOperations(cloudProvider, requestBody) + return start(atomicOperations, t.requestId) + } + @GetMapping("/task/{id}") Task get(@PathVariable("id") String id) { Task t = taskRepository.get(id) @@ -173,7 +237,8 @@ class OperationsController { } static class StartOperationResult { - @JsonProperty private final String id + @JsonProperty + private final String id StartOperationResult(String id) { this.id = id @@ -184,4 +249,20 @@ class OperationsController { return format("/task/%s", id) } } + + static class TaskOwnerResult { + @JsonProperty + private final String name + + TaskOwnerResult(String name) { + this.name = name + } + } + + private void validateCloudProvider(String inputCloudProvider, List supportedCloudProviders) { + if (inputCloudProvider == null || !supportedCloudProviders.contains(inputCloudProvider)) { + throw new UnsupportedOperationException("updating Task (id: $id) information not supported via this " + + "endpoint for cloudprovider: ${inputCloudProvider}. Supported cloudproviders are: ${supportedCloudProviders}") + } + } } From 7f7181642ecc0fd620bd8154a416806e87de2cab Mon Sep 17 00:00:00 2001 From: spinnakerbot Date: Thu, 8 Dec 2022 20:26:47 -0500 Subject: [PATCH 06/10] chore(dependencies): Autobump korkVersion (#5836) Co-authored-by: root --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4edcee2b0fd..ac587b9b615 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ fiatVersion=1.36.0 -korkVersion=7.153.0 +korkVersion=7.154.0 org.gradle.parallel=true spinnakerGradleVersion=8.24.0 targetJava11=true From 7caed07a384dd24626458b76e629a0a90bbbb634 Mon Sep 17 00:00:00 2001 From: spinnakerbot Date: Thu, 8 Dec 2022 21:30:30 -0500 Subject: [PATCH 07/10] chore(dependencies): Autobump korkVersion (#5837) Co-authored-by: root --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ac587b9b615..91da6d05c0b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ fiatVersion=1.36.0 -korkVersion=7.154.0 +korkVersion=7.155.0 org.gradle.parallel=true spinnakerGradleVersion=8.24.0 targetJava11=true From ecb75d0b1e066d4811a978c93b1e7d1f292ec382 Mon Sep 17 00:00:00 2001 From: spinnakerbot Date: Thu, 8 Dec 2022 22:36:49 -0500 Subject: [PATCH 08/10] chore(dependencies): Autobump korkVersion (#5838) Co-authored-by: root --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 91da6d05c0b..1ad1afef4a1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ fiatVersion=1.36.0 -korkVersion=7.155.0 +korkVersion=7.156.0 org.gradle.parallel=true spinnakerGradleVersion=8.24.0 targetJava11=true From 915e3c42dcf4394c8632a41d7d63d1c677b36d1d Mon Sep 17 00:00:00 2001 From: spinnakerbot Date: Fri, 9 Dec 2022 01:40:08 -0500 Subject: [PATCH 09/10] chore(dependencies): Autobump korkVersion (#5839) Co-authored-by: root --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 1ad1afef4a1..197b0b30da2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ fiatVersion=1.36.0 -korkVersion=7.156.0 +korkVersion=7.157.0 org.gradle.parallel=true spinnakerGradleVersion=8.24.0 targetJava11=true From 7294894a118d09e6ea141d14d6934f2cf0e01cec Mon Sep 17 00:00:00 2001 From: David Byron <82477955+dbyron-sf@users.noreply.github.com> Date: Fri, 9 Dec 2022 09:04:05 -0800 Subject: [PATCH 10/10] chore(dependencies): remove dependency on groovy-all where straightforward (#5840) with a specific goal to get org.testng:testng:7.4.0 out of shipping code, since it's vulnerable to CVE-2022-4065. Note: groovy-all and testng remain in clouddriver-yandex, but they're gone (besides test configurations) elsewhere. test(integration): test red/black deployment test(integration): test blue/green deployment --- cats/cats-core/cats-core.gradle | 2 +- cats/cats-redis/cats-redis.gradle | 1 + cats/cats-test/cats-test.gradle | 2 +- cats/cats.gradle | 2 +- .../clouddriver-alicloud.gradle | 2 +- .../clouddriver-appengine.gradle | 3 +- .../clouddriver-artifacts.gradle | 2 +- clouddriver-aws/clouddriver-aws.gradle | 2 +- clouddriver-azure/clouddriver-azure.gradle | 3 +- .../clouddriver-cloudfoundry.gradle | 2 +- .../clouddriver-cloudrun.gradle | 2 +- clouddriver-consul/clouddriver-consul.gradle | 3 +- .../clouddriver-core-tck.gradle | 2 +- clouddriver-core/clouddriver-core.gradle | 3 +- clouddriver-dcos/clouddriver-dcos.gradle | 2 +- clouddriver-docker/clouddriver-docker.gradle | 2 +- clouddriver-ecs/clouddriver-ecs.gradle | 2 +- .../clouddriver-elasticsearch.gradle | 2 +- clouddriver-eureka/clouddriver-eureka.gradle | 2 +- .../clouddriver-google-common.gradle | 2 +- clouddriver-google/clouddriver-google.gradle | 3 +- .../clouddriver-huaweicloud.gradle | 2 +- .../clouddriver-kubernetes.gradle | 2 +- .../kubernetes/it/DeployManifestIT.java | 150 +++++++++++++++++- clouddriver-lambda/clouddriver-lambda.gradle | 2 +- clouddriver-oracle/clouddriver-oracle.gradle | 3 +- .../clouddriver-security.gradle | 2 +- clouddriver-titus/clouddriver-titus.gradle | 3 +- clouddriver-web/clouddriver-web.gradle | 2 +- clouddriver-yandex/clouddriver-yandex.gradle | 2 +- 30 files changed, 184 insertions(+), 30 deletions(-) diff --git a/cats/cats-core/cats-core.gradle b/cats/cats-core/cats-core.gradle index d0b67d17a32..eb6523580aa 100644 --- a/cats/cats-core/cats-core.gradle +++ b/cats/cats-core/cats-core.gradle @@ -3,7 +3,7 @@ dependencies { implementation "org.slf4j:slf4j-api" implementation "com.fasterxml.jackson.core:jackson-annotations" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "com.google.guava:guava" compileOnly "org.projectlombok:lombok" diff --git a/cats/cats-redis/cats-redis.gradle b/cats/cats-redis/cats-redis.gradle index a648fffc733..69c7dbb8e34 100644 --- a/cats/cats-redis/cats-redis.gradle +++ b/cats/cats-redis/cats-redis.gradle @@ -5,6 +5,7 @@ dependencies { annotationProcessor "org.projectlombok:lombok" testAnnotationProcessor "org.projectlombok:lombok" + implementation "org.codehaus.groovy:groovy" implementation "com.fasterxml.jackson.core:jackson-databind" implementation "io.spinnaker.kork:kork-jedis" implementation "com.github.ben-manes.caffeine:guava" diff --git a/cats/cats-test/cats-test.gradle b/cats/cats-test/cats-test.gradle index 0457820a350..be24d4b4275 100644 --- a/cats/cats-test/cats-test.gradle +++ b/cats/cats-test/cats-test.gradle @@ -2,7 +2,7 @@ tasks.compileGroovy.enabled = true dependencies { implementation project(":cats:cats-core") - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.springframework.boot:spring-boot-starter-test" implementation "org.spockframework:spock-core" diff --git a/cats/cats.gradle b/cats/cats.gradle index 1423d76affd..da9cf367101 100644 --- a/cats/cats.gradle +++ b/cats/cats.gradle @@ -4,6 +4,6 @@ subprojects { dependencies { implementation project(":clouddriver-api") - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" } } diff --git a/clouddriver-alicloud/clouddriver-alicloud.gradle b/clouddriver-alicloud/clouddriver-alicloud.gradle index f90843a28e8..9ae1542f847 100644 --- a/clouddriver-alicloud/clouddriver-alicloud.gradle +++ b/clouddriver-alicloud/clouddriver-alicloud.gradle @@ -28,7 +28,7 @@ dependencies { implementation 'com.aliyun:aliyun-java-sdk-ess:2.3.2' implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.apache.commons:commons-lang3" testImplementation "cglib:cglib-nodep" diff --git a/clouddriver-appengine/clouddriver-appengine.gradle b/clouddriver-appengine/clouddriver-appengine.gradle index 730cce72750..ab9047c61a7 100644 --- a/clouddriver-appengine/clouddriver-appengine.gradle +++ b/clouddriver-appengine/clouddriver-appengine.gradle @@ -27,7 +27,8 @@ dependencies { implementation "com.squareup.retrofit:retrofit" implementation "commons-io:commons-io" implementation "org.apache.commons:commons-compress:1.21" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" + implementation "org.codehaus.groovy:groovy-json" implementation "org.eclipse.jgit:org.eclipse.jgit:5.7.0.202003110725-r" implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" diff --git a/clouddriver-artifacts/clouddriver-artifacts.gradle b/clouddriver-artifacts/clouddriver-artifacts.gradle index d725d3912b3..e6133e25bfb 100644 --- a/clouddriver-artifacts/clouddriver-artifacts.gradle +++ b/clouddriver-artifacts/clouddriver-artifacts.gradle @@ -51,7 +51,7 @@ dependencies { implementation "org.apache.commons:commons-lang3" implementation "org.apache.ivy:ivy:2.4.0" implementation "org.apache.maven:maven-resolver-provider:3.5.4" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" diff --git a/clouddriver-aws/clouddriver-aws.gradle b/clouddriver-aws/clouddriver-aws.gradle index 49298a30815..99143b9ae79 100644 --- a/clouddriver-aws/clouddriver-aws.gradle +++ b/clouddriver-aws/clouddriver-aws.gradle @@ -52,7 +52,7 @@ dependencies { implementation "io.reactivex:rxjava" implementation "org.apache.httpcomponents:httpclient" implementation "org.apache.httpcomponents:httpcore" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" implementation 'com.aestasit.infrastructure.sshoogr:sshoogr:0.9.25' diff --git a/clouddriver-azure/clouddriver-azure.gradle b/clouddriver-azure/clouddriver-azure.gradle index 4a2833a4eec..b2bbdc4bd56 100644 --- a/clouddriver-azure/clouddriver-azure.gradle +++ b/clouddriver-azure/clouddriver-azure.gradle @@ -10,7 +10,7 @@ dependencies { implementation "io.spinnaker.fiat:fiat-core:$fiatVersion" implementation "io.spinnaker.kork:kork-exceptions" implementation "io.spinnaker.kork:kork-moniker" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" implementation "com.azure.resourcemanager:azure-resourcemanager:2.19.0" @@ -22,6 +22,7 @@ dependencies { implementation "com.google.guava:guava:31.1-jre" testImplementation "cglib:cglib-nodep" + testImplementation "org.junit.jupiter:junit-jupiter-api" testImplementation "org.objenesis:objenesis" testImplementation "org.spockframework:spock-core" testImplementation "org.spockframework:spock-spring" diff --git a/clouddriver-cloudfoundry/clouddriver-cloudfoundry.gradle b/clouddriver-cloudfoundry/clouddriver-cloudfoundry.gradle index b0b2c359d8f..e5abaf458a8 100644 --- a/clouddriver-cloudfoundry/clouddriver-cloudfoundry.gradle +++ b/clouddriver-cloudfoundry/clouddriver-cloudfoundry.gradle @@ -39,7 +39,7 @@ dependencies { annotationProcessor "org.projectlombok:lombok" testAnnotationProcessor "org.projectlombok:lombok" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "io.spinnaker.fiat:fiat-core:$fiatVersion" implementation "org.apache.commons:commons-lang3" diff --git a/clouddriver-cloudrun/clouddriver-cloudrun.gradle b/clouddriver-cloudrun/clouddriver-cloudrun.gradle index 53ef8b9714a..9639d61f90d 100644 --- a/clouddriver-cloudrun/clouddriver-cloudrun.gradle +++ b/clouddriver-cloudrun/clouddriver-cloudrun.gradle @@ -27,7 +27,7 @@ dependencies { implementation "com.squareup.retrofit:retrofit" implementation "commons-io:commons-io" implementation "org.apache.commons:commons-compress:1.20" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.eclipse.jgit:org.eclipse.jgit:5.7.0.202003110725-r" implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" diff --git a/clouddriver-consul/clouddriver-consul.gradle b/clouddriver-consul/clouddriver-consul.gradle index 7a7eae96052..81b31fe1b53 100644 --- a/clouddriver-consul/clouddriver-consul.gradle +++ b/clouddriver-consul/clouddriver-consul.gradle @@ -3,7 +3,8 @@ dependencies { implementation "com.squareup.okhttp:okhttp" implementation "com.squareup.retrofit:retrofit" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" + implementation "org.codehaus.groovy:groovy-json" implementation "org.springframework.boot:spring-boot-starter-web" testImplementation "cglib:cglib-nodep" diff --git a/clouddriver-core-tck/clouddriver-core-tck.gradle b/clouddriver-core-tck/clouddriver-core-tck.gradle index 1367c087c84..856e94b7706 100644 --- a/clouddriver-core-tck/clouddriver-core-tck.gradle +++ b/clouddriver-core-tck/clouddriver-core-tck.gradle @@ -5,5 +5,5 @@ dependencies { implementation "junit:junit" implementation "org.apache.commons:commons-lang3" implementation "org.assertj:assertj-core" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" } diff --git a/clouddriver-core/clouddriver-core.gradle b/clouddriver-core/clouddriver-core.gradle index 77b0ef0a4a4..1b7c2683021 100644 --- a/clouddriver-core/clouddriver-core.gradle +++ b/clouddriver-core/clouddriver-core.gradle @@ -44,7 +44,8 @@ dependencies { implementation "io.reactivex:rxjava" implementation "net.jodah:failsafe:1.0.4" implementation "org.apache.commons:commons-exec" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" + implementation "org.codehaus.groovy:groovy-templates" implementation "org.springframework.boot:spring-boot-actuator" implementation "redis.clients:jedis" implementation "org.jooq:jooq" diff --git a/clouddriver-dcos/clouddriver-dcos.gradle b/clouddriver-dcos/clouddriver-dcos.gradle index 4aaa01b55ba..33cc26ce08b 100644 --- a/clouddriver-dcos/clouddriver-dcos.gradle +++ b/clouddriver-dcos/clouddriver-dcos.gradle @@ -11,7 +11,7 @@ dependencies { implementation "io.spinnaker.fiat:fiat-core:$fiatVersion" implementation "io.spinnaker.kork:kork-moniker" implementation "joda-time:joda-time:2.10.1" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" implementation 'com.cerner.marathon:marathon-client:0.6.3' diff --git a/clouddriver-docker/clouddriver-docker.gradle b/clouddriver-docker/clouddriver-docker.gradle index 61fe9ebabc4..40d70bc2dfd 100644 --- a/clouddriver-docker/clouddriver-docker.gradle +++ b/clouddriver-docker/clouddriver-docker.gradle @@ -10,7 +10,7 @@ dependencies { implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" implementation "org.springframework.cloud:spring-cloud-context" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "com.google.guava:guava" implementation "com.netflix.spectator:spectator-api" implementation "com.squareup.okhttp:okhttp" diff --git a/clouddriver-ecs/clouddriver-ecs.gradle b/clouddriver-ecs/clouddriver-ecs.gradle index 9400167b44b..576147ef360 100644 --- a/clouddriver-ecs/clouddriver-ecs.gradle +++ b/clouddriver-ecs/clouddriver-ecs.gradle @@ -44,7 +44,7 @@ dependencies { implementation "org.apache.commons:commons-lang3" implementation "org.apache.httpcomponents:httpclient" implementation "org.apache.httpcomponents:httpcore" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.springframework.boot:spring-boot-starter-test" implementation "org.springframework.boot:spring-boot-starter-web" diff --git a/clouddriver-elasticsearch/clouddriver-elasticsearch.gradle b/clouddriver-elasticsearch/clouddriver-elasticsearch.gradle index 4a4e83df9c8..e4f1afd4d89 100644 --- a/clouddriver-elasticsearch/clouddriver-elasticsearch.gradle +++ b/clouddriver-elasticsearch/clouddriver-elasticsearch.gradle @@ -9,7 +9,7 @@ dependencies { implementation "io.spinnaker.kork:kork-retrofit" implementation "io.spinnaker.kork:kork-security" implementation "com.squareup.retrofit:retrofit" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.elasticsearch:elasticsearch" implementation "org.springframework.boot:spring-boot-starter-web" diff --git a/clouddriver-eureka/clouddriver-eureka.gradle b/clouddriver-eureka/clouddriver-eureka.gradle index 2f8df28fef5..69923a35b3d 100644 --- a/clouddriver-eureka/clouddriver-eureka.gradle +++ b/clouddriver-eureka/clouddriver-eureka.gradle @@ -8,7 +8,7 @@ dependencies { implementation "com.amazonaws:aws-java-sdk" implementation "com.squareup.retrofit:converter-jackson" implementation "com.squareup.retrofit:retrofit" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.springframework.boot:spring-boot-starter-web" testImplementation "cglib:cglib-nodep" diff --git a/clouddriver-google-common/clouddriver-google-common.gradle b/clouddriver-google-common/clouddriver-google-common.gradle index 293079e0523..0e2b4d11bcf 100644 --- a/clouddriver-google-common/clouddriver-google-common.gradle +++ b/clouddriver-google-common/clouddriver-google-common.gradle @@ -11,7 +11,7 @@ dependencies { implementation "com.netflix.spectator:spectator-api" implementation "io.spinnaker.fiat:fiat-api:$fiatVersion" implementation "io.spinnaker.fiat:fiat-core:$fiatVersion" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.slf4j:slf4j-api" implementation "org.springframework.security:spring-security-config" implementation "org.springframework.security:spring-security-core" diff --git a/clouddriver-google/clouddriver-google.gradle b/clouddriver-google/clouddriver-google.gradle index 1867c29f058..13cb3fc6514 100644 --- a/clouddriver-google/clouddriver-google.gradle +++ b/clouddriver-google/clouddriver-google.gradle @@ -11,7 +11,8 @@ dependencies { annotationProcessor "org.projectlombok:lombok" testAnnotationProcessor "org.projectlombok:lombok" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" + implementation "org.codehaus.groovy:groovy-json" implementation "org.apache.commons:commons-lang3" implementation ("com.google.apis:google-api-services-compute:beta-rev20201102-1.30.10") { force = true diff --git a/clouddriver-huaweicloud/clouddriver-huaweicloud.gradle b/clouddriver-huaweicloud/clouddriver-huaweicloud.gradle index 9d65ee9a1df..d404e465953 100644 --- a/clouddriver-huaweicloud/clouddriver-huaweicloud.gradle +++ b/clouddriver-huaweicloud/clouddriver-huaweicloud.gradle @@ -21,7 +21,7 @@ dependencies { } implementation "org.glassfish.jersey.core:jersey-client:2.22.1" implementation "org.glassfish.jersey.media:jersey-media-json-jackson:2.11" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" implementation 'org.apache.commons:commons-lang3' diff --git a/clouddriver-kubernetes/clouddriver-kubernetes.gradle b/clouddriver-kubernetes/clouddriver-kubernetes.gradle index 8d8c06fe804..4fcfca306b4 100644 --- a/clouddriver-kubernetes/clouddriver-kubernetes.gradle +++ b/clouddriver-kubernetes/clouddriver-kubernetes.gradle @@ -61,7 +61,7 @@ dependencies { annotationProcessor "org.projectlombok:lombok" testAnnotationProcessor "org.projectlombok:lombok" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "com.google.code.findbugs:jsr305" implementation "com.google.guava:guava" diff --git a/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java b/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java index d6a343e7704..268885be9c0 100644 --- a/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java +++ b/clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java @@ -35,11 +35,9 @@ import org.junit.jupiter.api.Test; public class DeployManifestIT extends BaseTest { - private static final String DEPLOYMENT_1_NAME = "deployment1"; private static final String REPLICASET_1_NAME = "rs1"; private static final String SERVICE_1_NAME = "service1"; - private static final String SERVICE_2_NAME = "service2"; private static String account1Ns; @@ -1351,6 +1349,154 @@ public void shouldDeployBlueGreenReplicaSet() throws IOException, InterruptedExc 1, podNames.size(), "Only one pod expected to have the label for traffic selection"); } + @DisplayName( + ".\n===\n" + + "Given a deployment yaml with red/black deployment traffic strategy\n" + + " and an existing service\n" + + "When sending deploy manifest request two times\n" + + " And sending disable manifest one time\n" + + "Then there are two deployments with only the last one receiving traffic\n===") + @Test + public void shouldDeployRedBlackDeployment() throws IOException, InterruptedException { + // ------------------------- given -------------------------- + String appName = "red-black"; + System.out.println("> Using namespace: " + account1Ns + ", appName: " + appName); + String selectorValue = appName + "traffichere"; + + Map service = + KubeTestUtils.loadYaml("classpath:manifests/service.yml") + .withValue("metadata.namespace", account1Ns) + .withValue("metadata.name", SERVICE_2_NAME) + .withValue("spec.selector", ImmutableMap.of("pointer", selectorValue)) + .withValue("spec.type", "NodePort") + .asMap(); + kubeCluster.execKubectl("-n " + account1Ns + " apply -f -", service); + + List> manifest = + KubeTestUtils.loadYaml("classpath:manifests/deployment.yml") + .withValue("metadata.namespace", account1Ns) + .withValue("metadata.name", appName) + .withValue("spec.selector.matchLabels", ImmutableMap.of("label1", "value1")) + .withValue("spec.template.metadata.labels", ImmutableMap.of("label1", "value1")) + .asList(); + + // ------------------------- when -------------------------- + List> body = + KubeTestUtils.loadJson("classpath:requests/deploy_manifest.json") + .withValue("deployManifest.account", ACCOUNT1_NAME) + .withValue("deployManifest.moniker.app", appName) + .withValue("deployManifest.manifests", manifest) + .withValue( + "deployManifest.services", Collections.singleton("service " + SERVICE_2_NAME)) + .withValue("deployManifest.strategy", "RED_BLACK") + .withValue("deployManifest.trafficManagement.enabled", true) + .withValue("deployManifest.trafficManagement.options.strategy", "redblack") + .withValue("deployManifest.trafficManagement.options.enableTraffic", true) + .withValue("deployManifest.trafficManagement.options.namespace", account1Ns) + .withValue( + "deployManifest.trafficManagement.options.services", + Collections.singleton("service " + appName)) + .asList(); + KubeTestUtils.deployAndWaitStable( + baseUrl(), body, account1Ns, "deployment " + appName + "-v000"); + KubeTestUtils.deployAndWaitStable( + baseUrl(), body, account1Ns, "deployment " + appName + "-v001"); + body = + KubeTestUtils.loadJson("classpath:requests/disable_manifest.json") + .withValue("disableManifest.app", appName) + .withValue("disableManifest.manifestName", "deployment " + appName + "-v000") + .withValue("disableManifest.location", account1Ns) + .withValue("disableManifest.account", ACCOUNT1_NAME) + .asList(); + KubeTestUtils.disableManifest(baseUrl(), body, account1Ns, "deployment " + appName + "-v000"); + + // ------------------------- then -------------------------- + List podNames = + Splitter.on(" ") + .splitToList( + kubeCluster.execKubectl( + "-n " + + account1Ns + + " get pod -o=jsonpath='{.items[*].metadata.name}' -l=pointer=" + + selectorValue)); + assertEquals( + 1, podNames.size(), "Only one pod expected to have the label for traffic selection"); + } + + @DisplayName( + ".\n===\n" + + "Given a deployment yaml with blue/green deployment traffic strategy\n" + + " and an existing service\n" + + "When sending deploy manifest request two times\n" + + " And sending disable manifest one time\n" + + "Then there are two deployments with only the last one receiving traffic\n===") + @Test + public void shouldDeployBlueGreenDeployment() throws IOException, InterruptedException { + // ------------------------- given -------------------------- + String appName = "blue-green"; + System.out.println("> Using namespace: " + account1Ns + ", appName: " + appName); + String selectorValue = appName + "traffichere"; + + Map service = + KubeTestUtils.loadYaml("classpath:manifests/service.yml") + .withValue("metadata.namespace", account1Ns) + .withValue("metadata.name", SERVICE_2_NAME) + .withValue("spec.selector", ImmutableMap.of("pointer", selectorValue)) + .withValue("spec.type", "NodePort") + .asMap(); + kubeCluster.execKubectl("-n " + account1Ns + " apply -f -", service); + + List> manifest = + KubeTestUtils.loadYaml("classpath:manifests/deployment.yml") + .withValue("metadata.namespace", account1Ns) + .withValue("metadata.name", appName) + .withValue("spec.selector.matchLabels", ImmutableMap.of("label1", "value1")) + .withValue("spec.template.metadata.labels", ImmutableMap.of("label1", "value1")) + .asList(); + + // ------------------------- when -------------------------- + List> body = + KubeTestUtils.loadJson("classpath:requests/deploy_manifest.json") + .withValue("deployManifest.account", ACCOUNT1_NAME) + .withValue("deployManifest.moniker.app", appName) + .withValue("deployManifest.manifests", manifest) + .withValue( + "deployManifest.services", Collections.singleton("service " + SERVICE_2_NAME)) + .withValue("deployManifest.strategy", "BLUE_GREEN") + .withValue("deployManifest.trafficManagement.enabled", true) + .withValue("deployManifest.trafficManagement.options.strategy", "bluegreen") + .withValue("deployManifest.trafficManagement.options.enableTraffic", true) + .withValue("deployManifest.trafficManagement.options.namespace", account1Ns) + .withValue( + "deployManifest.trafficManagement.options.services", + Collections.singleton("service " + appName)) + .asList(); + KubeTestUtils.deployAndWaitStable( + baseUrl(), body, account1Ns, "deployment " + appName + "-v000"); + KubeTestUtils.deployAndWaitStable( + baseUrl(), body, account1Ns, "deployment " + appName + "-v001"); + body = + KubeTestUtils.loadJson("classpath:requests/disable_manifest.json") + .withValue("disableManifest.app", appName) + .withValue("disableManifest.manifestName", "deployment " + appName + "-v000") + .withValue("disableManifest.location", account1Ns) + .withValue("disableManifest.account", ACCOUNT1_NAME) + .asList(); + KubeTestUtils.disableManifest(baseUrl(), body, account1Ns, "deployment " + appName + "-v000"); + + // ------------------------- then -------------------------- + List podNames = + Splitter.on(" ") + .splitToList( + kubeCluster.execKubectl( + "-n " + + account1Ns + + " get pod -o=jsonpath='{.items[*].metadata.name}' -l=pointer=" + + selectorValue)); + assertEquals( + 1, podNames.size(), "Only one pod expected to have the label for traffic selection"); + } + @DisplayName( ".\n===\n" + "Given a cron job manifest without image tag\n" diff --git a/clouddriver-lambda/clouddriver-lambda.gradle b/clouddriver-lambda/clouddriver-lambda.gradle index 670ee4fd751..3bbe23ba125 100644 --- a/clouddriver-lambda/clouddriver-lambda.gradle +++ b/clouddriver-lambda/clouddriver-lambda.gradle @@ -22,7 +22,7 @@ dependencies { implementation "org.apache.httpcomponents:httpclient" implementation "org.apache.httpcomponents:httpcore" implementation "org.apache.commons:commons-compress:1.20" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.springframework.boot:spring-boot-starter-web" implementation "com.squareup.okhttp:okhttp" implementation "com.squareup.okhttp:okhttp-apache" diff --git a/clouddriver-oracle/clouddriver-oracle.gradle b/clouddriver-oracle/clouddriver-oracle.gradle index f9abe4efda5..62736f4662b 100644 --- a/clouddriver-oracle/clouddriver-oracle.gradle +++ b/clouddriver-oracle/clouddriver-oracle.gradle @@ -18,11 +18,12 @@ dependencies { implementation "com.oracle.oci.sdk:oci-java-sdk-identity" implementation "com.oracle.oci.sdk:oci-java-sdk-loadbalancer" implementation "com.oracle.oci.sdk:oci-java-sdk-objectstorage" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.springframework.boot:spring-boot-starter-actuator" implementation "org.springframework.boot:spring-boot-starter-web" testImplementation "cglib:cglib-nodep" + testImplementation "org.codehaus.groovy:groovy-all" testImplementation "org.objenesis:objenesis" testImplementation "org.spockframework:spock-core" testImplementation "org.spockframework:spock-spring" diff --git a/clouddriver-security/clouddriver-security.gradle b/clouddriver-security/clouddriver-security.gradle index 449e2097575..7ea4855c12e 100644 --- a/clouddriver-security/clouddriver-security.gradle +++ b/clouddriver-security/clouddriver-security.gradle @@ -9,7 +9,7 @@ dependencies { implementation "io.spinnaker.fiat:fiat-api:$fiatVersion" implementation "io.spinnaker.fiat:fiat-core:$fiatVersion" implementation "io.spinnaker.kork:kork-core" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.slf4j:jcl-over-slf4j" implementation "org.springframework.boot:spring-boot-starter-web" implementation "com.github.ben-manes.caffeine:guava" diff --git a/clouddriver-titus/clouddriver-titus.gradle b/clouddriver-titus/clouddriver-titus.gradle index fb5698ba306..759f2c8d793 100644 --- a/clouddriver-titus/clouddriver-titus.gradle +++ b/clouddriver-titus/clouddriver-titus.gradle @@ -37,7 +37,8 @@ dependencies { implementation "io.grpc:grpc-netty-shaded:$grpcVersion" implementation "io.grpc:grpc-protobuf:$grpcVersion" implementation "io.grpc:grpc-stub:$grpcVersion" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" + implementation "org.codehaus.groovy:groovy-json" implementation "org.slf4j:slf4j-api" implementation "org.springframework.boot:spring-boot-actuator" implementation "org.springframework.boot:spring-boot-starter-web" diff --git a/clouddriver-web/clouddriver-web.gradle b/clouddriver-web/clouddriver-web.gradle index b196dbefeee..dd4b70efcf6 100644 --- a/clouddriver-web/clouddriver-web.gradle +++ b/clouddriver-web/clouddriver-web.gradle @@ -36,7 +36,7 @@ dependencies { implementation "commons-io:commons-io" implementation "io.reactivex:rxjava" implementation "io.swagger:swagger-annotations" - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy" implementation "org.slf4j:slf4j-api" implementation "org.springframework.boot:spring-boot-starter-actuator" implementation "org.springframework.boot:spring-boot-starter-json" diff --git a/clouddriver-yandex/clouddriver-yandex.gradle b/clouddriver-yandex/clouddriver-yandex.gradle index f7770ee9931..94eee23546b 100644 --- a/clouddriver-yandex/clouddriver-yandex.gradle +++ b/clouddriver-yandex/clouddriver-yandex.gradle @@ -20,7 +20,7 @@ dependencies { compile("io.opencensus:opencensus-contrib-grpc-metrics:0.21.0") { force = true } - implementation "org.codehaus.groovy:groovy-all" + implementation "org.codehaus.groovy:groovy-all" // for at least org.apache.groovy.datetime.extensions.DateTimeExtensions implementation "org.apache.commons:commons-lang3" implementation "com.netflix.frigga:frigga" implementation "com.netflix.spectator:spectator-api"