export apps to apicurio

This commit is contained in:
Radek Davidek 2025-11-04 13:29:30 +01:00
parent 2aadbdb68d
commit a36ee218c2
11 changed files with 395 additions and 59 deletions

Binary file not shown.

View File

@ -2,17 +2,22 @@ package cz.trask.migration;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -28,6 +33,12 @@ import cz.trask.migration.model.ApplicationConfig;
import cz.trask.migration.model.HttpResponse;
import cz.trask.migration.model.RegisterResponse;
import cz.trask.migration.model.TokenResponse;
import io.apicurio.registry.rest.client.RegistryClient;
import io.apicurio.registry.rest.client.RegistryClientFactory;
import io.apicurio.registry.rest.v2.beans.ArtifactMetaData;
import io.apicurio.registry.rest.v2.beans.EditableMetaData;
import io.apicurio.registry.rest.v2.beans.Rule;
import io.apicurio.registry.types.RuleType;
public abstract class AbstractProcess {
@ -35,17 +46,22 @@ public abstract class AbstractProcess {
protected static final String PARAM_SOURCE_APIM = "source_apim";
protected static final String VERSION_32 = "v32";
public static final String PRIVATE_KEY_APIM_32 = "wso2apim32-pk.pem";
public static final String ARTIFACT_GROUP_SUBSCRIPTIONS = "SUBSCRIPTIONS";
public static final String ARTIFACT_NAME_SUBSCRIPTIONS = "subs.yaml";
public static final String ARTIFACT_GROUP_APPLICATIONS = "APPLICATIONS";
public static final String ARTIFACT_APPLICATION_DEFAULT_VERSION = "1.0.0";
public static final String DEFAULT_DOC_FILE_NAME = "document.yaml";
public static ObjectMapper mapper;
public static ObjectMapper mapperYaml;
public final RegistryClient client;
protected ApplicationConfig config;
protected AbstractProcess() {
@ -57,9 +73,11 @@ public abstract class AbstractProcess {
yamlFactory.configure(YAMLGenerator.Feature.SPLIT_LINES, false);
mapperYaml = new ObjectMapper(yamlFactory);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
config = ConfigManager.getInstance().getConfig();
this.client = RegistryClientFactory.create(config.getApicurio().getApiUrl());
setTrustStoreCredentials();
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() {
@ -70,18 +88,52 @@ public abstract class AbstractProcess {
}
protected void setTrustStoreCredentials() {
log.info("Setting truststore: " + config.getTrustStore().getPath());
System.setProperty("javax.net.ssl.trustStore", config.getTrustStore().getPath());
File trustStoreFile = new File(config.getTrustStore().getPath());
if (!trustStoreFile.exists() || !trustStoreFile.isFile()) {
log.warn("Truststore file '{}' does not exist. Skipping truststore setup.", config.getTrustStore().getPath());
return;
}
log.info("Setting truststore: " + trustStoreFile.getAbsolutePath());
System.setProperty("javax.net.ssl.trustStore", trustStoreFile.getAbsolutePath());
System.setProperty("javax.net.ssl.trustStorePassword", config.getTrustStore().getPassword());
}
private SSLContext createSSLContext(String trustStorePath, String trustStorePassword)
throws Exception {
// Vytvoříme TrustStore
KeyStore trustStore = KeyStore.getInstance("JKS");
try (FileInputStream fis = new FileInputStream(trustStorePath)) {
trustStore.load(fis, trustStorePassword.toCharArray());
}
// Inicializujeme TrustManagerFactory
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(trustStore);
// Vytvoříme SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
return sslContext;
}
protected void configureHttpsConnection(HttpsURLConnection connection)
throws Exception {
SSLContext sslContext = createSSLContext(
System.getProperty("javax.net.ssl.trustStore"),
System.getProperty("javax.net.ssl.trustStorePassword"));
connection.setSSLSocketFactory(sslContext.getSocketFactory());
}
protected TokenResponse authenticateToWso2AndGetToken() throws Exception {
RegisterResponse register = register(config.getSource().getRegistrationApiUrl(), config.getSource().getWso2User());
RegisterResponse register = register(config.getSource().getRegistrationApiUrl(),
config.getSource().getWso2User());
String clientId = register.getClientId();
log.info("Registered with clientId: {}", clientId);
TokenResponse token = getToken(config.getSource().getPublisherTokenUrl(), config.getSource().getWso2User(), register,
TokenResponse token = getToken(config.getSource().getPublisherTokenUrl(), config.getSource().getWso2User(),
register,
"apim:api_view apim:api_create apim:api_manage apim:api_delete apim:api_publish "
+ "apim:subscription_view apim:subscription_block apim:subscription_manage apim:external_services_discover "
+ "apim:threat_protection_policy_create apim:threat_protection_policy_manage apim:document_create apim:document_manage "
@ -92,7 +144,7 @@ public abstract class AbstractProcess {
+ "apim:common_operation_policy_manage apim:comment_write apim:comment_view apim:admin");
log.debug("Access token received {}", token.getAccess_token());
return token;
}
@ -201,9 +253,10 @@ public abstract class AbstractProcess {
URL url = new URL(urlStr);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setRequestMethod(method);
con.setDoInput(true);
configureHttpsConnection(con);
for (String key : httpHeaders.keySet()) {
con.addRequestProperty(key, httpHeaders.get(key));
@ -269,6 +322,7 @@ public abstract class AbstractProcess {
con.setRequestMethod("POST");
con.setDoInput(true);
con.setDoOutput(true);
configureHttpsConnection(con);
for (String key : httpHeaders.keySet()) {
con.addRequestProperty(key, httpHeaders.get(key));
@ -363,6 +417,7 @@ public abstract class AbstractProcess {
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setUseCaches(false);
con.setDoOutput(true);
configureHttpsConnection(con);
con.setRequestMethod(method);
con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
@ -412,4 +467,20 @@ public abstract class AbstractProcess {
return resp;
}
protected void setArtifactMetaData(ArtifactMetaData meta, Map<String, String> props) {
EditableMetaData metaData = new EditableMetaData();
metaData.setName(meta.getName());
metaData.setDescription(meta.getDescription());
if (props != null)
metaData.setProperties(props);
client.updateArtifactMetaData(meta.getGroupId(), meta.getId(), metaData);
}
protected void createRule(ArtifactMetaData meta, String config, RuleType type) {
Rule rule = new Rule();
rule.setConfig(config);
rule.setType(type);
client.createArtifactRule(meta.getGroupId(), meta.getId(), rule);
}
}

View File

@ -3,6 +3,7 @@ package cz.trask.migration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import cz.trask.migration.impl.v32.Wso2AppsToApicurio;
import cz.trask.migration.impl.v32.Wso2v32ToApicurio;
import cz.trask.migration.impl.v45.ExportToWso2FromV32;
import cz.trask.migration.model.StartParameters;
@ -12,7 +13,7 @@ public class ApiSync {
private static Logger log = LogManager.getLogger(ApiSync.class);
public static void main(String[] args) throws Exception {
log.info("API Operator started.");
log.info("WSO2 Operator started.");
if (args == null) {
log.error("No parameters found.");
printHelp();
@ -29,6 +30,10 @@ public class ApiSync {
log.info("Export command selected.");
ExportToWso2FromV32 exp = new ExportToWso2FromV32();
exp.process();
} else if (sp.getCommand().equalsIgnoreCase("wso2AppsToApicurio")) {
log.info("Export command selected.");
Wso2AppsToApicurio apps = new Wso2AppsToApicurio();
apps.process();
} else {
log.error("Unknown command: " + sp.getCommand());
printHelp();
@ -37,6 +42,7 @@ public class ApiSync {
}
private static void printHelp() {
System.out.println("Not enough parameters.\nRun command in this format:\njava -jar shaded.jar [wso2ToApicurio/apicurioToWso2]");
System.out.println(
"Not enough parameters.\nRun command in this format:\njava -jar shaded.jar [wso2ToApicurio/apicurioToWso2]");
}
}

View File

@ -0,0 +1,161 @@
package cz.trask.migration.impl.v32;
import java.io.ByteArrayInputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import cz.trask.migration.AbstractProcess;
import cz.trask.migration.model.HttpResponse;
import cz.trask.migration.model.TokenResponse;
import cz.trask.migration.model.ZipEntryData;
import cz.trask.migration.model.v32.ApplicationDetail;
import cz.trask.migration.model.v32.ApplicationList;
import cz.trask.migration.model.v32.ApplicationList.ApplicationInfo;
import io.apicurio.registry.rest.client.RegistryClient;
import io.apicurio.registry.rest.client.RegistryClientFactory;
import io.apicurio.registry.rest.v2.beans.ArtifactMetaData;
import io.apicurio.registry.rest.v2.beans.VersionSearchResults;
import io.apicurio.registry.types.RuleType;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class Wso2AppsToApicurio extends AbstractProcess {
private final AtomicInteger appCounter = new AtomicInteger(1);
private final RegistryClient client;
public Wso2AppsToApicurio() throws Exception {
this.client = RegistryClientFactory.create(config.getApicurio().getApiUrl());
}
/**
* Main entry point for the import process.
*
* @throws RuntimeException if any error occurs
*/
public void process() {
try {
log.info("Starting API import to Apicurio from WSO2...");
TokenResponse token = authenticateToWso2AndGetToken();
ApplicationList apps = getApplicationList(config.getSource().getAdminApiUrl(), token);
if (apps == null || apps.getList() == null || apps.getList().size() == 0) {
throw new IllegalStateException(
"No Applications to export that match your criteria! Check the name of the Application you want to export.");
}
log.info("Found {} Applications", apps.getCount());
int maxThreads = config.getMaxThreads();
ExecutorService executor = Executors.newFixedThreadPool(maxThreads);
for (ApplicationInfo app : apps.getList()) {
final int index = appCounter.getAndIncrement();
executor.submit(() -> processApplication(app, token, index, apps.getCount()));
}
executor.shutdown();
if (!executor.awaitTermination(10, TimeUnit.MINUTES)) {
log.warn("Timeout waiting for API import tasks to finish");
}
log.info("Finished processing Applications.");
} catch (Exception e) {
log.error("Error while exporting Applications.", e);
throw new RuntimeException("Export failed", e);
}
}
private void processApplication(ApplicationInfo app, TokenResponse tokenResponse, int index, int total) {
long start = System.currentTimeMillis();
try {
log.info("Processing API {} of {}", index, total);
Map<String, String> httpHeaders = Collections.singletonMap("Authorization",
"Bearer " + tokenResponse.getAccess_token());
HttpResponse exportedZip = makeRequest("GET",
config.getSource().getAdminApiUrl() + "/export/applications?appName=" + app.getName() + "&appOwner="
+ app.getOwner() + "&withKeys=true",
httpHeaders,
Collections.emptyMap(), true);
List<ZipEntryData> zipEntries = ZipUtils.extractFilesFromZip(exportedZip.getResponseBytes());
for (ZipEntryData entry : zipEntries) {
ApplicationDetail appDetail = mapper.readValue(entry.getContent(), ApplicationDetail.class);
String group = ARTIFACT_GROUP_APPLICATIONS;
String mainArtifactId = appDetail.getName() + "(" + appDetail.getOwner() + ")";
VersionSearchResults existingArtifacts;
try {
existingArtifacts = client.listArtifactVersions(group, mainArtifactId, 0, Integer.MAX_VALUE);
} catch (Exception e) {
log.debug("No Application {} exists will create it", mainArtifactId);
existingArtifacts = null;
}
if (existingArtifacts == null) {
log.info("Creating new Application to Apicurio '{}' ({}).", appDetail.getName(),
appDetail.getOwner());
// Create new artifact
ArtifactMetaData meta = client.createArtifact(group, mainArtifactId,
ARTIFACT_APPLICATION_DEFAULT_VERSION, null, null,
null,
appDetail.getName(), appDetail.getName(), null, null, null,
new ByteArrayInputStream(entry.getContent()), null);
// Create the three required rules
createRule(meta, "NONE", RuleType.COMPATIBILITY);
createRule(meta, "NONE", RuleType.VALIDITY);
createRule(meta, "NONE", RuleType.INTEGRITY);
}
}
log.info("Successfully imported Application '{}' ({}). Took {} ms", app.getName(), app.getOwner(),
System.currentTimeMillis() - start);
} catch (Exception e) {
log.error("Error processing Application {} of {}: {}", index, total, e.getMessage());
}
}
private ApplicationList getApplicationList(String adminApiUrl, TokenResponse tokenResponse) throws Exception {
ApplicationList listOfApps = null;
try {
String url = adminApiUrl.concat(String.format("/applications"));
log.debug("Getting Applications with token: '" + tokenResponse.getAccess_token() + "' URL: " + url);
Map<String, String> httpHeaders = new HashMap<>();
Map<String, String> params = new HashMap<>();
httpHeaders.put("Authorization", "Bearer ".concat(tokenResponse.getAccess_token()));
HttpResponse response = makeRequest("GET", url, httpHeaders, params);
log.debug("Listing APIs: HTTP Code " + response.getResponseCode() + " Data: " + response.getResponse());
listOfApps = mapper.readValue(response.getResponse(), ApplicationList.class);
if (response.getResponseCode() != 200)
log.error("Cannot list API. Something bad happened.");
} catch (Exception e) {
log.error("Cannot list API:" + e);
throw new Exception("Cannot list API:" + e.getMessage());
}
return listOfApps;
}
}

View File

@ -12,9 +12,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@ -28,28 +25,18 @@ import cz.trask.migration.model.ZipEntryData;
import cz.trask.migration.model.v32.Subscriptions;
import cz.trask.migration.model.v32.Subscriptions.ApplicationInfo;
import cz.trask.migration.model.v32.Subscriptions.Subscription;
import io.apicurio.registry.rest.client.RegistryClient;
import io.apicurio.registry.rest.client.RegistryClientFactory;
import io.apicurio.registry.rest.client.exception.VersionAlreadyExistsException;
import io.apicurio.registry.rest.v2.beans.ArtifactMetaData;
import io.apicurio.registry.rest.v2.beans.ArtifactReference;
import io.apicurio.registry.rest.v2.beans.EditableMetaData;
import io.apicurio.registry.rest.v2.beans.Rule;
import io.apicurio.registry.rest.v2.beans.VersionSearchResults;
import io.apicurio.registry.types.RuleType;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class Wso2v32ToApicurio extends AbstractProcess {
private static final Logger log = LogManager.getLogger(Wso2v32ToApicurio.class);
private final AtomicInteger apiCounter = new AtomicInteger(1);
private final RegistryClient client;
public Wso2v32ToApicurio() throws Exception {
this.client = RegistryClientFactory.create(config.getApicurio().getApiUrl());
}
/**
* Main entry point for the import process.
*
@ -176,7 +163,7 @@ public class Wso2v32ToApicurio extends AbstractProcess {
if (existingArtifacts == null) {
// Create new artifact
List<ArtifactReference> references = createReferencesFromZip(zipEntries, group, api);
List<ArtifactReference> references = createReferencesFromZip(zipEntries, api);
addSubscriptionsToReferences(references, subs, api);
ArtifactMetaData meta = client.createArtifact(group, mainArtifactId, api.getVersion(), null, null, null,
@ -199,7 +186,7 @@ public class Wso2v32ToApicurio extends AbstractProcess {
// Version missing will create it below
}
List<ArtifactReference> references = createReferencesFromZip(zipEntries, group, api);
List<ArtifactReference> references = createReferencesFromZip(zipEntries, api);
addSubscriptionsToReferences(references, subs, api);
if (!versionExists) {
@ -299,7 +286,7 @@ public class Wso2v32ToApicurio extends AbstractProcess {
}
}
private List<ArtifactReference> createReferencesFromZip(List<ZipEntryData> zipEntries, String group, APIInfo api)
private List<ArtifactReference> createReferencesFromZip(List<ZipEntryData> zipEntries, APIInfo api)
throws IOException {
List<ArtifactReference> references = new ArrayList<>();
@ -348,21 +335,4 @@ public class Wso2v32ToApicurio extends AbstractProcess {
ref.setVersion(api.getVersion());
references.add(ref);
}
private void setArtifactMetaData(ArtifactMetaData meta, Map<String, String> props) {
EditableMetaData metaData = new EditableMetaData();
metaData.setName(meta.getName());
metaData.setDescription(meta.getDescription());
if (props != null)
metaData.setProperties(props);
client.updateArtifactMetaData(meta.getGroupId(), meta.getId(), metaData);
}
private void createRule(ArtifactMetaData meta, String config, RuleType type) {
Rule rule = new Rule();
rule.setConfig(config);
rule.setType(type);
client.createArtifactRule(meta.getGroupId(), meta.getId(), rule);
}
}

View File

@ -8,16 +8,13 @@ import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import cz.trask.migration.model.FileType;
import cz.trask.migration.model.ZipEntryData;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class ZipUtils {
private static final Logger log = LogManager.getLogger(ZipUtils.class);
public static List<ZipEntryData> extractFilesFromZip(byte[] data) throws IOException {
List<ZipEntryData> fileList = new ArrayList<>();
@ -58,7 +55,7 @@ public class ZipUtils {
return FileType.CERTIFICATE;
} else if (lowerFileName.contains("/docs/")) {
return FileType.DOCUMENTATION;
}
return FileType.UNKNOWN;
}

View File

@ -15,9 +15,6 @@ import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.fasterxml.jackson.core.type.TypeReference;
import cz.trask.migration.AbstractProcess;
@ -40,11 +37,11 @@ import io.apicurio.registry.rest.v2.beans.ArtifactSearchResults;
import io.apicurio.registry.rest.v2.beans.SearchedArtifact;
import io.apicurio.registry.rest.v2.beans.SearchedVersion;
import io.apicurio.registry.rest.v2.beans.VersionSearchResults;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class ExportToWso2FromV32 extends AbstractProcess {
private static final Logger log = LogManager.getLogger(ExportToWso2FromV32.class);
private final AtomicInteger apiCounter = new AtomicInteger(1);
private final RegistryClient client;

View File

@ -26,6 +26,8 @@ public class ApplicationConfig {
private String registrationApiUrl;
@JsonProperty("publisher_api_url")
private String publisherApiUrl;
@JsonProperty("admin_api_url")
private String adminApiUrl;
@JsonProperty("devportal_api_url")
private String devPortalApiUrl;
@JsonProperty("publisher_token_url")

View File

@ -0,0 +1,103 @@
package cz.trask.migration.model.v32;
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApplicationDetail {
private int id;
private String name;
private String uuid;
private Subscriber subscriber;
private List<SubscribedAPI> subscribedAPIs;
private List<?> keys;
private Map<String, Map<String, KeyManagerApp>> keyManagerWiseOAuthApp;
private String tier;
private String status;
private String groupId;
private String owner;
private Map<String, Object> applicationAttributes;
private String tokenType;
private int subscriptionCount;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Subscriber {
private String name;
private int id;
private int tenantId;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class SubscribedAPI {
private int subscriptionId;
private Tier tier;
private Subscriber subscriber;
private ApiId apiId;
private Application application;
private String subStatus;
private String subCreatedStatus;
private List<?> keys;
private String uuid;
private boolean isBlocked;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Tier {
private String name;
private String displayName;
private int requestsPerMin;
private int requestCount;
private int unitTime;
private String timeUnit;
private boolean stopOnQuotaReached;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ApiId {
private String providerName;
private String apiName;
private String version;
private int id;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Application {
private int id;
private String name;
private String uuid;
private Subscriber subscriber;
private List<?> subscribedAPIs;
private List<?> keys;
private Map<String, Object> keyManagerWiseOAuthApp;
private Map<String, Object> applicationAttributes;
private int subscriptionCount;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class KeyManagerApp {
private String clientId;
private String clientName;
private String callBackURL;
private String clientSecret;
private Map<String, Object> parameters;
private boolean isSaasApplication;
private Map<String, Object> appAttributes;
}
}

View File

@ -0,0 +1,28 @@
package cz.trask.migration.model.v32;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApplicationList {
private int count;
private String next;
private String previous;
private List<ApplicationInfo> list;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ApplicationInfo {
private String applicationId;
private String name;
private String owner;
private String status;
private Object groupId;
}
}

View File

@ -1,6 +1,7 @@
source:
registration_api_url: https://localhost:9444/client-registration/v0.17/register
publisher_api_url: https://localhost:9444/api/am/publisher
admin_api_url: https://localhost:9444/api/am/admin/v1
devportal_api_url: https://localhost:9444/api/am/store
publisher_token_url: https://localhost:9444/oauth2/token
wso2_user: YWRtaW46YWRtaW4=