Skip to content

Commit b28c1b4

Browse files
committed
server-side-apply: support force-conflicts
1 parent a833b7d commit b28c1b4

File tree

6 files changed

+254
-18
lines changed

6 files changed

+254
-18
lines changed

clouddriver-kubernetes/src/integration/java/com/netflix/spinnaker/clouddriver/kubernetes/it/DeployManifestIT.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1802,7 +1802,7 @@ public void shouldDeployUsingServerSideApply() throws IOException, InterruptedEx
18021802
.withValue("metadata.name", DEPLOYMENT_1_NAME)
18031803
.withValue(
18041804
"metadata.annotations",
1805-
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "true"))
1805+
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "force-conflicts"))
18061806
.asList();
18071807

18081808
// ------------------------- when --------------------------
@@ -1853,6 +1853,52 @@ public void shouldDeployUsingServerSideApply() throws IOException, InterruptedEx
18531853
+ managedFields);
18541854
}
18551855

1856+
@DisplayName(
1857+
".\n===\n"
1858+
+ "Given a deployment manifest with server-side-apply disabled set\n"
1859+
+ "When sending deploy manifest request\n"
1860+
+ "Then a deployment is created using client-side apply\n===")
1861+
@Test
1862+
public void shouldDeployUsingApplyWithServerSideApplyDisabled()
1863+
throws IOException, InterruptedException {
1864+
// ------------------------- given --------------------------
1865+
String appName = "server-side-apply-disabled";
1866+
System.out.println("> Using namespace: " + account1Ns + ", appName: " + appName);
1867+
List<Map<String, Object>> manifest =
1868+
KubeTestUtils.loadYaml("classpath:manifests/deployment.yml")
1869+
.withValue("metadata.namespace", account1Ns)
1870+
.withValue("metadata.name", DEPLOYMENT_1_NAME)
1871+
.withValue(
1872+
"metadata.annotations",
1873+
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "false"))
1874+
.asList();
1875+
1876+
// ------------------------- when --------------------------
1877+
List<Map<String, Object>> body =
1878+
KubeTestUtils.loadJson("classpath:requests/deploy_manifest.json")
1879+
.withValue("deployManifest.account", ACCOUNT1_NAME)
1880+
.withValue("deployManifest.moniker.app", appName)
1881+
.withValue("deployManifest.manifests", manifest)
1882+
.asList();
1883+
KubeTestUtils.deployAndWaitStable(
1884+
baseUrl(), body, account1Ns, "deployment " + DEPLOYMENT_1_NAME);
1885+
1886+
// ------------------------- then --------------------------
1887+
String lastAppliedConfiguration =
1888+
kubeCluster.execKubectl(
1889+
"-n "
1890+
+ account1Ns
1891+
+ " get deployment "
1892+
+ DEPLOYMENT_1_NAME
1893+
+ " -o=jsonpath='{.metadata.annotations.kubectl\\.kubernetes\\.io/last-applied-configuration}'");
1894+
assertTrue(
1895+
Strings.isNotEmpty(lastAppliedConfiguration),
1896+
"Expected last-applied-configuration for "
1897+
+ DEPLOYMENT_1_NAME
1898+
+ " deployment to exist and be managed client-side. fields:\n"
1899+
+ lastAppliedConfiguration);
1900+
}
1901+
18561902
@DisplayName(
18571903
".\n===\n"
18581904
+ "Given a deployment manifest without a strategy set\n"

clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/description/manifest/KubernetesManifestStrategy.java

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,29 +40,38 @@ public final class KubernetesManifestStrategy {
4040
private static final String USE_SOURCE_CAPACITY =
4141
STRATEGY_ANNOTATION_PREFIX + "/use-source-capacity";
4242

43+
private static final String SERVER_SIDE_APPLY_STRATEGY =
44+
STRATEGY_ANNOTATION_PREFIX + "/server-side-apply";
45+
private static final String SERVER_SIDE_APPLY_FORCE_CONFLICTS = "force-conflicts";
46+
4347
private final DeployStrategy deployStrategy;
4448
private final Versioned versioned;
4549
private final OptionalInt maxVersionHistory;
4650
private final boolean useSourceCapacity;
51+
private final ServerSideApplyStrategy serverSideApplyStrategy;
4752

4853
@Builder
4954
@ParametersAreNullableByDefault
5055
private KubernetesManifestStrategy(
5156
DeployStrategy deployStrategy,
5257
Versioned versioned,
5358
Integer maxVersionHistory,
54-
boolean useSourceCapacity) {
59+
boolean useSourceCapacity,
60+
ServerSideApplyStrategy serverSideApplyStrategy) {
5561
this.deployStrategy = Optional.ofNullable(deployStrategy).orElse(DeployStrategy.APPLY);
5662
this.versioned = Optional.ofNullable(versioned).orElse(Versioned.DEFAULT);
5763
this.maxVersionHistory =
5864
maxVersionHistory == null ? OptionalInt.empty() : OptionalInt.of(maxVersionHistory);
5965
this.useSourceCapacity = useSourceCapacity;
66+
this.serverSideApplyStrategy =
67+
Optional.ofNullable(serverSideApplyStrategy).orElse(ServerSideApplyStrategy.DEFAULT);
6068
}
6169

6270
static KubernetesManifestStrategy fromAnnotations(Map<String, String> annotations) {
6371
return KubernetesManifestStrategy.builder()
6472
.versioned(Versioned.fromAnnotations(annotations))
6573
.deployStrategy(DeployStrategy.fromAnnotations(annotations))
74+
.serverSideApplyStrategy(ServerSideApplyStrategy.fromAnnotations(annotations))
6675
.useSourceCapacity(Boolean.parseBoolean(annotations.get(USE_SOURCE_CAPACITY)))
6776
.maxVersionHistory(Ints.tryParse(annotations.getOrDefault(MAX_VERSION_HISTORY, "")))
6877
.build();
@@ -72,6 +81,7 @@ ImmutableMap<String, String> toAnnotations() {
7281
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
7382
builder.putAll(deployStrategy.toAnnotations());
7483
builder.putAll(versioned.toAnnotations());
84+
builder.putAll(serverSideApplyStrategy.toAnnotations());
7585
if (maxVersionHistory.isPresent()) {
7686
builder.put(MAX_VERSION_HISTORY, Integer.toString(maxVersionHistory.getAsInt()));
7787
}
@@ -108,7 +118,7 @@ public enum DeployStrategy {
108118
APPLY(null),
109119
RECREATE(STRATEGY_ANNOTATION_PREFIX + "/recreate"),
110120
REPLACE(STRATEGY_ANNOTATION_PREFIX + "/replace"),
111-
SERVER_SIDE_APPLY(STRATEGY_ANNOTATION_PREFIX + "/server-side-apply");
121+
SERVER_SIDE_APPLY(SERVER_SIDE_APPLY_STRATEGY);
112122

113123
@Nullable private final String annotation;
114124

@@ -123,7 +133,9 @@ static DeployStrategy fromAnnotations(Map<String, String> annotations) {
123133
if (Boolean.parseBoolean(annotations.get(REPLACE.annotation))) {
124134
return REPLACE;
125135
}
126-
if (Boolean.parseBoolean(annotations.get(SERVER_SIDE_APPLY.annotation))) {
136+
if (annotations.containsKey(SERVER_SIDE_APPLY.annotation)
137+
&& ServerSideApplyStrategy.fromAnnotations(annotations)
138+
!= ServerSideApplyStrategy.DISABLED) {
127139
return SERVER_SIDE_APPLY;
128140
}
129141
return APPLY;
@@ -146,4 +158,33 @@ void setAnnotations(Map<String, String> annotations) {
146158
annotations.putAll(toAnnotations());
147159
}
148160
}
161+
162+
public enum ServerSideApplyStrategy {
163+
FORCE_CONFLICTS(ImmutableMap.of(SERVER_SIDE_APPLY_STRATEGY, SERVER_SIDE_APPLY_FORCE_CONFLICTS)),
164+
DISABLED(ImmutableMap.of(SERVER_SIDE_APPLY_STRATEGY, Boolean.FALSE.toString())),
165+
DEFAULT(ImmutableMap.of());
166+
private final ImmutableMap<String, String> annotations;
167+
168+
ServerSideApplyStrategy(ImmutableMap<String, String> annotations) {
169+
this.annotations = annotations;
170+
}
171+
172+
static ServerSideApplyStrategy fromAnnotations(Map<String, String> annotations) {
173+
if (annotations.containsKey(SERVER_SIDE_APPLY_STRATEGY)) {
174+
String strategy = annotations.get(SERVER_SIDE_APPLY_STRATEGY);
175+
if (Boolean.parseBoolean(strategy)) {
176+
return DEFAULT;
177+
}
178+
179+
if (strategy.equals(SERVER_SIDE_APPLY_FORCE_CONFLICTS)) {
180+
return FORCE_CONFLICTS;
181+
}
182+
}
183+
return DISABLED;
184+
}
185+
186+
ImmutableMap<String, String> toAnnotations() {
187+
return annotations;
188+
}
189+
}
149190
}

clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/handler/CanDeploy.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@
2525
import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesCredentials;
2626
import com.netflix.spinnaker.clouddriver.kubernetes.security.KubernetesSelectorList;
2727
import io.kubernetes.client.openapi.models.V1DeleteOptions;
28+
import java.util.ArrayList;
29+
import java.util.List;
2830

2931
public interface CanDeploy {
3032
default OperationResult deploy(
3133
KubernetesCredentials credentials,
3234
KubernetesManifest manifest,
3335
KubernetesManifestStrategy.DeployStrategy deployStrategy,
36+
KubernetesManifestStrategy.ServerSideApplyStrategy serverSideApplyStrategy,
3437
Task task,
3538
String opName) {
3639
// If the manifest has a generateName, we must apply with kubectl create as all other operations
@@ -60,7 +63,14 @@ default OperationResult deploy(
6063
deployedManifest = credentials.createOrReplace(manifest, task, opName);
6164
break;
6265
case SERVER_SIDE_APPLY:
63-
deployedManifest = credentials.deploy(manifest, task, opName, "--server-side");
66+
List<String> cmdArgs = new ArrayList<>();
67+
cmdArgs.add("--server-side=true");
68+
if (serverSideApplyStrategy.equals(
69+
KubernetesManifestStrategy.ServerSideApplyStrategy.FORCE_CONFLICTS)) {
70+
cmdArgs.add("--force-conflicts=true");
71+
}
72+
deployedManifest =
73+
credentials.deploy(manifest, task, opName, cmdArgs.toArray(new String[cmdArgs.size()]));
6474
break;
6575
case APPLY:
6676
deployedManifest = credentials.deploy(manifest, task, opName);

clouddriver-kubernetes/src/main/java/com/netflix/spinnaker/clouddriver/kubernetes/op/manifest/KubernetesDeployManifestOperation.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,12 @@ public OperationResult operate(List<OperationResult> _unused) {
158158
+ " to kubernetes master...");
159159
result.merge(
160160
deployer.deploy(
161-
credentials, holder.manifest, strategy.getDeployStrategy(), getTask(), OP_NAME));
161+
credentials,
162+
holder.manifest,
163+
strategy.getDeployStrategy(),
164+
strategy.getServerSideApplyStrategy(),
165+
getTask(),
166+
OP_NAME));
162167

163168
result.getCreatedArtifacts().add(holder.artifact);
164169
getTask()

clouddriver-kubernetes/src/test/java/com/netflix/spinnaker/clouddriver/kubernetes/description/manifest/KubernetesManifestStrategyTest.java

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import com.google.common.collect.ImmutableMap;
2424
import com.netflix.spinnaker.clouddriver.kubernetes.description.manifest.KubernetesManifestStrategy.DeployStrategy;
25+
import com.netflix.spinnaker.clouddriver.kubernetes.description.manifest.KubernetesManifestStrategy.ServerSideApplyStrategy;
2526
import com.netflix.spinnaker.clouddriver.kubernetes.description.manifest.KubernetesManifestStrategy.Versioned;
2627
import java.util.HashMap;
2728
import java.util.Map;
@@ -65,13 +66,62 @@ void replaceStrategy() {
6566
}
6667

6768
@Test
68-
void serverSideApplyStrategy() {
69+
void deployStrategysSrverSideApplyForce() {
70+
KubernetesManifestStrategy.DeployStrategy strategy =
71+
KubernetesManifestStrategy.DeployStrategy.fromAnnotations(
72+
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "force-conflicts"));
73+
assertThat(strategy).isEqualTo(DeployStrategy.SERVER_SIDE_APPLY);
74+
}
75+
76+
@Test
77+
void deployStrategyServerSideApplyDefault() {
6978
KubernetesManifestStrategy.DeployStrategy strategy =
7079
KubernetesManifestStrategy.DeployStrategy.fromAnnotations(
7180
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "true"));
7281
assertThat(strategy).isEqualTo(DeployStrategy.SERVER_SIDE_APPLY);
7382
}
7483

84+
@Test
85+
void deployStrategyServerSideApplyDisabled() {
86+
KubernetesManifestStrategy.DeployStrategy strategy =
87+
KubernetesManifestStrategy.DeployStrategy.fromAnnotations(
88+
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "false"));
89+
assertThat(strategy).isEqualTo(DeployStrategy.APPLY);
90+
}
91+
92+
@Test
93+
void serverSideApplyStrategyForceConflict() {
94+
KubernetesManifestStrategy.ServerSideApplyStrategy conflictResolution =
95+
KubernetesManifestStrategy.ServerSideApplyStrategy.fromAnnotations(
96+
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "force-conflicts"));
97+
assertThat(conflictResolution)
98+
.isEqualTo(KubernetesManifestStrategy.ServerSideApplyStrategy.FORCE_CONFLICTS);
99+
}
100+
101+
@Test
102+
void serverSideApplyStrategyDefault() {
103+
KubernetesManifestStrategy.ServerSideApplyStrategy conflictResolution =
104+
KubernetesManifestStrategy.ServerSideApplyStrategy.fromAnnotations(
105+
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "true"));
106+
assertThat(conflictResolution).isEqualTo(ServerSideApplyStrategy.DEFAULT);
107+
}
108+
109+
@Test
110+
void serverSideApplyStrategyDisabled() {
111+
KubernetesManifestStrategy.ServerSideApplyStrategy conflictResolution =
112+
KubernetesManifestStrategy.ServerSideApplyStrategy.fromAnnotations(
113+
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "false"));
114+
assertThat(conflictResolution).isEqualTo(ServerSideApplyStrategy.DISABLED);
115+
}
116+
117+
@Test
118+
void serverSideApplyStrategyInvalidValue() {
119+
KubernetesManifestStrategy.ServerSideApplyStrategy conflictResolution =
120+
KubernetesManifestStrategy.ServerSideApplyStrategy.fromAnnotations(
121+
ImmutableMap.of("strategy.spinnaker.io/server-side-apply", "zzzz"));
122+
assertThat(conflictResolution).isEqualTo(ServerSideApplyStrategy.DISABLED);
123+
}
124+
75125
@Test
76126
void nonBooleanValue() {
77127
KubernetesManifestStrategy.DeployStrategy strategy =
@@ -90,6 +140,16 @@ void recreatePreferredOverReplace() {
90140
assertThat(strategy).isEqualTo(DeployStrategy.RECREATE);
91141
}
92142

143+
@Test
144+
void replacePreferredOverServerSideApply() {
145+
KubernetesManifestStrategy.DeployStrategy strategy =
146+
KubernetesManifestStrategy.DeployStrategy.fromAnnotations(
147+
ImmutableMap.of(
148+
"strategy.spinnaker.io/replace", "true",
149+
"strategy.spinnaker.io/server-side-apply", "true"));
150+
assertThat(strategy).isEqualTo(DeployStrategy.REPLACE);
151+
}
152+
93153
@Test
94154
void applyToAnnotations() {
95155
Map<String, String> annotations = DeployStrategy.APPLY.toAnnotations();

0 commit comments

Comments
 (0)