Skip to content

Commit

Permalink
server-side-apply: support force-conflicts
Browse files Browse the repository at this point in the history
Signed-off-by: Amir Alavi <amiralavi7@gmail.com>
  • Loading branch information
a7i committed Nov 17, 2023
1 parent a833b7d commit 4b5dad6
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1802,7 +1802,7 @@ public void shouldDeployUsingServerSideApply() throws IOException, InterruptedEx
.withValue("metadata.name", DEPLOYMENT_1_NAME)
.withValue(
"metadata.annotations",
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "true"))
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "force-conflicts"))
.asList();

// ------------------------- when --------------------------
Expand Down Expand Up @@ -1853,6 +1853,52 @@ public void shouldDeployUsingServerSideApply() throws IOException, InterruptedEx
+ managedFields);
}

@DisplayName(
".\n===\n"
+ "Given a deployment manifest with server-side-apply disabled set\n"
+ "When sending deploy manifest request\n"
+ "Then a deployment is created using client-side apply\n===")
@Test
public void shouldDeployUsingApplyWithServerSideApplyDisabled()
throws IOException, InterruptedException {
// ------------------------- given --------------------------
String appName = "server-side-apply-disabled";
System.out.println("> Using namespace: " + account1Ns + ", appName: " + appName);
List<Map<String, Object>> manifest =
KubeTestUtils.loadYaml("classpath:manifests/deployment.yml")
.withValue("metadata.namespace", account1Ns)
.withValue("metadata.name", DEPLOYMENT_1_NAME)
.withValue(
"metadata.annotations",
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "false"))
.asList();

// ------------------------- when --------------------------
List<Map<String, Object>> body =
KubeTestUtils.loadJson("classpath:requests/deploy_manifest.json")
.withValue("deployManifest.account", ACCOUNT1_NAME)
.withValue("deployManifest.moniker.app", appName)
.withValue("deployManifest.manifests", manifest)
.asList();
KubeTestUtils.deployAndWaitStable(
baseUrl(), body, account1Ns, "deployment " + DEPLOYMENT_1_NAME);

// ------------------------- then --------------------------
String lastAppliedConfiguration =
kubeCluster.execKubectl(
"-n "
+ account1Ns
+ " get deployment "
+ DEPLOYMENT_1_NAME
+ " -o=jsonpath='{.metadata.annotations.kubectl\\.kubernetes\\.io/last-applied-configuration}'");
assertTrue(
Strings.isNotEmpty(lastAppliedConfiguration),
"Expected last-applied-configuration for "
+ DEPLOYMENT_1_NAME
+ " deployment to exist and be managed client-side. fields:\n"
+ lastAppliedConfiguration);
}

