Skip to content

Commit 2976db5

Browse files
feat: Static Definition creation endpoints in API Catalog (#1735)
* WIP Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add controller and error handler Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add override controller Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Refactoring and debug log Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add license and fix checkstyle Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add functionality to retrieve static location from discovery env endpoint Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add unit test Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add tests Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix code smell Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix bug Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add more test Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Raise coverage Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Address code review comments Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Address code review comments pt.2 Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Change the logic and add shared volumes mount Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add integration tests Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix location Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Add logs Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Set environment variable in docker containers Signed-off-by: at670475 <andrea.tabone@broadcom.com> * more debugging Signed-off-by: at670475 <andrea.tabone@broadcom.com> * additional debug Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix test Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Improve check Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix indentation Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Change the way to get static location Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix IT Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix static def path Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Try the docker URL Signed-off-by: Jakub Balhar <jakub@balhar.net> * Another take on volumes Signed-off-by: Jakub Balhar <jakub@balhar.net> * Fix assertion message Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Revert changes Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Get the first directory defined Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Fix code smells Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Revert staticApiservice Signed-off-by: at670475 <andrea.tabone@broadcom.com> * Increase code coverage Signed-off-by: at670475 <andrea.tabone@broadcom.com> * revert docker-compose.yml files for local setup Signed-off-by: at670475 <andrea.tabone@broadcom.com> Co-authored-by: Jakub Balhar <jakub@balhar.net>
1 parent 2274614 commit 2976db5

File tree

8 files changed

+579
-11
lines changed

8 files changed

+579
-11
lines changed

.github/workflows/containers.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ jobs:
5656
services:
5757
api-catalog-services:
5858
image: ghcr.io/balhar-jakub/api-catalog-services:${{ github.event.pull_request.number || 'latest' }}
59+
volumes:
60+
- /api-layer/config/docker/api-defs:/usr/local/etc/config/api-defs
61+
env:
62+
APIML_DISCOVERY_STATICAPIDEFINITIONSDIRECTORIES: /usr/local/etc/config/api-defs
5963
caching-service:
6064
image: ghcr.io/balhar-jakub/caching-service:${{ github.event.pull_request.number || 'latest' }}
6165
discoverable-client:
@@ -384,6 +388,10 @@ jobs:
384388
services:
385389
api-catalog-services:
386390
image: ghcr.io/balhar-jakub/api-catalog-services:${{ github.event.pull_request.number || 'latest' }}
391+
volumes:
392+
- /api-layer/config/docker/api-defs:/usr/local/etc/config/api-defs
393+
env:
394+
APIML_DISCOVERY_STATICAPIDEFINITIONSDIRECTORIES: /usr/local/etc/config/api-defs
387395
caching-service:
388396
image: ghcr.io/balhar-jakub/caching-service:${{ github.event.pull_request.number || 'latest' }}
389397
discoverable-client:
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright Contributors to the Zowe Project.
9+
*/
10+
11+
package org.zowe.apiml.apicatalog.staticapi;
12+
13+
import lombok.RequiredArgsConstructor;
14+
import org.springframework.http.MediaType;
15+
import org.springframework.http.ResponseEntity;
16+
import org.springframework.web.bind.annotation.*;
17+
18+
import java.io.IOException;
19+
20+
/**
21+
* Controller to handle the request issued from the UI to generate
22+
* a static definition file from the Wizard interface
23+
*/
24+
@RestController
25+
@RequestMapping("/static-api")
26+
@RequiredArgsConstructor
27+
public class StaticDefinitionController {
28+
private final StaticDefinitionGenerator staticDefinitionGenerator;
29+
30+
/**
31+
* Retrieve the yaml from the request and store it in the file
32+
*
33+
* @param payload the request payload
34+
* @return the response entity
35+
*/
36+
@PostMapping(value = "/generate", produces = MediaType.APPLICATION_JSON_VALUE)
37+
public ResponseEntity<String> generateStaticDef(@RequestBody String payload, @RequestHeader(value = "Service-Id") String serviceId) throws IOException {
38+
StaticAPIResponse staticAPIResponse = staticDefinitionGenerator.generateFile(payload, serviceId);
39+
return ResponseEntity
40+
.status(staticAPIResponse.getStatusCode())
41+
.body(staticAPIResponse.getBody());
42+
}
43+
44+
/**
45+
* Overwrite the file already created
46+
*
47+
* @param payload the request payload
48+
* @return the response entity
49+
*/
50+
@PostMapping(value = "/override", produces = MediaType.APPLICATION_JSON_VALUE)
51+
public ResponseEntity<String> overrideStaticDef(@RequestBody String payload, @RequestHeader(value = "Service-Id") String serviceId) throws IOException {
52+
StaticAPIResponse staticAPIResponse = staticDefinitionGenerator.overrideFile(payload, serviceId);
53+
return ResponseEntity
54+
.status(staticAPIResponse.getStatusCode())
55+
.body(staticAPIResponse.getBody());
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright Contributors to the Zowe Project.
9+
*/
10+
package org.zowe.apiml.apicatalog.staticapi;
11+
12+
import lombok.RequiredArgsConstructor;
13+
import org.springframework.http.HttpStatus;
14+
import org.springframework.http.ResponseEntity;
15+
import org.springframework.web.bind.annotation.ControllerAdvice;
16+
import org.springframework.web.bind.annotation.ExceptionHandler;
17+
import org.zowe.apiml.message.api.ApiMessageView;
18+
import org.zowe.apiml.message.core.Message;
19+
import org.zowe.apiml.message.core.MessageService;
20+
21+
import java.io.IOException;
22+
import java.nio.file.FileAlreadyExistsException;
23+
24+
/**
25+
* This class creates responses for exceptional behavior of the StaticDefinitionController
26+
*/
27+
@ControllerAdvice(assignableTypes = {StaticDefinitionController.class})
28+
@RequiredArgsConstructor
29+
public class StaticDefinitionControllerExceptionHandler {
30+
private final MessageService messageService;
31+
32+
/**
33+
* Could not create the static definition file
34+
*
35+
* @param exception IOException
36+
* @return 500 status code
37+
*/
38+
@ExceptionHandler(IOException.class)
39+
public ResponseEntity<ApiMessageView> handleIOException(IOException exception) {
40+
Message message = messageService.createMessage("org.zowe.apiml.apicatalog.StaticDefinitionGenerationFailed",
41+
exception);
42+
43+
return ResponseEntity
44+
.status(HttpStatus.INTERNAL_SERVER_ERROR)
45+
.body(message.mapToView());
46+
}
47+
48+
/**
49+
* Handle the exception when trying to override the file
50+
*
51+
* @param exception FileAlreadyExistsException
52+
* @return 409 status code
53+
*/
54+
@ExceptionHandler(FileAlreadyExistsException.class)
55+
public ResponseEntity<ApiMessageView> handleFileAlreadyExistsException(FileAlreadyExistsException exception) {
56+
Message message = messageService.createMessage("org.zowe.apiml.apicatalog.StaticDefinitionGenerationFailed",
57+
exception);
58+
59+
return ResponseEntity
60+
.status(HttpStatus.CONFLICT)
61+
.body(message.mapToView());
62+
}
63+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* This program and the accompanying materials are made available under the terms of the
3+
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
4+
* https://www.eclipse.org/legal/epl-v20.html
5+
*
6+
* SPDX-License-Identifier: EPL-2.0
7+
*
8+
* Copyright Contributors to the Zowe Project.
9+
*/
10+
11+
package org.zowe.apiml.apicatalog.staticapi;
12+
13+
import lombok.extern.slf4j.Slf4j;
14+
import org.springframework.beans.factory.annotation.Value;
15+
import org.springframework.stereotype.Service;
16+
17+
import java.io.File;
18+
import java.io.FileOutputStream;
19+
import java.io.IOException;
20+
import java.nio.charset.StandardCharsets;
21+
import java.nio.file.FileAlreadyExistsException;
22+
import java.util.concurrent.atomic.AtomicReference;
23+
import java.util.regex.Pattern;
24+
25+
/**
26+
* Service to handle the creation of the static definition file.
27+
* Allows the generation and the override of the .yml.
28+
* Retrieves the static definition location and stores the file there.
29+
*/
30+
@Service
31+
@Slf4j
32+
public class StaticDefinitionGenerator {
33+
34+
private AtomicReference<String> fileName = new AtomicReference<>("");
35+
36+
@Value("${apiml.discovery.userid:eureka}")
37+
private String eurekaUserid;
38+
39+
@Value("${apiml.discovery.password:password}")
40+
private String eurekaPassword;
41+
42+
@Value("${server.attls.enabled:false}")
43+
private boolean isAttlsEnabled;
44+
45+
@Value("${apiml.discovery.staticApiDefinitionsDirectories:config/local/api-defs}")
46+
private String staticApiDefinitionsDirectories;
47+
48+
public StaticAPIResponse generateFile(String file, String serviceId) throws IOException {
49+
if (!serviceIdIsValid(serviceId)) {
50+
log.debug("The service ID {} has not valid format", serviceId);
51+
return new StaticAPIResponse(400, "The service ID format is not valid.");
52+
}
53+
String location = retrieveStaticDefLocation();
54+
file = formatFile(file);
55+
String absoluteFilePath = String.format("./%s/%s.yml", location, serviceId);
56+
fileName.set(absoluteFilePath);
57+
58+
checkIfFileExists(absoluteFilePath);
59+
String message = "The static definition file has been created by the user! Its location is: %s";
60+
return writeFileAndSendResponse(file, fileName, String.format(message, fileName));
61+
}
62+
63+
public StaticAPIResponse overrideFile(String file, String serviceId) throws IOException {
64+
if (!serviceIdIsValid(serviceId)) {
65+
log.debug("The service ID {} has not valid format", serviceId);
66+
return new StaticAPIResponse(400, "The service ID format is not valid.");
67+
}
68+
String location = retrieveStaticDefLocation();
69+
file = formatFile(file);
70+
String absoluteFilePath = String.format("./%s/%s.yml", location, serviceId);
71+
fileName.set(absoluteFilePath);
72+
String message = "The static definition file %s has been overwritten by the user!";
73+
return writeFileAndSendResponse(file, fileName, String.format(message, fileName));
74+
}
75+
76+
private String formatFile(String file) {
77+
file = file.replace("\\n", System.lineSeparator());
78+
file = file.substring(1, file.length() - 1);
79+
return file;
80+
}
81+
82+
private StaticAPIResponse writeFileAndSendResponse(String file, AtomicReference<String> fileName, String message) throws IOException {
83+
try (FileOutputStream fos = new FileOutputStream(fileName.get())) {
84+
fos.write(file.getBytes(StandardCharsets.UTF_8));
85+
86+
log.debug(message);
87+
return new StaticAPIResponse(201, message);
88+
}
89+
}
90+
91+
private void checkIfFileExists(String location) throws FileAlreadyExistsException {
92+
File outFile = new File(location);
93+
if (outFile.exists()) {
94+
throw new FileAlreadyExistsException(String.format("The static definition file %s already exists!", location));
95+
}
96+
}
97+
98+
/**
99+
* Retrieve the static definition location either from the System environments or configuration. If no property is set,
100+
* the default value is used (local environment). The static definition is stored inside the first defined directory.
101+
* @return the static definition location
102+
*/
103+
private String retrieveStaticDefLocation() {
104+
log.debug(String.format("The value of apiml.discovery.staticApiDefinitionsDirectories is: %s", staticApiDefinitionsDirectories));
105+
String[] directories = staticApiDefinitionsDirectories.split(";");
106+
return directories[0];
107+
}
108+
109+
/**
110+
* Validate the service ID
111+
* @param serviceId the service ID
112+
* @return true if valid
113+
*/
114+
private boolean serviceIdIsValid(String serviceId) {
115+
if (serviceId != null && !serviceId.isEmpty()) {
116+
Pattern p = Pattern.compile("^[A-Za-z][A-Za-z0-9-]*$");
117+
if (p.matcher(serviceId).find() && serviceId.length() < 16) {
118+
return true;
119+
}
120+
}
121+
return false;
122+
}
123+
}
124+

api-catalog-services/src/main/resources/apicatalog-log-messages.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,10 @@ messages:
118118
- The URI is not valid. Ensure the service is providing a valid URL.\n
119119
- Not able to select a route for the URL of the specific service. The original URL is used. If necessary, check the routing metadata of the service.\n
120120
- The path of the service URL is not valid. Ensure the service is providing the correct path.\n"
121+
122+
- key: org.zowe.apiml.apicatalog.StaticDefinitionGenerationFailed
123+
number: ZWEAC709
124+
type: ERROR
125+
text: "Static definition generation failed, caused by exception: %s"
126+
reason: "The Static definition generation could not be performed because of exception."
127+
action: "Check the specific exception for troubleshooting."

0 commit comments

Comments
 (0)