Skip to content

Commit 2dedeba

Browse files
authored
feat: exclude unchanged resources from plan file (#37)
1 parent 99dd5e6 commit 2dedeba

18 files changed

+420
-110
lines changed

src/main/java/com/devshawn/kafka/gitops/cli/AccountCommand.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
import com.devshawn.kafka.gitops.MainCommand;
44
import com.devshawn.kafka.gitops.StateManager;
55
import com.devshawn.kafka.gitops.config.ManagerConfig;
6-
import com.devshawn.kafka.gitops.exception.*;
6+
import com.devshawn.kafka.gitops.exception.ConfluentCloudException;
7+
import com.devshawn.kafka.gitops.exception.KafkaExecutionException;
8+
import com.devshawn.kafka.gitops.exception.MissingConfigurationException;
9+
import com.devshawn.kafka.gitops.exception.ValidationException;
10+
import com.devshawn.kafka.gitops.exception.WritePlanOutputException;
711
import com.devshawn.kafka.gitops.service.ParserService;
812
import com.devshawn.kafka.gitops.util.LogUtil;
913
import picocli.CommandLine;
@@ -39,6 +43,7 @@ private ManagerConfig generateStateManagerConfig() {
3943
return new ManagerConfig.Builder()
4044
.setVerboseRequested(parent.isVerboseRequested())
4145
.setDeleteDisabled(parent.isDeleteDisabled())
46+
.setIncludeUnchangedEnabled(false)
4247
.setStateFile(parent.getFile())
4348
.build();
4449
}

src/main/java/com/devshawn/kafka/gitops/cli/ApplyCommand.java

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ private ManagerConfig generateStateManagerConfig() {
5353
return new ManagerConfig.Builder()
5454
.setVerboseRequested(parent.isVerboseRequested())
5555
.setDeleteDisabled(parent.isDeleteDisabled())
56+
.setIncludeUnchangedEnabled(false)
5657
.setStateFile(parent.getFile())
5758
.setNullablePlanFile(planFile)
5859
.build();

src/main/java/com/devshawn/kafka/gitops/cli/PlanCommand.java

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public class PlanCommand implements Callable<Integer> {
2323
description = "Specify the output file for the plan.")
2424
private File outputFile;
2525

26+
@CommandLine.Option(names = {"--include-unchanged"}, description = "Include unchanged resources in the plan file.")
27+
private boolean includeUnchanged = false;
28+
2629
@CommandLine.ParentCommand
2730
private MainCommand parent;
2831

@@ -54,6 +57,7 @@ private ManagerConfig generateStateManagerConfig() {
5457
return new ManagerConfig.Builder()
5558
.setVerboseRequested(parent.isVerboseRequested())
5659
.setDeleteDisabled(parent.isDeleteDisabled())
60+
.setIncludeUnchangedEnabled(includeUnchanged)
5761
.setStateFile(parent.getFile())
5862
.setNullablePlanFile(outputFile)
5963
.build();

src/main/java/com/devshawn/kafka/gitops/cli/ValidateCommand.java

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ private ManagerConfig generateStateManagerConfig() {
3636
return new ManagerConfig.Builder()
3737
.setVerboseRequested(parent.isVerboseRequested())
3838
.setDeleteDisabled(parent.isDeleteDisabled())
39+
.setIncludeUnchangedEnabled(false)
3940
.setStateFile(parent.getFile())
4041
.build();
4142
}

src/main/java/com/devshawn/kafka/gitops/config/ManagerConfig.java

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public interface ManagerConfig {
1414

1515
boolean isDeleteDisabled();
1616

17+
boolean isIncludeUnchangedEnabled();
18+
1719
File getStateFile();
1820

1921
Optional<File> getPlanFile();

src/main/java/com/devshawn/kafka/gitops/domain/plan/DesiredPlan.java

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.devshawn.kafka.gitops.domain.plan;
22

3+
import com.devshawn.kafka.gitops.enums.PlanAction;
34
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
45
import org.inferred.freebuilder.FreeBuilder;
56

@@ -13,6 +14,13 @@ public interface DesiredPlan {
1314

1415
List<AclPlan> getAclPlans();
1516

17+
default DesiredPlan toChangesOnlyPlan() {
18+
DesiredPlan.Builder builder = new DesiredPlan.Builder();
19+
getTopicPlans().stream().filter(it -> !it.getAction().equals(PlanAction.NO_CHANGE)).map(TopicPlan::toChangesOnlyPlan).forEach(builder::addTopicPlans);
20+
getAclPlans().stream().filter(it -> !it.getAction().equals(PlanAction.NO_CHANGE)).forEach(builder::addAclPlans);
21+
return builder.build();
22+
}
23+
1624
class Builder extends DesiredPlan_Builder {
1725
}
1826
}

src/main/java/com/devshawn/kafka/gitops/domain/plan/TopicPlan.java

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ public interface TopicPlan {
2020

2121
List<TopicConfigPlan> getTopicConfigPlans();
2222

23+
default TopicPlan toChangesOnlyPlan() {
24+
TopicPlan.Builder builder = new TopicPlan.Builder().setName(getName()).setAction(getAction()).setTopicDetails(getTopicDetails());
25+
getTopicConfigPlans().stream().filter(it -> !it.getAction().equals(PlanAction.NO_CHANGE)).forEach(builder::addTopicConfigPlans);
26+
return builder.build();
27+
}
28+
2329
class Builder extends TopicPlan_Builder {
2430
}
2531
}

src/main/java/com/devshawn/kafka/gitops/manager/PlanManager.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,9 @@ public void writePlanToFile(DesiredPlan desiredPlan) {
198198
if (managerConfig.getPlanFile().isPresent()) {
199199
try {
200200
managerConfig.getPlanFile().get().createNewFile();
201+
DesiredPlan outputPlan = managerConfig.isIncludeUnchangedEnabled() ? desiredPlan : desiredPlan.toChangesOnlyPlan();
201202
FileWriter writer = new FileWriter(managerConfig.getPlanFile().get());
202-
writer.write(objectMapper.writeValueAsString(desiredPlan));
203+
writer.write(objectMapper.writeValueAsString(outputPlan));
203204
writer.close();
204205
} catch (IOException ex) {
205206
throw new WritePlanOutputException(ex.getMessage());

src/test/groovy/com/devshawn/kafka/gitops/ApplyCommandIntegrationSpec.groovy

+8-7
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,14 @@ class ApplyCommandIntegrationSpec extends Specification {
9292
System.setOut(oldOut)
9393

9494
where:
95-
planFile | deleteDisabled
96-
"seed-topic-modification" | true
97-
"seed-topic-modification" | false
98-
"seed-topic-modification-3" | true
99-
"seed-topic-modification-3" | false
100-
"seed-acl-exists" | false
101-
"no-changes" | false
95+
planFile | deleteDisabled
96+
"seed-topic-modification" | true
97+
"seed-topic-modification" | false
98+
"seed-topic-modification-3" | true
99+
"seed-topic-modification-3" | false
100+
"seed-acl-exists" | false
101+
"no-changes" | false
102+
"no-changes-include-unchanged" | false
102103
}
103104

104105
void 'test reading missing file throws ReadPlanInputException'() {

src/test/groovy/com/devshawn/kafka/gitops/PlanCommandIntegrationSpec.groovy

+46-4
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,40 @@ class PlanCommandIntegrationSpec extends Specification {
108108
"seed-blacklist-topics" | false
109109
}
110110

111+
void 'test include unchanged flag - #planNam #includeUnchanged'() {
112+
setup:
113+
TestUtils.cleanUpCluster()
114+
TestUtils.seedCluster()
115+
String planOutputFile = "/tmp/plan.json"
116+
String file = TestUtils.getResourceFilePath("plans/${planName}.yaml")
117+
MainCommand mainCommand = new MainCommand()
118+
CommandLine cmd = new CommandLine(mainCommand)
119+
120+
when:
121+
int exitCode = -1
122+
if (includeUnchanged) {
123+
exitCode = cmd.execute("-f", file, "plan", "--include-unchanged", "-o", planOutputFile)
124+
} else {
125+
exitCode = cmd.execute("-f", file, "plan", "-o", planOutputFile)
126+
}
127+
128+
then:
129+
exitCode == 0
130+
131+
when:
132+
String expected = includeUnchanged ? "${planName}-include-unchanged" : planName
133+
String actualPlan = TestUtils.getFileContent(planOutputFile)
134+
String expectedPlan = TestUtils.getResourceFileContent("plans/${expected}-plan.json")
135+
136+
then:
137+
JSONAssert.assertEquals(expectedPlan, actualPlan, true)
138+
139+
where:
140+
planName | includeUnchanged
141+
"seed-basic" | false
142+
"seed-basic" | true
143+
}
144+
111145
void 'test invalid plans - #planName'() {
112146
setup:
113147
ByteArrayOutputStream err = new ByteArrayOutputStream()
@@ -187,7 +221,7 @@ class PlanCommandIntegrationSpec extends Specification {
187221
System.setOut(oldOut)
188222
}
189223

190-
void 'test plan that has no changes'() {
224+
void 'test plan that has no changes - #includeUnchanged'() {
191225
setup:
192226
TestUtils.cleanUpCluster()
193227
TestUtils.seedCluster()
@@ -200,15 +234,21 @@ class PlanCommandIntegrationSpec extends Specification {
200234
CommandLine cmd = new CommandLine(mainCommand)
201235

202236
when:
203-
int exitCode = cmd.execute("-f", file, "plan", "-o", planOutputFile)
237+
int exitCode = -1
238+
if (includeUnchanged) {
239+
exitCode = cmd.execute("-f", file, "plan", "--include-unchanged", "-o", planOutputFile)
240+
} else {
241+
exitCode = cmd.execute("-f", file, "plan", "-o", planOutputFile)
242+
}
204243

205244
then:
206245
exitCode == 0
207246
out.toString() == TestUtils.getResourceFileContent("plans/no-changes-output.txt")
208247

209248
when:
249+
String expected = includeUnchanged ? "${planName}-include-unchanged" : planName
210250
String actualPlan = TestUtils.getFileContent(planOutputFile)
211-
String expectedPlan = TestUtils.getResourceFileContent("plans/${planName}-plan.json")
251+
String expectedPlan = TestUtils.getResourceFileContent("plans/${expected}-plan.json")
212252

213253
then:
214254
JSONAssert.assertEquals(expectedPlan, actualPlan, true)
@@ -217,6 +257,8 @@ class PlanCommandIntegrationSpec extends Specification {
217257
System.setOut(oldOut)
218258

219259
where:
220-
planName << ["no-changes"]
260+
planName | includeUnchanged
261+
"no-changes" | false
262+
"no-changes" | true
221263
}
222264
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Executing apply...
2+
3+
[SUCCESS] There are no necessary changes; the actual state matches the desired state.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{
2+
"topicPlans": [
3+
{
4+
"name": "delete-topic",
5+
"action": "NO_CHANGE",
6+
"topicDetails": {
7+
"partitions": 1,
8+
"replication": 1,
9+
"configs": {}
10+
},
11+
"topicConfigPlans": []
12+
},
13+
{
14+
"name": "test-topic",
15+
"action": "NO_CHANGE",
16+
"topicDetails": {
17+
"partitions": 1,
18+
"replication": 1,
19+
"configs": {}
20+
},
21+
"topicConfigPlans": []
22+
},
23+
{
24+
"name": "topic-with-configs-1",
25+
"action": "NO_CHANGE",
26+
"topicDetails": {
27+
"partitions": 3,
28+
"replication": 1,
29+
"configs": {
30+
"cleanup.policy": "compact",
31+
"segment.bytes": "100000"
32+
}
33+
},
34+
"topicConfigPlans": [
35+
{
36+
"key": "cleanup.policy",
37+
"value": "compact",
38+
"action": "NO_CHANGE"
39+
},
40+
{
41+
"key": "segment.bytes",
42+
"value": "100000",
43+
"action": "NO_CHANGE"
44+
}
45+
]
46+
},
47+
{
48+
"name": "topic-with-configs-2",
49+
"action": "NO_CHANGE",
50+
"topicDetails": {
51+
"partitions": 6,
52+
"replication": 1,
53+
"configs": {
54+
"retention.ms": "60000"
55+
}
56+
},
57+
"topicConfigPlans": [
58+
{
59+
"key": "retention.ms",
60+
"value": "60000",
61+
"action": "NO_CHANGE"
62+
}
63+
]
64+
}
65+
],
66+
"aclPlans": [
67+
{
68+
"name": "test-service-0",
69+
"aclDetails": {
70+
"name": "test-topic",
71+
"type": "TOPIC",
72+
"pattern": "LITERAL",
73+
"principal": "User:test",
74+
"host": "*",
75+
"operation": "READ",
76+
"permission": "ALLOW"
77+
},
78+
"action": "NO_CHANGE"
79+
}
80+
]
81+
}
+2-79
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,4 @@
11
{
2-
"topicPlans": [
3-
{
4-
"name": "delete-topic",
5-
"action": "NO_CHANGE",
6-
"topicDetails": {
7-
"partitions": 1,
8-
"replication": 1,
9-
"configs": {}
10-
},
11-
"topicConfigPlans": []
12-
},
13-
{
14-
"name": "test-topic",
15-
"action": "NO_CHANGE",
16-
"topicDetails": {
17-
"partitions": 1,
18-
"replication": 1,
19-
"configs": {}
20-
},
21-
"topicConfigPlans": []
22-
},
23-
{
24-
"name": "topic-with-configs-1",
25-
"action": "NO_CHANGE",
26-
"topicDetails": {
27-
"partitions": 3,
28-
"replication": 1,
29-
"configs": {
30-
"cleanup.policy": "compact",
31-
"segment.bytes": "100000"
32-
}
33-
},
34-
"topicConfigPlans": [
35-
{
36-
"key": "cleanup.policy",
37-
"value": "compact",
38-
"action": "NO_CHANGE"
39-
},
40-
{
41-
"key": "segment.bytes",
42-
"value": "100000",
43-
"action": "NO_CHANGE"
44-
}
45-
]
46-
},
47-
{
48-
"name": "topic-with-configs-2",
49-
"action": "NO_CHANGE",
50-
"topicDetails": {
51-
"partitions": 6,
52-
"replication": 1,
53-
"configs": {
54-
"retention.ms": "60000"
55-
}
56-
},
57-
"topicConfigPlans": [
58-
{
59-
"key": "retention.ms",
60-
"value": "60000",
61-
"action": "NO_CHANGE"
62-
}
63-
]
64-
}
65-
],
66-
"aclPlans": [
67-
{
68-
"name": "test-service-0",
69-
"aclDetails": {
70-
"name": "test-topic",
71-
"type": "TOPIC",
72-
"pattern": "LITERAL",
73-
"principal": "User:test",
74-
"host": "*",
75-
"operation": "READ",
76-
"permission": "ALLOW"
77-
},
78-
"action": "NO_CHANGE"
79-
}
80-
]
2+
"topicPlans": [],
3+
"aclPlans": []
814
}

0 commit comments

Comments
 (0)