feat(kubernetes): support rolling restart operation for deployments
MODIFY
clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/orchestration/AtomicOperations.java
clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/orchestration/AtomicOperations.java
@@ -87,6 +87,7 @@ public final class AtomicOperations {
public static final String PAUSE_ROLLOUT_MANIFEST = "pauseRolloutManifest";
public static final String RESUME_ROLLOUT_MANIFEST = "resumeRolloutManifest";
public static final String UNDO_ROLLOUT_MANIFEST = "undoRolloutManifest";
+ public static final String ROLLING_RESTART_MANIFEST = "rollingRestartManifest";
public static final String DISABLE_MANIFEST = "disableManifest";
public static final String ENABLE_MANIFEST = "enableManifest";

ADD
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/converter/manifest/KubernetesRollingRestartManifestConverter.java
+/*
+ * Copyright 2019 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.netflix.spinnaker.clouddriver.kubernetes.v2.converter.manifest;
+
+import static com.netflix.spinnaker.clouddriver.orchestration.AtomicOperations.ROLLING_RESTART_MANIFEST;
+
+import com.netflix.spinnaker.clouddriver.kubernetes.KubernetesOperation;
+import com.netflix.spinnaker.clouddriver.kubernetes.deploy.converters.KubernetesAtomicOperationConverterHelper;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesRollingRestartManifestDescription;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.manifest.KubernetesRollingRestartManifestOperation;
+import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation;
+import com.netflix.spinnaker.clouddriver.security.AbstractAtomicOperationsCredentialsSupport;
+import com.netflix.spinnaker.clouddriver.security.ProviderVersion;
+import java.util.Map;
+import org.springframework.stereotype.Component;
+
+@KubernetesOperation(ROLLING_RESTART_MANIFEST)
+@Component
+public class KubernetesRollingRestartManifestConverter
+ extends AbstractAtomicOperationsCredentialsSupport {
+ @Override
+ public AtomicOperation convertOperation(Map input) {
+ return new KubernetesRollingRestartManifestOperation(convertDescription(input));
+ }
+
+ @Override
+ public KubernetesRollingRestartManifestDescription convertDescription(Map input) {
+ return KubernetesAtomicOperationConverterHelper.convertDescription(
+ input, this, KubernetesRollingRestartManifestDescription.class);
+ }
+
+ @Override
+ public boolean acceptsVersion(ProviderVersion version) {
+ return version == ProviderVersion.v2;
+ }
+}

ADD
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/description/manifest/KubernetesRollingRestartManifestDescription.java
+/*
+ * Copyright 2019 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class KubernetesRollingRestartManifestDescription
+ extends KubernetesManifestOperationDescription {}

ADD
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/CanRollingRestart.java
+/*
+ * Copyright 2019 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler;
+
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesKind;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.security.KubernetesV2Credentials;
+
+public interface CanRollingRestart {
+ KubernetesKind kind();
+
+ default void rollingRestart(KubernetesV2Credentials credentials, String namespace, String name) {
+ credentials.rollingRestart(kind(), namespace, name);
+ }
+}

MODIFY
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDaemonSetHandler.java
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDaemonSetHandler.java
@@ -38,7 +38,12 @@ import org.springframework.stereotype.Component;
@Component
public class KubernetesDaemonSetHandler extends KubernetesHandler
- implements CanResize, CanPauseRollout, CanResumeRollout, CanUndoRollout, ServerGroupHandler {
+ implements CanResize,
+ CanPauseRollout,
+ CanResumeRollout,
+ CanUndoRollout,
+ CanRollingRestart,
+ ServerGroupHandler {
@Nonnull
@Override

MODIFY
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDeploymentHandler.java
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesDeploymentHandler.java
@@ -47,6 +47,7 @@ public class KubernetesDeploymentHandler extends KubernetesHandler
CanPauseRollout,
CanResumeRollout,
CanUndoRollout,
+ CanRollingRestart,
ServerGroupManagerHandler {
private static final ImmutableSet<KubernetesApiVersion> SUPPORTED_API_VERSIONS =

MODIFY
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesStatefulSetHandler.java
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/handler/KubernetesStatefulSetHandler.java
@@ -52,6 +52,7 @@ public class KubernetesStatefulSetHandler extends KubernetesHandler
CanPauseRollout,
CanResumeRollout,
CanUndoRollout,
+ CanRollingRestart,
ServerGroupHandler {
@Nonnull
@Override

MODIFY
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/job/KubectlJobExecutor.java
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/job/KubectlJobExecutor.java
@@ -346,6 +346,31 @@ public class KubectlJobExecutor {
return null;
}
+ public Void rollingRestart(
+ KubernetesV2Credentials credentials, KubernetesKind kind, String namespace, String name) {
+ List<String> command = kubectlNamespacedAuthPrefix(credentials, namespace);
+
+ command.add("rollout");
+ command.add("restart");
+ command.add(kind.toString() + "/" + name);
+
+ JobResult<String> status = jobExecutor.runJob(new JobRequest(command));
+
+ if (status.getResult() != JobResult.Result.SUCCESS) {
+ throw new KubectlException(
+ "Failed to complete rolling restart of "
+ + kind
+ + "/"
+ + name
+ + " from "
+ + namespace
+ + ": "
+ + status.getError());
+ }
+
+ return null;
+ }
+
public KubernetesManifest get(
KubernetesV2Credentials credentials, KubernetesKind kind, String namespace, String name) {
List<String> command =

ADD
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/manifest/KubernetesRollingRestartManifestOperation.java
+/*
+ * Copyright 2019 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.netflix.spinnaker.clouddriver.kubernetes.v2.op.manifest;
+
+import com.netflix.spinnaker.clouddriver.data.task.Task;
+import com.netflix.spinnaker.clouddriver.data.task.TaskRepository;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesCoordinates;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.KubernetesResourceProperties;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesRollingRestartManifestDescription;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.CanRollingRestart;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.op.handler.KubernetesHandler;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.security.KubernetesV2Credentials;
+import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation;
+import java.util.List;
+
+public class KubernetesRollingRestartManifestOperation implements AtomicOperation<Void> {
+ private final KubernetesRollingRestartManifestDescription description;
+ private final KubernetesV2Credentials credentials;
+ private static final String OP_NAME = "ROLLING_RESTART_KUBERNETES_MANIFEST";
+
+ public KubernetesRollingRestartManifestOperation(
+ KubernetesRollingRestartManifestDescription description) {
+ this.description = description;
+ this.credentials = (KubernetesV2Credentials) description.getCredentials().getCredentials();
+ }
+
+ private static Task getTask() {
+ return TaskRepository.threadLocalTask.get();
+ }
+
+ @Override
+ public Void operate(List priorOutputs) {
+ getTask().updateStatus(OP_NAME, "Starting rolling restart operation...");
+ KubernetesCoordinates coordinates = description.getPointCoordinates();
+
+ getTask().updateStatus(OP_NAME, "Looking up resource properties...");
+ KubernetesResourceProperties properties =
+ credentials.getResourcePropertyRegistry().get(coordinates.getKind());
+ KubernetesHandler deployer = properties.getHandler();
+
+ if (!(deployer instanceof CanRollingRestart)) {
+ throw new IllegalArgumentException(
+ "Resource with " + coordinates + " does not support rolling restart");
+ }
+
+ CanRollingRestart canRollingRestart = (CanRollingRestart) deployer;
+
+ getTask().updateStatus(OP_NAME, "Calling rolling restart operation...");
+ canRollingRestart.rollingRestart(
+ credentials, coordinates.getNamespace(), coordinates.getName());
+
+ return null;
+ }
+}

MODIFY
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/security/KubernetesV2Credentials.java
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/security/KubernetesV2Credentials.java
@@ -531,6 +531,14 @@ public class KubernetesV2Credentials implements KubernetesCredentials {
() -> jobExecutor.resumeRollout(this, kind, namespace, name));
}
+ public void rollingRestart(KubernetesKind kind, String namespace, String name) {
+ runAndRecordMetrics(
+ "rollingRestart",
+ kind,
+ namespace,
+ () -> jobExecutor.rollingRestart(this, kind, namespace, name));
+ }
+
public void patch(
KubernetesKind kind,
String namespace,

ADD
clouddriver-kubernetes-v2/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/v2/validator/manifest/KubernetesRollingRestartManifestValidator.java
+/*
+ * Copyright 2019 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.netflix.spinnaker.clouddriver.kubernetes.v2.validator.manifest;
+
+import static com.netflix.spinnaker.clouddriver.orchestration.AtomicOperations.ROLLING_RESTART_MANIFEST;
+
+import com.netflix.spinnaker.clouddriver.deploy.DescriptionValidator;
+import com.netflix.spinnaker.clouddriver.kubernetes.KubernetesOperation;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.description.manifest.KubernetesRollingRestartManifestDescription;
+import com.netflix.spinnaker.clouddriver.kubernetes.v2.validator.KubernetesValidationUtil;
+import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider;
+import com.netflix.spinnaker.clouddriver.security.ProviderVersion;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.Errors;
+
+@KubernetesOperation(ROLLING_RESTART_MANIFEST)
+@Component
+public class KubernetesRollingRestartManifestValidator
+ extends DescriptionValidator<KubernetesRollingRestartManifestDescription> {
+ private final AccountCredentialsProvider provider;
+
+ @Autowired
+ public KubernetesRollingRestartManifestValidator(AccountCredentialsProvider provider) {
+ this.provider = provider;
+ }
+
+ @Override
+ public void validate(
+ List priorDescriptions,
+ KubernetesRollingRestartManifestDescription description,
+ Errors errors) {
+ KubernetesValidationUtil util =
+ new KubernetesValidationUtil("rollingRestartKubernetesManifest", errors);
+ if (!util.validateV2Credentials(
+ provider,
+ description.getAccount(),
+ description.getPointCoordinates().getKind(),
+ description.getPointCoordinates().getNamespace())) {
+ return;
+ }
+ }
+
+ @Override
+ public boolean acceptsVersion(ProviderVersion version) {
+ return version == ProviderVersion.v2;
+ }
+}

feat(kubernetes): support rolling restart operation for deployments