@DisplayName(
".\n===\n"
+ "Given a deployment manifest without a strategy set\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,29 +40,38 @@ public final class KubernetesManifestStrategy {
private static final String USE_SOURCE_CAPACITY =
STRATEGY_ANNOTATION_PREFIX + "/use-source-capacity";

private static final String SERVER_SIDE_APPLY_STRATEGY =
STRATEGY_ANNOTATION_PREFIX + "/server-side-apply";
private static final String SERVER_SIDE_APPLY_FORCE_CONFLICTS = "force-conflicts";

private final DeployStrategy deployStrategy;
private final Versioned versioned;
private final OptionalInt maxVersionHistory;
private final boolean useSourceCapacity;
private final ServerSideApplyStrategy serverSideApplyStrategy;

@Builder
@ParametersAreNullableByDefault
private KubernetesManifestStrategy(
DeployStrategy deployStrategy,
Versioned versioned,
Integer maxVersionHistory,
boolean useSourceCapacity) {
boolean useSourceCapacity,
ServerSideApplyStrategy serverSideApplyStrategy) {
this.deployStrategy = Optional.ofNullable(deployStrategy).orElse(DeployStrategy.APPLY);
this.versioned = Optional.ofNullable(versioned).orElse(Versioned.DEFAULT);
this.maxVersionHistory =
maxVersionHistory == null ? OptionalInt.empty() : OptionalInt.of(maxVersionHistory);
this.useSourceCapacity = useSourceCapacity;
this.serverSideApplyStrategy =
Optional.ofNullable(serverSideApplyStrategy).orElse(ServerSideApplyStrategy.DEFAULT);
}

static KubernetesManifestStrategy fromAnnotations(Map<String, String> annotations) {
return KubernetesManifestStrategy.builder()
.versioned(Versioned.fromAnnotations(annotations))
.deployStrategy(DeployStrategy.fromAnnotations(annotations))
.serverSideApplyStrategy(ServerSideApplyStrategy.fromAnnotations(annotations))
.useSourceCapacity(Boolean.parseBoolean(annotations.get(USE_SOURCE_CAPACITY)))
.maxVersionHistory(Ints.tryParse(annotations.getOrDefault(MAX_VERSION_HISTORY, "")))
.build();
Expand All @@ -72,6 +81,7 @@ ImmutableMap<String, String> toAnnotations() {
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
builder.putAll(deployStrategy.toAnnotations());
builder.putAll(versioned.toAnnotations());
builder.putAll(serverSideApplyStrategy.toAnnotations());
if (maxVersionHistory.isPresent()) {
builder.put(MAX_VERSION_HISTORY, Integer.toString(maxVersionHistory.getAsInt()));
}
Expand Down Expand Up @@ -108,7 +118,7 @@ public enum DeployStrategy {
APPLY(null),
RECREATE(STRATEGY_ANNOTATION_PREFIX + "/recreate"),
REPLACE(STRATEGY_ANNOTATION_PREFIX + "/replace"),
SERVER_SIDE_APPLY(STRATEGY_ANNOTATION_PREFIX + "/server-side-apply");
SERVER_SIDE_APPLY(SERVER_SIDE_APPLY_STRATEGY);

@Nullable private final String annotation;

Expand All @@ -123,7 +133,9 @@ static DeployStrategy fromAnnotations(Map<String, String> annotations) {
if (Boolean.parseBoolean(annotations.get(REPLACE.annotation))) {
return REPLACE;
}
if (Boolean.parseBoolean(annotations.get(SERVER_SIDE_APPLY.annotation))) {
if (annotations.containsKey(SERVER_SIDE_APPLY.annotation)
&& ServerSideApplyStrategy.fromAnnotations(annotations)
!= ServerSideApplyStrategy.DISABLED) {
return SERVER_SIDE_APPLY;
}
return APPLY;
Expand All @@ -146,4 +158,33 @@ void setAnnotations(Map<String, String> annotations) {
annotations.putAll(toAnnotations());
}
}

public enum ServerSideApplyStrategy {
FORCE_CONFLICTS(ImmutableMap.of(SERVER_SIDE_APPLY_STRATEGY, SERVER_SIDE_APPLY_FORCE_CONFLICTS)),
DISABLED(ImmutableMap.of(SERVER_SIDE_APPLY_STRATEGY, Boolean.FALSE.toString())),
DEFAULT(ImmutableMap.of());
private final ImmutableMap<String, String> annotations;

ServerSideApplyStrategy(ImmutableMap<String, String> annotations) {
this.annotations = annotations;
}

static ServerSideApplyStrategy fromAnnotations(Map<String, String> annotations) {
if (annotations.containsKey(SERVER_SIDE_APPLY_STRATEGY)) {
String strategy = annotations.get(SERVER_SIDE_APPLY_STRATEGY);
if (Boolean.parseBoolean(strategy)) {
return DEFAULT;
}

if (strategy.equals(SERVER_SIDE_APPLY_FORCE_CONFLICTS)) {
return FORCE_CONFLICTS;
}
}
return DISABLED;
}

ImmutableMap<String, String> toAnnotations() {
return annotations;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@
import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesCredentials;
import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesSelectorList;
import io.kubernetes.client.openapi.models.V1DeleteOptions;
import java.util.ArrayList;
import java.util.List;

public interface CanDeploy {
default OperationResult deploy(
KubernetesCredentials credentials,
KubernetesManifest manifest,
KubernetesManifestStrategy.DeployStrategy deployStrategy,
KubernetesManifestStrategy.ServerSideApplyStrategy serverSideApplyStrategy,
Task task,
String opName) {
// If the manifest has a generateName, we must apply with kubectl create as all other operations
Expand Down Expand Up @@ -60,7 +63,14 @@ default OperationResult deploy(
deployedManifest = credentials.createOrReplace(manifest, task, opName);
break;
case SERVER_SIDE_APPLY:
deployedManifest = credentials.deploy(manifest, task, opName, "--server-side");
List<String> cmdArgs = new ArrayList<>();
cmdArgs.add("--server-side=true");
if (serverSideApplyStrategy.equals(
KubernetesManifestStrategy.ServerSideApplyStrategy.FORCE_CONFLICTS)) {
cmdArgs.add("--force-conflicts=true");
}
deployedManifest =
credentials.deploy(manifest, task, opName, cmdArgs.toArray(new String[cmdArgs.size()]));
break;
case APPLY:
deployedManifest = credentials.deploy(manifest, task, opName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,12 @@ public OperationResult operate(List<OperationResult> _unused) {
+ " to kubernetes master...");
result.merge(
deployer.deploy(
credentials, holder.manifest, strategy.getDeployStrategy(), getTask(), OP_NAME));
credentials,
holder.manifest,
strategy.getDeployStrategy(),
strategy.getServerSideApplyStrategy(),
getTask(),
OP_NAME));

result.getCreatedArtifacts().add(holder.artifact);
getTask()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import com.google.common.collect.ImmutableMap;
import com.netflix.spinnaker.clouddriver.kubernetes.description.manifest.KubernetesManifestStrategy.DeployStrategy;
import com.netflix.spinnaker.clouddriver.kubernetes.description.manifest.KubernetesManifestStrategy.ServerSideApplyStrategy;
import com.netflix.spinnaker.clouddriver.kubernetes.description.manifest.KubernetesManifestStrategy.Versioned;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -65,13 +66,62 @@ void replaceStrategy() {
}

@Test
void serverSideApplyStrategy() {
void deployStrategysSrverSideApplyForce() {
KubernetesManifestStrategy.DeployStrategy strategy =
KubernetesManifestStrategy.DeployStrategy.fromAnnotations(
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "force-conflicts"));
assertThat(strategy).isEqualTo(DeployStrategy.SERVER_SIDE_APPLY);
}

@Test
void deployStrategyServerSideApplyDefault() {
KubernetesManifestStrategy.DeployStrategy strategy =
KubernetesManifestStrategy.DeployStrategy.fromAnnotations(
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "true"));
assertThat(strategy).isEqualTo(DeployStrategy.SERVER_SIDE_APPLY);
}

@Test
void deployStrategyServerSideApplyDisabled() {
KubernetesManifestStrategy.DeployStrategy strategy =
KubernetesManifestStrategy.DeployStrategy.fromAnnotations(
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "false"));
assertThat(strategy).isEqualTo(DeployStrategy.APPLY);
}

@Test
void serverSideApplyStrategyForceConflict() {
KubernetesManifestStrategy.ServerSideApplyStrategy conflictResolution =
KubernetesManifestStrategy.ServerSideApplyStrategy.fromAnnotations(
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "force-conflicts"));
assertThat(conflictResolution)
.isEqualTo(KubernetesManifestStrategy.ServerSideApplyStrategy.FORCE_CONFLICTS);
}

@Test
void serverSideApplyStrategyDefault() {
KubernetesManifestStrategy.ServerSideApplyStrategy conflictResolution =
KubernetesManifestStrategy.ServerSideApplyStrategy.fromAnnotations(
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "true"));
assertThat(conflictResolution).isEqualTo(ServerSideApplyStrategy.DEFAULT);
}

@Test
void serverSideApplyStrategyDisabled() {
KubernetesManifestStrategy.ServerSideApplyStrategy conflictResolution =
KubernetesManifestStrategy.ServerSideApplyStrategy.fromAnnotations(
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "false"));
assertThat(conflictResolution).isEqualTo(ServerSideApplyStrategy.DISABLED);
}

@Test
void serverSideApplyStrategyInvalidValue() {
KubernetesManifestStrategy.ServerSideApplyStrategy conflictResolution =
KubernetesManifestStrategy.ServerSideApplyStrategy.fromAnnotations(
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "zzzz"));
assertThat(conflictResolution).isEqualTo(ServerSideApplyStrategy.DISABLED);
}

@Test
void nonBooleanValue() {
KubernetesManifestStrategy.DeployStrategy strategy =
Expand All @@ -90,6 +140,16 @@ void recreatePreferredOverReplace() {
assertThat(strategy).isEqualTo(DeployStrategy.RECREATE);
}

@Test
void replacePreferredOverServerSideApply() {
KubernetesManifestStrategy.DeployStrategy strategy =
KubernetesManifestStrategy.DeployStrategy.fromAnnotations(
ImmutableMap.of(
"strategy.spinnaker.io/replace", "true",
"strategy.spinnaker.io/server-side-apply", "true"));
assertThat(strategy).isEqualTo(DeployStrategy.REPLACE);
}

@Test
void applyToAnnotations() {
Map<String, String> annotations = DeployStrategy.APPLY.toAnnotations();
Expand Down
Loading

0 comments on commit 4b5dad6

Please sign in to comment.