Skip to content

Commit 6c4da91

Browse files
authored
[Environment API Spark]: Compute MergedEnvironmentConfig etag using Objects.hash (#5812)
* Making imlelmetaion consistent with ruby api implemetation - Use `getEnvironments` method instead of `getAllMergedEnvironments` to get all environment for index call. - Use `getEnvironmentConfig` instead of `getMergedEnvironmentforDisplay` to find environment by name. * Calculate md5 using object hash. - This is done as Environment coming from config repo represeted using MergedEnvironmentConfig object and caculating etag for none config entity is not supported by entity hashing service. - Here we have used `Objects.hash(...)` which generates identical hash for same valued objects. * Fixed indentation of code. * Sort the EnvironmentConfigs by name to guaranty the order in the response as well as for Etag computation
1 parent 8390cf1 commit 6c4da91

File tree

12 files changed

+140
-140
lines changed

12 files changed

+140
-140
lines changed

api/api-environments-v2/src/main/java/com/thoughtworks/go/apiv2/environments/EnvironmentsControllerV2.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
import com.thoughtworks.go.apiv2.environments.representers.PatchEnvironmentRequestRepresenter;
3030
import com.thoughtworks.go.config.BasicEnvironmentConfig;
3131
import com.thoughtworks.go.config.EnvironmentConfig;
32+
import com.thoughtworks.go.config.exceptions.NoSuchEnvironmentException;
3233
import com.thoughtworks.go.config.exceptions.RecordNotFoundException;
34+
import com.thoughtworks.go.config.merge.MergeEnvironmentConfig;
3335
import com.thoughtworks.go.domain.ConfigElementForEdit;
3436
import com.thoughtworks.go.server.service.EntityHashingService;
3537
import com.thoughtworks.go.server.service.EnvironmentConfigService;
@@ -44,7 +46,10 @@
4446
import spark.Response;
4547

4648
import java.io.IOException;
49+
import java.util.Collection;
50+
import java.util.Comparator;
4751
import java.util.List;
52+
import java.util.Objects;
4853
import java.util.function.Consumer;
4954
import java.util.stream.Collectors;
5055

@@ -92,7 +97,8 @@ public void setupRoutes() {
9297
}
9398

9499
public String index(Request request, Response response) throws IOException {
95-
List<EnvironmentConfig> environmentViewModelList = environmentConfigService.getAllMergedEnvironments();
100+
List<EnvironmentConfig> environmentViewModelList = environmentConfigService.getEnvironments().stream()
101+
.sorted(Comparator.comparing(EnvironmentConfig::name)).collect(Collectors.toList());
96102

97103
setEtagHeader(response, calculateEtag(environmentViewModelList));
98104

@@ -179,14 +185,19 @@ public String remove(Request request, Response response) {
179185

180186
@Override
181187
public String etagFor(EnvironmentConfig entityFromServer) {
188+
if (entityFromServer instanceof MergeEnvironmentConfig) {
189+
return DigestUtils.md5Hex(String.valueOf(Objects.hash(entityFromServer)));
190+
}
182191
return entityHashingService.md5ForEntity(entityFromServer);
183192
}
184193

185194
@Override
186195
public EnvironmentConfig doFetchEntityFromConfig(String name) {
187-
ConfigElementForEdit<EnvironmentConfig> mergedEnvironmentforDisplay = environmentConfigService.getMergedEnvironmentforDisplay(name, new HttpLocalizedOperationResult());
188-
189-
return mergedEnvironmentforDisplay == null ? null : mergedEnvironmentforDisplay.getConfigElement();
196+
try {
197+
return environmentConfigService.getEnvironmentConfig(name);
198+
} catch (NoSuchEnvironmentException e) {
199+
throw new RecordNotFoundException(e);
200+
}
190201
}
191202

192203
@Override
@@ -212,7 +223,7 @@ private void haltIfEntityWithSameNameExists(EnvironmentConfig environmentConfig)
212223
throw haltBecauseEntityAlreadyExists(jsonWriter(environmentConfig), "environment", environmentConfig.name().toString());
213224
}
214225

215-
private String calculateEtag(List<EnvironmentConfig> environmentConfigs) {
226+
private String calculateEtag(Collection<EnvironmentConfig> environmentConfigs) {
216227
final String environmentConfigSegment = environmentConfigs
217228
.stream()
218229
.map(this::etagFor)

api/api-environments-v2/src/main/java/com/thoughtworks/go/apiv2/environments/representers/EnvironmentRepresenter.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@ public static EnvironmentConfig fromJSON(JsonReader jsonReader) {
6060

6161
jsonReader.readArrayIfPresent("environment_variables",
6262
array -> array.forEach(envVar -> {
63-
String name = envVar.getAsJsonObject().get("name").getAsString();
64-
String value = envVar.getAsJsonObject().get("value").getAsString();
65-
boolean secure = envVar.getAsJsonObject().get("secure").getAsBoolean();
66-
environmentConfig.addEnvironmentVariable(new EnvironmentVariableConfig(new GoCipher(), name, value, secure));
67-
}
63+
String name = envVar.getAsJsonObject().get("name").getAsString();
64+
String value = envVar.getAsJsonObject().get("value").getAsString();
65+
boolean secure = envVar.getAsJsonObject().get("secure").getAsBoolean();
66+
environmentConfig.addEnvironmentVariable(new EnvironmentVariableConfig(new GoCipher(), name, value, secure));
67+
}
6868
)
6969
);
7070

api/api-environments-v2/src/main/java/com/thoughtworks/go/apiv2/environments/representers/EnvironmentsRepresenter.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,18 @@
2020
import com.thoughtworks.go.config.EnvironmentConfig;
2121
import com.thoughtworks.go.spark.Routes;
2222

23-
import java.util.List;
23+
import java.util.Collection;
2424

2525
public class EnvironmentsRepresenter {
26-
public static void toJSON(OutputWriter writer, List<EnvironmentConfig> environmentViewModelList) {
27-
writer.addLinks(
28-
outputLinkWriter -> outputLinkWriter
29-
.addLink("self", Routes.Environments.BASE)
30-
.addAbsoluteLink("doc", Routes.Environments.DOC))
31-
.addChild("_embedded",
32-
embeddedWriter -> embeddedWriter.addChildList("environments",
33-
environmentsWriter -> environmentViewModelList.forEach(
34-
environment -> environmentsWriter.addChild(
35-
environmentWriter -> EnvironmentRepresenter.toJSON(environmentWriter, environment)))));
26+
public static void toJSON(OutputWriter writer, Collection<EnvironmentConfig> environmentViewModelList) {
27+
writer.addLinks(
28+
outputLinkWriter -> outputLinkWriter
29+
.addLink("self", Routes.Environments.BASE)
30+
.addAbsoluteLink("doc", Routes.Environments.DOC))
31+
.addChild("_embedded",
32+
embeddedWriter -> embeddedWriter.addChildList("environments",
33+
environmentsWriter -> environmentViewModelList.forEach(
34+
environment -> environmentsWriter.addChild(
35+
environmentWriter -> EnvironmentRepresenter.toJSON(environmentWriter, environment)))));
3636
}
3737
}

api/api-environments-v2/src/main/java/com/thoughtworks/go/apiv2/environments/representers/PatchEnvironmentRequestRepresenter.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ public static PatchEnvironmentRequest fromJSON(JsonReader jsonReader) {
5050
envVariableToRemove.addAll(reader.readStringArrayIfPresent("remove").orElse(emptyList()));
5151

5252
reader.readArrayIfPresent("add", array ->
53-
array.forEach(envVariable -> envVariablesToAdd
54-
.add(EnvironmentVariableRepresenter.fromJSON(envVariable.getAsJsonObject()))
55-
));
53+
array.forEach(envVariable -> envVariablesToAdd
54+
.add(EnvironmentVariableRepresenter.fromJSON(envVariable.getAsJsonObject()))
55+
));
5656
});
5757

5858
PatchEnvironmentRequest patchRequest = new PatchEnvironmentRequest(

api/api-environments-v2/src/main/java/com/thoughtworks/go/apiv2/environments/representers/PipelineRepresenter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ private static void addLinks(OutputLinkWriter linksWriter, EnvironmentPipelineCo
3232
String pipelineName = pipelineConfig.getName().toString();
3333
linksWriter.addLink("self", Routes.Pipeline.history(pipelineName))
3434
.addAbsoluteLink("doc", Routes.Pipeline.DOC)
35-
.addAbsoluteLink("find", Routes.PipelineConfig.find());
35+
.addAbsoluteLink("find", Routes.PipelineConfig.find());
3636
}
3737
}

api/api-environments-v2/src/test/groovy/com/thoughtworks/go/apiv2/environments/EnvironmentsControllerV2Test.groovy

Lines changed: 33 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class EnvironmentsControllerV2Test implements SecurityServiceTrait, ControllerTr
9494
env1.addPipeline(new CaseInsensitiveString("Pipeline2"))
9595

9696
when(entityHashingService.md5ForEntity(env1)).thenReturn("md5-hash")
97-
when(environmentConfigService.getMergedEnvironmentforDisplay(eq("env1"), any(HttpLocalizedOperationResult.class))).thenReturn(new ConfigElementForEdit(env1, "md5-hash"))
97+
when(environmentConfigService.getEnvironmentConfig(eq("env1"))).thenReturn(env1)
9898

9999
getWithApiHeader(controller.controllerPath("env1"))
100100

@@ -154,7 +154,7 @@ class EnvironmentsControllerV2Test implements SecurityServiceTrait, ControllerTr
154154
env1.addPipeline(new CaseInsensitiveString("Pipeline1"))
155155
env1.addPipeline(new CaseInsensitiveString("Pipeline2"))
156156

157-
when(environmentConfigService.getMergedEnvironmentforDisplay(anyString(), any(HttpLocalizedOperationResult.class))).thenReturn(new ConfigElementForEdit(env1, "3123abcef"))
157+
when(environmentConfigService.getEnvironmentConfig(anyString())).thenReturn(env1)
158158
when(environmentConfigService.deleteEnvironment(eq(env1), eq(currentUsername()), any(HttpLocalizedOperationResult))).then({
159159
InvocationOnMock invocation ->
160160
HttpLocalizedOperationResult result = (HttpLocalizedOperationResult) invocation.getArguments().last()
@@ -223,14 +223,11 @@ class EnvironmentsControllerV2Test implements SecurityServiceTrait, ControllerTr
223223
newConfig.addPipeline(new CaseInsensitiveString("Pipeline1"))
224224

225225
when(entityHashingService.md5ForEntity(existingConfig)).thenReturn("ffff")
226-
when(environmentConfigService.getMergedEnvironmentforDisplay(
227-
eq("env1"),
228-
any(HttpLocalizedOperationResult))
229-
).thenReturn(new ConfigElementForEdit<>(existingConfig, "ffff"))
226+
when(environmentConfigService.getEnvironmentConfig(eq("env1"))).thenReturn(existingConfig)
230227

231228
def json = toObjectString({ EnvironmentRepresenter.toJSON(it, newConfig) })
232229

233-
putWithApiHeader(controller.controllerPath("env1"),['if-match': 'ffff'], json)
230+
putWithApiHeader(controller.controllerPath("env1"), ['if-match': 'ffff'], json)
234231

235232
assertThatResponse()
236233
.isUnprocessableEntity()
@@ -245,16 +242,13 @@ class EnvironmentsControllerV2Test implements SecurityServiceTrait, ControllerTr
245242
env1.addPipeline(new CaseInsensitiveString("Pipeline1"))
246243

247244

248-
when(environmentConfigService.getMergedEnvironmentforDisplay(
249-
eq("env1"),
250-
any(HttpLocalizedOperationResult))
251-
).thenReturn(new ConfigElementForEdit<>(env1, "ffff"))
245+
when(environmentConfigService.getEnvironmentConfig(eq("env1"))).thenReturn(env1)
252246

253247
when(entityHashingService.md5ForEntity(env1)).thenReturn("wrong-md5")
254248

255249
def json = toObjectString({ EnvironmentRepresenter.toJSON(it, env1) })
256250

257-
putWithApiHeader(controller.controllerPath("env1"),['if-match': 'ffff'], json)
251+
putWithApiHeader(controller.controllerPath("env1"), ['if-match': 'ffff'], json)
258252

259253
assertThatResponse()
260254
.isPreconditionFailed()
@@ -272,10 +266,7 @@ class EnvironmentsControllerV2Test implements SecurityServiceTrait, ControllerTr
272266

273267
when(entityHashingService.md5ForEntity(env1)).thenReturn("ffff")
274268

275-
when(
276-
environmentConfigService.getMergedEnvironmentforDisplay(eq("env1"),
277-
any(HttpLocalizedOperationResult))
278-
).thenReturn(new ConfigElementForEdit<>(env1, "ffff"))
269+
when(environmentConfigService.getEnvironmentConfig(eq("env1"))).thenReturn(env1)
279270

280271
when(environmentConfigService.updateEnvironment(eq("env1"), eq(env1), eq(currentUsername()), anyString(), any(HttpLocalizedOperationResult))).then({
281272
InvocationOnMock invocation ->
@@ -285,7 +276,7 @@ class EnvironmentsControllerV2Test implements SecurityServiceTrait, ControllerTr
285276

286277
def json = toObjectString({ EnvironmentRepresenter.toJSON(it, env1) })
287278

288-
putWithApiHeader(controller.controllerPath("env1"),['if-match': 'ffff'], json)
279+
putWithApiHeader(controller.controllerPath("env1"), ['if-match': 'ffff'], json)
289280

290281
assertThatResponse()
291282
.isOk()
@@ -296,7 +287,7 @@ class EnvironmentsControllerV2Test implements SecurityServiceTrait, ControllerTr
296287
@Test
297288
void 'should error out if the environment does not exist'() {
298289
when(environmentConfigService.getMergedEnvironmentforDisplay(eq("env1"), any(HttpLocalizedOperationResult)))
299-
.then({
290+
.then({
300291
InvocationOnMock invocation ->
301292
def result = (HttpLocalizedOperationResult) invocation.arguments.last()
302293
result.badRequest("The environment does not exist")
@@ -352,12 +343,10 @@ class EnvironmentsControllerV2Test implements SecurityServiceTrait, ControllerTr
352343
updatedConfig.addPipeline(new CaseInsensitiveString("Pipeline2"))
353344

354345
when(entityHashingService.md5ForEntity(updatedConfig)).thenReturn("md5-hash")
355-
when(environmentConfigService.getMergedEnvironmentforDisplay(eq("env1"), any(HttpLocalizedOperationResult)))
356-
.thenReturn(new ConfigElementForEdit<>(oldConfig, "old_md5_hash"))
357-
when(environmentConfigService.getMergedEnvironmentforDisplay(eq("env1"), any(HttpLocalizedOperationResult)))
358-
.thenReturn(new ConfigElementForEdit<>(updatedConfig, "new_md5_hash"))
346+
when(environmentConfigService.getEnvironmentConfig(eq("env1"))).thenReturn(oldConfig)
347+
when(environmentConfigService.getEnvironmentConfig(eq("env1"))).thenReturn(updatedConfig)
359348
when(environmentConfigService.patchEnvironment(
360-
eq(oldConfig), anyList(), anyList(), anyList(), anyList(),anyList(), anyList(),eq(currentUsername()), any(HttpLocalizedOperationResult))
349+
eq(oldConfig), anyList(), anyList(), anyList(), anyList(), anyList(), anyList(), eq(currentUsername()), any(HttpLocalizedOperationResult))
361350
).then({
362351
InvocationOnMock invocation ->
363352
HttpLocalizedOperationResult result = (HttpLocalizedOperationResult) invocation.arguments.last()
@@ -366,26 +355,26 @@ class EnvironmentsControllerV2Test implements SecurityServiceTrait, ControllerTr
366355

367356

368357
patchWithApiHeader(controller.controllerPath("env1"), [
369-
"pipelines": [
370-
"add": [
358+
"pipelines" : [
359+
"add" : [
371360
"Pipeline1", "Pipeline2"
372361
],
373362
"remove": [
374363
"Pipeline3"
375364
]
376365
],
377-
"agents": [
378-
"add": [
366+
"agents" : [
367+
"add" : [
379368
"agent1"
380369
],
381370
"remove": [
382371
"agent2"
383372
]
384373
],
385374
"environment_variables": [
386-
"add": [
375+
"add" : [
387376
[
388-
"name": "JAVA_HOME",
377+
"name" : "JAVA_HOME",
389378
"value": "/bin/java"
390379
]
391380
],
@@ -516,24 +505,26 @@ class EnvironmentsControllerV2Test implements SecurityServiceTrait, ControllerTr
516505
}
517506

518507
@Test
519-
void 'should return all configured environments'() {
520-
def env1 = new BasicEnvironmentConfig(new CaseInsensitiveString("env1"))
521-
env1.addAgent("agent1")
522-
env1.addAgent("agent2")
523-
env1.addEnvironmentVariable("JAVA_HOME", "/bin/java")
524-
env1.addPipeline(new CaseInsensitiveString("Pipeline1"))
525-
env1.addPipeline(new CaseInsensitiveString("Pipeline2"))
508+
void 'should sort environments by name and return all configured environments'() {
509+
def prodEnv = new BasicEnvironmentConfig(new CaseInsensitiveString("prod"))
510+
prodEnv.addAgent("agent1")
511+
prodEnv.addAgent("agent2")
512+
prodEnv.addEnvironmentVariable("JAVA_HOME", "/bin/java")
513+
prodEnv.addPipeline(new CaseInsensitiveString("Pipeline1"))
514+
prodEnv.addPipeline(new CaseInsensitiveString("Pipeline2"))
526515

527-
def env2 = new BasicEnvironmentConfig(new CaseInsensitiveString("env2"))
516+
def devEnv = new BasicEnvironmentConfig(new CaseInsensitiveString("dev"))
517+
def qaEnv = new BasicEnvironmentConfig(new CaseInsensitiveString("qa"))
528518

529-
def listOfEnvironmentConfigs = [env1, env2]
519+
def listOfEnvironmentConfigs = new HashSet([qaEnv, devEnv, prodEnv])
530520

531-
when(environmentConfigService.getAllMergedEnvironments()).thenReturn(listOfEnvironmentConfigs)
521+
when(environmentConfigService.getEnvironments()).thenReturn(listOfEnvironmentConfigs)
532522

533523
getWithApiHeader(controller.controllerBasePath())
534-
524+
525+
def environmentsConfigSortedByName = new LinkedHashSet([devEnv, prodEnv, qaEnv])
535526
assertThatResponse()
536-
.hasBodyWithJsonObject(listOfEnvironmentConfigs, EnvironmentsRepresenter)
527+
.hasBodyWithJsonObject(environmentsConfigSortedByName, EnvironmentsRepresenter)
537528
}
538529

539530

@@ -553,7 +544,7 @@ class EnvironmentsControllerV2Test implements SecurityServiceTrait, ControllerTr
553544
]
554545
]
555546

556-
when(environmentConfigService.getAllMergedEnvironments()).thenReturn([])
547+
when(environmentConfigService.getEnvironments()).thenReturn(new HashSet<>([]))
557548

558549
getWithApiHeader(controller.controllerBasePath())
559550

api/api-environments-v2/src/test/groovy/com/thoughtworks/go/apiv2/environments/representers/AgentRepresenterTest.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class AgentRepresenterTest {
3333

3434
assertThatJson(json).isEqualTo([
3535
"_links": [
36-
"doc": [
36+
"doc" : [
3737
"href": apiDocsUrl("#agents")
3838
],
3939
"find": [
@@ -43,7 +43,7 @@ class AgentRepresenterTest {
4343
"href": "http://test.host/go/api/agents/agent1"
4444
]
4545
],
46-
"uuid": "agent1"
46+
"uuid" : "agent1"
4747
])
4848
}
4949
}

0 commit comments

Comments
 (0)