# Awards
# Stargazers over time
[](https://star-history.com/#apolloconfig/apollo&Date)
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Reporting a Vulnerability
If you have apprehensions regarding Apollo's security or you discover vulnerability or potential threat, don’t hesitate to get in touch with us by dropping a mail at apollo-config@googlegroups.com.
In the mail, specify the description of the issue or potential threat. You are also urged to recommend the way to reproduce and replicate the issue. The Apollo community will get back to you after assessing and analysing the findings.
PLEASE PAY ATTENTION to report the security issue on the security email before disclosing it on public domain.
================================================
FILE: apollo-adminservice/pom.xml
================================================
com.ctrip.framework.apolloapollo${revision}../pom.xml4.0.0apollo-adminserviceApollo AdminService${project.artifactId}com.ctrip.framework.apolloapollo-bizcom.ctrip.framework.apolloapollo-audit-spring-boot-starterorg.springframework.cloudspring-cloud-starter-netflix-eureka-servertestspring-cloud-starter-netflix-archaiusorg.springframework.cloudspring-cloud-starter-netflix-ribbonorg.springframework.cloudribbon-eurekacom.netflix.ribbonaws-java-sdk-corecom.amazonawsaws-java-sdk-ec2com.amazonawsaws-java-sdk-autoscalingcom.amazonawsaws-java-sdk-stscom.amazonawsaws-java-sdk-route53com.amazonawscom.sun.jersey.contribsjersey-apache-client4testjakarta.xml.bindjakarta.xml.bind-apiorg.glassfish.jaxbjaxb-runtimejakarta.activationjakarta.activation-apiorg.javassistjavassistorg.springframework.bootspring-boot-maven-pluginmaven-assembly-pluginpackagesingle${project.artifactId}-${project.version}-${package.environment}falsesrc/assembly/assembly-descriptor.xmlcom.spotifydocker-maven-plugin1.2.2apolloconfig/${project.artifactId}${project.version}latest${project.basedir}/src/main/dockerdocker-hub${project.version}/${project.build.directory}*.zipnacos-discoverycom.alibaba.bootnacos-discovery-spring-boot-startercom.alibabafastjson
================================================
FILE: apollo-adminservice/src/assembly/assembly-descriptor.xml
================================================
apollo-assemblyzipfalsesrc/main/scriptsscripts*.sh0755unixtarget/classes/apollo-adminservice.confunixtarget/classes/configapplication-github.propertiesapplication.propertiestarget/${project.artifactId}-*.jar0755
================================================
FILE: apollo-adminservice/src/main/docker/Dockerfile
================================================
#
# Copyright 2024 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.
#
# Dockerfile for apollo-adminservice
# 1. ./scripts/build.sh
# 2. Build with: mvn docker:build -pl apollo-adminservice
# 3. Run with: docker run -p 8090:8090 -e SPRING_DATASOURCE_URL="jdbc:mysql://fill-in-the-correct-server:3306/ApolloConfigDB?characterEncoding=utf8" -e SPRING_DATASOURCE_USERNAME=FillInCorrectUser -e SPRING_DATASOURCE_PASSWORD=FillInCorrectPassword -d -v /tmp/logs:/opt/logs --name apollo-adminservice apolloconfig/apollo-adminservice
FROM alpine:3.15.5
ARG VERSION
ENV VERSION $VERSION
COPY apollo-adminservice-${VERSION}-github.zip /apollo-adminservice/apollo-adminservice-${VERSION}-github.zip
RUN unzip /apollo-adminservice/apollo-adminservice-${VERSION}-github.zip -d /apollo-adminservice \
&& rm -rf /apollo-adminservice/apollo-adminservice-${VERSION}-github.zip \
&& chmod +x /apollo-adminservice/scripts/startup.sh
FROM eclipse-temurin:17-jre-jammy
LABEL maintainer="g632104866@gmail.com;finchcn@gmail.com;ameizi"
ENV APOLLO_RUN_MODE "Docker"
ENV SERVER_PORT 8090
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends procps curl bash tzdata \
&& rm -rf /var/lib/apt/lists/* \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
COPY --from=0 /apollo-adminservice /apollo-adminservice
EXPOSE $SERVER_PORT
CMD ["/apollo-adminservice/scripts/startup.sh"]
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceApplication.java
================================================
/*
* 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.adminservice;
import com.ctrip.framework.apollo.biz.ApolloBizConfig;
import com.ctrip.framework.apollo.common.ApolloCommonConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableAspectJAutoProxy
@Configuration
@PropertySource(value = {"classpath:adminservice.properties"})
@EnableAutoConfiguration(
exclude = {UserDetailsServiceAutoConfiguration.class, SessionAutoConfiguration.class})
@EnableTransactionManagement
@ComponentScan(basePackageClasses = {ApolloCommonConfig.class, ApolloBizConfig.class,
AdminServiceApplication.class})
public class AdminServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AdminServiceApplication.class, args);
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceAssemblyConfiguration.java
================================================
/*
* 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.adminservice;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Profile("assembly")
@Configuration
public class AdminServiceAssemblyConfiguration {
@Bean
@Order(101)
public SecurityFilterChain adminServiceAssemblySecurityFilterChain(HttpSecurity http)
throws Exception {
http.csrf(csrf -> csrf.disable());
http.httpBasic(Customizer.withDefaults());
return http.build();
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceAutoConfiguration.java
================================================
/*
* 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.adminservice;
import com.ctrip.framework.apollo.adminservice.filter.AdminServiceAuthenticationFilter;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AdminServiceAutoConfiguration {
private final BizConfig bizConfig;
public AdminServiceAutoConfiguration(final BizConfig bizConfig) {
this.bizConfig = bizConfig;
}
@Bean
public FilterRegistrationBean adminServiceAuthenticationFilter() {
FilterRegistrationBean filterRegistrationBean =
new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new AdminServiceAuthenticationFilter(bizConfig));
filterRegistrationBean.addUrlPatterns("/apollo/audit/*");
filterRegistrationBean.addUrlPatterns("/apps/*");
filterRegistrationBean.addUrlPatterns("/appnamespaces/*");
filterRegistrationBean.addUrlPatterns("/instances/*");
filterRegistrationBean.addUrlPatterns("/items/*");
filterRegistrationBean.addUrlPatterns("/items-search/*");
filterRegistrationBean.addUrlPatterns("/namespaces/*");
filterRegistrationBean.addUrlPatterns("/releases/*");
filterRegistrationBean.addUrlPatterns("/server/*");
return filterRegistrationBean;
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceHealthIndicator.java
================================================
/*
* 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.adminservice;
import com.ctrip.framework.apollo.biz.service.AppService;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Component;
@Component
public class AdminServiceHealthIndicator implements HealthIndicator {
private final AppService appService;
public AdminServiceHealthIndicator(final AppService appService) {
this.appService = appService;
}
@Override
public Health health() {
check();
return Health.up().build();
}
private void check() {
PageRequest pageable = PageRequest.of(0, 1);
appService.findAll(pageable);
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/ServletInitializer.java
================================================
/*
* 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.adminservice;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* Entry point for traditional web app
*
* @author Jason Song(song_s@ctrip.com)
*/
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(AdminServiceApplication.class);
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/aop/NamespaceAcquireLockAspect.java
================================================
/*
* 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.adminservice.aop;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.NamespaceLock;
import com.ctrip.framework.apollo.biz.service.ItemService;
import com.ctrip.framework.apollo.biz.service.NamespaceLockService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.ServiceException;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Component;
/**
* 一个namespace在一次发布中只能允许一个人修改配置
* 通过数据库lock表来实现
*/
@Aspect
@Component
public class NamespaceAcquireLockAspect {
private static final Logger logger = LoggerFactory.getLogger(NamespaceAcquireLockAspect.class);
private final NamespaceLockService namespaceLockService;
private final NamespaceService namespaceService;
private final ItemService itemService;
private final BizConfig bizConfig;
public NamespaceAcquireLockAspect(final NamespaceLockService namespaceLockService,
final NamespaceService namespaceService, final ItemService itemService,
final BizConfig bizConfig) {
this.namespaceLockService = namespaceLockService;
this.namespaceService = namespaceService;
this.itemService = itemService;
this.bizConfig = bizConfig;
}
// create item
@Before(
value = "@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, item, ..)",
argNames = "appId,clusterName,namespaceName,item")
public void requireLockAdvice(String appId, String clusterName, String namespaceName,
ItemDTO item) {
acquireLock(appId, clusterName, namespaceName, item.getDataChangeLastModifiedBy());
}
// update item
@Before(
value = "@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, itemId, item, ..)",
argNames = "appId,clusterName,namespaceName,itemId,item")
public void requireLockAdvice(String appId, String clusterName, String namespaceName, long itemId,
ItemDTO item) {
acquireLock(appId, clusterName, namespaceName, item.getDataChangeLastModifiedBy());
}
// update by change set
@Before(
value = "@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, changeSet, ..)",
argNames = "appId,clusterName,namespaceName,changeSet")
public void requireLockAdvice(String appId, String clusterName, String namespaceName,
ItemChangeSets changeSet) {
acquireLock(appId, clusterName, namespaceName, changeSet.getDataChangeLastModifiedBy());
}
// delete item
@Before(value = "@annotation(PreAcquireNamespaceLock) && args(itemId, operator, ..)",
argNames = "itemId,operator")
public void requireLockAdvice(long itemId, String operator) {
Item item = itemService.findOne(itemId);
if (item == null) {
throw BadRequestException.itemNotExists(itemId);
}
acquireLock(item.getNamespaceId(), operator);
}
void acquireLock(String appId, String clusterName, String namespaceName, String currentUser) {
if (bizConfig.isNamespaceLockSwitchOff()) {
return;
}
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
acquireLock(namespace, currentUser);
}
void acquireLock(long namespaceId, String currentUser) {
if (bizConfig.isNamespaceLockSwitchOff()) {
return;
}
Namespace namespace = namespaceService.findOne(namespaceId);
acquireLock(namespace, currentUser);
}
private void acquireLock(Namespace namespace, String currentUser) {
if (namespace == null) {
throw BadRequestException.namespaceNotExists();
}
long namespaceId = namespace.getId();
NamespaceLock namespaceLock = namespaceLockService.findLock(namespaceId);
if (namespaceLock == null) {
try {
tryLock(namespaceId, currentUser);
// lock success
} catch (DataIntegrityViolationException e) {
// lock fail
namespaceLock = namespaceLockService.findLock(namespaceId);
checkLock(namespace, namespaceLock, currentUser);
} catch (Exception e) {
logger.error("try lock error", e);
throw e;
}
} else {
// check lock owner is current user
checkLock(namespace, namespaceLock, currentUser);
}
}
private void tryLock(long namespaceId, String user) {
NamespaceLock lock = new NamespaceLock();
lock.setNamespaceId(namespaceId);
lock.setDataChangeCreatedBy(user);
lock.setDataChangeLastModifiedBy(user);
namespaceLockService.tryLock(lock);
}
private void checkLock(Namespace namespace, NamespaceLock namespaceLock, String currentUser) {
if (namespaceLock == null) {
throw new ServiceException(
String.format("Check lock for %s failed, please retry.", namespace.getNamespaceName()));
}
String lockOwner = namespaceLock.getDataChangeCreatedBy();
if (!lockOwner.equals(currentUser)) {
throw new BadRequestException(
"namespace:" + namespace.getNamespaceName() + " is modified by " + lockOwner);
}
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/aop/NamespaceUnlockAspect.java
================================================
/*
* 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.adminservice.aop;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.service.ItemService;
import com.ctrip.framework.apollo.biz.service.NamespaceLockService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.common.constants.GsonType;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* unlock namespace if is redo operation.
* --------------------------------------------
* For example: If namespace has a item K1 = v1
* --------------------------------------------
* First operate: change k1 = v2 (lock namespace)
* Second operate: change k1 = v1 (unlock namespace)
*/
@Aspect
@Component
public class NamespaceUnlockAspect {
private static final Gson GSON = new Gson();
private final NamespaceLockService namespaceLockService;
private final NamespaceService namespaceService;
private final ItemService itemService;
private final ReleaseService releaseService;
private final BizConfig bizConfig;
public NamespaceUnlockAspect(final NamespaceLockService namespaceLockService,
final NamespaceService namespaceService, final ItemService itemService,
final ReleaseService releaseService, final BizConfig bizConfig) {
this.namespaceLockService = namespaceLockService;
this.namespaceService = namespaceService;
this.itemService = itemService;
this.releaseService = releaseService;
this.bizConfig = bizConfig;
}
// create item
@After(
value = "@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, item, ..)",
argNames = "appId,clusterName,namespaceName,item")
public void requireLockAdvice(String appId, String clusterName, String namespaceName,
ItemDTO item) {
tryUnlock(namespaceService.findOne(appId, clusterName, namespaceName));
}
// update item
@After(
value = "@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, itemId, item, ..)",
argNames = "appId,clusterName,namespaceName,itemId,item")
public void requireLockAdvice(String appId, String clusterName, String namespaceName, long itemId,
ItemDTO item) {
tryUnlock(namespaceService.findOne(appId, clusterName, namespaceName));
}
// update by change set
@After(
value = "@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, changeSet, ..)",
argNames = "appId,clusterName,namespaceName,changeSet")
public void requireLockAdvice(String appId, String clusterName, String namespaceName,
ItemChangeSets changeSet) {
tryUnlock(namespaceService.findOne(appId, clusterName, namespaceName));
}
// delete item
@After(value = "@annotation(PreAcquireNamespaceLock) && args(itemId, operator, ..)",
argNames = "itemId,operator")
public void requireLockAdvice(long itemId, String operator) {
Item item = itemService.findOne(itemId);
if (item == null) {
throw BadRequestException.itemNotExists(itemId);
}
tryUnlock(namespaceService.findOne(item.getNamespaceId()));
}
private void tryUnlock(Namespace namespace) {
if (bizConfig.isNamespaceLockSwitchOff()) {
return;
}
if (!isModified(namespace)) {
namespaceLockService.unlock(namespace.getId());
}
}
boolean isModified(Namespace namespace) {
Release release = releaseService.findLatestActiveRelease(namespace);
List items = itemService.findItemsWithoutOrdered(namespace.getId());
if (release == null) {
return hasNormalItems(items);
}
Map releasedConfiguration =
GSON.fromJson(release.getConfigurations(), GsonType.CONFIG);
Map configurationFromItems = generateConfigurationFromItems(namespace, items);
MapDifference difference =
Maps.difference(releasedConfiguration, configurationFromItems);
return !difference.areEqual();
}
private boolean hasNormalItems(List items) {
for (Item item : items) {
if (!StringUtils.isEmpty(item.getKey())) {
return true;
}
}
return false;
}
private Map generateConfigurationFromItems(Namespace namespace,
List namespaceItems) {
Map configurationFromItems = Maps.newHashMap();
Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
// parent namespace
if (parentNamespace == null) {
generateMapFromItems(namespaceItems, configurationFromItems);
} else {// child namespace
Release parentRelease = releaseService.findLatestActiveRelease(parentNamespace);
if (parentRelease != null) {
configurationFromItems = GSON.fromJson(parentRelease.getConfigurations(), GsonType.CONFIG);
}
generateMapFromItems(namespaceItems, configurationFromItems);
}
return configurationFromItems;
}
private Map generateMapFromItems(List items,
Map configurationFromItems) {
for (Item item : items) {
String key = item.getKey();
if (StringUtils.isBlank(key)) {
continue;
}
configurationFromItems.put(key, item.getValue());
}
return configurationFromItems;
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/aop/PreAcquireNamespaceLock.java
================================================
/*
* 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.adminservice.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标识方法需要获取到namespace的lock才能执行
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAcquireNamespaceLock {
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/AccessKeyController.java
================================================
/*
* 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.adminservice.controller;
import static com.ctrip.framework.apollo.common.constants.AccessKeyMode.FILTER;
import com.ctrip.framework.apollo.biz.entity.AccessKey;
import com.ctrip.framework.apollo.biz.service.AccessKeyService;
import com.ctrip.framework.apollo.common.dto.AccessKeyDTO;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author nisiyong
*/
@RestController
public class AccessKeyController {
private final AccessKeyService accessKeyService;
public AccessKeyController(AccessKeyService accessKeyService) {
this.accessKeyService = accessKeyService;
}
@PostMapping(value = "/apps/{appId}/accesskeys")
public AccessKeyDTO create(@PathVariable String appId, @RequestBody AccessKeyDTO dto) {
AccessKey entity = BeanUtils.transform(AccessKey.class, dto);
entity = accessKeyService.create(appId, entity);
return BeanUtils.transform(AccessKeyDTO.class, entity);
}
@GetMapping(value = "/apps/{appId}/accesskeys")
public List findByAppId(@PathVariable String appId) {
List accessKeyList = accessKeyService.findByAppId(appId);
return BeanUtils.batchTransform(AccessKeyDTO.class, accessKeyList);
}
@DeleteMapping(value = "/apps/{appId}/accesskeys/{id}")
public void delete(@PathVariable String appId, @PathVariable long id, String operator) {
accessKeyService.delete(appId, id, operator);
}
@PutMapping(value = "/apps/{appId}/accesskeys/{id}/enable")
public void enable(@PathVariable String appId, @PathVariable long id,
@RequestParam(required = false, defaultValue = "" + FILTER) int mode, String operator) {
AccessKey entity = new AccessKey();
entity.setId(id);
entity.setMode(mode);
entity.setEnabled(true);
entity.setDataChangeLastModifiedBy(operator);
accessKeyService.update(appId, entity);
}
@PutMapping(value = "/apps/{appId}/accesskeys/{id}/disable")
public void disable(@PathVariable String appId, @PathVariable long id, String operator) {
AccessKey entity = new AccessKey();
entity.setId(id);
entity.setMode(FILTER);
entity.setEnabled(false);
entity.setDataChangeLastModifiedBy(operator);
accessKeyService.update(appId, entity);
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/AppController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.biz.service.AdminService;
import com.ctrip.framework.apollo.biz.service.AppService;
import com.ctrip.framework.apollo.common.dto.AppDTO;
import com.ctrip.framework.apollo.common.entity.App;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Objects;
@RestController
public class AppController {
private final AppService appService;
private final AdminService adminService;
public AppController(final AppService appService, final AdminService adminService) {
this.appService = appService;
this.adminService = adminService;
}
@PostMapping("/apps")
public AppDTO create(@Valid @RequestBody AppDTO dto) {
App entity = BeanUtils.transform(App.class, dto);
App managedEntity = appService.findOne(entity.getAppId());
if (managedEntity != null) {
throw BadRequestException.appAlreadyExists(entity.getAppId());
}
entity = adminService.createNewApp(entity);
return BeanUtils.transform(AppDTO.class, entity);
}
@DeleteMapping("/apps/{appId:.+}")
public void delete(@PathVariable("appId") String appId, @RequestParam String operator) {
App entity = appService.findOne(appId);
if (entity == null) {
throw NotFoundException.appNotFound(appId);
}
adminService.deleteApp(entity, operator);
}
@PutMapping("/apps/{appId:.+}")
public void update(@PathVariable String appId, @RequestBody App app) {
if (!Objects.equals(appId, app.getAppId())) {
throw new BadRequestException("The App Id of path variable and request body is different");
}
appService.update(app);
}
@GetMapping("/apps")
public List find(@RequestParam(value = "name", required = false) String name,
Pageable pageable) {
List app;
if (StringUtils.isBlank(name)) {
app = appService.findAll(pageable);
} else {
app = appService.findByName(name);
}
return BeanUtils.batchTransform(AppDTO.class, app);
}
@GetMapping("/apps/{appId:.+}")
public AppDTO get(@PathVariable("appId") String appId) {
App app = appService.findOne(appId);
if (app == null) {
throw NotFoundException.appNotFound(appId);
}
return BeanUtils.transform(AppDTO.class, app);
}
@GetMapping("/apps/{appId}/unique")
public boolean isAppIdUnique(@PathVariable("appId") String appId) {
return appService.isAppIdUnique(appId);
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/AppNamespaceController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.service.AppNamespaceService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.common.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class AppNamespaceController {
private final AppNamespaceService appNamespaceService;
private final NamespaceService namespaceService;
public AppNamespaceController(final AppNamespaceService appNamespaceService,
final NamespaceService namespaceService) {
this.appNamespaceService = appNamespaceService;
this.namespaceService = namespaceService;
}
@PostMapping("/apps/{appId}/appnamespaces")
public AppNamespaceDTO create(@RequestBody AppNamespaceDTO appNamespace,
@RequestParam(defaultValue = "false") boolean silentCreation) {
AppNamespace entity = BeanUtils.transform(AppNamespace.class, appNamespace);
AppNamespace managedEntity = appNamespaceService.findOne(entity.getAppId(), entity.getName());
if (managedEntity == null) {
if (StringUtils.isEmpty(entity.getFormat())) {
entity.setFormat(ConfigFileFormat.Properties.getValue());
}
entity = appNamespaceService.createAppNamespace(entity);
} else if (silentCreation) {
appNamespaceService.createNamespaceForAppNamespaceInAllCluster(appNamespace.getAppId(),
appNamespace.getName(), appNamespace.getDataChangeCreatedBy());
entity = managedEntity;
} else {
throw BadRequestException.appNamespaceAlreadyExists(entity.getAppId(), entity.getName());
}
return BeanUtils.transform(AppNamespaceDTO.class, entity);
}
@DeleteMapping("/apps/{appId}/appnamespaces/{namespaceName:.+}")
public void delete(@PathVariable("appId") String appId,
@PathVariable("namespaceName") String namespaceName, @RequestParam String operator) {
AppNamespace entity = appNamespaceService.findOne(appId, namespaceName);
if (entity == null) {
throw BadRequestException.appNamespaceNotExists(appId, namespaceName);
}
appNamespaceService.deleteAppNamespace(entity, operator);
}
@GetMapping("/appnamespaces/{publicNamespaceName}/namespaces")
public List findPublicAppNamespaceAllNamespaces(
@PathVariable String publicNamespaceName, Pageable pageable) {
List namespaces =
namespaceService.findPublicAppNamespaceAllNamespaces(publicNamespaceName, pageable);
return BeanUtils.batchTransform(NamespaceDTO.class, namespaces);
}
@GetMapping("/appnamespaces/{publicNamespaceName}/associated-namespaces/count")
public int countPublicAppNamespaceAssociatedNamespaces(@PathVariable String publicNamespaceName) {
return namespaceService.countPublicAppNamespaceAssociatedNamespaces(publicNamespaceName);
}
@GetMapping("/apps/{appId}/appnamespaces")
public List getAppNamespaces(@PathVariable("appId") String appId) {
List appNamespaces = appNamespaceService.findByAppId(appId);
return BeanUtils.batchTransform(AppNamespaceDTO.class, appNamespaces);
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ClusterController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Cluster;
import com.ctrip.framework.apollo.biz.service.ClusterService;
import com.ctrip.framework.apollo.common.dto.ClusterDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.ConfigConsts;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
import java.util.List;
@RestController
public class ClusterController {
private final ClusterService clusterService;
public ClusterController(final ClusterService clusterService) {
this.clusterService = clusterService;
}
@PostMapping("/apps/{appId}/clusters")
public ClusterDTO create(@PathVariable("appId") String appId,
@RequestParam(value = "autoCreatePrivateNamespace",
defaultValue = "true") boolean autoCreatePrivateNamespace,
@Valid @RequestBody ClusterDTO dto) {
Cluster entity = BeanUtils.transform(Cluster.class, dto);
Cluster managedEntity = clusterService.findOne(appId, entity.getName());
if (managedEntity != null) {
throw BadRequestException.clusterAlreadyExists(entity.getName());
}
if (autoCreatePrivateNamespace) {
entity = clusterService.saveWithInstanceOfAppNamespaces(entity);
} else {
entity = clusterService.saveWithoutInstanceOfAppNamespaces(entity);
}
return BeanUtils.transform(ClusterDTO.class, entity);
}
@DeleteMapping("/apps/{appId}/clusters/{clusterName:.+}")
public void delete(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName, @RequestParam String operator) {
Cluster entity = clusterService.findOne(appId, clusterName);
if (entity == null) {
throw NotFoundException.clusterNotFound(appId, clusterName);
}
if (ConfigConsts.CLUSTER_NAME_DEFAULT.equals(entity.getName())) {
throw new BadRequestException("can not delete default cluster!");
}
clusterService.delete(entity.getId(), operator);
}
@GetMapping("/apps/{appId}/clusters")
public List find(@PathVariable("appId") String appId) {
List clusters = clusterService.findParentClusters(appId);
return BeanUtils.batchTransform(ClusterDTO.class, clusters);
}
@GetMapping("/apps/{appId}/clusters/{clusterName:.+}")
public ClusterDTO get(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName) {
Cluster cluster = clusterService.findOne(appId, clusterName);
if (cluster == null) {
throw NotFoundException.clusterNotFound(appId, clusterName);
}
return BeanUtils.transform(ClusterDTO.class, cluster);
}
@GetMapping("/apps/{appId}/cluster/{clusterName}/unique")
public boolean isAppIdUnique(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName) {
return clusterService.isClusterNameUnique(appId, clusterName);
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/CommitController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.service.CommitService;
import com.ctrip.framework.apollo.common.dto.CommitDTO;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class CommitController {
private final CommitService commitService;
public CommitController(final CommitService commitService) {
this.commitService = commitService;
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/commit")
public List find(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, @RequestParam(required = false) String key,
Pageable pageable) {
List commits;
if (StringUtils.isEmpty(key)) {
commits = commitService.find(appId, clusterName, namespaceName, pageable);
} else {
commits = commitService.findByKey(appId, clusterName, namespaceName, key, pageable);
}
return BeanUtils.batchTransform(CommitDTO.class, commits);
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/IndexController.java
================================================
/*
* 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.adminservice.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(path = "/")
public class IndexController {
@GetMapping
public String index() {
return "apollo-adminservice";
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/InstanceConfigController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Instance;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.service.InstanceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.common.dto.InstanceConfigDTO;
import com.ctrip.framework.apollo.common.dto.InstanceDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RestController
@RequestMapping("/instances")
public class InstanceConfigController {
private static final Splitter RELEASES_SPLITTER =
Splitter.on(",").omitEmptyStrings().trimResults();
private final ReleaseService releaseService;
private final InstanceService instanceService;
public InstanceConfigController(final ReleaseService releaseService,
final InstanceService instanceService) {
this.releaseService = releaseService;
this.instanceService = instanceService;
}
@GetMapping("/by-release")
public PageDTO getByRelease(@RequestParam("releaseId") long releaseId,
Pageable pageable) {
Release release = releaseService.findOne(releaseId);
if (release == null) {
throw NotFoundException.releaseNotFound(releaseId);
}
Page instanceConfigsPage =
instanceService.findActiveInstanceConfigsByReleaseKey(release.getReleaseKey(), pageable);
List instanceDTOs = Collections.emptyList();
if (instanceConfigsPage.hasContent()) {
Multimap instanceConfigMap = HashMultimap.create();
Set otherReleaseKeys = Sets.newHashSet();
for (InstanceConfig instanceConfig : instanceConfigsPage.getContent()) {
instanceConfigMap.put(instanceConfig.getInstanceId(), instanceConfig);
otherReleaseKeys.add(instanceConfig.getReleaseKey());
}
Set instanceIds = instanceConfigMap.keySet();
List instances = instanceService.findInstancesByIds(instanceIds);
if (!CollectionUtils.isEmpty(instances)) {
instanceDTOs = BeanUtils.batchTransform(InstanceDTO.class, instances);
}
for (InstanceDTO instanceDTO : instanceDTOs) {
Collection configs = instanceConfigMap.get(instanceDTO.getId());
List configDTOs = configs.stream().map(instanceConfig -> {
InstanceConfigDTO instanceConfigDTO = new InstanceConfigDTO();
// to save some space
instanceConfigDTO.setRelease(null);
instanceConfigDTO.setReleaseDeliveryTime(instanceConfig.getReleaseDeliveryTime());
instanceConfigDTO
.setDataChangeLastModifiedTime(instanceConfig.getDataChangeLastModifiedTime());
return instanceConfigDTO;
}).collect(Collectors.toList());
instanceDTO.setConfigs(configDTOs);
}
}
return new PageDTO<>(instanceDTOs, pageable, instanceConfigsPage.getTotalElements());
}
@GetMapping("/by-namespace-and-releases-not-in")
public List getByReleasesNotIn(@RequestParam("appId") String appId,
@RequestParam("clusterName") String clusterName,
@RequestParam("namespaceName") String namespaceName,
@RequestParam("releaseIds") String releaseIds) {
Set releaseIdSet = RELEASES_SPLITTER.splitToList(releaseIds).stream().map(Long::parseLong)
.collect(Collectors.toSet());
List releases = releaseService.findByReleaseIds(releaseIdSet);
if (CollectionUtils.isEmpty(releases)) {
throw NotFoundException.releaseNotFound(releaseIds);
}
Set releaseKeys =
releases.stream().map(Release::getReleaseKey).collect(Collectors.toSet());
List instanceConfigs =
instanceService.findInstanceConfigsByNamespaceWithReleaseKeysNotIn(appId, clusterName,
namespaceName, releaseKeys);
Multimap instanceConfigMap = HashMultimap.create();
Set otherReleaseKeys = Sets.newHashSet();
for (InstanceConfig instanceConfig : instanceConfigs) {
instanceConfigMap.put(instanceConfig.getInstanceId(), instanceConfig);
otherReleaseKeys.add(instanceConfig.getReleaseKey());
}
List instances = instanceService.findInstancesByIds(instanceConfigMap.keySet());
if (CollectionUtils.isEmpty(instances)) {
return Collections.emptyList();
}
List instanceDTOs = BeanUtils.batchTransform(InstanceDTO.class, instances);
List otherReleases = releaseService.findByReleaseKeys(otherReleaseKeys);
Map releaseMap = Maps.newHashMap();
for (Release release : otherReleases) {
// unset configurations to save space
release.setConfigurations(null);
ReleaseDTO releaseDTO = BeanUtils.transform(ReleaseDTO.class, release);
releaseMap.put(release.getReleaseKey(), releaseDTO);
}
for (InstanceDTO instanceDTO : instanceDTOs) {
Collection configs = instanceConfigMap.get(instanceDTO.getId());
List configDTOs = configs.stream().map(instanceConfig -> {
InstanceConfigDTO instanceConfigDTO = new InstanceConfigDTO();
instanceConfigDTO.setRelease(releaseMap.get(instanceConfig.getReleaseKey()));
instanceConfigDTO.setReleaseDeliveryTime(instanceConfig.getReleaseDeliveryTime());
instanceConfigDTO
.setDataChangeLastModifiedTime(instanceConfig.getDataChangeLastModifiedTime());
return instanceConfigDTO;
}).collect(Collectors.toList());
instanceDTO.setConfigs(configDTOs);
}
return instanceDTOs;
}
@GetMapping("/by-namespace")
public PageDTO getInstancesByNamespace(@RequestParam("appId") String appId,
@RequestParam("clusterName") String clusterName,
@RequestParam("namespaceName") String namespaceName,
@RequestParam(value = "instanceAppId", required = false) String instanceAppId,
Pageable pageable) {
Page instances;
if (Strings.isNullOrEmpty(instanceAppId)) {
instances =
instanceService.findInstancesByNamespace(appId, clusterName, namespaceName, pageable);
} else {
instances = instanceService.findInstancesByNamespaceAndInstanceAppId(instanceAppId, appId,
clusterName, namespaceName, pageable);
}
List instanceDTOs =
BeanUtils.batchTransform(InstanceDTO.class, instances.getContent());
return new PageDTO<>(instanceDTOs, pageable, instances.getTotalElements());
}
@GetMapping("/by-namespace/count")
public long getInstancesCountByNamespace(@RequestParam("appId") String appId,
@RequestParam("clusterName") String clusterName,
@RequestParam("namespaceName") String namespaceName) {
Page instances = instanceService.findInstancesByNamespace(appId, clusterName,
namespaceName, PageRequest.of(0, 1));
return instances.getTotalElements();
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.adminservice.aop.PreAcquireNamespaceLock;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.service.CommitService;
import com.ctrip.framework.apollo.biz.service.ItemService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.dto.ItemInfoDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ItemController {
private final ItemService itemService;
private final NamespaceService namespaceService;
private final CommitService commitService;
private final ReleaseService releaseService;
private final BizConfig bizConfig;
public ItemController(final ItemService itemService, final NamespaceService namespaceService,
final CommitService commitService, final ReleaseService releaseService,
final BizConfig bizConfig) {
this.itemService = itemService;
this.namespaceService = namespaceService;
this.commitService = commitService;
this.releaseService = releaseService;
this.bizConfig = bizConfig;
}
@PreAcquireNamespaceLock
@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items")
public ItemDTO create(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, @RequestBody ItemDTO dto) {
Item entity = BeanUtils.transform(Item.class, dto);
Item managedEntity = itemService.findOne(appId, clusterName, namespaceName, entity.getKey());
if (managedEntity != null) {
throw BadRequestException.itemAlreadyExists(entity.getKey());
}
if (bizConfig.isItemNumLimitEnabled()) {
int itemCount = itemService.findNonEmptyItemCount(entity.getNamespaceId());
if (itemCount >= bizConfig.itemNumLimit()) {
throw new BadRequestException("The maximum number of items (" + bizConfig.itemNumLimit()
+ ") for this namespace has been reached. Current item count is " + itemCount + ".");
}
}
entity = itemService.save(entity);
dto = BeanUtils.transform(ItemDTO.class, entity);
commitService.createCommit(appId, clusterName, namespaceName,
new ConfigChangeContentBuilder().createItem(entity).build(),
dto.getDataChangeLastModifiedBy());
return dto;
}
@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/comment_items")
public ItemDTO createComment(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, @RequestBody ItemDTO dto) {
if (!StringUtils.isBlank(dto.getKey()) || !StringUtils.isBlank(dto.getValue())) {
throw new BadRequestException("Comment item's key or value should be blank.");
}
if (StringUtils.isBlank(dto.getComment())) {
throw new BadRequestException("Comment item's comment should not be blank.");
}
// check if comment existed
List allItems = itemService.findItemsWithOrdered(appId, clusterName, namespaceName);
for (Item item : allItems) {
if (StringUtils.isBlank(item.getKey()) && StringUtils.isBlank(item.getValue())
&& Objects.equals(item.getComment(), dto.getComment())) {
return BeanUtils.transform(ItemDTO.class, item);
}
}
Item entity = BeanUtils.transform(Item.class, dto);
entity = itemService.saveComment(entity);
return BeanUtils.transform(ItemDTO.class, entity);
}
@PreAcquireNamespaceLock
@PutMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/{itemId}")
public ItemDTO update(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, @PathVariable("itemId") long itemId,
@RequestBody ItemDTO itemDTO) {
Item managedEntity = itemService.findOne(itemId);
if (managedEntity == null) {
throw NotFoundException.itemNotFound(appId, clusterName, namespaceName, itemId);
}
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
// In case someone constructs an attack scenario
if (namespace == null || namespace.getId() != managedEntity.getNamespaceId()) {
throw BadRequestException.namespaceNotMatch();
}
Item entity = BeanUtils.transform(Item.class, itemDTO);
ConfigChangeContentBuilder builder = new ConfigChangeContentBuilder();
Item beforeUpdateItem = BeanUtils.transform(Item.class, managedEntity);
// protect. only value,type,comment,lastModifiedBy can be modified
managedEntity.setType(entity.getType());
managedEntity.setValue(entity.getValue());
managedEntity.setComment(entity.getComment());
managedEntity.setDataChangeLastModifiedBy(entity.getDataChangeLastModifiedBy());
entity = itemService.update(managedEntity);
builder.updateItem(beforeUpdateItem, entity);
itemDTO = BeanUtils.transform(ItemDTO.class, entity);
if (builder.hasContent()) {
commitService.createCommit(appId, clusterName, namespaceName, builder.build(),
itemDTO.getDataChangeLastModifiedBy());
}
return itemDTO;
}
@PreAcquireNamespaceLock
@DeleteMapping("/items/{itemId}")
public void delete(@PathVariable("itemId") long itemId, @RequestParam String operator) {
Item entity = itemService.findOne(itemId);
if (entity == null) {
throw NotFoundException.itemNotFound(itemId);
}
itemService.delete(entity.getId(), operator);
Namespace namespace = namespaceService.findOne(entity.getNamespaceId());
commitService.createCommit(namespace.getAppId(), namespace.getClusterName(),
namespace.getNamespaceName(), new ConfigChangeContentBuilder().deleteItem(entity).build(),
operator);
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items")
public List findItems(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName) {
return BeanUtils.batchTransform(ItemDTO.class,
itemService.findItemsWithOrdered(appId, clusterName, namespaceName));
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/deleted")
public List findDeletedItems(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName) {
// get latest release time
Release latestActiveRelease =
releaseService.findLatestActiveRelease(appId, clusterName, namespaceName);
List commits;
if (Objects.nonNull(latestActiveRelease)) {
commits = commitService.find(appId, clusterName, namespaceName,
latestActiveRelease.getDataChangeCreatedTime(), null);
} else {
commits = commitService.find(appId, clusterName, namespaceName, null);
}
if (Objects.nonNull(commits)) {
List deletedItems =
commits
.stream().map(item -> ConfigChangeContentBuilder
.convertJsonString(item.getChangeSets()).getDeleteItems())
.flatMap(Collection::stream).collect(Collectors.toList());
return BeanUtils.batchTransform(ItemDTO.class, deletedItems);
}
return Collections.emptyList();
}
@GetMapping("/items-search/key-and-value")
public PageDTO getItemInfoBySearch(
@RequestParam(value = "key", required = false) String key,
@RequestParam(value = "value", required = false) String value, Pageable limit) {
Page pageItemInfoDTO = itemService.getItemInfoBySearch(key, value, limit);
return new PageDTO<>(pageItemInfoDTO.getContent(), limit, pageItemInfoDTO.getTotalElements());
}
@GetMapping("/items/{itemId}")
public ItemDTO get(@PathVariable("itemId") long itemId) {
Item item = itemService.findOne(itemId);
if (item == null) {
throw NotFoundException.itemNotFound(itemId);
}
return BeanUtils.transform(ItemDTO.class, item);
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/{key:.+}")
public ItemDTO get(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, @PathVariable("key") String key) {
Item item = itemService.findOne(appId, clusterName, namespaceName, key);
if (item == null) {
throw NotFoundException.itemNotFound(appId, clusterName, namespaceName, key);
}
return BeanUtils.transform(ItemDTO.class, item);
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/encodedItems/{key:.+}")
public ItemDTO getByEncodedKey(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, @PathVariable("key") String key) {
return this.get(appId, clusterName, namespaceName,
new String(Base64.getUrlDecoder().decode(key.getBytes(StandardCharsets.UTF_8))));
}
@GetMapping(
value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items-with-page")
public PageDTO findItemsByNamespace(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, Pageable pageable) {
Page itemPage =
itemService.findItemsByNamespace(appId, clusterName, namespaceName, pageable);
List itemDTOS = BeanUtils.batchTransform(ItemDTO.class, itemPage.getContent());
return new PageDTO<>(itemDTOS, pageable, itemPage.getTotalElements());
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemSetController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.adminservice.aop.PreAcquireNamespaceLock;
import com.ctrip.framework.apollo.biz.service.ItemSetService;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ItemSetController {
private final ItemSetService itemSetService;
public ItemSetController(final ItemSetService itemSetService) {
this.itemSetService = itemSetService;
}
@PreAcquireNamespaceLock
@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/itemset")
public ResponseEntity create(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, @RequestBody ItemChangeSets changeSet) {
itemSetService.updateSet(appId, clusterName, namespaceName, changeSet);
return ResponseEntity.status(HttpStatus.OK).build();
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceBranchController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.message.MessageSender;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.service.NamespaceBranchService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.biz.utils.ReleaseMessageKeyGenerator;
import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.GrayReleaseRuleItemTransformer;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NamespaceBranchController {
private final MessageSender messageSender;
private final NamespaceBranchService namespaceBranchService;
private final NamespaceService namespaceService;
public NamespaceBranchController(final MessageSender messageSender,
final NamespaceBranchService namespaceBranchService,
final NamespaceService namespaceService) {
this.messageSender = messageSender;
this.namespaceBranchService = namespaceBranchService;
this.namespaceService = namespaceService;
}
@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches")
public NamespaceDTO createBranch(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, @RequestParam("operator") String operator) {
checkNamespace(appId, clusterName, namespaceName);
Namespace createdBranch =
namespaceBranchService.createBranch(appId, clusterName, namespaceName, operator);
return BeanUtils.transform(NamespaceDTO.class, createdBranch);
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules")
public GrayReleaseRuleDTO findBranchGrayRules(@PathVariable String appId,
@PathVariable String clusterName, @PathVariable String namespaceName,
@PathVariable String branchName) {
checkBranch(appId, clusterName, namespaceName, branchName);
GrayReleaseRule rules =
namespaceBranchService.findBranchGrayRules(appId, clusterName, namespaceName, branchName);
if (rules == null) {
return null;
}
GrayReleaseRuleDTO ruleDTO = new GrayReleaseRuleDTO(rules.getAppId(), rules.getClusterName(),
rules.getNamespaceName(), rules.getBranchName());
ruleDTO.setReleaseId(rules.getReleaseId());
ruleDTO.setRuleItems(GrayReleaseRuleItemTransformer.batchTransformFromJSON(rules.getRules()));
return ruleDTO;
}
@Transactional
@PutMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules")
public void updateBranchGrayRules(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, @PathVariable String branchName,
@RequestBody GrayReleaseRuleDTO newRuleDto) {
checkBranch(appId, clusterName, namespaceName, branchName);
GrayReleaseRule newRules = BeanUtils.transform(GrayReleaseRule.class, newRuleDto);
newRules
.setRules(GrayReleaseRuleItemTransformer.batchTransformToJSON(newRuleDto.getRuleItems()));
newRules.setBranchStatus(NamespaceBranchStatus.ACTIVE);
namespaceBranchService.updateBranchGrayRules(appId, clusterName, namespaceName, branchName,
newRules);
messageSender.sendMessage(
ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
Topics.APOLLO_RELEASE_TOPIC);
}
@Transactional
@DeleteMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}")
public void deleteBranch(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, @PathVariable String branchName,
@RequestParam("operator") String operator) {
checkBranch(appId, clusterName, namespaceName, branchName);
namespaceBranchService.deleteBranch(appId, clusterName, namespaceName, branchName,
NamespaceBranchStatus.DELETED, operator);
messageSender.sendMessage(
ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
Topics.APOLLO_RELEASE_TOPIC);
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches")
public NamespaceDTO loadNamespaceBranch(@PathVariable String appId,
@PathVariable String clusterName, @PathVariable String namespaceName) {
checkNamespace(appId, clusterName, namespaceName);
Namespace childNamespace = namespaceBranchService.findBranch(appId, clusterName, namespaceName);
if (childNamespace == null) {
return null;
}
return BeanUtils.transform(NamespaceDTO.class, childNamespace);
}
private void checkBranch(String appId, String clusterName, String namespaceName,
String branchName) {
// 1. check parent namespace
checkNamespace(appId, clusterName, namespaceName);
// 2. check child namespace
Namespace childNamespace = namespaceService.findOne(appId, branchName, namespaceName);
if (childNamespace == null) {
throw new BadRequestException(
"Namespace's branch not exist. AppId = %s, ClusterName = %s, NamespaceName = %s, BranchName = %s",
appId, clusterName, namespaceName, branchName);
}
}
private void checkNamespace(String appId, String clusterName, String namespaceName) {
Namespace parentNamespace = namespaceService.findOne(appId, clusterName, namespaceName);
if (parentNamespace == null) {
throw BadRequestException.namespaceNotExists(appId, clusterName, namespaceName);
}
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
@RestController
public class NamespaceController {
private final NamespaceService namespaceService;
public NamespaceController(final NamespaceService namespaceService) {
this.namespaceService = namespaceService;
}
@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces")
public NamespaceDTO create(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName, @Valid @RequestBody NamespaceDTO dto) {
Namespace entity = BeanUtils.transform(Namespace.class, dto);
Namespace managedEntity =
namespaceService.findOne(appId, clusterName, entity.getNamespaceName());
if (managedEntity != null) {
throw BadRequestException.namespaceAlreadyExists(entity.getNamespaceName());
}
entity = namespaceService.save(entity);
return BeanUtils.transform(NamespaceDTO.class, entity);
}
@DeleteMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName:.+}")
public void delete(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, @RequestParam String operator) {
Namespace entity = namespaceService.findOne(appId, clusterName, namespaceName);
if (entity == null) {
throw NotFoundException.namespaceNotFound(appId, clusterName, namespaceName);
}
namespaceService.deleteNamespace(entity, operator);
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces")
public List find(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName) {
List groups = namespaceService.findNamespaces(appId, clusterName);
return BeanUtils.batchTransform(NamespaceDTO.class, groups);
}
@GetMapping("/namespaces/{namespaceId}")
public NamespaceDTO get(@PathVariable("namespaceId") Long namespaceId) {
Namespace namespace = namespaceService.findOne(namespaceId);
if (namespace == null) {
throw NotFoundException.itemNotFound(namespaceId);
}
return BeanUtils.transform(NamespaceDTO.class, namespace);
}
/**
* the returned content's size is not fixed. so please carefully used.
*/
@GetMapping("/namespaces/find-by-item")
public PageDTO findByItem(@RequestParam String itemKey, Pageable pageable) {
Page namespacePage = namespaceService.findByItem(itemKey, pageable);
List namespaceDTOS =
BeanUtils.batchTransform(NamespaceDTO.class, namespacePage.getContent());
return new PageDTO<>(namespaceDTOS, pageable, namespacePage.getTotalElements());
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName:.+}")
public NamespaceDTO get(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName) {
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
if (namespace == null) {
throw NotFoundException.namespaceNotFound(appId, clusterName, namespaceName);
}
return BeanUtils.transform(NamespaceDTO.class, namespace);
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/associated-public-namespace")
public NamespaceDTO findPublicNamespaceForAssociatedNamespace(@PathVariable String appId,
@PathVariable String clusterName, @PathVariable String namespaceName) {
Namespace namespace =
namespaceService.findPublicNamespaceForAssociatedNamespace(clusterName, namespaceName);
if (namespace == null) {
throw new NotFoundException("public namespace not found. namespace:%s", namespaceName);
}
return BeanUtils.transform(NamespaceDTO.class, namespace);
}
/**
* cluster -> cluster has not published namespaces?
*/
@GetMapping("/apps/{appId}/namespaces/publish_info")
public Map namespacePublishInfo(@PathVariable String appId) {
return namespaceService.namespacePublishInfo(appId);
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceLockController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.NamespaceLock;
import com.ctrip.framework.apollo.biz.service.NamespaceLockService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.common.dto.NamespaceLockDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NamespaceLockController {
private final NamespaceLockService namespaceLockService;
private final NamespaceService namespaceService;
private final BizConfig bizConfig;
public NamespaceLockController(final NamespaceLockService namespaceLockService,
final NamespaceService namespaceService, final BizConfig bizConfig) {
this.namespaceLockService = namespaceLockService;
this.namespaceService = namespaceService;
this.bizConfig = bizConfig;
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/lock")
public NamespaceLockDTO getNamespaceLockOwner(@PathVariable String appId,
@PathVariable String clusterName, @PathVariable String namespaceName) {
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
if (namespace == null) {
throw BadRequestException.namespaceNotExists(appId, clusterName, namespaceName);
}
if (bizConfig.isNamespaceLockSwitchOff()) {
return null;
}
NamespaceLock lock = namespaceLockService.findLock(namespace.getId());
if (lock == null) {
return null;
}
return BeanUtils.transform(NamespaceLockDTO.class, lock);
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ReleaseController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.message.MessageSender;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.service.NamespaceBranchService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.biz.utils.ReleaseMessageKeyGenerator;
import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.google.common.base.Splitter;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@RestController
public class ReleaseController {
private static final Splitter RELEASES_SPLITTER =
Splitter.on(",").omitEmptyStrings().trimResults();
private final ReleaseService releaseService;
private final NamespaceService namespaceService;
private final MessageSender messageSender;
private final NamespaceBranchService namespaceBranchService;
public ReleaseController(final ReleaseService releaseService,
final NamespaceService namespaceService, final MessageSender messageSender,
final NamespaceBranchService namespaceBranchService) {
this.releaseService = releaseService;
this.namespaceService = namespaceService;
this.messageSender = messageSender;
this.namespaceBranchService = namespaceBranchService;
}
@GetMapping("/releases/{releaseId}")
public ReleaseDTO get(@PathVariable("releaseId") long releaseId) {
Release release = releaseService.findOne(releaseId);
if (release == null) {
throw NotFoundException.releaseNotFound(releaseId);
}
return BeanUtils.transform(ReleaseDTO.class, release);
}
@GetMapping("/releases")
public List findReleaseByIds(@RequestParam("releaseIds") String releaseIds) {
Set releaseIdSet = RELEASES_SPLITTER.splitToList(releaseIds).stream().map(Long::parseLong)
.collect(Collectors.toSet());
List releases = releaseService.findByReleaseIds(releaseIdSet);
return BeanUtils.batchTransform(ReleaseDTO.class, releases);
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/all")
public List findAllReleases(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, Pageable page) {
List releases =
releaseService.findAllReleases(appId, clusterName, namespaceName, page);
return BeanUtils.batchTransform(ReleaseDTO.class, releases);
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/active")
public List findActiveReleases(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, Pageable page) {
List releases =
releaseService.findActiveReleases(appId, clusterName, namespaceName, page);
return BeanUtils.batchTransform(ReleaseDTO.class, releases);
}
@GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/latest")
public ReleaseDTO getLatest(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName) {
Release release = releaseService.findLatestActiveRelease(appId, clusterName, namespaceName);
return BeanUtils.transform(ReleaseDTO.class, release);
}
@Transactional
@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases")
public ReleaseDTO publish(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, @RequestParam("name") String releaseName,
@RequestParam(name = "comment", required = false) String releaseComment,
@RequestParam("operator") String operator, @RequestParam(name = "isEmergencyPublish",
defaultValue = "false") boolean isEmergencyPublish) {
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
if (namespace == null) {
throw NotFoundException.namespaceNotFound(appId, clusterName, namespaceName);
}
Release release = releaseService.publish(namespace, releaseName, releaseComment, operator,
isEmergencyPublish);
// send release message
Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
String messageCluster;
if (parentNamespace != null) {
messageCluster = parentNamespace.getClusterName();
} else {
messageCluster = clusterName;
}
messageSender.sendMessage(
ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName),
Topics.APOLLO_RELEASE_TOPIC);
return BeanUtils.transform(ReleaseDTO.class, release);
}
/**
* merge branch items to master and publish master
*
* @return published result
*/
@Transactional
@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/updateAndPublish")
public ReleaseDTO updateAndPublish(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName,
@RequestParam("releaseName") String releaseName,
@RequestParam("branchName") String branchName,
@RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,
@RequestParam(name = "releaseComment", required = false) String releaseComment,
@RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish,
@RequestBody ItemChangeSets changeSets) {
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
if (namespace == null) {
throw NotFoundException.namespaceNotFound(appId, clusterName, namespaceName);
}
Release release = releaseService.mergeBranchChangeSetsAndRelease(namespace, branchName,
releaseName, releaseComment, isEmergencyPublish, changeSets);
if (deleteBranch) {
namespaceBranchService.deleteBranch(appId, clusterName, namespaceName, branchName,
NamespaceBranchStatus.MERGED, changeSets.getDataChangeLastModifiedBy());
}
messageSender.sendMessage(
ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
Topics.APOLLO_RELEASE_TOPIC);
return BeanUtils.transform(ReleaseDTO.class, release);
}
@Transactional
@PutMapping("/releases/{releaseId}/rollback")
public void rollback(@PathVariable("releaseId") long releaseId,
@RequestParam(name = "toReleaseId", defaultValue = "-1") long toReleaseId,
@RequestParam("operator") String operator) {
Release release;
if (toReleaseId > -1) {
release = releaseService.rollbackTo(releaseId, toReleaseId, operator);
} else {
release = releaseService.rollback(releaseId, operator);
}
String appId = release.getAppId();
String clusterName = release.getClusterName();
String namespaceName = release.getNamespaceName();
// send release message
messageSender.sendMessage(
ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
Topics.APOLLO_RELEASE_TOPIC);
}
@Transactional
@PostMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/gray-del-releases")
public ReleaseDTO publish(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName,
@RequestParam("operator") String operator, @RequestParam("releaseName") String releaseName,
@RequestParam(name = "comment", required = false) String releaseComment,
@RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish,
@RequestParam(name = "grayDelKeys") Set grayDelKeys) {
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
if (namespace == null) {
throw NotFoundException.namespaceNotFound(appId, clusterName, namespaceName);
}
Release release = releaseService.grayDeletionPublish(namespace, releaseName, releaseComment,
operator, isEmergencyPublish, grayDelKeys);
// send release message
Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
String messageCluster;
if (parentNamespace != null) {
messageCluster = parentNamespace.getClusterName();
} else {
messageCluster = clusterName;
}
messageSender.sendMessage(
ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName),
Topics.APOLLO_RELEASE_TOPIC);
return BeanUtils.transform(ReleaseDTO.class, release);
}
}
================================================
FILE: apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ReleaseHistoryController.java
================================================
/*
* 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.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.ReleaseHistory;
import com.ctrip.framework.apollo.biz.service.ReleaseHistoryService;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseHistoryDTO;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RestController
public class ReleaseHistoryController {
private static final Gson GSON = new Gson();
private final Type configurationTypeReference = new TypeToken