소스 검색

feat: Email notification when releasing by OpenAPI (#5324)

luke 2 달 전
부모
커밋
ba197ed36b
3개의 변경된 파일110개의 추가작업 그리고 69개의 파일을 삭제
  1. 2 0
      CHANGES.md
  2. 108 68
      apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/v1/controller/ReleaseController.java
  3. 0 1
      changes/changes-2.4.0.md

+ 2 - 0
CHANGES.md

@@ -26,6 +26,8 @@ Apollo 2.4.0
 * [Feature: add JSON formatting function in apollo-portal](https://github.com/apolloconfig/apollo/pull/5287)
 * [Fix: add missing url patterns for AdminServiceAuthenticationFilter](https://github.com/apolloconfig/apollo/pull/5291)
 * [Fix: support java.time.Instant serialization with gson](https://github.com/apolloconfig/apollo/pull/5298)
+* [Feature: support to assign users management authority by the cluster (modify, publish)](https://github.com/apolloconfig/apollo/pull/5302)
+* [Feature: notification by email when releasing by OpenApi also](https://github.com/apolloconfig/apollo/pull/5324)
 
 ------------------
 All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1)

+ 108 - 68
apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/v1/controller/ReleaseController.java

@@ -30,10 +30,12 @@ import com.ctrip.framework.apollo.openapi.dto.OpenReleaseDTO;
 import com.ctrip.framework.apollo.openapi.util.OpenApiBeanUtils;
 import com.ctrip.framework.apollo.portal.entity.model.NamespaceGrayDelReleaseModel;
 import com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel;
+import com.ctrip.framework.apollo.portal.listener.ConfigPublishEvent;
 import com.ctrip.framework.apollo.portal.service.NamespaceBranchService;
 import com.ctrip.framework.apollo.portal.service.ReleaseService;
 import com.ctrip.framework.apollo.portal.spi.UserService;
 import javax.servlet.http.HttpServletRequest;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -54,18 +56,21 @@ public class ReleaseController {
   private final NamespaceBranchService namespaceBranchService;
   private final ConsumerPermissionValidator consumerPermissionValidator;
   private final ReleaseOpenApiService releaseOpenApiService;
+  private final ApplicationEventPublisher publisher;
 
   public ReleaseController(
       final ReleaseService releaseService,
       final UserService userService,
       final NamespaceBranchService namespaceBranchService,
       final ConsumerPermissionValidator consumerPermissionValidator,
-      ReleaseOpenApiService releaseOpenApiService) {
+      ReleaseOpenApiService releaseOpenApiService,
+      ApplicationEventPublisher publisher) {
     this.releaseService = releaseService;
     this.userService = userService;
     this.namespaceBranchService = namespaceBranchService;
     this.consumerPermissionValidator = consumerPermissionValidator;
     this.releaseOpenApiService = releaseOpenApiService;
+    this.publisher = publisher;
   }
 
   @PreAuthorize(value = "@consumerPermissionValidator.hasReleaseNamespacePermission(#request, #appId, #namespaceName, #env)")
@@ -83,7 +88,19 @@ public class ReleaseController {
       throw BadRequestException.userNotExists(model.getReleasedBy());
     }
 
-    return this.releaseOpenApiService.publishNamespace(appId, env, clusterName, namespaceName, model);
+    OpenReleaseDTO releaseDTO = this.releaseOpenApiService.publishNamespace(appId, env,
+        clusterName, namespaceName, model);
+
+    ConfigPublishEvent event = ConfigPublishEvent.instance();
+    event.withAppId(appId)
+        .withCluster(clusterName)
+        .withNamespace(namespaceName)
+        .withReleaseId(releaseDTO.getId())
+        .setNormalPublishEvent(true)
+        .setEnv(Env.valueOf(env));
+    publisher.publishEvent(event);
+
+    return releaseDTO;
   }
 
   @GetMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/latest")
@@ -93,78 +110,91 @@ public class ReleaseController {
     return this.releaseOpenApiService.getLatestActiveRelease(appId, env, clusterName, namespaceName);
   }
 
-    @PreAuthorize(value = "@consumerPermissionValidator.hasReleaseNamespacePermission(#request, #appId, #namespaceName, #env)")
-    @PostMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/merge")
-    public OpenReleaseDTO merge(@PathVariable String appId, @PathVariable String env,
-                            @PathVariable String clusterName, @PathVariable String namespaceName,
-                            @PathVariable String branchName, @RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,
-                            @RequestBody NamespaceReleaseDTO model, HttpServletRequest request) {
-        RequestPrecondition.checkArguments(!StringUtils.isContainEmpty(model.getReleasedBy(), model
-                        .getReleaseTitle()),
-                "Params(releaseTitle and releasedBy) can not be empty");
-
-        if (userService.findByUserId(model.getReleasedBy()) == null) {
-            throw BadRequestException.userNotExists(model.getReleasedBy());
-        }
-
-        ReleaseDTO mergedRelease = namespaceBranchService.merge(appId, Env.valueOf(env.toUpperCase()), clusterName, namespaceName, branchName,
-                model.getReleaseTitle(), model.getReleaseComment(),
-                model.isEmergencyPublish(), deleteBranch, model.getReleasedBy());
-
-        return OpenApiBeanUtils.transformFromReleaseDTO(mergedRelease);
+  @PreAuthorize(value = "@consumerPermissionValidator.hasReleaseNamespacePermission(#request, #appId, #namespaceName, #env)")
+  @PostMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/merge")
+  public OpenReleaseDTO merge(@PathVariable String appId, @PathVariable String env,
+      @PathVariable String clusterName, @PathVariable String namespaceName,
+      @PathVariable String branchName,
+      @RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,
+      @RequestBody NamespaceReleaseDTO model, HttpServletRequest request) {
+    RequestPrecondition.checkArguments(
+        !StringUtils.isContainEmpty(model.getReleasedBy(), model.getReleaseTitle()),
+        "Params(releaseTitle and releasedBy) can not be empty");
+
+    if (userService.findByUserId(model.getReleasedBy()) == null) {
+      throw BadRequestException.userNotExists(model.getReleasedBy());
     }
 
-    @PreAuthorize(value = "@consumerPermissionValidator.hasReleaseNamespacePermission(#request, #appId, #namespaceName, #env)")
-    @PostMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/releases")
-    public OpenReleaseDTO createGrayRelease(@PathVariable String appId,
-                                        @PathVariable String env, @PathVariable String clusterName,
-                                        @PathVariable String namespaceName, @PathVariable String branchName,
-                                        @RequestBody NamespaceReleaseDTO model,
-                                        HttpServletRequest request) {
-        RequestPrecondition.checkArguments(!StringUtils.isContainEmpty(model.getReleasedBy(), model
-                        .getReleaseTitle()),
-                "Params(releaseTitle and releasedBy) can not be empty");
-
-        if (userService.findByUserId(model.getReleasedBy()) == null) {
-            throw BadRequestException.userNotExists(model.getReleasedBy());
-        }
-
-        NamespaceReleaseModel releaseModel = BeanUtils.transform(NamespaceReleaseModel.class, model);
-
-        releaseModel.setAppId(appId);
-        releaseModel.setEnv(Env.valueOf(env).toString());
-        releaseModel.setClusterName(branchName);
-        releaseModel.setNamespaceName(namespaceName);
-
-        return OpenApiBeanUtils.transformFromReleaseDTO(releaseService.publish(releaseModel));
+    ReleaseDTO mergedRelease = namespaceBranchService.merge(appId, Env.valueOf(env.toUpperCase()),
+        clusterName, namespaceName, branchName, model.getReleaseTitle(), model.getReleaseComment(),
+        model.isEmergencyPublish(), deleteBranch, model.getReleasedBy());
+
+    ConfigPublishEvent event = ConfigPublishEvent.instance();
+    event.withAppId(appId).withCluster(clusterName).withNamespace(namespaceName)
+        .withReleaseId(mergedRelease.getId()).setMergeEvent(true).setEnv(Env.valueOf(env));
+    publisher.publishEvent(event);
+
+    return OpenApiBeanUtils.transformFromReleaseDTO(mergedRelease);
+  }
+
+  @PreAuthorize(value = "@consumerPermissionValidator.hasReleaseNamespacePermission(#request, #appId, #namespaceName, #env)")
+  @PostMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/releases")
+  public OpenReleaseDTO createGrayRelease(@PathVariable String appId, @PathVariable String env,
+      @PathVariable String clusterName, @PathVariable String namespaceName,
+      @PathVariable String branchName, @RequestBody NamespaceReleaseDTO model,
+      HttpServletRequest request) {
+    RequestPrecondition.checkArguments(
+        !StringUtils.isContainEmpty(model.getReleasedBy(), model.getReleaseTitle()),
+        "Params(releaseTitle and releasedBy) can not be empty");
+
+    if (userService.findByUserId(model.getReleasedBy()) == null) {
+      throw BadRequestException.userNotExists(model.getReleasedBy());
     }
 
-    @PreAuthorize(value = "@consumerPermissionValidator.hasReleaseNamespacePermission(#request, #appId, #namespaceName, #env)")
-    @PostMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/gray-del-releases")
-    public OpenReleaseDTO createGrayDelRelease(@PathVariable String appId,
-                                               @PathVariable String env, @PathVariable String clusterName,
-                                               @PathVariable String namespaceName, @PathVariable String branchName,
-                                               @RequestBody NamespaceGrayDelReleaseDTO model,
-                                               HttpServletRequest request) {
-        RequestPrecondition.checkArguments(!StringUtils.isContainEmpty(model.getReleasedBy(), model
-                        .getReleaseTitle()),
-                "Params(releaseTitle and releasedBy) can not be empty");
-        RequestPrecondition.checkArguments(model.getGrayDelKeys() != null,
-                "Params(grayDelKeys) can not be null");
-
-        if (userService.findByUserId(model.getReleasedBy()) == null) {
-            throw BadRequestException.userNotExists(model.getReleasedBy());
-        }
-
-        NamespaceGrayDelReleaseModel releaseModel = BeanUtils.transform(NamespaceGrayDelReleaseModel.class, model);
-        releaseModel.setAppId(appId);
-        releaseModel.setEnv(env.toUpperCase());
-        releaseModel.setClusterName(branchName);
-        releaseModel.setNamespaceName(namespaceName);
-
-        return OpenApiBeanUtils.transformFromReleaseDTO(releaseService.publish(releaseModel, releaseModel.getReleasedBy()));
+    NamespaceReleaseModel releaseModel = BeanUtils.transform(NamespaceReleaseModel.class, model);
+
+    releaseModel.setAppId(appId);
+    releaseModel.setEnv(Env.valueOf(env).toString());
+    releaseModel.setClusterName(branchName);
+    releaseModel.setNamespaceName(namespaceName);
+
+    ReleaseDTO releaseDTO = releaseService.publish(releaseModel);
+
+    ConfigPublishEvent event = ConfigPublishEvent.instance();
+    event.withAppId(appId).withCluster(clusterName).withNamespace(namespaceName)
+        .withReleaseId(releaseDTO.getId()).setGrayPublishEvent(true).setEnv(Env.valueOf(env));
+    publisher.publishEvent(event);
+
+    return OpenApiBeanUtils.transformFromReleaseDTO(releaseDTO);
+  }
+
+  @PreAuthorize(value = "@consumerPermissionValidator.hasReleaseNamespacePermission(#request, #appId, #namespaceName, #env)")
+  @PostMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/gray-del-releases")
+  public OpenReleaseDTO createGrayDelRelease(@PathVariable String appId, @PathVariable String env,
+      @PathVariable String clusterName, @PathVariable String namespaceName,
+      @PathVariable String branchName, @RequestBody NamespaceGrayDelReleaseDTO model,
+      HttpServletRequest request) {
+    RequestPrecondition.checkArguments(
+        !StringUtils.isContainEmpty(model.getReleasedBy(), model.getReleaseTitle()),
+        "Params(releaseTitle and releasedBy) can not be empty");
+    RequestPrecondition.checkArguments(model.getGrayDelKeys() != null,
+        "Params(grayDelKeys) can not be null");
+
+    if (userService.findByUserId(model.getReleasedBy()) == null) {
+      throw BadRequestException.userNotExists(model.getReleasedBy());
     }
 
+    NamespaceGrayDelReleaseModel releaseModel = BeanUtils.transform(
+        NamespaceGrayDelReleaseModel.class, model);
+    releaseModel.setAppId(appId);
+    releaseModel.setEnv(env.toUpperCase());
+    releaseModel.setClusterName(branchName);
+    releaseModel.setNamespaceName(namespaceName);
+
+    return OpenApiBeanUtils.transformFromReleaseDTO(
+        releaseService.publish(releaseModel, releaseModel.getReleasedBy()));
+  }
+
   @PutMapping(path = "/releases/{releaseId}/rollback")
   public void rollback(@PathVariable String env,
       @PathVariable long releaseId, @RequestParam String operator, HttpServletRequest request) {
@@ -185,6 +215,16 @@ public class ReleaseController {
       throw new AccessDeniedException("Forbidden operation. you don't have release permission");
     }
 
+    ConfigPublishEvent event = ConfigPublishEvent.instance();
+    event.withAppId(release.getAppId())
+        .withCluster(release.getClusterName())
+        .withNamespace(release.getNamespaceName())
+        .withReleaseId(release.getId())
+        .setRollbackEvent(true)
+        .setEnv(Env.valueOf(env));
+    publisher.publishEvent(event);
+
+
     this.releaseOpenApiService.rollbackRelease(env, releaseId, operator);
   }
 

+ 0 - 1
changes/changes-2.4.0.md

@@ -9,6 +9,5 @@ Apollo 2.4.0
 * [Feature support portal restTemplate Client connection pool config](https://github.com/apolloconfig/apollo/pull/5200)
 * [Feature added the ability for administrators to globally search for Value](https://github.com/apolloconfig/apollo/pull/5182)
 * [Feature support the observe status access-key for pre-check and logging only](https://github.com/apolloconfig/apollo/pull/5236)
-
 ------------------
 All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1)