Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Apollo 3.0.0
* [Feature: auto-provision an enabled AccessKey per environment when creating a new app, controlled by `apollo.access-key.auto-provision.enabled` in ApolloConfigDB.ServerConfig](https://github.com/apolloconfig/apollo/pull/5589)
* [Feature: support ServerConfig create/update/delete by `key + cluster` in ConfigDB management, with UI cluster awareness and multi-cluster safety tests](https://github.com/apolloconfig/apollo/pull/5601)
* [Change: adapt Apollo Portal OpenAPI migration to apollo-openapi v0.2.0](https://github.com/apolloconfig/apollo/pull/5608)
* [Change: migrate Apollo Portal config item UI operations to OpenAPI](https://github.com/apolloconfig/apollo/pull/5610)

------------------
All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/18?closed=1)
2 changes: 1 addition & 1 deletion apollo-portal/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<artifactId>apollo-portal</artifactId>
<name>Apollo Portal</name>
<properties>
<apollo.openapi.spec.url>https://raw.githubusercontent.com/apolloconfig/apollo-openapi/refs/tags/v0.2.0/apollo-openapi.yaml</apollo.openapi.spec.url>
<apollo.openapi.spec.url>https://raw.githubusercontent.com/apolloconfig/apollo-openapi/refs/tags/v0.3.0/apollo-openapi.yaml</apollo.openapi.spec.url>
<github.path>${project.artifactId}</github.path>
<maven.build.timestamp.format>yyyyMMddHHmmss</maven.build.timestamp.format>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

public interface AppOpenApiService {

OpenAppDTO createApp(@NonNull OpenCreateAppDTO req, String operator);
void createApp(@NonNull OpenCreateAppDTO req, String operator);

/**
* Returns the legacy v0.2.0-compatible env and cluster-name list for an app.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2025 Apollo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.ctrip.framework.apollo.openapi.server.service;

import com.ctrip.framework.apollo.openapi.model.OpenItemDTO;
import com.ctrip.framework.apollo.openapi.model.OpenItemDiffDTO;
import com.ctrip.framework.apollo.openapi.model.OpenItemPageDTO;
import com.ctrip.framework.apollo.openapi.model.OpenNamespaceSyncDTO;
import com.ctrip.framework.apollo.openapi.model.OpenNamespaceTextModel;
import java.util.List;

/**
* Portal-local Item OpenAPI service backed by generated OpenAPI model contracts.
*/
public interface ItemOpenApiService {

OpenItemDTO getItem(String appId, String env, String clusterName, String namespaceName,
String key);

OpenItemDTO createItem(String appId, String env, String clusterName, String namespaceName,
OpenItemDTO itemDTO, String operator);

void updateItem(String appId, String env, String clusterName, String namespaceName,
OpenItemDTO itemDTO, String operator);

void createOrUpdateItem(String appId, String env, String clusterName, String namespaceName,
OpenItemDTO itemDTO, String operator);

void removeItem(String appId, String env, String clusterName, String namespaceName, String key,
String operator);

OpenItemPageDTO findItemsByNamespace(String appId, String env, String clusterName,
String namespaceName, int page, int size);

List<OpenItemDTO> findBranchItems(String appId, String env, String branchName,
String namespaceName);

void batchUpdateItemsByText(String appId, String env, String clusterName, String namespaceName,
OpenNamespaceTextModel model, String operator);

List<OpenItemDiffDTO> compareItems(String appId, String env, String clusterName,
String namespaceName, OpenNamespaceSyncDTO model);

void syncItems(String appId, String env, String clusterName, String namespaceName,
OpenNamespaceSyncDTO model, String operator);

void revertItems(String appId, String env, String clusterName, String namespaceName,
String operator);
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,13 @@ private App convert(OpenAppDTO dto) {
* @see com.ctrip.framework.apollo.portal.controller.AppController#create(AppModel)
*/
@Override
public OpenAppDTO createApp(OpenCreateAppDTO req, String operator) {
public void createApp(OpenCreateAppDTO req, String operator) {
App app = convert(req.getApp());
Set<String> admins = req.getAdmins();
if (admins == null) {
admins = Collections.emptySet();
}
App createdApp = appService.createAppAndAddRolePermission(app, admins, operator);
return OpenApiModelConverters.fromApp(createdApp);
appService.createAppAndAddRolePermission(app, admins, operator);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,26 @@

import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.openapi.api.ItemOpenApiService;
import com.ctrip.framework.apollo.openapi.dto.OpenItemDTO;
import com.ctrip.framework.apollo.openapi.dto.OpenPageDTO;
import com.ctrip.framework.apollo.openapi.util.OpenApiBeanUtils;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.openapi.model.OpenItemDTO;
import com.ctrip.framework.apollo.openapi.model.OpenItemDiffDTO;
import com.ctrip.framework.apollo.openapi.model.OpenItemPageDTO;
import com.ctrip.framework.apollo.openapi.model.OpenNamespaceSyncDTO;
import com.ctrip.framework.apollo.openapi.model.OpenNamespaceTextModel;
import com.ctrip.framework.apollo.openapi.util.OpenApiModelConverters;
import com.ctrip.framework.apollo.portal.entity.model.NamespaceTextModel;
import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.service.ItemService;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpStatusCodeException;

/**
* @author wxq
* Server-side Item OpenAPI service implementation.
*/
@Service
public class ServerItemOpenApiService implements ItemOpenApiService {
Expand All @@ -45,73 +53,135 @@ public OpenItemDTO getItem(String appId, String env, String clusterName, String
String key) {
ItemDTO itemDTO =
itemService.loadItem(Env.valueOf(env), appId, clusterName, namespaceName, key);
return itemDTO == null ? null : OpenApiBeanUtils.transformFromItemDTO(itemDTO);
return itemDTO == null ? null : OpenApiModelConverters.fromItemDTO(itemDTO);
}

@Override
public OpenItemDTO createItem(String appId, String env, String clusterName, String namespaceName,
OpenItemDTO itemDTO) {
OpenItemDTO itemDTO, String operator) {

ItemDTO toCreate = OpenApiBeanUtils.transformToItemDTO(itemDTO);
ItemDTO toCreate = OpenApiModelConverters.toItemDTO(itemDTO);

// protect
toCreate.setLineNum(0);
toCreate.setId(0);
toCreate.setDataChangeLastModifiedBy(toCreate.getDataChangeCreatedBy());
toCreate.setDataChangeCreatedBy(operator);
toCreate.setDataChangeLastModifiedBy(operator);
toCreate.setDataChangeLastModifiedTime(null);
toCreate.setDataChangeCreatedTime(null);

ItemDTO createdItem =
itemService.createItem(appId, Env.valueOf(env), clusterName, namespaceName, toCreate);
return OpenApiBeanUtils.transformFromItemDTO(createdItem);
return createdItem == null ? null : OpenApiModelConverters.fromItemDTO(createdItem);
}

@Override
public void updateItem(String appId, String env, String clusterName, String namespaceName,
OpenItemDTO itemDTO) {
OpenItemDTO itemDTO, String operator) {
ItemDTO toUpdateItem =
itemService.loadItem(Env.valueOf(env), appId, clusterName, namespaceName, itemDTO.getKey());
if (toUpdateItem == null) {
throw NotFoundException.itemNotFound(appId, clusterName, namespaceName, itemDTO.getKey());
}
// protect. only value,type,comment,lastModifiedBy can be modified
toUpdateItem.setComment(itemDTO.getComment());
toUpdateItem.setType(itemDTO.getType());
toUpdateItem.setValue(itemDTO.getValue());
toUpdateItem.setDataChangeLastModifiedBy(itemDTO.getDataChangeLastModifiedBy());
toUpdateItem.setDataChangeLastModifiedBy(operator);

itemService.updateItem(appId, Env.valueOf(env), clusterName, namespaceName, toUpdateItem);
}

@Override
public void createOrUpdateItem(String appId, String env, String clusterName, String namespaceName,
OpenItemDTO itemDTO) {
try {
this.updateItem(appId, env, clusterName, namespaceName, itemDTO);
} catch (Throwable ex) {
if (ex instanceof HttpStatusCodeException) {
// check createIfNotExists
if (((HttpStatusCodeException) ex).getStatusCode().equals(HttpStatus.NOT_FOUND)) {
this.createItem(appId, env, clusterName, namespaceName, itemDTO);
return;
OpenItemDTO itemDTO, String operator) {
ItemDTO existing =
itemService.loadItem(Env.valueOf(env), appId, clusterName, namespaceName, itemDTO.getKey());
if (existing == null) {
try {
this.createItem(appId, env, clusterName, namespaceName, itemDTO, operator);
} catch (RuntimeException ex) {
if (!isItemAlreadyExists(ex, itemDTO.getKey())) {
throw ex;
}
this.updateItem(appId, env, clusterName, namespaceName, itemDTO, operator);
}
throw ex;
return;
}
this.updateItem(appId, env, clusterName, namespaceName, itemDTO, operator);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

@Override
public void removeItem(String appId, String env, String clusterName, String namespaceName,
String key, String operator) {
ItemDTO toDeleteItem =
this.itemService.loadItem(Env.valueOf(env), appId, clusterName, namespaceName, key);
if (toDeleteItem == null) {
throw NotFoundException.itemNotFound(appId, clusterName, namespaceName, key);
}
this.itemService.deleteItem(Env.valueOf(env), toDeleteItem.getId(), operator);
}

@Override
public OpenPageDTO<OpenItemDTO> findItemsByNamespace(String appId, String env, String clusterName,
public OpenItemPageDTO findItemsByNamespace(String appId, String env, String clusterName,
String namespaceName, int page, int size) {
PageDTO<OpenItemDTO> commonOpenItemDTOPage = this.itemService.findItemsByNamespace(appId,
Env.valueOf(env), clusterName, namespaceName, page, size);
PageDTO<com.ctrip.framework.apollo.openapi.dto.OpenItemDTO> items = this.itemService
.findItemsByNamespace(appId, Env.valueOf(env), clusterName, namespaceName, page, size);
return OpenApiModelConverters.fromLegacyOpenItemPageDTO(items);
}

return new OpenPageDTO<>(commonOpenItemDTOPage.getPage(), commonOpenItemDTOPage.getSize(),
commonOpenItemDTOPage.getTotal(), commonOpenItemDTOPage.getContent());
@Override
public List<OpenItemDTO> findBranchItems(String appId, String env, String branchName,
String namespaceName) {
return OpenApiModelConverters
.fromItemDTOs(itemService.findItems(appId, Env.valueOf(env), branchName, namespaceName));
}

@Override
public void batchUpdateItemsByText(String appId, String env, String clusterName,
String namespaceName, OpenNamespaceTextModel model, String operator) {
NamespaceTextModel namespaceTextModel = OpenApiModelConverters.toNamespaceTextModel(model);
namespaceTextModel.setAppId(appId);
namespaceTextModel.setEnv(env);
namespaceTextModel.setClusterName(clusterName);
namespaceTextModel.setNamespaceName(namespaceName);
namespaceTextModel.setOperator(operator);
itemService.updateConfigItemByText(namespaceTextModel, operator);
}

@Override
public List<OpenItemDiffDTO> compareItems(String appId, String env, String clusterName,
String namespaceName, OpenNamespaceSyncDTO model) {
List<NamespaceIdentifier> syncToNamespaces =
OpenApiModelConverters.toNamespaceIdentifiers(model.getSyncToNamespaces());
List<ItemDTO> syncItems = OpenApiModelConverters.toItemDTOs(model.getSyncItems());
List<ItemDiffs> itemDiffs = itemService.compare(syncToNamespaces, syncItems);
return OpenApiModelConverters.fromItemDiffs(itemDiffs);
}

@Override
public void syncItems(String appId, String env, String clusterName, String namespaceName,
OpenNamespaceSyncDTO model, String operator) {
itemService.syncItems(
OpenApiModelConverters.toNamespaceIdentifiers(model.getSyncToNamespaces()),
OpenApiModelConverters.toItemDTOs(model.getSyncItems()), operator);
}

@Override
public void revertItems(String appId, String env, String clusterName, String namespaceName,
String operator) {
itemService.revokeItem(appId, Env.valueOf(env), clusterName, namespaceName, operator);
}

private boolean isItemAlreadyExists(RuntimeException ex, String key) {
String expectedMessage = BadRequestException.itemAlreadyExists(key).getMessage();
if (ex instanceof BadRequestException) {
return expectedMessage.equals(ex.getMessage());
}
if (ex instanceof HttpStatusCodeException statusException
&& statusException.getStatusCode() == HttpStatus.BAD_REQUEST) {
return statusException.getResponseBodyAsString().contains(expectedMessage);
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleDTO;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO;
import com.ctrip.framework.apollo.common.dto.InstanceDTO;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceLockDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.entity.App;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
Expand All @@ -35,6 +37,8 @@
import com.ctrip.framework.apollo.openapi.model.OpenGrayReleaseRuleItemDTO;
import com.ctrip.framework.apollo.openapi.model.OpenInstanceDTO;
import com.ctrip.framework.apollo.openapi.model.OpenItemDTO;
import com.ctrip.framework.apollo.openapi.model.OpenItemDiffDTO;
import com.ctrip.framework.apollo.openapi.model.OpenItemPageDTO;
import com.ctrip.framework.apollo.openapi.model.OpenNamespaceDTO;
import com.ctrip.framework.apollo.openapi.model.OpenNamespaceIdentifier;
import com.ctrip.framework.apollo.openapi.model.OpenNamespaceLockDTO;
Expand All @@ -45,6 +49,7 @@
import com.ctrip.framework.apollo.portal.entity.bo.NamespaceBO;
import com.ctrip.framework.apollo.portal.entity.model.NamespaceTextModel;
import com.ctrip.framework.apollo.portal.entity.vo.EnvClusterInfo;
import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier;
import com.ctrip.framework.apollo.portal.entity.vo.Organization;
import com.google.common.base.Preconditions;
Expand Down Expand Up @@ -101,6 +106,62 @@ public static List<OpenItemDTO> fromItemDTOs(List<ItemDTO> items) {
}
return items.stream().map(OpenApiModelConverters::fromItemDTO).collect(Collectors.toList());
}

public static OpenItemDTO fromLegacyOpenItemDTO(
com.ctrip.framework.apollo.openapi.dto.OpenItemDTO item) {
Preconditions.checkArgument(item != null);
return BeanUtils.transform(OpenItemDTO.class, item);
}

public static List<OpenItemDTO> fromLegacyOpenItemDTOs(
List<com.ctrip.framework.apollo.openapi.dto.OpenItemDTO> items) {
if (CollectionUtils.isEmpty(items)) {
return Collections.emptyList();
}
return items.stream().map(OpenApiModelConverters::fromLegacyOpenItemDTO)
.collect(Collectors.toList());
}

public static OpenItemPageDTO fromLegacyOpenItemPageDTO(
PageDTO<com.ctrip.framework.apollo.openapi.dto.OpenItemDTO> page) {
Preconditions.checkArgument(page != null);
OpenItemPageDTO result = new OpenItemPageDTO();
result.setPage(page.getPage());
result.setSize(page.getSize());
result.setTotal(page.getTotal());
result.setContent(fromLegacyOpenItemDTOs(page.getContent()));
return result;
}

public static OpenItemDiffDTO fromItemDiffs(ItemDiffs itemDiffs) {
Preconditions.checkArgument(itemDiffs != null);
OpenItemDiffDTO result = new OpenItemDiffDTO();
result.setCode(0);
result.setMessage(itemDiffs.getExtInfo());
if (itemDiffs.getNamespace() != null) {
result.setNamespace(fromNamespaceIdentifier(itemDiffs.getNamespace()));
}

ItemChangeSets diffs = itemDiffs.getDiffs();
if (diffs == null) {
result.setCreateItems(Collections.emptyList());
result.setUpdateItems(Collections.emptyList());
result.setDeleteItems(Collections.emptyList());
return result;
}
result.setCreateItems(fromItemDTOs(diffs.getCreateItems()));
result.setUpdateItems(fromItemDTOs(diffs.getUpdateItems()));
result.setDeleteItems(fromItemDTOs(diffs.getDeleteItems()));
return result;
}

public static List<OpenItemDiffDTO> fromItemDiffs(List<ItemDiffs> itemDiffs) {
if (CollectionUtils.isEmpty(itemDiffs)) {
return Collections.emptyList();
}
return itemDiffs.stream().map(OpenApiModelConverters::fromItemDiffs)
.collect(Collectors.toList());
}
// endregion

// region App/AppNamespace conversions
Expand Down
Loading
Loading