Skip to content

Commit

Permalink
Added the feature to export APIs as DAT-Files
Browse files Browse the repository at this point in the history
Solves #191
  • Loading branch information
Chris Wiechmann committed Jul 16, 2021
1 parent 2528fd7 commit c417d84
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 8 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Fixed
- No change detected when changing authenticationProfile in outboundProfiles (See issue [#184](https://github.com/Axway-API-Management-Plus/apim-cli/issues/184)) by @ftriolet
- A WSDL file with a long comment at the beginning is not recognized as a WSDL specification. (See issue [#190https://github.com/Axway-API-Management-Plus/apim-cli/issues/190))
- A WSDL file with a long comment at the beginning is not recognized as a WSDL specification. (See issue [#190](https://github.com/Axway-API-Management-Plus/apim-cli/issues/190))

### Added
- Added feature to export APIs as DAT-Files (See issue [#191](https://github.com/Axway-API-Management-Plus/apim-cli/issues/191))

## [1.3.9] 2021-07-02
### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public void loginToAPIManager(boolean useAdminClient) throws AppException {
int statusCode = httpResponse.getStatusLine().getStatusCode();
if(statusCode != 303 && (statusCode < 200 || statusCode > 299)) {
String response = EntityUtils.toString(httpResponse.getEntity());
LOG.warn("Login failed with statusCode: " +statusCode+ ". Got response: '"+response+"' ... Try again in 1 second.");
LOG.warn("Login failed with statusCode: " +statusCode+ " ... Try again in 1 second.");
Thread.sleep(1000);
httpResponse = loginRequest.execute();
statusCode = httpResponse.getStatusLine().getStatusCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -691,13 +691,58 @@ public void publishAPI(API api, String vhost) throws AppException {
updateAPIStatus(api, API.STATE_PUBLISHED, vhost);
}

public byte[] getAPIDatFile(API api, String password) throws AppException {
URI uri;
HttpResponse httpResponse = null;
RestAPICall request;
try {
List <NameValuePair> parameters = new ArrayList <NameValuePair>();
parameters.add(new BasicNameValuePair("filename", "api-export.dat"));
parameters.add(new BasicNameValuePair("password", password));
parameters.add(new BasicNameValuePair("id", api.getId()));
HttpEntity entity = new UrlEncodedFormEntity(parameters);

uri = new URIBuilder(cmd.getAPIManagerURL())
.setPath(cmd.getApiBasepath()+"/proxies/export")
.build();
request = new POSTRequest(entity, uri);
httpResponse = request.execute();
int statusCode = httpResponse.getStatusLine().getStatusCode();
if(statusCode != 201){
String response = EntityUtils.toString(httpResponse.getEntity());
LOG.error("Error exporting DAT-File representation of API: "+api.getName()+" ("+api.getId()+"). Received Status-Code: " +statusCode+ ", Response: '" + response + "'");
throw new AppException("Error exporting DAT-File representation of API: "+api.getName()+" ("+api.getId()+"). Received Status-Code: " +statusCode, ErrorCode.ERR_EXPORTING_API_DAT_FILE);
} else {
// The file can now be loaded from the returned Location header
String locationHeader = httpResponse.getHeaders("Location")[0].getValue();
uri = new URI(cmd.getAPIManagerURL() + locationHeader);
request = new GETRequest(uri);
httpResponse = request.execute();
statusCode = httpResponse.getStatusLine().getStatusCode();
if(statusCode != 200){
String response = EntityUtils.toString(httpResponse.getEntity());
LOG.error("Error getting DAT-File representation of API: "+api.getName()+" ("+api.getId()+"). Received Status-Code: " +statusCode+ ", Response: '" + response + "'");
throw new AppException("Error getting DAT-File representation of API: "+api.getName()+" ("+api.getId()+"). Received Status-Code: " +statusCode, ErrorCode.ERR_EXPORTING_API_DAT_FILE);
}
return EntityUtils.toByteArray(httpResponse.getEntity());
}
} catch (Exception e) {
throw new AppException("Cannot export API-DAT file.", ErrorCode.ERR_EXPORTING_API_DAT_FILE, e);
} finally {
try {
if(httpResponse!=null)
((CloseableHttpResponse)httpResponse).close();
} catch (Exception ignore) {}
}
}

public void updateAPIStatus(API api, String desiredState, String vhost) throws AppException {
LOG.debug("Update API-Proxy status to: " + api.getState());
URI uri;
HttpResponse httpResponse = null;
RestAPICall request;
try {
uri = new URIBuilder(cmd.getAPIManagerURL())
uri = new URIBuilder(cmd.getAPIManagerURL())
.setPath(cmd.getApiBasepath()+"/proxies/"+api.getId()+"/"+StatusEndpoint.valueOf(desiredState).endpoint)
.build();
if(vhost!=null && desiredState.equals(API.STATE_PUBLISHED)) { // During publish, it might be required to also set the VHost (See issue: #98)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ public void addOptions() {
option.setArgName("my/apis");
cliOptions.addOption(option);

option = new Option("o", "output", true, "Controls the output format. By default the console is used. CSV is not supported for all entities.");
option = new Option("o", "output", true, "Controls the output format. By default the console is used. CSV and DAT is not supported for all entities.");
option.setRequired(false);
option.setArgName("console|json|csv");
option.setArgName("console|json|csv|dat");
cliOptions.addOption(option);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public static enum Wide {
public static enum OutputFormat {
console,
json,
csv;
csv,
dat;

public static OutputFormat getFormat(String name) {
if(name==null || valueOf(name)==null) return console;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public enum ErrorCode {
ERR_DELETING_API (91, "API could not be deleted.", false),
ERR_DELETING_ORG (92, "Organization could not be deleted.", false),
ERR_GRANTING_ACCESS_TO_API (93, "Error granting access to an API.", false),
ERR_EXPORTING_API_DAT_FILE (94, "Error exporting API-Date file.", false),
UNXPECTED_ERROR (99, "An unexpected error occured.");

private final int code;
Expand Down
4 changes: 3 additions & 1 deletion modules/apis/src/main/java/com/axway/apim/APIExportApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class APIExportApp implements APIMCLIServiceProvider {
static ErrorCodeMapper errorCodeMapper = new ErrorCodeMapper();

public static void main(String args[]) {
int rc = grantAccess(args);
int rc = exportAPI(args);
System.exit(rc);
}

Expand Down Expand Up @@ -72,6 +72,8 @@ public int exportAPI(APIExportParams params) {
return execute(params, APIListImpl.JSON_EXPORTER);
case csv:
return execute(params, APIListImpl.CSV_EXPORTER);
case dat:
return execute(params, APIListImpl.DAT_EXPORTER);
default:
return execute(params, APIListImpl.CONSOLE_EXPORTER);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public enum APIListImpl {
JSON_EXPORTER(JsonAPIExporter.class),
CONSOLE_EXPORTER(ConsoleAPIExporter.class),
CSV_EXPORTER(CSVAPIExporter.class),
DAT_EXPORTER(DATAPIExporter.class),
API_DELETE_HANDLER(DeleteAPIHandler.class),
API_PUBLISH_HANDLER(PublishAPIHandler.class),
API_UNPUBLISH_HANDLER(UnpublishAPIHandler.class),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.axway.apim.api.export.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.axway.apim.adapter.APIManagerAdapter;
import com.axway.apim.adapter.apis.APIFilter;
import com.axway.apim.adapter.apis.APIFilter.Builder;
import com.axway.apim.adapter.apis.OrgFilter;
import com.axway.apim.api.API;
import com.axway.apim.api.export.lib.params.APIExportParams;
import com.axway.apim.lib.errorHandling.AppException;
import com.axway.apim.lib.errorHandling.ErrorCode;

public class DATAPIExporter extends APIResultHandler {
private static Logger LOG = LoggerFactory.getLogger(DATAPIExporter.class);

/** Where to store the exported API-Definition */
private String givenExportFolder = null;

private String datPassword = null;

APIManagerAdapter apiManager = APIManagerAdapter.getInstance();

public DATAPIExporter(APIExportParams params) throws AppException {
super(params);
this.givenExportFolder = params.getTarget();
this.datPassword = params.getDatPassword();
}

@Override
public void execute(List<API> apis) throws AppException {
for (API api : apis) {
try {
saveAPILocally(api);
} catch (AppException e) {
LOG.error("Can't export API: " + e.getMessage() + " as DAT-File. Please check in API-Manager UI the API is valid.", e);
}
}
return;
}

@Override
public APIFilter getFilter() {
Builder builder = getBaseAPIFilterBuilder();
return builder.build();
}

private void saveAPILocally(API api) throws AppException {
String apiPath = getAPIExportFolder(api.getPath());
File localFolder = new File(this.givenExportFolder +File.separator+ getVHost(api) + apiPath);
LOG.info("Going to export API into folder: " + localFolder);
if(localFolder.exists()) {
if(params.isDeleteTarget()) {
LOG.debug("Existing local export folder: " + localFolder + " already exists and will be deleted.");
try {
FileUtils.deleteDirectory(localFolder);
} catch (IOException e) {
throw new AppException("Error deleting local folder", ErrorCode.UNXPECTED_ERROR, e);
}
} else {
LOG.warn("Local export folder: " + localFolder + " already exists. API will not be exported. (You may set -deleteTarget)");
return;
}
}
if (!localFolder.mkdirs()) {
throw new AppException("Cant create export folder: " + localFolder, ErrorCode.UNXPECTED_ERROR);
}
byte[] datFileContent = apiManager.apiAdapter.getAPIDatFile(api, datPassword);

String targetFile = null;
try {
targetFile = localFolder.getCanonicalPath() + "/" + api.getName() + ".dat";
writeBytesToFile(datFileContent, targetFile);
} catch (IOException e) {
throw new AppException("Can't save API-DAT file locally: " + targetFile,
ErrorCode.UNXPECTED_ERROR, e);
}
LOG.info("Successfully exported API as DAT-File into folder: " + localFolder.getAbsolutePath());
}

private String getVHost(API api) throws AppException {
if(api.getVhost()!=null) return api.getVhost() + File.separator;
String orgVHost = APIManagerAdapter.getInstance().orgAdapter.getOrg(new OrgFilter.Builder().hasId(api.getOrganizationId()).build()).getVirtualHost();
if(orgVHost!=null) return orgVHost+File.separator;
return "";
}


private static void writeBytesToFile(byte[] bFile, String fileDest) throws AppException {

try (FileOutputStream fileOuputStream = new FileOutputStream(fileDest)) {
fileOuputStream.write(bFile);
} catch (IOException e) {
throw new AppException("Can't write file", ErrorCode.UNXPECTED_ERROR, e);
}
}

private String getAPIExportFolder(String apiExposurePath) {
if (apiExposurePath.startsWith("/"))
apiExposurePath = apiExposurePath.replaceFirst("/", "");
if (apiExposurePath.endsWith("/"))
apiExposurePath = apiExposurePath.substring(0, apiExposurePath.length() - 1);
apiExposurePath = apiExposurePath.replace("/", "-");
return apiExposurePath;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static CLIOptions create(String[] args) {
public Parameters getParams() {
APIExportParams params = new APIExportParams();
params.setUseFEAPIDefinition(hasOption("useFEAPIDefinition"));
params.setDatPassword(getValue("datPassword"));
return params;
}

Expand All @@ -37,6 +38,11 @@ public void addOptions() {
+ "from the FE-API instead of the original imported API. But the specification contains the host, basePath and scheme from the backend.");
option.setRequired(false);
addOption(option);

option = new Option("datPassword", true, "Password used when exporting APIs in a DAT-Format.");
option.setRequired(false);
option.setArgName("myExportPassword");
addOption(option);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class APIExportParams extends StandardExportParams implements APIFilterPa

private String tag;

private String datPassword;

public static synchronized APIExportParams getInstance() {
return (APIExportParams)CoreParameters.getInstance();
}
Expand Down Expand Up @@ -122,6 +124,14 @@ public void setTag(String tag) {
this.tag = tag;
}

public String getDatPassword() {
return datPassword;
}

public void setDatPassword(String datPassword) {
this.datPassword = datPassword;
}

public boolean isUseFEAPIDefinition() {
if(useFEAPIDefinition==null) return false;
return useFEAPIDefinition.booleanValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
public class APIExportCLIOptionsTest {
@Test
public void testAPIExportParams() throws ParseException, AppException {
String[] args = {"-s", "prod", "-a", "/api/v1/greet", "-n", "*MyAPIName*", "-id", "412378923", "-policy", "*PolicyName*", "-vhost", "custom.host.com", "-state", "approved", "-backend", "backend.customer.com", "-tag", "*myTag*", "-t", "myTarget", "-o", "csv", "-useFEAPIDefinition", "-wide", "-deleteTarget"};
String[] args = {"-s", "prod", "-a", "/api/v1/greet", "-n", "*MyAPIName*", "-id", "412378923", "-policy", "*PolicyName*", "-vhost", "custom.host.com", "-state", "approved", "-backend", "backend.customer.com", "-tag", "*myTag*", "-t", "myTarget", "-o", "csv", "-useFEAPIDefinition", "-wide", "-deleteTarget", "-datPassword", "123456Axway"};
CLIOptions options = CLIAPIExportOptions.create(args);
APIExportParams params = (APIExportParams) options.getParams();
Assert.assertEquals(params.getUsername(), "apiadmin");
Expand All @@ -44,6 +44,7 @@ public void testAPIExportParams() throws ParseException, AppException {
Assert.assertEquals(params.getVhost(), "custom.host.com");
Assert.assertEquals(params.getState(), "approved");
Assert.assertEquals(params.getBackend(), "backend.customer.com");
Assert.assertEquals(params.getDatPassword(), "123456Axway");
}

@Test
Expand Down

0 comments on commit c417d84

Please sign in to comment.