Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).


### Added
- API import supports `-safeUpdate` to prevent updates when an existing API path belongs to a different API name [#556](https://github.com/Axway-API-Management-Plus/apim-cli/issues/556)
- Allow deletion of application credentials like oauth/apikey [#583](https://github.com/Axway-API-Management-Plus/apim-cli/issues/583)
- Extend the functionality of "override_certificates" environment property [#565](https://github.com/Axway-API-Management-Plus/apim-cli/issues/565)

Expand Down Expand Up @@ -213,7 +214,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added
- Support of All / Global quotas for API and application. (See issue [#362](https://github.com/Axway-API-Management-Plus/apim-cli/issues/362))
- Host docker cli images on github docker registry (See issue [#373](https://github.com/Axway-API-Management-Plus/apim-cli/issues/373)
- Host docker cli images on github docker registry (See issue [#373](https://github.com/Axway-API-Management-Plus/apim-cli/issues/373))


## [1.13.5] 2023-03-15
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public enum ErrorCode {
GRANT_ACCESS_APPLICATION_ERR(102, "Error granting application access to API."),
REVOKE_ACCESS_APPLICATION_ERR(103, "Error revoking application access to API."),
INVALID_SECURITY_PROFILE_CONFIG(104, "The given security profile is invalid.", false),
ERR_IMPORTING_API_DAT_FILE(105, "Error importing API-Dat file.", false);
ERR_IMPORTING_API_DAT_FILE(105, "Error importing API-Dat file.", false),
API_PATH_IN_USE_BY_DIFFERENT_API(106, "Configured API path is already used by a different API.", false);


private final int code;
Expand Down
15 changes: 15 additions & 0 deletions modules/apis/src/main/java/com/axway/apim/APIImportApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ public int importAPI(APIImportParams params) {
.useFEAPIDefinition(params.isUseFEAPIDefinition()) // Should API-Definition load from the FE-API?
.build();
API actualAPI = apimAdapter.getApiAdapter().getAPI(filter, true);
validateSafeUpdate(actualAPI, desiredAPI, params);
APIChangeState changes = new APIChangeState(actualAPI, desiredAPI, params);
new APIImportManager().applyChanges(changes, params.isForceUpdate(), params.isUpdateOnly());
APIPropertiesExport.getInstance().store();
Expand All @@ -146,6 +147,20 @@ public int importAPI(APIImportParams params) {
}
}

void validateSafeUpdate(API actualAPI, API desiredAPI, APIImportParams params) throws AppException {
if (!params.isSafeUpdate()) return;
if (actualAPI == null) return;
if (actualAPI.getName() == null || desiredAPI.getName() == null) return;
if (actualAPI.getName().equals(desiredAPI.getName())) return;
if (desiredAPI.getId() != null && desiredAPI.getId().equals(actualAPI.getId())) return;

throw new AppException(
"Safe update blocked: Path '" + desiredAPI.getPath() + "' is already used by API '" + actualAPI.getName() +
"' (ID: " + actualAPI.getId() + "). Desired API name is '" + desiredAPI.getName() + "'.",
ErrorCode.API_PATH_IN_USE_BY_DIFFERENT_API
);
}

@Override
public String getGroupId() {
return "api";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ public void addOptions() {
option.setRequired(false);
addOption(option);

option = new Option("safeUpdate", "If set, import fails when the same API path is already used by a different API name (unless apiId matches).");
option.setRequired(false);
addOption(option);

option = new Option("zeroDowntimeUpdate", true,"Always update a published APIs by creating a new API and switch clients to it. Defaults to false");
option.setRequired(false);
addOption(option);
Expand Down Expand Up @@ -131,6 +135,7 @@ public Parameters getParams() {
params.setConfig(getValue("config"));
params.setApiDefinition(getValue("apidefinition"));
params.setForceUpdate(hasOption("forceUpdate"));
params.setSafeUpdate(hasOption("safeUpdate"));
params.setChangeOrganization(hasOption("changeOrganization"));
params.setUseFEAPIDefinition(hasOption("useFEAPIDefinition"));
params.setIgnoreQuotas(hasOption("ignoreQuotas"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
public class APIImportParams extends StandardImportParams implements Parameters {

private boolean forceUpdate;
private boolean safeUpdate;
private boolean useFEAPIDefinition;
private boolean validateRemoteHost;
private boolean updateOnly;
Expand All @@ -32,6 +33,12 @@ public boolean isForceUpdate() {
public void setForceUpdate(boolean forceUpdate) {
this.forceUpdate = forceUpdate;
}
public boolean isSafeUpdate() {
return safeUpdate;
}
public void setSafeUpdate(boolean safeUpdate) {
this.safeUpdate = safeUpdate;
}
public boolean isValidateRemoteHost() {
return validateRemoteHost;
}
Expand Down
58 changes: 58 additions & 0 deletions modules/apis/src/test/java/com/axway/apim/APIImportAppTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import com.axway.apim.lib.error.AppException;
import com.axway.apim.lib.error.ErrorCode;

public class APIImportAppTest extends WiremockWrapper {

@BeforeClass
Expand Down Expand Up @@ -59,4 +62,59 @@ public void testEmptyPassword() throws Exception {
Assert.assertEquals("",desiredAPI.getAuthenticationProfiles().get(0).getParameters().get("password"));

}

@Test
public void safeUpdateAllowsSameName() throws AppException {
APIImportApp app = new APIImportApp();
APIImportParams params = new APIImportParams();
params.setSafeUpdate(true);

API actualApi = new API();
actualApi.setId("actual-id");
actualApi.setName("Order API");

API desiredApi = new API();
desiredApi.setId("desired-id");
desiredApi.setName("Order API");
desiredApi.setPath("/api/orders/v1");

app.validateSafeUpdate(actualApi, desiredApi, params);
}

@Test
public void safeUpdateAllowsNameMismatchWhenApiIdMatches() throws AppException {
APIImportApp app = new APIImportApp();
APIImportParams params = new APIImportParams();
params.setSafeUpdate(true);

API actualApi = new API();
actualApi.setId("same-id");
actualApi.setName("Order API");

API desiredApi = new API();
desiredApi.setId("same-id");
desiredApi.setName("Inventory API");
desiredApi.setPath("/api/orders/v1");

app.validateSafeUpdate(actualApi, desiredApi, params);
}

@Test
public void safeUpdateBlocksNameMismatchForSamePath() {
APIImportApp app = new APIImportApp();
APIImportParams params = new APIImportParams();
params.setSafeUpdate(true);

API actualApi = new API();
actualApi.setId("actual-id");
actualApi.setName("Order API");

API desiredApi = new API();
desiredApi.setId("different-id");
desiredApi.setName("Inventory API");
desiredApi.setPath("/api/orders/v1");

AppException exception = Assert.expectThrows(AppException.class, () -> app.validateSafeUpdate(actualApi, desiredApi, params));
Assert.assertEquals(exception.getError(), ErrorCode.API_PATH_IN_USE_BY_DIFFERENT_API);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,12 @@ public void testAPIImportParameter() throws AppException {

@Test
public void testToggles() throws AppException {
String[] args = {"-s", "prod", "-c", "myConfig.json", "-rollback", "true", "-force", "-forceUpdate", "-ignoreCache", "-useFEAPIDefinition", "-changeOrganization", "-ignoreQuotas", "-updateOnly"};
String[] args = {"-s", "prod", "-c", "myConfig.json", "-rollback", "true", "-force", "-forceUpdate", "-safeUpdate", "-ignoreCache", "-useFEAPIDefinition", "-changeOrganization", "-ignoreQuotas", "-updateOnly"};
CLIOptions options = CLIAPIImportOptions.create(args);
APIImportParams params = (APIImportParams) options.getParams();
Assert.assertTrue(params.isForce());
Assert.assertTrue(params.isForceUpdate());
Assert.assertTrue(params.isSafeUpdate());
Assert.assertTrue(params.isIgnoreCache());
Assert.assertTrue(params.isUpdateOnly());
Assert.assertTrue(params.isChangeOrganization());
Expand Down Expand Up @@ -96,4 +97,12 @@ public void testRetireFlagAndDate() throws AppException {
Assert.assertTrue(params.isReferenceAPIRetire());
Assert.assertEquals(params.getReferenceAPIRetirementDate(), "31.12.2027");
}

@Test
public void testSafeUpdateDefault() throws AppException {
String[] args = {"-s", "prod", "-c", "myConfig.json"};
CLIOptions options = CLIAPIImportOptions.create(args);
APIImportParams params = (APIImportParams) options.getParams();
Assert.assertFalse(params.isSafeUpdate());
}
}
Loading